ww: add -w persistent workdir builds with content-identity reuse

A -w DIR workdir replaces the fresh .sepwork scratch with a caller-owned
persistent package-artifact tree. A package is reused only when its
freshly composed unit byte-equals the committed unit and byte copies of
the compiler/assembler recorded in the dir equal the live tools — pure
content identity, no mtimes, no hashes, every decision reproducible
with cmp against plain files. Recompiles stage at .new names and commit
by rename, unit strictly last, so an interrupted build forces a
recompile and can never leave a committed unit vouching for uncommitted
artifacts; .o/.a additionally reject zero size (ELF/ar are never
empty), while .s/.wwi accept legitimate empties (FFI-only rt). A mode
stamp pins the -T/-S shape and the artifact protocol revision. Classic
scratch keeps its exact acquire/refuse/cleanup contract; run rejects
-w; dir-mode test rejects -w; both driver stages implement identical
behavior and wording.
This commit is contained in:
2026-08-08 03:51:45 +09:00
parent e1141330f2
commit e06b871ab9
3 changed files with 693 additions and 72 deletions

View File

@@ -201,6 +201,22 @@ beneath one freshly acquired directory, remove both after every build result,
and make cleanup failure fail the command. Make recipes build driver-produced
tools in invocation-owned directories and apply the same exact cleanup rule.
`ww build -w DIR` and single-file `ww test -w DIR` replace that scratch with a
caller-owned persistent package-artifact workdir: the directory must already
exist, is never cleaned by the driver, and holds one committed unit, `.wwi`,
`.s`, `.o`, and dep `.a` per package plus byte copies of the compiler and
assembler and a small mode stamp. A package is reused only when its freshly
composed unit byte-equals the committed unit and the recorded tool copies
byte-equal the live tools — content identity only, no mtimes, no hashes, every
decision reproducible with `cmp` against plain files. Recompiled artifacts
land at staged `.new` names and commit by rename with the unit renamed last,
so an interrupted build forces a recompile rather than a false reuse; the
link always reruns. One workdir serves one invocation at a time and one
(root, mode) shape; both driver stages implement the identical contract.
This is build staleness in the Make/mk/Go sense, not a result cache: tests
always run, and the byte-identity and bootstrap gates keep building on fresh
scratch. `make clean` reclaims every workdir under `out/`.
`test/lang` currently uses one package per source file, so its complete gate
still performs independent package builds. That remaining source layout is not
hidden behind caching or concurrency; it is outside the small `test` target.