summaryrefslogtreecommitdiff
path: root/src/buildstream/element.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/element.py')
-rw-r--r--src/buildstream/element.py216
1 files changed, 139 insertions, 77 deletions
diff --git a/src/buildstream/element.py b/src/buildstream/element.py
index f1e909bce..f7d49d57f 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -80,7 +80,7 @@ import copy
import warnings
from collections import OrderedDict
import contextlib
-from contextlib import contextmanager
+from contextlib import contextmanager, suppress
from functools import partial
from itertools import chain
import string
@@ -106,6 +106,7 @@ from .sandbox._sandboxremote import SandboxRemote
from .types import CoreWarnings, Scope, _CacheBuildTrees, _KeyStrength
from ._artifact import Artifact
from ._elementsources import ElementSources
+from ._loader import Symbol, MetaSource
from .storage.directory import Directory
from .storage._filebaseddirectory import FileBasedDirectory
@@ -120,7 +121,7 @@ if TYPE_CHECKING:
from .sandbox import Sandbox
from .source import Source
from ._context import Context
- from ._loader.metaelement import MetaElement
+ from ._loader import LoadElement
from ._project import Project
# pylint: enable=cyclic-import
@@ -157,8 +158,8 @@ class Element(Plugin):
# The defaults from the yaml file and project
__defaults = None
- # A hash of Element by MetaElement
- __instantiated_elements = {} # type: Dict[MetaElement, Element]
+ # A hash of Element by LoadElement
+ __instantiated_elements = {} # type: Dict[LoadElement, Element]
# A list of (source, ref) tuples which were redundantly specified
__redundant_source_refs = [] # type: List[Tuple[Source, SourceRef]]
@@ -197,15 +198,17 @@ class Element(Plugin):
"""Whether the element produces an artifact when built.
"""
- def __init__(self, context: "Context", project: "Project", meta: "MetaElement", plugin_conf: Dict[str, Any]):
+ def __init__(
+ self, context: "Context", project: "Project", load_element: "LoadElement", plugin_conf: Dict[str, Any]
+ ):
self.__cache_key_dict = None # Dict for cache key calculation
self.__cache_key = None # Our cached cache key
- super().__init__(meta.name, context, project, meta.provenance, "element")
+ super().__init__(load_element.name, context, project, load_element.provenance, "element")
# Ensure the project is fully loaded here rather than later on
- if not meta.is_junction:
+ if not load_element.first_pass:
project.ensure_fully_loaded()
self.project_name = self._get_project().name
@@ -270,44 +273,44 @@ class Element(Plugin):
self.__resolved_initial_state = False # Whether the initial state of the Element has been resolved
# Ensure we have loaded this class's defaults
- self.__init_defaults(project, plugin_conf, meta.kind, meta.is_junction)
+ self.__init_defaults(project, plugin_conf, load_element.kind, load_element.first_pass)
# Collect the composited variables and resolve them
- variables = self.__extract_variables(project, meta)
+ variables = self.__extract_variables(project, load_element)
variables["element-name"] = self.name
self.__variables = Variables(variables)
- if not meta.is_junction:
+ if not load_element.first_pass:
self.__variables.check()
# Collect the composited environment now that we have variables
- unexpanded_env = self.__extract_environment(project, meta)
+ unexpanded_env = self.__extract_environment(project, load_element)
self.__variables.expand(unexpanded_env)
self.__environment = unexpanded_env.strip_node_info()
# Collect the environment nocache blacklist list
- nocache = self.__extract_env_nocache(project, meta)
+ nocache = self.__extract_env_nocache(project, load_element)
self.__env_nocache = nocache
# Grab public domain data declared for this instance
- self.__public = self.__extract_public(meta)
+ self.__public = self.__extract_public(load_element)
self.__variables.expand(self.__public)
self.__dynamic_public = None
# Collect the composited element configuration and
# ask the element to configure itself.
- self.__config = self.__extract_config(meta)
+ self.__config = self.__extract_config(load_element)
self.__variables.expand(self.__config)
self._configure(self.__config)
# Extract remote execution URL
- if meta.is_junction:
+ if load_element.first_pass:
self.__remote_execution_specs = None
else:
self.__remote_execution_specs = project.remote_execution_specs
# Extract Sandbox config
- sandbox_config = self.__extract_sandbox_config(project, meta)
+ sandbox_config = self.__extract_sandbox_config(project, load_element)
self.__variables.expand(sandbox_config)
self.__sandbox_config = SandboxConfig(sandbox_config, context.platform)
@@ -874,61 +877,56 @@ class Element(Plugin):
# Private Methods used in BuildStream #
#############################################################
- # _new_from_meta():
+ # _new_from_load_element():
#
# Recursively instantiate a new Element instance, its sources
- # and its dependencies from a meta element.
+ # and its dependencies from a LoadElement.
+ #
+ # FIXME: Need to use an iterative algorithm here since recursion
+ # will limit project dependency depth.
#
# Args:
- # meta (MetaElement): The meta element
+ # load_element (LoadElement): The LoadElement
# task (Task): A task object to report progress to
#
# Returns:
# (Element): A newly created Element instance
#
@classmethod
- def _new_from_meta(cls, meta, task=None):
+ def _new_from_load_element(cls, load_element, task=None):
- if not meta.first_pass:
- meta.project.ensure_fully_loaded()
+ if not load_element.first_pass:
+ load_element.project.ensure_fully_loaded()
- if meta in cls.__instantiated_elements:
- return cls.__instantiated_elements[meta]
+ with suppress(KeyError):
+ return cls.__instantiated_elements[load_element]
- element = meta.project.create_element(meta)
- cls.__instantiated_elements[meta] = element
+ element = load_element.project.create_element(load_element)
+ cls.__instantiated_elements[load_element] = element
- # Instantiate sources and generate their keys
- for meta_source in meta.sources:
- meta_source.first_pass = meta.is_junction
- source = meta.project.create_source(meta_source, variables=element.__variables)
+ # Load the sources from the LoadElement
+ element.__load_sources(load_element)
- redundant_ref = source._load_ref()
+ # Instantiate dependencies
+ for dep in load_element.dependencies:
+ dependency = Element._new_from_load_element(dep.element, task)
- element.__sources.add_source(source)
+ if dep.dep_type != "runtime":
+ element.__build_dependencies.append(dependency)
+ dependency.__reverse_build_deps.add(element)
- # Collect redundant refs which occurred at load time
- if redundant_ref is not None:
- cls.__redundant_source_refs.append((source, redundant_ref))
+ if dep.dep_type != "build":
+ element.__runtime_dependencies.append(dependency)
+ dependency.__reverse_runtime_deps.add(element)
+
+ if dep.strict:
+ element.__strict_dependencies.append(dependency)
- # Instantiate dependencies
- for meta_dep in meta.dependencies:
- dependency = Element._new_from_meta(meta_dep, task)
- element.__runtime_dependencies.append(dependency)
- dependency.__reverse_runtime_deps.add(element)
no_of_runtime_deps = len(element.__runtime_dependencies)
element.__runtime_deps_without_strict_cache_key = no_of_runtime_deps
element.__runtime_deps_without_cache_key = no_of_runtime_deps
element.__runtime_deps_uncached = no_of_runtime_deps
- for meta_dep in meta.build_dependencies:
- dependency = Element._new_from_meta(meta_dep, task)
- element.__build_dependencies.append(dependency)
- dependency.__reverse_build_deps.add(element)
-
- if meta_dep in meta.strict_dependencies:
- element.__strict_dependencies.append(dependency)
-
no_of_build_deps = len(element.__build_dependencies)
element.__build_deps_without_strict_cache_key = no_of_build_deps
element.__build_deps_without_cache_key = no_of_build_deps
@@ -2203,6 +2201,63 @@ class Element(Plugin):
# Private Local Methods #
#############################################################
+ # __load_sources()
+ #
+ # Load the Source objects from the LoadElement
+ #
+ def __load_sources(self, load_element):
+ project = self._get_project()
+ workspace = self._get_workspace()
+ meta_sources = []
+
+ # If there's a workspace for this element then we just load a workspace
+ # source plugin instead of the real plugins
+ if workspace:
+ workspace_node = {"kind": "workspace"}
+ workspace_node["path"] = workspace.get_absolute_path()
+ workspace_node["last_build"] = str(workspace.to_dict().get("last_build", ""))
+ meta = MetaSource(
+ self.name,
+ 0,
+ self.get_kind(),
+ "workspace",
+ Node.from_dict(workspace_node),
+ None,
+ load_element.first_pass,
+ )
+ meta_sources.append(meta)
+ else:
+ sources = load_element.node.get_sequence(Symbol.SOURCES, default=[])
+ for index, source in enumerate(sources):
+ kind = source.get_scalar(Symbol.KIND)
+
+ # The workspace source plugin is only valid for internal use
+ if kind.as_str() == "workspace":
+ raise LoadError(
+ "{}: Invalid usage of workspace source kind".format(kind.get_provenance()),
+ LoadErrorReason.INVALID_DATA,
+ )
+ del source[Symbol.KIND]
+
+ # Directory is optional
+ directory = source.get_str(Symbol.DIRECTORY, default=None)
+ if directory:
+ del source[Symbol.DIRECTORY]
+ meta_source = MetaSource(
+ self.name, index, self.get_kind(), kind.as_str(), source, directory, load_element.first_pass
+ )
+ meta_sources.append(meta_source)
+
+ for meta_source in meta_sources:
+ source = project.create_source(meta_source, variables=self.__variables)
+ redundant_ref = source._load_ref()
+
+ self.__sources.add_source(source)
+
+ # Collect redundant refs which occurred at load time
+ if redundant_ref is not None:
+ self.__redundant_source_refs.append((source, redundant_ref))
+
# __get_dependency_refs()
#
# Retrieve the artifact refs of the element's dependencies
@@ -2433,13 +2488,13 @@ class Element(Plugin):
yield sandbox
@classmethod
- def __compose_default_splits(cls, project, defaults, is_junction):
+ def __compose_default_splits(cls, project, defaults, first_pass):
- element_public = defaults.get_mapping("public", default={})
+ element_public = defaults.get_mapping(Symbol.PUBLIC, default={})
element_bst = element_public.get_mapping("bst", default={})
element_splits = element_bst.get_mapping("split-rules", default={})
- if is_junction:
+ if first_pass:
splits = element_splits.clone()
else:
assert project._splits is not None
@@ -2450,10 +2505,10 @@ class Element(Plugin):
element_bst["split-rules"] = splits
element_public["bst"] = element_bst
- defaults["public"] = element_public
+ defaults[Symbol.PUBLIC] = element_public
@classmethod
- def __init_defaults(cls, project, plugin_conf, kind, is_junction):
+ def __init_defaults(cls, project, plugin_conf, kind, first_pass):
# Defaults are loaded once per class and then reused
#
if cls.__defaults is None:
@@ -2468,11 +2523,11 @@ class Element(Plugin):
raise e
# Special case; compose any element-wide split-rules declarations
- cls.__compose_default_splits(project, defaults, is_junction)
+ cls.__compose_default_splits(project, defaults, first_pass)
# Override the element's defaults with element specific
# overrides from the project.conf
- if is_junction:
+ if first_pass:
elements = project.first_pass_config.element_overrides
else:
elements = project.element_overrides
@@ -2488,29 +2543,30 @@ class Element(Plugin):
# creating sandboxes for this element
#
@classmethod
- def __extract_environment(cls, project, meta):
- default_env = cls.__defaults.get_mapping("environment", default={})
+ def __extract_environment(cls, project, load_element):
+ default_env = cls.__defaults.get_mapping(Symbol.ENVIRONMENT, default={})
+ element_env = load_element.node.get_mapping(Symbol.ENVIRONMENT, default={}) or Node.from_dict({})
- if meta.is_junction:
+ if load_element.first_pass:
environment = Node.from_dict({})
else:
environment = project.base_environment.clone()
default_env._composite(environment)
- meta.environment._composite(environment)
+ element_env._composite(environment)
environment._assert_fully_composited()
return environment
@classmethod
- def __extract_env_nocache(cls, project, meta):
- if meta.is_junction:
+ def __extract_env_nocache(cls, project, load_element):
+ if load_element.first_pass:
project_nocache = []
else:
project_nocache = project.base_env_nocache
- default_nocache = cls.__defaults.get_str_list("environment-nocache", default=[])
- element_nocache = meta.env_nocache
+ default_nocache = cls.__defaults.get_str_list(Symbol.ENV_NOCACHE, default=[])
+ element_nocache = load_element.node.get_str_list(Symbol.ENV_NOCACHE, default=[])
# Accumulate values from the element default, the project and the element
# itself to form a complete list of nocache env vars.
@@ -2523,16 +2579,17 @@ class Element(Plugin):
# substituting command strings to be run in the sandbox
#
@classmethod
- def __extract_variables(cls, project, meta):
- default_vars = cls.__defaults.get_mapping("variables", default={})
+ def __extract_variables(cls, project, load_element):
+ default_vars = cls.__defaults.get_mapping(Symbol.VARIABLES, default={})
+ element_vars = load_element.node.get_mapping(Symbol.VARIABLES, default={}) or Node.from_dict({})
- if meta.is_junction:
+ if load_element.first_pass:
variables = project.first_pass_config.base_variables.clone()
else:
variables = project.base_variables.clone()
default_vars._composite(variables)
- meta.variables._composite(variables)
+ element_vars._composite(variables)
variables._assert_fully_composited()
for var in ("project-name", "element-name", "max-jobs"):
@@ -2554,13 +2611,14 @@ class Element(Plugin):
# off to element.configure()
#
@classmethod
- def __extract_config(cls, meta):
+ def __extract_config(cls, load_element):
+ element_config = load_element.node.get_mapping(Symbol.CONFIG, default={}) or Node.from_dict({})
# The default config is already composited with the project overrides
- config = cls.__defaults.get_mapping("config", default={})
+ config = cls.__defaults.get_mapping(Symbol.CONFIG, default={})
config = config.clone()
- meta.config._composite(config)
+ element_config._composite(config)
config._assert_fully_composited()
return config
@@ -2568,18 +2626,20 @@ class Element(Plugin):
# Sandbox-specific configuration data, to be passed to the sandbox's constructor.
#
@classmethod
- def __extract_sandbox_config(cls, project, meta):
- if meta.is_junction:
+ def __extract_sandbox_config(cls, project, load_element):
+ element_sandbox = load_element.node.get_mapping(Symbol.SANDBOX, default={}) or Node.from_dict({})
+
+ if load_element.first_pass:
sandbox_config = Node.from_dict({})
else:
sandbox_config = project._sandbox.clone()
# The default config is already composited with the project overrides
- sandbox_defaults = cls.__defaults.get_mapping("sandbox", default={})
+ sandbox_defaults = cls.__defaults.get_mapping(Symbol.SANDBOX, default={})
sandbox_defaults = sandbox_defaults.clone()
sandbox_defaults._composite(sandbox_config)
- meta.sandbox._composite(sandbox_config)
+ element_sandbox._composite(sandbox_config)
sandbox_config._assert_fully_composited()
return sandbox_config
@@ -2588,14 +2648,16 @@ class Element(Plugin):
# elements may extend but whos defaults are defined in the project.
#
@classmethod
- def __extract_public(cls, meta):
- base_public = cls.__defaults.get_mapping("public", default={})
+ def __extract_public(cls, load_element):
+ element_public = load_element.node.get_mapping(Symbol.PUBLIC, default={}) or Node.from_dict({})
+
+ base_public = cls.__defaults.get_mapping(Symbol.PUBLIC, default={})
base_public = base_public.clone()
base_bst = base_public.get_mapping("bst", default={})
base_splits = base_bst.get_mapping("split-rules", default={})
- element_public = meta.public.clone()
+ element_public = element_public.clone()
element_bst = element_public.get_mapping("bst", default={})
element_splits = element_bst.get_mapping("split-rules", default={})