diff --git a/.agents/docs/2026-09-29-build-progress-display-design.md b/.agents/docs/2026-09-29-build-progress-display-design.md new file mode 100644 index 000000000..1877215a0 --- /dev/null +++ b/.agents/docs/2026-09-29-build-progress-display-design.md @@ -0,0 +1,728 @@ +--- +subject: design +status: landed +--- + +# Build progress: each step's line states its outcome, and one status line states the build + +- Status: landed (revision 2). Implemented in #742, released as 2026.9.29.5 +- Date: 2026-09-29 +- Origin: the cross-verification of #742 on the validation project (run + 36562019799). `mcpp build --workspace` printed its last `Compiling` line at + 11:34:50 and `Finished` at 12:14:58; nothing was printed in between. The + discussion that followed settled the requirements in section 2. + +Revision 2 replaces the criterion "every step of the build is planned", which +a measurement refuted (section 3.1), with a completion rule that holds in +every build. It drops the per-package fraction and the `stopped` state, which +depended on that criterion, and states how a step is attributed to its +package (section 6.3) and how lines that bypass the renderer are handled +(section 5.3). + +## 0. Scope + +This document covers the human output of the commands that build (`build`, +`run`, `test`, `pack`, and the planning part of `emit build-database`), from +the first line to `Finished`. It does not change machine output +(`--message-format json`), the result lines of `mcpp test`, or the `Packed` +report of `mcpp pack` (revised in 2026.9.29.5). + +## 1. The problem, measured + +1. **The ninja phase prints nothing.** ninja runs with `--quiet`, and mcpp + collects its output whole (`capture_exec`, `capture_exec_deadline`) and + examines it after ninja exits. In the run above the vcpkg binary cache was + cold, so the `prepare` actions of deps-vcpkg built ports from source for + most of the 40 minutes 8 seconds, and the log gives no indication of it. +2. **A failure is reported late.** A failed step is printed after ninja exits, + that is, after every step already running has finished. A compile error + beside a running 20-minute vcpkg install is shown 20 minutes after it + occurred. +3. **`Compiling ` states an intention, not an outcome.** The line is + printed for the root's direct dependencies before ninja starts, whether or + not they have anything to compile. Transitive dependencies are not named at + all, and nothing later states what happened to any of them. +4. **A build program takes two lines and states no time**: + `build.mcpp compiling X` and `build.mcpp running X`, or + `build.mcpp up to date X (cached)`. +5. **`Finished … in X` counts only ninja.** The validation project's step took + 1124 s while `Finished` said 1020.65 s (the acceptance report of + 2026.9.29.4). +6. **Terminal detection answers "not a terminal" on macOS and Windows.** + `mcpp.platform.terminal::is_tty` is compiled only under `__unix__`, which + Apple's compilers do not define and Windows does not have. The one live + display mcpp has, the download bar, is therefore drawn only on Linux, and + colours are off on the other two platforms. + +## 2. Requirements + +These were settled in the discussion. + +- **R1** Each step's line carries the step's state. On a terminal the line is + updated in place while the step runs. In a log it is written once, when the + outcome is known. +- **R2** One status line summarises the build, for example + `Building 612/1203 · 14:32 · gpp.gui: vcpkg install 6:10`. It is always the + last line, separated from the step lines by one blank line, and not aligned + with them. +- **R3** `Finished` is separated from the step lines by one blank line and + states the total time and how it was spent. +- **R4** The default output names the packages the user asked to build. The + packages they depend on are folded into one line. +- **R5** `-v` or `--verbose` shows everything: every package, every step as + ninja reports it (commands and their output), and how long each build + program took to compile and to run. +- **R6** The states are named `done`, `ran`, `cached` and `waiting`, with + `failed` and `fresh` for the cases the discussion did not cover + (section 4.2). +- **R7** No line states something mcpp does not know. This is the codebase's + standing rule: the word `Cached` was once printed for three months while + ninja recompiled every unit behind it. + +## 3. What mcpp can know, and when + +| Fact | Source | Known | Exact | +|---|---|---|---| +| a download, a provisioning entry, a build program: start, end, outcome | mcpp runs them | as they happen | yes | +| steps finished, steps planned so far | ninja's status line (`NINJA_STATUS`: `%f`, `%t`, `%e`) | when each step finishes | yes | +| which step finished, when it started and ended | `.ninja_log`, written and flushed right after the status line (ninja 1.12.1, `build_log.cc:170`) | a moment after each step finishes | yes | +| a step's output, a failure | ninja's standard output, after the step's status line | when the step finishes | yes | +| which step is running | not reported by ninja through a pipe | not known | no | +| which steps ninja will run | not reported; `%t` counts them and changes during the build | not known | no | + +**ninja through a pipe reports a step only when it finishes.** ninja prints a +status line when a step starts only on a smart terminal or for the console +pool (`StatusPrinter::BuildEdgeStarted`); mcpp reads ninja through a pipe. +Measured with ninja 1.12.1 and a two-second step: the step's line appeared +two seconds after it started, together with its output. `check` and +`prepare` actions are the exception: they run through the engine's wrapper +(`mcpp __action`, `mcpp __action-stamp`) on every platform, so the wrapper can +report their start (section 6.4). These are the roles SPEC-007 assigns to +analysers and to external builds, the steps that run for minutes. + +### 3.1 Measured on a first build + +A probe (a root package, a path dependency with two module units, `import +std`, clang 22, ninja 1.12.1) built from an empty directory: + +- `%t` read 12 on the first seven status lines and 13 from the eighth on: + loading a dyndep file added the step that compiles `std.pcm`; +- the graph holds 14 steps, and the build ran 13: `std.compat.pcm` is never + built unless something imports it; +- `std.o` and `std.compat.o` ended in the same millisecond; the log tells + them apart by their command hashes. + +A criterion of the form "`%t` equals the graph's number of steps, so every +step runs" is therefore never met, and a package's planned number of steps +is not knowable from `%t`. + +### 3.2 The rule that follows + +**A package is complete when every step the graph assigns to it has finished +in this build.** The rule needs no knowledge of ninja's plan: + +- in a first build it is met as the package's last step finishes; +- in an incremental build a package rarely re-runs every step, and its + outcome is then stated when ninja exits; +- a step the graph assigns to a package and that is never built (the + standard library's `std.compat` module in the probe) keeps the package open + until ninja exits. The rule can only state completion late, never early. + +A package is never shown as `waiting`, because mcpp cannot tell a package +whose first step is running from one whose steps have not started. `waiting` +is stated only where mcpp itself decides the order: build programs, which +mcpp runs one after another. + +## 4. The output + +### 4.1 Two layers + +The output has two layers: + +- **the log**: lines that do not change once they are written; +- **the status line**: always the last line. On a terminal it is rewritten + in place. In a log it is repeated when the log has been silent (section 5). + +A step line belongs to the log once its outcome is known. On a terminal it is +shown between the log and the status line while it runs. + +### 4.2 Step lines + +A step line has the shape + + + +The state begins two columns after the longest subject of its block, and at +column 56 at most; a longer subject is followed by two spaces. A block is the +set of lines of one kind: the build programs, the packages. + +| Verb | Subject | Live states (terminal) | Final states | +|---|---|---|---| +| `Downloading` | the package | the existing bar | unchanged: `done, 211.9 MB in 5.0s` | +| `build.mcpp` | the package | `waiting`, `compiling 0:02`, `running 0:05` | `ran 7.52s`, `cached`, `failed` | +| `Compiling` | the package: `gpp.core (GalTranslPP)`, `fmt v11.0.2`, `x (path)`, `y (git tag v1)` | `61 steps` | `done 3m12s`, `cached 12 units`, `failed`, `12 steps`, `fresh` | +| `Compiling` | `23 dependencies` | `412 steps` | `done 1m12s`, `done 1m12s · 5 cached` | + +The meaning of each state: + +- `done `: every step of the package that ran succeeded, and the + package is complete (section 3.2) or the build has ended. The span runs + from the start of its first step to the end of its last, both read from + ninja's log. +- `ran