summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Van Berkom <tristan.vanberkom@codethink.co.uk>2017-07-17 22:47:07 +0900
committerTristan Van Berkom <tristan.vanberkom@codethink.co.uk>2017-07-17 22:55:29 +0900
commit68748a1f9cf8f91c60bca5b53ea7946bb1d9bb50 (patch)
tree6490975ea7909f7b11196f14213dbfc01acf6cc6
parenteaba4aaaaf32a424f0f8c1a935d95fb46dffd984 (diff)
downloadbuildstream-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.py124
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: