Skip to content

Commit 12b11f8

Browse files
authored
Merge pull request #2097 from apache/jbilleter/links
Merge duplicate dependencies via link elements
2 parents a5072d4 + e90c246 commit 12b11f8

File tree

9 files changed

+56
-7
lines changed

9 files changed

+56
-7
lines changed

src/buildstream/_loader/loadelement.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ cdef class Dependency:
202202
# Args:
203203
# other (Dependency): The dependency to merge into this one
204204
#
205-
cdef merge(self, Dependency other):
205+
cpdef merge(self, Dependency other):
206206
self.dep_type = self.dep_type | other.dep_type
207207
self.strict = self.strict or other.strict
208208

src/buildstream/_loader/loader.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ def _load_file(self, filename, provenance_node, *, load_subprojects=True):
495495
# The loader queue is a stack of tuples
496496
# [0] is the LoadElement instance
497497
# [1] is a stack of Dependency objects to load
498-
# [2] is a list of dependency names used to warn when all deps are loaded
499-
loader_queue = [(top_element, list(reversed(dependencies)), [])]
498+
# [2] is a Dict[LoadElement, Dependency] of loaded dependencies
499+
loader_queue = [(top_element, list(reversed(dependencies)), {})]
500500

501501
# Load all dependency files for the new LoadElement
502502
while loader_queue:
@@ -505,8 +505,6 @@ def _load_file(self, filename, provenance_node, *, load_subprojects=True):
505505

506506
# Process the first dependency of the last loaded element
507507
dep = current_element[1].pop()
508-
# And record its name for checking later
509-
current_element[2].append(dep.name)
510508

511509
if dep.junction:
512510
loader = self.get_loader(dep.junction, dep.node)
@@ -529,7 +527,7 @@ def _load_file(self, filename, provenance_node, *, load_subprojects=True):
529527
dep_element.mark_fully_loaded()
530528

531529
dep_deps = extract_depends_from_node(dep_element.node)
532-
loader_queue.append((dep_element, list(reversed(dep_deps)), []))
530+
loader_queue.append((dep_element, list(reversed(dep_deps)), {}))
533531

534532
# Pylint is not very happy about Cython and can't understand 'node' is a 'MappingNode'
535533
if dep_element.node.get_str(Symbol.KIND) == "junction": # pylint: disable=no-member
@@ -542,7 +540,15 @@ def _load_file(self, filename, provenance_node, *, load_subprojects=True):
542540
# LoadElement on the dependency and append the dependency to the owning
543541
# LoadElement dependency list.
544542
dep.set_element(dep_element)
545-
current_element[0].dependencies.append(dep) # pylint: disable=no-member
543+
544+
dep_dict = current_element[2]
545+
if dep.element in dep_dict:
546+
# Duplicate LoadElement in dependency list, this can happen if a dependency is
547+
# a link element that points to an element that is already a dependency.
548+
dep_dict[dep.element].merge(dep)
549+
else:
550+
current_element[0].dependencies.append(dep) # pylint: disable=no-member
551+
dep_dict[dep.element] = dep
546552
else:
547553
# And pop the element off the queue
548554
loader_queue.pop()

tests/format/link.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,23 @@ def test_cross_link_junction_include(cli, tmpdir, datafiles):
198198
# Read back some of our project defaults from the env
199199
variables = _yaml.load_data(result.output)
200200
assert variables.get_str("test") == "the test"
201+
202+
203+
#
204+
# Test two links to the same element, both links are dependencies of the build target.
205+
#
206+
@pytest.mark.datafiles(DATA_DIR)
207+
def test_multiple_links_same_target(cli, tmpdir, datafiles):
208+
project = os.path.join(str(datafiles), "multiple-links-same-target")
209+
checkoutdir = os.path.join(str(tmpdir), "checkout")
210+
211+
target = "target.bst"
212+
213+
# Build, checkout
214+
result = cli.run(project=project, args=["build", target])
215+
result.assert_success()
216+
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
217+
result.assert_success()
218+
219+
# Check that the checkout contains the expected files from sub-sub-project
220+
assert os.path.exists(os.path.join(checkoutdir, "hello.txt"))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: link
2+
3+
config:
4+
target: hello.bst
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: link
2+
3+
config:
4+
target: hello.bst
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: import
2+
3+
sources:
4+
- kind: local
5+
path: files/hello.txt
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: stack
2+
3+
depends:
4+
- hello-link-1.bst
5+
- hello-link-2.bst
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: multiple-links-single-target
2+
min-version: 2.0
3+
4+
element-path: elements

0 commit comments

Comments
 (0)