summaryrefslogtreecommitdiff
path: root/src/buildstream/_loader/loader.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/_loader/loader.py')
-rw-r--r--src/buildstream/_loader/loader.py119
1 files changed, 79 insertions, 40 deletions
diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py
index e63dd6c57..76d5eef82 100644
--- a/src/buildstream/_loader/loader.py
+++ b/src/buildstream/_loader/loader.py
@@ -46,9 +46,10 @@ from .._message import Message, MessageType
# Args:
# project (Project): The toplevel Project object
# parent (Loader): A parent Loader object, in the case this is a junctioned Loader
+# provenance (ProvenanceInformation): The provenance of the reference to this project's junction
#
class Loader:
- def __init__(self, project, *, parent=None):
+ def __init__(self, project, *, parent=None, provenance=None):
# Ensure we have an absolute path for the base directory
basedir = project.element_path
@@ -60,6 +61,7 @@ class Loader:
#
self.load_context = project.load_context # The LoadContext
self.project = project # The associated Project
+ self.provenance = provenance # The provenance of whence this loader was instantiated
self.loaded = None # The number of loaded Elements
#
@@ -73,9 +75,14 @@ class Loader:
self._meta_elements = {} # Dict of resolved meta elements by name
self._elements = {} # Dict of elements
self._loaders = {} # Dict of junction loaders
+ self._loader_search_provenances = {} # Dictionary of provenances of ongoing child loader searches
self._includes = Includes(self, copy_tree=True)
+ assert project.name is not None
+
+ self.load_context.register_loader(self)
+
# load():
#
# Loads the project based on the parameters given to the constructor
@@ -163,12 +170,27 @@ class Loader:
# Returns:
# (Loader): loader for sub-project
#
- def get_loader(self, name, provenance, *, level=0):
+ def get_loader(self, name, provenance):
junction_path = name.split(":")
loader = self
+ circular_provenance = self._loader_search_provenances.get(name, None)
+ if circular_provenance:
+ detail = None
+ if circular_provenance is not provenance:
+ detail = "Already searching for '{}' at: {}".format(name, circular_provenance)
+ raise LoadError(
+ "{}: Circular reference while searching for '{}'".format(provenance, name),
+ LoadErrorReason.CIRCULAR_REFERENCE,
+ detail=detail,
+ )
+
+ self._loader_search_provenances[name] = provenance
+
for junction_name in junction_path:
- loader = loader._get_loader(junction_name, provenance, level=level)
+ loader = loader._get_loader(junction_name, provenance)
+
+ del self._loader_search_provenances[name]
return loader
@@ -514,6 +536,40 @@ class Loader:
return self._meta_elements[top_element.name]
+ # _search_for_override():
+ #
+ # Search parent projects for an overridden subproject to replace this junction.
+ #
+ # Args:
+ # filename (str): Junction name
+ #
+ def _search_for_override(self, filename):
+ loader = self
+ override_path = filename
+
+ # Collect any overrides to this junction in the ancestry
+ #
+ overriding_loaders = []
+ while loader._parent:
+ junction = loader.project.junction
+ override_filename, override_provenance = junction.overrides.get(override_path, (None, None))
+ if override_filename:
+ overriding_loaders.append((loader, override_filename, override_provenance))
+
+ override_path = junction.name + ":" + override_path
+ loader = loader._parent
+
+ # If there are any overriding loaders, use the highest one in
+ # the ancestry to lookup the loader for this project.
+ #
+ if overriding_loaders:
+ loader, filename, provenance = overriding_loaders[-1]
+ return loader._parent.get_loader(filename, provenance)
+
+ # No overrides were found in the ancestry
+ #
+ return None
+
# _get_loader():
#
# Return loader for specified junction
@@ -526,7 +582,7 @@ class Loader:
#
# Returns: A Loader or None if specified junction does not exist
#
- def _get_loader(self, filename, provenance, *, level=0):
+ def _get_loader(self, filename, provenance):
loader = None
provenance_str = ""
if provenance is not None:
@@ -534,43 +590,21 @@ class Loader:
# return previously determined result
if filename in self._loaders:
- loader = self._loaders[filename]
-
- if loader is None:
- # do not allow junctions with the same name in different
- # subprojects
- raise LoadError(
- "{}Conflicting junction {} in subprojects, define junction in {}".format(
- provenance_str, filename, self.project.name
- ),
- LoadErrorReason.CONFLICTING_JUNCTION,
- )
-
- return loader
+ return self._loaders[filename]
- if self._parent:
- # junctions in the parent take precedence over junctions defined
- # in subprojects
- loader = self._parent._get_loader(filename, level=level + 1, provenance=provenance)
- if loader:
- self._loaders[filename] = loader
- return loader
-
- try:
- self._load_file(filename, provenance=provenance)
- except LoadError as e:
- if e.reason != LoadErrorReason.MISSING_FILE:
- # other load error
- raise
-
- if level == 0:
- # junction element not found in this or ancestor projects
- raise
+ #
+ # Search the ancestry for an overridden loader to use in place
+ # of using the locally defined junction.
+ #
+ override_loader = self._search_for_override(filename)
+ if override_loader:
+ self._loaders[filename] = override_loader
+ return override_loader
- # mark junction as not available to allow detection of
- # conflicting junctions in subprojects
- self._loaders[filename] = None
- return None
+ #
+ # Load the junction file
+ #
+ self._load_file(filename, provenance)
# At this point we've loaded the LoadElement
load_element = self._elements[filename]
@@ -668,7 +702,12 @@ class Loader:
from .._project import Project # pylint: disable=cyclic-import
project = Project(
- project_dir, self.load_context.context, junction=element, parent_loader=self, search_for_project=False,
+ project_dir,
+ self.load_context.context,
+ junction=element,
+ parent_loader=self,
+ search_for_project=False,
+ provenance=provenance,
)
except LoadError as e:
if e.reason == LoadErrorReason.MISSING_PROJECT_CONF: