summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbst-marge-bot <marge-bot@buildstream.build>2020-11-04 07:18:54 +0000
committerbst-marge-bot <marge-bot@buildstream.build>2020-11-04 07:18:54 +0000
commit5d51b8d00ff5de466521cdecb42dda1550e787d8 (patch)
tree7e91551d8aa972ade689a30fec705209f25b741f
parent7fee0e64ffd2b5dcfd1766df8ed3a692e86a640d (diff)
parent6bf78c0734f0ff22fe0eef090b794f0a1b1a3fb8 (diff)
downloadbuildstream-5d51b8d00ff5de466521cdecb42dda1550e787d8.tar.gz
Merge branch 'tristan/override-elements' into 'master'
Support overrides semantic for elements See merge request BuildStream/buildstream!2094
-rw-r--r--src/buildstream/_loader/loadelement.pyx17
-rw-r--r--src/buildstream/_loader/loader.py181
-rw-r--r--src/buildstream/plugins/elements/junction.py78
-rw-r--r--tests/format/junctions.py47
-rw-r--r--tests/format/junctions/override-element/element.txt0
-rw-r--r--tests/format/junctions/override-element/override-subproject-dep.bst11
-rw-r--r--tests/format/junctions/override-element/override-subproject-element-using-link.bst12
-rw-r--r--tests/format/junctions/override-element/override-subproject-element-with-link.bst12
-rw-r--r--tests/format/junctions/override-element/override-subproject-element.bst11
-rw-r--r--tests/format/junctions/override-element/override-subsubproject.bst12
-rw-r--r--tests/format/junctions/override-element/project.conf2
-rw-r--r--tests/format/junctions/override-element/subdep-override.bst4
-rw-r--r--tests/format/junctions/override-element/subdep-override.txt0
-rw-r--r--tests/format/junctions/override-element/subelement-alternative-link.bst8
-rw-r--r--tests/format/junctions/override-element/subelement-override.bst14
-rw-r--r--tests/format/junctions/override-element/subelement-override.txt0
-rw-r--r--tests/format/junctions/override-element/subproject-override-dep.bst8
-rw-r--r--tests/format/junctions/override-element/subproject-override-element-using-link.bst8
-rw-r--r--tests/format/junctions/override-element/subproject-override-element-with-link.bst8
-rw-r--r--tests/format/junctions/override-element/subproject-override-element.bst8
-rw-r--r--tests/format/junctions/override-element/subproject-override-subsubproject-element.bst8
-rw-r--r--tests/format/junctions/override-element/subproject/project.conf2
-rw-r--r--tests/format/junctions/override-element/subproject/sub-alternative.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/sub.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/subdep.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/subdependency.bst4
-rw-r--r--tests/format/junctions/override-element/subproject/subelement-alternative.bst7
-rw-r--r--tests/format/junctions/override-element/subproject/subelement-link.bst8
-rw-r--r--tests/format/junctions/override-element/subproject/subelement.bst7
-rw-r--r--tests/format/junctions/override-element/subproject/subsubdep-override.bst4
-rw-r--r--tests/format/junctions/override-element/subproject/subsubdep-override.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject-override-dep.bst8
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject/project.conf2
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject/subsub.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject/subsubdep.txt0
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject/subsubdependency.bst4
-rw-r--r--tests/format/junctions/override-element/subproject/subsubproject/subsubelement.bst7
37 files changed, 456 insertions, 46 deletions
diff --git a/src/buildstream/_loader/loadelement.pyx b/src/buildstream/_loader/loadelement.pyx
index 60de948c8..210869e51 100644
--- a/src/buildstream/_loader/loadelement.pyx
+++ b/src/buildstream/_loader/loadelement.pyx
@@ -238,6 +238,7 @@ cdef class LoadElement:
# TODO: if/when pyroaring exports symbols, we could type this statically
cdef object _dep_cache
cdef readonly list dependencies
+ cdef readonly bint fully_loaded # This is True if dependencies were also loaded
def __cinit__(self, MappingNode node, str filename, object loader):
@@ -250,6 +251,7 @@ cdef class LoadElement:
self.full_name = None # The element full name (with associated junction)
self.node_id = _next_synthetic_counter()
self.link_target = None # The target of a link element (ScalarNode)
+ self.fully_loaded = False # Whether we entered the loop to load dependencies or not
#
# Private members
@@ -338,6 +340,21 @@ cdef class LoadElement:
self._ensure_depends_cache()
return other.node_id in self._dep_cache
+ # mark_fully_loaded()
+ #
+ # Sets the fully loaded state on this load element
+ #
+ # This state bit is used by the Loader to distinguish
+ # between an element which has only been shallow loaded
+ # and an element which has entered the loop which loads
+ # it's dependencies.
+ #
+ # Args:
+ # element (LoadElement): The resolved LoadElement
+ #
+ def mark_fully_loaded(self):
+ self.fully_loaded = True
+
###########################################
# Private Methods #
###########################################
diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py
index 46e8884c0..54efd27ae 100644
--- a/src/buildstream/_loader/loader.py
+++ b/src/buildstream/_loader/loader.py
@@ -392,6 +392,66 @@ class Loader:
return path
+ # _load_one_file():
+ #
+ # A helper function to load a single file within the _load_file() process,
+ # this allows us to handle redirections more consistently.
+ #
+ # Args:
+ # filename (str): The element-path relative bst file
+ # provenance_node (Node): The location from where the file was referred to, or None
+ # load_subprojects (bool): Whether to load subprojects
+ #
+ # Returns:
+ # (LoadElement): A LoadElement, which might be shallow loaded or fully loaded.
+ #
+ def _load_one_file(self, filename, provenance_node, *, load_subprojects=True):
+
+ element = None
+
+ # First check the cache, the cache might contain shallow loaded
+ # elements.
+ #
+ try:
+ element = self._elements[filename]
+
+ # If the cached element has already entered the loop which loads
+ # it's dependencies, it is fully loaded and any further checks in
+ # this function are expected to have already been performed.
+ #
+ if element.fully_loaded:
+ return element
+
+ except KeyError:
+
+ # Shallow load if it's not yet loaded.
+ element = self._load_file_no_deps(filename, provenance_node)
+
+ # Check if there was an override for this element
+ #
+ override = self._search_for_override_element(filename)
+ if override:
+ #
+ # If there was an override for the element, then it was
+ # implicitly fully loaded by _search_for_override_element(),
+ #
+ return override
+
+ # If this element is a link then we need to resolve it, and return
+ # the linked element instead of this one.
+ #
+ if element.link_target is not None:
+ link_target = element.link_target.as_str() # pylint: disable=no-member
+ _, filename, loader = self._parse_name(link_target, element.link_target, load_subprojects=load_subprojects)
+
+ #
+ # Redirect the loading of the file and it's dependencies to the appropriate loader,
+ # which might or might not be the same loader.
+ #
+ return loader._load_file(filename, element.link_target, load_subprojects=load_subprojects)
+
+ return element
+
# _load_file():
#
# Semi-Iteratively load bst files
@@ -402,32 +462,26 @@ class Loader:
#
# Args:
# filename (str): The element-path relative bst file
- # load_subprojects (bool): Whether to load subprojects
# provenance_node (Node): The location from where the file was referred to, or None
+ # load_subprojects (bool): Whether to load subprojects
#
# Returns:
# (LoadElement): A loaded LoadElement
#
def _load_file(self, filename, provenance_node, *, load_subprojects=True):
- # Silently ignore already loaded files
- with suppress(KeyError):
- return self._elements[filename]
-
- top_element = self._load_file_no_deps(filename, provenance_node)
+ top_element = self._load_one_file(filename, provenance_node, load_subprojects=load_subprojects)
- # If this element is a link then we need to resolve it
- # and replace the dependency we've processed with this one
- if top_element.link_target is not None:
- link_target = top_element.link_target.as_str() # pylint: disable=no-member
- _, filename, loader = self._parse_name(
- link_target, top_element.link_target, load_subprojects=load_subprojects
- )
+ # Already loaded dependencies for a fully loaded element, early return.
+ #
+ if top_element.fully_loaded:
+ return top_element
- # Early return, redirect the loading of the file and it's dependencies to the
- # appropriate loader.
- #
- return loader._load_file(filename, top_element.link_target, load_subprojects=load_subprojects)
+ #
+ # Mark the top element here as "fully loaded", so that we will avoid trying to
+ # load it's dependencies more than once.
+ #
+ top_element.mark_fully_loaded()
dependencies = extract_depends_from_node(top_element.node)
# The loader queue is a stack of tuples
@@ -449,14 +503,18 @@ class Loader:
if dep.junction:
loader = self.get_loader(dep.junction, dep.node)
dep_element = loader._load_file(dep.name, dep.node)
+
else:
- dep_element = self._elements.get(dep.name)
- if dep_element is None:
- # The loader does not have this available so we need to
- # either recursively cause it to be loaded, or else we
- # need to push this onto the loader queue in this loader
- dep_element = self._load_file_no_deps(dep.name, dep.node)
+ dep_element = self._load_one_file(dep.name, dep.node, load_subprojects=load_subprojects)
+
+ # If the loaded element is not fully loaded, queue up the dependencies to be loaded in this loop.
+ #
+ if not dep_element.fully_loaded:
+
+ # Mark the dep_element as fully_loaded, as we're already queueing it's deps
+ dep_element.mark_fully_loaded()
+
dep_deps = extract_depends_from_node(dep_element.node)
loader_queue.append((dep_element, list(reversed(dep_deps)), []))
@@ -467,12 +525,6 @@ class Loader:
LoadErrorReason.INVALID_DATA,
)
- # If this dependency is a link then we need to resolve it
- # and replace the dependency we've processed with this one
- if dep_element.link_target:
- _, filename, loader = self._parse_name(dep_element.link_target.as_str(), dep_element.link_target)
- dep_element = loader._load_file(filename, dep_element.link_target)
-
# We've now resolved the element for this dependency, lets set the resolved
# LoadElement on the dependency and append the dependency to the owning
# LoadElement dependency list.
@@ -572,19 +624,19 @@ class Loader:
return None
- # _search_for_override():
+ # _search_for_overrides():
#
- # Search parent projects for an overridden subproject to replace this junction.
- #
- # This function is called once for each direct child while looking up
- # child loaders, after which point the child loader is cached in the `_loaders`
- # table. This function also has the side effect of recording alternative parents
- # of a child loader in the case that the child loader is overridden.
+ # Search for parent loaders which have an override for the specified element,
+ # returning a list of loaders with the highest level overriding loader at the
+ # end of the list, and the closest ancestor being at the beginning of the list.
#
# Args:
- # filename (str): Junction name
+ # filename (str): The local element name
+ #
+ # Returns:
+ # (list): A list of loaders which override this element
#
- def _search_for_override(self, filename):
+ def _search_for_overrides(self, filename):
loader = self
override_path = filename
@@ -600,6 +652,29 @@ class Loader:
override_path = junction.name + ":" + override_path
loader = loader._parent
+ return overriding_loaders
+
+ # _search_for_override_loader():
+ #
+ # Search parent projects an override of the junction specified by @filename,
+ # returning the loader object which should be used in place of the local
+ # junction specified by @filename.
+ #
+ # This function is called once for each direct child while looking up
+ # child loaders, after which point the child loader is cached in the `_loaders`
+ # table. This function also has the side effect of recording alternative parents
+ # of a child loader in the case that the child loader is overridden.
+ #
+ # Args:
+ # filename (str): Junction name
+ #
+ # Returns:
+ # (Loader): The loader to use, in case @filename was overridden, otherwise None.
+ #
+ def _search_for_override_loader(self, filename):
+
+ overriding_loaders = self._search_for_overrides(filename)
+
# If there are any overriding loaders, use the highest one in
# the ancestry to lookup the loader for this project.
#
@@ -630,6 +705,34 @@ class Loader:
#
return None
+ # _search_for_override_element():
+ #
+ # Search parent projects an override of the element specified by @filename,
+ # returning the loader object which should be used in place of the local
+ # element specified by @filename.
+ #
+ # Args:
+ # filename (str): Junction name
+ #
+ # Returns:
+ # (Loader): The loader to use, in case @filename was overridden, otherwise None.
+ #
+ def _search_for_override_element(self, filename):
+ element = None
+
+ # If there are any overriding loaders, use the highest one in
+ # the ancestry to lookup the element which should be used in place
+ # of @filename.
+ #
+ overriding_loaders = self._search_for_overrides(filename)
+ if overriding_loaders:
+ overriding_loader, override_node = overriding_loaders[-1]
+
+ _, filename, loader = overriding_loader._parse_name(override_node.as_str(), override_node)
+ element = loader._load_file(filename, override_node)
+
+ return element
+
# _get_loader():
#
# Return loader for specified junction
@@ -660,7 +763,7 @@ class Loader:
# Search the ancestry for an overridden loader to use in place
# of using the locally defined junction.
#
- override_loader = self._search_for_override(filename)
+ override_loader = self._search_for_override_loader(filename)
if override_loader:
self._loaders[filename] = override_loader
return override_loader
diff --git a/src/buildstream/plugins/elements/junction.py b/src/buildstream/plugins/elements/junction.py
index 8693313af..cc980bec3 100644
--- a/src/buildstream/plugins/elements/junction.py
+++ b/src/buildstream/plugins/elements/junction.py
@@ -48,8 +48,7 @@ Overview
# Optionally look in a subpath of the source repository for the project
path: projects/hello
- # Optionally override junction configurations in the subproject
- # with a junction declaration in this project.
+ # Optionally override elements in subprojects, including junctions.
#
overrides:
subproject-junction.bst: local-junction.bst
@@ -138,6 +137,79 @@ configuration in a subproject which matches the toplevel project's
configuration.
+Overriding elements
+-------------------
+It is possible to override elements in subprojects. This can be useful if for
+example, you need to work with a custom variant or fork of some software in the
+subproject. This is a better strategy than overlapping and overwriting shared
+libraries built by the subproject later on, as we can ensure that reverse dependencies
+in the subproject are built against the overridden element.
+
+Overridding elements allows you to build on top of an existing project
+and benefit from updates and releases for the vast majority of the upstream project,
+even when there are some parts of the upstream project which need to be customized
+for your own applications.
+
+Even junction elements in subprojects can be overridden, this is sometimes important
+in order to reconcile conflicts when multiple projects depend on the same subproject,
+as :ref:`discussed below <core_junction_nested_overrides>`.
+
+.. code:: yaml
+
+ kind: junction
+
+ ...
+
+ config:
+
+ # Override elements in a junctioned project
+ #
+ overrides:
+ subproject-element.bst: local-element.bst
+
+It is also possible to override elements in deeply nested subprojects, using
+project relative :ref:`junction paths <format_element_names>`:
+
+.. code:: yaml
+
+ kind: junction
+
+ ...
+
+ config:
+
+ # Override deeply nested elements
+ #
+ overrides:
+ subproject.bst:subsubproject-element.bst: local-element.bst
+
+.. attention::
+
+ Overriding an element causes your project to completely define the
+ element being overridden, which means you will no longer receive updates
+ or security patches to the element in question when updating to newer
+ versions and releases of the upstream project.
+
+ As such, overriding elements is only recommended in cases where the
+ element is very significantly redefined.
+
+ Such cases include cases when you need a newer version of the element than
+ the one maintained by the upstream project you are using as a subproject,
+ or when you have significanly modified the code in your own custom ways.
+
+ If you only need to introduce a security patch, then it is recommended that
+ you create your own downstream branch of the upstream project, not only will
+ this allow you to more easily consume updates with VCS tools like ``git rebase``,
+ but it will also be more convenient for submitting your security patches
+ to the upstream project so that you can drop them in a future update.
+
+ Similarly, if you only need to enable/disable a specific feature of a module,
+ it is also preferrable to use a downstream branch of the upstream project.
+ In such a case, it is also worth trying to convince the upstream project to
+ support a :ref:`project option <project_options>` for your specific element
+ configuration, if it would be of use to other users too.
+
+
.. _core_junction_nested:
Nested Junctions
@@ -152,6 +224,8 @@ the user to resolve conflicting nested junctions, and will provide an error
message whenever a conflict is detected.
+.. _core_junction_nested_overrides:
+
Overriding subproject junctions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your project and a subproject share a subproject in common, then one way
diff --git a/tests/format/junctions.py b/tests/format/junctions.py
index c4eea2d1c..5c65a5534 100644
--- a/tests/format/junctions.py
+++ b/tests/format/junctions.py
@@ -388,11 +388,48 @@ def test_full_path_not_found(cli, tmpdir, datafiles, target, provenance):
#
-# Test the overrides feature.
+# Test overridding elements
#
-# Here we reuse the `nested` project since it already has deep
-# nesting, and add to it a couple of additional junctions to
-# test overriding of junctions at various depts
+@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.parametrize(
+ "target,expected",
+ [
+ # Override an element in a subproject, this dependency will depend on
+ # the same element in the subproject as the overridden element did.
+ ("override-subproject-element.bst", ["element.txt", "subelement-override.txt", "subdep.txt"]),
+ # Override an element in a subproject while depending on an element which depends
+ # on the overridden element, in this case we ensure that the reverse dependencies
+ # of the replaced element are built against the replacement.
+ ("override-subproject-dep.bst", ["element.txt", "sub.txt", "subdep-override.txt"]),
+ # Override an element in a subproject with a local link element which points to another
+ # element in the same subproject.
+ ("override-subproject-element-with-link.bst", ["element.txt", "sub-alternative.txt", "subdep.txt"]),
+ # Override a link to an element in a subproject with an alternative element
+ # in the same subproject.
+ ("override-subproject-element-using-link.bst", ["element.txt", "sub-alternative.txt", "subdep.txt"]),
+ # Override an element in a nested subsubproject, where the intermediate project also overrides
+ # the same element
+ ("override-subsubproject.bst", ["element.txt", "subsub.txt", "subdep-override.txt"]),
+ ],
+ ids=["element-with-deps", "dependency-of-element", "with-link", "using-link", "priority",],
+)
+def test_override_element(cli, tmpdir, datafiles, target, expected):
+ project = os.path.join(str(datafiles), "override-element")
+ 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 file(s)
+ for expect in expected:
+ assert os.path.exists(os.path.join(checkoutdir, expect))
+
+
+#
+# Test overridding junctions
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
@@ -420,7 +457,7 @@ def test_full_path_not_found(cli, tmpdir, datafiles, target, provenance):
"override-subproject-with-subsubproject",
],
)
-def test_overrides(cli, tmpdir, datafiles, target, expected):
+def test_override_junction(cli, tmpdir, datafiles, target, expected):
project = os.path.join(str(datafiles), "overrides")
checkoutdir = os.path.join(str(tmpdir), "checkout")
diff --git a/tests/format/junctions/override-element/element.txt b/tests/format/junctions/override-element/element.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/element.txt
diff --git a/tests/format/junctions/override-element/override-subproject-dep.bst b/tests/format/junctions/override-element/override-subproject-dep.bst
new file mode 100644
index 000000000..814afc1d4
--- /dev/null
+++ b/tests/format/junctions/override-element/override-subproject-dep.bst
@@ -0,0 +1,11 @@
+kind: import
+sources:
+- kind: local
+ path: element.txt
+
+#
+# Depend on the subproject using a junction which overrides the dependency
+# of the element we're depending on.
+#
+depends:
+- subproject-override-dep.bst:subelement.bst
diff --git a/tests/format/junctions/override-element/override-subproject-element-using-link.bst b/tests/format/junctions/override-element/override-subproject-element-using-link.bst
new file mode 100644
index 000000000..ed196f568
--- /dev/null
+++ b/tests/format/junctions/override-element/override-subproject-element-using-link.bst
@@ -0,0 +1,12 @@
+kind: import
+sources:
+- kind: local
+ path: element.txt
+
+#
+# Depend on the subproject using a junction which overrides the
+# element we're depending on, using a link to address the element
+# we are overridding.
+#
+depends:
+- subproject-override-element-using-link.bst:subelement.bst
diff --git a/tests/format/junctions/override-element/override-subproject-element-with-link.bst b/tests/format/junctions/override-element/override-subproject-element-with-link.bst
new file mode 100644
index 000000000..d58a59bf1
--- /dev/null
+++ b/tests/format/junctions/override-element/override-subproject-element-with-link.bst
@@ -0,0 +1,12 @@
+kind: import
+sources:
+- kind: local
+ path: element.txt
+
+#
+# Depend on the subproject using a junction which overrides the
+# element we're depending on, with a local link to an alternative
+# element in that same subproject.
+#
+depends:
+- subproject-override-element-with-link.bst:subelement.bst
diff --git a/tests/format/junctions/override-element/override-subproject-element.bst b/tests/format/junctions/override-element/override-subproject-element.bst
new file mode 100644
index 000000000..74782110a
--- /dev/null
+++ b/tests/format/junctions/override-element/override-subproject-element.bst
@@ -0,0 +1,11 @@
+kind: import
+sources:
+- kind: local
+ path: element.txt
+
+#
+# Depend on the subproject using a junction which overrides the
+# element we're depending on.
+#
+depends:
+- subproject-override-element.bst:subelement.bst
diff --git a/tests/format/junctions/override-element/override-subsubproject.bst b/tests/format/junctions/override-element/override-subsubproject.bst
new file mode 100644
index 000000000..8d26dc19d
--- /dev/null
+++ b/tests/format/junctions/override-element/override-subsubproject.bst
@@ -0,0 +1,12 @@
+kind: import
+sources:
+- kind: local
+ path: element.txt
+
+#
+# Depend on the subsubproject element, and override it's dependency, the intermediate
+# project also overrides the same subsubproject element, and this test ensures
+# that the toplevel override takes precedence.
+#
+depends:
+- subproject-override-subsubproject-element.bst:subsubproject-override-dep.bst:subsubelement.bst
diff --git a/tests/format/junctions/override-element/project.conf b/tests/format/junctions/override-element/project.conf
new file mode 100644
index 000000000..20636c446
--- /dev/null
+++ b/tests/format/junctions/override-element/project.conf
@@ -0,0 +1,2 @@
+name: test
+min-version: 2.0
diff --git a/tests/format/junctions/override-element/subdep-override.bst b/tests/format/junctions/override-element/subdep-override.bst
new file mode 100644
index 000000000..93d4a5402
--- /dev/null
+++ b/tests/format/junctions/override-element/subdep-override.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+ path: subdep-override.txt
diff --git a/tests/format/junctions/override-element/subdep-override.txt b/tests/format/junctions/override-element/subdep-override.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subdep-override.txt
diff --git a/tests/format/junctions/override-element/subelement-alternative-link.bst b/tests/format/junctions/override-element/subelement-alternative-link.bst
new file mode 100644
index 000000000..bd43d0750
--- /dev/null
+++ b/tests/format/junctions/override-element/subelement-alternative-link.bst
@@ -0,0 +1,8 @@
+kind: link
+
+#
+# This link replaces subelement.bst in the same project, use the
+# same junction to access the alternative in the subproject.
+#
+config:
+ target: subproject-override-element-with-link.bst:subelement-alternative.bst
diff --git a/tests/format/junctions/override-element/subelement-override.bst b/tests/format/junctions/override-element/subelement-override.bst
new file mode 100644
index 000000000..99715e098
--- /dev/null
+++ b/tests/format/junctions/override-element/subelement-override.bst
@@ -0,0 +1,14 @@
+kind: import
+sources:
+- kind: local
+ path: subelement-override.txt
+
+# We override the subproject direct dependency with this element,
+# and this element explicitly depends on it's dependency in that
+# subproject.
+#
+# The dependency we depend on happens to be the same dependency
+# as the element we are overridding.
+#
+depends:
+- subproject-override-element.bst:subdependency.bst
diff --git a/tests/format/junctions/override-element/subelement-override.txt b/tests/format/junctions/override-element/subelement-override.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subelement-override.txt
diff --git a/tests/format/junctions/override-element/subproject-override-dep.bst b/tests/format/junctions/override-element/subproject-override-dep.bst
new file mode 100644
index 000000000..f39b7393b
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject-override-dep.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subproject
+
+config:
+ overrides:
+ subdependency.bst: subdep-override.bst
diff --git a/tests/format/junctions/override-element/subproject-override-element-using-link.bst b/tests/format/junctions/override-element/subproject-override-element-using-link.bst
new file mode 100644
index 000000000..f4d36d7ea
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject-override-element-using-link.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subproject
+
+config:
+ overrides:
+ subelement-link.bst: subproject-override-element-using-link.bst:subelement-alternative.bst
diff --git a/tests/format/junctions/override-element/subproject-override-element-with-link.bst b/tests/format/junctions/override-element/subproject-override-element-with-link.bst
new file mode 100644
index 000000000..1a8a5fc53
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject-override-element-with-link.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subproject
+
+config:
+ overrides:
+ subelement.bst: subelement-alternative-link.bst
diff --git a/tests/format/junctions/override-element/subproject-override-element.bst b/tests/format/junctions/override-element/subproject-override-element.bst
new file mode 100644
index 000000000..cc9fa80a4
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject-override-element.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subproject
+
+config:
+ overrides:
+ subelement.bst: subelement-override.bst
diff --git a/tests/format/junctions/override-element/subproject-override-subsubproject-element.bst b/tests/format/junctions/override-element/subproject-override-subsubproject-element.bst
new file mode 100644
index 000000000..be8b900ad
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject-override-subsubproject-element.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subproject
+
+config:
+ overrides:
+ subsubproject-override-dep.bst:subsubdependency.bst: subdep-override.bst
diff --git a/tests/format/junctions/override-element/subproject/project.conf b/tests/format/junctions/override-element/subproject/project.conf
new file mode 100644
index 000000000..39a53e2ab
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/project.conf
@@ -0,0 +1,2 @@
+name: subtest
+min-version: 2.0
diff --git a/tests/format/junctions/override-element/subproject/sub-alternative.txt b/tests/format/junctions/override-element/subproject/sub-alternative.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/sub-alternative.txt
diff --git a/tests/format/junctions/override-element/subproject/sub.txt b/tests/format/junctions/override-element/subproject/sub.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/sub.txt
diff --git a/tests/format/junctions/override-element/subproject/subdep.txt b/tests/format/junctions/override-element/subproject/subdep.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subdep.txt
diff --git a/tests/format/junctions/override-element/subproject/subdependency.bst b/tests/format/junctions/override-element/subproject/subdependency.bst
new file mode 100644
index 000000000..59e549210
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subdependency.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+ path: subdep.txt
diff --git a/tests/format/junctions/override-element/subproject/subelement-alternative.bst b/tests/format/junctions/override-element/subproject/subelement-alternative.bst
new file mode 100644
index 000000000..51a063799
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subelement-alternative.bst
@@ -0,0 +1,7 @@
+kind: import
+sources:
+- kind: local
+ path: sub-alternative.txt
+
+depends:
+- subdependency.bst
diff --git a/tests/format/junctions/override-element/subproject/subelement-link.bst b/tests/format/junctions/override-element/subproject/subelement-link.bst
new file mode 100644
index 000000000..874969cee
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subelement-link.bst
@@ -0,0 +1,8 @@
+kind: link
+
+#
+# A link to the subelement, used by the override in
+# the subproject-override-element-using-link.bst junction
+#
+config:
+ target: subelement.bst
diff --git a/tests/format/junctions/override-element/subproject/subelement.bst b/tests/format/junctions/override-element/subproject/subelement.bst
new file mode 100644
index 000000000..aff3b3f6e
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subelement.bst
@@ -0,0 +1,7 @@
+kind: import
+sources:
+- kind: local
+ path: sub.txt
+
+depends:
+- subdependency.bst
diff --git a/tests/format/junctions/override-element/subproject/subsubdep-override.bst b/tests/format/junctions/override-element/subproject/subsubdep-override.bst
new file mode 100644
index 000000000..9ab11a2f8
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubdep-override.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+ path: subsubdep-override.txt
diff --git a/tests/format/junctions/override-element/subproject/subsubdep-override.txt b/tests/format/junctions/override-element/subproject/subsubdep-override.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubdep-override.txt
diff --git a/tests/format/junctions/override-element/subproject/subsubproject-override-dep.bst b/tests/format/junctions/override-element/subproject/subsubproject-override-dep.bst
new file mode 100644
index 000000000..6b2379d7c
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject-override-dep.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+ path: subsubproject
+
+config:
+ overrides:
+ subsubdependency.bst: subsubdep-override.bst
diff --git a/tests/format/junctions/override-element/subproject/subsubproject/project.conf b/tests/format/junctions/override-element/subproject/subsubproject/project.conf
new file mode 100644
index 000000000..d11bcbb30
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject/project.conf
@@ -0,0 +1,2 @@
+name: subsubtest
+min-version: 2.0
diff --git a/tests/format/junctions/override-element/subproject/subsubproject/subsub.txt b/tests/format/junctions/override-element/subproject/subsubproject/subsub.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject/subsub.txt
diff --git a/tests/format/junctions/override-element/subproject/subsubproject/subsubdep.txt b/tests/format/junctions/override-element/subproject/subsubproject/subsubdep.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject/subsubdep.txt
diff --git a/tests/format/junctions/override-element/subproject/subsubproject/subsubdependency.bst b/tests/format/junctions/override-element/subproject/subsubproject/subsubdependency.bst
new file mode 100644
index 000000000..d235791b5
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject/subsubdependency.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+ path: subsubdep.txt
diff --git a/tests/format/junctions/override-element/subproject/subsubproject/subsubelement.bst b/tests/format/junctions/override-element/subproject/subsubproject/subsubelement.bst
new file mode 100644
index 000000000..087a562b6
--- /dev/null
+++ b/tests/format/junctions/override-element/subproject/subsubproject/subsubelement.bst
@@ -0,0 +1,7 @@
+kind: import
+sources:
+- kind: local
+ path: subsub.txt
+
+depends:
+- subsubdependency.bst