test: cover exact package tool invocation paths

This commit is contained in:
2026-08-12 14:29:17 +09:00
parent d78020baf7
commit bb71b31f81
6 changed files with 321 additions and 31 deletions

View File

@@ -851,11 +851,11 @@ clean:
# (it takes no -o/-I and writes asm to stdout), so the chainable sep
# frontend w6c is the bootstrapped target (test 994 pins w6c ==
# wwdump -c).
# - The wwstage DRIVER (`ww_ww`) is NOT yet in this loop: it cannot
# swap its frontend (it hardcodes <selfdir>/w6c_ww and honors no
# WW_W6C override). Closing that — so `ww_ww` itself carries the
# glue — is task #66. Until then cstage `ww` orchestrates and this
# bootstrap does NOT claim a fully wwstage-driven self-host.
# - The wwstage DRIVER (`ww_ww`) can now swap all three tools through
# exact-path WW_W6C/WW_W6A/WW_W6L overrides. This loop deliberately
# still keeps cstage `ww` as its fixed orchestrator, so the driver is
# outside the compared artifacts and this target does NOT yet claim a
# fully wwstage-driven self-host.
#
# Why a fourth stage? Stage 3 proves stage-1 w6c_ww and ww2 (which differ
# in how they were built) emit the same machine code. Stage 4 proves ww3 is
@@ -940,7 +940,7 @@ nocc:
@rm -rf $(NOCC_OUT)
@mkdir -p $(NOCC_BIN) $(NOCC_LIB) $(NOCC_OBJ)
@# Stage 0: drop bootstrap binaries under both natural names and
@# the _ww-suffixed names the driver expects when shelling out.
@# the _ww-suffixed names used by the driver's default sibling lookup.
@for f in ww w6c w6a w6l; do \
cp $(STAGE0)/$$f $(NOCC_BIN)/$$f; \
cp $(STAGE0)/$$f $(NOCC_BIN)/$${f}_ww; \

View File

