summaryrefslogtreecommitdiff
path: root/morphlib/morphloader.py
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/morphloader.py')
-rw-r--r--morphlib/morphloader.py789
1 files changed, 789 insertions, 0 deletions
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
new file mode 100644
index 00000000..8289b01e
--- /dev/null
+++ b/morphlib/morphloader.py
@@ -0,0 +1,789 @@
+# Copyright (C) 2013-2014 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import collections
+import logging
+import warnings
+import yaml
+
+import morphlib
+
+
+class MorphologyObsoleteFieldWarning(UserWarning):
+
+ def __init__(self, morphology, spec, field):
+ self.kind = morphology['kind']
+ self.morphology_name = morphology.get('name', '<unknown>')
+ self.stratum_name = spec.get('alias', spec['morph'])
+ self.field = field
+
+ def __str__(self):
+ format_string = ('%(kind)s morphology %(morphology_name)s refers to '
+ 'stratum %(stratum_name)s with the %(field)s field. '
+ 'Defaulting to null.')
+ return format_string % self.__dict__
+
+
+class MorphologySyntaxError(morphlib.Error):
+ pass
+
+
+class MorphologyNotYamlError(MorphologySyntaxError):
+
+ def __init__(self, morphology, errmsg):
+ self.msg = 'Syntax error in morphology %s:\n%s' % (morphology, errmsg)
+
+
+class NotADictionaryError(MorphologySyntaxError):
+
+ def __init__(self, morph_filename):
+ self.msg = 'Not a dictionary: morphology %s' % morph_filename
+
+
+class MorphologyValidationError(morphlib.Error):
+ pass
+
+
+class UnknownKindError(MorphologyValidationError):
+
+ def __init__(self, kind, morph_filename):
+ self.msg = (
+ 'Unknown kind %s in morphology %s' % (kind, morph_filename))
+
+
+class MissingFieldError(MorphologyValidationError):
+
+ def __init__(self, field, morphology_name):
+ self.field = field
+ self.morphology_name = morphology_name
+ self.msg = (
+ 'Missing field %s from morphology %s' % (field, morphology_name))
+
+
+class InvalidFieldError(MorphologyValidationError):
+
+ def __init__(self, field, morphology_name):
+ self.field = field
+ self.morphology_name = morphology_name
+ self.msg = (
+ 'Field %s not allowed in morphology %s' % (field, morphology_name))
+
+
+class InvalidTypeError(MorphologyValidationError):
+
+ def __init__(self, field, expected, actual, morphology_name):
+ self.field = field
+ self.expected = expected
+ self.actual = actual
+ self.morphology_name = morphology_name
+ self.msg = (
+ 'Field %s expected type %s, got %s in morphology %s' %
+ (field, expected, actual, morphology_name))
+
+
+class ObsoleteFieldsError(MorphologyValidationError):
+
+ def __init__(self, fields, morph_filename):
+ self.msg = (
+ 'Morphology %s uses obsolete fields: %s' %
+ (morph_filename, ' '.join(fields)))
+
+
+class UnknownArchitectureError(MorphologyValidationError):
+
+ def __init__(self, arch, morph_filename):
+ self.msg = ('Unknown architecture %s in morphology %s'
+ % (arch, morph_filename))
+
+
+class NoBuildDependenciesError(MorphologyValidationError):
+
+ def __init__(self, stratum_name, chunk_name, morph_filename):
+ self.msg = (
+ 'Stratum %s has no build dependencies for chunk %s in %s' %
+ (stratum_name, chunk_name, morph_filename))
+
+
+class NoStratumBuildDependenciesError(MorphologyValidationError):
+
+ def __init__(self, stratum_name, morph_filename):
+ self.msg = (
+ 'Stratum %s has no build dependencies in %s' %
+ (stratum_name, morph_filename))
+
+
+class EmptyStratumError(MorphologyValidationError):
+
+ def __init__(self, stratum_name, morph_filename):
+ self.msg = (
+ 'Stratum %s has no chunks in %s' %
+ (stratum_name, morph_filename))
+
+
+class DuplicateChunkError(MorphologyValidationError):
+
+ def __init__(self, stratum_name, chunk_name):
+ self.stratum_name = stratum_name
+ self.chunk_name = chunk_name
+ MorphologyValidationError.__init__(
+ self, 'Duplicate chunk %(chunk_name)s '\
+ 'in stratum %(stratum_name)s' % locals())
+
+
+class EmptyRefError(MorphologyValidationError):
+
+ def __init__(self, ref_location, morph_filename):
+ self.ref_location = ref_location
+ self.morph_filename = morph_filename
+ MorphologyValidationError.__init__(
+ self, 'Empty ref found for %(ref_location)s '\
+ 'in %(morph_filename)s' % locals())
+
+
+class ChunkSpecRefNotStringError(MorphologyValidationError):
+
+ def __init__(self, ref_value, chunk_name, stratum_name):
+ self.ref_value = ref_value
+ self.chunk_name = chunk_name
+ self.stratum_name = stratum_name
+ MorphologyValidationError.__init__(
+ self, 'Ref %(ref_value)s for %(chunk_name)s '\
+ 'in stratum %(stratum_name)s is not a string' % locals())
+
+
+class SystemStrataNotListError(MorphologyValidationError):
+
+ def __init__(self, system_name, strata_type):
+ self.system_name = system_name
+ self.strata_type = strata_type
+ typename = strata_type.__name__
+ MorphologyValidationError.__init__(
+ self, 'System %(system_name)s has the wrong type for its strata: '\
+ '%(typename)s, expected list' % locals())
+
+
+class DuplicateStratumError(MorphologyValidationError):
+
+ def __init__(self, system_name, stratum_name):
+ self.system_name = system_name
+ self.stratum_name = stratum_name
+ MorphologyValidationError.__init__(
+ self, 'Duplicate stratum %(stratum_name)s '\
+ 'in system %(system_name)s' % locals())
+
+
+class SystemStratumSpecsNotMappingError(MorphologyValidationError):
+
+ def __init__(self, system_name, strata):
+ self.system_name = system_name
+ self.strata = strata
+ MorphologyValidationError.__init__(
+ self, 'System %(system_name)s has stratum specs '\
+ 'that are not mappings.' % locals())
+
+
+class EmptySystemError(MorphologyValidationError):
+
+ def __init__(self, system_name):
+ MorphologyValidationError.__init__(
+ self, 'System %(system_name)s has no strata.' % locals())
+
+
+class MultipleValidationErrors(MorphologyValidationError):
+
+ def __init__(self, name, errors):
+ self.name = name
+ self.errors = errors
+ self.msg = 'Multiple errors when validating %(name)s:'
+ for error in errors:
+ self.msg += ('\n' + str(error))
+
+
+class DuplicateDeploymentNameError(MorphologyValidationError):
+
+ def __init__(self, cluster_filename, duplicates):
+ self.duplicates = duplicates
+ self.cluster_filename = cluster_filename
+ morphlib.Error.__init__(self,
+ 'Cluster %s contains the following duplicate deployment names:%s'
+ % (cluster_filename, '\n ' + '\n '.join(duplicates)))
+
+
+class MorphologyDumper(yaml.SafeDumper):
+ keyorder = (
+ 'name',
+ 'kind',
+ 'description',
+ 'arch',
+ 'strata',
+ 'configuration-extensions',
+ 'morph',
+ 'repo',
+ 'ref',
+ 'unpetrify-ref',
+ 'build-depends',
+ 'build-mode',
+ 'artifacts',
+ 'max-jobs',
+ 'products',
+ 'chunks',
+ 'build-system',
+ 'pre-configure-commands',
+ 'configure-commands',
+ 'post-configure-commands',
+ 'pre-build-commands',
+ 'build-commands',
+ 'post-build-commands',
+ 'pre-install-commands',
+ 'install-commands',
+ 'post-install-commands',
+ 'artifact',
+ 'include',
+ 'systems',
+ 'deploy-defaults',
+ 'deploy',
+ 'type',
+ 'location',
+ )
+
+ @classmethod
+ def _iter_in_global_order(cls, mapping):
+ for key in cls.keyorder:
+ if key in mapping:
+ yield key, mapping[key]
+ for key in sorted(mapping.iterkeys()):
+ if key not in cls.keyorder:
+ yield key, mapping[key]
+
+ @classmethod
+ def _represent_dict(cls, dumper, mapping):
+ return dumper.represent_mapping('tag:yaml.org,2002:map',
+ cls._iter_in_global_order(mapping))
+
+ @classmethod
+ def _represent_str(cls, dumper, orig_data):
+ fallback_representer = yaml.representer.SafeRepresenter.represent_str
+ try:
+ data = unicode(orig_data, 'ascii')
+ if data.count('\n') == 0:
+ return fallback_representer(dumper, orig_data)
+ except UnicodeDecodeError:
+ try:
+ data = unicode(orig_data, 'utf-8')
+ if data.count('\n') == 0:
+ return fallback_representer(dumper, orig_data)
+ except UnicodeDecodeError:
+ return fallback_representer(dumper, orig_data)
+ return dumper.represent_scalar(u'tag:yaml.org,2002:str',
+ data, style='|')
+
+ @classmethod
+ def _represent_unicode(cls, dumper, data):
+ if data.count('\n') == 0:
+ return yaml.representer.SafeRepresenter.represent_unicode(dumper,
+ data)
+ return dumper.represent_scalar(u'tag:yaml.org,2002:str',
+ data, style='|')
+
+ def __init__(self, *args, **kwargs):
+ yaml.SafeDumper.__init__(self, *args, **kwargs)
+ self.add_representer(dict, self._represent_dict)
+ self.add_representer(str, self._represent_str)
+ self.add_representer(unicode, self._represent_unicode)
+
+
+class MorphologyLoader(object):
+
+ '''Load morphologies from disk, or save them back to disk.'''
+
+ _required_fields = {
+ 'chunk': [
+ 'name',
+ ],
+ 'stratum': [
+ 'name',
+ ],
+ 'system': [
+ 'name',
+ 'arch',
+ 'strata',
+ ],
+ 'cluster': [
+ 'name',
+ 'systems',
+ ],
+ }
+
+ _obsolete_fields = {
+ 'system': [
+ 'system-kind',
+ 'disk-size',
+ ],
+ }
+
+ _static_defaults = {
+ 'chunk': {
+ 'description': '',
+ 'pre-configure-commands': None,
+ 'configure-commands': None,
+ 'post-configure-commands': None,
+ 'pre-build-commands': None,
+ 'build-commands': None,
+ 'post-build-commands': None,
+ 'pre-test-commands': None,
+ 'test-commands': None,
+ 'post-test-commands': None,
+ 'pre-install-commands': None,
+ 'install-commands': None,
+ 'post-install-commands': None,
+ 'devices': [],
+ 'products': [],
+ 'max-jobs': None,
+ 'build-system': 'manual',
+ 'build-mode': 'staging',
+ 'prefix': '/usr',
+ 'system-integration': [],
+ },
+ 'stratum': {
+ 'chunks': [],
+ 'description': '',
+ 'build-depends': [],
+ 'products': [],
+ },
+ 'system': {
+ 'description': '',
+ 'arch': None,
+ 'configuration-extensions': [],
+ },
+ 'cluster': {
+ 'description': '',
+ },
+ }
+
+ def parse_morphology_text(self, text, morph_filename):
+ '''Parse a textual morphology.
+
+ The text may be a string, or an open file handle.
+
+ Return the new Morphology object, or raise an error indicating
+ the problem. This method does minimal validation: a syntactically
+ correct morphology is fine, even if none of the fields are
+ valid. It also does not set any default values for any of the
+ fields. See validate and set_defaults.
+
+ '''
+
+ try:
+ obj = yaml.safe_load(text)
+ except yaml.error.YAMLError as e:
+ raise MorphologyNotYamlError(morph_filename, e)
+
+ if not isinstance(obj, dict):
+ raise NotADictionaryError(morph_filename)
+
+ return morphlib.morphology.Morphology(obj)
+
+ def load_from_string(self, string, filename='string'):
+ '''Load a morphology from a string.
+
+ Return the Morphology object.
+
+ '''
+
+ m = self.parse_morphology_text(string, filename)
+ m.filename = filename
+ self.validate(m)
+ self.set_commands(m)
+ self.set_defaults(m)
+ return m
+
+ def load_from_file(self, filename):
+ '''Load a morphology from a named file.
+
+ Return the Morphology object.
+
+ '''
+
+ with open(filename) as f:
+ text = f.read()
+ return self.load_from_string(text, filename=filename)
+
+ def save_to_string(self, morphology):
+ '''Return normalised textual form of morphology.'''
+
+ return yaml.dump(morphology.data, Dumper=MorphologyDumper,
+ default_flow_style=False)
+
+ def save_to_file(self, filename, morphology):
+ '''Save a morphology object to a named file.'''
+
+ text = self.save_to_string(morphology)
+ with morphlib.savefile.SaveFile(filename, 'w') as f:
+ f.write(text)
+
+ def validate(self, morph):
+ '''Validate a morphology.'''
+
+ # Validate that the kind field is there.
+ self._require_field('kind', morph)
+
+ # The rest of the validation is dependent on the kind.
+ kind = morph['kind']
+ if kind not in ('system', 'stratum', 'chunk', 'cluster'):
+ raise UnknownKindError(morph['kind'], morph.filename)
+
+ required = ['kind'] + self._required_fields[kind]
+ obsolete = self._obsolete_fields.get(kind, [])
+ allowed = self._static_defaults[kind].keys()
+ self._require_fields(required, morph)
+ self._deny_obsolete_fields(obsolete, morph)
+ self._deny_unknown_fields(required + allowed, morph)
+
+ getattr(self, '_validate_%s' % kind)(morph)
+
+ def _validate_cluster(self, morph):
+ # Deployment names must be unique within a cluster
+ deployments = collections.Counter()
+ for system in morph['systems']:
+ deployments.update(system['deploy'].iterkeys())
+ if 'subsystems' in system:
+ deployments.update(self._get_subsystem_names(system))
+ duplicates = set(deployment for deployment, count
+ in deployments.iteritems() if count > 1)
+ if duplicates:
+ raise DuplicateDeploymentNameError(morph.filename, duplicates)
+
+ def _get_subsystem_names(self, system): # pragma: no cover
+ for subsystem in system.get('subsystems', []):
+ for name in subsystem['deploy'].iterkeys():
+ yield name
+ for name in self._get_subsystem_names(subsystem):
+ yield name
+
+ def _validate_system(self, morph):
+ # A system must contain at least one stratum
+ strata = morph['strata']
+ if (not isinstance(strata, collections.Iterable)
+ or isinstance(strata, collections.Mapping)):
+
+ raise SystemStrataNotListError(morph['name'],
+ type(strata))
+
+ if not strata:
+ raise EmptySystemError(morph['name'])
+
+ if not all(isinstance(o, collections.Mapping) for o in strata):
+ raise SystemStratumSpecsNotMappingError(morph['name'], strata)
+
+ # All stratum names should be unique within a system.
+ names = set()
+ for spec in strata:
+ name = spec.get('alias', spec['morph'])
+ if name in names:
+ raise DuplicateStratumError(morph['name'], name)
+ names.add(name)
+
+ # Validate stratum spec fields
+ self._validate_stratum_specs_fields(morph, 'strata')
+
+ # We allow the ARMv7 little-endian architecture to be specified
+ # as armv7 and armv7l. Normalise.
+ if morph['arch'] == 'armv7':
+ morph['arch'] = 'armv7l'
+
+ # Architecture name must be known.
+ if morph['arch'] not in morphlib.valid_archs:
+ raise UnknownArchitectureError(morph['arch'], morph.filename)
+
+ def _validate_stratum(self, morph):
+ # Require at least one chunk.
+ if len(morph.get('chunks', [])) == 0:
+ raise EmptyStratumError(morph['name'], morph.filename)
+
+ # All chunk names must be unique within a stratum.
+ names = set()
+ for spec in morph['chunks']:
+ name = spec.get('alias', spec['name'])
+ if name in names:
+ raise DuplicateChunkError(morph['name'], name)
+ names.add(name)
+
+ # All chunk refs must be strings.
+ for spec in morph['chunks']:
+ if 'ref' in spec:
+ ref = spec['ref']
+ if ref == None:
+ raise EmptyRefError(
+ spec.get('alias', spec['name']), morph.filename)
+ elif not isinstance(ref, basestring):
+ raise ChunkSpecRefNotStringError(
+ ref, spec.get('alias', spec['name']), morph.filename)
+
+ # Require build-dependencies for the stratum itself, unless
+ # it has chunks built in bootstrap mode.
+ if 'build-depends' in morph:
+ if not isinstance(morph['build-depends'], list):
+ raise InvalidTypeError(
+ 'build-depends', list, type(morph['build-depends']),
+ morph['name'])
+ else:
+ for spec in morph['chunks']:
+ if spec.get('build-mode') in ['bootstrap', 'test']:
+ break
+ else:
+ raise NoStratumBuildDependenciesError(
+ morph['name'], morph.filename)
+
+ # Validate build-dependencies if specified
+ self._validate_stratum_specs_fields(morph, 'build-depends')
+
+ # Require build-dependencies for each chunk.
+ for spec in morph['chunks']:
+ chunk_name = spec.get('alias', spec['name'])
+ if 'build-depends' in spec:
+ if not isinstance(spec['build-depends'], list):
+ raise InvalidTypeError(
+ '%s.build-depends' % chunk_name, list,
+ type(spec['build-depends']), morph['name'])
+ else:
+ raise NoBuildDependenciesError(
+ morph['name'], chunk_name, morph.filename)
+
+ @classmethod
+ def _validate_chunk(cls, morphology):
+ errors = []
+
+ if 'products' in morphology:
+ cls._validate_products(morphology['name'],
+ morphology['products'], errors)
+
+ if len(errors) == 1:
+ raise errors[0]
+ elif errors:
+ raise MultipleValidationErrors(morphology['name'], errors)
+
+ @classmethod
+ def _validate_products(cls, morphology_name, products, errors):
+ '''Validate the products field is of the correct type.'''
+ if (not isinstance(products, collections.Iterable)
+ or isinstance(products, collections.Mapping)):
+ raise InvalidTypeError('products', list,
+ type(products), morphology_name)
+
+ for spec_index, spec in enumerate(products):
+
+ if not isinstance(spec, collections.Mapping):
+ e = InvalidTypeError('products[%d]' % spec_index,
+ dict, type(spec), morphology_name)
+ errors.append(e)
+ continue
+
+ cls._validate_products_spec_fields_exist(morphology_name,
+ spec_index, spec, errors)
+
+ if 'include' in spec:
+ cls._validate_products_specs_include(
+ morphology_name, spec_index, spec['include'], errors)
+
+ product_spec_required_fields = ('artifact', 'include')
+ @classmethod
+ def _validate_products_spec_fields_exist(
+ cls, morphology_name, spec_index, spec, errors):
+
+ given_fields = sorted(spec.iterkeys())
+ missing = (field for field in cls.product_spec_required_fields
+ if field not in given_fields)
+ for field in missing:
+ e = MissingFieldError('products[%d].%s' % (spec_index, field),
+ morphology_name)
+ errors.append(e)
+ unexpected = (field for field in given_fields
+ if field not in cls.product_spec_required_fields)
+ for field in unexpected:
+ e = InvalidFieldError('products[%d].%s' % (spec_index, field),
+ morphology_name)
+ errors.append(e)
+
+ @classmethod
+ def _validate_products_specs_include(cls, morphology_name, spec_index,
+ include_patterns, errors):
+ '''Validate that products' include field is a list of strings.'''
+ # Allow include to be most iterables, but not a mapping
+ # or a string, since iter of a mapping is just the keys,
+ # and the iter of a string is a 1 character length string,
+ # which would also validate as an iterable of strings.
+ if (not isinstance(include_patterns, collections.Iterable)
+ or isinstance(include_patterns, collections.Mapping)
+ or isinstance(include_patterns, basestring)):
+
+ e = InvalidTypeError('products[%d].include' % spec_index, list,
+ type(include_patterns), morphology_name)
+ errors.append(e)
+ else:
+ for pattern_index, pattern in enumerate(include_patterns):
+ pattern_path = ('products[%d].include[%d]' %
+ (spec_index, pattern_index))
+ if not isinstance(pattern, basestring):
+ e = InvalidTypeError(pattern_path, str,
+ type(pattern), morphology_name)
+ errors.append(e)
+
+ @classmethod
+ def _warn_obsolete_field(cls, morphology, spec, field):
+ warnings.warn(MorphologyObsoleteFieldWarning(morphology, spec, field),
+ stacklevel=2)
+
+ @classmethod
+ def _validate_stratum_specs_fields(cls, morphology, specs_field):
+ for spec in morphology.get(specs_field, None) or []:
+ for obsolete_field in ('repo', 'ref'):
+ if obsolete_field in spec:
+ cls._warn_obsolete_field(morphology, spec, obsolete_field)
+
+ def _require_field(self, field, morphology):
+ if field not in morphology:
+ raise MissingFieldError(field, morphology.filename)
+
+ def _require_fields(self, fields, morphology):
+ for field in fields:
+ self._require_field(field, morphology)
+
+ def _deny_obsolete_fields(self, fields, morphology):
+ obsolete_ones = [x for x in morphology if x in fields]
+ if obsolete_ones:
+ raise ObsoleteFieldsError(obsolete_ones, morphology.filename)
+
+ def _deny_unknown_fields(self, allowed, morphology):
+ for field in morphology:
+ if field not in allowed:
+ raise InvalidFieldError(field, morphology.filename)
+
+ def set_defaults(self, morphology):
+ '''Set all missing fields in the morpholoy to their defaults.
+
+ The morphology is assumed to be valid.
+
+ '''
+
+ kind = morphology['kind']
+ defaults = self._static_defaults[kind]
+ for key in defaults:
+ if key not in morphology:
+ morphology[key] = defaults[key]
+
+ getattr(self, '_set_%s_defaults' % kind)(morphology)
+
+ def unset_defaults(self, morphology):
+ '''If a field is equal to its default, delete it.
+
+ The morphology is assumed to be valid.
+
+ '''
+
+ kind = morphology['kind']
+ defaults = self._static_defaults[kind]
+ for key in defaults:
+ if key in morphology and morphology[key] == defaults[key]:
+ del morphology[key]
+
+ getattr(self, '_unset_%s_defaults' % kind)(morphology)
+
+ @classmethod
+ def _set_stratum_specs_defaults(cls, morphology, specs_field):
+ for spec in morphology.get(specs_field, None) or []:
+ for obsolete_field in ('repo', 'ref'):
+ if obsolete_field in spec:
+ del spec[obsolete_field]
+
+ @classmethod
+ def _unset_stratum_specs_defaults(cls, morphology, specs_field):
+ for spec in morphology.get(specs_field, []):
+ for obsolete_field in ('repo', 'ref'):
+ if obsolete_field in spec:
+ del spec[obsolete_field]
+
+ def _set_cluster_defaults(self, morph):
+ for system in morph.get('systems', []):
+ if 'deploy-defaults' not in system:
+ system['deploy-defaults'] = {}
+ if 'deploy' not in system:
+ system['deploy'] = {}
+
+ def _unset_cluster_defaults(self, morph):
+ for system in morph.get('systems', []):
+ if 'deploy-defaults' in system and system['deploy-defaults'] == {}:
+ del system['deploy-defaults']
+ if 'deploy' in system and system['deploy'] == {}:
+ del system['deploy']
+
+ def _set_system_defaults(self, morph):
+ self._set_stratum_specs_defaults(morph, 'strata')
+
+ def _unset_system_defaults(self, morph):
+ self._unset_stratum_specs_defaults(morph, 'strata')
+
+ def _set_stratum_defaults(self, morph):
+ for spec in morph['chunks']:
+ if 'repo' not in spec:
+ spec['repo'] = spec['name']
+ if 'build-mode' not in spec:
+ spec['build-mode'] = \
+ self._static_defaults['chunk']['build-mode']
+ if 'prefix' not in spec:
+ spec['prefix'] = \
+ self._static_defaults['chunk']['prefix']
+ self._set_stratum_specs_defaults(morph, 'build-depends')
+
+ def _unset_stratum_defaults(self, morph):
+ for spec in morph['chunks']:
+ if 'repo' in spec and spec['repo'] == spec['name']:
+ del spec['repo']
+ if 'build-mode' in spec and spec['build-mode'] == \
+ self._static_defaults['chunk']['build-mode']:
+ del spec['build-mode']
+ if 'prefix' in spec and spec['prefix'] == \
+ self._static_defaults['chunk']['prefix']:
+ del spec['prefix']
+ self._unset_stratum_specs_defaults(morph, 'strata')
+
+ def _set_chunk_defaults(self, morph):
+ if morph['max-jobs'] is not None:
+ morph['max-jobs'] = int(morph['max-jobs'])
+
+ def _unset_chunk_defaults(self, morph): # pragma: no cover
+ for key in self._static_defaults['chunk']:
+ if key not in morph: continue
+ if 'commands' not in key: continue
+ attr = key.replace('-', '_')
+ default_bs = self._static_defaults['chunk']['build-system']
+ bs = morphlib.buildsystem.lookup_build_system(
+ morph.get('build-system', default_bs))
+ default_value = getattr(bs, attr)
+ if morph[key] == default_value:
+ del morph[key]
+
+ def set_commands(self, morph):
+ if morph['kind'] == 'chunk':
+ for key in self._static_defaults['chunk']:
+ if 'commands' not in key: continue
+ if key not in morph:
+ attr = '_'.join(key.split('-'))
+ default = self._static_defaults['chunk']['build-system']
+ bs = morphlib.buildsystem.lookup_build_system(
+ morph.get('build-system', default))
+ morph[key] = getattr(bs, attr)