Skip to content

Commit 8052078

Browse files
committed
2026.10.1.1: the version, the release notes, the plan's implementation record, and the sandbox verification script
1 parent a8c2de1 commit 8052078

6 files changed

Lines changed: 349 additions & 6 deletions

File tree

‎.agents/docs/2026-09-30-member-selection-and-build-program-cost-plan.md‎

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
---
22
subject: design
3-
status: active
3+
status: landed
44
---
55

66
# Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)
77

8-
- Status: revision 3, in implementation.
8+
- Status: implemented as 2026.10.1.1. Section 15 records what was built,
9+
what was measured, and where the implementation departs from sections 3 to
10+
6. Revision 3, below, is the plan as it was implemented.
911
- Revision 3 settles the open decisions by their recommendations (D3, D6,
1012
D7), since the review directed that the plan be implemented. It removes
1113
the stage-specific downstream verification from the plan, adds a review
@@ -826,3 +828,99 @@ the sandbox (`mcpp self config --mirror CN`), a script runs:
826828

827829
Each section reports ok, failed or not run separately. The script's result is
828830
posted on #748, #749 and #750, which are then closed.
831+
832+
## 15. Implementation record (2026.10.1.1)
833+
834+
The four tasks of section 13 were implemented on separate branches and merged
835+
into one integration branch in the order T1, T3, T2, T4, followed by an
836+
independent review of the merged code and its corrections.
837+
838+
### 15.1 B0, measured
839+
840+
A virtual workspace of four members, each with a build program that imports
841+
one host module of a path dependency, which imports `std` and `mcpp`; clang
842+
22.1.8, four jobs. Compiler time per step, summed from a trace of the build:
843+
844+
| Step | Before | After, first build | After, second workspace |
845+
|---|---|---|---|
846+
| bundled `mcpp` module | 16 commands, 0.559 s | 4 commands, 0.118 s | none |
847+
| host module | 8 commands, 0.245 s | 2 commands, 0.046 s | 2 commands, 0.054 s |
848+
| `build.mcpp` compile and link | 4 commands, 0.297 s | 0.306 s, concurrent | 0.290 s |
849+
850+
Steps 1 and 3 of F3 are 73% of the compiler time and all of the preparation
851+
outside a program's own compile, which confirms the attribution; the `std`
852+
module is a cache hit. The whole build took 0.89 to 1.22 s before, 0.48 s
853+
after on a cold home and 0.33 to 0.39 s on a warm one. On this machine the
854+
preparation is about 0.2 s per program against #748's 7.8 s on a Windows
855+
runner, so the measurement confirms the shape, not the absolute cost. The
856+
bundled module's `base` flags contain only payload paths below the home, none
857+
of the project's (section 11, item 10).
858+
859+
### 15.2 Departures from the plan
860+
861+
- **Selection.** `--workspace` together with `-p` is refused, naming both,
862+
like the other contradictions of S1; the S1 table did not list it. `mcpp
863+
test --list` over several members keeps its per-member form.
864+
- **The test stream.** Revision 3 kept `duration_ms` as "build+run". The code
865+
of 2026.9.30.2 recorded the run of a test that ran and the build of a
866+
`compile_fail`, and that is the meaning kept; the documentation's
867+
"build+run" is corrected. The build time of a test's own binary is the
868+
added field `build_ms`.
869+
- **The store key (B1).** Besides the inputs of section 4, the key of a
870+
workspace entry includes a digest of the provider package's whole tree, so
871+
that an edit of a header the interface includes is not served a stale BMI.
872+
A tree of more than 4096 files or 64 MiB is keyed for the process only and
873+
loses reuse across commands. The compile code moved from
874+
`hostprogram.cppm` into `src/build/host_module_compile.cppm`, and the store
875+
is `src/build/host_module_store.cppm`.
876+
- **Result lines (R3).** A result line is written by `mcpp::ui::result`,
877+
which the caller chooses; the stream is not inferred from a verb.
878+
- **Pack (K1).**
879+
- A bare `mcpp pack` at a virtual root keeps its behaviour of 2026.9.30.2 and
880+
packs the first member with a program; several members are packed with
881+
`--workspace`, `--exclude` or several `-p`.
882+
- An action belongs to the package that submitted it. A package acts for
883+
itself when it is a packed member, otherwise for the one packed member
884+
whose closure reaches it. A provider that several packed members reach is
885+
refused, because one run of its program cannot stage a tree per member.
886+
- An `--output` that does not exist is created as a directory.
887+
- `member_request` and `workspace_groups` moved into `mcpp.cli.selection`:
888+
an import of `mcpp.cli.cmd_build` from `mcpp.cli.cmd_publish` made GCC
889+
16.1 fail with an internal compiler error.
890+
- A provider's program runs once in each of the two passes a dispatched
891+
format has always had; a shared member that provides nothing runs once.
892+
893+
### 15.3 Review
894+
895+
An independent review of the merged code found no data race, deadlock or
896+
reuse of a stale compile. It found, and the integration corrected:
897+
898+
- a member of a group whose package failed after the group's first failure
899+
was reported without its own diagnostics;
900+
- the `duration_ms` change above;
901+
- a GCC host-module entry that kept the copies of the imported BMIs staged for
902+
its compile, the `std` module's among them;
903+
- the `Finished` breakdown, which added the overlapping compile times of the
904+
concurrent phase; the phase is now counted once, as its wall time, and it
905+
states no warning of its own;
906+
- the announcement of a multi-member test plan, which named the virtual root;
907+
- comments and documentation that no longer described the code.
908+
909+
### 15.4 Verification before the pull request
910+
911+
- Unit tests: 142 passed.
912+
- The e2e scripts added by this plan (852 to 869) pass under clang and, for
913+
those that depend on the family, under GCC.
914+
- The full e2e suite on the integration of T1 to T3, on a machine whose
915+
default toolchain is clang: 473 passed, 19 failed, 61 skipped. The 19 fail
916+
with the same message on 2026.9.30.2; 17 of them pass with GCC selected, and
917+
the other two need an Android NDK or a Windows host.
918+
- The sandbox script `.agents/docs/2026-10-01-member-selection-verify.sh`,
919+
run on the host against the integration binary: every section passes.
920+
Against 2026.9.30.2, every section marked CHANGE fails and every other
921+
section passes.
922+
923+
### 15.5 Open
924+
925+
- #751 (section 8).
926+
- The Windows and macOS paths of B2-0, B1 and R3 are exercised by CI only.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env bash
2+
# Sandbox verification of the release that implements
3+
# .agents/docs/2026-09-30-member-selection-and-build-program-cost-plan.md
4+
# (#748, #749, #750), run against the PUBLISHED release inside an xlings
5+
# sandbox:
6+
#
7+
# B64=$(base64 -w0 .agents/docs/2026-10-01-member-selection-verify.sh)
8+
# xlings subos new v1001 2>/dev/null || true
9+
# xlings subos use v1001 --sandbox --cmd "echo $B64 | base64 -d > /tmp/v.sh && VER=<version> bash /tmp/v.sh"
10+
#
11+
# VER selects the release under test. Running it with VER=2026.9.30.2 is the
12+
# control: every section marked CHANGE must fail there, and every other
13+
# section must pass on both.
14+
#
15+
# Every probe directory is removed at the start of its section, because the
16+
# sandbox's $HOME persists between runs of the same subos. A section that does
17+
# not run is reported as SKIP and counted apart from a pass.
18+
set -u
19+
VER="${VER:?VER=<version under test>}"
20+
W="${W:-$HOME/v1001}"
21+
fails=0; passes=0; skips=0
22+
pass() { echo "PASS $1"; passes=$((passes+1)); }
23+
fail() { echo "FAIL $1"; [ -n "${2:-}" ] && [ -f "$2" ] && tail -20 "$2"; fails=$((fails+1)); }
24+
skip() { echo "SKIP $1"; skips=$((skips+1)); }
25+
26+
# 0. The release under test, from the published channel, with the CN mirror.
27+
if [ -n "${MCPP_OVERRIDE:-}" ]; then
28+
MCPP="$MCPP_OVERRIDE"
29+
else
30+
xlings config --mirror CN >/dev/null 2>&1 || true
31+
xlings update >/dev/null 2>&1 || true
32+
xlings install "mcpp@$VER" -y > /tmp/v1001-install.log 2>&1 || true
33+
MCPP="$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp"
34+
fi
35+
if [ ! -x "$MCPP" ]; then
36+
echo "FATAL: mcpp $VER is not installable from the index"; tail -20 /tmp/v1001-install.log; exit 2
37+
fi
38+
got=$("$MCPP" --version 2>&1 | head -1)
39+
case "$got" in *"$VER"*) pass "0 installed: $got";; *) fail "0 version: $got";; esac
40+
# The sandbox's home is empty, so the mirror is set inside it. A run on a host
41+
# with MCPP_OVERRIDE leaves the host's configuration as it is.
42+
[ -n "${MCPP_OVERRIDE:-}" ] || "$MCPP" self config --mirror CN >/dev/null 2>&1 || true
43+
mkdir -p "$W"
44+
45+
lib_member() { # <dir> <name> <value>: a library member with one module and one test
46+
mkdir -p "$1/src" "$1/tests"
47+
printf '[package]\nname = "%s"\nversion = "0.1.0"\n\n[targets.%s]\nkind = "lib"\n' "$2" "$2" > "$1/mcpp.toml"
48+
printf 'export module %s;\nexport int %s_value() { return %s; }\n' "$2" "$2" "$3" > "$1/src/$2.cppm"
49+
printf 'import %s;\nint main() { return %s_value() == %s ? 0 : 1; }\n' "$2" "$2" "$3" > "$1/tests/test_$2.cpp"
50+
}
51+
52+
# 1. CHANGE (#750): a repeated -p selects every member it names.
53+
rm -rf "$W/s1"; mkdir -p "$W/s1"; cd "$W/s1"
54+
printf '[workspace]\nmembers = ["a", "b", "c"]\n' > mcpp.toml
55+
lib_member a a 1; lib_member b b 2; lib_member c c 3
56+
if "$MCPP" test -p a -p b > s1.log 2>&1 \
57+
&& grep -q 'test_a' s1.log && grep -q 'test_b' s1.log && ! grep -q 'test_c' s1.log; then
58+
pass "1 CHANGE: mcpp test -p a -p b tests a and b, and not c"
59+
else fail "1 CHANGE: a repeated -p" s1.log; fi
60+
61+
# 2. CHANGE: --exclude, and a member name that matches nothing.
62+
cd "$W/s1"
63+
if "$MCPP" test --workspace --exclude c > s2a.log 2>&1 \
64+
&& grep -q 'test_a' s2a.log && grep -q 'test_b' s2a.log && ! grep -q 'test_c' s2a.log; then
65+
pass "2a CHANGE: --workspace --exclude c tests a and b"
66+
else fail "2a CHANGE: --exclude" s2a.log; fi
67+
if "$MCPP" build -p nosuch > s2b.log 2>&1; then fail "2b an unknown member was accepted" s2b.log
68+
elif grep -q 'nosuch' s2b.log; then pass "2b an unknown member is refused by name"
69+
else fail "2b the refusal does not name the member" s2b.log; fi
70+
71+
# 3. CHANGE (R1, R2): run -q writes exactly the program's output, and a
72+
# failed build exits 101.
73+
rm -rf "$W/s3"; mkdir -p "$W/s3/src"; cd "$W/s3"
74+
printf '[package]\nname = "p3"\nversion = "0.1.0"\n\n[targets.p3]\nkind = "bin"\nmain = "src/main.cpp"\n' > mcpp.toml
75+
printf '#include <cstdio>\nint main() { std::puts("OUT"); std::fputs("ERR\\n", stderr); return 0; }\n' > src/main.cpp
76+
"$MCPP" run -q > s3.out 2> s3.err; rc=$?
77+
if [ "$rc" = 0 ] && [ "$(od -c s3.out | head -1)" = "$(printf 'OUT\n' | od -c | head -1)" ]; then
78+
pass "3a CHANGE: run -q writes exactly the program's stdout"
79+
else fail "3a CHANGE: run -q stdout (rc=$rc)" s3.out; fi
80+
printf 'int main() { x }\n' > src/main.cpp
81+
"$MCPP" run -q > s3b.log 2>&1; rc=$?
82+
if [ "$rc" = 101 ]; then pass "3b CHANGE: a run whose build failed exits 101"
83+
else fail "3b CHANGE: exit status $rc for a failed build" s3b.log; fi
84+
85+
# 4. CHANGE (R3): a build narrates on stderr.
86+
cd "$W/s1"
87+
out=$("$MCPP" build --workspace 2>/dev/null)
88+
if [ -z "$out" ] && "$MCPP" build --workspace 2>&1 >/dev/null | grep -q 'Finished'; then
89+
pass "4 CHANGE: status lines are on stderr and stdout is empty"
90+
else fail "4 CHANGE: status stream"; echo "$out" | head -5; fi
91+
92+
# 5. index packages build and run with the release.
93+
rm -rf "$W/s5"; mkdir -p "$W/s5/src"; cd "$W/s5"
94+
printf '[package]\nname = "eco"\nversion = "0.1.0"\n\n[dependencies]\n"compat.zlib" = "*"\n"mcpplibs.cmdline" = "*"\n\n[targets.eco]\nkind = "bin"\nmain = "src/main.cpp"\n' > mcpp.toml
95+
cat > src/main.cpp <<'EOF'
96+
#include <cstdio>
97+
#include <zlib.h>
98+
import mcpplibs.cmdline;
99+
int main() { std::printf("zlib %s\n", zlibVersion()); return 0; }
100+
EOF
101+
if "$MCPP" run > s5.log 2>&1 && grep -q '^zlib ' s5.log; then
102+
pass "5 compat.zlib and mcpplibs.cmdline from the index build and run"
103+
else fail "5 index packages" s5.log; fi
104+
105+
# 6. CHANGE (#748): four members' build programs import one host module of a
106+
# path dependency; the module is compiled once for the four, and the engine's
107+
# own module is kept in the global cache.
108+
rm -rf "$W/s6"; mkdir -p "$W/s6/rules/src"; cd "$W/s6"
109+
printf '[workspace]\nmembers = ["m1", "m2", "m3", "m4"]\n' > mcpp.toml
110+
printf '[package]\nname = "rule6"\nversion = "0.1.0"\n\n[targets.rule6]\nkind = "lib"\n' > rules/mcpp.toml
111+
printf 'export module v6.rules;\nimport mcpp;\nexport void apply(const char* d) { mcpp::define(d); }\n' > rules/src/rule6.cppm
112+
for m in m1 m2 m3 m4; do
113+
mkdir -p $m/src
114+
printf '[package]\nname = "%s"\nversion = "0.1.0"\n\n[build-dependencies]\nrule6 = { path = "../rules", host-module = true }\n\n[targets.%s]\nkind = "bin"\nmain = "src/main.cpp"\n' $m $m > $m/mcpp.toml
115+
printf 'int main() { return 0; }\n' > $m/src/main.cpp
116+
printf 'import mcpp;\nimport v6.rules;\nint main() { apply("V6_%s=1"); return 0; }\n' $m > $m/build.mcpp
117+
done
118+
if MCPP_VERBOSE=1 "$MCPP" build --workspace > s6.log 2>&1; then
119+
compiles=$(grep -cE "host module 'v6\.rules' (precompile|compile):" s6.log || true)
120+
ran=$(grep -cE "^ *build\.mcpp m[1-4] .* ran " s6.log || true)
121+
if [ "$compiles" = 1 ] && [ "$ran" = 4 ]; then
122+
pass "6 CHANGE: one host module compile for four build programs"
123+
else fail "6 CHANGE: $compiles host module compiles for $ran programs" s6.log; fi
124+
else fail "6 the workspace of four build programs did not build" s6.log; fi
125+
126+
# 7. CHANGE (#749): one pack of two program members that share a member with a
127+
# build program: one package per member, and the shared program runs once.
128+
rm -rf "$W/s7"; mkdir -p "$W/s7/core/src"; cd "$W/s7"
129+
printf '[workspace]\nmembers = ["core", "cli", "gui"]\n' > mcpp.toml
130+
printf '[package]\nname = "core"\nversion = "0.1.0"\n\n[targets.core]\nkind = "lib"\n' > core/mcpp.toml
131+
printf 'export module v7core;\nexport int core_answer() { return 42; }\n' > core/src/core.cppm
132+
cat > core/build.mcpp <<'EOF'
133+
import mcpp;
134+
#include <fstream>
135+
#include <string>
136+
int main() {
137+
std::ofstream log(std::string(mcpp::out_dir()) + "/runs.log", std::ios::app);
138+
log << "ran\n";
139+
return 0;
140+
}
141+
EOF
142+
for m in cli gui; do
143+
mkdir -p $m/src
144+
printf '[package]\nname = "%s"\nversion = "0.1.0"\n\n[dependencies]\ncore = { path = "../core" }\n\n[targets.%s]\nkind = "bin"\nmain = "src/main.cpp"\n' $m $m > $m/mcpp.toml
145+
printf '#include <cstdio>\nimport v7core;\nint main() { std::printf("%s %%d\\n", core_answer()); }\n' $m > $m/src/main.cpp
146+
done
147+
if "$MCPP" pack --workspace --format dir > s7.log 2>&1; then
148+
packed=$(grep -cE '^ *Packed ' s7.log || true)
149+
runs=$(find core -name runs.log -exec cat {} + 2>/dev/null | grep -c ran || true)
150+
if [ "$packed" = 2 ] && [ "$runs" = 1 ]; then
151+
pass "7 CHANGE: one pack of two members, and the shared build program ran once"
152+
else fail "7 CHANGE: $packed packages, the shared program ran $runs times" s7.log; fi
153+
else fail "7 CHANGE: mcpp pack --workspace" s7.log; fi
154+
155+
echo "---- $passes passed, $fails failed, $skips skipped"
156+
[ "$fails" = 0 ]

