ww build: discard exact null outputs

This commit is contained in:
2026-08-20 23:17:23 +09:00
parent 84f5e68709
commit 71a9da2058
7 changed files with 751 additions and 31 deletions

View File

@@ -6405,6 +6405,134 @@ injected late-link request rollback over old files and modes; occupied-stage
rejection; simultaneous builds with different umasks; diagnostic parity;
artifact and binary byte identity; and absence of `.new` or umask-probe residue.
### 11.29 Implemented exact null-output discard for builds
Exact `ww build -o /dev/null` now removes output installation while preserving
the ordinary load and action graph. It is not a request to create an archive,
executable, interface, or scratch tree at the null-device pathname.
#### Pinned Go evidence and fact classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `runBuild` loads packages and reports load errors first, then recognizes the
null output and clears `BuildO` before any output-directory, multi-package,
or install-action branch. It finally constructs ordinary `ModeBuild` actions
for every selected package
([`cmd/go/internal/work/build.go`, lines 459558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L558)).
- `AutoAction` maps a main package in that ordinary mode to a link action and a
non-main package to a compile action
([`cmd/go/internal/work/action.go`, lines 450455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L450-L455)).
- On the pinned Unix target, `IsNull` accepts exact `os.DevNull`, which is
`/dev/null`; its only additional spelling rule is the Windows-only
case-insensitive `NUL` exception
([`cmd/go/internal/base/path.go`, lines 8192](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/path.go#L81-L92)).
- The test builder treats a nonempty `-o` as a binary-retention request, exempts
the null device from multi-package output rejection, and makes a null target
use the private build action instead of an install action
([`cmd/go/internal/test/test.go`, lines 631646, 771804, and 12591294](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1259-L1294)).
- Official `devnull.txt` requires `go test -c -o $devnull` and a non-main
package `go build -o $devnull` to succeed without changing the device
([lines 325](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/devnull.txt#L3-L25)).
`build_dash_o_dev_null.txt` requires a command-line source build to succeed
without its default executable
([lines 112](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_dash_o_dev_null.txt#L1-L12)).
`build_cache_link.txt` requires a cold null build to compile and link and an
unchanged warm null build to skip compilation but link again
([lines 422](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_cache_link.txt#L4-L22)).
`TestRemoveDevNull` requires cleanup never to remove the device
([`cmd/go/internal/work/build_test.go`, lines 2235](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build_test.go#L22-L35)).
The load-before-output order, exact spelling, absence of installation, normal
command link, normal non-command compile, cold/warm link behavior, raw-source
behavior, and device preservation are directly implemented or asserted by the
pinned sources above. Acceptance of multiple mixed package roots and an empty
matched set is derived from clearing `BuildO` before the output-cardinality
branches and then iterating the ordinary package action list. That conclusion
applies to WW's manifest-free local package set without importing Go's module,
cache, or distribution model. No installed host Go behavior is authority.
#### Direct pre-fix measurements
Fresh public-driver probes measured both stages before production edits:
- raw source, one command directory, and one non-main library directory each
failed with exit 1 and `ww: cannot create scratch /dev/null.sepwork`;
- a two-command-plus-library request failed with exit 2 and
`wwtest package: cannot use -o with multiple packages`;
- a persistent command request reached the linker but failed through
`/dev/null.new`: Cstage reported `w6l: cannot open /dev/null.new`, WWstage
reported `w6l: cannot open output`, and both left the caller workdir empty;
- raw `ww test -c -o /dev/null FILE` and its running form failed at the same
adjacent-scratch acquisition, while directory `test -c` and running test
requests already built privately, discarded the retained copy, and preserved
their compile-only versus run distinction;
- a blank import of a missing package produced the same positioned
`cannot find package missing.pkg` diagnostic in Cstage and WWstage before
output setup; and
- `/dev/null` remained the same character device, mode, device/inode, and size
throughout the failed probes.
Those are directly measured WW facts. The externally observable gap was thus
the build/raw-driver interpretation of exact null as an artifact stem, plus the
coordinator's ordinary multi-output rejection, rather than a loader, compiler,
linker, or device-write defect.
#### Ownership, actions, publication, and identity
`internal/wwpackage.packagecommand` owns the shared package-request output
policy. After argument parsing and before output planning it records exact Unix
null discard. Loading, source classification, package/import validation,
canonical grouping, graph construction, and diagnostic precedence remain
unchanged. The coordinator suppresses default names, output-directory setup,
ordinary non-directory fan-out rejection, caller publication, and the
visibility-only `-S` workdir requirement, then gives every selected group a
request-private plan product. Commands still link; libraries still compile and
archive; mixed and repeated roots still use their canonical graph/action
deduplication. A recursive pattern matching no package emits its ordinary
warning and has no output-cardinality error.
The direct Cstage and WWstage drivers own raw or single-directory requests that
do not enter the coordinator. For exact null they allocate a private command
product, run the unchanged separate-compilation pipeline, and remove that
product and its scratch on every return. Their raw-test paths use the existing
unretained private test binary rather than setting `/dev/null` as output and
object stem. `-c` still suppresses execution; a running request still reports
ordinary pass, assertion, signal, and harness outcomes. Directory tests retain
their previously established private-runnable/null-publication behavior.
There is no caller-visible stage or destination to commit, occupy, replace, or
chmod. Producer failure or signal removes private plan state; persistent action
rollback preserves every prior unit, interface, assembly, object, archive,
tool record, and stamp. Successful persistent requests commit semantic actions
normally, unchanged actions are warm-reused, source changes invalidate their
owners, and command links still run for each request. Separate simultaneous
Cstage and WWstage requests own disjoint private products and workdirs. Exact
lookalikes remain normal caller-owned outputs and retain their existing
fan-out, `.sepwork`, permission, occupied-stage, transaction, and diagnostic
rules.
Output disposition remains request metadata. Dotted package identity, declared
name, physical source directory, import binding, graph edges, action/storage
keys, compiler/assembler/linker semantic argv, symbols, `.wwi`, unit and
artifact bytes, and persistent invalidation are unchanged. No persisted byte
contract changed: build workdir format remains `18`, test workdir format remains
`19`, and semantic storage remains `3`.
The WW-native owner `exact_null_output_discards_build_products` covers Cstage
and WWstage command, library, mixed-root, raw-build, assembly-only, and raw-test
routes; load/import precedence; empty-pattern warning; exact lookalike
rejection/publication; cold, warm, and invalidated persistence; normal link
actions and captured runnable bytes; injected linker failure and signal;
persistent rollback; concurrent stage isolation; device preservation; private
path and `.new` cleanup; semantic-artifact and captured-binary byte identity;
and diagnostic/output parity. Existing directory-test owners cover test-product
parallelism, timeout, child-setup failure, retained-output transactions, and
runtime cwd/environment/stdin. Build runtime behavior is inapplicable, and
caller-output rollback, occupied caller stages, and output permissions are
inapplicable to the exact discard branch because it creates no public inode.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -319,6 +319,19 @@ ImportPath = ident { "." ident } .
build therefore reuses unchanged semantic actions while refreshing the
caller-visible output with the current invocation's mode. Assembly-only
builds publish no executable or archive.
- On the Unix target, exact `-o /dev/null` is a build-output discard request,
not an ordinary output filename. Package loading, import validation, graph
construction, compilation, assembly, archiving, command linking, failure,
and persistent invalidation proceed normally; only installation of a
caller-visible command, archive, interface, default basename, or adjacent
scratch tree is omitted. One such request may select any number of command
and non-command packages, and an empty recursive match is successful after
its normal warning. `-S` likewise needs no caller workdir merely to retain
discarded assembly. Raw-file and directory roots use the same rule. A
non-exact spelling is an ordinary output and keeps all established fan-out,
rejection, permission, scratch, and publication behavior. Output discard is
request metadata; it does not change package, import, graph, action,
artifact, symbol, `.wwi`, or persistence identity.
- Only names marked `export` (§5) are visible across module boundaries.
Import paths remain unquoted and dotted. Grouped imports, quoted import paths,
@@ -662,7 +675,11 @@ directory. An output ending in `/` or naming an existing directory receives
that basename and may have missing parent directories created. One
non-directory output may name only one package. Multiple packages whose
visible import leaves would produce the same test-binary name reject before
tools or output creation; exact `/dev/null` is the discard exception. Declared
tools or output creation; exact `/dev/null` is the discard exception. It keeps
the ordinary private link and, unless `-c` is present, the ordinary execution,
but installs no retained copy. The raw single-file compatibility route uses
the same private-output rule and never treats `/dev/null` as an explicit
artifact or adjacent-scratch stem. Declared
package names, test variants, source filenames, physical directories, output
paths, and retained binary names remain presentation or loader metadata and do
not become canonical package or action identity.

View File

@@ -227,7 +227,10 @@ naming an existing directory receives one such name per selected package and
missing parents are created. A single non-directory output is legal for one
package only. Multi-package non-directory output and duplicate visible binary
names reject before tools; exact `/dev/null` discards every copy and permits
duplicate names. A package with no selected test source validates production
duplicate names. The private test link still runs unless `-c` suppresses it;
the raw single-file compatibility route also links into driver-owned private
storage instead of using `/dev/null` as an artifact or `.sepwork` stem. A
package with no selected test source validates production
but publishes nothing and does not create an otherwise unneeded output
directory. Successful `-c` output is silent apart from no-test reporting.