aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Fung <stevefung@google.com>2016-04-19 17:04:06 -0700
committerTreehugger Robot <treehugger-gerrit@google.com>2016-04-20 21:30:16 +0000
commitbb621f2078fb37008f80c61c460daa6f42915812 (patch)
tree8a7939f1d038f89829524eb3a1842b28c7954986
parent6d8c0d1759e35c654eef64c5e88955e1ab925073 (diff)
downloadbdk-bb621f2078fb37008f80c61c460daa6f42915812.tar.gz
Convert project/ to 4 space indent
Convert all files in the project/ folder to a 4 space indent to comply with PEP8 style rules. Bug: 28007659 Test: `python test_runner.py` passes. Test: pylint passes. Change-Id: Ie558e256e0c343d4786f5fd7f18a671446e79c41
-rw-r--r--cli/lib/project/acl.py383
-rw-r--r--cli/lib/project/acl_unittest.py190
-rw-r--r--cli/lib/project/collection.py62
-rw-r--r--cli/lib/project/common.py94
-rw-r--r--cli/lib/project/config.py198
-rw-r--r--cli/lib/project/config_stub.py6
-rw-r--r--cli/lib/project/config_unittest.py196
-rw-r--r--cli/lib/project/dependency.py70
-rw-r--r--cli/lib/project/loader.py186
-rw-r--r--cli/lib/project/pack.py791
-rw-r--r--cli/lib/project/pack_unittest.py471
-rw-r--r--cli/lib/project/packmap.py351
-rw-r--r--cli/lib/project/packmap_stub.py16
-rw-r--r--cli/lib/project/packs.py169
-rw-r--r--cli/lib/project/project_spec.py345
-rw-r--r--cli/lib/project/project_spec_stub.py58
-rw-r--r--cli/lib/project/project_spec_unittest.py389
-rw-r--r--cli/lib/project/target.py299
-rw-r--r--cli/lib/project/target_stub.py82
-rw-r--r--cli/lib/project/targets.py95
-rw-r--r--cli/lib/project/xml_parser.py133
21 files changed, 2310 insertions, 2274 deletions
diff --git a/cli/lib/project/acl.py b/cli/lib/project/acl.py
index 173f523..cb0f44c 100644
--- a/cli/lib/project/acl.py
+++ b/cli/lib/project/acl.py
@@ -27,194 +27,195 @@ from selinux import file_context
class FileAcl(object):
- DEFAULTS = {
- 'USER': '1000',
- 'GROUP': '1000',
- 'SELABEL': '',
- 'PERMISSIONS': '0400',
- 'CAPABILITIES': '0'
- }
-
- def __init__(self, copy):
- self._origin = common.Origin()
- self._copy = copy
- # For now, we default to system.
- self._user = self.DEFAULTS['USER']
- self._group = self.DEFAULTS['GROUP']
- self._selabel = self.DEFAULTS['SELABEL']
- self._perms = self.DEFAULTS['PERMISSIONS']
- self._fcaps = self.DEFAULTS['CAPABILITIES']
- # For packs which wrap a Android build, we will often
- # want to just use the build systems default values.
- # When this is False, fs_config and file_context will
- # return empty.
- self._override_build = True
- # TODO(wad): add more types (b/27848879).
- self._file_type = file_context.FILE_TYPE_FILE
- default_acl = None
- if (copy is not None and
- copy.pack is not None and
- copy.pack.defaults is not None):
- default_acl = copy.pack.defaults.acl
- if default_acl is not None:
- self._user = default_acl.user
- self._group = default_acl.group
- self._selabel = default_acl.selabel
- self._perms = default_acl.perms
- self._fcaps = default_acl.fcaps
-
- def load(self, ele):
- """Loads the FileAcl from a XML element node (set-acl)."""
- self._origin = ele.origin.copy()
- ele.limit_attribs(['user', 'group', 'selabel', 'perms', 'fcaps'])
- # Since all attributes are optional, we can just pull from the
- # element attrib.
- for key in ele.attrib:
- if ele.attrib[key] not in (None, ''):
- self.__dict__['_{}'.format(key)] = ele.attrib[key]
- if self._perms is not None:
- if not re.match('^0[0-7][0-7][0-7]$', self._perms):
- raise common.LoadErrorWithOrigin(
- self._origin,
- '@perms must match ^0[0-7][0-7][0-7]$: {}'.format(self._perms))
- # TODO(wad) Add fcaps content validation.
- if self._fcaps != self.DEFAULTS['CAPABILITIES']:
- raise common.LoadErrorWithOrigin(self._origin,
- '@fcaps support is not yet implemented.')
-
- @property
- def override_build(self):
- return self._override_build
-
- @override_build.setter
- def override_build(self, build_def):
- self._override_build = build_def
-
- @property
- def user(self):
- return self._user
-
- @user.setter
- def user(self, user):
- # TODO(wad) http://b/27564772
- try:
- self._user = int(user)
- except TypeError:
- raise ValueError('user must be numeric: {}'.format(user))
-
- @property
- def group(self):
- return self._group
-
- @group.setter
- def group(self, group):
- # TODO(wad) http://b/27564772
- try:
- self._group = int(group)
- except TypeError:
- raise ValueError('group must be numeric: {}'.format(group))
-
- @property
- def perms(self):
- return self._perms
-
- @perms.setter
- def perms(self, perms):
- try:
- self._perms = oct(int(perms, 8))
- except TypeError:
- raise ValueError('perms must be octal: {}'.format(perms))
-
- @property
- def selabel(self):
- return self._selabel
-
- @selabel.setter
- def selabel(self, selabel):
- self._selabel = selabel
-
- @property
- def fcaps(self):
- return self._fcaps
-
- @fcaps.setter
- def fcaps(self, fcaps):
- self._fcaps = fcaps
-
- @property
- def copy(self):
- return self._copy
-
- @copy.setter
- def copy(self, cpy):
- self._copy = cpy
-
- @property
- def file_type(self):
- return self._file_type
-
- @file_type.setter
- def file_type(self, ftype):
- if ftype not in file_context.FILE_TYPE.keys():
- raise ValueError('ftype must be one of {}: {}'.format(
- file_context.FILE_TYPE.keys(), ftype))
- self._file_type = ftype
-
- def file_context(self, path=None, ftype=None):
- """Builds a ASCII SELinux file context entry for the given ACL.
-
- If the ACL is applied to a GLOB, then path and ftype
- should be applied or the glob will be written into the
- file_context.
- """
- if self.override_build == False:
- return ''
- path = path or self._copy.dst
- if not os.path.isabs(path):
- path = os.path.sep + path
- ftype = ftype or file_context.FILE_TYPE[self._file_type]
- return '{} {} {}'.format(path, ftype, self._selabel)
-
- def fs_config(self, path=None, binary=False):
- """Returns the fs_config entry in ASCII or binary format.
-
- Args:
- path: Optionally override the copy destination.
- binary: If True, returns the binary fs_config format.
- If False, returns the ASCII fs_config format.
-
- Note for callers: binary fs_config files will be evaluated
- in first match order so it is important to use more specific
- paths first.
- """
- if self.override_build == False:
- return ''
- path = path or self._copy.dst
- if path.startswith(os.path.sep):
- path = path.lstrip('/')
- if binary:
- # https://android.googlesource.com/platform/system/core/+/master/libcutils/fs_config.c#45
- # struct fs_path_config_from_file {
- # uint16_t length; /* header plus prefix */
- # uint16_t mode;
- # uint16_t uid;
- # uint16_t gid
- # uint64_t capabilities;
- # char prefix[];
- # } __attribute__((__aligned__(sizeof(uint64_t))));
- path_len = len(path) + 1 # Terminating NUL is required.
- total_len = 16 + path_len
- # The on-disk format is little endian byte order.
- entry = struct.pack(
- '<HHHHQ{}s'.format(path_len), total_len,
- int(self._perms, 8), int(self._user),
- int(self._group), int(self._fcaps), path)
- return entry
- return '{} {} {} {} capabilities={}'.format(
- path, self._user, self._group, self._perms, self._fcaps)
-
- def __repr__(self):
- return ('<set-acl user="{}" group="{}" selabel="{}" '
- 'perms="{}" fcaps="{}"/>'.format(
- self._user or '', self._group or '', self._selabel or '',
- self._perms or '', self._fcaps or ''))
+ DEFAULTS = {
+ 'USER': '1000',
+ 'GROUP': '1000',
+ 'SELABEL': '',
+ 'PERMISSIONS': '0400',
+ 'CAPABILITIES': '0'
+ }
+
+ def __init__(self, copy):
+ self._origin = common.Origin()
+ self._copy = copy
+ # For now, we default to system.
+ self._user = self.DEFAULTS['USER']
+ self._group = self.DEFAULTS['GROUP']
+ self._selabel = self.DEFAULTS['SELABEL']
+ self._perms = self.DEFAULTS['PERMISSIONS']
+ self._fcaps = self.DEFAULTS['CAPABILITIES']
+ # For packs which wrap a Android build, we will often
+ # want to just use the build systems default values.
+ # When this is False, fs_config and file_context will
+ # return empty.
+ self._override_build = True
+ # TODO(wad): add more types (b/27848879).
+ self._file_type = file_context.FILE_TYPE_FILE
+ default_acl = None
+ if (copy is not None and
+ copy.pack is not None and
+ copy.pack.defaults is not None):
+ default_acl = copy.pack.defaults.acl
+ if default_acl is not None:
+ self._user = default_acl.user
+ self._group = default_acl.group
+ self._selabel = default_acl.selabel
+ self._perms = default_acl.perms
+ self._fcaps = default_acl.fcaps
+
+ def load(self, ele):
+ """Loads the FileAcl from a XML element node (set-acl)."""
+ self._origin = ele.origin.copy()
+ ele.limit_attribs(['user', 'group', 'selabel', 'perms', 'fcaps'])
+ # Since all attributes are optional, we can just pull from the
+ # element attrib.
+ for key in ele.attrib:
+ if ele.attrib[key] not in (None, ''):
+ self.__dict__['_{}'.format(key)] = ele.attrib[key]
+ if self._perms is not None:
+ if not re.match('^0[0-7][0-7][0-7]$', self._perms):
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '@perms must match ^0[0-7][0-7][0-7]$: {}'.format(
+ self._perms))
+ # TODO(wad) Add fcaps content validation.
+ if self._fcaps != self.DEFAULTS['CAPABILITIES']:
+ raise common.LoadErrorWithOrigin(
+ self._origin, '@fcaps support is not yet implemented.')
+
+ @property
+ def override_build(self):
+ return self._override_build
+
+ @override_build.setter
+ def override_build(self, build_def):
+ self._override_build = build_def
+
+ @property
+ def user(self):
+ return self._user
+
+ @user.setter
+ def user(self, user):
+ # TODO(wad) http://b/27564772
+ try:
+ self._user = int(user)
+ except TypeError:
+ raise ValueError('user must be numeric: {}'.format(user))
+
+ @property
+ def group(self):
+ return self._group
+
+ @group.setter
+ def group(self, group):
+ # TODO(wad) http://b/27564772
+ try:
+ self._group = int(group)
+ except TypeError:
+ raise ValueError('group must be numeric: {}'.format(group))
+
+ @property
+ def perms(self):
+ return self._perms
+
+ @perms.setter
+ def perms(self, perms):
+ try:
+ self._perms = oct(int(perms, 8))
+ except TypeError:
+ raise ValueError('perms must be octal: {}'.format(perms))
+
+ @property
+ def selabel(self):
+ return self._selabel
+
+ @selabel.setter
+ def selabel(self, selabel):
+ self._selabel = selabel
+
+ @property
+ def fcaps(self):
+ return self._fcaps
+
+ @fcaps.setter
+ def fcaps(self, fcaps):
+ self._fcaps = fcaps
+
+ @property
+ def copy(self):
+ return self._copy
+
+ @copy.setter
+ def copy(self, cpy):
+ self._copy = cpy
+
+ @property
+ def file_type(self):
+ return self._file_type
+
+ @file_type.setter
+ def file_type(self, ftype):
+ if ftype not in file_context.FILE_TYPE.keys():
+ raise ValueError('ftype must be one of {}: {}'.format(
+ file_context.FILE_TYPE.keys(), ftype))
+ self._file_type = ftype
+
+ def file_context(self, path=None, ftype=None):
+ """Builds a ASCII SELinux file context entry for the given ACL.
+
+ If the ACL is applied to a GLOB, then path and ftype
+ should be applied or the glob will be written into the
+ file_context.
+ """
+ if self.override_build == False:
+ return ''
+ path = path or self._copy.dst
+ if not os.path.isabs(path):
+ path = os.path.sep + path
+ ftype = ftype or file_context.FILE_TYPE[self._file_type]
+ return '{} {} {}'.format(path, ftype, self._selabel)
+
+ def fs_config(self, path=None, binary=False):
+ """Returns the fs_config entry in ASCII or binary format.
+
+ Args:
+ path: Optionally override the copy destination.
+ binary: If True, returns the binary fs_config format.
+ If False, returns the ASCII fs_config format.
+
+ Note for callers: binary fs_config files will be evaluated
+ in first match order so it is important to use more specific
+ paths first.
+ """
+ if self.override_build == False:
+ return ''
+ path = path or self._copy.dst
+ if path.startswith(os.path.sep):
+ path = path.lstrip('/')
+ if binary:
+ # https://android.googlesource.com/platform/system/core/+/master/libcutils/fs_config.c#45
+ # struct fs_path_config_from_file {
+ # uint16_t length; /* header plus prefix */
+ # uint16_t mode;
+ # uint16_t uid;
+ # uint16_t gid
+ # uint64_t capabilities;
+ # char prefix[];
+ # } __attribute__((__aligned__(sizeof(uint64_t))));
+ path_len = len(path) + 1 # Terminating NUL is required.
+ total_len = 16 + path_len
+ # The on-disk format is little endian byte order.
+ entry = struct.pack(
+ '<HHHHQ{}s'.format(path_len), total_len,
+ int(self._perms, 8), int(self._user),
+ int(self._group), int(self._fcaps), path)
+ return entry
+ return '{} {} {} {} capabilities={}'.format(
+ path, self._user, self._group, self._perms, self._fcaps)
+
+ def __repr__(self):
+ return ('<set-acl user="{}" group="{}" selabel="{}" '
+ 'perms="{}" fcaps="{}"/>'.format(
+ self._user or '', self._group or '', self._selabel or '',
+ self._perms or '', self._fcaps or ''))
diff --git a/cli/lib/project/acl_unittest.py b/cli/lib/project/acl_unittest.py
index f570d07..3a3498b 100644
--- a/cli/lib/project/acl_unittest.py
+++ b/cli/lib/project/acl_unittest.py
@@ -28,98 +28,98 @@ from project.pack import Copy
class FileAclTest(unittest.TestCase):
- USER = '1000'
- GROUP = '1001'
- FCAPS = '0'
- SELABEL = 'u:object_r:unlabeled:s0'
- PERMS = '0600'
- XML = '<set-acl perms="{}" user="{}" group="{}" fcaps="{}" selabel="{}"/>'
-
- def setUp(self):
- # Setup a common ACL when needed.
- self.acl = FileAcl(None)
- xml = self.XML.format(
- self.PERMS, self.USER, self.GROUP, self.FCAPS, self.SELABEL)
- node = self.node_from_str(xml)
- self.acl.load(node)
-
- def tearDown(self):
- pass
-
- def node_from_str(self, s):
- xml = StringIO.StringIO(s)
- tree = xml_parser.parse(xml)
- return tree.getroot()
-
-
- def test_valid_perms(self):
- xml = ('<set-acl perms="0600"/>')
- node = self.node_from_str(xml)
- acl = FileAcl(None)
- acl.load(node)
- self.assertIsInstance(acl, FileAcl)
- self.assertEqual(acl.perms, '0600')
-
- def test_invalid_perms(self):
- xml = ('<set-acl perms="0609"/>')
- node = self.node_from_str(xml)
- acl = FileAcl(None)
- with self.assertRaises(common.LoadErrorWithOrigin):
- acl.load(node)
-
- def test_valid_attrs(self):
- self.assertIsInstance(self.acl, FileAcl)
- self.assertEqual(self.acl.perms, self.PERMS)
- self.assertEqual(self.acl.user, self.USER)
- self.assertEqual(self.acl.group, self.GROUP)
- self.assertEqual(self.acl.fcaps, self.FCAPS)
- self.assertEqual(self.acl.selabel, self.SELABEL)
-
- def test_invalid_attrs(self):
- xml = ('<set-acl perms="0600" user="1000" group="123" fcaps="0"'
- ' custom="value" selabel="u:object_r:unlabeled:s0"/>')
- node = self.node_from_str(xml)
- acl = FileAcl(None)
- with self.assertRaises(common.LoadErrorWithOrigin):
- acl.load(node)
-
- def test_fs_config_ascii(self):
- FS_CONFIG_FMT = '{} {} {} {} capabilities={}'
- self.acl.copy = Copy(None, dst='/bin/helper')
- entry = self.acl.fs_config(binary=False)
- expected_entry = FS_CONFIG_FMT.format(
- 'bin/helper', self.USER, self.GROUP, self.PERMS, self.FCAPS)
- self.assertEqual(entry, expected_entry)
-
- def test_file_context_ascii(self):
- FS_CONFIG_FMT = '{} {} {} {} capabilities={}'
- self.acl.copy = Copy(None, dst='/bin/helper')
- entry = self.acl.fs_config(binary=False)
- expected_entry = FS_CONFIG_FMT.format(
- 'bin/helper', self.USER, self.GROUP, self.PERMS, self.FCAPS)
- self.assertEqual(entry, expected_entry)
-
- def test_fs_config_binary(self):
- FS_CONFIG_PACK = 'HHHHQ'
- self.acl.copy = Copy(None, dst='/bin/helper')
- entry = self.acl.fs_config(binary=True)
- header = struct.unpack(FS_CONFIG_PACK, entry[:16])
- path = entry[16:]
- self.assertEqual(16+len(path), header[0])
- self.assertEqual(int(self.PERMS, 8), header[1])
- self.assertEqual(int(self.USER), header[2])
- self.assertEqual(int(self.GROUP), header[3])
- self.assertEqual(int(self.FCAPS), header[4])
- self.assertEqual(path, 'bin/helper\x00')
-
- def test_override_build_use(self):
- self.acl.copy = Copy(None, dst='/bin/helper')
- # Default is True.
- self.assertNotEqual(self.acl.fs_config(), '')
- self.assertNotEqual(self.acl.file_context(), '')
- self.acl.override_build = True
- self.assertNotEqual(self.acl.fs_config(), '')
- self.assertNotEqual(self.acl.file_context(), '')
- self.acl.override_build = False
- self.assertEqual(self.acl.fs_config(), '')
- self.assertEqual(self.acl.file_context(), '')
+ USER = '1000'
+ GROUP = '1001'
+ FCAPS = '0'
+ SELABEL = 'u:object_r:unlabeled:s0'
+ PERMS = '0600'
+ XML = '<set-acl perms="{}" user="{}" group="{}" fcaps="{}" selabel="{}"/>'
+
+ def setUp(self):
+ # Setup a common ACL when needed.
+ self.acl = FileAcl(None)
+ xml = self.XML.format(
+ self.PERMS, self.USER, self.GROUP, self.FCAPS, self.SELABEL)
+ node = self.node_from_str(xml)
+ self.acl.load(node)
+
+ def tearDown(self):
+ pass
+
+ def node_from_str(self, s):
+ xml = StringIO.StringIO(s)
+ tree = xml_parser.parse(xml)
+ return tree.getroot()
+
+
+ def test_valid_perms(self):
+ xml = ('<set-acl perms="0600"/>')
+ node = self.node_from_str(xml)
+ acl = FileAcl(None)
+ acl.load(node)
+ self.assertIsInstance(acl, FileAcl)
+ self.assertEqual(acl.perms, '0600')
+
+ def test_invalid_perms(self):
+ xml = ('<set-acl perms="0609"/>')
+ node = self.node_from_str(xml)
+ acl = FileAcl(None)
+ with self.assertRaises(common.LoadErrorWithOrigin):
+ acl.load(node)
+
+ def test_valid_attrs(self):
+ self.assertIsInstance(self.acl, FileAcl)
+ self.assertEqual(self.acl.perms, self.PERMS)
+ self.assertEqual(self.acl.user, self.USER)
+ self.assertEqual(self.acl.group, self.GROUP)
+ self.assertEqual(self.acl.fcaps, self.FCAPS)
+ self.assertEqual(self.acl.selabel, self.SELABEL)
+
+ def test_invalid_attrs(self):
+ xml = ('<set-acl perms="0600" user="1000" group="123" fcaps="0"'
+ ' custom="value" selabel="u:object_r:unlabeled:s0"/>')
+ node = self.node_from_str(xml)
+ acl = FileAcl(None)
+ with self.assertRaises(common.LoadErrorWithOrigin):
+ acl.load(node)
+
+ def test_fs_config_ascii(self):
+ FS_CONFIG_FMT = '{} {} {} {} capabilities={}'
+ self.acl.copy = Copy(None, dst='/bin/helper')
+ entry = self.acl.fs_config(binary=False)
+ expected_entry = FS_CONFIG_FMT.format(
+ 'bin/helper', self.USER, self.GROUP, self.PERMS, self.FCAPS)
+ self.assertEqual(entry, expected_entry)
+
+ def test_file_context_ascii(self):
+ FS_CONFIG_FMT = '{} {} {} {} capabilities={}'
+ self.acl.copy = Copy(None, dst='/bin/helper')
+ entry = self.acl.fs_config(binary=False)
+ expected_entry = FS_CONFIG_FMT.format(
+ 'bin/helper', self.USER, self.GROUP, self.PERMS, self.FCAPS)
+ self.assertEqual(entry, expected_entry)
+
+ def test_fs_config_binary(self):
+ FS_CONFIG_PACK = 'HHHHQ'
+ self.acl.copy = Copy(None, dst='/bin/helper')
+ entry = self.acl.fs_config(binary=True)
+ header = struct.unpack(FS_CONFIG_PACK, entry[:16])
+ path = entry[16:]
+ self.assertEqual(16+len(path), header[0])
+ self.assertEqual(int(self.PERMS, 8), header[1])
+ self.assertEqual(int(self.USER), header[2])
+ self.assertEqual(int(self.GROUP), header[3])
+ self.assertEqual(int(self.FCAPS), header[4])
+ self.assertEqual(path, 'bin/helper\x00')
+
+ def test_override_build_use(self):
+ self.acl.copy = Copy(None, dst='/bin/helper')
+ # Default is True.
+ self.assertNotEqual(self.acl.fs_config(), '')
+ self.assertNotEqual(self.acl.file_context(), '')
+ self.acl.override_build = True
+ self.assertNotEqual(self.acl.fs_config(), '')
+ self.assertNotEqual(self.acl.file_context(), '')
+ self.acl.override_build = False
+ self.assertEqual(self.acl.fs_config(), '')
+ self.assertEqual(self.acl.file_context(), '')
diff --git a/cli/lib/project/collection.py b/cli/lib/project/collection.py
index 7bc524d..cf303f9 100644
--- a/cli/lib/project/collection.py
+++ b/cli/lib/project/collection.py
@@ -19,34 +19,34 @@
class Base(object):
- def __init__(self, tag):
- self._tag = tag
- self._entries = {}
- self._origins = []
-
- def __iter__(self):
- """Enable for entries in collection semantics."""
- return self._entries.values().__iter__()
-
- @property
- def entries(self):
- """Returns a dict of name to objects."""
- return self._entries
-
- @entries.setter
- def entries(self, entries):
- self._entries = entries
-
- @property
- def origins(self):
- """Return the list of Origin objects parsed for this Packs."""
- return self._origins
-
- @origins.setter
- def origins(self, origins):
- self._origins = origins
-
- def __repr__(self):
- return '<{}>{}</{}>'.format(
- self._tag, ''.join([str(e) for e in self._entries.values()]),
- self._tag)
+ def __init__(self, tag):
+ self._tag = tag
+ self._entries = {}
+ self._origins = []
+
+ def __iter__(self):
+ """Enable for entries in collection semantics."""
+ return self._entries.values().__iter__()
+
+ @property
+ def entries(self):
+ """Returns a dict of name to objects."""
+ return self._entries
+
+ @entries.setter
+ def entries(self, entries):
+ self._entries = entries
+
+ @property
+ def origins(self):
+ """Return the list of Origin objects parsed for this Packs."""
+ return self._origins
+
+ @origins.setter
+ def origins(self, origins):
+ self._origins = origins
+
+ def __repr__(self):
+ return '<{}>{}</{}>'.format(
+ self._tag, ''.join([str(e) for e in self._entries.values()]),
+ self._tag)
diff --git a/cli/lib/project/common.py b/cli/lib/project/common.py
index cefb323..40b78fc 100644
--- a/cli/lib/project/common.py
+++ b/cli/lib/project/common.py
@@ -28,75 +28,75 @@ pathsep = '/'
# source files.
# TODO(wad,dpursell): Sort out how absolute paths work in this world.
def path_to_host(path):
- return path.replace(os.sep, pathsep)
+ return path.replace(os.sep, pathsep)
def path_join(*args):
- """Naive path join to use the module-local pathsep."""
- path = ''
- for c in args:
- if (path is not '' and
- not path.endswith(pathsep) and
- not c.startswith(pathsep)):
- path += pathsep
- path += c
- return path
+ """Naive path join to use the module-local pathsep."""
+ path = ''
+ for c in args:
+ if (path is not '' and
+ not path.endswith(pathsep) and
+ not c.startswith(pathsep)):
+ path += pathsep
+ path += c
+ return path
def basename(path):
- return path.split(pathsep)[-1]
+ return path.split(pathsep)[-1]
class Origin(object):
- """Used to track the origin location of XML tags."""
- def __init__(self, f='<unknown>', line='-', col='-'):
- self.source_file = f
- self.line_number = line
- self.column_number = col
-
- def __eq__(self, other):
- return (
- isinstance(other, self.__class__) and
- other.source_file == self.source_file and
- other.line_number == self.line_number and
- other.column_number == self.column_number
- )
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return '{}:{}:{}'.format(
- self.source_file, self.line_number, self.column_number)
-
- def copy(self):
- """Provide a dedicated helper for copying to encourage
- deep copying to avoid keeping references to XML tree
- data.
- """
- return copy.deepcopy(self)
+ """Used to track the origin location of XML tags."""
+ def __init__(self, f='<unknown>', line='-', col='-'):
+ self.source_file = f
+ self.line_number = line
+ self.column_number = col
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, self.__class__) and
+ other.source_file == self.source_file and
+ other.line_number == self.line_number and
+ other.column_number == self.column_number
+ )
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return '{}:{}:{}'.format(
+ self.source_file, self.line_number, self.column_number)
+
+ def copy(self):
+ """Provide a dedicated helper for copying to encourage
+ deep copying to avoid keeping references to XML tree
+ data.
+ """
+ return copy.deepcopy(self)
class Error(error.Error):
- pass
+ pass
class LoadError(Error):
- pass
+ pass
class LoadErrorWithOrigin(LoadError):
- def __init__(self, origin, message):
- # Prefix where the error occurred at.
- message = '{}: {}'.format(origin, message)
- super(LoadErrorWithOrigin, self).__init__(message)
+ def __init__(self, origin, message):
+ # Prefix where the error occurred at.
+ message = '{}: {}'.format(origin, message)
+ super(LoadErrorWithOrigin, self).__init__(message)
class UnknownAttributes(LoadErrorWithOrigin):
- pass
+ pass
class MissingAttribute(LoadErrorWithOrigin):
- pass
+ pass
class PathConflictError(LoadErrorWithOrigin):
- pass
+ pass
diff --git a/cli/lib/project/config.py b/cli/lib/project/config.py
index 838a911..480d9e6 100644
--- a/cli/lib/project/config.py
+++ b/cli/lib/project/config.py
@@ -24,103 +24,103 @@ from project import common
class Config(object):
- """Config represents project-specific runtime configuration.
-
- When a given Project is being built, the supporting tools
- will have configuration that relates more to the host environment
- and less to the creating project artifacts. These may be
- output and cache directories or the defaults for commandline
- tool behavior.
- """
- def __init__(self, base_path=os.getcwd()):
- self._version = 1
- self._cache_dir = 'bdk.cache'
- self._output_dir = 'bdk.out'
- # Fake up an origin using the current directory or a passed
- # in base path.
- self._origin = common.Origin()
- self._origin.source_file = os.path.join(base_path, 'placeholder')
- self._default_target = ''
-
- def _abs_dir(self, p):
- if os.path.isabs(p):
- return p
- # Automatically absolutize relative paths against the origin.
- return os.path.join(os.path.dirname(self._origin.source_file), p)
-
- @property
- def cache_dir(self):
- """Returns the absolute path dir."""
- return self._abs_dir(self._cache_dir)
-
- @cache_dir.setter
- def cache_dir(self, path):
- self._cache_dir = path
-
- @property
- def output_dir(self):
- """Returns the absolute path dir."""
- return self._abs_dir(self._output_dir)
-
- @output_dir.setter
- def output_dir(self, path):
- self._output_dir = path
-
- @property
- def default_target(self):
- return self._default_target
-
- @default_target.setter
- def default_target(self, target_name):
- self._default_target = target_name
-
- @property
- def origin(self):
- return self._origin
-
- @origin.setter
- def origin(self, o):
- self._origin = o.copy()
-
- def __repr__(self):
- return ('<config>'
- '<output-dir path="{}"/>'
- '<cache-dir path="{}"/>'
- '<default target="{}"/></config>').format(
- self._output_dir, self._cache_dir, self._default_target)
-
- @classmethod
- def from_element(cls, node):
- """Creates a new Config from the @node.
-
- Args:
- node: The ElementTree node a <config> element.
-
- Returns:
- instance of Config
-
- Raises:
- common.LoadErrorWithOrigin
+ """Config represents project-specific runtime configuration.
+
+ When a given Project is being built, the supporting tools
+ will have configuration that relates more to the host environment
+ and less to the creating project artifacts. These may be
+ output and cache directories or the defaults for commandline
+ tool behavior.
"""
- config = cls()
- config.origin = node.origin
- node.limit_attribs([])
- # Ensure we have a project document.
- if node.tag != 'config':
- raise common.LoadErrorWithOrigin(node.origin,
- 'node not <config>')
-
- # Parse the dirs
- for tag in ('output-dir', 'cache-dir'):
- for ele in node.findall(tag):
- ele.limit_attribs(['path'])
- if 'path' in ele.attrib:
- config.__dict__['_' + tag.replace('-', '_')] = ele.get_attrib('path')
-
- # Grab the default element.
- for ele in node.findall('default'):
- ele.limit_attribs(['target'])
- if 'target' in ele.attrib:
- config.default_target = ele.get_attrib('target')
-
- return config
+ def __init__(self, base_path=os.getcwd()):
+ self._version = 1
+ self._cache_dir = 'bdk.cache'
+ self._output_dir = 'bdk.out'
+ # Fake up an origin using the current directory or a passed
+ # in base path.
+ self._origin = common.Origin()
+ self._origin.source_file = os.path.join(base_path, 'placeholder')
+ self._default_target = ''
+
+ def _abs_dir(self, p):
+ if os.path.isabs(p):
+ return p
+ # Automatically absolutize relative paths against the origin.
+ return os.path.join(os.path.dirname(self._origin.source_file), p)
+
+ @property
+ def cache_dir(self):
+ """Returns the absolute path dir."""
+ return self._abs_dir(self._cache_dir)
+
+ @cache_dir.setter
+ def cache_dir(self, path):
+ self._cache_dir = path
+
+ @property
+ def output_dir(self):
+ """Returns the absolute path dir."""
+ return self._abs_dir(self._output_dir)
+
+ @output_dir.setter
+ def output_dir(self, path):
+ self._output_dir = path
+
+ @property
+ def default_target(self):
+ return self._default_target
+
+ @default_target.setter
+ def default_target(self, target_name):
+ self._default_target = target_name
+
+ @property
+ def origin(self):
+ return self._origin
+
+ @origin.setter
+ def origin(self, o):
+ self._origin = o.copy()
+
+ def __repr__(self):
+ return ('<config>'
+ '<output-dir path="{}"/>'
+ '<cache-dir path="{}"/>'
+ '<default target="{}"/></config>').format(
+ self._output_dir, self._cache_dir, self._default_target)
+
+ @classmethod
+ def from_element(cls, node):
+ """Creates a new Config from the @node.
+
+ Args:
+ node: The ElementTree node a <config> element.
+
+ Returns:
+ instance of Config
+
+ Raises:
+ common.LoadErrorWithOrigin
+ """
+ config = cls()
+ config.origin = node.origin
+ node.limit_attribs([])
+ # Ensure we have a project document.
+ if node.tag != 'config':
+ raise common.LoadErrorWithOrigin(node.origin, 'node not <config>')
+
+ # Parse the dirs
+ for tag in ('output-dir', 'cache-dir'):
+ for ele in node.findall(tag):
+ ele.limit_attribs(['path'])
+ if 'path' in ele.attrib:
+ config.__dict__['_' + tag.replace('-', '_')] = (
+ ele.get_attrib('path'))
+
+ # Grab the default element.
+ for ele in node.findall('default'):
+ ele.limit_attribs(['target'])
+ if 'target' in ele.attrib:
+ config.default_target = ele.get_attrib('target')
+
+ return config
diff --git a/cli/lib/project/config_stub.py b/cli/lib/project/config_stub.py
index b803725..ce2dc22 100644
--- a/cli/lib/project/config_stub.py
+++ b/cli/lib/project/config_stub.py
@@ -20,6 +20,6 @@
class StubConfig(object):
- def __init__(self, default_target='', origin='test_config_origin'):
- self.default_target = default_target
- self.origin = origin
+ def __init__(self, default_target='', origin='test_config_origin'):
+ self.default_target = default_target
+ self.origin = origin
diff --git a/cli/lib/project/config_unittest.py b/cli/lib/project/config_unittest.py
index ff0f4c1..6d8eb0c 100644
--- a/cli/lib/project/config_unittest.py
+++ b/cli/lib/project/config_unittest.py
@@ -28,101 +28,101 @@ from project.config import Config
class ConfigTest(unittest.TestCase):
- def config_from_str(self, s):
- xml = StringIO.StringIO(s)
- tree = xml_parser.parse(xml)
- config = Config.from_element(tree.getroot())
- return config
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_relative_made_absolute(self):
- config = Config()
- self.assertEqual(os.path.join(os.getcwd(), 'bdk.cache'),
- config.cache_dir)
- origin = common.Origin()
- origin.source_file = os.path.join('some', 'path', 'file.xml')
- config.origin = origin
- self.assertEqual(os.path.join('some', 'path', 'bdk.out'),
- config.output_dir)
- path = 'proj.cache'
- xml_s = '<config><cache-dir path="{}"/></config>'.format(path)
- config = self.config_from_str(xml_s)
- self.assertEqual(path, config.cache_dir)
-
- def test_absolute_stays_absolute(self):
- config = Config()
- path = '/foo/bar'
- config.output_dir = path
- self.assertEqual(path, config.output_dir)
- xml_s = '<config><output-dir path="{}"/></config>'.format(path)
- config = self.config_from_str(xml_s)
- self.assertEqual(path, config.output_dir)
- xml_s = '<config><cache-dir path="{}"/></config>'.format(path)
- config = self.config_from_str(xml_s)
- self.assertEqual(path, config.cache_dir)
-
- def test_default_cache(self):
- config = Config(base_path=os.sep)
- self.assertEqual(os.path.join(os.sep, 'bdk.cache'), config.cache_dir)
- xml_s = '<config><output-dir path="/some/path"/></config>'
- config = self.config_from_str(xml_s)
- # No absolutizing happens when the source_file is "<StringIO...>"
- # TODO(wad) should we default this (in Origin) to getcwd()?
- self.assertEqual('bdk.cache', config.cache_dir)
-
- def test_default_output(self):
- config = Config(base_path=os.sep)
- self.assertEqual(os.path.join(os.sep, 'bdk.out'), config.output_dir)
- xml_s = '<config></config>'
- config = self.config_from_str(xml_s)
- # No absolutizing happens when the source_file is "<StringIO...>"
- self.assertEqual('bdk.out', config.output_dir)
-
- def test_default_target_missing(self):
- config = Config()
- self.assertEqual('', config.default_target)
- xml_s = '<config/>'
- config = self.config_from_str(xml_s)
- self.assertEqual('', config.default_target)
-
- def test_default_target_supplied(self):
- config = Config()
- config.default_target = 'foo'
- self.assertEqual('foo', config.default_target)
- xml_s = '<config><default target="tgt"/></config>'
- config = self.config_from_str(xml_s)
- self.assertEqual('tgt', config.default_target)
-
- def test_config_with_attrs(self):
- xml_s = '<config some="pig"/>'
- with self.assertRaises(common.UnknownAttributes):
- self.config_from_str(xml_s)
-
- def test_config_with_no_children(self):
- xml_s = '<config/>'
- self.config_from_str(xml_s)
-
- def test_config_with_unknown_child(self):
- xml_s = '<config><random/></config>'
- # TODO(wad): currently, extra nodes are _not_ checked.
- self.config_from_str(xml_s)
-
- def test_output_dir_with_other_attr(self):
- xml_s = '<config><output-dir path="./" other="bar"/></config>'
- with self.assertRaises(common.UnknownAttributes):
- self.config_from_str(xml_s)
-
- def test_cache_dir_with_other_attr(self):
- xml_s = '<config><cache-dir path="./" other="bar"/></config>'
- with self.assertRaises(common.UnknownAttributes):
- self.config_from_str(xml_s)
-
- def test_default_with_other_attr(self):
- xml_s = '<config><default target="xyz" other="bar"/></config>'
- with self.assertRaises(common.UnknownAttributes):
- self.config_from_str(xml_s)
+ def config_from_str(self, s):
+ xml = StringIO.StringIO(s)
+ tree = xml_parser.parse(xml)
+ config = Config.from_element(tree.getroot())
+ return config
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_relative_made_absolute(self):
+ config = Config()
+ self.assertEqual(os.path.join(os.getcwd(), 'bdk.cache'),
+ config.cache_dir)
+ origin = common.Origin()
+ origin.source_file = os.path.join('some', 'path', 'file.xml')
+ config.origin = origin
+ self.assertEqual(os.path.join('some', 'path', 'bdk.out'),
+ config.output_dir)
+ path = 'proj.cache'
+ xml_s = '<config><cache-dir path="{}"/></config>'.format(path)
+ config = self.config_from_str(xml_s)
+ self.assertEqual(path, config.cache_dir)
+
+ def test_absolute_stays_absolute(self):
+ config = Config()
+ path = '/foo/bar'
+ config.output_dir = path
+ self.assertEqual(path, config.output_dir)
+ xml_s = '<config><output-dir path="{}"/></config>'.format(path)
+ config = self.config_from_str(xml_s)
+ self.assertEqual(path, config.output_dir)
+ xml_s = '<config><cache-dir path="{}"/></config>'.format(path)
+ config = self.config_from_str(xml_s)
+ self.assertEqual(path, config.cache_dir)
+
+ def test_default_cache(self):
+ config = Config(base_path=os.sep)
+ self.assertEqual(os.path.join(os.sep, 'bdk.cache'), config.cache_dir)
+ xml_s = '<config><output-dir path="/some/path"/></config>'
+ config = self.config_from_str(xml_s)
+ # No absolutizing happens when the source_file is "<StringIO...>"
+ # TODO(wad) should we default this (in Origin) to getcwd()?
+ self.assertEqual('bdk.cache', config.cache_dir)
+
+ def test_default_output(self):
+ config = Config(base_path=os.sep)
+ self.assertEqual(os.path.join(os.sep, 'bdk.out'), config.output_dir)
+ xml_s = '<config></config>'
+ config = self.config_from_str(xml_s)
+ # No absolutizing happens when the source_file is "<StringIO...>"
+ self.assertEqual('bdk.out', config.output_dir)
+
+ def test_default_target_missing(self):
+ config = Config()
+ self.assertEqual('', config.default_target)
+ xml_s = '<config/>'
+ config = self.config_from_str(xml_s)
+ self.assertEqual('', config.default_target)
+
+ def test_default_target_supplied(self):
+ config = Config()
+ config.default_target = 'foo'
+ self.assertEqual('foo', config.default_target)
+ xml_s = '<config><default target="tgt"/></config>'
+ config = self.config_from_str(xml_s)
+ self.assertEqual('tgt', config.default_target)
+
+ def test_config_with_attrs(self):
+ xml_s = '<config some="pig"/>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.config_from_str(xml_s)
+
+ def test_config_with_no_children(self):
+ xml_s = '<config/>'
+ self.config_from_str(xml_s)
+
+ def test_config_with_unknown_child(self):
+ xml_s = '<config><random/></config>'
+ # TODO(wad): currently, extra nodes are _not_ checked.
+ self.config_from_str(xml_s)
+
+ def test_output_dir_with_other_attr(self):
+ xml_s = '<config><output-dir path="./" other="bar"/></config>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.config_from_str(xml_s)
+
+ def test_cache_dir_with_other_attr(self):
+ xml_s = '<config><cache-dir path="./" other="bar"/></config>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.config_from_str(xml_s)
+
+ def test_default_with_other_attr(self):
+ xml_s = '<config><default target="xyz" other="bar"/></config>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.config_from_str(xml_s)
diff --git a/cli/lib/project/dependency.py b/cli/lib/project/dependency.py
index 36b73f5..f5115e0 100644
--- a/cli/lib/project/dependency.py
+++ b/cli/lib/project/dependency.py
@@ -19,54 +19,54 @@ from project import common
class Error(common.Error):
- """The base class for any dependency-related error."""
+ """The base class for any dependency-related error."""
class UnsatisfiedVirtualPackError(Error):
- """Raised when a dependency could be resolved globally, but isn't for
- the given dependency tree.
- """
- def __init__(self, deps):
- # TODO(wad) Sort by origin
- msg = '\n'.join([str(d) for d in deps])
- super(UnsatisfiedVirtualPackError, self).__init__(msg)
+ """Raised when a dependency could be resolved globally, but isn't for
+ the given dependency tree.
+ """
+ def __init__(self, deps):
+ # TODO(wad) Sort by origin
+ msg = '\n'.join([str(d) for d in deps])
+ super(UnsatisfiedVirtualPackError, self).__init__(msg)
class UndefinedPackError(Error):
- def __init__(self, undef):
- # TODO(wad) Sort by origin
- msg = '\n'.join([str(d) for d in undef])
- super(UndefinedPackError, self).__init__(msg)
+ def __init__(self, undef):
+ # TODO(wad) Sort by origin
+ msg = '\n'.join([str(d) for d in undef])
+ super(UndefinedPackError, self).__init__(msg)
class Dependency(object):
- pass
+ pass
class Virtual(Dependency):
- def __init__(self, name, root, required_by, candidates):
- self._name = name
- self._root = root
- self._required_by = required_by
- self._candidates = candidates
+ def __init__(self, name, root, required_by, candidates):
+ self._name = name
+ self._root = root
+ self._required_by = required_by
+ self._candidates = candidates
- def __repr__(self):
- return ('Pack(s) {} requires virtual pack "{}" is unsatisfied in '
- 'the dependency tree for pack "{}". The following packs '
- 'may satisfy the requirement: {}'.format(
- ', '.join(['"{}"'.format(r) for r in self._required_by]),
- self._name, self._root, self._candidates))
+ def __repr__(self):
+ return ('Pack(s) {} requires virtual pack "{}" is unsatisfied in '
+ 'the dependency tree for pack "{}". The following packs '
+ 'may satisfy the requirement: {}'.format(
+ ', '.join(['"{}"'.format(r) for r in self._required_by]),
+ self._name, self._root, self._candidates))
class Undefined(Dependency):
- def __init__(self, name, required_by):
- self._name = name
- self._required_by = required_by
-
- def __repr__(self):
- s = 'Undefined pack "{}" is required by '.format(self._name)
- callers = []
- for p in self._required_by:
- callers += ['pack "{}" defined at "{}"'.format(p.uid, p.origin)]
- s += '{}.'.format(', '.join(callers))
- return s
+ def __init__(self, name, required_by):
+ self._name = name
+ self._required_by = required_by
+
+ def __repr__(self):
+ s = 'Undefined pack "{}" is required by '.format(self._name)
+ callers = []
+ for p in self._required_by:
+ callers += ['pack "{}" defined at "{}"'.format(p.uid, p.origin)]
+ s += '{}.'.format(', '.join(callers))
+ return s
diff --git a/cli/lib/project/loader.py b/cli/lib/project/loader.py
index ce825bf..c82b66c 100644
--- a/cli/lib/project/loader.py
+++ b/cli/lib/project/loader.py
@@ -22,104 +22,106 @@ from project import xml_parser
class PathElementLoader(object):
- """Processes an XML element node with either a 'path' attribute
- or child nodes. All attributes in the attrib_rollover list
- will be propagated down to the final returned root node.
- """
- def __init__(self, tag, rollover_attribs):
- self._tag = tag
- self._rollover_names = rollover_attribs
- self._rollover_attribs = {}
- # List of origins for any parsed @tag
- # nodes. This is only a list because
- # @tag may load a different @tag node.
- self._origins = []
+ """Processes an XML element node with either a 'path' attribute
+ or child nodes. All attributes in the attrib_rollover list
+ will be propagated down to the final returned root node.
+ """
+ def __init__(self, tag, rollover_attribs):
+ self._tag = tag
+ self._rollover_names = rollover_attribs
+ self._rollover_attribs = {}
+ # List of origins for any parsed @tag
+ # nodes. This is only a list because
+ # @tag may load a different @tag node.
+ self._origins = []
- @property
- def origins(self):
- """Return the list of Origin objects parsed for this loader."""
- return self._origins
+ @property
+ def origins(self):
+ """Return the list of Origin objects parsed for this loader."""
+ return self._origins
- def load(self, **source):
- """Populates the instance from an XML file or element.
+ def load(self, **source):
+ """Populates the instance from an XML file or element.
- Dereferences elements that may include other files returning
- the final defined element.
+ Dereferences elements that may include other files returning
+ the final defined element.
- Args:
- **source: May have one of the following keys set:
- - 'path': The path (str) to a file to open, read, and parse.
- - 'file': A file-compatible object to read from for parsing.
- - 'element': Provides the root element node to walk.
+ Args:
+ **source: May have one of the following keys set:
+ - 'path': The path (str) to a file to open, read, and parse.
+ - 'file': A file-compatible object to read from for parsing.
+ - 'element': Provides the root element node to walk.
- Returns:
- Actual root node with overridden rollover attributes and
- a new 'old_attribs' attribute containing the most recent root node's
- original values.
+ Returns:
+ Actual root node with overridden rollover attributes and
+ a new 'old_attribs' attribute containing the most recent root node's
+ original values.
- Raises
- LoadError: LoadError, or a subclass, will be raised if an error occurs
- loading and validating content from the XML.
- """
- ALLOWED_KEYS = ('path', 'file', 'element')
- if len(source) != 1 or source.keys()[0] not in ALLOWED_KEYS:
- raise ValueError(
- 'Exactly one of {} must be supplied: {}'.format(ALLOWED_KEYS, source))
- if 'element' in source:
- return self._load_element(source['element'])
+ Raises
+ LoadError: LoadError, or a subclass, will be raised if an error
+ occurs loading and validating content from the XML.
+ """
+ ALLOWED_KEYS = ('path', 'file', 'element')
+ if len(source) != 1 or source.keys()[0] not in ALLOWED_KEYS:
+ raise ValueError(
+ 'Exactly one of {} must be supplied: {}'.format(ALLOWED_KEYS,
+ source))
+ if 'element' in source:
+ return self._load_element(source['element'])
- src = ''
- if 'path' in source:
- src = os.path.abspath(source['path'])
- elif 'file' in source:
- src = source['file']
- root = self._load_file(src)
- if root is None:
- raise common.LoadError(
- 'Failed to access and read file: {}'.format(src))
- return root
+ src = ''
+ if 'path' in source:
+ src = os.path.abspath(source['path'])
+ elif 'file' in source:
+ src = source['file']
+ root = self._load_file(src)
+ if root is None:
+ raise common.LoadError(
+ 'Failed to access and read file: {}'.format(src))
+ return root
- def _load_file(self, f):
- seen = [x for x in self._origins if x.source_file == f]
- if len(seen):
- # Break cycles: A -> B -> A
- print 'Warning: path "{}" included more than once.'.format(f)
- return None
- try:
- tree = xml_parser.parse(f)
- except IOError:
- return None
- root = tree.getroot()
- if root.tag != self._tag:
- # pylint: disable=no-member
- raise common.LoadErrorWithOrigin(
- root.origin,
- 'Included file "{}" root node is not a <{}>'.format(f, self._tag))
- return self._load_element(root)
+ def _load_file(self, f):
+ seen = [x for x in self._origins if x.source_file == f]
+ if len(seen):
+ # Break cycles: A -> B -> A
+ print 'Warning: path "{}" included more than once.'.format(f)
+ return None
+ try:
+ tree = xml_parser.parse(f)
+ except IOError:
+ return None
+ root = tree.getroot()
+ if root.tag != self._tag:
+ # pylint: disable=no-member
+ raise common.LoadErrorWithOrigin(
+ root.origin,
+ 'Included file "{}" root node is not a <{}>'.format(f,
+ self._tag))
+ return self._load_element(root)
- def _load_element(self, root):
- self._origins += [root.origin.copy()]
- # Overwrite any attribs from rollover_attribs.
- root.old_attrib = {}
- for attr in self._rollover_attribs:
- if attr in root.attrib:
- root.old_attrib[attr] = root.attrib[attr]
- root.attrib[attr] = self._rollover_attribs[attr]
- # Copy out any attribs that should rollover.
- for attr in root.attrib:
- if attr in self._rollover_names:
- self._rollover_attribs[attr] = root.attrib[attr]
- if 'path' in root.attrib:
- path = root.get_attrib('path')
- # Resolve relative paths using the including file's path.
- if path[0] != '/':
- caller = root.origin.source_file
- path = os.path.join(os.path.dirname(caller), path)
- # Populate internal state from the given path.
- new_root = self._load_file(path)
- if new_root is None:
- raise common.LoadErrorWithOrigin(
- root.origin,
- 'Unable to load <{}> from file: {}'.format(self._tag, path))
- return new_root
- return root
+ def _load_element(self, root):
+ self._origins += [root.origin.copy()]
+ # Overwrite any attribs from rollover_attribs.
+ root.old_attrib = {}
+ for attr in self._rollover_attribs:
+ if attr in root.attrib:
+ root.old_attrib[attr] = root.attrib[attr]
+ root.attrib[attr] = self._rollover_attribs[attr]
+ # Copy out any attribs that should rollover.
+ for attr in root.attrib:
+ if attr in self._rollover_names:
+ self._rollover_attribs[attr] = root.attrib[attr]
+ if 'path' in root.attrib:
+ path = root.get_attrib('path')
+ # Resolve relative paths using the including file's path.
+ if path[0] != '/':
+ caller = root.origin.source_file
+ path = os.path.join(os.path.dirname(caller), path)
+ # Populate internal state from the given path.
+ new_root = self._load_file(path)
+ if new_root is None:
+ raise common.LoadErrorWithOrigin(
+ root.origin,
+ 'Unable to load <{}> from file: {}'.format(self._tag, path))
+ return new_root
+ return root
diff --git a/cli/lib/project/pack.py b/cli/lib/project/pack.py
index 83e766a..ab0db05 100644
--- a/cli/lib/project/pack.py
+++ b/cli/lib/project/pack.py
@@ -26,414 +26,417 @@ WILDCARD = '*'
class Pack(object):
- """Pack objects represent the <pack> element."""
- def __init__(self, namespace, name):
- self._namespace = namespace
- self._name = name
- self._depends = {'requires':[], 'provides':[]}
- self._copies = []
- self._configs = []
- self._defaults = DefaultCopy()
- self._origin = common.Origin()
-
- def _alias_puidlist(self, old_ns, new_ns, puidlist):
- """Takes a list of pack ids and changes their prefix in a new list."""
- new_list = []
- for puid in puidlist:
- if puid.startswith('{}.'.format(old_ns)):
- new_list += ['{}{}'.format(new_ns, puid[len(old_ns):])]
- else:
- new_list += [puid]
- return new_list
-
- def alias(self, new_ns):
- """Swap out the namespace prefix in all absolute ids."""
- self._depends['requires'] = self._alias_puidlist(
- self._namespace, new_ns, self._depends['requires'])
- self._depends['provides'] = self._alias_puidlist(
- self._namespace, new_ns, self._depends['provides'])
- self._namespace = new_ns
-
- @property
- def namespace(self):
- return self._namespace
-
- @property
- def origin(self):
- """Returns string indicating where the pack was defined."""
- return self._origin
-
- @property
- def uid(self):
- """Returns the fully qualified unique pack name"""
- return '{}.{}'.format(self._namespace, self._name)
-
- @property
- def name(self):
- """Returns the namespace-local pack name."""
- return self._name
-
- @property
- def requires(self):
- """Returns a list of required fully qualified pack unique names."""
- return self._depends['requires']
-
- @property
- def provides(self):
- """Returns a list of provided fully qualified pack virtual names."""
- return self._depends['provides']
-
- def add_provides(self, virtual_name):
- # Duplicates are ignored.
- if virtual_name in self._depends['provides']:
- return
- self._depends['provides'].append(virtual_name)
-
- @property
- def defaults(self):
- """Returns the DefaultCopy node if defined."""
- return self._defaults
-
- @defaults.setter
- def defaults(self, default_copy):
- self._defaults = default_copy
-
- @property
- def copies(self):
- """Returns the list of child Copy objects."""
- return self._copies
-
- def add_copy(self, copy):
- self._copies.append(copy)
-
- @property
- def configs(self):
- """Returns the list of child Config objects."""
- return self._configs
-
- def __repr__(self):
- s = '<pack name="{}">{}{}{}{}{}</pack>'.format(
- self._name,
- ''.join(['<provides pack="{}"/>'.format(p)
- for p in self._depends['provides']]),
- ''.join(['<requires pack="{}"/>'.format(p)
- for p in self._depends['requires']]),
- ''.join([str(c) for c in self._copies]),
- self._configs or '',
- self._defaults
- )
- return s
-
- def load(self, ele):
- """Loads the Pack from a XML element node."""
- ele.limit_attribs(['name'])
- # Override any pre-defined name.
- self._name = ele.get_attrib('name')
- self._origin = ele.origin.copy()
- for child in ele.findall('defaults'):
- self._defaults.load(child)
- for pack_op in self._depends:
- for child in ele.findall(pack_op):
- child.limit_attribs(['pack'])
- n = child.get_attrib('pack')
- if '.' not in n:
- raise common.LoadErrorWithOrigin(
- child.origin,
- '<{}> must supply fully qualified pack names: {}'.format(
- pack_op, n))
- self._depends[pack_op].append(n)
- for child in ele.findall('copy'):
- c = Copy(self)
- c.load(child)
- self._copies.append(c)
- for child in ele.findall('config'):
- c = Config(self)
- c.load(child)
- self._configs.append(c)
+ """Pack objects represent the <pack> element."""
+ def __init__(self, namespace, name):
+ self._namespace = namespace
+ self._name = name
+ self._depends = {'requires':[], 'provides':[]}
+ self._copies = []
+ self._configs = []
+ self._defaults = DefaultCopy()
+ self._origin = common.Origin()
+
+ def _alias_puidlist(self, old_ns, new_ns, puidlist):
+ """Takes a list of pack ids and changes their prefix in a new list."""
+ new_list = []
+ for puid in puidlist:
+ if puid.startswith('{}.'.format(old_ns)):
+ new_list += ['{}{}'.format(new_ns, puid[len(old_ns):])]
+ else:
+ new_list += [puid]
+ return new_list
+
+ def alias(self, new_ns):
+ """Swap out the namespace prefix in all absolute ids."""
+ self._depends['requires'] = self._alias_puidlist(
+ self._namespace, new_ns, self._depends['requires'])
+ self._depends['provides'] = self._alias_puidlist(
+ self._namespace, new_ns, self._depends['provides'])
+ self._namespace = new_ns
+
+ @property
+ def namespace(self):
+ return self._namespace
+
+ @property
+ def origin(self):
+ """Returns string indicating where the pack was defined."""
+ return self._origin
+
+ @property
+ def uid(self):
+ """Returns the fully qualified unique pack name"""
+ return '{}.{}'.format(self._namespace, self._name)
+
+ @property
+ def name(self):
+ """Returns the namespace-local pack name."""
+ return self._name
+
+ @property
+ def requires(self):
+ """Returns a list of required fully qualified pack unique names."""
+ return self._depends['requires']
+
+ @property
+ def provides(self):
+ """Returns a list of provided fully qualified pack virtual names."""
+ return self._depends['provides']
+
+ def add_provides(self, virtual_name):
+ # Duplicates are ignored.
+ if virtual_name in self._depends['provides']:
+ return
+ self._depends['provides'].append(virtual_name)
+
+ @property
+ def defaults(self):
+ """Returns the DefaultCopy node if defined."""
+ return self._defaults
+
+ @defaults.setter
+ def defaults(self, default_copy):
+ self._defaults = default_copy
+
+ @property
+ def copies(self):
+ """Returns the list of child Copy objects."""
+ return self._copies
+
+ def add_copy(self, copy):
+ self._copies.append(copy)
+
+ @property
+ def configs(self):
+ """Returns the list of child Config objects."""
+ return self._configs
+
+ def __repr__(self):
+ s = '<pack name="{}">{}{}{}{}{}</pack>'.format(
+ self._name,
+ ''.join(['<provides pack="{}"/>'.format(p)
+ for p in self._depends['provides']]),
+ ''.join(['<requires pack="{}"/>'.format(p)
+ for p in self._depends['requires']]),
+ ''.join([str(c) for c in self._copies]),
+ self._configs or '',
+ self._defaults
+ )
+ return s
+
+ def load(self, ele):
+ """Loads the Pack from a XML element node."""
+ ele.limit_attribs(['name'])
+ # Override any pre-defined name.
+ self._name = ele.get_attrib('name')
+ self._origin = ele.origin.copy()
+ for child in ele.findall('defaults'):
+ self._defaults.load(child)
+ for pack_op in self._depends:
+ for child in ele.findall(pack_op):
+ child.limit_attribs(['pack'])
+ n = child.get_attrib('pack')
+ if '.' not in n:
+ raise common.LoadErrorWithOrigin(
+ child.origin,
+ ('<{}> must supply fully qualified pack names: '
+ '{}'.format(pack_op, n)))
+ self._depends[pack_op].append(n)
+ for child in ele.findall('copy'):
+ c = Copy(self)
+ c.load(child)
+ self._copies.append(c)
+ for child in ele.findall('config'):
+ c = Config(self)
+ c.load(child)
+ self._configs.append(c)
class CopyType(object):
- DIR = 1
- FILE = 2
- GLOB = 3
- @staticmethod
- def get(path):
- if path.endswith(os.sep):
- return CopyType.DIR
- if path.endswith(WILDCARD):
- return CopyType.GLOB
- return CopyType.FILE
+ DIR = 1
+ FILE = 2
+ GLOB = 3
+ @staticmethod
+ def get(path):
+ if path.endswith(os.sep):
+ return CopyType.DIR
+ if path.endswith(WILDCARD):
+ return CopyType.GLOB
+ return CopyType.FILE
class DefaultCopy(object):
- def __init__(self):
- self._origin = common.Origin()
- self._dst = None
- self._acl = acl.FileAcl(None)
-
- def load(self, ele):
- ele.limit_attribs([])
- copies = ele.findall('copy')
- if len(copies) == 0:
- return
- if len(copies) != 1:
- raise common.LoadErrorWithOrigin(
- ele.origin, 'Only one copy element may be defined in defaults.')
- ele = copies[0]
- ele.limit_attribs(['to'])
- # TODO(wad) Here and elsewhere, ensure attrib only
- # has keys that are explicitly supported.
- self._origin = ele.origin.copy()
- if 'to' in ele.attrib:
- self._dst = ele.get_attrib('to')
- if CopyType.get(self._dst) != CopyType.DIR:
- raise common.LoadErrorWithOrigin(
- self._origin,
- ('default copy destinations must be directories, suffixed '
- 'by a {}'.format(common.pathsep)))
- acls = ele.findall('set-acl')
- if len(acls) > 1:
- raise common.LoadErrorWithOrigin(ele.origin,
- 'Only one set-acl default may be set')
- if len(acls):
- self._acl.load(acls[0])
-
- @property
- def dst(self):
- return self._dst
-
- @property
- def acl(self):
- return self._acl
-
- def __repr__(self):
- return '<defaults><copy to="{}">{}</copy></defaults>'.format(
- self._dst or '', self._acl)
+ def __init__(self):
+ self._origin = common.Origin()
+ self._dst = None
+ self._acl = acl.FileAcl(None)
+
+ def load(self, ele):
+ ele.limit_attribs([])
+ copies = ele.findall('copy')
+ if len(copies) == 0:
+ return
+ if len(copies) != 1:
+ raise common.LoadErrorWithOrigin(
+ ele.origin, 'Only one copy element may be defined in defaults.')
+ ele = copies[0]
+ ele.limit_attribs(['to'])
+ # TODO(wad) Here and elsewhere, ensure attrib only
+ # has keys that are explicitly supported.
+ self._origin = ele.origin.copy()
+ if 'to' in ele.attrib:
+ self._dst = ele.get_attrib('to')
+ if CopyType.get(self._dst) != CopyType.DIR:
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ ('default copy destinations must be directories, suffixed '
+ 'by a {}'.format(common.pathsep)))
+ acls = ele.findall('set-acl')
+ if len(acls) > 1:
+ raise common.LoadErrorWithOrigin(
+ ele.origin, 'Only one set-acl default may be set')
+ if len(acls):
+ self._acl.load(acls[0])
+
+ @property
+ def dst(self):
+ return self._dst
+
+ @property
+ def acl(self):
+ return self._acl
+
+ def __repr__(self):
+ return '<defaults><copy to="{}">{}</copy></defaults>'.format(
+ self._dst or '', self._acl)
class Copy(object):
- def __init__(self, pack,
- dst='', dst_type=CopyType.FILE,
- src='', src_type=CopyType.FILE):
- self._pack = pack
- self._dst = dst
- self._dst_type = dst_type
- # Set the default "to" if the pack has none.
- if pack is not None and pack.defaults is not None:
- if pack.defaults.dst is not None:
- self._dst = pack.defaults.dst
+ def __init__(self, pack, dst='', dst_type=CopyType.FILE, src='',
+ src_type=CopyType.FILE):
+ self._pack = pack
+ self._dst = dst
+ self._dst_type = dst_type
+ # Set the default "to" if the pack has none.
+ if pack is not None and pack.defaults is not None:
+ if pack.defaults.dst is not None:
+ self._dst = pack.defaults.dst
+ self._dst_type = CopyType.get(self._dst)
+ self._src = src
+ self._src_type = src_type
+ self._recurse = False
+ self._acl = acl.FileAcl(self)
+ self._origin = common.Origin()
+
+ @property
+ def pack(self):
+ return self._pack
+
+ @property
+ def origin(self):
+ return self._origin
+
+ def load(self, ele):
+ """Loads the Copy from a XML element node (copy)."""
+ ele.limit_attribs(['to', 'from', 'recurse'])
+ # Always get to from the element.
+ if 'to' in ele.attrib:
+ self._dst = ele.get_attrib('to')
+ # Fail if there is no 'to' default or defined 'to'.
+ if self._dst == '':
+ self._dst = ele.get_attrib('to')
+
self._dst_type = CopyType.get(self._dst)
- self._src = src
- self._src_type = src_type
- self._recurse = False
- self._acl = acl.FileAcl(self)
- self._origin = common.Origin()
-
- @property
- def pack(self):
- return self._pack
-
- @property
- def origin(self):
- return self._origin
-
- def load(self, ele):
- """Loads the Copy from a XML element node (copy)."""
- ele.limit_attribs(['to', 'from', 'recurse'])
- # Always get to from the element.
- if 'to' in ele.attrib:
- self._dst = ele.get_attrib('to')
- # Fail if there is no 'to' default or defined 'to'.
- if self._dst == '':
- self._dst = ele.get_attrib('to')
-
- self._dst_type = CopyType.get(self._dst)
- self._src = ele.get_attrib('from')
- self._src_type = CopyType.get(self._src)
- recurse = (ele.attrib.get('recurse') or 'false').lower()
- self._origin = ele.origin.copy()
- if recurse == 'true':
- self._recurse = True
- elif recurse == 'false':
- self._recurse = False
- else:
- raise common.LoadErrorWithOrigin(
- self._origin, '<copy> recurse element not "true" or "false"')
- self._recurse = recurse == 'true' or False
-
- acls = ele.findall('set-acl')
- if len(acls) > 1:
- raise common.LoadErrorWithOrigin(
- ele.origin, 'Only one <set-acl> element is allowed per <copy>')
- if len(acls):
- self._acl.load(acls[0])
- self._reconcile_paths()
-
- def _reconcile_paths(self):
- if not self._dst.startswith(common.pathsep):
- raise common.LoadErrorWithOrigin(
- self._origin,
- '<copy> destinations must be absolute (start with a {}): {}'.format(
- common.pathsep, self._dst))
- if self._recurse:
- # Ensure recursive destinations are paths and require a trailing slash.
- # This is pedantic, but it is better to start explicit.
- if not self._dst_type == CopyType.DIR:
- raise common.LoadErrorWithOrigin(
- self._origin,
- '<copy> specifies recursion but the destination "{}" '
- 'does not have a trailing path separator'.format(self._dst))
- # Similarly, recursive sources must be labeled as paths using trailing
- # slashes or end with a wildcard (*) allowing a path to be copied
- # or the contents of a path.
- if self._src_type == CopyType.FILE:
- raise common.LoadErrorWithOrigin(
- self._origin,
- '<copy> specifies recursion but the source "{}" '
- 'does not have a trailing path separator or wildcard'.format(
- self._src))
- # If we're copying the path, we could translate it into a more
- # specific glob here, but there is no benefit using normal python
- # helpers.
-
- # For file copies into a path, compute the final path.
- if not self._recurse:
- if self._dst_type == CopyType.DIR and self._src_type == CopyType.FILE:
- # foo/bar.txt -> /system/bin/ becomes /system/bin/bar.txt
- self._dst = common.path_join(self._dst, common.basename(self._src))
- self._dst_type = CopyType.FILE
- elif self._dst_type == CopyType.DIR and self._src_type == CopyType.DIR:
- # foo/bar/ -> /system/blah becomes /system/blah/bar/
- # (GLOBs are used to copy contents to the same name.)
- self._dst = common.path_join(
- self._dst, common.basename(self._src.rstrip(common.pathsep)))
- elif (self._src_type == CopyType.GLOB and not
- self._dst_type == CopyType.DIR):
- raise common.LoadErrorWithOrigin(
- self._origin,
- '<copy> source "{}" uses a wildcard but the '
- 'destination "{}" does not have a trailing path separator'.format(
- self._src, self._dst))
-
- # Absolutize the sources based on the defining file.
- # This also means that sources must use host separators.
- # TODO(wad) Consider allowing a base to be set by Packs on
- # inclusion of a file.
- self._src = common.path_to_host(self._src)
- if not os.path.isabs(self._src):
- base_path = os.path.dirname(self._origin.source_file)
- self._src = os.path.abspath(os.path.join(base_path, self._src))
-
- # Note, if we end up creating an image root, then we can also absolutize
- # the destinations (excepting wildcards).
-
- @property
- def src(self):
- """Returns the path on the caller's client."""
- return self._src
-
- @src.setter
- def src(self, src):
- self._src = src
-
- @property
- def dst(self):
- return self._dst
-
- @dst.setter
- def dst(self, dst):
- self._dst = dst
-
- @property
- def src_type(self):
- return self._src_type
-
- @property
- def dst_type(self):
- return self._dst_type
-
- @src_type.setter
- def src_type(self, t):
- self._src_type = t
-
- @dst_type.setter
- def dst_type(self, t):
- self._dst_type = t
-
- @property
- def recurse(self):
- return self._recurse
-
- @recurse.setter
- def recurse(self, r):
- self._recurse = r
-
- @property
- def acl(self):
- return self._acl
-
- @acl.setter
- def acl(self, acl_):
- self._acl = acl_
-
- def __repr__(self):
- return '<copy to="{}" from="{}" recurse="{}">{}</copy>'.format(
- self._dst, self._src, self._recurse, self._acl)
+ self._src = ele.get_attrib('from')
+ self._src_type = CopyType.get(self._src)
+ recurse = (ele.attrib.get('recurse') or 'false').lower()
+ self._origin = ele.origin.copy()
+ if recurse == 'true':
+ self._recurse = True
+ elif recurse == 'false':
+ self._recurse = False
+ else:
+ raise common.LoadErrorWithOrigin(
+ self._origin, '<copy> recurse element not "true" or "false"')
+ self._recurse = recurse == 'true' or False
+
+ acls = ele.findall('set-acl')
+ if len(acls) > 1:
+ raise common.LoadErrorWithOrigin(
+ ele.origin, 'Only one <set-acl> element is allowed per <copy>')
+ if len(acls):
+ self._acl.load(acls[0])
+ self._reconcile_paths()
+
+ def _reconcile_paths(self):
+ if not self._dst.startswith(common.pathsep):
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ ('<copy> destinations must be absolute (start with a {}): '
+ '{}'.format(common.pathsep, self._dst)))
+ if self._recurse:
+ # Ensure recursive destinations are paths and require a trailing
+ # slash. This is pedantic, but it is better to start explicit.
+ if not self._dst_type == CopyType.DIR:
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '<copy> specifies recursion but the destination "{}" '
+ 'does not have a trailing path separator'.format(self._dst))
+ # Similarly, recursive sources must be labeled as paths using
+ # trailing slashes or end with a wildcard (*) allowing a path to be
+ # copied or the contents of a path.
+ if self._src_type == CopyType.FILE:
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '<copy> specifies recursion but the source "{}" '
+ 'does not have a trailing path separator or '
+ 'wildcard'.format(self._src))
+ # If we're copying the path, we could translate it into a more
+ # specific glob here, but there is no benefit using normal python
+ # helpers.
+
+ # For file copies into a path, compute the final path.
+ if not self._recurse:
+ if (self._dst_type == CopyType.DIR
+ and self._src_type == CopyType.FILE):
+ # foo/bar.txt -> /system/bin/ becomes /system/bin/bar.txt
+ self._dst = common.path_join(self._dst,
+ common.basename(self._src))
+ self._dst_type = CopyType.FILE
+ elif (self._dst_type == CopyType.DIR
+ and self._src_type == CopyType.DIR):
+ # foo/bar/ -> /system/blah becomes /system/blah/bar/
+ # (GLOBs are used to copy contents to the same name.)
+ self._dst = common.path_join(
+ self._dst,
+ common.basename(self._src.rstrip(common.pathsep)))
+ elif (self._src_type == CopyType.GLOB
+ and not self._dst_type == CopyType.DIR):
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '<copy> source "{}" uses a wildcard but the destination '
+ '"{}" does not have a trailing path separator'.format(
+ self._src, self._dst))
+
+ # Absolutize the sources based on the defining file.
+ # This also means that sources must use host separators.
+ # TODO(wad) Consider allowing a base to be set by Packs on
+ # inclusion of a file.
+ self._src = common.path_to_host(self._src)
+ if not os.path.isabs(self._src):
+ base_path = os.path.dirname(self._origin.source_file)
+ self._src = os.path.abspath(os.path.join(base_path, self._src))
+
+ # Note, if we end up creating an image root, then we can also absolutize
+ # the destinations (excepting wildcards).
+
+ @property
+ def src(self):
+ """Returns the path on the caller's client."""
+ return self._src
+
+ @src.setter
+ def src(self, src):
+ self._src = src
+
+ @property
+ def dst(self):
+ return self._dst
+
+ @dst.setter
+ def dst(self, dst):
+ self._dst = dst
+
+ @property
+ def src_type(self):
+ return self._src_type
+
+ @property
+ def dst_type(self):
+ return self._dst_type
+
+ @src_type.setter
+ def src_type(self, t):
+ self._src_type = t
+
+ @dst_type.setter
+ def dst_type(self, t):
+ self._dst_type = t
+
+ @property
+ def recurse(self):
+ return self._recurse
+
+ @recurse.setter
+ def recurse(self, r):
+ self._recurse = r
+
+ @property
+ def acl(self):
+ return self._acl
+
+ @acl.setter
+ def acl(self, acl_):
+ self._acl = acl_
+
+ def __repr__(self):
+ return '<copy to="{}" from="{}" recurse="{}">{}</copy>'.format(
+ self._dst, self._src, self._recurse, self._acl)
class ConfigType(object):
- UNKNOWN = 0
- KERNEL_FRAGMENT = 1
- SELINUX_POLICY = 2
- NAMES = {'kernel-fragment': KERNEL_FRAGMENT,
- 'sepolicy': SELINUX_POLICY}
+ UNKNOWN = 0
+ KERNEL_FRAGMENT = 1
+ SELINUX_POLICY = 2
+ NAMES = {'kernel-fragment': KERNEL_FRAGMENT,
+ 'sepolicy': SELINUX_POLICY}
- @staticmethod
- def get(type_name):
- if type_name in ConfigType.NAMES:
- return ConfigType.NAMES[type_name]
- return ConfigType.UNKNOWN
+ @staticmethod
+ def get(type_name):
+ if type_name in ConfigType.NAMES:
+ return ConfigType.NAMES[type_name]
+ return ConfigType.UNKNOWN
class Config(object):
- def __init__(self, pack):
- self._pack = pack
- self._path = ()
- self._type = ConfigType.UNKNOWN
- self._origin = common.Origin()
-
- @property
- def pack(self):
- return self._pack
-
- def load(self, ele):
- """Loads the Config from a XML element node (config)."""
- ele.limit_attribs(['path', 'type'])
- self._origin = ele.origin.copy()
- self._path = common.path_to_host(ele.get_attrib('path'))
- if not isinstance(self._path, basestring):
- raise common.LoadErrorWithOrigin(ele.origin,
- 'Failed to parse "path" attribute')
-
- # Anchor a relative path to the origin's location.
- if not os.path.isabs(self._path):
- base_path = os.path.dirname(self._origin.source_file)
- self._path = os.path.abspath(os.path.join(base_path, self._path))
- if self._path.endswith(os.sep):
- raise common.LoadErrorWithOrigin(
- self._origin,
- '@path must specify a file and not a directory: {}'.format(
- self._path))
- self._type = ConfigType.get(ele.get_attrib('type'))
- if self._type == ConfigType.UNKNOWN:
- raise common.LoadErrorWithOrigin(
- self._origin,
- '@type must be one of {}'.format(ConfigType.NAMES.keys()))
-
- @property
- def path(self):
- return self._path
-
- def __repr__(self):
- return '<config type="{}" path="{}"'.format(
- self._type, self._path)
+ def __init__(self, pack):
+ self._pack = pack
+ self._path = ()
+ self._type = ConfigType.UNKNOWN
+ self._origin = common.Origin()
+
+ @property
+ def pack(self):
+ return self._pack
+
+ def load(self, ele):
+ """Loads the Config from a XML element node (config)."""
+ ele.limit_attribs(['path', 'type'])
+ self._origin = ele.origin.copy()
+ self._path = common.path_to_host(ele.get_attrib('path'))
+ if not isinstance(self._path, basestring):
+ raise common.LoadErrorWithOrigin(ele.origin,
+ 'Failed to parse "path" attribute')
+
+ # Anchor a relative path to the origin's location.
+ if not os.path.isabs(self._path):
+ base_path = os.path.dirname(self._origin.source_file)
+ self._path = os.path.abspath(os.path.join(base_path, self._path))
+ if self._path.endswith(os.sep):
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '@path must specify a file and not a directory: {}'.format(
+ self._path))
+ self._type = ConfigType.get(ele.get_attrib('type'))
+ if self._type == ConfigType.UNKNOWN:
+ raise common.LoadErrorWithOrigin(
+ self._origin,
+ '@type must be one of {}'.format(ConfigType.NAMES.keys()))
+
+ @property
+ def path(self):
+ return self._path
+
+ def __repr__(self):
+ return '<config type="{}" path="{}"'.format(
+ self._type, self._path)
diff --git a/cli/lib/project/pack_unittest.py b/cli/lib/project/pack_unittest.py
index 2fa80dd..ba02866 100644
--- a/cli/lib/project/pack_unittest.py
+++ b/cli/lib/project/pack_unittest.py
@@ -34,36 +34,36 @@ from project.pack import Pack
class PackTest(unittest.TestCase):
- def pack_from_str(self, ns, name, s):
- pack_xml = StringIO.StringIO(s)
- tree = xml_parser.parse(pack_xml)
- pack = Pack(ns, name)
- pack.load(tree.getroot())
- return pack
+ def pack_from_str(self, ns, name, s):
+ pack_xml = StringIO.StringIO(s)
+ tree = xml_parser.parse(pack_xml)
+ pack = Pack(ns, name)
+ pack.load(tree.getroot())
+ return pack
- def setUp(self):
- self.ns = 'old.name.space'
- self.p = Pack(self.ns, 'my_pack')
+ def setUp(self):
+ self.ns = 'old.name.space'
+ self.p = Pack(self.ns, 'my_pack')
- def tearDown(self):
- pass
+ def tearDown(self):
+ pass
class AliasTest(PackTest):
- def test_alias_empty(self):
- new_ns = 'new.name.space'
- self.assertNotEqual(self.p.namespace, new_ns)
- self.p.alias(new_ns)
- self.assertEqual(self.p.namespace, new_ns)
-
- def test_alias_requires_matches(self):
- required = '{}.foo1'.format(self.ns)
- new_ns = 'new.name.space'
- expected = ['{}.foo1'.format(new_ns)]
- self.p.requires.append(required)
- self.assertEqual(self.p.requires, [required])
- self.p.alias(new_ns)
- self.assertEqual(self.p.requires, expected)
+ def test_alias_empty(self):
+ new_ns = 'new.name.space'
+ self.assertNotEqual(self.p.namespace, new_ns)
+ self.p.alias(new_ns)
+ self.assertEqual(self.p.namespace, new_ns)
+
+ def test_alias_requires_matches(self):
+ required = '{}.foo1'.format(self.ns)
+ new_ns = 'new.name.space'
+ expected = ['{}.foo1'.format(new_ns)]
+ self.p.requires.append(required)
+ self.assertEqual(self.p.requires, [required])
+ self.p.alias(new_ns)
+ self.assertEqual(self.p.requires, expected)
TEST_DEFAULT_COPY_XML = """
@@ -84,217 +84,222 @@ TEST_DEFAULT_COPY_XML = """
"""
class AttributeExistenceTest(PackTest):
- def test_pack_valid_full(self):
- xml = '<pack name="foo"><defaults/></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_pack_valid_named_null(self):
- xml = '<pack name="foo"/>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_pack_invalid_no_name(self):
- xml = '<pack/>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_pack_invalid_extra_attr(self):
- xml = '<pack name="blah" secret="x12"/>'
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_defaults_valid_no_attrs(self):
- xml = '<pack name="test"><defaults/></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_defaults_invalid_extra(self):
- xml = '<pack name="test"><defaults something="is_here"/></pack>'
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_defaults_copy_valid_no_attrs(self):
- xml = '<pack name="test"><defaults><copy/></defaults></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_defaults_copy_valid_all_attrs(self):
- xml = '<pack name="test"><defaults><copy to="/me/"/></defaults></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_defaults_copy_invalid_extra(self):
- xml = ('<pack name="test"><defaults><copy to="/me/" from="/baz"/>'
- '</defaults></pack>')
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_defaults_copy_acl_valid_no_attrs(self):
- xml = ('<pack name="test"><defaults><copy>'
- '<set-acl/></copy></defaults></pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_defaults_copy_acl_valid_all_attrs(self):
- xml = ('<pack name="test"><defaults><copy>'
- '<set-acl perms="0600" fcaps="" '
- 'user="a_user" group="a_group" '
- 'selabel="test_t"/></copy></defaults></pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_defaults_copy_acl_invalid_extra(self):
- xml = ('<pack name="test"><defaults><copy>'
- '<set-acl perms="0600" fcaps="CAP_SYS_ADMIN CAP_CHOWN" '
- 'user="a_user" group="a_group" '
- 'selabel="test_t" apparmor_p="gfoo"/></copy></defaults></pack>')
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_copy_invalid_no_attrs(self):
- xml = '<pack name="test"><copy/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_copy_invalid_extra(self):
- xml = '<pack name="test"><copy to="/hi/" from="/bar" baz="123"/></pack>'
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_copy_valid_minimal(self):
- xml = '<pack name="test"><copy to="/x" from="/y"/></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_copy_valid_all(self):
- xml = '<pack name="test"><copy to="/x/" from="/y/*" recurse="true"/></pack>'
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_config_invalid_no_attrs(self):
- xml = '<pack name="test"><config/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_config_invalid_path_only(self):
- xml = '<pack name="test"><config path="/foo"/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_config_invalid_type_only(self):
- xml = '<pack name="test"><config type="sepolicy"/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_config_invalid_extra(self):
- xml = ('<pack name="test">'
- '<config type="kernel-fragment" path="/foo" z="1"/></pack>')
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_config_valid_all(self):
- xml = ('<pack name="test"><config path="/foo" type="kernel-fragment"/>'
- '</pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_provides_invalid_bare(self):
- xml = '<pack name="test"><provides/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_provides_invalid_extra(self):
- xml = ('<pack name="test">'
- '<provides pack="hello.bar" from="somewhere"/></pack>')
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_provides_valid(self):
- xml = ('<pack name="test">'
- '<provides pack="hello.bar"/></pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_requires_invalid_bare(self):
- xml = '<pack name="test"><requires/></pack>'
- with self.assertRaises(common.MissingAttribute):
- self.pack_from_str('a', 'b', xml)
-
- def test_requires_invalid_extra(self):
- xml = ('<pack name="test">'
- '<requires pack="hello.bar" from="somewhere"/></pack>')
- with self.assertRaises(common.UnknownAttributes):
- self.pack_from_str('a', 'b', xml)
-
- def test_requires_valid(self):
- xml = ('<pack name="test">'
- '<requires pack="hello.bar"/></pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
+ def test_pack_valid_full(self):
+ xml = '<pack name="foo"><defaults/></pack>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_pack_valid_named_null(self):
+ xml = '<pack name="foo"/>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_pack_invalid_no_name(self):
+ xml = '<pack/>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_pack_invalid_extra_attr(self):
+ xml = '<pack name="blah" secret="x12"/>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_defaults_valid_no_attrs(self):
+ xml = '<pack name="test"><defaults/></pack>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_defaults_invalid_extra(self):
+ xml = '<pack name="test"><defaults something="is_here"/></pack>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_defaults_copy_valid_no_attrs(self):
+ xml = '<pack name="test"><defaults><copy/></defaults></pack>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_defaults_copy_valid_all_attrs(self):
+ xml = '<pack name="test"><defaults><copy to="/me/"/></defaults></pack>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_defaults_copy_invalid_extra(self):
+ xml = ('<pack name="test"><defaults><copy to="/me/" from="/baz"/>'
+ '</defaults></pack>')
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_defaults_copy_acl_valid_no_attrs(self):
+ xml = ('<pack name="test"><defaults><copy>'
+ '<set-acl/></copy></defaults></pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_defaults_copy_acl_valid_all_attrs(self):
+ xml = ('<pack name="test"><defaults><copy>'
+ '<set-acl perms="0600" fcaps="" '
+ 'user="a_user" group="a_group" '
+ 'selabel="test_t"/></copy></defaults></pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_defaults_copy_acl_invalid_extra(self):
+ xml = ('<pack name="test"><defaults><copy>'
+ '<set-acl perms="0600" fcaps="CAP_SYS_ADMIN CAP_CHOWN" '
+ 'user="a_user" group="a_group" '
+ 'selabel="test_t" apparmor_p="gfoo"/></copy></defaults></pack>')
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_copy_invalid_no_attrs(self):
+ xml = '<pack name="test"><copy/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_copy_invalid_extra(self):
+ xml = '<pack name="test"><copy to="/hi/" from="/bar" baz="123"/></pack>'
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_copy_valid_minimal(self):
+ xml = '<pack name="test"><copy to="/x" from="/y"/></pack>'
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_copy_valid_all(self):
+ xml = ('<pack name="test"><copy to="/x/" from="/y/*" recurse="true"/>'
+ '</pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_config_invalid_no_attrs(self):
+ xml = '<pack name="test"><config/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_config_invalid_path_only(self):
+ xml = '<pack name="test"><config path="/foo"/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_config_invalid_type_only(self):
+ xml = '<pack name="test"><config type="sepolicy"/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_config_invalid_extra(self):
+ xml = ('<pack name="test">'
+ '<config type="kernel-fragment" path="/foo" z="1"/></pack>')
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_config_valid_all(self):
+ xml = ('<pack name="test"><config path="/foo" type="kernel-fragment"/>'
+ '</pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_provides_invalid_bare(self):
+ xml = '<pack name="test"><provides/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_provides_invalid_extra(self):
+ xml = ('<pack name="test">'
+ '<provides pack="hello.bar" from="somewhere"/></pack>')
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_provides_valid(self):
+ xml = ('<pack name="test">'
+ '<provides pack="hello.bar"/></pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_requires_invalid_bare(self):
+ xml = '<pack name="test"><requires/></pack>'
+ with self.assertRaises(common.MissingAttribute):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_requires_invalid_extra(self):
+ xml = ('<pack name="test">'
+ '<requires pack="hello.bar" from="somewhere"/></pack>')
+ with self.assertRaises(common.UnknownAttributes):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_requires_valid(self):
+ xml = ('<pack name="test">'
+ '<requires pack="hello.bar"/></pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
class ConfigTest(PackTest):
- # TODO(wad) Replace all '/' with os.path.sep.
- def test_valid_types(self):
- for t in ('sepolicy', 'kernel-fragment'):
- xml = '<pack name="test"><config path="/foo" type="{}"/></pack>'.format(t)
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
-
- def test_invalid_types(self):
- for t in ('a_type', 'firmware-fragment'):
- xml = '<pack name="test"><config path="/foo" type="{}"/></pack>'.format(t)
- with self.assertRaises(common.LoadErrorWithOrigin):
- self.pack_from_str('a', 'b', xml)
-
- def test_trailing_slash(self):
- xml = ('<pack name="test"><config path="/foo/" type="kernel-fragment"/>'
- '</pack>')
- with self.assertRaises(common.LoadErrorWithOrigin):
- self.pack_from_str('a', 'b', xml)
-
- def test_absolute_path(self):
- xml = ('<pack name="test"><config path="/foo" type="kernel-fragment"/>'
- '</pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
- self.assertEqual(pack.configs[0].path, '/foo')
-
- def test_relative_path(self):
- xml = ('<pack name="test"><config path="foo.kconfig" '
- 'type="kernel-fragment"/></pack>')
- pack = self.pack_from_str('a', 'b', xml)
- self.assertIsInstance(pack, Pack)
- self.assertEqual(pack.configs[0].path,
- os.path.join(os.getcwd(), 'foo.kconfig'))
+ # TODO(wad) Replace all '/' with os.path.sep.
+ def test_valid_types(self):
+ for t in ('sepolicy', 'kernel-fragment'):
+ xml = ('<pack name="test"><config path="/foo" type="{}"/>'
+ '</pack>'.format(t))
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+
+ def test_invalid_types(self):
+ for t in ('a_type', 'firmware-fragment'):
+ xml = ('<pack name="test"><config path="/foo" type="{}"/>'
+ '</pack>'.format(t))
+ with self.assertRaises(common.LoadErrorWithOrigin):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_trailing_slash(self):
+ xml = ('<pack name="test"><config path="/foo/" type="kernel-fragment"/>'
+ '</pack>')
+ with self.assertRaises(common.LoadErrorWithOrigin):
+ self.pack_from_str('a', 'b', xml)
+
+ def test_absolute_path(self):
+ xml = ('<pack name="test"><config path="/foo" type="kernel-fragment"/>'
+ '</pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+ self.assertEqual(pack.configs[0].path, '/foo')
+
+ def test_relative_path(self):
+ xml = ('<pack name="test"><config path="foo.kconfig" '
+ 'type="kernel-fragment"/></pack>')
+ pack = self.pack_from_str('a', 'b', xml)
+ self.assertIsInstance(pack, Pack)
+ self.assertEqual(pack.configs[0].path,
+ os.path.join(os.getcwd(), 'foo.kconfig'))
class DefaultCopyPropagationTest(PackTest):
- def setUp(self):
- self._pack = self.pack_from_str('a_namespace', 'a_pack',
- TEST_DEFAULT_COPY_XML)
-
- def test_default_user_merge(self):
- for node in self._pack.copies:
- if node.src == '/d/place':
- self.assertEqual(node.dst, '/a/location/place')
- self.assertEqual(node.acl.group, 'merge')
- self.assertEqual(node.acl.user, 'some_user')
-
- def test_default_noclobber(self):
- for node in self._pack.copies:
- if node.src == '/b/place':
- self.assertEqual(node.dst, '/c/place')
- self.assertEqual(node.acl.user, 'dont_overwrite_me')
- self.assertEqual(node.acl.group, 'a_group')
-
- def test_default_to_empty(self):
- for node in self._pack.copies:
- if node.src == '/a/place':
- self.assertEqual(node.dst, '/a/location/place')
- self.assertEqual(node.acl.user, 'some_user')
- self.assertEqual(node.acl.group, node.acl.DEFAULTS['GROUP'])
- self.assertEqual(node.acl.selabel, node.acl.DEFAULTS['SELABEL'])
- self.assertEqual(node.acl.fcaps, node.acl.DEFAULTS['CAPABILITIES'])
- self.assertEqual(node.acl.perms, node.acl.DEFAULTS['PERMISSIONS'])
+ def setUp(self):
+ self._pack = self.pack_from_str('a_namespace', 'a_pack',
+ TEST_DEFAULT_COPY_XML)
+
+ def test_default_user_merge(self):
+ for node in self._pack.copies:
+ if node.src == '/d/place':
+ self.assertEqual(node.dst, '/a/location/place')
+ self.assertEqual(node.acl.group, 'merge')
+ self.assertEqual(node.acl.user, 'some_user')
+
+ def test_default_noclobber(self):
+ for node in self._pack.copies:
+ if node.src == '/b/place':
+ self.assertEqual(node.dst, '/c/place')
+ self.assertEqual(node.acl.user, 'dont_overwrite_me')
+ self.assertEqual(node.acl.group, 'a_group')
+
+ def test_default_to_empty(self):
+ for node in self._pack.copies:
+ if node.src == '/a/place':
+ self.assertEqual(node.dst, '/a/location/place')
+ self.assertEqual(node.acl.user, 'some_user')
+ self.assertEqual(node.acl.group, node.acl.DEFAULTS['GROUP'])
+ self.assertEqual(node.acl.selabel, node.acl.DEFAULTS['SELABEL'])
+ self.assertEqual(node.acl.fcaps,
+ node.acl.DEFAULTS['CAPABILITIES'])
+ self.assertEqual(node.acl.perms,
+ node.acl.DEFAULTS['PERMISSIONS'])
diff --git a/cli/lib/project/packmap.py b/cli/lib/project/packmap.py
index 07284d0..3315b66 100644
--- a/cli/lib/project/packmap.py
+++ b/cli/lib/project/packmap.py
@@ -26,184 +26,187 @@ from project import packs
class UpdateError(common.LoadErrorWithOrigin):
- pass
+ pass
class PackMap(object):
- """PackMap provides a map of unique pack names to pack objects.
-
- Additionally, PackMap acts as the centralized location for any
- other indexing needed by its consumers to minimize inconsistency.
- """
- def __init__(self):
- # { pack.uid => pack }
- self._map = {}
- # { virtual_uid => [providing pack list]
- self._provides = defaultdict(list)
- # {source_files => [pack_uids] }
- self._origins = defaultdict(list)
- # [ required_uid => [requiring pack list] ]
- self._missing = defaultdict(list)
- # Imported Packs objects
- self._packs = []
- # { path => [Copy()] }
- self._destinations = defaultdict(list)
-
- @property
- def map(self):
- return self._map
-
- @property
- def copy_destinations(self):
- return self._destinations
-
- @property
- def packs(self):
- return self._packs
-
- @property
- def virtuals(self):
- return self._provides
-
- @property
- def origins(self):
- return self._origins
-
- @property
- def missing(self):
- return self._missing
-
- def submap(self, pack_uid, aliases):
- """Returns a PackMap containing just the packs in a tree from @pack_uid.
-
- Raises:
- dependency.Error: if there are any unfulfilled dependencies.
- """
- pm = PackMap()
- p = [self.map[pack_uid]]
- pm.update(p)
- while len(p) != 0:
- # Iterate until we can no longer resolve pm.missing() from map
- p = []
- for uid in pm.missing:
- if uid in self.map:
- p.append(self.map[uid])
- # Automatically pick an implementation if there is
- # only one that matches a prefix above.
- if uid in self.virtuals:
- provides = []
- for alias, prefix in aliases.iteritems():
- if uid.startswith('{}.'.format(alias)):
- provides += [x for x in self.virtuals[uid]
- if x.startswith(prefix)]
- if len(provides) == 1:
- p.append(self.map[provides[0]])
- pm.update(p)
-
- unsatisfied = []
- for missing, req_uids in pm.missing.iteritems():
- if missing in self.virtuals:
- unsatisfied.append(dependency.Virtual(
- missing, pack_uid, req_uids, self.virtuals[missing]))
- if len(unsatisfied):
- raise dependency.UnsatisfiedVirtualPackError(unsatisfied)
- if len(pm.missing):
- pm.report_missing()
- return pm
-
- def report_missing(self):
- """Raise a UndefinedPackError with useful text for all missing
- required packages.
- """
- undef = []
- for req_uid, needed_by in self.missing.iteritems():
- undef.append(
- dependency.Undefined(req_uid, [self.map[u] for u in needed_by]))
- if len(undef):
- raise dependency.UndefinedPackError(undef)
-
- def update(self, packs_):
- """Merges a Packs object (or container of Pack objects) into the PackMap"""
- self._packs += [packs_]
- for pack in packs_:
- if pack.uid in self._map:
- # Check if the caller passed in an already seen pack by checking
- # the origins.
- if pack.origin == self._map[pack.uid].origin:
- continue
- raise UpdateError(
- pack.origin,
- 'Redefinition of pack "{}". Previously defined here: {}'.format(
- pack.uid, self._map[pack.uid].origin))
- if pack.uid in self._provides:
- prevs = [self._map[p] for p in self._provides[pack.uid]]
- msg = 'Redefinition of virtual pack "{}". '.format(pack.uid)
- msg += 'Previously declared as provided by '
- msgs = []
- for p in prevs:
- msgs += ['pack "{}" defined at "{}"'.format(p.uid, p.origin)]
- msg += '{}.'.format(', '.join(msgs))
- raise UpdateError(pack.origin, msg)
- self._map[pack.uid] = pack
- for provides in pack.provides:
- if provides in self._map:
- raise UpdateError(
- pack.origin,
- 'Pack "{}" provides declaration conflicts with pack '
- '"{}" previously defined here: {}'.format(
- pack.uid, provides, self._map[provides].origin))
- self._provides[provides] += [pack.uid]
- # Check if the provides fills any open dependencies.
- if provides in self._missing:
- del self._missing[provides]
-
- self._origins[pack.origin.source_file] += [pack.uid]
- # Create a quick reference for which packs claim which files.
- # The conflict packs themselves can be found via copy.pack
- for copy in pack.copies:
- self._destinations[copy.dst] += [copy]
-
- # Check if this pack fulfills any open dependencies.
- if pack.uid in self._missing:
- del self._missing[pack.uid]
-
- # Check if this pack has any open dependencies.
- for requires in pack.requires:
- if (requires not in self._map and
- requires not in self._provides):
- self._missing[requires].append(pack.uid)
-
- def check_paths(self):
- """Checks the packmap for destination conflicts.
-
- As wildcards and recursion leave ambiguity, this is
- best effort to follow the fail-early user experience.
+ """PackMap provides a map of unique pack names to pack objects.
+
+ Additionally, PackMap acts as the centralized location for any
+ other indexing needed by its consumers to minimize inconsistency.
"""
- for dst, copies in self.copy_destinations.iteritems():
- if len(copies) > 1:
- raise common.PathConflictError(
- copies[0].origin,
- 'Multiple sources for one destination "{}": {}'.format(
- dst, [c.pack.uid for c in copies]))
+ def __init__(self):
+ # { pack.uid => pack }
+ self._map = {}
+ # { virtual_uid => [providing pack list]
+ self._provides = defaultdict(list)
+ # {source_files => [pack_uids] }
+ self._origins = defaultdict(list)
+ # [ required_uid => [requiring pack list] ]
+ self._missing = defaultdict(list)
+ # Imported Packs objects
+ self._packs = []
+ # { path => [Copy()] }
+ self._destinations = defaultdict(list)
+
+ @property
+ def map(self):
+ return self._map
+
+ @property
+ def copy_destinations(self):
+ return self._destinations
+
+ @property
+ def packs(self):
+ return self._packs
+
+ @property
+ def virtuals(self):
+ return self._provides
+
+ @property
+ def origins(self):
+ return self._origins
+
+ @property
+ def missing(self):
+ return self._missing
+
+ def submap(self, pack_uid, aliases):
+ """Returns a PackMap containing just the packs in a tree from @pack_uid.
+
+ Raises:
+ dependency.Error: if there are any unfulfilled dependencies.
+ """
+ pm = PackMap()
+ p = [self.map[pack_uid]]
+ pm.update(p)
+ while len(p) != 0:
+ # Iterate until we can no longer resolve pm.missing() from map
+ p = []
+ for uid in pm.missing:
+ if uid in self.map:
+ p.append(self.map[uid])
+ # Automatically pick an implementation if there is
+ # only one that matches a prefix above.
+ if uid in self.virtuals:
+ provides = []
+ for alias, prefix in aliases.iteritems():
+ if uid.startswith('{}.'.format(alias)):
+ provides += [x for x in self.virtuals[uid]
+ if x.startswith(prefix)]
+ if len(provides) == 1:
+ p.append(self.map[provides[0]])
+ pm.update(p)
+
+ unsatisfied = []
+ for missing, req_uids in pm.missing.iteritems():
+ if missing in self.virtuals:
+ unsatisfied.append(dependency.Virtual(
+ missing, pack_uid, req_uids, self.virtuals[missing]))
+ if len(unsatisfied):
+ raise dependency.UnsatisfiedVirtualPackError(unsatisfied)
+ if len(pm.missing):
+ pm.report_missing()
+ return pm
+
+ def report_missing(self):
+ """Raise a UndefinedPackError with useful text for all missing
+ required packages.
+ """
+ undef = []
+ for req_uid, needed_by in self.missing.iteritems():
+ undef.append(
+ dependency.Undefined(req_uid, [self.map[u] for u in needed_by]))
+ if len(undef):
+ raise dependency.UndefinedPackError(undef)
+
+ def update(self, packs_):
+ """Merges a Packs object (or container of Pack objects) into the
+ PackMap
+ """
+ self._packs += [packs_]
+ for pack in packs_:
+ if pack.uid in self._map:
+ # Check if the caller passed in an already seen pack by checking
+ # the origins.
+ if pack.origin == self._map[pack.uid].origin:
+ continue
+ raise UpdateError(
+ pack.origin,
+ ('Redefinition of pack "{}". Previously defined here: '
+ '{}'.format(pack.uid, self._map[pack.uid].origin)))
+ if pack.uid in self._provides:
+ prevs = [self._map[p] for p in self._provides[pack.uid]]
+ msg = 'Redefinition of virtual pack "{}". '.format(pack.uid)
+ msg += 'Previously declared as provided by '
+ msgs = []
+ for p in prevs:
+ msgs += ['pack "{}" defined at "{}"'.format(p.uid,
+ p.origin)]
+ msg += '{}.'.format(', '.join(msgs))
+ raise UpdateError(pack.origin, msg)
+ self._map[pack.uid] = pack
+ for provides in pack.provides:
+ if provides in self._map:
+ raise UpdateError(
+ pack.origin,
+ 'Pack "{}" provides declaration conflicts with pack '
+ '"{}" previously defined here: {}'.format(
+ pack.uid, provides, self._map[provides].origin))
+ self._provides[provides] += [pack.uid]
+ # Check if the provides fills any open dependencies.
+ if provides in self._missing:
+ del self._missing[provides]
+
+ self._origins[pack.origin.source_file] += [pack.uid]
+ # Create a quick reference for which packs claim which files.
+ # The conflict packs themselves can be found via copy.pack
+ for copy in pack.copies:
+ self._destinations[copy.dst] += [copy]
+
+ # Check if this pack fulfills any open dependencies.
+ if pack.uid in self._missing:
+ del self._missing[pack.uid]
+
+ # Check if this pack has any open dependencies.
+ for requires in pack.requires:
+ if (requires not in self._map
+ and requires not in self._provides):
+ self._missing[requires].append(pack.uid)
+
+ def check_paths(self):
+ """Checks the packmap for destination conflicts.
+
+ As wildcards and recursion leave ambiguity, this is
+ best effort to follow the fail-early user experience.
+ """
+ for dst, copies in self.copy_destinations.iteritems():
+ if len(copies) > 1:
+ raise common.PathConflictError(
+ copies[0].origin,
+ 'Multiple sources for one destination "{}": {}'.format(
+ dst, [c.pack.uid for c in copies]))
if __name__ == "__main__":
- # Example usage to remain until captured in unittests.
- import sys
- packmap = PackMap()
- re_ns = 0
- for files in sys.argv[1:]:
- my_packs = packs.PacksFactory().new(path=os.path.abspath(files))
- print 'Loaded packs from: {}'.format(os.path.abspath(files))
- if re_ns:
- my_packs.namespace = 'example.alias.ns'
- packmap.update(my_packs)
- re_ns = (re_ns + 1) % 2
- print 'Global packs: {}'.format(packmap.map.keys())
- print 'Global virtual packs: {}'.format(packmap.virtuals.keys())
- print 'Missing requirements: {}'.format(packmap.missing)
- spm = packmap.submap(packmap.map.keys()[1], '')
- print 'Submap for {}:'.format(packmap.map.keys()[1])
- print '--] map: {}'.format(spm.map.keys())
- print '--] virtuals: {}'.format(spm.virtuals.keys())
- print '--] missing: {}'.format(spm.missing.keys())
+ # Example usage to remain until captured in unittests.
+ import sys
+ packmap = PackMap()
+ re_ns = 0
+ for files in sys.argv[1:]:
+ my_packs = packs.PacksFactory().new(path=os.path.abspath(files))
+ print 'Loaded packs from: {}'.format(os.path.abspath(files))
+ if re_ns:
+ my_packs.namespace = 'example.alias.ns'
+ packmap.update(my_packs)
+ re_ns = (re_ns + 1) % 2
+ print 'Global packs: {}'.format(packmap.map.keys())
+ print 'Global virtual packs: {}'.format(packmap.virtuals.keys())
+ print 'Missing requirements: {}'.format(packmap.missing)
+ spm = packmap.submap(packmap.map.keys()[1], '')
+ print 'Submap for {}:'.format(packmap.map.keys()[1])
+ print '--] map: {}'.format(spm.map.keys())
+ print '--] virtuals: {}'.format(spm.virtuals.keys())
+ print '--] missing: {}'.format(spm.missing.keys())
diff --git a/cli/lib/project/packmap_stub.py b/cli/lib/project/packmap_stub.py
index 5bbfd57..4edef17 100644
--- a/cli/lib/project/packmap_stub.py
+++ b/cli/lib/project/packmap_stub.py
@@ -20,11 +20,11 @@
class StubPackMap(object):
- def __init__(self, pack_map=None, provides=None, missing=None, origins=None,
- packs=None, destinations=None):
- self.map = pack_map or {}
- self.provides = provides or {}
- self.missing = missing or {}
- self.origins = origins or {}
- self.copy_destinations = destinations or {}
- self.packs = packs or []
+ def __init__(self, pack_map=None, provides=None, missing=None, origins=None,
+ packs=None, destinations=None):
+ self.map = pack_map or {}
+ self.provides = provides or {}
+ self.missing = missing or {}
+ self.origins = origins or {}
+ self.copy_destinations = destinations or {}
+ self.packs = packs or []
diff --git a/cli/lib/project/packs.py b/cli/lib/project/packs.py
index 0d54c26..8927ac7 100644
--- a/cli/lib/project/packs.py
+++ b/cli/lib/project/packs.py
@@ -24,90 +24,91 @@ from project import pack
class PacksFactory(object):
- @staticmethod
- def new(**kwargs):
- """Creates a new Packs instance from an XML file or element.
-
- Walks an ElementTree element looking for <pack> child
- nodes and instantiates Pack objects.
-
- Args:
- **kwargs: Any valid keywords for loader.PathElementLoader.load()
-
- Returns:
- new Packs() instance
-
- Raises
- LoadError: LoadError, or a subclass, will be raised if an error occurs
- loading and validating content from the XML.
- """
- ps = Packs()
- l = loader.PathElementLoader('packs', ['namespace'])
- # Get the final root node.
- root = l.load(**kwargs)
- root.limit_attribs(['version', 'namespace', 'path'])
- ps.namespace = root.get_attrib('namespace')
- ns = ps.namespace
- # pylint: disable=no-member
- if 'namespace' in root.old_attrib:
- ns = root.old_attrib['namespace']
-
- packs = {}
- for node in root.findall('pack'):
- name = node.get_attrib('name')
- if name in packs:
- # Note, this only catch duplication within the same
- # packs tree. As packs namespaces are non-unique, collision
- # can occur when aggregating packs.
- raise common.LoadErrorWithOrigin(
- node,
- 'Duplicate pack {} in namespace {}'.format(name, ps.namespace))
- p = pack.Pack(ns, name)
- p.load(node)
- # Even if 'namespace' is carried over in the root attributes, we have to
- # alias any global requires/provides in the pack.
- if ns != ps.namespace:
- p.alias(ps.namespace)
- packs[name] = p
-
- ps.entries = packs
- ps.origins = l.origins
- return ps
+ @staticmethod
+ def new(**kwargs):
+ """Creates a new Packs instance from an XML file or element.
+
+ Walks an ElementTree element looking for <pack> child
+ nodes and instantiates Pack objects.
+
+ Args:
+ **kwargs: Any valid keywords for loader.PathElementLoader.load()
+
+ Returns:
+ new Packs() instance
+
+ Raises
+ LoadError: LoadError, or a subclass, will be raised if an error
+ occurs loading and validating content from the XML.
+ """
+ ps = Packs()
+ l = loader.PathElementLoader('packs', ['namespace'])
+ # Get the final root node.
+ root = l.load(**kwargs)
+ root.limit_attribs(['version', 'namespace', 'path'])
+ ps.namespace = root.get_attrib('namespace')
+ ns = ps.namespace
+ # pylint: disable=no-member
+ if 'namespace' in root.old_attrib:
+ ns = root.old_attrib['namespace']
+
+ packs = {}
+ for node in root.findall('pack'):
+ name = node.get_attrib('name')
+ if name in packs:
+ # Note, this only catch duplication within the same
+ # packs tree. As packs namespaces are non-unique, collision
+ # can occur when aggregating packs.
+ raise common.LoadErrorWithOrigin(
+ node,
+ 'Duplicate pack {} in namespace {}'.format(name,
+ ps.namespace))
+ p = pack.Pack(ns, name)
+ p.load(node)
+ # Even if 'namespace' is carried over in the root attributes, we
+ # have to alias any global requires/provides in the pack.
+ if ns != ps.namespace:
+ p.alias(ps.namespace)
+ packs[name] = p
+
+ ps.entries = packs
+ ps.origins = l.origins
+ return ps
class Packs(collection.Base):
- """Collection of Pack objects."""
- def __init__(self):
- super(Packs, self).__init__('packs')
- self._namespace = ()
-
- @property
- def packs(self):
- return self.entries
-
- @property
- def namespace(self):
- """Return the namespace for all child pack objects."""
- return self._namespace
-
- @namespace.setter
- def namespace(self, ns):
- for pack_ in self._entries.values():
- pack_.alias(ns)
- self._namespace = ns
-
- def add_pack(self, new_pack):
- if new_pack.name in self.entries:
- # Note, this only catch duplication within the same
- # packs tree. As packs namespaces are non-unique, collision
- # can occur when aggregating packs.
- raise common.LoadErrorWithOrigin(
- new_pack.origin,
- 'Duplicate pack {} in namespace {}'.format(new_pack.name,
- self.namespace))
- self.entries[new_pack.name] = new_pack
-
-
- def __repr__(self):
- return '<packs namespace="{}">{}</packs>'.format(
- self._namespace, ''.join([str(p) for p in self._entries.values()]))
+ """Collection of Pack objects."""
+ def __init__(self):
+ super(Packs, self).__init__('packs')
+ self._namespace = ()
+
+ @property
+ def packs(self):
+ return self.entries
+
+ @property
+ def namespace(self):
+ """Return the namespace for all child pack objects."""
+ return self._namespace
+
+ @namespace.setter
+ def namespace(self, ns):
+ for pack_ in self._entries.values():
+ pack_.alias(ns)
+ self._namespace = ns
+
+ def add_pack(self, new_pack):
+ if new_pack.name in self.entries:
+ # Note, this only catch duplication within the same
+ # packs tree. As packs namespaces are non-unique, collision
+ # can occur when aggregating packs.
+ raise common.LoadErrorWithOrigin(
+ new_pack.origin,
+ 'Duplicate pack {} in namespace {}'.format(new_pack.name,
+ self.namespace))
+ self.entries[new_pack.name] = new_pack
+
+
+ def __repr__(self):
+ return '<packs namespace="{}">{}</packs>'.format(
+ self._namespace, ''.join([str(p) for p in self._entries.values()]))
diff --git a/cli/lib/project/project_spec.py b/cli/lib/project/project_spec.py
index 9f89f64..21a2f2e 100644
--- a/cli/lib/project/project_spec.py
+++ b/cli/lib/project/project_spec.py
@@ -15,14 +15,14 @@
"""
- This file is the parsing entry point for BDK XML project.
+ This file is the parsing entry point for BDK XML project.
- BDK XML configuration is specified in the schemas/ path and
- all input files should be verifiable with a RELAX-NG verifier
- like 'jing'.
+ BDK XML configuration is specified in the schemas/ path and
+ all input files should be verifiable with a RELAX-NG verifier
+ like 'jing'.
- At present, this entry point only performs content validation
- and not formal syntax validation.
+ At present, this entry point only performs content validation
+ and not formal syntax validation.
"""
@@ -37,169 +37,172 @@ from project import xml_parser
class ProjectSpec(object):
- """ProjectSpec represents the primary interface to a user-defined project.
-
- The project is created from a BDK project XML file (defined by
- the bdk.rng RELAX NG schema).
- """
- def __init__(self):
- self._version = 1
- self._targets = {}
- self._config = config.Config()
- self._packmap = packmap.PackMap()
- self._origin = common.Origin()
-
- @property
- def config(self):
- """Returns the Config object."""
- return self._config
-
- @config.setter
- def config(self, config_):
- self._config = config_
-
- @property
- def packmap(self):
- """Returns the PackMap object."""
- return self._packmap
-
- def add_target(self, target):
- if target.name in self._targets:
- raise common.LoadErrorWithOrigin(
- target.origin,
- 'Target "{}" redefined. Previously definition here: "{}"'.format(
- target.name, self._targets[target.name].origin))
- self._targets[target.name] = target
-
- def add_packs(self, packs_obj):
- self._packmap.update(packs_obj)
-
- @property
- def targets(self):
- """Returns a dict of Targets keyed by name."""
- return self._targets
-
- @property
- def origin(self):
- return self._origin
-
- @origin.setter
- def origin(self, o):
- self._origin = o.copy()
-
- @property
- def version(self):
- return self._version
-
- @version.setter
- def version(self, v):
- self._version = v
-
- def __repr__(self):
- return ('<project version="{}" origins="{}">'
- '{}{}{}</project>').format(
- self._version, self.packmap.origins, self._config,
- self.packmap.packs, self.targets)
-
- @classmethod
- def from_xml(cls, src='project.xml'):
- """Populates this instance with the ProjectSpec from file.
-
- To validate the resulting ProjectSpec,
- config.packmap.report_missing()
- may be called to identify globally undefined packs.
-
- And config.targets[t].create_submap(config.packmap)
- must be called to identify unsatisfied dependencies or undefined
- pack names per-target.
-
- Args:
- src: If str, a path to XML file defining a <project>.
- If file compatible, the XML file.
-
- Returns:
- instance of ProjectSpec
-
- Raises:
- IOError
- common.LoadError or ones of its subclasses:
- common.LoadErrorWithOrigin
- common.PathConflictError
- packmap.UpdateError
- """
- project_spec = cls()
- if isinstance(src, basestring):
- src = os.path.abspath(src)
- tree = xml_parser.parse(src)
- root = tree.getroot()
- # pylint: disable=no-member
- project_spec.origin = root.origin
- root.limit_attribs(['version'])
- # Ensure we have a project document.
- if root.tag != 'project':
- # pylint: disable=no-member
- raise common.LoadErrorWithOrigin(root.origin,
- 'root node not <project>')
-
- if 'version' in root.attrib:
- project_spec.version = root.attrib['version']
-
- # Pull in each top level element.
- cfg = root.findall('config')
- if len(cfg) > 1:
- raise common.LoadErrorWithOrigin(
- cfg[1].origin, 'Only one <config> element may be defined.')
- if len(cfg):
- project_spec.config = config.Config.from_element(cfg[0])
- cls.collect_packs(root, project_spec.add_packs)
- cls.collect_targets(root, project_spec.packmap, project_spec.add_target)
- if (project_spec.config.default_target and
- project_spec.config.default_target not in project_spec.targets):
- # TODO(wad) The origin could be made more specific we keep the origins
- # per-child around.
- raise common.LoadErrorWithOrigin(
- project_spec.config.origin,
- 'default target "{}" is not defined.'.format(
- project_spec.config.default_target))
- # If there's only one target and no default, set the default to that one.
- if (not project_spec.config.default_target and
- len(project_spec.targets)) == 1:
- project_spec.config.default_target = project_spec.targets.keys()[0]
- return project_spec
-
- @staticmethod
- def collect_packs(root_node, callback):
- """Collects all <Packs> passing them to callback(packs)."""
- for node in root_node.findall('packs'):
- callback(packs.PacksFactory().new(element=node))
-
- @staticmethod
- def collect_targets(root, packmap_, callback):
- """Collects all <target> from <targets> passing them to callback(target)."""
- for node in root.findall('targets'):
- t = targets.TargetsFactory.new(packmap_, element=node)
- for tgt in t.targets.values():
- callback(tgt)
-
- def get_target(self, target_name=None):
- """Gets a target by name, or the default target if none is specified.
-
- Raises:
- KeyError: if the spec does not have the requested target,
- or the default target is requested but not defined.
+ """ProjectSpec represents the primary interface to a user-defined project.
+
+ The project is created from a BDK project XML file (defined by
+ the bdk.rng RELAX NG schema).
"""
- # Try to find a default target if not specified.
- if not target_name:
- if self.config.default_target:
- target_name = self.config.default_target
- else:
- raise KeyError('{}: No default target could be found '
- 'in project spec. Possible targets are: {}.'.format(
- self.origin, self.targets.keys()))
-
- # Check that the desired target exists.
- if target_name not in self.targets:
- raise KeyError('{}: No such target "{}" (options are: {})'.format(
- self.origin, target_name, self.targets.keys()))
-
- return self.targets[target_name]
+ def __init__(self):
+ self._version = 1
+ self._targets = {}
+ self._config = config.Config()
+ self._packmap = packmap.PackMap()
+ self._origin = common.Origin()
+
+ @property
+ def config(self):
+ """Returns the Config object."""
+ return self._config
+
+ @config.setter
+ def config(self, config_):
+ self._config = config_
+
+ @property
+ def packmap(self):
+ """Returns the PackMap object."""
+ return self._packmap
+
+ def add_target(self, target):
+ if target.name in self._targets:
+ raise common.LoadErrorWithOrigin(
+ target.origin,
+ ('Target "{}" redefined. Previously definition here: '
+ '"{}"'.format(target.name, self._targets[target.name].origin)))
+ self._targets[target.name] = target
+
+ def add_packs(self, packs_obj):
+ self._packmap.update(packs_obj)
+
+ @property
+ def targets(self):
+ """Returns a dict of Targets keyed by name."""
+ return self._targets
+
+ @property
+ def origin(self):
+ return self._origin
+
+ @origin.setter
+ def origin(self, o):
+ self._origin = o.copy()
+
+ @property
+ def version(self):
+ return self._version
+
+ @version.setter
+ def version(self, v):
+ self._version = v
+
+ def __repr__(self):
+ return ('<project version="{}" origins="{}">'
+ '{}{}{}</project>').format(
+ self._version, self.packmap.origins, self._config,
+ self.packmap.packs, self.targets)
+
+ @classmethod
+ def from_xml(cls, src='project.xml'):
+ """Populates this instance with the ProjectSpec from file.
+
+ To validate the resulting ProjectSpec,
+ config.packmap.report_missing()
+ may be called to identify globally undefined packs.
+
+ And config.targets[t].create_submap(config.packmap)
+ must be called to identify unsatisfied dependencies or undefined
+ pack names per-target.
+
+ Args:
+ src: If str, a path to XML file defining a <project>.
+ If file compatible, the XML file.
+
+ Returns:
+ instance of ProjectSpec
+
+ Raises:
+ IOError
+ common.LoadError or ones of its subclasses:
+ common.LoadErrorWithOrigin
+ common.PathConflictError
+ packmap.UpdateError
+ """
+ project_spec = cls()
+ if isinstance(src, basestring):
+ src = os.path.abspath(src)
+ tree = xml_parser.parse(src)
+ root = tree.getroot()
+ # pylint: disable=no-member
+ project_spec.origin = root.origin
+ root.limit_attribs(['version'])
+ # Ensure we have a project document.
+ if root.tag != 'project':
+ # pylint: disable=no-member
+ raise common.LoadErrorWithOrigin(root.origin,
+ 'root node not <project>')
+
+ if 'version' in root.attrib:
+ project_spec.version = root.attrib['version']
+
+ # Pull in each top level element.
+ cfg = root.findall('config')
+ if len(cfg) > 1:
+ raise common.LoadErrorWithOrigin(
+ cfg[1].origin, 'Only one <config> element may be defined.')
+ if len(cfg):
+ project_spec.config = config.Config.from_element(cfg[0])
+ cls.collect_packs(root, project_spec.add_packs)
+ cls.collect_targets(root, project_spec.packmap, project_spec.add_target)
+ if (project_spec.config.default_target and
+ project_spec.config.default_target not in project_spec.targets):
+ # TODO(wad) The origin could be made more specific we keep the
+ # origins per-child around.
+ raise common.LoadErrorWithOrigin(
+ project_spec.config.origin,
+ 'default target "{}" is not defined.'.format(
+ project_spec.config.default_target))
+ # If there's only one target and no default, set the default to that
+ # one.
+ if (not project_spec.config.default_target and
+ len(project_spec.targets)) == 1:
+ project_spec.config.default_target = project_spec.targets.keys()[0]
+ return project_spec
+
+ @staticmethod
+ def collect_packs(root_node, callback):
+ """Collects all <Packs> passing them to callback(packs)."""
+ for node in root_node.findall('packs'):
+ callback(packs.PacksFactory().new(element=node))
+
+ @staticmethod
+ def collect_targets(root, packmap_, callback):
+ """Collects all <target> from <targets> passing them to
+ callback(target).
+ """
+ for node in root.findall('targets'):
+ t = targets.TargetsFactory.new(packmap_, element=node)
+ for tgt in t.targets.values():
+ callback(tgt)
+
+ def get_target(self, target_name=None):
+ """Gets a target by name, or the default target if none is specified.
+
+ Raises:
+ KeyError: if the spec does not have the requested target,
+ or the default target is requested but not defined.
+ """
+ # Try to find a default target if not specified.
+ if not target_name:
+ if self.config.default_target:
+ target_name = self.config.default_target
+ else:
+ raise KeyError('{}: No default target could be found '
+ 'in project spec. Possible targets are: '
+ '{}.'.format(self.origin, self.targets.keys()))
+
+ # Check that the desired target exists.
+ if target_name not in self.targets:
+ raise KeyError('{}: No such target "{}" (options are: {})'.format(
+ self.origin, target_name, self.targets.keys()))
+
+ return self.targets[target_name]
diff --git a/cli/lib/project/project_spec_stub.py b/cli/lib/project/project_spec_stub.py
index 86a27bb..445de21 100644
--- a/cli/lib/project/project_spec_stub.py
+++ b/cli/lib/project/project_spec_stub.py
@@ -23,35 +23,35 @@ from project import config_stub
class StubProjectSpec(object):
- def __init__(self, targets=None, file_default_target='', file_targets=None,
- should_read_file=None, packmap=None):
- self.targets = targets or {}
- self.file_default_target = file_default_target
- self.file_targets = file_targets or {}
- self.should_read_file = should_read_file or []
- self.packmap = packmap
- self.config = None
-
- def add_packs(self, packs_obj):
- self.packmap.update(packs_obj)
-
- def from_xml(self, f):
- if not f in self.should_read_file:
- raise IOError('Not supposed to read from file "{}"'.format(f))
- self.targets = self.file_targets
- self.config = config_stub.StubConfig(self.file_default_target)
- self.should_read_file.remove(f)
- return self
+ def __init__(self, targets=None, file_default_target='', file_targets=None,
+ should_read_file=None, packmap=None):
+ self.targets = targets or {}
+ self.file_default_target = file_default_target
+ self.file_targets = file_targets or {}
+ self.should_read_file = should_read_file or []
+ self.packmap = packmap
+ self.config = None
+
+ def add_packs(self, packs_obj):
+ self.packmap.update(packs_obj)
+
+ def from_xml(self, f):
+ if not f in self.should_read_file:
+ raise IOError('Not supposed to read from file "{}"'.format(f))
+ self.targets = self.file_targets
+ self.config = config_stub.StubConfig(self.file_default_target)
+ self.should_read_file.remove(f)
+ return self
class StubProjectSpecGenerator(object):
- def __init__(self):
- self.targets = {}
- self.file_default_target = ''
- self.file_targets = {}
- self.should_read_file = []
-
- @property
- def ProjectSpec(self):
- return StubProjectSpec(self.targets, self.file_default_target,
- self.file_targets, self.should_read_file)
+ def __init__(self):
+ self.targets = {}
+ self.file_default_target = ''
+ self.file_targets = {}
+ self.should_read_file = []
+
+ @property
+ def ProjectSpec(self):
+ return StubProjectSpec(self.targets, self.file_default_target,
+ self.file_targets, self.should_read_file)
diff --git a/cli/lib/project/project_spec_unittest.py b/cli/lib/project/project_spec_unittest.py
index 3748aec..f105536 100644
--- a/cli/lib/project/project_spec_unittest.py
+++ b/cli/lib/project/project_spec_unittest.py
@@ -82,205 +82,208 @@ BDK_XML_TWO_TARGETS = '''
class ProjectSpecTest(unittest.TestCase):
- def setUp(self):
- os = StringIO.StringIO(MINIMAL_OS_XML)
- board = StringIO.StringIO(MINIMAL_BOARD_XML)
- self.minimal_board = packs.PacksFactory().new(file=board)
- self.minimal_os = packs.PacksFactory().new(file=os)
+ def setUp(self):
+ os = StringIO.StringIO(MINIMAL_OS_XML)
+ board = StringIO.StringIO(MINIMAL_BOARD_XML)
+ self.minimal_board = packs.PacksFactory().new(file=board)
+ self.minimal_os = packs.PacksFactory().new(file=os)
- def tearDown(self):
- pass
+ def tearDown(self):
+ pass
- def add_minimal_packs(self, project_spec_):
- for ps in (self.minimal_os, self.minimal_board):
- project_spec_.packmap.update(ps)
+ def add_minimal_packs(self, project_spec_):
+ for ps in (self.minimal_os, self.minimal_board):
+ project_spec_.packmap.update(ps)
- def project_spec_from_str(self, xml):
- s = StringIO.StringIO(xml)
- b = project_spec.ProjectSpec.from_xml(s)
- return b
+ def project_spec_from_str(self, xml):
+ s = StringIO.StringIO(xml)
+ b = project_spec.ProjectSpec.from_xml(s)
+ return b
class ParseTest(ProjectSpecTest):
- def test_minimal_ok(self):
- spec = self.project_spec_from_str(MINIMAL_BDK_XML)
- # Only 1 target, should become default target.
- self.assertEqual(spec.config.default_target, 'sample.ledflasher')
-
- def test_two_targets_ok(self):
- spec = self.project_spec_from_str(BDK_XML_TWO_TARGETS)
- # 2 targets, neither should be the default.
- self.assertFalse(spec.config.default_target)
-
- def test_minimal_populated(self):
- b = self.project_spec_from_str(MINIMAL_BDK_XML)
- self.add_minimal_packs(b)
- b.packmap.report_missing()
-
- def test_minimal_no_os(self):
- b = self.project_spec_from_str(MINIMAL_BDK_XML)
- # There will be no global suppliers of "os.core".
- with self.assertRaises(dependency.UndefinedPackError):
- b.packmap.report_missing()
-
- def test_minimal_wrong_os_version(self):
- b = self.project_spec_from_str(MINIMAL_BDK_XML)
- # Create a mismatched os pack version.
- self.minimal_os.namespace = 'brillo.2'
- self.minimal_board.namespace = 'edison.2'
- self.add_minimal_packs(b)
- with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
- b.targets['sample.ledflasher'].create_submap(b.packmap)
-
- def test_minimal_wrong_board_version(self):
- b = self.project_spec_from_str(MINIMAL_BDK_XML)
- # Create a mismatched os pack version.
- self.minimal_board.namespace = 'edison.2'
- self.add_minimal_packs(b)
- with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
- b.targets['sample.ledflasher'].create_submap(b.packmap)
-
- def test_minimal_requires_missing_pack(self):
- xml = MINIMAL_BDK_XML.replace('os.core', 'missing.pack')
- b = self.project_spec_from_str(xml)
- # Create a mismatched os pack version.
- self.add_minimal_packs(b)
- with self.assertRaises(dependency.UndefinedPackError):
- b.packmap.report_missing()
- with self.assertRaises(dependency.UndefinedPackError):
- b.targets['sample.ledflasher'].create_submap(b.packmap)
-
- def test_minimal_requires_overpopulated_os_virtual(self):
- b = self.project_spec_from_str(MINIMAL_BDK_XML)
- self.add_minimal_packs(b)
- # Add an extra pack that provides os.core so that the
- # autoselection for os prefix fails.
- os = StringIO.StringIO(MINIMAL_OS_XML.replace(
- 'all_of_brillo', 'the_other_half'))
- os_dupe = packs.PacksFactory().new(file=os)
- b.packmap.update(os_dupe)
- # No global undefinedness errors.
- b.packmap.report_missing()
- with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
- b.targets['sample.ledflasher'].create_submap(b.packmap)
-
- def test_minimal_requires_unfulfilled_virtual(self):
- b = self.project_spec_from_str(
- MINIMAL_BDK_XML.replace('os.core', 'some.pack'))
- self.add_minimal_packs(b)
- # Create a single implementation for the virtual, but since it will
- # not be autoselected by the os-prefix, an error will be raised that
- # suggests this new pack: project_extras.all_of_brillo.
- os = StringIO.StringIO(MINIMAL_OS_XML.replace(
- 'brillo.1', 'project_extras').replace('os.core', 'some.pack'))
- os_dupe = packs.PacksFactory().new(file=os)
- b.packmap.update(os_dupe)
- b.packmap.report_missing()
- with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
- b.targets['sample.ledflasher'].create_submap(b.packmap)
-
- def test_bdk_complex_no_os(self):
- p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
- b = project_spec.ProjectSpec.from_xml(p)
- with self.assertRaises(dependency.UndefinedPackError) as upe:
- # Take the default target and it should fail.
- dfl = b.targets[b.config.default_target]
- dfl.create_submap(b.packmap)
- for line in str(upe.exception).split('\n'):
- # Expect only undefined os packs.
- self.assertIn('Undefined pack "os.', line)
-
- def test_bdk_complex_wrong_os(self):
- p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
- b = project_spec.ProjectSpec.from_xml(p)
- os_paths = ['schema/testcases/pass/brillo.12.xml',
- 'schema/testcases/pass/brillo.12.common.xml']
- for p in os_paths:
- p = util.GetBDKPath(p)
- b.packmap.update(packs.PacksFactory().new(path=p))
- with self.assertRaises(dependency.UnsatisfiedVirtualPackError) as uvpe:
- for tgt in b.targets.values():
- # Test all targets we didnt load an os for.
- if tgt.os != 'brillo' or tgt.os_version != '12':
- tgt.create_submap(b.packmap)
-
- for line in str(uvpe.exception).split('\n'):
- # Expect candidates from brillo.12 because the first target is brillo.8
- # and will be unable to satisfy virtuals from the os.
- self.assertIn('may satisfy the requirement: [\'brillo.12.', line)
-
- def test_bdk_complex_fulfilled(self):
- p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
- b = project_spec.ProjectSpec.from_xml(p)
- os_paths = ['schema/testcases/pass/brillo.12.xml',
- 'schema/testcases/pass/brillo.12.common.xml',
- 'schema/testcases/pass/brillo.8.xml',
- 'schema/testcases/pass/brillo.8.common.xml']
- for p in os_paths:
- p = util.GetBDKPath(p)
- b.packmap.update(packs.PacksFactory().new(path=p))
- # No undefined packs
- b.packmap.report_missing()
- expected_target_names = ['doorman_ed', 'doorman_db', 'doorman_ab',
- 'doorman_2_db', 'doorman_2_ed', 'doorman_2_ab',
- 'doorman_2_cba']
- self.assertEqual(expected_target_names, b.targets.keys())
- expected_doorman_2_cba_packs = [
- 'speech.synth_service', '3p.opencv.libopencv', 'products.doorman',
- 'soc.hw.vision.libface_offload', 'brillo.12.0.0.audiorec',
- 'products.doorman_v2', '3p.cloud.voice_api', 'products.doorman_v2_hw',
- 'speech.local_synth', 'brillo.12.0.0.core_stuff', 'cam.facerecr_v2',
- 'mic.cloud_voicerec', 'mic.local_voicerec',
- 'brillo.12.0.0.new_super_cam', '3p.sndobj.libsndobj',
- 'brillo.12.0.0.peripheral_iod'].sort()
- t = b.targets['doorman_2_cba']
- tgt_map = t.create_submap(b.packmap)
- self.assertEqual(expected_doorman_2_cba_packs, tgt_map.map.keys().sort())
- expected_files = [
- '/system/bin/cloud_voiced', '/lib/libfacerecr-2.0.so',
- '/system/etc/init.d/local_voiced.init', '/lib/facerecrd',
- '/system/etc/init.d/cloud_voiced.init', '/system/bin/',
- '/system/lib/libsndobj-1.2.so', '/data/voice_api.wsdl',
- '/system/lib/libcloudvoice.so', '/data/speech/training/',
- '/system/bin/local_voiced', '/system/lib/libspeech-be.so',
- '/system/etc/init.d/facerecerd.init',
- '/system/lib/libface_offload.so'].sort()
- self.assertEqual(expected_files, tgt_map.copy_destinations.keys().sort())
- # Make sure all other packs are satisfied.
- for tgt in b.targets.values():
- tgt.create_submap(b.packmap)
+ def test_minimal_ok(self):
+ spec = self.project_spec_from_str(MINIMAL_BDK_XML)
+ # Only 1 target, should become default target.
+ self.assertEqual(spec.config.default_target, 'sample.ledflasher')
+
+ def test_two_targets_ok(self):
+ spec = self.project_spec_from_str(BDK_XML_TWO_TARGETS)
+ # 2 targets, neither should be the default.
+ self.assertFalse(spec.config.default_target)
+
+ def test_minimal_populated(self):
+ b = self.project_spec_from_str(MINIMAL_BDK_XML)
+ self.add_minimal_packs(b)
+ b.packmap.report_missing()
+
+ def test_minimal_no_os(self):
+ b = self.project_spec_from_str(MINIMAL_BDK_XML)
+ # There will be no global suppliers of "os.core".
+ with self.assertRaises(dependency.UndefinedPackError):
+ b.packmap.report_missing()
+
+ def test_minimal_wrong_os_version(self):
+ b = self.project_spec_from_str(MINIMAL_BDK_XML)
+ # Create a mismatched os pack version.
+ self.minimal_os.namespace = 'brillo.2'
+ self.minimal_board.namespace = 'edison.2'
+ self.add_minimal_packs(b)
+ with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
+ b.targets['sample.ledflasher'].create_submap(b.packmap)
+
+ def test_minimal_wrong_board_version(self):
+ b = self.project_spec_from_str(MINIMAL_BDK_XML)
+ # Create a mismatched os pack version.
+ self.minimal_board.namespace = 'edison.2'
+ self.add_minimal_packs(b)
+ with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
+ b.targets['sample.ledflasher'].create_submap(b.packmap)
+
+ def test_minimal_requires_missing_pack(self):
+ xml = MINIMAL_BDK_XML.replace('os.core', 'missing.pack')
+ b = self.project_spec_from_str(xml)
+ # Create a mismatched os pack version.
+ self.add_minimal_packs(b)
+ with self.assertRaises(dependency.UndefinedPackError):
+ b.packmap.report_missing()
+ with self.assertRaises(dependency.UndefinedPackError):
+ b.targets['sample.ledflasher'].create_submap(b.packmap)
+
+ def test_minimal_requires_overpopulated_os_virtual(self):
+ b = self.project_spec_from_str(MINIMAL_BDK_XML)
+ self.add_minimal_packs(b)
+ # Add an extra pack that provides os.core so that the
+ # autoselection for os prefix fails.
+ os = StringIO.StringIO(MINIMAL_OS_XML.replace(
+ 'all_of_brillo', 'the_other_half'))
+ os_dupe = packs.PacksFactory().new(file=os)
+ b.packmap.update(os_dupe)
+ # No global undefinedness errors.
+ b.packmap.report_missing()
+ with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
+ b.targets['sample.ledflasher'].create_submap(b.packmap)
+
+ def test_minimal_requires_unfulfilled_virtual(self):
+ b = self.project_spec_from_str(
+ MINIMAL_BDK_XML.replace('os.core', 'some.pack'))
+ self.add_minimal_packs(b)
+ # Create a single implementation for the virtual, but since it will
+ # not be autoselected by the os-prefix, an error will be raised that
+ # suggests this new pack: project_extras.all_of_brillo.
+ os = StringIO.StringIO(MINIMAL_OS_XML.replace(
+ 'brillo.1', 'project_extras').replace('os.core', 'some.pack'))
+ os_dupe = packs.PacksFactory().new(file=os)
+ b.packmap.update(os_dupe)
+ b.packmap.report_missing()
+ with self.assertRaises(dependency.UnsatisfiedVirtualPackError):
+ b.targets['sample.ledflasher'].create_submap(b.packmap)
+
+ def test_bdk_complex_no_os(self):
+ p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
+ b = project_spec.ProjectSpec.from_xml(p)
+ with self.assertRaises(dependency.UndefinedPackError) as upe:
+ # Take the default target and it should fail.
+ dfl = b.targets[b.config.default_target]
+ dfl.create_submap(b.packmap)
+ for line in str(upe.exception).split('\n'):
+ # Expect only undefined os packs.
+ self.assertIn('Undefined pack "os.', line)
+
+ def test_bdk_complex_wrong_os(self):
+ p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
+ b = project_spec.ProjectSpec.from_xml(p)
+ os_paths = ['schema/testcases/pass/brillo.12.xml',
+ 'schema/testcases/pass/brillo.12.common.xml']
+ for p in os_paths:
+ p = util.GetBDKPath(p)
+ b.packmap.update(packs.PacksFactory().new(path=p))
+ with self.assertRaises(dependency.UnsatisfiedVirtualPackError) as uvpe:
+ for tgt in b.targets.values():
+ # Test all targets we didnt load an os for.
+ if tgt.os != 'brillo' or tgt.os_version != '12':
+ tgt.create_submap(b.packmap)
+
+ for line in str(uvpe.exception).split('\n'):
+ # Expect candidates from brillo.12 because the first target is
+ # brillo.8 and will be unable to satisfy virtuals from the os.
+ self.assertIn('may satisfy the requirement: [\'brillo.12.', line)
+
+ def test_bdk_complex_fulfilled(self):
+ p = util.GetBDKPath('schema/testcases/pass/bdk_complex.xml')
+ b = project_spec.ProjectSpec.from_xml(p)
+ os_paths = ['schema/testcases/pass/brillo.12.xml',
+ 'schema/testcases/pass/brillo.12.common.xml',
+ 'schema/testcases/pass/brillo.8.xml',
+ 'schema/testcases/pass/brillo.8.common.xml']
+ for p in os_paths:
+ p = util.GetBDKPath(p)
+ b.packmap.update(packs.PacksFactory().new(path=p))
+ # No undefined packs
+ b.packmap.report_missing()
+ expected_target_names = ['doorman_ed', 'doorman_db', 'doorman_ab',
+ 'doorman_2_db', 'doorman_2_ed', 'doorman_2_ab',
+ 'doorman_2_cba']
+ self.assertEqual(expected_target_names, b.targets.keys())
+ expected_doorman_2_cba_packs = [
+ 'speech.synth_service', '3p.opencv.libopencv', 'products.doorman',
+ 'soc.hw.vision.libface_offload', 'brillo.12.0.0.audiorec',
+ 'products.doorman_v2', '3p.cloud.voice_api',
+ 'products.doorman_v2_hw', 'speech.local_synth',
+ 'brillo.12.0.0.core_stuff', 'cam.facerecr_v2',
+ 'mic.cloud_voicerec', 'mic.local_voicerec',
+ 'brillo.12.0.0.new_super_cam', '3p.sndobj.libsndobj',
+ 'brillo.12.0.0.peripheral_iod'].sort()
+ t = b.targets['doorman_2_cba']
+ tgt_map = t.create_submap(b.packmap)
+ self.assertEqual(expected_doorman_2_cba_packs,
+ tgt_map.map.keys().sort())
+ expected_files = [
+ '/system/bin/cloud_voiced', '/lib/libfacerecr-2.0.so',
+ '/system/etc/init.d/local_voiced.init', '/lib/facerecrd',
+ '/system/etc/init.d/cloud_voiced.init', '/system/bin/',
+ '/system/lib/libsndobj-1.2.so', '/data/voice_api.wsdl',
+ '/system/lib/libcloudvoice.so', '/data/speech/training/',
+ '/system/bin/local_voiced', '/system/lib/libspeech-be.so',
+ '/system/etc/init.d/facerecerd.init',
+ '/system/lib/libface_offload.so'].sort()
+ self.assertEqual(expected_files,
+ tgt_map.copy_destinations.keys().sort())
+ # Make sure all other packs are satisfied.
+ for tgt in b.targets.values():
+ tgt.create_submap(b.packmap)
class ProjectSpecMethodsTest(unittest.TestCase):
- def setUp(self):
- self.spec = project_spec.ProjectSpec()
- self.target_a = target_stub.StubTarget('a')
- self.spec.add_target(self.target_a)
- self.target_b = target_stub.StubTarget('b')
- self.spec.add_target(self.target_b)
- self.spec.config = config_stub.StubConfig(default_target='b')
-
- def test_get_target(self):
- self.assertEqual(self.spec.get_target('a'), self.target_a)
-
- def test_get_default_target(self):
- self.assertEqual(self.spec.get_target(), self.target_b)
-
- def test_get_target_invalid(self):
- with self.assertRaises(KeyError):
- self.spec.get_target('c')
-
- def test_get_target_no_default(self):
- self.spec.config.default_target = None
- with self.assertRaises(KeyError):
- self.spec.get_target()
-
- def test_add_target(self):
- target_c = target_stub.StubTarget('c')
- self.spec.add_target(target_c)
- self.assertEqual(self.spec.get_target('c'), target_c)
-
- def test_add_target_name_conflict(self):
- target_b_2 = target_stub.StubTarget('b')
- with self.assertRaises(common.LoadErrorWithOrigin):
- self.spec.add_target(target_b_2)
+ def setUp(self):
+ self.spec = project_spec.ProjectSpec()
+ self.target_a = target_stub.StubTarget('a')
+ self.spec.add_target(self.target_a)
+ self.target_b = target_stub.StubTarget('b')
+ self.spec.add_target(self.target_b)
+ self.spec.config = config_stub.StubConfig(default_target='b')
+
+ def test_get_target(self):
+ self.assertEqual(self.spec.get_target('a'), self.target_a)
+
+ def test_get_default_target(self):
+ self.assertEqual(self.spec.get_target(), self.target_b)
+
+ def test_get_target_invalid(self):
+ with self.assertRaises(KeyError):
+ self.spec.get_target('c')
+
+ def test_get_target_no_default(self):
+ self.spec.config.default_target = None
+ with self.assertRaises(KeyError):
+ self.spec.get_target()
+
+ def test_add_target(self):
+ target_c = target_stub.StubTarget('c')
+ self.spec.add_target(target_c)
+ self.assertEqual(self.spec.get_target('c'), target_c)
+
+ def test_add_target_name_conflict(self):
+ target_b_2 = target_stub.StubTarget('b')
+ with self.assertRaises(common.LoadErrorWithOrigin):
+ self.spec.add_target(target_b_2)
diff --git a/cli/lib/project/target.py b/cli/lib/project/target.py
index 9a28d65..719b6d8 100644
--- a/cli/lib/project/target.py
+++ b/cli/lib/project/target.py
@@ -21,7 +21,6 @@ import os
from bsp import manifest
from core import user_config
-from core import util
from project import common
@@ -33,156 +32,162 @@ PLATFORM_CACHE_FORMAT = os.path.join('{user_configured_platform_cache}',
class Error(common.Error):
- """General Error for targets."""
+ """General Error for targets."""
class BoardError(Error):
- """Raised when there is a problem with the specified board/version."""
- description = 'Invalid board'
+ """Raised when there is a problem with the specified board/version."""
+ description = 'Invalid board'
class Target(object):
- """A target for a project.
-
- Specifies what a target consists of, and what platform
- it is intended for.
- """
-
- def __init__(self, name, os='', os_version='', board='', board_version=''):
- self._name = name
- self._pack = None
- self._pack_name = ''
- self._os = os
- self._os_version = os_version
- self._board = board
- self._board_version = board_version
- self._build = 'userdebug'
- self._origin = common.Origin()
-
- def __repr__(self):
- return ('<target name="{}" os="{}" os-version="{}" '
- 'board="{}" board-version="{}" build="{}" pack="{}"/>').format(
- self._name, self._os, self._os_version, self._board,
- self._board_version, self._build, self._pack_name)
-
- @property
- def name(self):
- return self._name
-
- @property
- def pack_name(self):
- return self._pack_name
-
- @property
- def pack(self):
- return self._pack
-
- @property
- def origin(self):
- return self._origin
-
- @property
- def os(self):
- return self._os
-
- @os.setter
- def os(self, b):
- self._os = b
-
- @property
- def os_namespace(self):
- return '{}.{}'.format(self.os, self.os_version)
-
- @property
- def os_version(self):
- return self._os_version
-
- @os_version.setter
- def os_version(self, b):
- self._os_version = b
-
- @property
- def board(self):
- return self._board
-
- @board.setter
- def board(self, b):
- self._board = b
-
- @property
- def board_version(self):
- return self._board_version
-
- @board_version.setter
- def board_version(self, b):
- self._board_version = b
-
- @property
- def board_namespace(self):
- return '{}.{}'.format(self.board, self.board_version)
-
- @property
- def build_type(self):
- return self._build
-
- def get_device(self):
- """Get the bsp.Device associated with this Target.
-
- Returns:
- The bsp.Device associated with this Target.
-
- Raises:
- BoardError: If the target board and version is not in the manifest.
- """
- bsp_manifest = manifest.Manifest.from_json()
- board = bsp_manifest.devices.get(self.board)
- if not board:
- raise BoardError('unrecognized name "{}". '
- 'Run `bdk bsp list` to see available boards.'.format(
- self.board))
- if board.version != self.board_version:
- raise BoardError('"{}" only has version {} (requested {}).'.format(
- self.board, board.version, self.board_version))
- return board
-
- def platform_cache(self, *relpath):
- return os.path.join(PLATFORM_CACHE_FORMAT.format(
- user_configured_platform_cache=user_config.USER_CONFIG.platform_cache,
- os=self.os,
- os_version=self.os_version,
- board=self.board,
- board_version=self.board_version),
- *relpath)
-
- def platform_build_cache(self, *relpath):
- return self.platform_cache(self.build_type, *relpath)
-
- def load(self, ele):
- """Populates this instance from Element @ele."""
- self._origin = ele.origin.copy()
- ele.limit_attribs(['name', 'pack', 'os', 'board',
- 'os-version', 'board-version', 'build'])
- self._pack_name = ele.get_attrib('pack')
- self._os = ele.get_attrib('os')
- self._os_version = ele.get_attrib('os-version')
- self._board = ele.get_attrib('board')
- self._board_version = ele.get_attrib('board-version')
- if 'build' in ele.attrib:
- self._build = ele.get_attrib('build')
-
- def create_submap(self, global_packmap):
- """Create a packmap from the global packmap.
-
- Prior to calling, populate the global packmap with any
- requires OS or Board packs.
-
- Args:
- global_packmap: A packmap of all available packs.
-
- Returns:
- A submap of global_packmap for this target, with paths validated.
+ """A target for a project.
+
+ Specifies what a target consists of, and what platform
+ it is intended for.
"""
- aliases = {'os': self.os_namespace, 'board': self.board_namespace}
- packmap = global_packmap.submap(self._pack_name, aliases)
- packmap.check_paths()
- return packmap
+
+ # TODO(b/28296932): Rename the 'os' input variable to not conflict with the
+ # import.
+ # pylint: disable=redefined-outer-name
+ def __init__(self, name, os='', os_version='', board='',
+ board_version=''):
+ self._name = name
+ self._pack = None
+ self._pack_name = ''
+ self._os = os
+ self._os_version = os_version
+ self._board = board
+ self._board_version = board_version
+ self._build = 'userdebug'
+ self._origin = common.Origin()
+
+ def __repr__(self):
+ return ('<target name="{}" os="{}" os-version="{}" '
+ 'board="{}" board-version="{}" build="{}" pack="{}"/>').format(
+ self._name, self._os, self._os_version, self._board,
+ self._board_version, self._build, self._pack_name)
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def pack_name(self):
+ return self._pack_name
+
+ @property
+ def pack(self):
+ return self._pack
+
+ @property
+ def origin(self):
+ return self._origin
+
+ @property
+ def os(self):
+ return self._os
+
+ @os.setter
+ def os(self, b):
+ self._os = b
+
+ @property
+ def os_namespace(self):
+ return '{}.{}'.format(self.os, self.os_version)
+
+ @property
+ def os_version(self):
+ return self._os_version
+
+ @os_version.setter
+ def os_version(self, b):
+ self._os_version = b
+
+ @property
+ def board(self):
+ return self._board
+
+ @board.setter
+ def board(self, b):
+ self._board = b
+
+ @property
+ def board_version(self):
+ return self._board_version
+
+ @board_version.setter
+ def board_version(self, b):
+ self._board_version = b
+
+ @property
+ def board_namespace(self):
+ return '{}.{}'.format(self.board, self.board_version)
+
+ @property
+ def build_type(self):
+ return self._build
+
+ def get_device(self):
+ """Get the bsp.Device associated with this Target.
+
+ Returns:
+ The bsp.Device associated with this Target.
+
+ Raises:
+ BoardError: If the target board and version is not in the manifest.
+ """
+ bsp_manifest = manifest.Manifest.from_json()
+ board = bsp_manifest.devices.get(self.board)
+ if not board:
+ raise BoardError('unrecognized name "{}". '
+ 'Run `bdk bsp list` to see available '
+ 'boards.'.format(self.board))
+ if board.version != self.board_version:
+ raise BoardError('"{}" only has version {} (requested {}).'.format(
+ self.board, board.version, self.board_version))
+ return board
+
+ def platform_cache(self, *relpath):
+ return os.path.join(
+ PLATFORM_CACHE_FORMAT.format(
+ user_configured_platform_cache=(
+ user_config.USER_CONFIG.platform_cache),
+ os=self.os,
+ os_version=self.os_version,
+ board=self.board,
+ board_version=self.board_version),
+ *relpath)
+
+ def platform_build_cache(self, *relpath):
+ return self.platform_cache(self.build_type, *relpath)
+
+ def load(self, ele):
+ """Populates this instance from Element @ele."""
+ self._origin = ele.origin.copy()
+ ele.limit_attribs(['name', 'pack', 'os', 'board',
+ 'os-version', 'board-version', 'build'])
+ self._pack_name = ele.get_attrib('pack')
+ self._os = ele.get_attrib('os')
+ self._os_version = ele.get_attrib('os-version')
+ self._board = ele.get_attrib('board')
+ self._board_version = ele.get_attrib('board-version')
+ if 'build' in ele.attrib:
+ self._build = ele.get_attrib('build')
+
+ def create_submap(self, global_packmap):
+ """Create a packmap from the global packmap.
+
+ Prior to calling, populate the global packmap with any
+ requires OS or Board packs.
+
+ Args:
+ global_packmap: A packmap of all available packs.
+
+ Returns:
+ A submap of global_packmap for this target, with paths validated.
+ """
+ aliases = {'os': self.os_namespace, 'board': self.board_namespace}
+ packmap = global_packmap.submap(self._pack_name, aliases)
+ packmap.check_paths()
+ return packmap
diff --git a/cli/lib/project/target_stub.py b/cli/lib/project/target_stub.py
index 2f4f993..f236533 100644
--- a/cli/lib/project/target_stub.py
+++ b/cli/lib/project/target_stub.py
@@ -20,53 +20,55 @@
import os
-from core import util
from project import target
class StubTarget(object):
- def __init__(self, name='', os='', os_version='',
- board='', board_version='', build_type='',
- origin='test_target_origin',
- platform_dir=None,
- device_raises=False, device=None,
- submap_raises=None, submaps=None):
- self.name = name
- self.os = os
- self.os_version = os_version
- self.os_namespace = '{}.{}'.format(os, os_version)
- self.board = board
- self.board_version = board_version
- self.board_namespace = '{}.{}'.format(board, board_version)
- self.build_type = build_type
- self.origin = origin
- self.device = device
- self.platform_dir = platform_dir
+ # TODO(b/28296932): Rename the 'os' input variable to not conflict with the
+ # import.
+ # pylint: disable=redefined-outer-name
+ def __init__(self, name='', os='', os_version='',
+ board='', board_version='', build_type='',
+ origin='test_target_origin',
+ platform_dir=None,
+ device_raises=False, device=None,
+ submap_raises=None, submaps=None):
+ self.name = name
+ self.os = os
+ self.os_version = os_version
+ self.os_namespace = '{}.{}'.format(os, os_version)
+ self.board = board
+ self.board_version = board_version
+ self.board_namespace = '{}.{}'.format(board, board_version)
+ self.build_type = build_type
+ self.origin = origin
+ self.device = device
+ self.platform_dir = platform_dir
- self.device_raises = device_raises
- self._submap_raises = submap_raises
- self._submaps = submaps or []
+ self.device_raises = device_raises
+ self._submap_raises = submap_raises
+ self._submaps = submaps or []
- def get_device(self):
- if self.device_raises:
- raise target.BoardError
- return self.device
+ def get_device(self):
+ if self.device_raises:
+ raise target.BoardError
+ return self.device
- def platform_cache(self, *relpath):
- return os.path.join(self.platform_dir, *relpath)
+ def platform_cache(self, *relpath):
+ return os.path.join(self.platform_dir, *relpath)
- def platform_build_cache(self, *relpath):
- return self.platform_cache(self.build_type, *relpath)
+ def platform_build_cache(self, *relpath):
+ return self.platform_cache(self.build_type, *relpath)
- def create_submap(self, _packmap):
- if self._submap_raises:
- # Pylint issues an error about raising NoneType, since self._submap_raises
- # is set to None by default and pylint doesn't recognize that the raise
- # call is only called if it is not None. In the case that it is set to
- # a non-Exception, running the unit test should raise a TypeError and
- # (correctly) fail the unit test, so it should be safe to just disable
- # the pylint check here.
- # pylint: disable=raising-bad-type
- raise self._submap_raises
- return self._submaps.pop(0)
+ def create_submap(self, _packmap):
+ if self._submap_raises:
+ # Pylint issues an error about raising NoneType, since
+ # self._submap_raises is set to None by default and pylint doesn't
+ # recognize that the raise call is only called if it is not None.
+ # In the case that it is set to a non-Exception, running the unit
+ # test should raise a TypeError and (correctly) fail the unit test,
+ # so it should be safe to just disable the pylint check here.
+ # pylint: disable=raising-bad-type
+ raise self._submap_raises
+ return self._submaps.pop(0)
diff --git a/cli/lib/project/targets.py b/cli/lib/project/targets.py
index 05bd817..b7dde51 100644
--- a/cli/lib/project/targets.py
+++ b/cli/lib/project/targets.py
@@ -24,59 +24,60 @@ from project import target
class TargetsFactory(object):
- @staticmethod
- def new(packmap, **kwargs):
- """Creates a new Targets instance from an XML file or element.
+ @staticmethod
+ def new(packmap, **kwargs):
+ """Creates a new Targets instance from an XML file or element.
- Walks an ElementTree <target> looking for <target> child
- nodes and instantiates Target objects.
+ Walks an ElementTree <target> looking for <target> child
+ nodes and instantiates Target objects.
- Args:
- packmap: A PackMap containing all necessary Packs for the parsed targets.
- **kwargs: Any valid keywords for loader.PathElementLoader.load()
+ Args:
+ packmap: A PackMap containing all necessary Packs for the parsed
+ targets.
+ **kwargs: Any valid keywords for loader.PathElementLoader.load()
- Returns:
- new Targets() instance
+ Returns:
+ new Targets() instance
- Raises
- LoadError: LoadError, or a subclass, will be raised if an error occurs
- loading and validating content from the XML.
- """
- ts = Targets()
- l = loader.PathElementLoader('targets', [])
- root = l.load(**kwargs)
- root.limit_attribs(['version', 'path'])
- targets = {}
- for node in root.findall('target'):
- # The pack name is the default target name.
- name = node.get_attrib('pack')
- if 'name' in node.attrib:
- name = node.get_attrib('name')
- if name in targets:
- raise common.LoadErrorWithOrigin(
- node,
- 'Target "{}" redefined. Previously defined here: "{}".'.format(
- name, targets[name].origin))
- t = target.Target(name)
- t.load(node)
- if t.pack_name not in packmap.map:
- raise common.LoadErrorWithOrigin(
- t.origin,
- 'Target "{}" specifies undefined pack "{}"'.format(
- t.name,
- t.pack_name))
- targets[name] = t
+ Raises
+ LoadError: LoadError, or a subclass, will be raised if an error
+ occurs loading and validating content from the XML.
+ """
+ ts = Targets()
+ l = loader.PathElementLoader('targets', [])
+ root = l.load(**kwargs)
+ root.limit_attribs(['version', 'path'])
+ targets = {}
+ for node in root.findall('target'):
+ # The pack name is the default target name.
+ name = node.get_attrib('pack')
+ if 'name' in node.attrib:
+ name = node.get_attrib('name')
+ if name in targets:
+ raise common.LoadErrorWithOrigin(
+ node,
+ ('Target "{}" redefined. Previously defined here: '
+ '"{}".'.format(name, targets[name].origin)))
+ t = target.Target(name)
+ t.load(node)
+ if t.pack_name not in packmap.map:
+ raise common.LoadErrorWithOrigin(
+ t.origin,
+ 'Target "{}" specifies undefined pack "{}"'.format(
+ t.name,
+ t.pack_name))
+ targets[name] = t
- ts.origins = l.origins
- ts.entries = targets
- return ts
+ ts.origins = l.origins
+ ts.entries = targets
+ return ts
class Targets(collection.Base):
- """Collection of Target objects."""
- def __init__(self):
- super(Targets, self).__init__('targets')
+ """Collection of Target objects."""
+ def __init__(self):
+ super(Targets, self).__init__('targets')
- @property
- def targets(self):
- return self.entries
+ @property
+ def targets(self):
+ return self.entries
diff --git a/cli/lib/project/xml_parser.py b/cli/lib/project/xml_parser.py
index 131d6c1..d467889 100644
--- a/cli/lib/project/xml_parser.py
+++ b/cli/lib/project/xml_parser.py
@@ -34,73 +34,80 @@ from project import common
class ElementExtras(object):
- @staticmethod
- def get_attrib(node, key):
- if key not in node.attrib:
- raise common.MissingAttribute(
- node.origin, '{} lacks a @{} attribute'.format(node.tag, key))
- val = node.attrib.get(key)
- if len(val) == 0:
- raise common.MissingAttribute(
- node.origin,
- '<{}>\'s @{} attribute is empty'.format(node.tag, key))
- return val
-
- @staticmethod
- def limit_attribs(node, keys):
- bad_keys = set(node.attrib) - set(keys)
- if len(bad_keys):
- raise common.UnknownAttributes(
- node.origin,
- '{} contains unsupported attributes: {} '.format(node.tag, bad_keys))
-
- @staticmethod
- def extend_element():
- ET.Element.get_attrib = ElementExtras.get_attrib
- ET.Element.limit_attribs = ElementExtras.limit_attribs
+ @staticmethod
+ def get_attrib(node, key):
+ if key not in node.attrib:
+ raise common.MissingAttribute(
+ node.origin, '{} lacks a @{} attribute'.format(node.tag, key))
+ val = node.attrib.get(key)
+ if len(val) == 0:
+ raise common.MissingAttribute(
+ node.origin,
+ '<{}>\'s @{} attribute is empty'.format(node.tag, key))
+ return val
+
+ @staticmethod
+ def limit_attribs(node, keys):
+ bad_keys = set(node.attrib) - set(keys)
+ if len(bad_keys):
+ raise common.UnknownAttributes(
+ node.origin,
+ '{} contains unsupported attributes: {} '.format(node.tag,
+ bad_keys))
+
+ @staticmethod
+ def extend_element():
+ ET.Element.get_attrib = ElementExtras.get_attrib
+ ET.Element.limit_attribs = ElementExtras.limit_attribs
class AnnotatedXMLTreeBuilder(XMLTreeBuilder):
- def __init__(self):
- super(AnnotatedXMLTreeBuilder, self).__init__()
- self.file_name = None
-
- def _start_fixup(self, element):
- """Adds element source annotation as the ElementTree is built."""
- # Grab the line/col from expat, but expect the path
- # to be passed in.
- element.origin = common.Origin(self.file_name,
- self._parser.CurrentLineNumber,
- self._parser.CurrentColumnNumber)
- return element
-
- def _start(self, tag, attrib_in):
- """Thunks the XMLTreeBuilder _start adding the element annotations."""
- # If _start_list was supported, then we'll see a list and should call over.
- if type(attrib_in) == list:
- return self._start_list(tag, attrib_in)
-
- element = super(AnnotatedXMLTreeBuilder, self)._start(tag, attrib_in)
- return self._start_fixup(element)
-
- def _start_list(self, tag, attrib_in):
- """Thunks the XMLTreeBuilder _start_list adding the element annotations."""
- element = super(AnnotatedXMLTreeBuilder, self)._start_list(tag, attrib_in)
- return self._start_fixup(element)
-
- def subclass_fixups(self):
- """Updates the parser callback to the _start element in this instance."""
- # This may clobber start_list, but we hide that in _start.
- self._parser.StartElementHandler = self._start
+ def __init__(self):
+ super(AnnotatedXMLTreeBuilder, self).__init__()
+ self.file_name = None
+
+ def _start_fixup(self, element):
+ """Adds element source annotation as the ElementTree is built."""
+ # Grab the line/col from expat, but expect the path
+ # to be passed in.
+ element.origin = common.Origin(self.file_name,
+ self._parser.CurrentLineNumber,
+ self._parser.CurrentColumnNumber)
+ return element
+
+ def _start(self, tag, attrib_in):
+ """Thunks the XMLTreeBuilder _start adding the element annotations."""
+ # If _start_list was supported, then we'll see a list and should call
+ # over.
+ if type(attrib_in) == list:
+ return self._start_list(tag, attrib_in)
+
+ element = super(AnnotatedXMLTreeBuilder, self)._start(tag, attrib_in)
+ return self._start_fixup(element)
+
+ def _start_list(self, tag, attrib_in):
+ """Thunks the XMLTreeBuilder _start_list adding the element
+ annotations.
+ """
+ element = super(AnnotatedXMLTreeBuilder, self)._start_list(tag,
+ attrib_in)
+ return self._start_fixup(element)
+
+ def subclass_fixups(self):
+ """Updates the parser callback to the _start element in this
+ instance.
+ """
+ # This may clobber start_list, but we hide that in _start.
+ self._parser.StartElementHandler = self._start
def parse(path):
- """Replaces ET.parse() as an entry point for adding source annotations."""
- ElementExtras.extend_element()
- parser = AnnotatedXMLTreeBuilder()
- parser.file_name = str(path)
- if hasattr(path, 'name'):
- parser.file_name = path.name
- parser.subclass_fixups()
- return ET.parse(path, parser=parser)
+ """Replaces ET.parse() as an entry point for adding source annotations."""
+ ElementExtras.extend_element()
+ parser = AnnotatedXMLTreeBuilder()
+ parser.file_name = str(path)
+ if hasattr(path, 'name'):
+ parser.file_name = path.name
+ parser.subclass_fixups()
+ return ET.parse(path, parser=parser)