Skip to content

Commit 11470f2

Browse files
committed
ci(examples): the criterion for a rebuilt compiler had to be built to isolate it
The device-language step still asserted the previous example: it grepped for `toy_answer() = 42` and edited `rules-toy/tools/toyc.sh`, a shell script that no longer exists. The job failed with "the .toy did not reach the link". Rewriting it turned up a claim I had written from reading the rule rather than from measuring it, and the measurement contradicts it. WHAT THE FIRST REWRITE ASSERTED. Bump the tool package's version, and the artifact follows. That is true, and it does not test what its message said. Falsified by removing `a.input(compiler)` from the rule: the version bump still reached the artifact and the step still passed. The tool's path is on the action's command line, so a new path re-runs the edge whether or not the compiler is also a declared input. WHAT ISOLATES IT. Different bytes at the same path: build a compiler that behaves differently and overwrite the cached binary in place, leaving the command line byte-identical. And both directions, because neither alone is the property -- with the input removed, the artifact followed the overwrite and then stopped following the restore. With the input present the step exits 0; with it removed, 1. The step now covers four things: the `.toy` reaches the link; the emitted loop runs (`gcd(1071, 462) = 21`, a value no constant in the tree holds); the compiler's bytes are tracked by the action; and the tool store holds no source content, with a version bump as the way out. Probe runs exit non-zero on purpose -- the program returns 1 when the answer is not 42 -- so they are judged by their output rather than their status. The README and chapter 30 said the action re-runs when the compiler binary changes. That is now measured rather than reasoned, and both state which case does not isolate it.
1 parent b65a64c commit 11470f2

4 files changed

Lines changed: 142 additions & 39 deletions

File tree