@@ -9,6 +9,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
/* `path` is acceptable iff it's either an archive ("!<arch>\n") or
* an ELF file ("\x7fELF"). Distros often ship lib<name>.so as a GNU
@@ -128,10 +129,9 @@ main(int argc, char **argv)
}
int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
fclose(f);
if (rc == 0) {
char cmd[1024];
snprintf(cmd, sizeof cmd, "chmod +x %s", out);
(void)system(cmd);
if (rc == 0 && chmod(out, 0755) != 0) {
fprintf(stderr, "w6l: cannot make %s executable\n", out);
rc = 1;
}
free(inputs);
return rc;

View File

@@ -2651,11 +2651,13 @@ Other accidental constraints include fixed 256/1024-byte name/path buffers. The
compiler parser silently truncates dotted full imports beyond 255 bytes, while
the C driver scanner can stop advancing and hang on an import identifier at that
limit; the dynamically sized WWstage scanner differs. There is no regression
test for this stage divergence. Other defects include unquoted Cstage `system()` command
construction, and dependence on `/bin/sh`. Cstage honors `WW_W6C`, `WW_W6A`,
`WW_W6L`, `WW_LIB`, and source-library fallbacks; the self-hosted driver instead
hardcodes sibling tools and relative libraries. The two implementations are
therefore similar algorithms, not one protocol implementation.
test for this stage divergence. Compiler, assembler, and linker launches now use
structured argument vectors in both stages. Both drivers honor exact executable
paths in `WW_W6C`, `WW_W6A`, and `WW_W6L` and otherwise select their
stage-specific sibling tools. Cstage additionally honors `WW_LIB` and
source-library fallbacks; the self-hosted driver still uses relative source and
runtime libraries. The two implementations remain parallel production
algorithms rather than one protocol implementation.
Build and test disagree about source symlinks. External package tests are built
from a generated single-file root plus `-I`; an external import of a multi-file
@@ -2689,8 +2691,8 @@ The observed invalidation rules are:
content is not rehashed.
Hidden or incompletely modeled inputs include `CC`, `AR`, `PATH`, `ccache`, Make
flags, shell quoting/behavior, compiler built-ins, assembler/linker defaults,
inferred `argv[0]` library locations, current working directory, `chmod`, runtime
flags, compiler built-ins, assembler/linker defaults,
inferred `argv[0]` library locations, current working directory, file mode, runtime
archive, linker binary, native-library resolution, host libc/CRT/loader, SDK,
CPU, target, and environment. Make does not invalidate existing C objects when
the host compiler or C flags change. Workdir reuse also omits the driver binary/
@@ -2736,9 +2738,10 @@ holds for measured WW artifacts, not for the complete build.
Cstage and WWstage drivers building the same eight-package graph produced a
byte-identical final executable and all 38 non-tool artifacts, but took 0.138 s
and 0.356 s respectively. Under `env -i PATH=/nonexistent`, Cstage returned
success after ambient `chmod` failed and left mode 0644; WWstage returned success
with mode 0755. This exposes implementation asymmetry and an undeclared host tool.
and 0.356 s respectively. The audit also found that Cstage formerly returned
success after an ambient `chmod` lookup failed and left mode 0644, while WWstage
created mode 0755 directly. Cstage now calls `chmod(2)` on the exact output path
and reports failure, removing that host-tool and path-splitting asymmetry.
### 11.5 What survives and what is deleted
@@ -2985,6 +2988,42 @@ boundary in
with compiler and linker import configurations emitted separately in
[`cmd/go/internal/work/exec.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go).
### 11.8 Implemented exact package-tool invocation slice
The local package builder now launches the compiler, assembler, and linker as
an executable plus an argument vector in both Cstage and WWstage. No package
source path, work-directory artifact path, output path, test-support qualifier,
or link-closure member is flattened into a shell command. Paths containing
spaces therefore retain one argument boundary from the package coordinator
through compilation, assembly, and final executable linking.
`WW_W6C`, `WW_W6A`, and `WW_W6L` each name one exact executable path. They are
not shell fragments and are not searched through `PATH`. With no override,
Cstage keeps its `w6c`/`w6a`/`w6l` siblings and WWstage keeps its
`w6c_ww`/`w6a_ww`/`w6l_ww` siblings. The coordinator preserves these variables
when it starts the one command-scoped package build, so the same contract covers
ordinary directory builds, same-package tests, external tests, recursive test
requests, and persistent-workdir tool identity. A failed overridden compiler or
assembler remains attributed to its owning package in both stages. The Cstage
linker also sets executable mode with `chmod(2)` on the exact output path rather
than invoking an ambient command.
This changes only process invocation and publication. Source imports still own
the graph, each directory is still one production package, compiler actions
still consume direct dependency export data through `.unit.ww`, and links still
receive the complete per-root `.a` closure. Repository-native coverage wraps
all three real stage tools at executable paths containing spaces, records every
argument boundary, inspects `.unit.ww`, `.wwi`, `.a`, and root object placement,
runs the published test binary, compares Cstage/WWstage artifacts and traces,
and injects a compiler failure to compare package attribution.
Go 1.26.5 keeps the same responsibility boundary: its work executor passes the
selected compiler or linker tool and a constructed argument slice to the
builder, while package loading and action construction remain separate
([`cmd/go/internal/work/exec.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go)).
WW adopts that exact-tool boundary without adding a command schema, generalized
action graph, scheduler, manifest, cache protocol, or package-manager behavior.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -1110,10 +1110,9 @@ fn workescape(s: str) str = {
append(traceenv, strings.concat("WW_PACKAGE_LINKER_TRACE=", linkertrace));
append(traceenv, strings.concat("WW_PACKAGE_W6C=", driver("w6c")));
append(traceenv, strings.concat("WW_PACKAGE_W6L=", driver("w6l")));
// The WW driver intentionally takes sibling tools rather than WW_W6C
// overrides. Run an exact copied driver beside narrow tracing wrappers so
// its real compiler/linker actions are observable without changing that
// production contract.
// Exercise default sibling discovery independently of the exact-path
// override contract covered below: the copied driver sits beside narrow
// tracing wrappers for its real compiler and linker actions.
let wwtracebin: str = strings.concat(repo(), "/out/package-trace-",
workescape(root));
assert(os.mkdir(wwtracebin, 448i32) == 0);
@@ -2143,7 +2142,8 @@ fn workescape(s: str) str = {
let trace: str = strings.concat(root, "/compiler.trace");
let wrapper: str = strings.concat(root, "/trace-w6c.sh");
writefile(trace, "");
writefile(wrapper, strings.concat(
writeexecutable(wrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_PACKAGE_TRACE\"\n",
"exec \"$WW_PACKAGE_W6C\" \"$@\"\n"));
let baseenv: []str = os.getenvs();
@@ -2157,7 +2157,7 @@ fn workescape(s: str) str = {
};
ei += 1;
};
append(env, strings.concat("WW_W6C=/bin/sh ", wrapper));
append(env, strings.concat("WW_W6C=", wrapper));
append(env, strings.concat("WW_PACKAGE_TRACE=", trace));
append(env, strings.concat("WW_PACKAGE_W6C=", driver("w6c")));
let bin: str = strings.concat(root, "/diamond.test");
@@ -2187,6 +2187,245 @@ fn workescape(s: str) str = {
clean(root);
};
@test fn exact_package_tool_argv_stage_parity() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source tree");
let leaf: str = strings.concat(source, "/leaf");
let dep: str = strings.concat(source, "/dep");
let target: str = strings.concat(source, "/target");
let tools: str = strings.concat(root, "/tool wrappers");
assert(os.mkdir(source, 448i32) == 0);
assert(os.mkdir(leaf, 448i32) == 0);
assert(os.mkdir(dep, 448i32) == 0);
assert(os.mkdir(target, 448i32) == 0);
assert(os.mkdir(tools, 448i32) == 0);
writefile(strings.concat(leaf, "/leaf.ww"), strings.concat(
"package leaf;\n",
"export fn value() i32 = { return 40; };\n"));
writefile(strings.concat(dep, "/dep.ww"), strings.concat(
"package dep;\nimport leaf;\n",
"export fn value() i32 = { return leaf.value() + 2; };\n"));
writefile(strings.concat(target, "/target.ww"), strings.concat(
"package target;\nimport dep;\n",
"fn value() i32 = { return dep.value(); };\n"));
writefile(strings.concat(target, "/target_test.ww"), strings.concat(
"package target;\n",
"@test fn exact_argv() void = { assert(value() == 42); };\n"));
let compilerwrapper: str = strings.concat(tools, "/w6c wrapper.sh");
let assemblerwrapper: str = strings.concat(tools, "/w6a wrapper.sh");
let linkerwrapper: str = strings.concat(tools, "/w6l wrapper.sh");
let failurewrapper: str = strings.concat(tools, "/failing w6c wrapper.sh");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ARGV_COMPILER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ARGV_COMPILER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ARGV_COMPILER_TRACE\"\n",
"exec \"$WW_ARGV_REAL_COMPILER\" \"$@\"\n"));
writeexecutable(assemblerwrapper, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ARGV_ASSEMBLER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ARGV_ASSEMBLER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ARGV_ASSEMBLER_TRACE\"\n",
"exec \"$WW_ARGV_REAL_ASSEMBLER\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ARGV_LINKER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ARGV_LINKER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ARGV_LINKER_TRACE\"\n",
"exec \"$WW_ARGV_REAL_LINKER\" \"$@\"\n"));
writeexecutable(failurewrapper, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ARGV_FAILURE_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ARGV_FAILURE_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ARGV_FAILURE_TRACE\"\n",
"exit 23\n"));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let assemblers: []str = ["w6a", "w6a_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let tags: []str = ["c", "ww"];
let bin: str = strings.concat(root, "/published test binary");
let workroot: str = strings.concat(bin, ".sepwork");
let work: str = strings.concat(workroot, "/");
let referenceunit: str = "";
let referencewwi: str = "";
let referencearchive: str = "";
let referencebin: str = "";
let referencecompiler: str = "";
let referenceassembler: str = "";
let referencelinker: str = "";
let referenceout: str = "";
let referenceerr: str = "";
let referencefailure: str = "";
let baseenv: []str = os.getenvs();
let si: i32 = 0;
for (si < stages.len) {
let compilertrace: str = strings.concat(root, "/", tags[si],
" compiler trace");
let assemblertrace: str = strings.concat(root, "/", tags[si],
" assembler trace");
let linkertrace: str = strings.concat(root, "/", tags[si],
" linker trace");
let failuretrace: str = strings.concat(root, "/", tags[si],
" failure trace");
writefile(compilertrace, "");
writefile(assemblertrace, "");
writefile(linkertrace, "");
writefile(failuretrace, "");
let env: []str = alloc([], (baseenv.len + 9): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_COMPILER_TRACE=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_ASSEMBLER_TRACE=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_LINKER_TRACE=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_REAL_COMPILER=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_REAL_ASSEMBLER=")
&& !strings.hasprefix(baseenv[ei],
"WW_ARGV_REAL_LINKER=")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_W6A=", assemblerwrapper));
append(env, strings.concat("WW_W6L=", linkerwrapper));
append(env, strings.concat("WW_ARGV_COMPILER_TRACE=", compilertrace));
append(env, strings.concat("WW_ARGV_ASSEMBLER_TRACE=", assemblertrace));
append(env, strings.concat("WW_ARGV_LINKER_TRACE=", linkertrace));
append(env, strings.concat("WW_ARGV_REAL_COMPILER=",
driver(compilers[si])));
append(env, strings.concat("WW_ARGV_REAL_ASSEMBLER=",
driver(assemblers[si])));
append(env, strings.concat("WW_ARGV_REAL_LINKER=",
driver(linkers[si])));
let av: []str = [driver(stages[si]), "test", "-c", "-o", bin,
"-I", source, target];
let out: commandout;
runcommandenv(root, strings.concat("exact-argv-", tags[si]), av, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0);
let rootunit: str = readfile(strings.concat(work,
"__ww-test-000-same.unit.ww"));
let depunit: str = readfile(strings.concat(work, "dep.unit.ww"));
assert(has(rootunit, "//ww:module dep\n"));
assert(!has(rootunit, "//ww:module leaf\n"));
assert(has(depunit, "//ww:module leaf\n"));
assert(os.exists(strings.concat(work, "leaf.wwi")));
assert(os.exists(strings.concat(work, "leaf.a")));
assert(os.exists(strings.concat(work, "dep.wwi")));
assert(os.exists(strings.concat(work, "dep.a")));
assert(!os.exists(strings.concat(work,
"__ww-test-000-same.wwi")));
assert(!os.exists(strings.concat(work,
"__ww-test-000-same.a")));
assert(os.exists(bin));
let ctrace: str = readfile(compilertrace);
let atrace: str = readfile(assemblertrace);
let ltrace: str = readfile(linkertrace);
assert(occurrences(ctrace, strings.concat("<", work,
"leaf.unit.ww>")) == 1);
assert(occurrences(ctrace, strings.concat("<", work,
"dep.unit.ww>")) == 1);
assert(occurrences(ctrace, strings.concat("<", work,
"__ww-test-000-same.unit.ww>")) == 1);
assert(pos(ctrace, strings.concat("<", work, "leaf.unit.ww>"))
< pos(ctrace, strings.concat("<", work, "dep.unit.ww>")));
assert(pos(ctrace, strings.concat("<", work, "dep.unit.ww>"))
< pos(ctrace, strings.concat("<", work,
"__ww-test-000-same.unit.ww>")));
assert(has(ctrace, strings.concat("BEGIN<-c><-I><", work,
"dep.wwi><-o><", work, "dep.s><", work, "dep.unit.ww>")));
assert(has(ctrace, strings.concat(
"BEGIN<-T><--test-support-module><test><-c><-o><", work,
"__ww-test-000-same.s><", work,
"__ww-test-000-same.unit.ww>")));
assert(has(atrace, strings.concat("BEGIN<-o><", work,
"dep.o><", work, "dep.s>")));
assert(occurrences(ltrace, "\n") == 1);
assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work,
"__ww-test-000-same.o>")));
assert(has(ltrace, strings.concat("<", work, "dep.a>")));
assert(has(ltrace, strings.concat("<", work, "leaf.a>")));
assert(has(ltrace, strings.concat("<", work, "test.a>")));
assert(!has(ltrace, ".wwi>"));
let runav: []str = [bin];
runcommand(root, strings.concat("exact-argv-run-", tags[si]), runav,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "exact_argv ... ok\n"));
if (si == 0) {
referenceunit = strings.dup(rootunit);
referencewwi = strings.dup(readfile(strings.concat(work,
"dep.wwi")));
referencearchive = strings.dup(readfile(strings.concat(work,
"dep.a")));
referencebin = strings.dup(readfile(bin));
referencecompiler = strings.dup(ctrace);
referenceassembler = strings.dup(atrace);
referencelinker = strings.dup(ltrace);
referenceout = strings.dup(out.stdout);
referenceerr = strings.dup(out.stderr);
} else {
assert(same(referenceunit, rootunit));
assert(same(referencewwi,
readfile(strings.concat(work, "dep.wwi"))));
assert(same(referencearchive,
readfile(strings.concat(work, "dep.a"))));
assert(same(referencebin, readfile(bin)));
assert(same(referencecompiler, ctrace));
assert(same(referenceassembler, atrace));
assert(same(referencelinker, ltrace));
assert(same(referenceout, out.stdout));
assert(same(referenceerr, out.stderr));
};
clean(workroot);
clean(bin);
let failenv: []str = alloc([], (env.len + 2): u64)!;
ei = 0;
for (ei < env.len) {
if (!strings.hasprefix(env[ei], "WW_W6C=")
&& !strings.hasprefix(env[ei],
"WW_ARGV_FAILURE_TRACE=")) {
append(failenv, env[ei]);
};
ei += 1;
};
append(failenv, strings.concat("WW_W6C=", failurewrapper));
append(failenv, strings.concat("WW_ARGV_FAILURE_TRACE=", failuretrace));
runcommandenv(root, strings.concat("exact-argv-fail-", tags[si]), av,
failenv, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "ww: w6c failed for leaf\n"));
assert(has(readfile(failuretrace), strings.concat("<-I><", work,
"leaf.wwi>")));
if (si == 0) { referencefailure = strings.dup(out.stderr); }
else { assert(same(referencefailure, out.stderr)); };
clean(workroot);
if (os.exists(bin)) { clean(bin); };
si += 1;
};
clean(root);
};
@test fn package_graph_diagnostics_are_stable() void = {
let root: str = fresh();
let missing: str = strings.concat(root, "/missing");
@@ -2640,9 +2879,9 @@ fn runtimepath(relative: str) str = {
let ctraced: str = readfile(linktrace);
assert(has(ctraced, strings.concat(" -L ", root, " -l extra")));
// WWstage intentionally locates sibling tools. Copy the real driver beside
// a tracing linker and exact compiler/assembler siblings to inspect its
// joined native-linker flag contract.
// Keep this row on default sibling discovery: a copied driver beside a
// tracing linker and exact compiler/assembler siblings exposes WWstage's
// joined native-linker flag contract independently of tool overrides.
let tracedir: str = strings.concat(repo(), "/out/link-flag-trace-",
workescape(root));
assert(os.mkdir(tracedir, 448i32) == 0);

View File

@@ -421,7 +421,8 @@ fn writediamond(td: str, reverse: bool) str = {
let trace: str = strings.concat(td, "/compiler.trace");
let wrapper: str = strings.concat(td, "/trace-w6c.sh");
testenv.writefile(trace, "");
testenv.writefile(wrapper, strings.concat(
testenv.writeexecutable(wrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_LOCALBUILD_TRACE\"\n",
"exec \"$WW_LOCALBUILD_W6C\" \"$@\"\n"));
let baseenv: []str = os.getenvs();
@@ -435,7 +436,7 @@ fn writediamond(td: str, reverse: bool) str = {
};
ei += 1;
};
append(env, strings.concat("WW_W6C=/bin/sh ", wrapper));
append(env, strings.concat("WW_W6C=", wrapper));
append(env, strings.concat("WW_LOCALBUILD_TRACE=", trace));
append(env, strings.concat("WW_LOCALBUILD_W6C=",
testenv.driver("w6c")));

View File

@@ -76,6 +76,17 @@ export fn writefile(path: str, content: str) void = {
assert(os.close(fd) == 0);
};
export fn writeexecutable(path: str, content: str) void = {
let fd: i32 = os.open(path,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 448i32);
assert(fd >= 0);
match (os.writeall(fd, content.ptr, content.len: u64)) {
case let n: i64 => assert(n == content.len: i64);
case let e: os.oserror => abort("write failed");
};
assert(os.close(fd) == 0);
};
export fn exists(path: str) bool = {
return os.access(path, 0i32) == 0;
};