summaryrefslogtreecommitdiff
path: root/src/buildstream/_loader
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/_loader')
-rw-r--r--src/buildstream/_loader/__init__.py1
-rw-r--r--src/buildstream/_loader/loadcontext.py66
-rw-r--r--src/buildstream/_loader/loadelement.pyx2
-rw-r--r--src/buildstream/_loader/loader.py141
4 files changed, 117 insertions, 93 deletions
diff --git a/src/buildstream/_loader/__init__.py b/src/buildstream/_loader/__init__.py
index a2c31796e..fd5cac2ae 100644
--- a/src/buildstream/_loader/__init__.py
+++ b/src/buildstream/_loader/__init__.py
@@ -19,4 +19,5 @@
from .metasource import MetaSource
from .metaelement import MetaElement
+from .loadcontext import LoadContext
from .loader import Loader
diff --git a/src/buildstream/_loader/loadcontext.py b/src/buildstream/_loader/loadcontext.py
new file mode 100644
index 000000000..161be913b
--- /dev/null
+++ b/src/buildstream/_loader/loadcontext.py
@@ -0,0 +1,66 @@
+#
+# Copyright (C) 2020 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+
+
+# LoaderContext()
+#
+# An object to keep track of overall context during the load process.
+#
+# Args:
+# context (Context): The invocation context
+#
+class LoadContext:
+ def __init__(self, context):
+
+ # Keep track of global context required throughout the recursive load
+ self.context = context
+ self.rewritable = False
+ self.fetch_subprojects = None
+ self.task = None
+
+ # set_rewritable()
+ #
+ # Sets whether the projects are to be loaded in a rewritable fashion,
+ # this is used for tracking and is slightly more expensive in load time.
+ #
+ # Args:
+ # task (Task): The task to report progress on
+ #
+ def set_rewritable(self, rewritable):
+ self.rewritable = rewritable
+
+ # set_task()
+ #
+ # Sets the task for progress reporting.
+ #
+ # Args:
+ # task (Task): The task to report progress on
+ #
+ def set_task(self, task):
+ self.task = task
+
+ # set_fetch_subprojects()
+ #
+ # Sets the task for progress reporting.
+ #
+ # Args:
+ # task (callable): The callable for loading subprojects
+ #
+ def set_fetch_subprojects(self, fetch_subprojects):
+ self.fetch_subprojects = fetch_subprojects
diff --git a/src/buildstream/_loader/loadelement.pyx b/src/buildstream/_loader/loadelement.pyx
index 014f01746..784ab8f7b 100644
--- a/src/buildstream/_loader/loadelement.pyx
+++ b/src/buildstream/_loader/loadelement.pyx
@@ -127,7 +127,7 @@ cdef class LoadElement:
# store the link target and provenance
#
if self.node.get_str(Symbol.KIND, default=None) == 'link':
- meta_element = self._loader.collect_element_no_deps(self, None)
+ meta_element = self._loader.collect_element_no_deps(self)
element = Element._new_from_meta(meta_element)
element._initialize_state()
diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py
index fd9e2ef2d..13d8f9f21 100644
--- a/src/buildstream/_loader/loader.py
+++ b/src/buildstream/_loader/loader.py
@@ -44,13 +44,11 @@ from .._message import Message, MessageType
# with their own MetaSources, ready for instantiation by the core.
#
# Args:
-# context (Context): The Context object
# project (Project): The toplevel Project object
-# fetch_subprojects (callable): A function to fetch subprojects
# parent (Loader): A parent Loader object, in the case this is a junctioned Loader
#
class Loader:
- def __init__(self, context, project, *, fetch_subprojects, parent=None):
+ def __init__(self, project, *, parent=None):
# Ensure we have an absolute path for the base directory
basedir = project.element_path
@@ -60,18 +58,17 @@ class Loader:
#
# Public members
#
+ self.load_context = project.load_context # The LoadContext
self.project = project # The associated Project
self.loaded = None # The number of loaded Elements
#
# Private members
#
- self._context = context
self._options = project.options # Project options (OptionPool)
self._basedir = basedir # Base project directory
self._first_pass_options = project.first_pass_config.options # Project options (OptionPool)
self._parent = parent # The parent loader
- self._fetch_subprojects = fetch_subprojects
self._meta_elements = {} # Dict of resolved meta elements by name
self._elements = {} # Dict of elements
@@ -84,16 +81,13 @@ class Loader:
# Loads the project based on the parameters given to the constructor
#
# Args:
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # ticker (callable): An optional function for tracking load progress
# targets (list of str): Target, element-path relative bst filenames in the project
- # task (Task): A task object to report progress to
#
# Raises: LoadError
#
# Returns: The toplevel LoadElement
- def load(self, targets, task, rewritable=False, ticker=None):
+ #
+ def load(self, targets):
for filename in targets:
if os.path.isabs(filename):
@@ -113,8 +107,8 @@ class Loader:
for target in targets:
with PROFILER.profile(Topics.LOAD_PROJECT, target):
- _junction, name, loader = self._parse_name(target, rewritable, ticker)
- element = loader._load_file(name, rewritable, ticker)
+ _junction, name, loader = self._parse_name(target, None)
+ element = loader._load_file(name, None)
target_elements.append(element)
#
@@ -147,14 +141,14 @@ class Loader:
# Finally, wrap what we have into LoadElements and return the target
#
- ret.append(loader._collect_element(element, task))
+ ret.append(loader._collect_element(element))
self._clean_caches()
# Cache how many Elements have just been loaded
- if task:
+ if self.load_context.task:
# Workaround for task potentially being None (because no State object)
- self.loaded = task.current_progress
+ self.loaded = self.load_context.task.current_progress
return ret
@@ -165,21 +159,16 @@ class Loader:
# Args:
# name (str): Name of junction, may have multiple `:` in the name
# provenance (ProvenanceInformation): The provenance
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # ticker (callable): An optional function for tracking load progress
#
# Returns:
# (Loader): loader for sub-project
#
- def get_loader(self, name, provenance, *, rewritable=False, ticker=None, level=0):
+ def get_loader(self, name, provenance, *, level=0):
junction_path = name.split(":")
loader = self
for junction_name in junction_path:
- loader = loader._get_loader(
- junction_name, rewritable=rewritable, ticker=ticker, level=level, provenance=provenance
- )
+ loader = loader._get_loader(junction_name, provenance, level=level)
return loader
@@ -192,12 +181,14 @@ class Loader:
#
# Args:
# element (LoadElement): The element for which to load a MetaElement
- # task (Task): A task to write progress information to
+ # report_progress (bool): Whether to report progress for this element, this is
+ # because we ignore junctions and links when counting
+ # how many elements we load.
#
# Returns:
# (MetaElement): A partially loaded MetaElement
#
- def collect_element_no_deps(self, element, task=None):
+ def collect_element_no_deps(self, element, *, report_progress=False):
# Return the already built one, if we already built it
meta_element = self._meta_elements.get(element.name)
if meta_element:
@@ -211,7 +202,7 @@ class Loader:
# if there's a workspace for this element then just append a dummy workspace
# metasource.
- workspace = self._context.get_workspaces().get_workspace(element.name)
+ workspace = self.load_context.context.get_workspaces().get_workspace(element.name)
skip_workspace = True
if workspace:
workspace_node = {"kind": "workspace"}
@@ -253,8 +244,8 @@ class Loader:
# Cache it now, make sure it's already there before recursing
self._meta_elements[element.name] = meta_element
- if task:
- task.add_current_progress()
+ if self.load_context.task and report_progress:
+ self.load_context.task.add_current_progress()
return meta_element
@@ -272,17 +263,18 @@ class Loader:
#
# Args:
# filename (str): The element-path relative bst file
- # rewritable (bool): Whether we should load in round trippable mode
# provenance (Provenance): The location from where the file was referred to, or None
#
# Returns:
# (LoadElement): A partially-loaded LoadElement
#
- def _load_file_no_deps(self, filename, rewritable, provenance=None):
+ def _load_file_no_deps(self, filename, provenance=None):
# Load the data and process any conditional statements therein
fullpath = os.path.join(self._basedir, filename)
try:
- node = _yaml.load(fullpath, shortname=filename, copy_tree=rewritable, project=self.project)
+ node = _yaml.load(
+ fullpath, shortname=filename, copy_tree=self.load_context.rewritable, project=self.project
+ )
except LoadError as e:
if e.reason == LoadErrorReason.MISSING_FILE:
@@ -346,32 +338,24 @@ class Loader:
#
# Args:
# filename (str): The element-path relative bst file
- # rewritable (bool): Whether we should load in round trippable mode
- # ticker (callable): A callback to report loaded filenames to the frontend
# provenance (Provenance): The location from where the file was referred to, or None
#
# Returns:
# (LoadElement): A loaded LoadElement
#
- def _load_file(self, filename, rewritable, ticker, provenance=None):
+ def _load_file(self, filename, provenance):
# Silently ignore already loaded files
if filename in self._elements:
return self._elements[filename]
- # Call the ticker
- if ticker:
- ticker(filename)
-
- top_element = self._load_file_no_deps(filename, rewritable, provenance)
+ top_element = self._load_file_no_deps(filename, provenance)
# 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:
- _, filename, loader = self._parse_name(
- top_element.link_target, rewritable, ticker, top_element.link_target_provenance
- )
- top_element = loader._load_file(filename, rewritable, ticker, top_element.link_target_provenance)
+ _, filename, loader = self._parse_name(top_element.link_target, top_element.link_target_provenance)
+ top_element = loader._load_file(filename, top_element.link_target_provenance)
dependencies = extract_depends_from_node(top_element.node)
# The loader queue is a stack of tuples
@@ -391,8 +375,8 @@ class Loader:
current_element[2].append(dep.name)
if dep.junction:
- loader = self.get_loader(dep.junction, dep.provenance, rewritable=rewritable, ticker=ticker)
- dep_element = loader._load_file(dep.name, rewritable, ticker, dep.provenance)
+ loader = self.get_loader(dep.junction, dep.provenance)
+ dep_element = loader._load_file(dep.name, dep.provenance)
else:
dep_element = self._elements.get(dep.name)
@@ -400,7 +384,7 @@ class Loader:
# 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, rewritable, dep.provenance)
+ dep_element = self._load_file_no_deps(dep.name, dep.provenance)
dep_deps = extract_depends_from_node(dep_element.node)
loader_queue.append((dep_element, list(reversed(dep_deps)), []))
@@ -413,10 +397,8 @@ class Loader:
# 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, rewritable, ticker, dep_element.link_target_provenance
- )
- dep_element = loader._load_file(filename, rewritable, ticker, dep_element.link_target_provenance)
+ _, filename, loader = self._parse_name(dep_element.link_target, dep_element.link_target_provenance)
+ dep_element = loader._load_file(filename, dep_element.link_target_provenance)
# All is well, push the dependency onto the LoadElement
# Pylint is not very happy with Cython and can't understand 'dependencies' is a list
@@ -492,14 +474,13 @@ class Loader:
#
# Args:
# top_element (LoadElement): The element for which to load a MetaElement
- # task (Task): The task to update with progress changes
#
# Returns:
# (MetaElement): A fully loaded MetaElement
#
- def _collect_element(self, top_element, task=None):
+ def _collect_element(self, top_element):
element_queue = [top_element]
- meta_element_queue = [self.collect_element_no_deps(top_element, task)]
+ meta_element_queue = [self.collect_element_no_deps(top_element, report_progress=True)]
while element_queue:
element = element_queue.pop()
@@ -515,12 +496,12 @@ class Loader:
loader = dep.element._loader
name = dep.element.name
- if name not in loader._meta_elements:
- meta_dep = loader.collect_element_no_deps(dep.element, task)
+ try:
+ meta_dep = loader._meta_elements[name]
+ except KeyError:
+ meta_dep = loader.collect_element_no_deps(dep.element, report_progress=True)
element_queue.append(dep.element)
meta_element_queue.append(meta_dep)
- else:
- meta_dep = loader._meta_elements[name]
if dep.dep_type != "runtime":
meta_element.build_dependencies.append(meta_dep)
@@ -539,13 +520,14 @@ class Loader:
#
# Args:
# filename (str): Junction name
+ # provenance (Provenance): The location from where the file was referred to, or None
#
# Raises: LoadError
#
# Returns: A Loader or None if specified junction does not exist
#
- def _get_loader(self, filename, *, rewritable=False, ticker=None, level=0, provenance=None):
-
+ def _get_loader(self, filename, provenance, *, level=0):
+ loader = None
provenance_str = ""
if provenance is not None:
provenance_str = "{}: ".format(provenance)
@@ -569,15 +551,13 @@ class Loader:
if self._parent:
# junctions in the parent take precedence over junctions defined
# in subprojects
- loader = self._parent._get_loader(
- filename, rewritable=rewritable, ticker=ticker, level=level + 1, provenance=provenance
- )
+ loader = self._parent._get_loader(filename, level=level + 1, provenance=provenance)
if loader:
self._loaders[filename] = loader
return loader
try:
- self._load_file(filename, rewritable, ticker, provenance=provenance)
+ self._load_file(filename, provenance=provenance)
except LoadError as e:
if e.reason != LoadErrorReason.MISSING_FILE:
# other load error
@@ -599,21 +579,8 @@ class Loader:
# immediately and move on to the target.
#
if load_element.link_target:
-
- _, filename, loader = self._parse_name(
- load_element.link_target, rewritable, ticker, provenance=load_element.link_target_provenance
- )
-
- if loader != self:
- level = level + 1
-
- return loader._get_loader(
- filename,
- rewritable=rewritable,
- ticker=ticker,
- level=level,
- provenance=load_element.link_target_provenance,
- )
+ _, filename, loader = self._parse_name(load_element.link_target, load_element.link_target_provenance)
+ return loader.get_loader(filename, load_element.link_target_provenance)
# meta junction element
#
@@ -664,9 +631,7 @@ class Loader:
# Handle the case where a subproject needs to be fetched
#
if not element._has_all_sources_in_source_cache():
- if ticker:
- ticker(filename, "Fetching subproject")
- self._fetch_subprojects([element])
+ self.load_context.fetch_subprojects([element])
sources = list(element.sources())
if len(sources) == 1 and sources[0]._get_local_path():
@@ -699,12 +664,7 @@ class Loader:
from .._project import Project # pylint: disable=cyclic-import
project = Project(
- project_dir,
- self._context,
- junction=element,
- parent_loader=self,
- search_for_project=False,
- fetch_subprojects=self._fetch_subprojects,
+ project_dir, self.load_context.context, junction=element, parent_loader=self, search_for_project=False,
)
except LoadError as e:
if e.reason == LoadErrorReason.MISSING_PROJECT_CONF:
@@ -730,9 +690,6 @@ class Loader:
#
# Args:
# name (str): Name of target
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # ticker (callable): An optional function for tracking load progress
# provenance (ProvenanceInformation): The provenance
#
# Returns:
@@ -740,7 +697,7 @@ class Loader:
# - (str): name of the element
# - (Loader): loader for sub-project
#
- def _parse_name(self, name, rewritable, ticker, provenance=None):
+ def _parse_name(self, name, provenance):
# We allow to split only once since deep junctions names are forbidden.
# Users who want to refer to elements in sub-sub-projects are required
# to create junctions on the top level project.
@@ -748,7 +705,7 @@ class Loader:
if len(junction_path) == 1:
return None, junction_path[-1], self
else:
- loader = self.get_loader(junction_path[-2], provenance, rewritable=rewritable, ticker=ticker)
+ loader = self.get_loader(junction_path[-2], provenance)
return junction_path[-2], junction_path[-1], loader
# Print a warning message, checks warning_token against project configuration
@@ -767,7 +724,7 @@ class Loader:
raise LoadError(brief, warning_token)
message = Message(MessageType.WARN, brief)
- self._context.messenger.message(message)
+ self.load_context.context.messenger.message(message)
# Print warning messages if any of the specified elements have invalid names.
#