.github/workflows/ci-linux.yml

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -313,20 +313,95 @@ jobs:
313313
echo "ok: the optional package is absent without its feature"
314314
cd - >/dev/null
315315
316-
# 12-a-new-device-language. The `.toy` compiles on an engine that does
317-
# not know the extension, AND editing the compiler reaches the
318-
# artifact -- the half that fails silently when the tool is not a
319-
# declared input of the action.
316+
# 12-a-new-device-language. Three criteria, and a probe run exits
317+
# non-zero on purpose -- the program returns 1 when the answer is not
318+
# 42 -- so those runs are guarded with `|| true` and judged by their
319+
# output rather than their status.
320320
cd examples/12-a-new-device-language/app
321321
"$MCPP" run | tee /tmp/toy.log
322-
grep -q 'toy_answer() = 42' /tmp/toy.log || {
322+
grep -q 'answer() = 42' /tmp/toy.log || {
323323
echo "FAIL: the .toy did not reach the link"; exit 1; }
324-
sed -i 's/print s + 0 }/print s + 100 }/' ../rules-toy/tools/toyc.sh
325-
"$MCPP" run | tee /tmp/toy2.log
326-
sed -i 's/print s + 100 }/print s + 0 }/' ../rules-toy/tools/toyc.sh
327-
grep -q 'toy_answer() = 142' /tmp/toy2.log || {
328-
echo "FAIL: editing the compiler did not reach the artifact"; exit 1; }
329-
echo "ok: the compiler is a declared input"
324+
# THE LANGUAGE IS EXECUTED, NOT PATTERN-MATCHED: 21 is what the
325+
# emitted `while` loop computes, and no constant in the tree holds it.
326+
grep -q 'gcd(1071, 462) = 21' /tmp/toy.log || {
327+
echo "FAIL: the emitted loop did not run"; exit 1; }
328+
329+
# Editing the .toy reaches the artifact.
330+
sed -i 's/scale(gcd(1071, 462), 2)/scale(gcd(1071, 462), 3)/' src/kernels/answer.toy
331+
"$MCPP" run > /tmp/toy2.log 2>&1 || true
332+
sed -i 's/scale(gcd(1071, 462), 3)/scale(gcd(1071, 462), 2)/' src/kernels/answer.toy
333+
grep -q 'answer() = 63' /tmp/toy2.log || {
334+
cat /tmp/toy2.log; echo "FAIL: editing the .toy did not reach the artifact"; exit 1; }
335+
336+
# THE COMPILER IS A DECLARED INPUT OF THE ACTION -- the half that
337+
# fails silently, and the one criterion here that has to be built to
338+
# isolate it. Bumping the tool's version is NOT that criterion: the
339+
# tool's path is on the action's command line, so a new path re-runs
340+
# the edge whether or not it is also declared as an input. Measured:
341+
# with `a.input(compiler)` removed, a version bump still reached the
342+
# artifact, and this step passed.
343+
#
344+
# The isolating change is different BYTES AT THE SAME PATH. Build a
345+
# compiler that behaves differently, overwrite the cached binary in
346+
# place, and the command line is byte-identical.
347+
#
348+
# BOTH DIRECTIONS ARE THE CRITERION, not either one. Falsified by
349+
# removing `a.input(compiler)`: the artifact followed the first
350+
# overwrite anyway and stopped following the restore, so a check that
351+
# asserted only the first direction would have passed on a rule that
352+
# tracks nothing.
353+
#
354+
# `n.value * 2` keeps `0` at `0`: a probe that changed every literal
355+
# would turn `while (b != 0)` into a loop that divides by zero, and
356+
# this step would report a crash rather than an answer.
357+
probe_on() { sed -i 's/return std::format("{}", n.value);/return std::format("{}", n.value * 2);/' ../toyc/src/compile.cppm; }
358+
probe_off() { sed -i 's/return std::format("{}", n.value \* 2);/return std::format("{}", n.value);/' ../toyc/src/compile.cppm; }
359+
install_toyc() {
360+
( cd ../toyc && "$MCPP" build >/dev/null )
361+
cp "$(find ../toyc/target -name toyc -type f -perm -u+x | head -1)" "$1"
362+
}
363+
364+
# `mcpp cache dir` prints a legacy-directory note on a second line.
365+
store="$("$MCPP" cache dir | head -1)/tool"
366+
cached="$(find "$store" -path '*toyc@0.1.0*/bin/toyc' | head -1)"
367+
[ -n "$cached" ] || {
368+
echo "FAIL: no toyc in the tool store under $store"; exit 1; }
369+
370+
probe_on; install_toyc "$cached"; probe_off
371+
"$MCPP" run > /tmp/toy3.log 2>&1 || true
372+
grep -q 'answer() = 168' /tmp/toy3.log || {
373+
cat /tmp/toy3.log
374+
echo "FAIL: a changed compiler binary did not reach the artifact."
375+
echo " rules-toy must declare the compiler among the action's inputs."
376+
exit 1; }
377+
install_toyc "$cached"
378+
"$MCPP" run > /tmp/toy4.log 2>&1 || true
379+
grep -q 'answer() = 42' /tmp/toy4.log || {
380+
cat /tmp/toy4.log
381+
echo "FAIL: restoring the compiler binary did not reach the artifact --"
382+
echo " the action is not tracking the compiler's bytes."
383+
echo " rules-toy must declare the compiler among the action's inputs."
384+
exit 1; }
385+
386+
# THE STORE HOLDS NO SOURCE CONTENT, which the example's README and
387+
# docs/30 both state. Editing the compiler's sources at the same
388+
# version changes nothing, because nothing rebuilds the tool.
389+
probe_on
390+
"$MCPP" run > /tmp/toy5.log 2>&1 || true
391+
grep -q 'answer() = 42' /tmp/toy5.log || {
392+
cat /tmp/toy5.log
393+
echo "FAIL: the tool store now sees source content."
394+
echo " The example's README and docs/30 state that it does not; update them."
395+
exit 1; }
396+
397+
# ... and bumping the version is the way out both of them offer.
398+
sed -i 's/^version = "0.1.0"/version = "0.1.1"/' ../toyc/mcpp.toml
399+
"$MCPP" run > /tmp/toy6.log 2>&1 || true
400+
sed -i 's/^version = "0.1.1"/version = "0.1.0"/' ../toyc/mcpp.toml
401+
probe_off
402+
grep -q 'answer() = 168' /tmp/toy6.log || {
403+
cat /tmp/toy6.log; echo "FAIL: bumping the tool version did not rebuild it"; exit 1; }
404+
echo "ok: the compiler is a declared input, and the store is keyed on the version"
330405
331406
- name: "Graphics example: render offscreen on lavapipe and assert the pixels"
332407
run: |

docs/30-build-mcpp.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -936,10 +936,15 @@ A published version is immutable, so for a tool that arrives from an index the
936936
key is exact. A tool being edited next door has the same version from one build
937937
to the next, and the cached binary stays: measured on
938938
[`examples/12-a-new-device-language`](../examples/12-a-new-device-language/),
939-
a change to the tool's emitter left `mcpp run` reporting `Finished dev in 0.00s`
940-
and printing the previous answer. Bump the tool package's version, or empty the
941-
build cache with `mcpp cache clean` — the tool store lives inside it, at
942-
`<mcpp cache dir>/tool/<index>/<name>@<version>/`.
939+
a change to the tool's emitter left `mcpp run` printing the previous answer,
940+
while bumping the tool package's version rebuilt it and changed the artifact.
941+
Bump the version, or empty the build cache with `mcpp cache clean` — the tool
942+
store lives inside it, at `<mcpp cache dir>/tool/<index>/<name>@<version>/`.
943+
944+
This is a gap in the rebuild, not in the tracking. An action that declares the
945+
tool among its inputs does re-run when that file's bytes change, measured by
946+
overwriting the binary in the store: the artifact followed. What does not happen
947+
is the rebuild that would change those bytes.
943948

944949
### `[tools.overrides]` — use an existing binary
945950

docs/zh/30-build-mcpp.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -789,9 +789,13 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] }
789789
所以对来自索引的工具,这个键是精确的。而正在旁边被编辑的工具,两次构建之间版本相同,
790790
缓存里的二进制就留在原地:在
791791
[`examples/12-a-new-device-language`](../../examples/12-a-new-device-language/)
792-
上实测,改动工具的 emitter 之后,`mcpp run` 报告 `Finished dev in 0.00s` 并打印上一次
793-
的答案。抬工具包的版本,或用 `mcpp cache clean` 清空构建缓存 —— tool store 就住在
794-
里面,路径是 `<mcpp cache dir>/tool/<index>/<name>@<version>/`
792+
上实测,改动工具的 emitter 之后 `mcpp run` 打印的是上一次的答案,而抬高工具包的版本
793+
之后它被重建、产物随之改变。抬版本,或用 `mcpp cache clean` 清空构建缓存 ——
794+
tool store 就住在里面,路径是 `<mcpp cache dir>/tool/<index>/<name>@<version>/`
795+
796+
**缺口在重建,不在跟踪。** 把工具列进 action 输入的规则,确实会在那个文件的字节变化
797+
时重跑 —— 实测直接覆盖 store 里的二进制,产物随之改变。不发生的是「让这些字节变化」
798+
的那次重建。
795799

