diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buildstream/_loader/loadelement.pyx | 35 | ||||
-rw-r--r-- | src/buildstream/_loader/loader.py | 36 | ||||
-rw-r--r-- | src/buildstream/exceptions.py | 3 | ||||
-rw-r--r-- | src/buildstream/plugins/elements/link.py | 90 |
4 files changed, 160 insertions, 4 deletions
diff --git a/src/buildstream/_loader/loadelement.pyx b/src/buildstream/_loader/loadelement.pyx index 31c3aef1a..2f4e7a0f9 100644 --- a/src/buildstream/_loader/loadelement.pyx +++ b/src/buildstream/_loader/loadelement.pyx @@ -21,8 +21,11 @@ from functools import cmp_to_key from pyroaring import BitMap, FrozenBitMap # pylint: disable=no-name-in-module -from ..node cimport MappingNode -from .types import Symbol +from .._exceptions import LoadError +from ..exceptions import LoadErrorReason +from ..element import Element +from ..node cimport MappingNode, ProvenanceInformation +from .types import Symbol, extract_depends_from_node # Counter to get ids to LoadElements @@ -74,6 +77,8 @@ cdef class LoadElement: cdef public bint meta_done cdef int node_id cdef readonly object _loader + cdef readonly str link_target + cdef readonly ProvenanceInformation link_target_provenance # TODO: if/when pyroaring exports symbols, we could type this statically cdef object _dep_cache cdef readonly list dependencies @@ -88,6 +93,8 @@ cdef class LoadElement: self.full_name = None # The element full name (with associated junction) self.meta_done = False # If the MetaElement for this LoadElement is done self.node_id = _next_synthetic_counter() + self.link_target = None # The target of a link element + self.link_target_provenance = None # The provenance of the link target # # Private members @@ -105,6 +112,8 @@ cdef class LoadElement: # dependency is in top-level project self.full_name = self.name + self.dependencies = [] + # Ensure the root node is valid self.node.validate_keys([ 'kind', 'depends', 'sources', 'sandbox', @@ -113,7 +122,27 @@ cdef class LoadElement: 'build-depends', 'runtime-depends', ]) - self.dependencies = [] + # + # If this is a link, resolve it right away and just + # 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) + element = Element._new_from_meta(meta_element) + element._initialize_state() + + # Custom error for link dependencies, since we don't completely + # parse their dependencies we cannot rely on the built-in ElementError. + deps = extract_depends_from_node(self.node) + if deps: + provenance = self.node + raise LoadError( + "{}: Dependencies are forbidden for 'link' elements".format(element), + LoadErrorReason.LINK_FORBIDDEN_DEPENDENCIES + ) + + self.link_target = element.target + self.link_target_provenance = element.target_provenance @property def junction(self): diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py index fecfc8d0f..ed0d345a3 100644 --- a/src/buildstream/_loader/loader.py +++ b/src/buildstream/_loader/loader.py @@ -217,6 +217,27 @@ class Loader: self._loaders[filename] = None return None + # At this point we've loaded the LoadElement + load_element = self._elements[filename] + + # If the loaded element is a link, then just follow it + # immediately and move on to the target. + # + if load_element.link_target: + + _, filename, loader = self._parse_name(load_element.link_target, rewritable, ticker) + + if loader != self: + level = level + 1 + + return loader.get_loader( + filename, + rewritable=rewritable, + ticker=ticker, + level=level, + provenance=load_element.link_target_provenance, + ) + # meta junction element # # Note that junction elements are not allowed to have @@ -247,7 +268,7 @@ class Loader: # through the entire loading process), which is nice UX. It # would be nice if this could be done for *all* element types, # but since we haven't loaded those yet that's impossible. - if self._elements[filename].dependencies: + if load_element.dependencies: raise LoadError("Dependencies are forbidden for 'junction' elements", LoadErrorReason.INVALID_JUNCTION) element = Element._new_from_meta(meta_element) @@ -549,6 +570,13 @@ class Loader: ticker(filename) top_element = self._load_file_no_deps(filename, rewritable, 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 = loader._load_file(filename, rewritable, ticker, top_element.link_target_provenance) + dependencies = extract_depends_from_node(top_element.node) # The loader queue is a stack of tuples # [0] is the LoadElement instance @@ -588,6 +616,12 @@ class Loader: "{}: Cannot depend on junction".format(dep.provenance), 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, rewritable, ticker) + dep_element = loader._load_file(filename, rewritable, ticker, 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 current_element[0].dependencies.append( # pylint: disable=no-member diff --git a/src/buildstream/exceptions.py b/src/buildstream/exceptions.py index cc2bd6381..2c455be84 100644 --- a/src/buildstream/exceptions.py +++ b/src/buildstream/exceptions.py @@ -139,3 +139,6 @@ class LoadErrorReason(Enum): DUPLICATE_DEPENDENCY = 24 """A duplicate dependency was detected""" + + LINK_FORBIDDEN_DEPENDENCIES = 25 + """A link element declared dependencies""" diff --git a/src/buildstream/plugins/elements/link.py b/src/buildstream/plugins/elements/link.py new file mode 100644 index 000000000..611108241 --- /dev/null +++ b/src/buildstream/plugins/elements/link.py @@ -0,0 +1,90 @@ +# +# 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> + +""" +link - Link elements +================================ +This element is a link to another element, allowing one to create +a symbolic element which will be resolved to another element. + + +Overview +-------- +The only configuration allowed in a ``link`` element is the specified +target of the link. + +.. code:: yaml + + kind: link + + config: + target: element.bst + +The ``link`` element can be used to refer to elements in subprojects, and +can be used to symbolically link junctions as well as other elements. +""" + +from buildstream import Element + + +# Element implementation for the 'link' kind. +class LinkElement(Element): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + + # Links are not allowed any dependencies or sources + BST_FORBID_BDEPENDS = True + BST_FORBID_RDEPENDS = True + BST_FORBID_SOURCES = True + + def configure(self, node): + + node.validate_keys(["target"]) + + # Hold onto the provenance of the specified target, + # allowing the loader to raise errors with better context. + # + target_node = node.get_scalar("target") + self.target = target_node.as_str() + self.target_provenance = target_node.get_provenance() + + def preflight(self): + pass + + def get_unique_key(self): + # This is only used early on but later discarded + return 1 + + def configure_sandbox(self, sandbox): + assert False, "link elements should be discarded at load time" + + def stage(self, sandbox): + assert False, "link elements should be discarded at load time" + + def generate_script(self): + assert False, "link elements should be discarded at load time" + + def assemble(self, sandbox): + assert False, "link elements should be discarded at load time" + + +# Plugin entry point +def setup(): + return LinkElement |