driver: implement post-target run argv semantics

This commit is contained in:
2026-08-23 17:33:31 +09:00
parent 0b24b11d65
commit 29122f2a48
6 changed files with 579 additions and 17 deletions

View File

@@ -7849,14 +7849,11 @@ parse_build_flags(const char *cmd, int argc, char **argv,
return -1;
} else if (*src_out == NULL) {
*src_out = argv[i];
/* Go's FlagSet stops at the first positional. For build,
* everything after it is package-request input, even "--". */
if (strcmp(cmd, "build") == 0) {
i++;
break;
}
} else {
break; /* leave remaining argv to caller (run-args) */
/* Go's FlagSet stops at the first positional. Build leaves
* the suffix as package-request input; run passes it verbatim
* to the selected program, including flag-like operands. */
i++;
break;
}
}
return i;

View File

@@ -2300,7 +2300,7 @@ The default workflow is deliberately short:
```sh
ww init example.org/hello
ww build
ww run -- argument
ww run . argument
ww test .
```
@@ -2313,8 +2313,9 @@ shown in verbose output.
`ww build [DIR|PRODUCT]` builds the default root product or one named product.
The default profile is the fully specified `debug` profile; `--profile=release`
selects the toolchain's immutable release profile. `ww run` first performs that
same build, then runs only a product with `H = B`; arguments after `--` are never
interpreted by the build.
same build, then runs only a product with `H = B`. After one explicit run target
is selected, every remaining operand is program input and is never interpreted
as a build option. A leading `--` is not the target boundary.
There is no command that means “build and opportunistically download whatever is
missing.” If a locked source or toolchain is absent, the diagnostic names its
@@ -11168,6 +11169,165 @@ source packages, shared test-process state and failure topology, RE2-compatible
flat `-run`, finite special-source handling, or external-driver interruption
recovery.
### 11.57 Implemented post-target `ww run` argument boundary
After one explicit `ww run` target has been selected, every later operand is
now program input. Neither driver reparses that suffix as build options. Known
and unknown option spellings, would-be option values, a lone `--`, empty
strings, later ordinary operands, and later `.ww` spellings retain their exact
bytes and order in the child vector. WW still supports only one selected run
target: this boundary does not turn a later `.ww` spelling into a second source
file.
#### Pinned authority, official tests, and applicability
The sole semantic authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- **behavior directly implemented or asserted by pinned Go** — the Go command
parses a run command's registered flags before entering `runRun` and passes
only `Flag.Args()` to it
([`cmd/go/main.go`, lines 312322](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/main.go#L312-L322)).
The standard flag parser stops at the first non-flag positional; it consumes
`--` only when that spelling occurs before the positional boundary
([`flag/flag.go`, lines 10741089 and 11491176](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/flag/flag.go#L1074-L1089)).
- **behavior directly implemented or asserted by pinned Go** — `runRun`
consumes either the contiguous named-file prefix or one selected package,
leaves the suffix as `cmdArgs`, and attaches those exact arguments to the run
action
([`cmd/go/internal/run/run.go`, lines 96140 and 170173](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/run/run.go#L96-L140)).
- **behavior directly implemented or asserted by pinned Go** — official
regression test
[`cmd/go/testdata/script/mod_run_flags_issue64738.txt`, lines 14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/mod_run_flags_issue64738.txt#L1-L4)
asserts that `-p ignored` after a requested package is program input, not a
`cmd/go` flag. Official
[`cmd/go/testdata/script/run_dirs.txt`, lines 120](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/run_dirs.txt#L1-L20)
separately anchors Go's contiguous multi-file prefix; that source-set rule
remains open in WW.
- **behavior directly implemented or asserted by pinned Go** — Go deliberately
does not preserve the compiled program's exact nonzero exit status
([`cmd/go/internal/run/run.go`, lines 56 and 198210](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/run/run.go#L198-L210));
command error accounting owns the resulting Go-command status
([`cmd/go/internal/base/base.go`, lines 218246](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/base.go#L218-L246)).
That independent exit-status difference is not credited to this slice.
- **behavior derived from the pinned implementation** — WW's one explicit
local run target supplies the same honest semantic boundary without modules,
manifests, registries, network resolution, generalized imports, or source
build expressions. WW's implicit default-current-directory extension has no
explicit target token, so this slice leaves its leading option parsing
unchanged. A leading or pre-target `--` therefore retains WW's existing
exact unknown-flag rejection and is not claimed as Go `FlagSet` terminator
parity.
Before this slice, **directly measured WW behavior** was identical in Cstage
and WWstage but differed from the pinned boundary. Immediately target-following
`-p ignored` and `-- -p ignored` returned status 2, empty stdout, and exact
stderr `ww run: unknown flag\n`; `-o sentinel` and `-I path` were consumed by
the driver; and only a suffix after a second nonflag was passed unchanged.
Named-source and directory-package probes agreed on status, stdout, stderr,
and diagnostic order. Accepted runs loaded one target and its ordinary import
closure, constructed the private run action, compiled, assembled, archived,
linked, executed, propagated WW's established child status, and removed the
private executable and `.sepwork`. Rejected flag rows stopped before target
resolution, graph/action construction, tools, runtime, or scratch creation.
#### Ownership, source selection, and the four permanent axes
- **directly measured WW behavior** — Cstage owns the boundary in
`parse_build_flags` and its `do_run` consumer; WWstage owns the semantic twin
in its `dorun` parser and executor. No loader, package coordinator, compiler,
assembler, archiver, linker, test coordinator, checker, import binder, or
runtime library can repair a driver option that was already consumed.
- **behavior derived from the pinned implementation** — the primary axis is
build/run execution semantics. Driver options are recognized before the
first explicit target. That target alone controls resolution; the complete
later suffix controls only child invocation.
- **behavior derived from the pinned implementation** — the test axis is an
explicit non-effect. `ww test` option parsing, target discovery, filters,
source variants, generated harness, retained products, execution topology,
and result accounting do not use this run boundary.
- **behavior derived from the pinned implementation** — the package axis is an
explicit non-effect. The suffix is never searched, statted, opened, or
classified as source. Declared-name validation, source membership, command
classification, initializer topology, and canonical package identity remain
those of the one selected target.
- **behavior derived from the pinned implementation** — the import axis is an
explicit non-effect. Dotted spelling, aliases, local/vendor search, binding,
visibility, cycles, graph edges, interface ownership, and initialization
order are determined only by the selected source closure. Runtime argv is
never package, import, graph, action, symbol, artifact, `.wwi`, publication,
or persistence identity.
Direct roots retain `__root.*`; dotted directories retain dotted package,
import, action, symbol, artifact, and semantic identities. Physical target
spellings and paths remain loader or presentation observations. Post-target
arguments add no root, edge, action, source, or invalidation input and cannot
change compilation or comparable artifact bytes.
#### Phase order, diagnostics, and lifecycle
- **behavior derived from the pinned implementation** — target resolution,
loading, import closure, graph construction, compilation, assembly,
in-process archiving, and linking retain their existing order and inputs.
After a successful private link, the child vector is the private executable
at index 0 followed by the exact post-target suffix. The suffix reaches no
earlier phase.
- **directly measured WW behavior** — `os.args()` exposes that complete vector,
including the PID-bearing private executable path at index 0. WW currently
propagates an ordinary child's exact exit code. The argument repair changes
only indices 1 onward; PID presentation and exact child-status propagation
are preserved, including their independent difference from pinned Go.
- **behavior derived from the pinned implementation** — known, unknown,
incomplete, or `--` option spellings before the target retain their existing
driver diagnostics and status. No spelling after the target can emit a
driver-option diagnostic. Missing, invalid, non-main, or producer-failing
targets diagnose before runtime; a valid target starts and thereafter owns
output and failure caused by its arguments.
- **behavior derived from the pinned implementation** — run products remain
request-private. No public executable, retained test product, semantic fact,
work record, transaction, result, or cache entry is published. Post-target
argv enters no unit, interface, assembly, object, archive, initializer,
executable, stamp, or persistence byte and creates no reuse or invalidation
key. Ordinary build and every test route are byte-for-byte non-effects.
- **behavior derived from the pinned implementation** — target or producer
failure starts no program and follows ordinary rollback. Runtime nonzero
follows the established WW status mapping after successful private linking.
Existing unrelated public and committed semantic bytes remain untouched.
Normal success, producer failure, runtime failure, and concurrent runs remove
each request's owned private executable, `.sepwork`, stage, transaction,
capture, result, request, descriptor, and child. Parser state and argv are
invocation-local, so overlapping suffixes cannot cross between requests.
- **behavior derived from the pinned implementation** — Cstage and WWstage
must select the same boundary and retain exact status, stdout, stderr,
diagnostic order, runtime argv, normal cleanup, and comparable build-artifact
byte identity. The existing PID-bearing private path difference outside
stable comparisons is not reclassified by this slice.
The WW-native observer `run_post_target_arguments_are_program_argv` covers both
driver stages with literal named-source and directory-package targets. It
checks known separate and joined option spellings, unknown options, would-be
values, singleton value-taking spellings, `--`, later nonflags and `.ww`, an
empty string, pre-target controls, no-operand default-dot selection, diagnostic
precedence, exact child status and output, concurrent isolation, build/test
controls, artifact-byte parity, and normal residue cleanup.
No signal or process-supervision owner changes. Direct external SIGTERM during
blocked persistent compilation still can leave the owned compiler alive and
exactly three fixed `.new` stages, poisoning the next request while preserving
prior public and committed semantic bytes. That verified interruption gap
remains open; blind stage deletion is not this argument-boundary repair.
No serialized representation changes. Build workdir format remains `18`, test
workdir format remains `19`, and semantic storage format remains `3`; AST and
`.wwi` schemas, action descriptors, request protocols, transaction markers,
and stored facts are unchanged. This closes only the post-target argv slice.
Regular or missing `_test.ww`, missing `.ww`, hidden named sources, multiple
leading sources and their source-set boundary, shared test-package state,
panic/exit/Fatal/FailNow topology, RE2-compatible flat `-run`, literal
nonregular named-source behavior, three-way no-buildable-source diagnostics,
Go-like run exit-status mapping, and external-driver interruption recovery
remain open where applicable.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -703,6 +703,32 @@ ImportPath = ident { "." ident } .
arguments and result; this rule does not adopt Go's source signature. An
ordinary import of a package declared `main` is rejected, except for the
toolchain's colocated external-test wiring.
- For `ww run`, the first explicit non-option operand selects the one run
target. Every later operand is program input and is passed unchanged and in
order after the private executable's `argv[0]`; it is not reparsed as a
driver option. This includes known or unknown option spellings, would-be
option values, a lone `--`, empty arguments, ordinary nonflags, and later
`.ww` spellings. A later `.ww` is therefore argv, not another source in a
command-line package; multiple named sources remain unsupported. Registered
driver options before the target retain their established spelling, value,
repetition, and diagnostic rules. A leading or otherwise pre-target `--`
retains WW's exact unknown-flag rejection rather than acting as a general
terminator. With no explicit target, `ww run` retains its implicit current-
directory selection and has no post-target suffix.
Only the selected target and pre-target driver options affect resolution,
source loading, package/import identity, graph construction, compiler,
assembler, archiver, linker, private executable bytes, reuse, or
invalidation. A missing, invalid, non-main, or producer-failing target
diagnoses before runtime and no suffix spelling can mask that result. A
successful private link executes with the exact suffix and retains WW's
existing stdout, stderr, and exact child-status propagation. The private run
product is removed normally and publishes no executable or persistent
semantic state. `ww build`, every `ww test` route, package membership, and
dotted import semantics are unchanged. Direct roots remain `__root.*`,
dotted directories retain dotted identity, and argv never becomes package,
import, action, symbol, artifact, `.wwi`, publication, or persistence
identity. Build workdir format remains 18, test workdir format remains 19,
and semantic storage format remains 3.
- For `ww build`, the output-option name is exactly `o`. The accepted forms are
`-o VALUE`, `--o VALUE`, `-o=VALUE`, and `--o=VALUE`. An equals form splits
at its first `=` and preserves every later byte, including further `=`

View File

@@ -479,6 +479,82 @@ supervision is unchanged: the known orphan compiler, fixed `.new` staging, and
later persistent-request poisoning remain open and are not credited to this
slice.
The run front has one separate post-target argument contract. The sole
authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- **behavior directly implemented or asserted by pinned Go** — command flag
parsing supplies only its positional suffix to `runRun`
(`cmd/go/main.go:312322`; `flag/flag.go:10741089,11491176`), and
`runRun` consumes the named-file prefix or one package before attaching the
untouched suffix to the run action
(`cmd/go/internal/run/run.go:96140,170173`).
- **behavior directly implemented or asserted by pinned Go** — official
`cmd/go/testdata/script/mod_run_flags_issue64738.txt:14` proves that
target-following `-p ignored` is program input rather than a Go-command
option. `cmd/go/testdata/script/run_dirs.txt:120` anchors the independent
contiguous named-file prefix, which WW does not claim here.
- **behavior derived from the pinned implementation** — after WW's one
explicit local run target, every remaining operand is runtime argv. The
rule honestly applies without importing Go modules or manifests. WW's
default-dot extension has no explicit target boundary, and leading or
pre-target `--` remains its existing unknown flag rather than gaining
general Go flag-terminator semantics.
Before the repair, **directly measured WW behavior** was stage-equal but
different: both drivers rejected immediately target-following `-p ignored` and
`-- -p ignored` at status 2 with empty stdout and exact unknown-flag stderr,
consumed target-following `-o` and `-I` values as driver configuration, and
passed the ordered suffix only after a second nonflag. Accepted controls loaded
one target and its ordinary import closure and completed the private compiler,
assembler, archiver, linker, and runtime route; rejected rows stopped before
loading, actions, tools, runtime, or scratch.
The focused `run_post_target_arguments_are_program_argv` observer owns the
completed boundary in `test/package/package_test.ww`. Across Cstage and WWstage
it uses both a literal named source and a directory package and requires exact
ordered delivery for separate and joined known-option spellings, unknown
options, would-be values, a singleton value-taking spelling, `--`, later
nonflags and `.ww`, and an empty string. It separately requires pre-target
options and their missing/unknown diagnostics to remain driver-owned, leading
`--` to keep its current rejection, no-operand run to keep default-dot
selection, and target load/producer diagnostics to precede inert suffix bytes.
Valid programs expose the private executable at `os.args()[0]`, receive the
exact suffix at indices 1 onward, and retain WW's existing stdout, stderr, and
exact child-status mapping. Pinned Go's distinct command-level nonzero status
mapping (`cmd/go/internal/run/run.go:56,198210` and
`cmd/go/internal/base/base.go:218246`) remains an open difference.
The observer also treats all four permanent axes as one contract. The build/run
axis changes only the parser-to-child boundary. Build and test controls retain
their status, streams, action inputs, and comparable artifact bytes. Package
membership and command classification still come only from the selected
target, while dotted-import resolution, edges, interfaces, visibility, and
initialization still come only from that target's closure. Direct actions
remain `__root.*`; dotted directories retain dotted identity; argv creates no
package, import, graph, action, symbol, artifact, `.wwi`, publication,
persistence, reuse, or invalidation identity.
Post-target bytes therefore cannot change compiler, assembler, archiver,
linker, initializer, or private executable bytes. Run publishes no public
product or semantic state. Normal success, target/producer failure, runtime
nonzero, and overlapping requests must keep suffixes isolated and remove each
owned private executable, `.sepwork`, stage, transaction, capture, result,
request, descriptor, and child. Cstage and WWstage must agree on stable status,
stdout, stderr, diagnostic order, runtime argv, cleanup, and comparable
artifact bytes. No AST, interface, descriptor, request, transaction, or
persistent schema changes; build workdir format remains 18, test workdir format
remains 19, semantic storage format remains 3, and no test-result cache is
introduced.
The observer makes no claim for regular or missing `_test.ww`, missing `.ww`,
hidden named sources, multiple leading source operands and source-set
boundaries, literal nonregular named sources, shared test-package state,
panic/exit/Fatal/FailNow topology, Go-compatible RE2 `-run`, three-way
no-buildable-source causes, or Go-like run exit-status mapping. External-driver
SIGTERM supervision is also unchanged: the verified orphan compiler, three
fixed `.new` stages, and later persistent-request poisoning remain open.
An existing local directory whose requested build basename ends `.ww`
(including a visible `_test.ww` symlink to a directory) remains a directory
package, not a raw named test source. WWstage `ww build` now uses the same

View File

@@ -9832,12 +9832,11 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
}; }; }; };
i += 1;
} else {
if (src == nil) {
src = p;
i += 1;
} else {
passstart = i;
};
src = p;
i += 1;
// The first target ends driver option parsing. The exact
// suffix, including flag-like operands, belongs to the program.
passstart = i;
};
};
};

View File

@@ -25131,3 +25131,307 @@ fn documentationclean(root: str) void = {
&& !directoryhasfragment(root, ".request"));
clean(root);
};
// An explicit run target ends driver-option parsing. The child receives every
// later string unchanged, including spellings that would have been options at
// the driver front; argv[0] remains WW's private PID-bearing executable path.
fn runargvexpect(out: *commandout, code: i32, stdout: str, stderr: str,
message: str) void = {
if (out.termination != exec.termination.EXIT || out.code != code
|| !same(out.stdout, stdout) || !same(out.stderr, stderr)) {
abort(message);
};
};
fn runargvrequire(ok: bool, message: str) void = {
if (!ok) { abort(message); };
};
fn runargvprivateclean(record: str, message: str) void = {
let path: str = readfile(record);
runargvrequire(path.len != 0 && !os.exists(path)
&& !os.exists(strings.concat(path, ".sepwork")), message);
};
fn runargvnormalizedstderr(stderr: str) str = {
let prefix: str = "/tmp/ww_run_";
let begin: i32 = pos(stderr, prefix);
runargvrequire(begin >= 0, "run argv: missing private diagnostic path");
let digits: i32 = begin + prefix.len;
let end: i32 = digits;
for (end < stderr.len && stderr[end] >= '0' && stderr[end] <= '9') {
end += 1;
};
runargvrequire(end > digits,
"run argv: malformed private diagnostic path");
return strings.concat(strings.sub(stderr, 0, begin), "$RUN",
strings.sub(stderr, end, stderr.len));
};
@test fn run_post_target_arguments_are_program_argv() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let command: str = strings.concat(source, "/cmd/argv");
let library: str = strings.concat(source, "/lib/argv");
let tests: str = strings.concat(source, "/proof/tests");
mkdirall(command); mkdirall(library); mkdirall(tests);
let named: str = strings.concat(source, "/named.ww");
let program: str = strings.concat(
"package main;\nimport os;\nimport strings;\n",
"fn main() i32 = {\n",
" let av: []str = os.args(); let i: i32 = 1;\n",
" if (av.len > 1) {\n",
" let fd: i32 = os.open(av[1], os.flag.WRONLY | os.flag.CREATE |\n",
" os.flag.TRUNC, 384i32); if (fd < 0) { return 96; };\n",
" os.write(fd, av[0].ptr, av[0].len: u64); os.close(fd);\n",
" };\n",
" for (i < av.len) {\n",
" os.write(os.STDOUT_FILENO, \"<\".ptr, 1u64);\n",
" os.write(os.STDOUT_FILENO, av[i].ptr, av[i].len: u64);\n",
" os.write(os.STDOUT_FILENO, \">\\n\".ptr, 2u64); i += 1;\n",
" };\n",
" if (av.len == 3 && strings.compare(av[2], \"exit-zero\") == 0) {\n",
" return 0;\n",
" }; return 47;\n};\n");
writefile(named, program);
writefile(strings.concat(command, "/main.ww"), program);
writefile(strings.concat(library, "/library.ww"),
"package argvlib;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(tests, "/tests.ww"),
"package argvtests;\nfn value() i32 = { return 1; };\n");
writefile(strings.concat(tests, "/tests_test.ww"), strings.concat(
"package argvtests;\n",
"@test fn ordinary() void = { assert(value() == 1); };\n"));
let expected: str = strings.concat(
"<-I>\n<runtime-include>\n<--I=joined-include>\n",
"<-o>\n<runtime-output>\n<--o=joined-output>\n",
"<-L>\n<runtime-library-dir>\n<-l>\n<runtime-library>\n",
"<-p>\n<ignored>\n<-->\n<later>\n<later.ww>\n<>\n<-I>\n");
let suffix: []str = ["-I", "runtime-include", "--I=joined-include",
"-o", "runtime-output", "--o=joined-output", "-L",
"runtime-library-dir", "-l", "runtime-library", "-p", "ignored",
"--", "later", "later.ww", "", "-I"];
let stages: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "ww"];
let targets: []str = [named, "cmd.argv", command];
let targettags: []str = ["named", "dotted", "directory"];
let out: commandout;
let si: i32 = 0;
for (si < stages.len) {
let ti: i32 = 0;
for (ti < targets.len) {
let record: str = strings.concat(root, "/private-", tags[si],
"-", targettags[ti]);
let ignoredoutput: str = strings.concat(root, "/ignored-run-",
tags[si], "-", targettags[ti]);
let av: []str = [driver(stages[si]), "run", "-I", source,
"-o", ignoredoutput, targets[ti]];
append(av, record);
let ai: i32 = 0;
for (ai < suffix.len) { append(av, suffix[ai]); ai += 1; };
runcommand(root, strings.concat("run-argv-", tags[si], "-",
targettags[ti]), av,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 47,
strings.concat("<", record, ">\n", expected), "",
"run argv: target suffix result");
runargvrequire(!os.exists(ignoredoutput)
&& !os.exists(strings.concat(ignoredoutput, ".sepwork")),
"run argv: run published an output");
runargvprivateclean(record, "run argv: runtime-nonzero cleanup");
ti += 1;
};
let successrecord: str = strings.concat(root, "/private-success-",
tags[si]);
let successav: []str = [driver(stages[si]), "run", named,
successrecord, "exit-zero"];
runcommand(root, strings.concat("run-argv-success-", tags[si]),
successav, (120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 0,
strings.concat("<", successrecord, ">\n<exit-zero>\n"), "",
"run argv: runtime-success result");
runargvprivateclean(successrecord, "run argv: runtime-success cleanup");
// Before an explicit target, the existing option grammar remains in
// force. No-operand run retains its implicit current-directory target.
let unknownav: []str = [driver(stages[si]), "run", "-p", named];
runcommand(root, strings.concat("run-argv-unknown-", tags[si]),
unknownav, time.second, &out);
runargvexpect(&out, 2, "", "ww run: unknown flag\n",
"run argv: pre-target unknown flag");
let missingflagav: []str = [driver(stages[si]), "run", "-I"];
runcommand(root, strings.concat("run-argv-missing-flag-", tags[si]),
missingflagav, time.second, &out);
runargvexpect(&out, 2, "", "ww run: -I needs an argument\n",
"run argv: pre-target missing option value");
let leadingstopav: []str = [driver(stages[si]), "run", "--", named];
runcommand(root, strings.concat("run-argv-leading-stop-", tags[si]),
leadingstopav, time.second, &out);
runargvexpect(&out, 2, "", "ww run: unknown flag\n",
"run argv: leading terminator control");
let defaultav: []str = [driver(stages[si]), "run"];
runcommanddir(root, strings.concat("run-argv-default-", tags[si]),
command, defaultav,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 47, "", "", "run argv: default-dot control");
// Target selection and loading diagnose before inert runtime bytes.
let missingtarget: []str = [driver(stages[si]), "run", "-I", source,
"missing.argv", "-p", "ignored"];
runcommand(root, strings.concat("run-argv-missing-target-", tags[si]),
missingtarget, time.second, &out);
runargvexpect(&out, 1, "",
"ww run: cannot find module missing.argv\n",
"run argv: missing target precedence");
let nonmain: []str = [driver(stages[si]), "run", "-I", source,
"lib.argv", "--", "later.ww"];
runcommand(root, strings.concat("run-argv-non-main-", tags[si]),
nonmain, time.second, &out);
runargvexpect(&out, 1, "",
"ww: package lib.argv is not a main package\n",
"run argv: non-main target precedence");
si += 1;
};
// A selected main that reaches the compiler still diagnoses before any
// flag-like suffix can be interpreted or any runtime side effect can occur.
let broken: str = strings.concat(source, "/broken.ww");
let brokenmarker: str = strings.concat(root, "/broken-ran");
writefile(broken, strings.concat(
"package main;\nimport os;\nfn main() i32 = {\n",
" let fd: i32 = os.open(\"", brokenmarker, "\", os.flag.WRONLY |\n",
" os.flag.CREATE | os.flag.TRUNC, 384i32);\n",
" if (fd >= 0) { os.close(fd); }; return missing;\n};\n"));
let producerstderr: str = "";
let producerwant: str = strings.concat(
"$RUN/main.sepwork/__root.unit.ww:7:41: error: undefined: missing\n",
"ww: w6c failed for (root)\n");
si = 0;
for (si < stages.len) {
let brokenav: []str = [driver(stages[si]), "run", broken,
"-p", "ignored", "--", brokenmarker];
runcommand(root, strings.concat("run-argv-producer-", tags[si]),
brokenav, (120i64 * (time.second: i64)): time.duration, &out);
runargvrequire(out.termination == exec.termination.EXIT
&& out.code == 1 && out.stdout.len == 0
&& occurrences(out.stderr, "undefined: missing") == 1
&& occurrences(out.stderr, "ww: w6c failed for (root)") == 1
&& pos(out.stderr, "undefined: missing")
< pos(out.stderr, "ww: w6c failed for (root)")
&& !has(out.stderr, "unknown flag") && !os.exists(brokenmarker),
"run argv: producer failure precedence");
let normalized: str = runargvnormalizedstderr(out.stderr);
runargvrequire(same(normalized, producerwant),
"run argv: exact producer diagnostic");
if (si == 0) { producerstderr = strings.dup(normalized); }
else {
runargvrequire(same(producerstderr, normalized),
"run argv: producer diagnostic stage parity");
};
si += 1;
};
// Runtime suffixes are request-local even while the two fronts execute
// concurrently and share their source closure.
let crecord: str = strings.concat(root, "/private-parallel-c");
let wrecord: str = strings.concat(root, "/private-parallel-ww");
let cav: []str = [driver("ww"), "run", named, crecord,
"c-only", "", "-I"];
let wav: []str = [driver("ww_ww"), "run", "-I", source, command,
wrecord, "ww-only", "--", "tail.ww"];
let cc: exec.command;
cc.path = cav[0]; cc.argv = cav; cc.env = os.getenvs(); cc.dir = repo();
cc.stdoutpath = strings.concat(root, "/run-argv-parallel-c.stdout");
cc.stderrpath = strings.concat(root, "/run-argv-parallel-c.stderr");
cc.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
cc.grace = (100i64 * (time.millisecond: i64)): time.duration;
let wc: exec.command;
wc.path = wav[0]; wc.argv = wav; wc.env = os.getenvs(); wc.dir = repo();
wc.stdoutpath = strings.concat(root, "/run-argv-parallel-ww.stdout");
wc.stderrpath = strings.concat(root, "/run-argv-parallel-ww.stderr");
wc.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
wc.grace = (100i64 * (time.millisecond: i64)): time.duration;
let cp: exec.process;
let wp: exec.process;
exec.start(&cp, &cc); exec.start(&wp, &wc);
let cdone: bool = false;
let wdone: bool = false;
for (!cdone || !wdone) {
if (!cdone) { cdone = exec.poll(&cp); };
if (!wdone) { wdone = exec.poll(&wp); };
if (!cdone || !wdone) {
time.sleep(time.millisecond, time.clock.monotonic);
};
};
runargvrequire(cp.result.errno == 0 && cp.result.cleanuperrno == 0
&& cp.result.termination == exec.termination.EXIT && cp.result.code == 47
&& wp.result.errno == 0 && wp.result.cleanuperrno == 0
&& wp.result.termination == exec.termination.EXIT && wp.result.code == 47
&& same(readfile(cc.stdoutpath), strings.concat("<", crecord,
">\n<c-only>\n<>\n<-I>\n"))
&& readfile(cc.stderrpath).len == 0
&& same(readfile(wc.stdoutpath), strings.concat("<", wrecord,
">\n<ww-only>\n<-->\n<tail.ww>\n"))
&& readfile(wc.stderrpath).len == 0,
"run argv: concurrent isolation");
runargvprivateclean(crecord, "run argv: parallel C cleanup");
runargvprivateclean(wrecord, "run argv: parallel WW cleanup");
// Run argv never changes build/test products or persistent action bytes.
let cbuildwork: str = strings.concat(root, "/build-work-c");
let wbuildwork: str = strings.concat(root, "/build-work-ww");
let cbuildout: str = strings.concat(root, "/build-output-c");
let wbuildout: str = strings.concat(root, "/build-output-ww");
mkdirall(cbuildwork); mkdirall(wbuildwork);
let cbuildav: []str = [driver("ww"), "build", "-w", cbuildwork,
"-o", cbuildout, named];
let wbuildav: []str = [driver("ww_ww"), "build", "-w", wbuildwork,
"-o", wbuildout, named];
runcommand(root, "run-argv-build-c", cbuildav,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 0, "", "", "run argv: C build control");
runcommand(root, "run-argv-build-ww", wbuildav,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 0, "", "", "run argv: WW build control");
runargvrequire(same(readfile(cbuildout), readfile(wbuildout))
&& same(wrongsuffixsemantictreesnapshot(cbuildwork),
wrongsuffixsemantictreesnapshot(wbuildwork)),
"run argv: build artifact parity");
let ctestwork: str = strings.concat(root, "/test-work-c");
let wtestwork: str = strings.concat(root, "/test-work-ww");
let ctestout: str = strings.concat(root, "/test-output-c");
let wtestout: str = strings.concat(root, "/test-output-ww");
mkdirall(ctestwork); mkdirall(wtestwork);
let ctestav: []str = [driver("ww"), "test", "-c", "-w", ctestwork,
"-I", source, "-o", ctestout, "proof.tests"];
let wtestav: []str = [driver("ww_ww"), "test", "-c", "-w", wtestwork,
"-I", source, "-o", wtestout, "proof.tests"];
runcommand(root, "run-argv-test-c", ctestav,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 0, "", "", "run argv: C test control");
runcommand(root, "run-argv-test-ww", wtestav,
(120i64 * (time.second: i64)): time.duration, &out);
runargvexpect(&out, 0, "", "", "run argv: WW test control");
runargvrequire(same(readfile(ctestout), readfile(wtestout))
&& same(wrongsuffixsemantictreesnapshot(ctestwork),
wrongsuffixsemantictreesnapshot(wtestwork)),
"run argv: test artifact parity");
runargvrequire(!wrongsuffixpathfragment(root, ".new")
&& !wrongsuffixpathfragment(root, ".old")
&& !wrongsuffixpathfragment(root, ".wwtxn.")
&& !wrongsuffixpathfragment(root, ".install")
&& !wrongsuffixpathfragment(root, ".sepwork")
&& !wrongsuffixpathfragment(root, ".stage")
&& !wrongsuffixpathfragment(root, ".capture")
&& !wrongsuffixpathfragment(root, ".result")
&& !wrongsuffixpathfragment(root, ".request")
&& !wrongsuffixpathfragment(root, ".descriptor")
&& !wrongsuffixpathfragment(root, ".child"),
"run argv: normal residue");
clean(root);
};