796800
### `[tools.overrides]` —— 使用已有的二进制
797801

examples/12-a-new-device-language/README.md

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -184,28 +184,47 @@ a real compiler rather than a script.
184184

185185
## The boundary this example measured: a host tool is cached by version
186186

187-
Editing `toyc`'s **source** does not reach the artifact. Measured: a change to
188-
the emitter left `mcpp run` reporting `Finished dev in 0.00s` and printing the
189-
previous answer.
187+
Four changes were made one at a time, each from the same starting state:
190188

191-
The tool store's key is the tool package's identity, version, host triple,
192-
compiler identity, profile, features and the versions of its transitive
193-
dependencies — not the content of its sources. For a package that arrives from
194-
an index that key is exact, because a published version is immutable. For a
195-
`path` dependency being edited it is not:
196-
197-
| situation | effect |
198-
|---|---|
199-
| the tool's version changes | the tool is rebuilt, and the action re-runs because its declared input changed |
200-
| the tool's sources change, its version does not | the cached binary stays, and the build is green over the previous compiler's output |
201-
202-
Two ways out, and they are the same one at different sizes: bump the tool
203-
package's version, or empty the build cache with `mcpp cache clean` — the tool
204-
store lives inside it, at `<mcpp cache dir>/tool/<index>/<name>@<version>/`.
205-
206-
The action itself is not the gap. `rules-toy` declares the compiler as an input
207-
beside the source, so an action whose compiler binary changes does re-run. What
208-
does not happen is the rebuild that would change those bytes.
189+
| what changed | the artifact | how it was changed |
190+
|---|---|---|
191+
| the `.toy` source | follows: `42``63` | `scale(…, 2)``scale(…, 3)` |
192+
| the compiler's **bytes**, at the path the action names | follows: `42``168` | overwriting the binary in the tool store |
193+
| the compiler's **sources**, its version unchanged | does not follow: the previous answer stands | editing the emitter |
194+
| the compiler's **version** | follows: `42``168`, and the tool is rebuilt | `0.1.0``0.1.1` |
195+
196+
Rows two and three are the whole finding, and they separate two things that are
197+
easy to merge. **The action's input tracking works**: `rules-toy` declares the
198+
compiler beside the source, and changing that file's bytes re-runs the edge.
199+
**What does not happen is the rebuild that would change those bytes.** The tool
200+
store's key is the tool package's identity, version, host triple, compiler
201+
identity, profile, features and the versions of its transitive dependencies —
202+
it holds no source content. For a package that arrives from an index the key is
203+
exact, because a published version is immutable; for a `path` dependency being
204+
edited it is not.
205+
206+
`mcpp run` prints `Finished dev in 0.00s` in row three, and that line is mcpp's
207+
own summary rather than evidence: row two prints it too, and the artifact
208+
changed.
209+
210+
**Row four does not test row two, which is why the difference is worth stating.**
211+
The tool's path is on the action's command line, so a new version re-runs the
212+
edge whether or not the compiler is also a declared input. Removing
213+
`a.input(compiler)` from `rules-toy` and bumping the version left the artifact
214+
following anyway. The isolating change is different bytes at the *same* path —
215+
row two — and it takes both of its directions: with the input removed, the
216+
artifact followed the overwrite and then stopped following the restore. CI runs
217+
that pair.
218+
219+
Two ways out: bump the tool package's version, or empty the build cache with
220+
`mcpp cache clean` — the tool store lives inside it, at
221+
`<mcpp cache dir>/tool/<index>/<name>@<version>/`.
222+
223+
**One more trap sits behind them.** Going back from `0.1.1` to `0.1.0`, whose
224+
clean tool was still in the store, left the artifact at `168`. The build program
225+
did not re-run, so the plan still named the `0.1.1` binary, which had not
226+
changed. `rm -rf target` cleared it. Iterating on a compiler means the version
227+
goes forward only.
209228

210229
## Three things the first version got wrong
211230

0 commit comments

Comments
 (0)