diff options
author | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2017-09-22 13:14:42 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2017-10-08 17:03:36 +0900 |
commit | a37e54d3f4b63f1d857b72cc0c823cd830ef99e2 (patch) | |
tree | 691d85acce765922d43c3d8849a3e5a584c8414e | |
parent | 8c6e8e1661531267e1715edf67e14678ab845ce8 (diff) | |
download | buildstream-a37e54d3f4b63f1d857b72cc0c823cd830ef99e2.tar.gz |
_loader.py: Removing all traces of loading variants.
-rw-r--r-- | buildstream/_loader.py | 468 |
1 files changed, 16 insertions, 452 deletions
diff --git a/buildstream/_loader.py b/buildstream/_loader.py index 2385cf4c5..b02750cbf 100644 --- a/buildstream/_loader.py +++ b/buildstream/_loader.py @@ -42,9 +42,6 @@ class Symbol(): FILENAME = "filename" KIND = "kind" DEPENDS = "depends" - VARIANT = "variant" - VARIANTS = "variants" - PROJECT_VARIANT = "project-variant" ARCHES = "arches" HOST_ARCHES = "host-arches" SOURCES = "sources" @@ -63,88 +60,15 @@ class Symbol(): # A simple dependency object # class Dependency(): - def __init__(self, owner_name, name, variant_name=None, filename=None, + def __init__(self, owner_name, name, filename=None, dep_type=None, provenance=None): self.owner = owner_name self.name = name - self.variant_name = variant_name self.filename = filename self.dep_type = dep_type self.provenance = provenance -# Holds a variant dictionary and normalized Dependency list -# for later compositing, after resolving which variants to choose -# -class Variant(): - def __init__(self, owner, data, project_variants): - self.data = data - self.name = _yaml.node_get(self.data, str, Symbol.VARIANT) - self.dependencies = extract_depends_from_node(owner, self.data) - - # Assert valid project variants - assert_project_variant(self.data, project_variants) - - del self.data[Symbol.VARIANT] - - -# VariantDisagreement is raised to indicate that 2 elements -# depend on a given element in a way that conflicts -# -class VariantDisagreement(Exception): - def __init__(self, element_config1, element_config2, variant=None): - path1 = element_config1.make_path() - - if variant: - path2 = "Expected variant {}".format(variant) - else: - path2 = element_config2.make_path() - super(VariantDisagreement, self).__init__( - "Variant disagreement occurred.\n {}\n {}" - .format(path1, path2)) - - -# VariantInvalid is raised to indicate that a nonexisting -# variant name on an element was requested -# -class VariantInvalid(Exception): - def __init__(self, dependency, element, variant_name): - message = "Variant '{}' is invalid for element {}" \ - .format(variant_name, element.name) - if dependency: - message = "{}: {}".format(dependency.provenance, message) - - super(VariantInvalid, self).__init__(message) - - -# A utility object wrapping the LoadElement, this represents -# a hypothetical configuration of an element, it describes: -# -# o The dependency pulling in the element -# o The chosen variant -# o The dependencies the element has when configured for the given variant -# -class LoadElementConfig(): - def __init__(self, parent_config, element, variant_name=None): - self.parent_config = parent_config - self.element = element - self.filename = element.filename - self.variant_name = variant_name - self.deps = element.deps_for_variant(variant_name) - - def __str__(self): - if self.variant_name: - return "{} ({})".format(self.filename, self.variant_name) - - return self.filename - - def make_path(self): - path = str(self) - if self.parent_config: - path = self.parent_config.make_path() + ' -> ' + path - return path - - # resolve_arch() # # Composites the data node with the active arch dict and discards @@ -181,16 +105,6 @@ def resolve_arch(data, host_arch, target_arch=None): resolve_single_arch_conditional(Symbol.ARCHES, active_arch=target_arch or host_arch) -def assert_project_variant(node, project_variants): - variant = _yaml.node_get(node, str, - Symbol.PROJECT_VARIANT, - default_value="") or None - if variant and variant not in project_variants: - provenance = _yaml.node_get_provenance(node, key=Symbol.PROJECT_VARIANT) - raise LoadError(LoadErrorReason.INVALID_DATA, - "{}: Specified invalid project variant '{}'".format(provenance, variant)) - - # A transient object breaking down what is loaded # allowing us to do complex operations in multiple # passes @@ -199,7 +113,7 @@ class LoadElement(): def __init__(self, data, filename, basedir, host_arch, target_arch, - elements, project_variants): + elements): self.filename = filename self.data = data @@ -216,52 +130,24 @@ class LoadElement(): 'kind', 'depends', 'sources', 'variables', 'environment', 'environment-nocache', 'config', 'public', 'description', - 'arches', 'variants', 'host-arches' + 'arches', 'host-arches' ]) # Process arch conditionals resolve_arch(self.data, self.host_arch, self.target_arch) - # Assert valid project variant, if any - assert_project_variant(self.data, project_variants) - - # Dependency objects after resolving variants - self.variant_name = None - self.deps = [] + # Cache dependency tree to detect circular dependencies self.dep_cache = None - # Base dependencies - self.base_deps = extract_depends_from_node(self.name, self.data) - - # Load the Variants - self.variants = [] - variants_node = _yaml.node_get(self.data, list, Symbol.VARIANTS, default_value=[]) - for variant_node in variants_node: - index = variants_node.index(variant_node) - variant_node = _yaml.node_get(self.data, Mapping, Symbol.VARIANTS, indices=[index]) - variant = Variant(self.name, variant_node, project_variants) - - # Process arch conditionals on individual variants - resolve_arch(variant.data, self.host_arch, self.target_arch) - self.variants.append(variant) - - if len(self.variants) == 1: - provenance = _yaml.node_get_provenance(self.data, key=Symbol.VARIANTS) - raise LoadError(LoadErrorReason.INVALID_DATA, - "%s: Only one variant declared, an element " - "declaring variants must declare at least two variants" % - str(provenance)) - - # Strip em from the data now - del self.data[Symbol.VARIANTS] + # Dependencies + self.deps = extract_depends_from_node(self.name, self.data) ############################################# # Routines used by the Loader # ############################################# # Checks if this element depends on another element, directly - # or indirectly. This does NOT follow variants and is only - # useful after variants are resolved. + # or indirectly. # def depends(self, other): @@ -286,79 +172,6 @@ class LoadElement(): # And we depend on everything this element depends on self.dep_cache.update(elt.dep_cache) - # Fetch a Variant by name - # - def lookup_variant(self, variant_name): - for variant in self.variants: - if variant.name == variant_name: - return variant - - # deps_for_variant() - # - # Fetches the set of Dependency objects for a given variant name - # - def deps_for_variant(self, variant_name): - deps = copy.copy(self.base_deps) - - variant = None - if variant_name: - variant = self.lookup_variant(variant_name) - - # If the Dependency is already mentioned in the base dependencies - # a variant may modify it by overriding the dependency variant - if variant: - - for variant_dep in variant.dependencies: - override = False - for dep in deps: - if dep.filename == variant_dep.filename: - index = deps.index(dep) - deps[index] = variant_dep - override = True - break - - # Dependency not already declared, append new one - if not override: - deps.append(variant_dep) - - # Return the list of dependencies for this variant - return deps - - # Apply the chosen variant into the element data - # - def apply_element_config(self, element_config): - - # Save the final decision on Dependencies - self.element_config = element_config - self.variant_name = element_config.variant_name - self.deps = element_config.deps - - variant = None - if self.variant_name: - variant = self.lookup_variant(self.variant_name) - - 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.data, 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), - element_config.variant_name, - e.actual_type.__name__, e.path, - e.expected_type.__name__)) from e - # Creates an array of dependency dicts from a given dict node 'data', # allows both strings and dicts for expressing the dependency and @@ -378,12 +191,7 @@ def extract_depends_from_node(owner, data): dependency = Dependency(owner, dep, filename=dep, provenance=dep_provenance) elif isinstance(dep, Mapping): - _yaml.node_validate(dep, ['filename', 'type', 'variant']) - - # Make variant optional, for this we set it to None after - variant = _yaml.node_get(dep, str, Symbol.VARIANT, default_value="") - if not variant: - variant = None + _yaml.node_validate(dep, ['filename', 'type']) # Make type optional, for this we set it to None after dep_type = _yaml.node_get(dep, str, Symbol.TYPE, default_value="") @@ -396,7 +204,7 @@ def extract_depends_from_node(owner, data): (str(provenance), dep_type)) filename = _yaml.node_get(dep, str, Symbol.FILENAME) - dependency = Dependency(owner, filename, variant_name=variant, filename=filename, + dependency = Dependency(owner, filename, filename=filename, dep_type=dep_type, provenance=dep_provenance) else: @@ -424,7 +232,7 @@ def extract_depends_from_node(owner, data): # class Loader(): - def __init__(self, basedir, filename, variant, host_arch, target_arch, project_variants): + def __init__(self, basedir, filename, host_arch, target_arch): # Ensure we have an absolute path for the base directory # @@ -446,14 +254,6 @@ class Loader(): self.target_filename = filename self.target = filename - # Optional variant - self.target_variant = variant - - # Project Variant - self.project_variants = project_variants - self.project_variant = None - self.project_variant_requester = None - self.host_arch = host_arch self.target_arch = target_arch @@ -481,24 +281,10 @@ class Loader(): # First pass, recursively load files and populate our table of LoadElements # profile_start(Topics.LOAD_PROJECT, self.target_filename) - - try: - target = self.load_file(self.target_filename, rewritable, ticker) - if self.target_variant and target.lookup_variant(self.target_variant) is None: - raise VariantInvalid(None, target, self.target_variant) - except VariantInvalid as e: - raise LoadError(LoadErrorReason.INVALID_VARIANT, str(e)) from e - + target = self.load_file(self.target_filename, rewritable, ticker) profile_end(Topics.LOAD_PROJECT, self.target_filename) # - # Deal with variants - # - profile_start(Topics.VARIANTS, self.target_filename) - self.resolve_variants() - profile_end(Topics.VARIANTS, self.target_filename) - - # # Now that we've resolve the dependencies, scan them for circular dependencies # profile_start(Topics.CIRCULAR_CHECK, self.target_filename) @@ -506,11 +292,6 @@ class Loader(): profile_end(Topics.CIRCULAR_CHECK, self.target_filename) # - # Resolve the project variant, if any - # - self.resolve_project_variant(self.target) - - # # Sort direct dependencies of elements by their dependency ordering # profile_start(Topics.SORT_DEPENDENCIES, self.target_filename) @@ -543,199 +324,16 @@ class Loader(): data = _yaml.load(fullpath, shortname=filename, copy_tree=rewritable) element = LoadElement(data, filename, self.basedir, self.host_arch, self.target_arch, - self.elements, self.project_variants) + self.elements) self.elements[filename] = element - # Load all possible dependency files for the new LoadElement - for dep in element.base_deps: - self.load_dependency_file(dep, rewritable, ticker) - - for variant in element.variants: - for dep in variant.dependencies: - self.load_dependency_file(dep, rewritable, ticker) + # Load all dependency files for the new LoadElement + for dep in element.deps: + self.load_file(dep.filename, rewritable, ticker) return element - def load_dependency_file(self, dependency, rewritable, ticker): - - element = self.load_file(dependency.filename, rewritable, ticker) - - # Check for invalid variant dependencies - if dependency.variant_name and element.lookup_variant(dependency.variant_name) is None: - raise VariantInvalid(dependency, element, dependency.variant_name) - - ######################################## - # Resolving Variants # - ######################################## - # - # The first rule of variants is that for any given element provided by - # itself as a pipeline target, all variants of that element must be - # buildable and not present any variant conflict. - # - # However, any variant of a given element that is not the target may - # end up being built differently - this is because siblings in the pipeline - # may prefer a variant of a dependency for which the given element's - # dependency was ambivalent. - # - # Considering that variants can effect what and how an element depends - # on other elements; resolving the variants is a trial and error activity, - # even if this is not true for the target element (as stated as the first - # rule), it is true of every other element in a given pipeline. - # - # As such, resolving the variants is a recursive process of trial and error - # - # 1.) Construct a "variant tree" - # - # The variant tree is a tree of elements dicts, these refer to the - # element filename and contain an array of variants; each member of - # the variant array holds an array of the dependencies which would be - # chosen if the given variant of the given element were chosen. - # - # 2.) Starting at the top level, try to resolve the - # - # For each element; collect an array of its variants; each member of - # the variant array speaks for the dependencies of the given element - # - def resolve_variants(self): - target_variant = self.target_variant - target_element = self.elements[self.target] - - # If a target was not specified, this is an explicit request for the - # first variant - if not target_variant and target_element.variants: - target_variant = target_element.variants[0].name - - # Recurse until the cows come home ! - # - toplevel_config = LoadElementConfig(None, target_element, target_variant) - try: - pool, _ = self.try_element_configuration(toplevel_config, {}, {}) - - except VariantDisagreement as e: - raise LoadError(LoadErrorReason.VARIANT_DISAGREEMENT, str(e)) from e - - # Now apply the chosen variant configurations - # - for _, element_config in pool.items(): - element_config.element.apply_element_config(element_config) - - # - # try_element_configuration() - # - # Args: - # element_config (LoadElementConfig): the element to try - # pool (dict): A dict of LoadElementConfig objects - # restruction_pool: A dict of variant names explicitly required - # - # Returns: - # A new configuration - # - # With a given configuration in context, reports whether the configuration - # is a valid one for the given element and all of the possible elements on - # which this element depends, returning a new configuration comprised of - # the given configuration and the first valid configuration of its - # dependencies - # - def try_element_configuration(self, element_config, pool, restriction_pool): - - if element_config.filename in pool: - config = pool[element_config.filename] - - # The configuration pool can have only one selected configuration - # for each element, handle intersections and conflicts. - # - if config.element is element_config.element: - - if config.variant_name != element_config.variant_name: - - # Two different variants of the same element should be reached - # on a path of variant agreement. - raise VariantDisagreement(element_config, config) - else: - # A path converges on the same element configuration, - # no need to recurse as we already have a result for this. - return (pool, restriction_pool) - - if element_config.filename in restriction_pool: - restrict_variant = restriction_pool[element_config.filename] - - if element_config.variant_name and element_config.variant_name != restrict_variant: - raise VariantDisagreement(element_config, None, restrict_variant) - - # Now add ourselves to the pool and recurse into the dependency list - new_pool = dict(pool) - new_pool[element_config.filename] = element_config - - # Add restriction pool to go along with this pool - # - new_restriction_pool = dict(restriction_pool) - for dep in element_config.deps: - if dep.variant_name: - new_restriction_pool[dep.name] = dep.variant_name - - return self.configure_dependency_variants(element_config, element_config.deps, - new_pool, new_restriction_pool) - - def configure_dependency_variants(self, parent_config, deps, pool, restriction_pool): - - # This is just the end of the list - if not deps: - return (pool, restriction_pool) - - # Loop over the possible variants for this dependency - dependency = deps[0] - element = self.elements[dependency.name] - - # First create one list of element configurations to try, one for - # each possible variant under this element configuration - # - element_configs_to_try = [] - if dependency.variant_name: - config = LoadElementConfig(parent_config, element, dependency.variant_name) - element_configs_to_try.append(config) - elif len(element.variants) == 0: - config = LoadElementConfig(parent_config, element, None) - element_configs_to_try.append(config) - else: - for variant in element.variants: - config = LoadElementConfig(parent_config, element, variant.name) - element_configs_to_try.append(config) - - # Loop over every possible element configuration for this dependency - # - result_pool = None - result_restrict = None - last_error = None - - for element_config in element_configs_to_try: - - # Reset the attempted new pool for each try - result_pool = None - result_restrict = None - - try: - # If this configuration of the this element succeeds... - try_pool, try_restrict = self.try_element_configuration(element_config, pool, restriction_pool) - - # ... Then recurse into sibling elements - result_pool, result_restrict = self.configure_dependency_variants(parent_config, deps[1:], - try_pool, try_restrict) - - except VariantDisagreement as e: - last_error = e - continue - - # If we've reached here without any disagreement, then we've found the - # first valid configuration, which should take priority over any others. - break - - # If unable to find any valid configuration, raise a VariantDisagreement - if not result_pool: - raise last_error - - return (result_pool, result_restrict) - ######################################## # Checking Circular Dependencies # ######################################## @@ -771,40 +369,6 @@ class Loader(): validated[element_name] = True ######################################## - # Resolve Project Variant # - ######################################## - def resolve_project_variant(self, element_name, resolved=None): - if resolved is None: - resolved = {} - - if resolved.get(element_name) is not None: - return - - element = self.elements[element_name] - project_variant = _yaml.node_get(element.data, str, - Symbol.PROJECT_VARIANT, - default_value="") or None - - if project_variant: - if not self.project_variant: - self.project_variant = project_variant - self.project_variant_requester = element_name - elif project_variant != self.project_variant: - request1 = "{} requested project variant '{}'" \ - .format(self.project_variant_requester, - self.project_variant) - request2 = "{} requested project variant '{}'" \ - .format(element_name, project_variant) - raise LoadError(LoadErrorReason.VARIANT_DISAGREEMENT, - "Project variant disagreement occurred.\n {}\n {}" - .format(request1, request2)) - - for dep in element.deps: - self.resolve_project_variant(dep.name, resolved) - - resolved[element_name] = True - - ######################################## # Element Sorting # ######################################## # @@ -905,7 +469,7 @@ class Loader(): meta_sources.append(meta_source) kind = _yaml.node_get(data, str, Symbol.KIND) - meta_element = MetaElement(element_name, kind, element.variant_name, + meta_element = MetaElement(element_name, kind, elt_provenance, meta_sources, _yaml.node_get(data, Mapping, Symbol.CONFIG, default_value={}), _yaml.node_get(data, Mapping, Symbol.VARIABLES, default_value={}), |