Skip to content

Commit 2f5b71c

Browse files
committed
fix(emit): a plan that builds nothing writes no link specs
prepare_build ran `g++ -dumpspecs` to write mcpp-clean-link.specs under plan_only. The file is read only by the link line, so the planning pass neither needs it nor should run the driver for it, and the compile arguments are the same without it (e2e 688 F still compares them with configure-only's). Found by an independent review of #639. e2e 688 criterion L measures what SPEC-005 R2.2 states: the fresh work directory holds the resolution record and no object, BMI or link specs, and a home whose build cache is empty stays without objects or BMIs while its document lists the std unit. Its control leg runs configure-only on a copy in the same home and sees the std module compiled. R2.2 now says what is true: nothing is compiled, and the toolchain is still queried as for `mcpp build`. Refs #636
1 parent db74e78 commit 2f5b71c

4 files changed

Lines changed: 68 additions & 8 deletions

File tree

.agents/docs/2026-09-14-636-build-database-and-the-latest-xlings.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,10 +387,14 @@ disagree about `import std`.
387387
source when run in its `work-directory`.
388388
5. A project with a syntax error, and a project never built, both produce a
389389
document.
390-
6. With mcpp's own tree as input (it has no build program), no compiler process
391-
starts, on a cold std cache and on a warm one; a driver wrapper that fails
392-
when invoked is the probe. The wall time on a warm cache is of the order of
393-
`--configure-only`.
390+
6. Nothing is compiled and no link input is written, on a cold std cache and on
391+
a warm one: the planning pass's work directory holds no object, BMI or
392+
`mcpp-clean-link.specs`, and a home whose build cache is empty gains no object
393+
or BMI, while `--configure-only` in the same home compiles the std module (the
394+
control). The driver still answers the queries toolchain resolution makes
395+
(`--version`, `-dumpmachine`, `-print-sysroot`), as it does for `mcpp build`,
396+
so a driver wrapper that fails whenever it is invoked cannot be the probe.
397+
The wall time on a warm cache is of the order of `--configure-only`.
394398
7. `tests/unit/test_wire.cpp` pins the kind's key set; `--format ndjson` exits 2
395399
with empty stdout.
396400

@@ -511,7 +515,14 @@ on another repository, and it is small.
511515
`~/.xlings` shim as the xlings binary: nothing was vendored and the test
512516
stopped before either criterion. It now plans in a home with its own
513517
configuration.
514-
5. **The swept-payload fingerprint.** The first xlings rendering flagged any
518+
5. **A link input written by a plan that links nothing.** The independent review
519+
of #639 found `prepare_build` running `g++ -dumpspecs` to write
520+
`mcpp-clean-link.specs` under `plan_only`. Criterion 6 as first written
521+
(a driver wrapper that fails when invoked) could not have caught it: toolchain
522+
resolution queries the driver on every plan. The call is now skipped under
523+
`plan_only`, the link specs being read only by the link line, and e2e 688
524+
criterion L measures the work directory and a cold home instead.
525+
6. **The swept-payload fingerprint.** The first xlings rendering flagged any
515526
top-level file with a download extension or a `.meta` name, so a package
516527
shipping `setup.exe` would be reinstalled on every `--fix`. The fingerprint
517528
is now a zero-length `<name>.lock` whose `<name>` is a sibling, has a download

docs/specs/build-database.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ Database 定义,本规范不重复它们的字段定义,只规定 mcpp 作为生
4444
- **R2.1** 命令**禁止**写入工程目录,即根包、工作区成员与 path 依赖的源码树。规划
4545
写入 `$MCPP_HOME/cache/build-database/<key>`,`<key>` 由工程根与成员决定。该目录
4646
是缓存,可以随时删除。**已实现**
47-
- **R2.2** 标准库模块被描述而不被编译。对没有构建程序的工程,命令不启动任何编译器。
48-
**已实现**
47+
- **R2.2** 命令不编译:标准库模块被描述而不被编译,也不生成只供链接使用的输入(GCC 的
48+
`mcpp-clean-link.specs`)。工具链照常被查询(版本、目标三元组、sysroot 等),与
49+
`mcpp build` 相同;对没有构建程序的工程,驱动只为这些查询运行。**已实现**
4950
- **R2.3** `mcpp.lock` 从工程根读取,从不写回。规划得出的解析与工程中的锁不一致,
5051
或工程中没有锁而规划会写出一份时,输出警告 `MCPP_LOCK_WOULD_CHANGE`**已实现**
5152
- **R2.4** 根包 `[build] generated_files` 中缺失或内容与声明不一致的文件不被写入,

src/build/prepare.cppm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11895,7 +11895,10 @@ prepare_build(bool print_fingerprint,
1189511895
// moved in — an earlier assignment was silently overwritten by that move,
1189611896
// which produced a generated file that nothing ever passed to the driver.
1189711897
// Generated here rather than in compute_flags, which runs twice per build.
11898-
if (tc->compiler == mcpp::toolchain::CompilerId::GCC)
11898+
// A link input only: a plan that builds nothing (`plan_only`) neither reads
11899+
// it nor runs the driver to produce it, and its compile arguments are the
11900+
// same without it.
11901+
if (tc->compiler == mcpp::toolchain::CompilerId::GCC && !overrides.plan_only)
1189911902
ctx.plan.gccCleanSpecs = mcpp::toolchain::write_clean_link_specs(
1190011903
tc->binaryPath, ctx.outputDir);
1190111904

tests/e2e/688_emit_build_database.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
# the units bypass with `--no-default-config`.
3030
# K. The discovered test is scanned as a package source is: the imports inside
3131
# its comment and its raw string are not in `requires`.
32+
# L. The planning pass compiles nothing and writes no link input: its work
33+
# directory, fresh for this project, holds the resolution record and no
34+
# object, BMI or `mcpp-clean-link.specs`; in a home whose std cache is cold
35+
# the document still lists the std unit and the home gains no object or
36+
# BMI. The control leg at the end runs `build --configure-only` on a copy
37+
# in that home and sees the std module compiled there.
3238
set -e
3339

3440
TMP=$(mktemp -d) # the measured tree: the project and its dev-dependency
@@ -262,6 +268,34 @@ norm = obj.replace("\\", "/")
262268
print(obj[: norm.index("/target/")])
263269
' "$OUT/env.json")
264270

271+
# ── L ──────────────────────────────────────────────────────────────────────
272+
[ -n "$(find "$WORK_DIR" -type f -name resolution.json 2>/dev/null)" ] \
273+
|| fail "L: no resolution record under the work directory $WORK_DIR"
274+
written=$(find "$WORK_DIR" -type f \( -name '*.o' -o -name '*.obj' -o -name '*.gcm' \
275+
-o -name '*.pcm' -o -name '*.ifc' -o -name 'mcpp-clean-link.specs' \) | head -5)
276+
[ -z "$written" ] || fail "L: the planning pass wrote compile or link outputs: $written"
277+
278+
# A home with the machine's toolchain payloads and an empty build cache.
279+
COLD="$OUT/cold-home"
280+
compiled_in_cold_home() {
281+
find "$COLD" -path "$COLD/registry" -prune -o -type f \( -name '*.o' -o -name '*.obj' \
282+
-o -name '*.gcm' -o -name '*.pcm' -o -name '*.ifc' \) -print | head -5
283+
}
284+
(
285+
export MCPP_HOME="$COLD" MCPP_OFFLINE=1
286+
source "$HERE/_inherit_toolchain.sh"
287+
"$MCPP" emit build-database --format json > "$OUT/cold.json" 2> "$OUT/cold.err"
288+
) || fail "L: emit in a home with a cold std cache" "$OUT/cold.err"
289+
"$PY" - "$OUT/cold.json" <<'EOF' || fail "L: the cold-home document" "$OUT/cold.json"
290+
import json, sys
291+
db = json.load(open(sys.argv[1]))["data"]["database"]
292+
std = [u for s in db["sets"] if s["name"] == "mcpp:std" for u in s["translation-units"] if "std" in u["provides"]]
293+
assert len(std) == 1, db["sets"]
294+
EOF
295+
written=$(compiled_in_cold_home)
296+
[ -z "$written" ] || fail "L: emit compiled into a cold home: $written" "$OUT/cold.err"
297+
echo "ok: L, the planning pass wrote no object, BMI or link input, and a cold std cache stays cold"
298+
265299
# ── F (against configure-only) and the control leg of B ───────────────────
266300
"$MCPP" build --configure-only > "$OUT/conf.out" 2>&1 || fail "F: configure-only" "$OUT/conf.out"
267301
[ "$(tree_digest)" != "$before" ] || fail "B control: configure-only did not change the tree"
@@ -288,3 +322,14 @@ for f in e:
288322
assert e[f] == w[f], (f, e[f], w[f])
289323
EOF
290324
echo "ok: F, the arguments are configure-only's; B control, configure-only writes the project"
325+
326+
# ── the control leg of L ──────────────────────────────────────────────────
327+
mkdir -p "$OUT/copy"
328+
cp -R "$TMP/hello" "$TMP/devkit" "$OUT/copy/"
329+
rm -rf "$OUT/copy/hello/target" "$OUT/copy/hello/compile_commands.json"
330+
(
331+
export MCPP_HOME="$COLD" MCPP_OFFLINE=1
332+
cd "$OUT/copy/hello" && "$MCPP" build --configure-only > "$OUT/cold-conf.out" 2>&1
333+
) || fail "L control: configure-only in the cold home" "$OUT/cold-conf.out"
334+
[ -n "$(compiled_in_cold_home)" ] || fail "L control: configure-only compiled nothing into the home" "$OUT/cold-conf.out"
335+
echo "ok: L control, configure-only compiles the std module into the same home"

0 commit comments

Comments
 (0)