Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/graph/builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,11 +694,13 @@ auto expand_glob_pattern(
}
auto match_path_sv = pool.get(pup::strip_path_prefix(node_path_sv, build_root_name));
if (glob.matches(match_path_sv)) {
matches.push_back(pool.intern(node_path_sv));
// Stripped, not physical: one path space for both halves (resolve_input_node
// is BuildRoot-first, so this still names the generated node).
matches.push_back(pool.intern(match_path_sv));
}
}

// In-tree builds see a generated file from both sources; it is one input.
// Sorting and deduping across two path spaces would order by where the build tree lives.
std::ranges::sort(matches, {}, [&pool](StringId id) { return pool.get(id); });
matches.erase(std::unique(matches.begin(), matches.end()), matches.end());
for (auto id : matches) {
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/fixtures/glob_mixed_space/Tupfile.fixture
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Placeholder - overwritten by test
: |> true |>
1 change: 1 addition & 0 deletions test/e2e/fixtures/glob_mixed_space/Tupfile.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Project root marker
1 change: 1 addition & 0 deletions test/e2e/fixtures/glob_mixed_space/tup.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test config
94 changes: 94 additions & 0 deletions test/unit/test_e2e.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4522,6 +4522,100 @@ SCENARIO("Editing a rule's recipe does not make it a different rule", "[e2e][ide
}
}

SCENARIO("A glob's %f order does not depend on the build directory's name", "[e2e][glob][pathspace]")
{
GIVEN("a glob matching one generated and one source file, built out-of-tree")
{
auto f = E2EFixture { "glob_mixed_space" };
f.mkdir("a");
f.write_file("a/Tupfile", ": src.in |> cp %f %o |> gen.dat\n: *.dat |> cat %f > %o |> all.txt\n");
f.write_file("a/src.in", "GEN\n");
f.write_file("a/keep.dat", "KEEP\n");

WHEN("built into two differently-named build directories")
{
f.mkdir("AAA");
REQUIRE(f.pup({ "configure", "-B", "AAA" }).success());
REQUIRE(f.build({ "-B", "AAA" }).success());
auto from_aaa = f.read_file("AAA/a/all.txt");

f.mkdir("zz");
REQUIRE(f.pup({ "configure", "-B", "zz" }).success());
REQUIRE(f.build({ "-B", "zz" }).success());
auto from_zz = f.read_file("zz/a/all.txt");

THEN("the artifact is byte-identical")
{
INFO("AAA: " << from_aaa);
INFO("zz: " << from_zz);
REQUIRE(from_aaa == from_zz);
// Pins both halves and the canonical order: equality alone would still
// hold if the filesystem half stopped contributing entirely.
REQUIRE(from_aaa == "GEN\nKEEP\n");
}
}
}
}

SCENARIO("A generated file shadowing a source file is one glob match", "[e2e][glob][pathspace][deviation]")
{
// Upstream tup rejects this project outright ("Attempting to insert 'x.dat' as a generated
// node when it already exists as a different type"). putup accepts it and lets the generated
// node win; issue #194 tracks that deviation. This asserts only the dedup #191 is about --
// one match rather than two -- and is not a claim that shadowing should be legal.
GIVEN("a generated file whose name also exists as a checked-in source")
{
auto f = E2EFixture { "glob_mixed_space" };
f.mkdir("a");
f.write_file("a/Tupfile", ": gen.src |> cp %f %o |> x.dat\n: *.dat |> cat %f > %o |> all.txt\n");
f.write_file("a/gen.src", "FROMRULE\n");
f.write_file("a/x.dat", "FROMSRC\n");
f.mkdir("build");
REQUIRE(f.pup({ "configure", "-B", "build" }).success());

WHEN("built out-of-tree")
{
REQUIRE(f.build({ "-B", "build" }).success());

THEN("it is consumed once, not twice")
{
auto content = f.read_file("build/a/all.txt");
INFO("all.txt: " << content);
REQUIRE(content == "FROMRULE\n");
}
}
}
}

SCENARIO("An exclusion applies to generated glob matches out-of-tree", "[e2e][glob][pathspace]")
{
GIVEN("a glob over generated files with one of them excluded")
{
auto f = E2EFixture { "glob_mixed_space" };
f.write_file("Tupfile",
": keep.in |> cp %f %o |> keep.gen\n"
": skip.in |> cp %f %o |> skip.gen\n"
": *.gen !skip.gen |> echo %f > %o |> out.txt\n");
f.write_file("keep.in", "k\n");
f.write_file("skip.in", "s\n");
f.mkdir("build");
REQUIRE(f.pup({ "configure", "-B", "build" }).success());

WHEN("built out-of-tree")
{
REQUIRE(f.build({ "-B", "build" }).success());

THEN("the excluded file is absent from %f")
{
auto content = f.read_file("build/out.txt");
INFO("out.txt: " << content);
REQUIRE(content.find("keep.gen") != std::string::npos);
REQUIRE(content.find("skip.gen") == std::string::npos);
}
}
}
}

SCENARIO("Dropping one output of a rule removes that output", "[e2e][identity][join][stale]")
{
GIVEN("a built rule that declares two outputs")
Expand Down
Loading