From abb500b124bb752b0150135258cc9d4da929a97b Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Mon, 1 Jun 2020 23:00:24 +0900 Subject: _loader/loader.py: Make collect_element_no_deps() public Within the _loader module, the LoadElement will need to use this in order to resolve links. --- src/buildstream/_loader/loader.py | 153 +++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py index 49018629e..fecfc8d0f 100644 --- a/src/buildstream/_loader/loader.py +++ b/src/buildstream/_loader/loader.py @@ -227,7 +227,7 @@ class Loader: # # Any task counting *inside* the junction will be handled by # its loader. - meta_element = self._collect_element_no_deps(self._elements[filename]) + meta_element = self.collect_element_no_deps(self._elements[filename]) if meta_element.kind != "junction": raise LoadError( "{}{}: Expected junction but element kind is {}".format(provenance_str, filename, meta_element.kind), @@ -368,6 +368,81 @@ class Loader: return state + # collect_element_no_deps() + # + # Collect a single element, without its dependencies, into a meta_element + # + # NOTE: This is declared public in order to share with the LoadElement + # and should not be used outside of that `_loader` module. + # + # Args: + # element (LoadElement): The element for which to load a MetaElement + # task (Task): A task to write progress information to + # + # Returns: + # (MetaElement): A partially loaded MetaElement + # + def collect_element_no_deps(self, element, task=None): + # Return the already built one, if we already built it + meta_element = self._meta_elements.get(element.name) + if meta_element: + return meta_element + + node = element.node + elt_provenance = node.get_provenance() + meta_sources = [] + + element_kind = node.get_str(Symbol.KIND) + + # if there's a workspace for this element then just append a dummy workspace + # metasource. + workspace = self._context.get_workspaces().get_workspace(element.name) + skip_workspace = True + if workspace: + workspace_node = {"kind": "workspace"} + workspace_node["path"] = workspace.get_absolute_path() + workspace_node["last_build"] = str(workspace.to_dict().get("last_build", "")) + node[Symbol.SOURCES] = [workspace_node] + skip_workspace = False + + sources = node.get_sequence(Symbol.SOURCES, default=[]) + for index, source in enumerate(sources): + kind = source.get_str(Symbol.KIND) + # the workspace source plugin cannot be used unless the element is workspaced + if kind == "workspace" and skip_workspace: + continue + + del source[Symbol.KIND] + + # Directory is optional + directory = source.get_str(Symbol.DIRECTORY, default=None) + if directory: + del source[Symbol.DIRECTORY] + meta_source = MetaSource(element.name, index, element_kind, kind, source, directory) + meta_sources.append(meta_source) + + meta_element = MetaElement( + self.project, + element.name, + element_kind, + elt_provenance, + meta_sources, + node.get_mapping(Symbol.CONFIG, default={}), + node.get_mapping(Symbol.VARIABLES, default={}), + node.get_mapping(Symbol.ENVIRONMENT, default={}), + node.get_str_list(Symbol.ENV_NOCACHE, default=[]), + node.get_mapping(Symbol.PUBLIC, default={}), + node.get_mapping(Symbol.SANDBOX, default={}), + element_kind == "junction", + ) + + # Cache it now, make sure it's already there before recursing + self._meta_elements[element.name] = meta_element + if task: + task.add_current_progress() + + return meta_element + ########################################### # Private Methods # ########################################### @@ -581,78 +656,6 @@ class Loader: check_elements.remove(this_element) validated.add(this_element) - # _collect_element_no_deps() - # - # Collect a single element, without its dependencies, into a meta_element - # - # Args: - # element (LoadElement): The element for which to load a MetaElement - # task (Task): A task to write progress information to - # - # Returns: - # (MetaElement): A partially loaded MetaElement - # - def _collect_element_no_deps(self, element, task=None): - # Return the already built one, if we already built it - meta_element = self._meta_elements.get(element.name) - if meta_element: - return meta_element - - node = element.node - elt_provenance = node.get_provenance() - meta_sources = [] - - element_kind = node.get_str(Symbol.KIND) - - # if there's a workspace for this element then just append a dummy workspace - # metasource. - workspace = self._context.get_workspaces().get_workspace(element.name) - skip_workspace = True - if workspace: - workspace_node = {"kind": "workspace"} - workspace_node["path"] = workspace.get_absolute_path() - workspace_node["last_build"] = str(workspace.to_dict().get("last_build", "")) - node[Symbol.SOURCES] = [workspace_node] - skip_workspace = False - - sources = node.get_sequence(Symbol.SOURCES, default=[]) - for index, source in enumerate(sources): - kind = source.get_str(Symbol.KIND) - # the workspace source plugin cannot be used unless the element is workspaced - if kind == "workspace" and skip_workspace: - continue - - del source[Symbol.KIND] - - # Directory is optional - directory = source.get_str(Symbol.DIRECTORY, default=None) - if directory: - del source[Symbol.DIRECTORY] - meta_source = MetaSource(element.name, index, element_kind, kind, source, directory) - meta_sources.append(meta_source) - - meta_element = MetaElement( - self.project, - element.name, - element_kind, - elt_provenance, - meta_sources, - node.get_mapping(Symbol.CONFIG, default={}), - node.get_mapping(Symbol.VARIABLES, default={}), - node.get_mapping(Symbol.ENVIRONMENT, default={}), - node.get_str_list(Symbol.ENV_NOCACHE, default=[]), - node.get_mapping(Symbol.PUBLIC, default={}), - node.get_mapping(Symbol.SANDBOX, default={}), - element_kind == "junction", - ) - - # Cache it now, make sure it's already there before recursing - self._meta_elements[element.name] = meta_element - if task: - task.add_current_progress() - - return meta_element - # _collect_element() # # Collect the toplevel elements we have @@ -666,7 +669,7 @@ class Loader: # def _collect_element(self, top_element, task=None): 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, task)] while element_queue: element = element_queue.pop() @@ -683,7 +686,7 @@ class Loader: name = dep.element.name if name not in loader._meta_elements: - meta_dep = loader._collect_element_no_deps(dep.element, task) + meta_dep = loader.collect_element_no_deps(dep.element, task) element_queue.append(dep.element) meta_element_queue.append(meta_dep) else: -- cgit v1.2.1 From f774efaa72f3400ff8bd506a0fd7c536fd8f608f Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Sat, 30 May 2020 19:37:44 +0900 Subject: link element: Adding support for new link element This element acts as a symbolic link, it has no other configurations other than to specify the element (or junction) to which it refers to, either in the local project or in a subproject. --- src/buildstream/_loader/loadelement.pyx | 35 +++++++++++-- src/buildstream/_loader/loader.py | 36 ++++++++++++- src/buildstream/exceptions.py | 3 ++ src/buildstream/plugins/elements/link.py | 90 ++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/buildstream/plugins/elements/link.py 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 . +# +# Authors: +# Tristan van Berkom + +""" +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 -- cgit v1.2.1 From 35f0545e0781294cab8e69c64f76c611a64640d9 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Sat, 30 May 2020 19:34:34 +0900 Subject: tests/format/link.py: Adding tests for the link element --- tests/format/link.py | 145 +++++++++++++++++++++ .../conditional-junctions/elements/subproject.bst | 5 + .../elements/subsubproject-link.bst | 8 ++ .../link/conditional-junctions/elements/target.bst | 4 + .../format/link/conditional-junctions/project.conf | 13 ++ .../subproject/elements/hello.bst | 5 + .../elements/subsubproject-goodbye-junction.bst | 5 + .../elements/subsubproject-hello-junction.bst | 5 + .../conditional-junctions/subproject/project.conf | 4 + .../subsubproject-goodbye/elements/target.bst | 5 + .../subsubproject-goodbye/files/goodbye.txt | 1 + .../subproject/subsubproject-goodbye/project.conf | 4 + .../subsubproject-hello/elements/target.bst | 5 + .../subproject/subsubproject-hello/files/hello.txt | 1 + .../subproject/subsubproject-hello/project.conf | 4 + tests/format/link/conditional/elements/goodbye.bst | 5 + tests/format/link/conditional/elements/hello.bst | 5 + .../link/conditional/elements/target-link.bst | 8 ++ tests/format/link/conditional/elements/target.bst | 4 + tests/format/link/conditional/files/goodbye.txt | 1 + tests/format/link/conditional/files/hello.txt | 1 + tests/format/link/conditional/project.conf | 13 ++ tests/format/link/invalid/elements/base-file.bst | 5 + .../invalid/elements/link-with-dependencies.bst | 7 + .../link/invalid/elements/link-with-sources.bst | 8 ++ .../invalid/elements/target-link-with-sources.bst | 4 + tests/format/link/invalid/project.conf | 4 + .../notfound/elements/depends-on-link-target.bst | 4 + .../format/link/notfound/elements/link-target.bst | 4 + .../elements/linked-local-junction-target.bst | 4 + .../notfound/elements/linked-local-junction.bst | 4 + .../elements/linked-nested-junction-target.bst | 4 + .../notfound/elements/linked-nested-junction.bst | 4 + .../notfound/elements/subproject-link-notfound.bst | 4 + .../link/notfound/elements/subproject-link.bst | 4 + tests/format/link/notfound/elements/subproject.bst | 5 + .../elements/subsubproject-link-notfound.bst | 4 + .../link/notfound/elements/subsubproject-link.bst | 4 + tests/format/link/notfound/project.conf | 4 + .../subproject/elements/subsubproject-junction.bst | 5 + tests/format/link/notfound/subproject/project.conf | 4 + .../notfound/subproject/subsubproject/project.conf | 2 + .../simple-junctions/elements/subproject-link.bst | 4 + .../link/simple-junctions/elements/subproject.bst | 5 + .../elements/subsubproject-link.bst | 4 + .../simple-junctions/elements/target-local.bst | 4 + .../simple-junctions/elements/target-nested.bst | 4 + tests/format/link/simple-junctions/project.conf | 4 + .../simple-junctions/subproject/elements/hello.bst | 5 + .../subproject/elements/subsubproject-junction.bst | 5 + .../simple-junctions/subproject/files/hello.txt | 1 + .../link/simple-junctions/subproject/project.conf | 4 + .../subproject/subsubproject/elements/hello.bst | 5 + .../subproject/subsubproject/files/hello.txt | 1 + .../subproject/subsubproject/project.conf | 4 + tests/format/link/simple/elements/hello-link.bst | 4 + tests/format/link/simple/elements/hello.bst | 5 + tests/format/link/simple/elements/target.bst | 4 + tests/format/link/simple/files/hello.txt | 1 + tests/format/link/simple/project.conf | 4 + 60 files changed, 407 insertions(+) create mode 100644 tests/format/link.py create mode 100644 tests/format/link/conditional-junctions/elements/subproject.bst create mode 100644 tests/format/link/conditional-junctions/elements/subsubproject-link.bst create mode 100644 tests/format/link/conditional-junctions/elements/target.bst create mode 100644 tests/format/link/conditional-junctions/project.conf create mode 100644 tests/format/link/conditional-junctions/subproject/elements/hello.bst create mode 100644 tests/format/link/conditional-junctions/subproject/elements/subsubproject-goodbye-junction.bst create mode 100644 tests/format/link/conditional-junctions/subproject/elements/subsubproject-hello-junction.bst create mode 100644 tests/format/link/conditional-junctions/subproject/project.conf create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/elements/target.bst create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/files/goodbye.txt create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/project.conf create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-hello/elements/target.bst create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-hello/files/hello.txt create mode 100644 tests/format/link/conditional-junctions/subproject/subsubproject-hello/project.conf create mode 100644 tests/format/link/conditional/elements/goodbye.bst create mode 100644 tests/format/link/conditional/elements/hello.bst create mode 100644 tests/format/link/conditional/elements/target-link.bst create mode 100644 tests/format/link/conditional/elements/target.bst create mode 100644 tests/format/link/conditional/files/goodbye.txt create mode 100644 tests/format/link/conditional/files/hello.txt create mode 100644 tests/format/link/conditional/project.conf create mode 100644 tests/format/link/invalid/elements/base-file.bst create mode 100644 tests/format/link/invalid/elements/link-with-dependencies.bst create mode 100644 tests/format/link/invalid/elements/link-with-sources.bst create mode 100644 tests/format/link/invalid/elements/target-link-with-sources.bst create mode 100644 tests/format/link/invalid/project.conf create mode 100644 tests/format/link/notfound/elements/depends-on-link-target.bst create mode 100644 tests/format/link/notfound/elements/link-target.bst create mode 100644 tests/format/link/notfound/elements/linked-local-junction-target.bst create mode 100644 tests/format/link/notfound/elements/linked-local-junction.bst create mode 100644 tests/format/link/notfound/elements/linked-nested-junction-target.bst create mode 100644 tests/format/link/notfound/elements/linked-nested-junction.bst create mode 100644 tests/format/link/notfound/elements/subproject-link-notfound.bst create mode 100644 tests/format/link/notfound/elements/subproject-link.bst create mode 100644 tests/format/link/notfound/elements/subproject.bst create mode 100644 tests/format/link/notfound/elements/subsubproject-link-notfound.bst create mode 100644 tests/format/link/notfound/elements/subsubproject-link.bst create mode 100644 tests/format/link/notfound/project.conf create mode 100644 tests/format/link/notfound/subproject/elements/subsubproject-junction.bst create mode 100644 tests/format/link/notfound/subproject/project.conf create mode 100644 tests/format/link/notfound/subproject/subsubproject/project.conf create mode 100644 tests/format/link/simple-junctions/elements/subproject-link.bst create mode 100644 tests/format/link/simple-junctions/elements/subproject.bst create mode 100644 tests/format/link/simple-junctions/elements/subsubproject-link.bst create mode 100644 tests/format/link/simple-junctions/elements/target-local.bst create mode 100644 tests/format/link/simple-junctions/elements/target-nested.bst create mode 100644 tests/format/link/simple-junctions/project.conf create mode 100644 tests/format/link/simple-junctions/subproject/elements/hello.bst create mode 100644 tests/format/link/simple-junctions/subproject/elements/subsubproject-junction.bst create mode 100644 tests/format/link/simple-junctions/subproject/files/hello.txt create mode 100644 tests/format/link/simple-junctions/subproject/project.conf create mode 100644 tests/format/link/simple-junctions/subproject/subsubproject/elements/hello.bst create mode 100644 tests/format/link/simple-junctions/subproject/subsubproject/files/hello.txt create mode 100644 tests/format/link/simple-junctions/subproject/subsubproject/project.conf create mode 100644 tests/format/link/simple/elements/hello-link.bst create mode 100644 tests/format/link/simple/elements/hello.bst create mode 100644 tests/format/link/simple/elements/target.bst create mode 100644 tests/format/link/simple/files/hello.txt create mode 100644 tests/format/link/simple/project.conf diff --git a/tests/format/link.py b/tests/format/link.py new file mode 100644 index 000000000..47e19b90c --- /dev/null +++ b/tests/format/link.py @@ -0,0 +1,145 @@ +# Pylint doesn't play well with fixtures and dependency injection from pytest +# pylint: disable=redefined-outer-name + +import os + +import pytest + +from buildstream.testing import cli # pylint: disable=unused-import +from buildstream.exceptions import ErrorDomain, LoadErrorReason + + +DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "link",) + +# +# Test links to elements, this tests both specifying the link as +# the main target, and also as a dependency of the main target. +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("target", ["target.bst", "hello-link.bst"]) +def test_simple_link(cli, tmpdir, datafiles, target): + project = os.path.join(str(datafiles), "simple") + checkoutdir = os.path.join(str(tmpdir), "checkout") + + # Build, checkout + result = cli.run(project=project, args=["build", target]) + result.assert_success() + result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir]) + result.assert_success() + + # Check that the checkout contains the expected files from sub-sub-project + assert os.path.exists(os.path.join(checkoutdir, "hello.txt")) + + +# +# Test links to elements, this tests both specifying the link as +# the main target, and also as a dependency of the main target, while +# also using a conditional statement in the link +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("target", ["target.bst", "target-link.bst"]) +@pytest.mark.parametrize("greeting,expected_file", [("hello", "hello.txt"), ("goodbye", "goodbye.txt")]) +def test_conditional_link(cli, tmpdir, datafiles, target, greeting, expected_file): + project = os.path.join(str(datafiles), "conditional") + checkoutdir = os.path.join(str(tmpdir), "checkout") + + # Build, checkout + result = cli.run(project=project, args=["-o", "greeting", greeting, "build", target]) + result.assert_success() + result = cli.run( + project=project, args=["-o", "greeting", greeting, "artifact", "checkout", target, "--directory", checkoutdir] + ) + result.assert_success() + + # Check that the checkout contains the expected files from sub-sub-project + assert os.path.exists(os.path.join(checkoutdir, expected_file)) + + +# +# Test links to junctions from local projects and subprojects +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("target", ["target-local.bst", "target-nested.bst"]) +def test_simple_junctions(cli, tmpdir, datafiles, target): + project = os.path.join(str(datafiles), "simple-junctions") + checkoutdir = os.path.join(str(tmpdir), "checkout") + + # Build, checkout + result = cli.run(project=project, args=["build", target]) + result.assert_success() + result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir]) + result.assert_success() + + # Check that the checkout contains the expected files from sub-sub-project + assert os.path.exists(os.path.join(checkoutdir, "hello.txt")) + + +# +# Test links which resolve junction targets conditionally +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("greeting,expected_file", [("hello", "hello.txt"), ("goodbye", "goodbye.txt")]) +def test_conditional_junctions(cli, tmpdir, datafiles, greeting, expected_file): + project = os.path.join(str(datafiles), "conditional-junctions") + checkoutdir = os.path.join(str(tmpdir), "checkout") + + # Build, checkout + result = cli.run(project=project, args=["-o", "greeting", greeting, "build", "target.bst"]) + result.assert_success() + result = cli.run( + project=project, + args=["-o", "greeting", greeting, "artifact", "checkout", "target.bst", "--directory", checkoutdir], + ) + result.assert_success() + + # Check that the checkout contains the expected files from sub-sub-project + assert os.path.exists(os.path.join(checkoutdir, expected_file)) + + +# +# Tests links which refer to non-existing elements or junctions +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize( + "target,provenance", + [ + # Target is a link to a non-existing local element + ("link-target.bst", "link-target.bst [line 4 column 10]"), + # Target is a stack depending on a link to a non-existing local element + ("depends-on-link-target.bst", "link-target.bst [line 4 column 10]",), + # Depends on non-existing subproject element, via a local link + ("linked-local-junction-target.bst", "linked-local-junction-target.bst [line 4 column 2]",), + # Depends on non-existing subsubproject element, via a local link + ("linked-nested-junction-target.bst", "linked-nested-junction-target.bst [line 4 column 2]",), + # Depends on an element via a link to a non-existing local junction + ("linked-local-junction.bst", "subproject-link-notfound.bst [line 4 column 10]",), + # Depends on an element via a link to a non-existing subproject junction + ("linked-nested-junction.bst", "subsubproject-link-notfound.bst [line 4 column 10]",), + ], +) +def test_link_not_found(cli, tmpdir, datafiles, target, provenance): + project = os.path.join(str(datafiles), "notfound") + result = cli.run(project=project, args=["build", target]) + + result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE) + assert provenance in result.stderr + + +# +# Tests links with invalid configurations +# +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize( + "target,expected_error,expected_reason", + [ + # Test link which declares sources, either directly of via a dependency + ("link-with-sources.bst", ErrorDomain.ELEMENT, "element-forbidden-sources"), + ("target-link-with-sources.bst", ErrorDomain.ELEMENT, "element-forbidden-sources"), + # Test link which declares dependencies, either directly of via a dependency + ("link-with-dependencies.bst", ErrorDomain.LOAD, LoadErrorReason.LINK_FORBIDDEN_DEPENDENCIES), + ], +) +def test_link_invalid_config(cli, tmpdir, datafiles, target, expected_error, expected_reason): + project = os.path.join(str(datafiles), "invalid") + result = cli.run(project=project, args=["show", target]) + result.assert_main_error(expected_error, expected_reason) diff --git a/tests/format/link/conditional-junctions/elements/subproject.bst b/tests/format/link/conditional-junctions/elements/subproject.bst new file mode 100644 index 000000000..6664eeec6 --- /dev/null +++ b/tests/format/link/conditional-junctions/elements/subproject.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subproject diff --git a/tests/format/link/conditional-junctions/elements/subsubproject-link.bst b/tests/format/link/conditional-junctions/elements/subsubproject-link.bst new file mode 100644 index 000000000..97e54c0ad --- /dev/null +++ b/tests/format/link/conditional-junctions/elements/subsubproject-link.bst @@ -0,0 +1,8 @@ +kind: link + +config: + (?): + - greeting == "hello": + target: subproject.bst:subsubproject-hello-junction.bst + - greeting == "goodbye": + target: subproject.bst:subsubproject-goodbye-junction.bst diff --git a/tests/format/link/conditional-junctions/elements/target.bst b/tests/format/link/conditional-junctions/elements/target.bst new file mode 100644 index 000000000..583061a29 --- /dev/null +++ b/tests/format/link/conditional-junctions/elements/target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subsubproject-link.bst:target.bst diff --git a/tests/format/link/conditional-junctions/project.conf b/tests/format/link/conditional-junctions/project.conf new file mode 100644 index 000000000..aae690c84 --- /dev/null +++ b/tests/format/link/conditional-junctions/project.conf @@ -0,0 +1,13 @@ +name: conditional +min-version: 2.0 + +element-path: elements + +options: + greeting: + type: enum + description: The greeting + values: + - hello + - goodbye + default: hello diff --git a/tests/format/link/conditional-junctions/subproject/elements/hello.bst b/tests/format/link/conditional-junctions/subproject/elements/hello.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/elements/hello.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/conditional-junctions/subproject/elements/subsubproject-goodbye-junction.bst b/tests/format/link/conditional-junctions/subproject/elements/subsubproject-goodbye-junction.bst new file mode 100644 index 000000000..ab37f9a45 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/elements/subsubproject-goodbye-junction.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subsubproject-goodbye diff --git a/tests/format/link/conditional-junctions/subproject/elements/subsubproject-hello-junction.bst b/tests/format/link/conditional-junctions/subproject/elements/subsubproject-hello-junction.bst new file mode 100644 index 000000000..99db3b997 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/elements/subsubproject-hello-junction.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subsubproject-hello diff --git a/tests/format/link/conditional-junctions/subproject/project.conf b/tests/format/link/conditional-junctions/subproject/project.conf new file mode 100644 index 000000000..1529ece04 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/project.conf @@ -0,0 +1,4 @@ +name: subproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/elements/target.bst b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/elements/target.bst new file mode 100644 index 000000000..f7217c514 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/elements/target.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/goodbye.txt diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/files/goodbye.txt b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/files/goodbye.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/files/goodbye.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/project.conf b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/project.conf new file mode 100644 index 000000000..162143c80 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-goodbye/project.conf @@ -0,0 +1,4 @@ +name: subsubproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-hello/elements/target.bst b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/elements/target.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/elements/target.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-hello/files/hello.txt b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/files/hello.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/files/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/conditional-junctions/subproject/subsubproject-hello/project.conf b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/project.conf new file mode 100644 index 000000000..162143c80 --- /dev/null +++ b/tests/format/link/conditional-junctions/subproject/subsubproject-hello/project.conf @@ -0,0 +1,4 @@ +name: subsubproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/conditional/elements/goodbye.bst b/tests/format/link/conditional/elements/goodbye.bst new file mode 100644 index 000000000..f7217c514 --- /dev/null +++ b/tests/format/link/conditional/elements/goodbye.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/goodbye.txt diff --git a/tests/format/link/conditional/elements/hello.bst b/tests/format/link/conditional/elements/hello.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/conditional/elements/hello.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/conditional/elements/target-link.bst b/tests/format/link/conditional/elements/target-link.bst new file mode 100644 index 000000000..43bffebce --- /dev/null +++ b/tests/format/link/conditional/elements/target-link.bst @@ -0,0 +1,8 @@ +kind: link + +config: + (?): + - greeting == "hello": + target: hello.bst + - greeting == "goodbye": + target: goodbye.bst diff --git a/tests/format/link/conditional/elements/target.bst b/tests/format/link/conditional/elements/target.bst new file mode 100644 index 000000000..92ccc16a7 --- /dev/null +++ b/tests/format/link/conditional/elements/target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- target-link.bst diff --git a/tests/format/link/conditional/files/goodbye.txt b/tests/format/link/conditional/files/goodbye.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/conditional/files/goodbye.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/conditional/files/hello.txt b/tests/format/link/conditional/files/hello.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/conditional/files/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/conditional/project.conf b/tests/format/link/conditional/project.conf new file mode 100644 index 000000000..aae690c84 --- /dev/null +++ b/tests/format/link/conditional/project.conf @@ -0,0 +1,13 @@ +name: conditional +min-version: 2.0 + +element-path: elements + +options: + greeting: + type: enum + description: The greeting + values: + - hello + - goodbye + default: hello diff --git a/tests/format/link/invalid/elements/base-file.bst b/tests/format/link/invalid/elements/base-file.bst new file mode 100644 index 000000000..92948a068 --- /dev/null +++ b/tests/format/link/invalid/elements/base-file.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: . diff --git a/tests/format/link/invalid/elements/link-with-dependencies.bst b/tests/format/link/invalid/elements/link-with-dependencies.bst new file mode 100644 index 000000000..f30a00560 --- /dev/null +++ b/tests/format/link/invalid/elements/link-with-dependencies.bst @@ -0,0 +1,7 @@ +kind: link + +depends: +- base-file.bst + +config: + target: base-file.bst diff --git a/tests/format/link/invalid/elements/link-with-sources.bst b/tests/format/link/invalid/elements/link-with-sources.bst new file mode 100644 index 000000000..90ef16601 --- /dev/null +++ b/tests/format/link/invalid/elements/link-with-sources.bst @@ -0,0 +1,8 @@ +kind: link + +sources: +- kind: local + path: . + +config: + target: base-file.bst diff --git a/tests/format/link/invalid/elements/target-link-with-sources.bst b/tests/format/link/invalid/elements/target-link-with-sources.bst new file mode 100644 index 000000000..8fd00433d --- /dev/null +++ b/tests/format/link/invalid/elements/target-link-with-sources.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- link-with-sources.bst diff --git a/tests/format/link/invalid/project.conf b/tests/format/link/invalid/project.conf new file mode 100644 index 000000000..b4fb73ba9 --- /dev/null +++ b/tests/format/link/invalid/project.conf @@ -0,0 +1,4 @@ +name: invalid +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/notfound/elements/depends-on-link-target.bst b/tests/format/link/notfound/elements/depends-on-link-target.bst new file mode 100644 index 000000000..1f3c7a61e --- /dev/null +++ b/tests/format/link/notfound/elements/depends-on-link-target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- link-target.bst diff --git a/tests/format/link/notfound/elements/link-target.bst b/tests/format/link/notfound/elements/link-target.bst new file mode 100644 index 000000000..7ab8902b0 --- /dev/null +++ b/tests/format/link/notfound/elements/link-target.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: no-element-found.bst diff --git a/tests/format/link/notfound/elements/linked-local-junction-target.bst b/tests/format/link/notfound/elements/linked-local-junction-target.bst new file mode 100644 index 000000000..ac89cf604 --- /dev/null +++ b/tests/format/link/notfound/elements/linked-local-junction-target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subproject-link.bst:hello.bst diff --git a/tests/format/link/notfound/elements/linked-local-junction.bst b/tests/format/link/notfound/elements/linked-local-junction.bst new file mode 100644 index 000000000..6b0be9370 --- /dev/null +++ b/tests/format/link/notfound/elements/linked-local-junction.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subproject-link-notfound.bst:element.bst diff --git a/tests/format/link/notfound/elements/linked-nested-junction-target.bst b/tests/format/link/notfound/elements/linked-nested-junction-target.bst new file mode 100644 index 000000000..b39a75f0a --- /dev/null +++ b/tests/format/link/notfound/elements/linked-nested-junction-target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subsubproject-link.bst:hello.bst diff --git a/tests/format/link/notfound/elements/linked-nested-junction.bst b/tests/format/link/notfound/elements/linked-nested-junction.bst new file mode 100644 index 000000000..5edf294e0 --- /dev/null +++ b/tests/format/link/notfound/elements/linked-nested-junction.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subsubproject-link-notfound.bst:element.bst diff --git a/tests/format/link/notfound/elements/subproject-link-notfound.bst b/tests/format/link/notfound/elements/subproject-link-notfound.bst new file mode 100644 index 000000000..ec00414db --- /dev/null +++ b/tests/format/link/notfound/elements/subproject-link-notfound.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject-notfound.bst diff --git a/tests/format/link/notfound/elements/subproject-link.bst b/tests/format/link/notfound/elements/subproject-link.bst new file mode 100644 index 000000000..5e72e81f1 --- /dev/null +++ b/tests/format/link/notfound/elements/subproject-link.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject.bst diff --git a/tests/format/link/notfound/elements/subproject.bst b/tests/format/link/notfound/elements/subproject.bst new file mode 100644 index 000000000..6664eeec6 --- /dev/null +++ b/tests/format/link/notfound/elements/subproject.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subproject diff --git a/tests/format/link/notfound/elements/subsubproject-link-notfound.bst b/tests/format/link/notfound/elements/subsubproject-link-notfound.bst new file mode 100644 index 000000000..1277713e7 --- /dev/null +++ b/tests/format/link/notfound/elements/subsubproject-link-notfound.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject.bst:subsubproject-junction-notfound.bst diff --git a/tests/format/link/notfound/elements/subsubproject-link.bst b/tests/format/link/notfound/elements/subsubproject-link.bst new file mode 100644 index 000000000..be08bb5fe --- /dev/null +++ b/tests/format/link/notfound/elements/subsubproject-link.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject.bst:subsubproject-junction.bst diff --git a/tests/format/link/notfound/project.conf b/tests/format/link/notfound/project.conf new file mode 100644 index 000000000..b792ee157 --- /dev/null +++ b/tests/format/link/notfound/project.conf @@ -0,0 +1,4 @@ +name: notfound +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/notfound/subproject/elements/subsubproject-junction.bst b/tests/format/link/notfound/subproject/elements/subsubproject-junction.bst new file mode 100644 index 000000000..018fb8ec4 --- /dev/null +++ b/tests/format/link/notfound/subproject/elements/subsubproject-junction.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subsubproject diff --git a/tests/format/link/notfound/subproject/project.conf b/tests/format/link/notfound/subproject/project.conf new file mode 100644 index 000000000..1529ece04 --- /dev/null +++ b/tests/format/link/notfound/subproject/project.conf @@ -0,0 +1,4 @@ +name: subproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/notfound/subproject/subsubproject/project.conf b/tests/format/link/notfound/subproject/subsubproject/project.conf new file mode 100644 index 000000000..3b470ccf2 --- /dev/null +++ b/tests/format/link/notfound/subproject/subsubproject/project.conf @@ -0,0 +1,2 @@ +name: subsubproject +min-version: 2.0 diff --git a/tests/format/link/simple-junctions/elements/subproject-link.bst b/tests/format/link/simple-junctions/elements/subproject-link.bst new file mode 100644 index 000000000..5e72e81f1 --- /dev/null +++ b/tests/format/link/simple-junctions/elements/subproject-link.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject.bst diff --git a/tests/format/link/simple-junctions/elements/subproject.bst b/tests/format/link/simple-junctions/elements/subproject.bst new file mode 100644 index 000000000..6664eeec6 --- /dev/null +++ b/tests/format/link/simple-junctions/elements/subproject.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subproject diff --git a/tests/format/link/simple-junctions/elements/subsubproject-link.bst b/tests/format/link/simple-junctions/elements/subsubproject-link.bst new file mode 100644 index 000000000..be08bb5fe --- /dev/null +++ b/tests/format/link/simple-junctions/elements/subsubproject-link.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: subproject.bst:subsubproject-junction.bst diff --git a/tests/format/link/simple-junctions/elements/target-local.bst b/tests/format/link/simple-junctions/elements/target-local.bst new file mode 100644 index 000000000..ac89cf604 --- /dev/null +++ b/tests/format/link/simple-junctions/elements/target-local.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subproject-link.bst:hello.bst diff --git a/tests/format/link/simple-junctions/elements/target-nested.bst b/tests/format/link/simple-junctions/elements/target-nested.bst new file mode 100644 index 000000000..b39a75f0a --- /dev/null +++ b/tests/format/link/simple-junctions/elements/target-nested.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- subsubproject-link.bst:hello.bst diff --git a/tests/format/link/simple-junctions/project.conf b/tests/format/link/simple-junctions/project.conf new file mode 100644 index 000000000..4e2fb0063 --- /dev/null +++ b/tests/format/link/simple-junctions/project.conf @@ -0,0 +1,4 @@ +name: simple +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/simple-junctions/subproject/elements/hello.bst b/tests/format/link/simple-junctions/subproject/elements/hello.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/elements/hello.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/simple-junctions/subproject/elements/subsubproject-junction.bst b/tests/format/link/simple-junctions/subproject/elements/subsubproject-junction.bst new file mode 100644 index 000000000..018fb8ec4 --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/elements/subsubproject-junction.bst @@ -0,0 +1,5 @@ +kind: junction + +sources: +- kind: local + path: subsubproject diff --git a/tests/format/link/simple-junctions/subproject/files/hello.txt b/tests/format/link/simple-junctions/subproject/files/hello.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/files/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/simple-junctions/subproject/project.conf b/tests/format/link/simple-junctions/subproject/project.conf new file mode 100644 index 000000000..1529ece04 --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/project.conf @@ -0,0 +1,4 @@ +name: subproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/simple-junctions/subproject/subsubproject/elements/hello.bst b/tests/format/link/simple-junctions/subproject/subsubproject/elements/hello.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/subsubproject/elements/hello.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/simple-junctions/subproject/subsubproject/files/hello.txt b/tests/format/link/simple-junctions/subproject/subsubproject/files/hello.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/subsubproject/files/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/simple-junctions/subproject/subsubproject/project.conf b/tests/format/link/simple-junctions/subproject/subsubproject/project.conf new file mode 100644 index 000000000..162143c80 --- /dev/null +++ b/tests/format/link/simple-junctions/subproject/subsubproject/project.conf @@ -0,0 +1,4 @@ +name: subsubproject +min-version: 2.0 + +element-path: elements diff --git a/tests/format/link/simple/elements/hello-link.bst b/tests/format/link/simple/elements/hello-link.bst new file mode 100644 index 000000000..83b0fbe46 --- /dev/null +++ b/tests/format/link/simple/elements/hello-link.bst @@ -0,0 +1,4 @@ +kind: link + +config: + target: hello.bst diff --git a/tests/format/link/simple/elements/hello.bst b/tests/format/link/simple/elements/hello.bst new file mode 100644 index 000000000..a04a856cd --- /dev/null +++ b/tests/format/link/simple/elements/hello.bst @@ -0,0 +1,5 @@ +kind: import + +sources: +- kind: local + path: files/hello.txt diff --git a/tests/format/link/simple/elements/target.bst b/tests/format/link/simple/elements/target.bst new file mode 100644 index 000000000..7c1be3a42 --- /dev/null +++ b/tests/format/link/simple/elements/target.bst @@ -0,0 +1,4 @@ +kind: stack + +depends: +- hello-link.bst diff --git a/tests/format/link/simple/files/hello.txt b/tests/format/link/simple/files/hello.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/format/link/simple/files/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/format/link/simple/project.conf b/tests/format/link/simple/project.conf new file mode 100644 index 000000000..4e2fb0063 --- /dev/null +++ b/tests/format/link/simple/project.conf @@ -0,0 +1,4 @@ +name: simple +min-version: 2.0 + +element-path: elements -- cgit v1.2.1 From 6ec85d2942ea5a4adb7fbd9f9cd8e8d6beb41435 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Sat, 30 May 2020 22:08:18 +0900 Subject: doc: Adding new `link` plugin to the core plugin index --- doc/source/core_plugins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/core_plugins.rst b/doc/source/core_plugins.rst index b40728669..7b968b7b9 100644 --- a/doc/source/core_plugins.rst +++ b/doc/source/core_plugins.rst @@ -19,6 +19,7 @@ General elements elements/import elements/compose elements/script + elements/link elements/junction elements/filter -- cgit v1.2.1 From e5d0e0c8ab134a874b74176b308f45b440a6f07b Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Mon, 1 Jun 2020 20:06:12 +0900 Subject: junctions: Remove all traces of the `target` option This removes: * The `target` feature from the junction plugin * Special case code in the loader for the `target` feature * The `target` related cases in tests/format/junctions.py This also adjusts the `target` related documentation in the `junction` element to suggest using a `link` element for the purpose of using a subproject junction configuration to access a common sub-subproject. --- src/buildstream/_loader/loader.py | 12 ----- src/buildstream/plugins/elements/junction.py | 62 +++++----------------- tests/format/junctions.py | 54 ------------------- .../elements/invalid-source-target.bst | 8 --- .../elements/nested-junction-target.bst | 4 -- .../config-target/elements/no-junction.bst | 4 -- .../config-target/elements/subproject.bst | 5 -- .../config-target/elements/subsubproject.bst | 4 -- .../junctions/config-target/elements/target.bst | 4 -- tests/format/junctions/config-target/project.conf | 4 -- .../subproject/elements/subsubproject-junction.bst | 5 -- .../config-target/subproject/project.conf | 4 -- .../subproject/subsubproject/elements/hello.bst | 5 -- .../subproject/subsubproject/files/hello.txt | 1 - .../subproject/subsubproject/project.conf | 4 -- 15 files changed, 13 insertions(+), 167 deletions(-) delete mode 100644 tests/format/junctions/config-target/elements/invalid-source-target.bst delete mode 100644 tests/format/junctions/config-target/elements/nested-junction-target.bst delete mode 100644 tests/format/junctions/config-target/elements/no-junction.bst delete mode 100644 tests/format/junctions/config-target/elements/subproject.bst delete mode 100644 tests/format/junctions/config-target/elements/subsubproject.bst delete mode 100644 tests/format/junctions/config-target/elements/target.bst delete mode 100644 tests/format/junctions/config-target/project.conf delete mode 100644 tests/format/junctions/config-target/subproject/elements/subsubproject-junction.bst delete mode 100644 tests/format/junctions/config-target/subproject/project.conf delete mode 100644 tests/format/junctions/config-target/subproject/subsubproject/elements/hello.bst delete mode 100644 tests/format/junctions/config-target/subproject/subsubproject/files/hello.txt delete mode 100644 tests/format/junctions/config-target/subproject/subsubproject/project.conf diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py index ed0d345a3..b73f5b862 100644 --- a/src/buildstream/_loader/loader.py +++ b/src/buildstream/_loader/loader.py @@ -274,18 +274,6 @@ class Loader: element = Element._new_from_meta(meta_element) element._initialize_state() - # If this junction element points to a sub-sub-project, we need to - # find loader for that project. - if element.target: - subproject_loader = self.get_loader( - element.target_junction, rewritable=rewritable, ticker=ticker, level=level, provenance=provenance - ) - loader = subproject_loader.get_loader( - element.target_element, rewritable=rewritable, ticker=ticker, level=level, provenance=provenance - ) - self._loaders[filename] = loader - return loader - # Handle the case where a subproject has no ref # if not element._has_all_sources_resolved(): diff --git a/src/buildstream/plugins/elements/junction.py b/src/buildstream/plugins/elements/junction.py index 86d1de8f8..c9e78632f 100644 --- a/src/buildstream/plugins/elements/junction.py +++ b/src/buildstream/plugins/elements/junction.py @@ -48,13 +48,6 @@ Overview # Optionally look in a subpath of the source repository for the project path: projects/hello - # Optionally specify another junction element to serve as a target for - # this element. Target should be defined using the syntax - # ``{junction-name}:{element-name}``. - # - # Note that this option cannot be used in conjunction with sources. - target: sub-project.bst:sub-sub-project.bst - # Optionally declare whether elements within the junction project # should interact with project remotes (default: False). cache-junction-elements: False @@ -132,29 +125,24 @@ simply use one junction and ignore the others. Due to this, BuildStream requires the user to resolve possibly conflicting nested junctions by creating a junction with the same name in the top-level project, which then takes precedence. -Targeting other junctions -~~~~~~~~~~~~~~~~~~~~~~~~~ -When working with nested junctions, you can also create a junction element that -targets another junction element in the sub-project. This can be useful if you -need to ensure that both the top-level project and the sub-project are using -the same version of the sub-sub-project. +Linking to other junctions +~~~~~~~~~~~~~~~~~~~~~~~~~~ +When working with nested junctions, you often need to ensure that multiple +projects are using the same version of a given subproject. + +In order to ensure that your project is using a junction to a sub-subproject +declared by a direct subproject, then you can use a :mod:`link ` +element in place of declaring a junction. -This can be done using the ``target`` configuration option. See below for an -example: +This lets you create a link to a junction in the subproject, which you +can then treat as a regular junction in your toplevel project. .. code:: yaml - kind: junction + kind: link config: target: subproject.bst:subsubproject.bst - -In the above example, this junction element would be targeting the junction -element named ``subsubproject.bst`` in the subproject referred to by -``subproject.bst``. - -Note that when targeting another junction, the names of the junction element -must not be the same as the name of the target. """ from buildstream import Element, ElementError @@ -173,39 +161,15 @@ class JunctionElement(Element): def configure(self, node): - node.validate_keys(["path", "options", "target", "cache-junction-elements", "ignore-junction-remotes"]) + node.validate_keys(["path", "options", "cache-junction-elements", "ignore-junction-remotes"]) self.path = node.get_str("path", default="") self.options = node.get_mapping("options", default={}) - self.target = node.get_str("target", default=None) - self.target_element = None - self.target_junction = None self.cache_junction_elements = node.get_bool("cache-junction-elements", default=False) self.ignore_junction_remotes = node.get_bool("ignore-junction-remotes", default=False) def preflight(self): - # "target" cannot be used in conjunction with: - # 1. sources - # 2. config['options'] - # 3. config['path'] - if self.target and any(self.sources()): - raise ElementError("junction elements cannot define both 'sources' and 'target' config option") - if self.target and any(self.options.items()): - raise ElementError("junction elements cannot define both 'options' and 'target'") - if self.target and self.path: - raise ElementError("junction elements cannot define both 'path' and 'target'") - - # Validate format of target, if defined - if self.target: - try: - self.target_junction, self.target_element = self.target.split(":") - except ValueError: - raise ElementError("'target' option must be in format '{junction-name}:{element-name}'") - - # We cannot target a junction that has the same name as us, since that - # will cause an infinite recursion while trying to load it. - if self.name == self.target_element: - raise ElementError("junction elements cannot target an element with the same name") + pass def get_unique_key(self): # Junctions do not produce artifacts. get_unique_key() implementation diff --git a/tests/format/junctions.py b/tests/format/junctions.py index 70572ee3e..f097e0b8b 100644 --- a/tests/format/junctions.py +++ b/tests/format/junctions.py @@ -410,60 +410,6 @@ def test_build_git_cross_junction_names(cli, tmpdir, datafiles): assert os.path.exists(os.path.join(checkoutdir, "base.txt")) -@pytest.mark.datafiles(DATA_DIR) -def test_config_target(cli, tmpdir, datafiles): - project = os.path.join(str(datafiles), "config-target") - checkoutdir = os.path.join(str(tmpdir), "checkout") - - # Build, checkout - result = cli.run(project=project, args=["build", "target.bst"]) - result.assert_success() - result = cli.run(project=project, args=["artifact", "checkout", "target.bst", "--directory", checkoutdir]) - result.assert_success() - - # Check that the checkout contains the expected files from sub-sub-project - assert os.path.exists(os.path.join(checkoutdir, "hello.txt")) - - -@pytest.mark.datafiles(DATA_DIR) -def test_invalid_sources_and_target(cli, tmpdir, datafiles): - project = os.path.join(str(datafiles), "config-target") - - result = cli.run(project=project, args=["show", "invalid-source-target.bst"]) - result.assert_main_error(ErrorDomain.ELEMENT, None) - - assert "junction elements cannot define both 'sources' and 'target' config option" in result.stderr - - -@pytest.mark.datafiles(DATA_DIR) -def test_invalid_target_name(cli, tmpdir, datafiles): - project = os.path.join(str(datafiles), "config-target") - - # Rename our junction element to the same name as its target - old_path = os.path.join(project, "elements/subsubproject.bst") - new_path = os.path.join(project, "elements/subsubproject-junction.bst") - os.rename(old_path, new_path) - - # This should fail now - result = cli.run(project=project, args=["show", "subsubproject-junction.bst"]) - result.assert_main_error(ErrorDomain.ELEMENT, None) - - assert "junction elements cannot target an element with the same name" in result.stderr - - -# We cannot exhaustively test all possible ways in which this can go wrong, so -# test a couple of common ways in which we expect this to go wrong. -@pytest.mark.parametrize("target", ["no-junction.bst", "nested-junction-target.bst"]) -@pytest.mark.datafiles(DATA_DIR) -def test_invalid_target_format(cli, tmpdir, datafiles, target): - project = os.path.join(str(datafiles), "config-target") - - result = cli.run(project=project, args=["show", target]) - result.assert_main_error(ErrorDomain.ELEMENT, None) - - assert "'target' option must be in format '{junction-name}:{element-name}'" in result.stderr - - @pytest.mark.datafiles(DATA_DIR) def test_junction_show(cli, tmpdir, datafiles): project = os.path.join(str(datafiles), "foo") diff --git a/tests/format/junctions/config-target/elements/invalid-source-target.bst b/tests/format/junctions/config-target/elements/invalid-source-target.bst deleted file mode 100644 index b97d09034..000000000 --- a/tests/format/junctions/config-target/elements/invalid-source-target.bst +++ /dev/null @@ -1,8 +0,0 @@ -kind: junction - -sources: -- kind: local - path: subproject/subsubproject - -config: - target: subproject.bst:subsubproject-junction.bst diff --git a/tests/format/junctions/config-target/elements/nested-junction-target.bst b/tests/format/junctions/config-target/elements/nested-junction-target.bst deleted file mode 100644 index f76a264e5..000000000 --- a/tests/format/junctions/config-target/elements/nested-junction-target.bst +++ /dev/null @@ -1,4 +0,0 @@ -kind: junction - -config: - target: subproject.bst:subsubproject.bst:hello.bst diff --git a/tests/format/junctions/config-target/elements/no-junction.bst b/tests/format/junctions/config-target/elements/no-junction.bst deleted file mode 100644 index 15d1842f6..000000000 --- a/tests/format/junctions/config-target/elements/no-junction.bst +++ /dev/null @@ -1,4 +0,0 @@ -kind: junction - -config: - target: subproject.bst diff --git a/tests/format/junctions/config-target/elements/subproject.bst b/tests/format/junctions/config-target/elements/subproject.bst deleted file mode 100644 index 6664eeec6..000000000 --- a/tests/format/junctions/config-target/elements/subproject.bst +++ /dev/null @@ -1,5 +0,0 @@ -kind: junction - -sources: -- kind: local - path: subproject diff --git a/tests/format/junctions/config-target/elements/subsubproject.bst b/tests/format/junctions/config-target/elements/subsubproject.bst deleted file mode 100644 index 20dc4a0c4..000000000 --- a/tests/format/junctions/config-target/elements/subsubproject.bst +++ /dev/null @@ -1,4 +0,0 @@ -kind: junction - -config: - target: subproject.bst:subsubproject-junction.bst diff --git a/tests/format/junctions/config-target/elements/target.bst b/tests/format/junctions/config-target/elements/target.bst deleted file mode 100644 index 50d74489a..000000000 --- a/tests/format/junctions/config-target/elements/target.bst +++ /dev/null @@ -1,4 +0,0 @@ -kind: stack - -depends: -- subsubproject.bst:hello.bst diff --git a/tests/format/junctions/config-target/project.conf b/tests/format/junctions/config-target/project.conf deleted file mode 100644 index d9e1d7a4f..000000000 --- a/tests/format/junctions/config-target/project.conf +++ /dev/null @@ -1,4 +0,0 @@ -name: config-target -min-version: 2.0 - -element-path: elements diff --git a/tests/format/junctions/config-target/subproject/elements/subsubproject-junction.bst b/tests/format/junctions/config-target/subproject/elements/subsubproject-junction.bst deleted file mode 100644 index 018fb8ec4..000000000 --- a/tests/format/junctions/config-target/subproject/elements/subsubproject-junction.bst +++ /dev/null @@ -1,5 +0,0 @@ -kind: junction - -sources: -- kind: local - path: subsubproject diff --git a/tests/format/junctions/config-target/subproject/project.conf b/tests/format/junctions/config-target/subproject/project.conf deleted file mode 100644 index 1529ece04..000000000 --- a/tests/format/junctions/config-target/subproject/project.conf +++ /dev/null @@ -1,4 +0,0 @@ -name: subproject -min-version: 2.0 - -element-path: elements diff --git a/tests/format/junctions/config-target/subproject/subsubproject/elements/hello.bst b/tests/format/junctions/config-target/subproject/subsubproject/elements/hello.bst deleted file mode 100644 index a04a856cd..000000000 --- a/tests/format/junctions/config-target/subproject/subsubproject/elements/hello.bst +++ /dev/null @@ -1,5 +0,0 @@ -kind: import - -sources: -- kind: local - path: files/hello.txt diff --git a/tests/format/junctions/config-target/subproject/subsubproject/files/hello.txt b/tests/format/junctions/config-target/subproject/subsubproject/files/hello.txt deleted file mode 100644 index ce0136250..000000000 --- a/tests/format/junctions/config-target/subproject/subsubproject/files/hello.txt +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/tests/format/junctions/config-target/subproject/subsubproject/project.conf b/tests/format/junctions/config-target/subproject/subsubproject/project.conf deleted file mode 100644 index 162143c80..000000000 --- a/tests/format/junctions/config-target/subproject/subsubproject/project.conf +++ /dev/null @@ -1,4 +0,0 @@ -name: subsubproject -min-version: 2.0 - -element-path: elements -- cgit v1.2.1 From e4930651ffd27471e60e81a25454ab9f60cc33e5 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Mon, 1 Jun 2020 20:28:03 +0900 Subject: NEWS: Updated for breaking change of junction target removal --- NEWS | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 2aa5e32f9..22ef7e670 100644 --- a/NEWS +++ b/NEWS @@ -18,18 +18,19 @@ Format o BREAKING CHANGE: Now deprecation warnings are suppressed using the `allow-deprecated` configuration with the plugin origins in project.conf, instead of on the source/element overrides section (See issue #1291) + o BREAKING CHANGE: The `target` option of junctions has been completely removed, + Use `link` elements instead. o Variables from an element can now be used in source configurations - Plugins ------- o Cache keys will change for all elements that have defined the `command-subdir` variable. This is the result of fixing a bug where this variable was not included in the cache key correctly. - o The `pip` element has been removed. Please use the one from bst-plugins-experimental - + o Introduced new `link` element which can be used as a symbolic link to other + elements or junctions, in the local project or in subprojects. API --- -- cgit v1.2.1