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.py181
1 files changed, 142 insertions, 39 deletions
diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py
index 46e8884c0..54efd27ae 100644
--- a/src/buildstream/_loader/loader.py
+++ b/src/buildstream/_loader/loader.py
@@ -392,6 +392,66 @@ class Loader:
return path
+ # _load_one_file():
+ #
+ # A helper function to load a single file within the _load_file() process,
+ # this allows us to handle redirections more consistently.
+ #
+ # Args:
+ # filename (str): The element-path relative bst file
+ # provenance_node (Node): The location from where the file was referred to, or None
+ # load_subprojects (bool): Whether to load subprojects
+ #
+ # Returns:
+ # (LoadElement): A LoadElement, which might be shallow loaded or fully loaded.
+ #
+ def _load_one_file(self, filename, provenance_node, *, load_subprojects=True):
+
+ element = None
+
+ # First check the cache, the cache might contain shallow loaded
+ # elements.
+ #
+ try:
+ element = self._elements[filename]
+
+ # If the cached element has already entered the loop which loads
+ # it's dependencies, it is fully loaded and any further checks in
+ # this function are expected to have already been performed.
+ #
+ if element.fully_loaded:
+ return element
+
+ except KeyError:
+
+ # Shallow load if it's not yet loaded.
+ element = self._load_file_no_deps(filename, provenance_node)
+
+ # Check if there was an override for this element
+ #
+ override = self._search_for_override_element(filename)
+ if override:
+ #
+ # If there was an override for the element, then it was
+ # implicitly fully loaded by _search_for_override_element(),
+ #
+ return override
+
+ # If this element is a link then we need to resolve it, and return
+ # the linked element instead of this one.
+ #
+ if element.link_target is not None:
+ link_target = element.link_target.as_str() # pylint: disable=no-member
+ _, filename, loader = self._parse_name(link_target, element.link_target, load_subprojects=load_subprojects)
+
+ #
+ # Redirect the loading of the file and it's dependencies to the appropriate loader,
+ # which might or might not be the same loader.
+ #
+ return loader._load_file(filename, element.link_target, load_subprojects=load_subprojects)
+
+ return element
+
# _load_file():
#
# Semi-Iteratively load bst files
@@ -402,32 +462,26 @@ class Loader:
#
# Args:
# filename (str): The element-path relative bst file
- # load_subprojects (bool): Whether to load subprojects
# provenance_node (Node): The location from where the file was referred to, or None
+ # load_subprojects (bool): Whether to load subprojects
#
# Returns:
# (LoadElement): A loaded LoadElement
#
def _load_file(self, filename, provenance_node, *, load_subprojects=True):
- # Silently ignore already loaded files
- with suppress(KeyError):
- return self._elements[filename]
-
- top_element = self._load_file_no_deps(filename, provenance_node)
+ top_element = self._load_one_file(filename, provenance_node, load_subprojects=load_subprojects)
- # If this element is a link then we need to resolve it
- # and replace the dependency we've processed with this one
- if top_element.link_target is not None:
- link_target = top_element.link_target.as_str() # pylint: disable=no-member
- _, filename, loader = self._parse_name(
- link_target, top_element.link_target, load_subprojects=load_subprojects
- )
+ # Already loaded dependencies for a fully loaded element, early return.
+ #
+ if top_element.fully_loaded:
+ return top_element
- # Early return, redirect the loading of the file and it's dependencies to the
- # appropriate loader.
- #
- return loader._load_file(filename, top_element.link_target, load_subprojects=load_subprojects)
+ #
+ # Mark the top element here as "fully loaded", so that we will avoid trying to
+ # load it's dependencies more than once.
+ #
+ top_element.mark_fully_loaded()
dependencies = extract_depends_from_node(top_element.node)
# The loader queue is a stack of tuples
@@ -449,14 +503,18 @@ class Loader:
if dep.junction:
loader = self.get_loader(dep.junction, dep.node)
dep_element = loader._load_file(dep.name, dep.node)
+
else:
- dep_element = self._elements.get(dep.name)
- if dep_element is None:
- # The loader does not have this available so we need to
- # either recursively cause it to be loaded, or else we
- # need to push this onto the loader queue in this loader
- dep_element = self._load_file_no_deps(dep.name, dep.node)
+ dep_element = self._load_one_file(dep.name, dep.node, load_subprojects=load_subprojects)
+
+ # If the loaded element is not fully loaded, queue up the dependencies to be loaded in this loop.
+ #
+ if not dep_element.fully_loaded:
+
+ # Mark the dep_element as fully_loaded, as we're already queueing it's deps
+ dep_element.mark_fully_loaded()
+
dep_deps = extract_depends_from_node(dep_element.node)
loader_queue.append((dep_element, list(reversed(dep_deps)), []))
@@ -467,12 +525,6 @@ class Loader:
LoadErrorReason.INVALID_DATA,
)
- # If this dependency is a link then we need to resolve it
- # and replace the dependency we've processed with this one
- if dep_element.link_target:
- _, filename, loader = self._parse_name(dep_element.link_target.as_str(), dep_element.link_target)
- dep_element = loader._load_file(filename, dep_element.link_target)
-
# We've now resolved the element for this dependency, lets set the resolved
# LoadElement on the dependency and append the dependency to the owning
# LoadElement dependency list.
@@ -572,19 +624,19 @@ class Loader:
return None
- # _search_for_override():
+ # _search_for_overrides():
#
- # Search parent projects for an overridden subproject to replace this junction.
- #
- # This function is called once for each direct child while looking up
- # child loaders, after which point the child loader is cached in the `_loaders`
- # table. This function also has the side effect of recording alternative parents
- # of a child loader in the case that the child loader is overridden.
+ # Search for parent loaders which have an override for the specified element,
+ # returning a list of loaders with the highest level overriding loader at the
+ # end of the list, and the closest ancestor being at the beginning of the list.
#
# Args:
- # filename (str): Junction name
+ # filename (str): The local element name
+ #
+ # Returns:
+ # (list): A list of loaders which override this element
#
- def _search_for_override(self, filename):
+ def _search_for_overrides(self, filename):
loader = self
override_path = filename
@@ -600,6 +652,29 @@ class Loader:
override_path = junction.name + ":" + override_path
loader = loader._parent
+ return overriding_loaders
+
+ # _search_for_override_loader():
+ #
+ # Search parent projects an override of the junction specified by @filename,
+ # returning the loader object which should be used in place of the local
+ # junction specified by @filename.
+ #
+ # This function is called once for each direct child while looking up
+ # child loaders, after which point the child loader is cached in the `_loaders`
+ # table. This function also has the side effect of recording alternative parents
+ # of a child loader in the case that the child loader is overridden.
+ #
+ # Args:
+ # filename (str): Junction name
+ #
+ # Returns:
+ # (Loader): The loader to use, in case @filename was overridden, otherwise None.
+ #
+ def _search_for_override_loader(self, filename):
+
+ overriding_loaders = self._search_for_overrides(filename)
+
# If there are any overriding loaders, use the highest one in
# the ancestry to lookup the loader for this project.
#
@@ -630,6 +705,34 @@ class Loader:
#
return None
+ # _search_for_override_element():
+ #
+ # Search parent projects an override of the element specified by @filename,
+ # returning the loader object which should be used in place of the local
+ # element specified by @filename.
+ #
+ # Args:
+ # filename (str): Junction name
+ #
+ # Returns:
+ # (Loader): The loader to use, in case @filename was overridden, otherwise None.
+ #
+ def _search_for_override_element(self, filename):
+ element = None
+
+ # If there are any overriding loaders, use the highest one in
+ # the ancestry to lookup the element which should be used in place
+ # of @filename.
+ #
+ overriding_loaders = self._search_for_overrides(filename)
+ if overriding_loaders:
+ overriding_loader, override_node = overriding_loaders[-1]
+
+ _, filename, loader = overriding_loader._parse_name(override_node.as_str(), override_node)
+ element = loader._load_file(filename, override_node)
+
+ return element
+
# _get_loader():
#
# Return loader for specified junction
@@ -660,7 +763,7 @@ class Loader:
# Search the ancestry for an overridden loader to use in place
# of using the locally defined junction.
#
- override_loader = self._search_for_override(filename)
+ override_loader = self._search_for_override_loader(filename)
if override_loader:
self._loaders[filename] = override_loader
return override_loader