Skip to content

Commit ad8adbb

Browse files
committed
The first thing the new refusal caught was this repository's own fixtures
Three e2e fixtures built a `build.mcpp` that read `mcpp::device_sources()`, printed it, and compiled nothing. They were asserting on the env contract, which is a real thing to assert on -- and they were modelling a project whose device files compile to nothing, which is exactly the defect the new refusal exists to catch. So the refusal caught them, and it was right to. 606, 609 and 613 now declare a `check` action per device source, which is the edge a real rule package declares, and go on asserting on the variable exactly as before. The action's input has to be ABSOLUTE: `device_sources()` is package-root-relative and an action does not run in the package root, which the first attempt got wrong and `cp: cannot stat 'shaders/s.tesc'` said plainly. EXAMPLE 05 PINS ITS TOOLCHAIN, and this is a different failure that surfaced in the same run. `mcpp pack` names its output directory after the ABI tag, and the consumer beside it names that directory literally -- so the pair only agrees on a machine whose DEFAULT toolchain is the one that produced the tag. Measured: a runner whose sandbox had been left with an LLVM default produced `mathkit-0.1.0-x86_64-linux-gnu-clang22-libcxx22-c++23` while the consumer named the `gcc16-libstdcxx16` one, and the example that had passed for months failed without anything in it changing. An example that hard-codes an ABI tag has to name the toolchain that produces it. That is not a workaround for CI: a packed artifact IS specific to the ABI it was built against, which is the entire subject of that example, so leaving the toolchain implicit was the inconsistency.
1 parent 7a8cc01 commit ad8adbb

5 files changed

Lines changed: 115 additions & 0 deletions

File tree

‎examples/05-lib-distribution/consumer/mcpp.toml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,25 @@ license = "Apache-2.0"
77
# A packed library is an ordinary package: a path dependency, a git dependency,
88
# a downloaded archive and an index entry all reach it the same way. Point this
99
# at whatever `mcpp pack mathkit` left under ../producer/target/dist/.
10+
11+
# THE TOOLCHAIN IS PINNED BECAUSE THE CONSUMER NAMES THE TAG.
12+
#
13+
# `mcpp pack` writes a directory whose name carries the ABI tag --
14+
# `mathkit-0.1.0-x86_64-linux-gnu-gcc16-libstdcxx16-c++23` -- and the consumer
15+
# beside this one names that directory literally, which is what the README
16+
# tells a reader to do. The tag therefore depends on which toolchain built it,
17+
# and without this pin the pair only agrees on a machine whose DEFAULT
18+
# toolchain happens to be gcc 16. Measured: a CI runner whose sandbox had been
19+
# left with an LLVM default produced
20+
# `mathkit-0.1.0-x86_64-linux-gnu-clang22-libcxx22-c++23`, and the consumer
21+
# named a directory that did not exist.
22+
#
23+
# An example that hard-codes an ABI tag has to name the toolchain that
24+
# produces it. That is not a workaround: a packed artifact IS specific to the
25+
# ABI it was built against, which is the whole subject of this example.
26+
[toolchain]
27+
default = "gcc@16.1.0"
28+
1029
[dependencies]
1130
mathkit = { path = "../producer/target/dist/mathkit-0.1.0-x86_64-linux-gnu-gcc16-libstdcxx16-c++23" }
1231

‎examples/05-lib-distribution/producer/mcpp.toml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ version = "0.1.0"
44
description = "Demo: shipping a prebuilt library with both a header and a module interface"
55
license = "Apache-2.0"
66

7+
8+
# THE TOOLCHAIN IS PINNED BECAUSE THE CONSUMER NAMES THE TAG.
9+
#
10+
# `mcpp pack` writes a directory whose name carries the ABI tag --
11+
# `mathkit-0.1.0-x86_64-linux-gnu-gcc16-libstdcxx16-c++23` -- and the consumer
12+
# beside this one names that directory literally, which is what the README
13+
# tells a reader to do. The tag therefore depends on which toolchain built it,
14+
# and without this pin the pair only agrees on a machine whose DEFAULT
15+
# toolchain happens to be gcc 16. Measured: a CI runner whose sandbox had been
16+
# left with an LLVM default produced
17+
# `mathkit-0.1.0-x86_64-linux-gnu-clang22-libcxx22-c++23`, and the consumer
18+
# named a directory that did not exist.
19+
#
20+
# An example that hard-codes an ABI tag has to name the toolchain that
21+
# produces it. That is not a workaround: a packed artifact IS specific to the
22+
# ABI it was built against, which is the whole subject of this example.
23+
[toolchain]
24+
default = "gcc@16.1.0"
25+
726
[build]
827
sources = ["src/*.cppm", "src/*.cpp", "src/*.c"]
928
# The public headers. Published whole — see include/mathkit_c.h.

