summaryrefslogtreecommitdiff
path: root/src/buildstream/_loader/loadcontext.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/_loader/loadcontext.py')
-rw-r--r--src/buildstream/_loader/loadcontext.py164
1 files changed, 134 insertions, 30 deletions
diff --git a/src/buildstream/_loader/loadcontext.py b/src/buildstream/_loader/loadcontext.py
index 65b7e9157..c6d256050 100644
--- a/src/buildstream/_loader/loadcontext.py
+++ b/src/buildstream/_loader/loadcontext.py
@@ -21,6 +21,118 @@ from .._exceptions import LoadError
from ..exceptions import LoadErrorReason
+# ProjectLoaders()
+#
+# An object representing all of the loaders for a given project.
+#
+class ProjectLoaders:
+ def __init__(self, project_name):
+
+ # The project name
+ self._name = project_name
+
+ # A list of all loaded loaders for this project
+ self._collect = []
+
+ # register_loader()
+ #
+ # Register a Loader for this project
+ #
+ # Args:
+ # loader (Loader): The loader to register
+ #
+ def register_loader(self, loader):
+ assert loader.project.name == self._name
+
+ self._collect.append(loader)
+
+ # assert_loaders():
+ #
+ # Asserts the validity of loaders for this project
+ #
+ # Raises:
+ # (LoadError): In case there is a CONFLICTING_JUNCTION error
+ #
+ def assert_loaders(self):
+ duplicates = {}
+ primary = []
+
+ for loader in self._collect:
+ duplicating = self._search_duplicates(loader)
+ if duplicating:
+ duplicates[loader] = duplicating
+ else:
+ primary.append(loader)
+
+ if len(primary) > 1:
+ self._raise_conflict(duplicates)
+
+ elif primary and duplicates:
+ self._raise_conflict(duplicates)
+
+ # _search_duplicates()
+ #
+ # Searches this loader's ancestry for projects which mark this
+ # loader as a duplicate.
+ #
+ # Args:
+ # loader (Loader): The loader to search for duplicate markers of
+ #
+ # Returns:
+ # (list): A list of Loader objects who's project has marked
+ # this junction as a duplicate
+ #
+ def _search_duplicates(self, loader):
+ duplicates = []
+ for parent in loader.ancestors():
+ if parent.project.junction_is_duplicated(self._name, loader):
+ duplicates.append(parent)
+ return duplicates
+
+ # _raise_conflict()
+ #
+ # Raises the LoadError indicating there was a conflict, this
+ # will list all of the instances in which the project has
+ # been loaded as the LoadError detail string
+ #
+ # Args:
+ # duplicates (dict): A table of duplicating Loaders, indexed
+ # by duplicated Loader
+ #
+ # Raises:
+ # (LoadError): In case there is a CONFLICTING_JUNCTION error
+ #
+ def _raise_conflict(self, duplicates):
+ lines = [self._loader_description(loader, duplicates) for loader in self._collect]
+ raise LoadError(
+ "Project '{}' was loaded in multiple contexts".format(self._name),
+ LoadErrorReason.CONFLICTING_JUNCTION,
+ detail="\n".join(lines),
+ )
+
+ # _loader_description()
+ #
+ # Args:
+ # loader (Loader): The loader to describe
+ # duplicates (dict): A table of duplicating Loaders, indexed
+ # by duplicated Loader
+ #
+ # Returns:
+ # (str): A string representing how this loader was loaded
+ #
+ def _loader_description(self, loader, duplicates):
+
+ line = "{}\n".format(loader)
+
+ # Mention projects which have marked this project as a duplicate
+ duplicating = duplicates.get(loader)
+ if duplicating:
+ for dup in duplicating:
+ line += " Duplicated by: {}\n".format(dup)
+
+ return line
+
+
# LoaderContext()
#
# An object to keep track of overall context during the load process.
@@ -71,44 +183,36 @@ class LoadContext:
def set_fetch_subprojects(self, fetch_subprojects):
self.fetch_subprojects = fetch_subprojects
+ # assert_loaders()
+ #
+ # Asserts that there are no conflicting projects loaded.
+ #
+ # Raises:
+ # (LoadError): A CONFLICTING_JUNCTION LoadError in the case of a conflict
+ #
+ def assert_loaders(self):
+ for _, loaders in self._loaders.items():
+ loaders.assert_loaders()
+
# register_loader()
#
# Registers a new loader in the load context, possibly
- # raising an error in the case of a conflict
+ # raising an error in the case of a conflict.
+ #
+ # This must be called after a recursive load process has completed,
+ # and after the pipeline is resolved (which is to say that all related
+ # Plugin derived objects have been instantiated).
#
# Args:
# loader (Loader): The Loader object to register into context
#
- # Raises:
- # (LoadError): A CONFLICTING_JUNCTION LoadError in the case of a conflict
- #
def register_loader(self, loader):
project = loader.project
- existing_loader = self._loaders.get(project.name, None)
- if existing_loader:
+ try:
+ project_loaders = self._loaders[project.name]
+ except KeyError:
+ project_loaders = ProjectLoaders(project.name)
+ self._loaders[project.name] = project_loaders
- assert project.junction is not None
-
- if existing_loader.project.junction:
- # The existing provenance can be None even if there is a junction, this
- # can happen when specifying a full element path on the command line.
- #
- provenance_str = ""
- if existing_loader.provenance:
- provenance_str = ": {}".format(existing_loader.provenance)
-
- detail = "Project '{}' was already loaded by junction '{}'{}".format(
- project.name, existing_loader.project.junction._get_full_name(), provenance_str
- )
- else:
- detail = "Project '{}' is also the toplevel project".format(project.name)
-
- raise LoadError(
- "{}: Error loading project '{}' from junction: {}".format(
- loader.provenance, project.name, project.junction._get_full_name()
- ),
- LoadErrorReason.CONFLICTING_JUNCTION,
- detail=detail,
- )
- self._loaders[project.name] = loader
+ project_loaders.register_loader(loader)