summaryrefslogtreecommitdiff
path: root/src/buildstream/_includes.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/_includes.py')
-rw-r--r--src/buildstream/_includes.py145
1 files changed, 145 insertions, 0 deletions
diff --git a/src/buildstream/_includes.py b/src/buildstream/_includes.py
new file mode 100644
index 000000000..f792b7716
--- /dev/null
+++ b/src/buildstream/_includes.py
@@ -0,0 +1,145 @@
+import os
+from . import _yaml
+from ._exceptions import LoadError, LoadErrorReason
+
+
+# Includes()
+#
+# This takes care of processing include directives "(@)".
+#
+# Args:
+# loader (Loader): The Loader object
+# copy_tree (bool): Whether to make a copy, of tree in
+# provenance. Should be true if intended to be
+# serialized.
+class Includes:
+
+ def __init__(self, loader, *, copy_tree=False):
+ self._loader = loader
+ self._loaded = {}
+ self._copy_tree = copy_tree
+
+ # process()
+ #
+ # Process recursively include directives in a YAML node.
+ #
+ # Args:
+ # node (dict): A YAML node
+ # included (set): Fail for recursion if trying to load any files in this set
+ # current_loader (Loader): Use alternative loader (for junction files)
+ # only_local (bool): Whether to ignore junction files
+ def process(self, node, *,
+ included=set(),
+ current_loader=None,
+ only_local=False):
+ if current_loader is None:
+ current_loader = self._loader
+
+ includes = _yaml.node_get(node, None, '(@)', default_value=None)
+ if isinstance(includes, str):
+ includes = [includes]
+
+ if not isinstance(includes, list) and includes is not None:
+ provenance = _yaml.node_get_provenance(node, key='(@)')
+ raise LoadError(LoadErrorReason.INVALID_DATA,
+ "{}: {} must either be list or str".format(provenance, includes))
+
+ include_provenance = None
+ if includes:
+ include_provenance = _yaml.node_get_provenance(node, key='(@)')
+ _yaml.node_del(node, '(@)')
+
+ for include in reversed(includes):
+ if only_local and ':' in include:
+ continue
+ try:
+ include_node, file_path, sub_loader = self._include_file(include,
+ current_loader)
+ except LoadError as e:
+ if e.reason == LoadErrorReason.MISSING_FILE:
+ message = "{}: Include block references a file that could not be found: '{}'.".format(
+ include_provenance, include)
+ raise LoadError(LoadErrorReason.MISSING_FILE, message) from e
+ elif e.reason == LoadErrorReason.LOADING_DIRECTORY:
+ message = "{}: Include block references a directory instead of a file: '{}'.".format(
+ include_provenance, include)
+ raise LoadError(LoadErrorReason.LOADING_DIRECTORY, message) from e
+ else:
+ raise
+
+ if file_path in included:
+ raise LoadError(LoadErrorReason.RECURSIVE_INCLUDE,
+ "{}: trying to recursively include {}". format(include_provenance,
+ file_path))
+ # Because the included node will be modified, we need
+ # to copy it so that we do not modify the toplevel
+ # node of the provenance.
+ include_node = _yaml.node_copy(include_node)
+
+ try:
+ included.add(file_path)
+ self.process(include_node, included=included,
+ current_loader=sub_loader,
+ only_local=only_local)
+ finally:
+ included.remove(file_path)
+
+ _yaml.composite_and_move(node, include_node)
+
+ for _, value in _yaml.node_items(node):
+ self._process_value(value,
+ included=included,
+ current_loader=current_loader,
+ only_local=only_local)
+
+ # _include_file()
+ #
+ # Load include YAML file from with a loader.
+ #
+ # Args:
+ # include (str): file path relative to loader's project directory.
+ # Can be prefixed with junctio name.
+ # loader (Loader): Loader for the current project.
+ def _include_file(self, include, loader):
+ shortname = include
+ if ':' in include:
+ junction, include = include.split(':', 1)
+ junction_loader = loader._get_loader(junction, fetch_subprojects=True)
+ current_loader = junction_loader
+ else:
+ current_loader = loader
+ project = current_loader.project
+ directory = project.directory
+ file_path = os.path.join(directory, include)
+ key = (current_loader, file_path)
+ if key not in self._loaded:
+ self._loaded[key] = _yaml.load(file_path,
+ shortname=shortname,
+ project=project,
+ copy_tree=self._copy_tree)
+ return self._loaded[key], file_path, current_loader
+
+ # _process_value()
+ #
+ # Select processing for value that could be a list or a dictionary.
+ #
+ # Args:
+ # value: Value to process. Can be a list or a dictionary.
+ # included (set): Fail for recursion if trying to load any files in this set
+ # current_loader (Loader): Use alternative loader (for junction files)
+ # only_local (bool): Whether to ignore junction files
+ def _process_value(self, value, *,
+ included=set(),
+ current_loader=None,
+ only_local=False):
+ if _yaml.is_node(value):
+ self.process(value,
+ included=included,
+ current_loader=current_loader,
+ only_local=only_local)
+ elif isinstance(value, list):
+ for v in value:
+ self._process_value(v,
+ included=included,
+ current_loader=current_loader,
+ only_local=only_local)