‎tests/e2e/606_constrained_source_globs_narrow_to_the_build.sh‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,36 @@ kind = "bin"
4949
main = "src/main.cpp"
5050
EOF
5151
cat > build.mcpp <<'EOF'
52+
import std;
5253
import mcpp;
54+
55+
// A `check` action per device source, and the reason it is here rather than a
56+
// bare read of the variable: mcpp refuses a device source that reached no
57+
// action (2026.9.6.5). A build program that only LOOKS at
58+
// `mcpp::device_sources()` models a project whose device files compile to
59+
// nothing, which is the defect that refusal exists to catch -- so this fixture
60+
// declares the edge a real rule package would declare, and asserts on the
61+
// variable as before.
5362
int main() {
5463
const char* d = mcpp::device_sources();
64+
std::string flat(d);
65+
std::size_t n = 0, start = 0;
66+
while (start <= flat.size()) {
67+
auto nl = flat.find('\n', start);
68+
auto one = flat.substr(start, nl == std::string::npos ? flat.size() - start : nl - start);
69+
start = nl == std::string::npos ? flat.size() + 1 : nl + 1;
70+
if (one.empty()) continue;
71+
auto stamp = std::string(mcpp::out_dir()) + "/dev-" + std::to_string(n++) + ".stamp";
72+
mcpp::action a;
73+
a.id = "seen";
74+
a.role = "check";
75+
a.description = "account for a device source";
76+
auto abs = std::string(mcpp::manifest_dir()) + "/" + one;
77+
a.arg("cp").arg(abs.c_str()).arg(stamp.c_str());
78+
a.input(abs.c_str());
79+
a.output(stamp.c_str());
80+
a.submit();
81+
}
5582
mcpp::warning((*d ? d : "(no device sources)"));
5683
return 0;
5784
}

‎tests/e2e/609_shader_sources_are_device_sources.sh‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,33 @@ mkdir -p shaders
3939
cat > build.mcpp <<'EOF'
4040
import std;
4141
import mcpp;
42+
43+
// A `check` action per device source, and the reason it is here rather than a
44+
// bare read of the variable: mcpp refuses a device source that reached no
45+
// action (2026.9.6.5). A build program that only LOOKS at
46+
// `mcpp::device_sources()` models a project whose device files compile to
47+
// nothing, which is the defect that refusal exists to catch -- so this fixture
48+
// declares the edge a real rule package would declare, and asserts on the
49+
// variable as before.
4250
int main() {
4351
std::string flat(mcpp::device_sources());
52+
std::size_t n = 0, start = 0;
53+
while (start <= flat.size()) {
54+
auto nl = flat.find('\n', start);
55+
auto one = flat.substr(start, nl == std::string::npos ? flat.size() - start : nl - start);
56+
start = nl == std::string::npos ? flat.size() + 1 : nl + 1;
57+
if (one.empty()) continue;
58+
auto stamp = std::string(mcpp::out_dir()) + "/dev-" + std::to_string(n++) + ".stamp";
59+
mcpp::action a;
60+
a.id = "seen";
61+
a.role = "check";
62+
a.description = "account for a device source";
63+
auto abs = std::string(mcpp::manifest_dir()) + "/" + one;
64+
a.arg("cp").arg(abs.c_str()).arg(stamp.c_str());
65+
a.input(abs.c_str());
66+
a.output(stamp.c_str());
67+
a.submit();
68+
}
4469
for (auto& c : flat) if (c == '\n') c = ' ';
4570
mcpp::warning(("device=[" + flat + "]").c_str());
4671
return 0;

‎tests/e2e/613_sycl_sources_are_device_sources.sh‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,33 @@ mkdir -p src/kernels
3434
cat > build.mcpp <<'EOF2'
3535
import std;
3636
import mcpp;
37+
38+
// A `check` action per device source, and the reason it is here rather than a
39+
// bare read of the variable: mcpp refuses a device source that reached no
40+
// action (2026.9.6.5). A build program that only LOOKS at
41+
// `mcpp::device_sources()` models a project whose device files compile to
42+
// nothing, which is the defect that refusal exists to catch -- so this fixture
43+
// declares the edge a real rule package would declare, and asserts on the
44+
// variable as before.
3745
int main() {
3846
std::string flat(mcpp::device_sources());
47+
std::size_t n = 0, start = 0;
48+
while (start <= flat.size()) {
49+
auto nl = flat.find('\n', start);
50+
auto one = flat.substr(start, nl == std::string::npos ? flat.size() - start : nl - start);
51+
start = nl == std::string::npos ? flat.size() + 1 : nl + 1;
52+
if (one.empty()) continue;
53+
auto abs = std::string(mcpp::manifest_dir()) + "/" + one;
54+
auto stamp = std::string(mcpp::out_dir()) + "/dev-" + std::to_string(n++) + ".stamp";
55+
mcpp::action a;
56+
a.id = "seen";
57+
a.role = "check";
58+
a.description = "account for a device source";
59+
a.arg("cp").arg(abs.c_str()).arg(stamp.c_str());
60+
a.input(abs.c_str());
61+
a.output(stamp.c_str());
62+
a.submit();
63+
}
3964
for (auto& c : flat) if (c == '\n') c = ' ';
4065
mcpp::warning(("device=[" + flat + "]").c_str());
4166
return 0;

0 commit comments

Comments
 (0)