‎.agents/docs/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Records that declare one. Everything else is listed by date below.
3030

3131
### design
3232

33-
- [Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)](2026-09-30-member-selection-and-build-program-cost-plan.md) — active
33+
- [Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)](2026-09-30-member-selection-and-build-program-cost-plan.md) — landed
3434
- [The build's wall time, its progress count, a hang after the build, and #732 and #744: measurements and a remediation plan](2026-09-30-build-wall-time-progress-count-and-hang-plan.md) — landed
3535
- [Build output, revision 3: every package that does work is named, the live display is one line drawn in one write, and a repeated warning is stated once per file](2026-09-30-build-output-refinement-design.md) — landed
3636
- [The workspace as the unit of build: one graph per configuration, one scheduler, product directories, and a reusable graph module](2026-09-29-workspace-build-graph-design.md) — landed
@@ -112,7 +112,7 @@ Records that declare one. Everything else is listed by date below.
112112

113113
### 2026-09
114114

115-
- [Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)](2026-09-30-member-selection-and-build-program-cost-plan.md) — active
115+
- [Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)](2026-09-30-member-selection-and-build-program-cost-plan.md) — landed
116116
- [The build's wall time, its progress count, a hang after the build, and #732 and #744: measurements and a remediation plan](2026-09-30-build-wall-time-progress-count-and-hang-plan.md) — landed
117117
- [Build output, revision 3: every package that does work is named, the live display is one line drawn in one write, and a repeated warning is stated once per file](2026-09-30-build-output-refinement-design.md) — landed
118118
- [The workspace as the unit of build: one graph per configuration, one scheduler, product directories, and a reusable graph module](2026-09-29-workspace-build-graph-design.md) — landed

0 commit comments

Comments
 (0)