diff options
author | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2017-07-17 22:47:07 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2017-07-17 22:55:29 +0900 |
commit | 68748a1f9cf8f91c60bca5b53ea7946bb1d9bb50 (patch) | |
tree | 6490975ea7909f7b11196f14213dbfc01acf6cc6 | |
parent | eaba4aaaaf32a424f0f8c1a935d95fb46dffd984 (diff) | |
download | buildstream-68748a1f9cf8f91c60bca5b53ea7946bb1d9bb50.tar.gz |
project.py: Support project aliases.
Now the project is created in two phases
o First the constructor will resolve only a minimal set
from the loaded file after compositing against the default,
this includes resolving arch conditionals and loading a list
of floating variants.
o Second step, in Project._resolve(variant), a variant is chosen
and composited against the loaded configuration, before resolving
things such as variables and environments and element specifics.
-rw-r--r-- | buildstream/project.py | 124 |
1 files changed, 101 insertions, 23 deletions
diff --git a/buildstream/project.py b/buildstream/project.py index 6ec270857..e16fd9ed1 100644 --- a/buildstream/project.py +++ b/buildstream/project.py @@ -28,17 +28,27 @@ was loaded from. import os import multiprocessing # for cpu_count() from collections import Mapping +from .exceptions import LoadError, LoadErrorReason +from ._yaml import CompositePolicy, CompositeTypeError, CompositeOverrideError from . import utils from . import _site from . import _yaml from . import _loader # For resolve_arch() from ._profile import Topics, profile_start, profile_end - # The separator we use for user specified aliases _ALIAS_SEPARATOR = ':' +# Private object for dealing with project variants +# +class _ProjectVariant(): + def __init__(self, data): + self.name = _yaml.node_get(data, str, 'variant') + self.data = data + del self.data['variant'] + + class Project(): """Project Configuration @@ -52,9 +62,6 @@ class Project(): """ def __init__(self, directory, host_arch, target_arch=None): - host_arch = host_arch - target_arch = target_arch or host_arch - self.name = None """str: The project name""" @@ -71,9 +78,12 @@ class Project(): self._plugin_source_paths = [] # Paths to custom sources self._plugin_element_paths = [] # Paths to custom plugins self._cache_key = None + self._variants = [] + self._host_arch = host_arch + self._target_arch = target_arch or host_arch profile_start(Topics.LOAD_PROJECT, self.directory.replace(os.sep, '-')) - self._load(host_arch, target_arch) + self._unresolved_config = self._load_first_half() profile_end(Topics.LOAD_PROJECT, self.directory.replace(os.sep, '-')) def translate_url(self, url): @@ -98,14 +108,14 @@ class Project(): return url - # _load(): + # _load_first_half(): # # Loads the project configuration file in the project directory # and extracts some things. # # Raises: LoadError if there was a problem with the project.conf # - def _load(self, host_arch, target_arch): + def _load_first_half(self): # Load builtin default projectfile = os.path.join(self.directory, "project.conf") @@ -116,49 +126,117 @@ class Project(): variables = _yaml.node_get(config, Mapping, 'variables') variables['max-jobs'] = multiprocessing.cpu_count() - variables['bst-host-arch'] = host_arch - variables['bst-target-arch'] = target_arch + variables['bst-host-arch'] = self._host_arch + variables['bst-target-arch'] = self._target_arch # This is kept around for compatibility with existing definitions, # but we should probably remove it due to being ambiguous. - variables['bst-arch'] = host_arch + variables['bst-arch'] = self._host_arch # Load project local config and override the builtin project_conf = _yaml.load(projectfile) _yaml.composite(config, project_conf, typesafe=True) # Resolve arches keyword, project may have arch conditionals - _loader.resolve_arch(config, host_arch, target_arch) + _loader.resolve_arch(config, self._host_arch, self._target_arch) + + # Resolve element base path + elt_path = _yaml.node_get(config, str, 'element-path') + self.element_path = os.path.join(self.directory, elt_path) + + # Load variants + variants_node = _yaml.node_get(config, list, 'variants', default_value=[]) + for variant_node in variants_node: + index = variants_node.index(variant_node) + variant_node = _yaml.node_get(config, Mapping, 'variants', indices=[index]) + variant = _ProjectVariant(variant_node) + + # Process arch conditionals on individual variants + _loader.resolve_arch(variant.data, self._host_arch, self._target_arch) + self._variants.append(variant) + + if len(self._variants) == 1: + provenance = _yaml.node_get_provenance(config, key='variants') + raise LoadError(LoadErrorReason.INVALID_DATA, + "{}: Only one variant declared, a project " + "declaring variants must declare at least two" + .format(provenance)) + + return config + + # _resolve(): + # + # First resolves the project variant and then resolves the remaining + # properties of the project based on the final composition + # + # Raises: LoadError if there was a problem with the project.conf + # + def _resolve(self, variant_name): + + # Apply the selected variant + # + variant = None + if variant_name: + variant = self._lookup_variant(variant_name) + elif self._variants: + variant = self._variants[0] + + if variant: + provenance = _yaml.node_get_provenance(variant.data) + + # Composite anything from the variant data into the element data + # + # Possibly this should not be typesafe, since branch names can + # possibly be strings or interpreted by YAML as integers (for + # numeric branch names) + # + try: + _yaml.composite_dict(self._unresolved_config, variant.data, + policy=CompositePolicy.ARRAY_APPEND, + typesafe=True) + except CompositeTypeError as e: + raise LoadError( + LoadErrorReason.ILLEGAL_COMPOSITE, + "%s: Variant '%s' specifies type '%s' for path '%s', expected '%s'" % + (str(provenance), + variant.name, + e.actual_type.__name__, e.path, + e.expected_type.__name__)) from e # The project name - self.name = _yaml.node_get(config, str, 'name') + self.name = _yaml.node_get(self._unresolved_config, str, 'name') # Load the plugin paths - plugins = _yaml.node_get(config, Mapping, 'plugins', default_value={}) + plugins = _yaml.node_get(self._unresolved_config, Mapping, 'plugins', default_value={}) self._plugin_source_paths = [os.path.join(self.directory, path) for path in self._extract_plugin_paths(plugins, 'sources')] self._plugin_element_paths = [os.path.join(self.directory, path) for path in self._extract_plugin_paths(plugins, 'elements')] - # Resolve element base path - elt_path = _yaml.node_get(config, str, 'element-path') - self.element_path = os.path.join(self.directory, elt_path) - # Source url aliases - self._aliases = _yaml.node_get(config, Mapping, 'aliases', default_value={}) + self._aliases = _yaml.node_get(self._unresolved_config, Mapping, 'aliases', default_value={}) # Load base variables - self._variables = _yaml.node_get(config, Mapping, 'variables') + self._variables = _yaml.node_get(self._unresolved_config, Mapping, 'variables') # Load sandbox configuration - self._environment = _yaml.node_get(config, Mapping, 'environment') - self._env_nocache = _yaml.node_get(config, list, 'environment-nocache') + self._environment = _yaml.node_get(self._unresolved_config, Mapping, 'environment') + self._env_nocache = _yaml.node_get(self._unresolved_config, list, 'environment-nocache') # Load project split rules - self._splits = _yaml.node_get(config, Mapping, 'split-rules') + self._splits = _yaml.node_get(self._unresolved_config, Mapping, 'split-rules') # Element configurations - self._elements = _yaml.node_get(config, Mapping, 'elements', default_value={}) + self._elements = _yaml.node_get(self._unresolved_config, Mapping, 'elements', default_value={}) + + def _lookup_variant(self, variant_name): + for variant in self._variants: + if variant.name == variant_name: + return variant + + def _list_variants(self): + for variant in self._variants: + yield variant.name def _extract_plugin_paths(self, node, name): if not node: |