ww output: match Go exact -o parsing

This commit is contained in:
2026-08-22 01:59:02 +09:00
parent 4b9968e4e6
commit d61805262e
7 changed files with 701 additions and 36 deletions

View File

@@ -7375,7 +7375,8 @@ parse_build_flags(const char *cmd, int argc, char **argv,
} else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2]) {
if (include_append(cmd, incs, incsz, argv[i] + 2) < 0)
return -1;
} else if (strcmp(argv[i], "-o") == 0) {
} else if (strcmp(argv[i], "-o") == 0 ||
(strcmp(cmd, "build") == 0 && strcmp(argv[i], "--o") == 0)) {
if (i + 1 >= argc) {
fprintf(stderr,
"ww %s: -o needs an argument\n", cmd);
@@ -7383,7 +7384,16 @@ parse_build_flags(const char *cmd, int argc, char **argv,
}
if (cli_copy(cmd, "-o", outpath, outsz, argv[++i]) < 0)
return -1;
} else if (strncmp(argv[i], "-o", 2) == 0 && argv[i][2]) {
} else if (strcmp(cmd, "build") == 0 &&
strncmp(argv[i], "-o=", 3) == 0) {
if (cli_copy(cmd, "-o", outpath, outsz, argv[i] + 3) < 0)
return -1;
} else if (strcmp(cmd, "build") == 0 &&
strncmp(argv[i], "--o=", 4) == 0) {
if (cli_copy(cmd, "-o", outpath, outsz, argv[i] + 4) < 0)
return -1;
} else if (strcmp(cmd, "run") == 0 &&
strncmp(argv[i], "-o", 2) == 0 && argv[i][2]) {
if (cli_copy(cmd, "-o", outpath, outsz, argv[i] + 2) < 0)
return -1;
} else if (argv[i][0] == '-') {
@@ -7863,7 +7873,8 @@ do_test(int argc, char **argv)
} else if (strncmp(argv[i], "-timeout-ms=", 12) == 0 &&
argv[i][12] != '\0') {
packageopts = 1;
} else if (strcmp(argv[i], "-o") == 0) {
} else if (strcmp(argv[i], "-o") == 0 ||
strcmp(argv[i], "--o") == 0) {
if (i + 1 >= argc) {
fprintf(stderr,
"ww test: -o needs an argument\n");
@@ -7871,9 +7882,12 @@ do_test(int argc, char **argv)
}
if (cli_copy("test", "-o", outstem, sizeof outstem,
argv[++i]) < 0) return 2;
} else if (argv[i][1] == 'o' && argv[i][2]) {
} else if (strncmp(argv[i], "-o=", 3) == 0) {
if (cli_copy("test", "-o", outstem, sizeof outstem,
argv[i] + 2) < 0) return 2;
argv[i] + 3) < 0) return 2;
} else if (strncmp(argv[i], "--o=", 4) == 0) {
if (cli_copy("test", "-o", outstem, sizeof outstem,
argv[i] + 4) < 0) return 2;
} else if (strcmp(argv[i], "-w") == 0) {
if (i + 1 >= argc) {
fprintf(stderr,

View File

@@ -8658,6 +8658,176 @@ executable, and retained-test-product bytes are unchanged. Build workdir format
remains `18`, test workdir format remains `19`, and semantic storage format
remains `3`.
### 11.44 Implemented exact output-option name and value semantics
The `ww build` and `ww test` output option has the exact registered name `o`.
Its accepted forms are `-o VALUE`, `--o VALUE`, `-o=VALUE`, and
`--o=VALUE`. An equals form splits at the first `=` and preserves the complete
remaining value, including an empty value and additional `=` bytes. Repeated
occurrences are last-value-wins. A final empty value means no effective
explicit output: a single command uses its ordinary default, a multi-command
build performs its ordinary no-public-output build, a running test retains no
copy, and compile-only testing uses its ordinary default retained name.
Concatenated names such as `-oVALUE` and `--oVALUE` are unknown flags rather
than output requests. Build option parsing stops at the first root operand;
test parsing continues to recognize known test options, including exact `o`,
on either side of its package operands.
#### Pinned evidence and fact classification
The sole authority is official Go 1.26.5 at
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `init` registers exactly string flag name `o` for the build command in
[`cmd/go/internal/work/build.go`, lines 241248](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L241-L248),
and `invoke` parses that flag set before passing only remaining operands to
`runBuild` in
[`cmd/go/main.go`, lines 290322](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/main.go#L290-L322).
- `(*FlagSet).parseOne` accepts one or two leading dashes, splits the first
`=`, consumes the next argument only when there was no equals delimiter, and
rejects an unregistered concatenated name in
[`flag/flag.go`, lines 10741146](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/flag/flag.go#L1074-L1146).
`(*FlagSet).Parse` stops at the first non-flag operand at lines 11491176.
`stringValue.Set` and `(*FlagSet).Set` overwrite a repeated string value at
lines 240250 and 494528. `runBuild` derives effective explicit output from
the final value's nonzero length and otherwise selects its default behavior
in
[`cmd/go/internal/work/build.go`, lines 459478](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L478).
- Build command testdata uses an equals-delimited output successfully in
[`version_buildvcs_nested.txt`, line 57](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/version_buildvcs_nested.txt#L57).
`testParse` exercises exact names with two leading dashes and separate
values at
[`flag_test.go`, lines 164215](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/flag/flag_test.go#L164-L215),
`TestUserDefined` asserts an equals-delimited string value at lines 251267,
and `TestUsage` asserts unknown-flag failure at lines 153161.
- Test command `init` registers exactly string flag name `o` in
[`cmd/go/internal/test/testflag.go`, lines 3238](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L32-L38).
`cmdflag.ParseOne` accepts one or two dashes, cuts the first `=`, preserves
empty and remaining-equals value bytes, and rejects unknown names in
[`cmd/go/internal/cmdflag/flag.go`, lines 53118](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/cmdflag/flag.go#L53-L118).
`testFlags` recognizes known flags before and after the package list and
rejects an unknown flag with `-c` in
[`cmd/go/internal/test/testflag.go`, lines 219349](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L219-L349).
`testNeedBinary` and the output-classification branch treat final empty
`testO` as no explicit retained destination in
[`cmd/go/internal/test/test.go`, lines 631646 and 771781](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L631-L781).
- Official command testdata uses compile-only test output successfully in
[`devnull.txt`, lines 38](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/devnull.txt#L3-L8)
and
[`test_race_tag.txt`, lines 19](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_race_tag.txt#L1-L9).
[`test2json_interrupt.txt`, line 10](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test2json_interrupt.txt#L10)
places `-o` after a package operand, while
[`test_flag.txt`, lines 1116](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_flag.txt#L11-L16)
asserts unknown-flag rejection with `-c` across supported placements.
Those registrations, parser branches, effective-output branches, and official
assertions are **behavior directly implemented or asserted by pinned Go**.
Applying their exact-name, first-equals, final-value, and command-placement
rules to WW's local command surface while retaining WW's diagnostic wording is
**behavior derived from the pinned implementation**. The measured WW matrix
below is **directly measured WW behavior**; no installed host Go result is used
as authority.
#### Direct pre-fix Cstage and WWstage matrix
Both stages had identical pre-fix behavior in every row:
| Route and spelling | Directly measured pre-fix result |
| --- | --- |
| single build, `-o=name` | exited 0 with empty streams, left `name` absent, and published executable `=name` |
| single build, `-oname` | exited 0 with empty streams and incorrectly accepted the concatenated name |
| single build, `--o name` or `--o=name` | exited 2 with empty stdout and byte-identical `ww build: unknown flag\n` stderr |
| single build, final `-o=` | exited 0 with empty streams and published literal file `=` rather than the default command name |
| build values containing `=` | retained an erroneous leading `=`; repeated separate forms were already last-value-wins |
| build option after the first root | remained package input rather than being reparsed, matching the required placement boundary |
| two-command coordinator, `-o=DIR/` | published both products beneath a literal leading-`=` directory |
| two-command coordinator, `-oDIR/` | incorrectly accepted the joined name and published beneath the requested directory |
| two-command coordinator, final `-o=` | exited 2 as `cannot use -o with multiple packages` instead of selecting no effective explicit output |
| compile-only directory test, `-o=name` | exited 0 with empty streams, left `name` absent, and published test binary `=name` |
| compile-only directory test, `-oname` | incorrectly accepted the concatenated name and published that retained binary |
| compile-only directory test, `--o=name` | exited 2 with empty stdout and byte-identical `ww test: unknown flag\n` stderr |
| compile-only directory test, final `-o=` or a value containing `=` | published literal `=` or an erroneous leading-`=` destination |
| test `-o` after its package operand | was already recognized, matching the required test placement boundary |
The wrong-path single-build executables were stage-byte-identical 4,268-byte
files with SHA-256
`866c1eb875dad271d37572f43fb9d9b0eb6a2344d2e61646e655bb09f7909bf6`.
The wrong-path retained test binaries were stage-byte-identical 112,829-byte
files with SHA-256
`5ea3ac9add844dc4cd98cc07fb64816415b5e3c03745dc4cc756ec093cf5cea7`.
The build and test unknown-flag diagnostics were respectively 23 and 22 bytes,
also byte-identical between Cstage and WWstage. These byte counts and hashes
describe only the direct pre-fix measurements.
#### Ownership and complete four-axis result
The Cstage command owners are `parse_build_flags` and `do_test` in
`cmd/ww/main.c`. Their WWstage semantic twins are `dobuild` and `dotest` in
`selfhost/cmd/ww/main.ww`. The shared multi-package owner is
`packagecommand` in `internal/wwpackage/package.ww`. Each recognizes exact
one-/two-dash separate/equals forms, replaces prior occurrences with the final
value, and derives effective explicit-output state from that final value's
non-emptiness. The build parser retains its first-root stop, while test and the
coordinator retain their established after-package recognition. `dorun` and
the shared `run` route are not changed. The compiler, assembler, archiver,
linker, runtime, package checker, and import resolver do not own this rule.
- **Go-like build:** exact accepted forms select the same established output
path as separate `-o VALUE`; a final empty value selects the existing
default/no-public-output branch. Invalid concatenated names reject before
loading, graph or action construction, producers, publication, or runtime.
- **Go-like test:** the same exact forms select retained destinations on both
sides of package operands. Final empty means no running-test retention or
the normal compile-only default. Discovery, variants, generated main,
filtering, execution, accounting, result annotation, and absence of a
test-result cache do not change.
- **Go-like package:** output bytes remain presentation metadata. Source
eligibility, package clauses, declared names, variants, command
classification, canonical package representatives, graph nodes, actions,
symbols, artifacts, and persistence keys are unchanged.
- **Go-like import:** output spelling creates no binding or edge and changes no
dotted import spelling, alias, search, local/vendor/internal rule,
visibility, cycle, initialization order, canonical identity, or `.wwi`
ownership.
#### Loading, lifecycle, parity, and proof
Accepted forms enter the same existing loading, graph, scheduling, compiler,
assembler, archiver, linker, runtime, publication, persistence, reuse, and
invalidation paths as `-o VALUE`. They add no action, process, transaction,
cache, key, artifact byte, or runtime state. Producer or runtime failure,
late output rejection, rollback, prior-state preservation, concurrent
publication, interruption, and process-group cleanup therefore retain their
established owners and results. Output installation keeps the existing
transaction, object-safety, mode, null-device, output-directory, and
running-retained-test guard rules.
Invalid concatenated names stop before all loading and work, create no
diagnostic competitor or product, and leave no unit, interface, assembly,
object, archive, executable, retained test binary, capture, output prefix,
`.new`, `.install`, `.wwtxn.*`, or scratch residue. A final empty value cannot
create literal `=`, `=.sepwork`, or transaction residue. Cold and warm
accepted requests use the ordinary publication and reuse paths; changing only
an accepted spelling does not rekey semantic work. Independent concurrent
requests own independent argument state, workdirs, stages, captures, and
outputs. Build starts no runtime; running tests keep their private executable
and publish only after successful execution.
The WW-native `output_flag_exact_name_and_value_semantics` observer is the
focused owner for both public stages and all three parser routes. It covers the
four accepted forms, extra and empty equals values, repetition, concatenated
name rejection, build/test placement controls, multi-command behavior,
diagnostic parity, runnable and retained artifact-byte parity, warm reuse, and
absence of literal-equals and transaction residue. Existing transaction,
producer/runtime failure, rollback, concurrency, interruption, output-mode,
and cleanup observers remain authoritative for the unchanged downstream
mechanisms. Post-fix byte counts and hashes are recorded only after direct
focused measurement; this section does not infer them from the implementation.
This is command parsing and output presentation only. No persisted-byte
contract changes: build workdir format remains `18`, test workdir format
remains `19`, and semantic storage format remains `3`.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -344,7 +344,19 @@ 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 build`, an explicit `-o` names an output directory when ordinary
- 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 `=`
characters; an empty value is valid. Repetition is last-value-wins, and a
final empty value means that there is no effective explicit output, so the
ordinary default-output or no-public-output rule applies. A concatenated
spelling such as `-oVALUE` or `--oVALUE` is an unknown flag, not an output
option. Build option parsing stops at the first package or source operand;
later flag-like arguments remain operands and are not reparsed as `-o`.
These spelling and placement rules select only caller-visible output
disposition and never supply package, import, graph, action, symbol,
artifact, `.wwi`, or persistence identity.
A nonempty effective `-o` names an output directory when ordinary
`stat` reports an existing directory (following symlinks) or its spelling
ends in `/`. This classification is independent of whether one or many
package roots were requested. Each selected command is published beneath
@@ -771,11 +783,22 @@ coordinator reports that successful validation exactly as
`? <package> [no test files]\n`.
Every test-bearing directory product links one request-private runnable.
`-c` retains an executable copy and suppresses its execution; `-o` retains a
copy at the named destination and still executes unless `-c` is present. With
no explicit output, `-c` writes `<import-leaf>.test` in the invocation
directory. An output ending in `/` or naming an existing directory receives
that basename and may have missing parent directories created. One
The test output-option name is exactly `o`, with the accepted forms `-o VALUE`,
`--o VALUE`, `-o=VALUE`, and `--o=VALUE`. Equals forms split only at their
first `=` and preserve an empty value or any additional `=` bytes. Repeated
occurrences are last-value-wins. A final empty value means no effective
explicit output and therefore requests no running-test retention; with `-c`,
the ordinary default retained name applies. A concatenated `-oVALUE` or
`--oVALUE` is unknown. Unlike build option parsing, known test options,
including these exact output forms, are recognized before or after package
operands. Invalid output-option names reject before package loading, product
construction, execution, or publication.
`-c` retains an executable copy and suppresses its execution; a nonempty
effective `-o` retains a copy at the named destination and still executes
unless `-c` is present. With no effective explicit output, `-c` writes
`<import-leaf>.test` in the invocation 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. It keeps

View File

@@ -236,13 +236,23 @@ warning on stderr and accounting on stdout because no coordinator combines its
descriptors or emits a package result.
Directory test binaries are always linked under the coordinator's temporary
product root. `-c` independently requests a caller-visible executable copy and
suppresses execution. `-o` independently requests a copy and still runs the
temporary binary unless `-c` is also present. Without `-o`, `-c` publishes
`<import-leaf>.test` in the invocation directory; an output ending in `/` or
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
product root. The retained-output option has the exact registered name `o` and
accepts `-o VALUE`, `--o VALUE`, `-o=VALUE`, and `--o=VALUE`. An equals form
splits only at its first `=`, preserving an empty value and any later `=`
bytes. Repeated occurrences are last-value-wins. A final empty value is no
effective explicit output: it requests no retained copy for a running test,
while `-c` falls back to the ordinary `<import-leaf>.test` name. Concatenated
`-oVALUE` and `--oVALUE` spell unknown flags and reject before loading, product
construction, tools, execution, or publication. Known test flags, including
these exact output forms, remain recognized before or after package operands.
`-c` independently requests a caller-visible executable copy and suppresses
execution. A nonempty effective `-o` independently requests a copy and still
runs the temporary binary unless `-c` is also present. Without an effective
explicit output, `-c` publishes `<import-leaf>.test` in the invocation
directory; an output ending in `/` or 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. The private test link still runs unless `-c` suppresses it;
the raw single-file compatibility route also links into driver-owned private

View File

@@ -2055,16 +2055,20 @@ export fn packagecommand(args: []str) int = {
i += 1;
continue;
};
if (strings.compare(a, "-o") == 0) {
if (strings.compare(a, "-o") == 0
|| strings.compare(a, "--o") == 0) {
if (i + 1 >= args.len) { pkgusage(); return 2; };
outname = args[i + 1];
explicitout = true;
explicitout = outname.len != 0;
i += 2;
continue;
};
if (strings.hasprefix(a, "-o") && a.len > 2) {
outname = a[2:a.len];
explicitout = true;
if (strings.hasprefix(a, "-o=")
|| strings.hasprefix(a, "--o=")) {
let valueoff: i32 = 3;
if (a[1] == '-') { valueoff = 4; };
outname = a[valueoff:a.len];
explicitout = outname.len != 0;
i += 1;
continue;
};

View File

@@ -8853,19 +8853,25 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
libs[nlibs] = nm;
nlibs += 1;
} else { if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outflag = p + 2u64;
} else { if (cstreqlit(p, "-o") || cstreqlit(p, "--o")
|| (p[1u64] == 111u8 && p[2u64] == 61u8)
|| (p[1u64] == 45u8 && p[2u64] == 111u8
&& p[3u64] == 61u8)) {
if (p[1u64] == 111u8 && p[2u64] == 61u8) {
outflag = p + 3u64;
} else { if (p[1u64] == 45u8
&& p[2u64] == 111u8 && p[3u64] == 61u8) {
outflag = p + 4u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -o needs an argument\n");
return 2;
};
i += 1;
outflag = argv[i];
};
if (!clipathfits("build", "-o", outflag)) { return 2; };
} else { if (p[1u64] == 119u8) { // '-w'
outflag = argv[i];
}; };
if (!clipathfits("build", "-o", outflag)) { return 2; };
} else { if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
workdir = p + 2u64;
} else {
@@ -8892,6 +8898,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
i += 1;
};
if (outflag != nil && outflag[0u64] == 0u8) { outflag = nil; };
if (src == nil) {
let dot: [2]u8 = ['.': u8, 0u8];
@@ -9656,19 +9663,26 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
&& ps.len > 12) {
packageopts = true; i += 1; continue;
};
if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outstem = p + 2u64;
if (cstreqlit(p, "-o") || cstreqlit(p, "--o")
|| (p[1u64] == 111u8 && p[2u64] == 61u8)
|| (p[1u64] == 45u8 && p[2u64] == 111u8
&& p[3u64] == 61u8)) {
if (p[1u64] == 111u8 && p[2u64] == 61u8) {
outstem = p + 3u64;
} else { if (p[1u64] == 45u8
&& p[2u64] == 111u8 && p[3u64] == 61u8) {
outstem = p + 4u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -o needs an argument\n");
return 2;
};
i += 1;
outstem = argv[i];
outstem = argv[i];
};
if (!clipathfits("test", "-o", outstem)) { return 2; };
i += 1; continue;
};
if (!clipathfits("test", "-o", outstem)) { return 2; };
i += 1; continue;
};
if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
@@ -9691,6 +9705,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
i += 1;
};
if (outstem != nil && outstem[0u64] == 0u8) { outstem = nil; };
if (target == nil) {
let dot: [2]u8 = ['.': u8, 0u8];
target = &dot[0];

View File

@@ -13816,6 +13816,435 @@ fn runtimepath(relative: str) str = {
clean(root);
};
@test fn output_flag_exact_name_and_value_semantics() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let multi_a: str = strings.concat(source, "/alpha");
let multi_b: str = strings.concat(source, "/beta");
let tests: str = strings.concat(source, "/tests");
let outputs: str = strings.concat(root, "/outputs");
mkdirall(source); mkdirall(multi_a); mkdirall(multi_b);
mkdirall(tests); mkdirall(outputs);
let single: str = strings.concat(source, "/single.ww");
writefile(single,
"package main;\nfn main() i32 = { return 29; };\n");
writefile(strings.concat(multi_a, "/main.ww"),
"package main;\nfn main() i32 = { return 31; };\n");
writefile(strings.concat(multi_b, "/main.ww"),
"package main;\nfn main() i32 = { return 37; };\n");
writefile(strings.concat(tests, "/tests.ww"),
"package exactflags;\nfn value() i32 = { return 41; };\n");
writefile(strings.concat(tests, "/tests_test.ww"), strings.concat(
"package exactflags;\n",
"@test fn exact() void = { assert(value() == 41); };\n"));
let rawtest: str = strings.concat(source, "/raw_test.ww");
writefile(rawtest, strings.concat(
"package exactraw;\n",
"@test fn raw_exact() void = { assert(true); };\n"));
let compilerwrapper: str = strings.concat(root, "/output-flag-w6c.sh");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$WW_OUTPUT_FLAG_TRACE\"\n",
"exec \"$WW_OUTPUT_FLAG_REAL_C\" \"$@\"\n"));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let tags: []str = ["c", "ww"];
let forms: []str = ["one-separate", "one-equals", "two-separate",
"two-equals"];
let buildref: str = "";
let testref: str = "";
let alpharef: str = "";
let betaref: str = "";
let rawref: str = "";
let rejectrefs: []str = ["", "", "", "", ""];
let coordinatorrejectref: str = "";
let buildmissingref: str = "";
let testmissingref: str = "";
let stopref: str = "";
let baseenv: []str = os.getenvs();
let out: commandout;
let si: i32 = 0;
for (si < stages.len) {
let trace: str = strings.concat(root, "/output-flag-", tags[si],
".trace");
writefile(trace, "");
let env: []str = alloc([], (baseenv.len + 3): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_OUTPUT_FLAG_")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_OUTPUT_FLAG_REAL_C=",
driver(compilers[si])));
append(env, strings.concat("WW_OUTPUT_FLAG_TRACE=", trace));
let ignoredrunout: str = strings.concat(outputs, "/ignored-run-",
tags[si]);
let runcontrol: []str = [driver(stages[si]), "run", "-I", source,
strings.concat("-o", ignoredrunout), single];
runcommand(root, strings.concat("output-flag-run-control-", tags[si]),
runcontrol, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 29);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(ignoredrunout));
// Exact output names change only publication. Distinct accepted
// spellings retain the same action and executable bytes.
let buildwork: str = strings.concat(root, "/build-work-", tags[si]);
mkdirall(buildwork);
let fi: i32 = 0;
for (fi < forms.len) {
let binary: str = strings.concat(outputs, "/build-", tags[si],
"-", forms[fi], "=tail");
let av: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source];
if (fi == 0) { append(av, "-o"); append(av, binary); }
else if (fi == 1) { append(av, strings.concat("-o=", binary)); }
else if (fi == 2) { append(av, "--o"); append(av, binary); }
else { append(av, strings.concat("--o=", binary)); };
append(av, single);
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-build-", tags[si],
"-", forms[fi]), av, env,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& os.exists(binary));
if (fi == 0) { assert(readfile(trace).len != 0); }
else { assert(readfile(trace).len == 0); };
let bytes: str = readfile(binary);
if (buildref.len == 0) { buildref = strings.dup(bytes); }
else { assert(same(buildref, bytes)); };
let runav: []str = [binary];
runcommand(root, strings.concat("output-flag-build-run-", tags[si],
"-", forms[fi]), runav, time.second, &out);
expectexit(&out, 29);
assert(out.stdout.len == 0 && out.stderr.len == 0);
fi += 1;
};
// Repeated string flags are last-wins. A final empty value returns to
// default naming and cannot create a file or scratch tree named '='.
let stale: str = strings.concat(outputs, "/stale-build-", tags[si]);
let winner: str = strings.concat(outputs, "/winner-build-", tags[si],
"=last");
let lastav: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source, "-o", stale, strings.concat("--o=", winner), single];
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-last-", tags[si]),
lastav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(stale) && same(buildref, readfile(winner))
&& readfile(trace).len == 0);
let defaultbin: str = strings.concat(root, "/single");
let emptyav: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source, "-o", stale, "-o=", single];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-empty-", tags[si]),
emptyav, env, root,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(stale) && same(buildref, readfile(defaultbin))
&& readfile(trace).len == 0
&& !os.exists(strings.concat(root, "/="))
&& !os.exists(strings.concat(root, "/=.sepwork")));
assert(os.remove(defaultbin) == 0);
clean(strings.concat(defaultbin, ".sepwork"));
// Build parsing stops at its first operand: a later flag-like value is
// package input, not a second output option.
let early: str = strings.concat(outputs, "/early");
let late: str = strings.concat(outputs, "/late");
let stopav: []str = [driver(stages[si]), "build", "-I", source,
"-o", early, single, strings.concat("--o=", late)];
runcommand(root, strings.concat("output-flag-stop-", tags[si]), stopav,
(60i64 * (time.second: i64)): time.duration, &out);
assert(out.termination == exec.termination.EXIT && out.code != 0);
assert(out.stdout.len == 0 && !has(out.stderr, "unknown flag")
&& !os.exists(early) && !os.exists(late));
if (si == 0) { stopref = strings.dup(out.stderr); }
else { assert(same(stopref, out.stderr)); };
// Joined names are unknown before loading, work creation, or output.
let ri: i32 = 0;
for (ri < 2) {
let badout: str = strings.concat(outputs, "/bad-build-", tags[si],
"-", forms[ri]);
let badwork: str = strings.concat(root, "/bad-build-work-", tags[si],
"-", forms[ri]);
let prefix: str = "-o";
if (ri != 0) { prefix = "--o"; };
let joined: str = strings.concat(prefix, badout);
let badav: []str = [driver(stages[si]), "build", "-w", badwork,
"-I", source, joined, single];
runcommand(root, strings.concat("output-flag-reject-build-", tags[si],
"-", forms[ri]), badav, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: unknown flag\n")
&& !os.exists(badout) && !os.exists(badwork));
if (si == 0) { rejectrefs[ri] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[ri], out.stderr)); };
ri += 1;
};
let buildmissingwork: str = strings.concat(root,
"/missing-build-work-", tags[si]);
let buildmissing: []str = [driver(stages[si]), "build", "-w",
buildmissingwork, "-I", source, "--o"];
runcommand(root, strings.concat("output-flag-missing-build-", tags[si]),
buildmissing, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: -o needs an argument\n")
&& !os.exists(buildmissingwork));
if (si == 0) { buildmissingref = strings.dup(out.stderr); }
else { assert(same(buildmissingref, out.stderr)); };
// Two roots exercise coordinator output planning. The second spelling
// reuses the first request's compiled actions, and final empty performs
// the established no-public-output multi-build.
let multiwork: str = strings.concat(root, "/multi-work-", tags[si]);
mkdirall(multiwork);
let multione: str = strings.concat(root, "/multi-one-", tags[si], "/");
let multiav: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, strings.concat("--o=", multione), multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-one-", tags[si]),
multiav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len != 0);
let alphaone: str = strings.concat(multione, "alpha");
let betaone: str = strings.concat(multione, "beta");
if (alpharef.len == 0) {
alpharef = readfile(alphaone); betaref = readfile(betaone);
} else {
assert(same(alpharef, readfile(alphaone))
&& same(betaref, readfile(betaone)));
};
let alpharun: []str = [alphaone];
runcommand(root, strings.concat("output-flag-alpha-run-", tags[si]),
alpharun, time.second, &out);
expectexit(&out, 31);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let betarun: []str = [betaone];
runcommand(root, strings.concat("output-flag-beta-run-", tags[si]),
betarun, time.second, &out);
expectexit(&out, 37);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let multitwo: str = strings.concat(root, "/multi-two-", tags[si], "/");
let warmav: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, "-o", multitwo, multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-warm-", tags[si]),
warmav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len == 0
&& same(alpharef, readfile(strings.concat(multitwo, "alpha")))
&& same(betaref, readfile(strings.concat(multitwo, "beta"))));
let unusedmulti: str = strings.concat(root, "/multi-unused-", tags[si],
"/");
let multiempty: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, "-o", unusedmulti, "--o=", multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-empty-", tags[si]),
multiempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len == 0 && !os.exists(unusedmulti)
&& !os.exists(strings.concat(root, "/alpha"))
&& !os.exists(strings.concat(root, "/beta")));
let multibadout: str = strings.concat(outputs, "/bad-multi-", tags[si]);
let multibadwork: str = strings.concat(root, "/bad-multi-work-", tags[si]);
let multibad: []str = [driver(stages[si]), "build", "-w", multibadwork,
"-I", source, strings.concat("--o", multibadout), multi_a, multi_b];
runcommand(root, strings.concat("output-flag-reject-multi-", tags[si]),
multibad, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: unknown flag\n")
&& !os.exists(multibadout) && !os.exists(multibadwork));
if (si == 0) { rejectrefs[4] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[4], out.stderr)); };
let directbadout: str = strings.concat(outputs,
"/bad-direct-coordinator-", tags[si]);
let directbadwork: str = strings.concat(root,
"/bad-direct-coordinator-work-", tags[si]);
let directbad: []str = [driver("wwtest"), "package",
"--ww-operation", "build", "--ww-driver", driver(stages[si]),
"-w", directbadwork, "-I", source,
strings.concat("-o", directbadout), multi_a, multi_b];
runcommand(root, strings.concat("output-flag-reject-coordinator-",
tags[si]), directbad, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0 && has(out.stderr, "usage: wwtest package")
&& !os.exists(directbadout) && !os.exists(directbadwork));
if (si == 0) { coordinatorrejectref = strings.dup(out.stderr); }
else { assert(same(coordinatorrejectref, out.stderr)); };
// Compile-only directory testing accepts the same four spellings. Its
// final empty option may follow the package and restores the default.
let testwork: str = strings.concat(root, "/test-work-", tags[si]);
mkdirall(testwork);
fi = 0;
for (fi < forms.len) {
let binary: str = strings.concat(outputs, "/test-", tags[si], "-",
forms[fi], "=tail");
let av: []str = [driver(stages[si]), "test", "-c", "-w", testwork,
"-I", source];
if (fi == 0) { append(av, "-o"); append(av, binary); }
else if (fi == 1) { append(av, strings.concat("-o=", binary)); }
else if (fi == 2) { append(av, "--o"); append(av, binary); }
else { append(av, strings.concat("--o=", binary)); };
append(av, tests);
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-test-", tags[si], "-",
forms[fi]), av, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& os.exists(binary));
if (fi == 0) { assert(readfile(trace).len != 0); }
else { assert(readfile(trace).len == 0); };
let bytes: str = readfile(binary);
if (testref.len == 0) { testref = strings.dup(bytes); }
else { assert(same(testref, bytes)); };
let runav: []str = [binary];
runcommand(root, strings.concat("output-flag-test-run-", tags[si], "-",
forms[fi]), runav,
(30i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n"));
fi += 1;
};
let staletest: str = strings.concat(outputs, "/stale-test-", tags[si]);
let runningretained: str = strings.concat(outputs,
"/running-test-", tags[si], "=tail");
let runningretainedav: []str = [driver(stages[si]), "test", "-w",
testwork, "-I", source, strings.concat("--o=", runningretained),
tests];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-running-retained-",
tags[si]), runningretainedav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n")
&& same(testref, readfile(runningretained))
&& readfile(trace).len == 0);
let defaulttest: str = strings.concat(root, "/tests.test");
let testempty: []str = [driver(stages[si]), "test", "-c", "-w",
testwork, "-I", source, "-o", staletest, tests, "--o", ""];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-empty-", tags[si]),
testempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(staletest) && same(testref, readfile(defaulttest))
&& readfile(trace).len == 0
&& !os.exists(strings.concat(root, "/=")));
assert(os.remove(defaulttest) == 0);
let runningstale: str = strings.concat(outputs,
"/stale-running-test-", tags[si]);
let runningempty: []str = [driver(stages[si]), "test", "-w",
testwork, "-I", source, "-o", runningstale, tests, "-o="];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-running-empty-",
tags[si]), runningempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n")
&& !os.exists(runningstale) && readfile(trace).len == 0);
// The raw single-file route owns output state in the public driver, so
// final empty must normalize before its retain/copy decision too.
let rawwork: str = strings.concat(root, "/raw-work-", tags[si]);
mkdirall(rawwork);
let rawretained: str = strings.concat(outputs, "/raw-test-", tags[si],
"=tail");
let rawretainedav: []str = [driver(stages[si]), "test", "-w", rawwork,
strings.concat("--o=", rawretained), rawtest];
runcommandenvdir(root, strings.concat("output-flag-raw-retained-",
tags[si]), rawretainedav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "raw_exact ... ok\n")
&& os.exists(rawretained));
let rawpublishedbytes: str = readfile(rawretained);
if (rawref.len == 0) { rawref = strings.dup(rawpublishedbytes); }
else { assert(same(rawref, rawpublishedbytes)); };
let rawstale: str = strings.concat(outputs, "/stale-raw-", tags[si]);
let rawempty: []str = [driver(stages[si]), "test", "-c", "-w",
rawwork, "-o", rawstale, "--o=", rawtest];
runcommandenvdir(root, strings.concat("output-flag-raw-empty-", tags[si]),
rawempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let rawbinary: str = strings.concat(rawwork, "/main");
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(rawstale) && os.exists(rawbinary)
&& !os.exists(strings.concat(root, "/=")));
let rawbytes: str = readfile(rawbinary);
assert(same(rawref, rawbytes));
ri = 0;
for (ri < 2) {
let badout: str = strings.concat(outputs, "/bad-test-", tags[si],
"-", forms[ri]);
let badwork: str = strings.concat(root, "/bad-test-work-", tags[si],
"-", forms[ri]);
let prefix: str = "-o";
if (ri != 0) { prefix = "--o"; };
let joined: str = strings.concat(prefix, badout);
let badav: []str = [driver(stages[si]), "test", "-c", "-w",
badwork, "-I", source, joined, tests];
runcommand(root, strings.concat("output-flag-reject-test-", tags[si],
"-", forms[ri]), badav, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww test: unknown flag\n")
&& !os.exists(badout) && !os.exists(badwork));
if (si == 0) { rejectrefs[2 + ri] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[2 + ri], out.stderr)); };
ri += 1;
};
let testmissingwork: str = strings.concat(root,
"/missing-test-work-", tags[si]);
let testmissing: []str = [driver(stages[si]), "test", "-c", "-w",
testmissingwork, "-I", source, "--o"];
runcommand(root, strings.concat("output-flag-missing-test-", tags[si]),
testmissing, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww test: -o needs an argument\n")
&& !os.exists(testmissingwork));
if (si == 0) { testmissingref = strings.dup(out.stderr); }
else { assert(same(testmissingref, out.stderr)); };
let cleanpaths: []str = [root, outputs, source, multi_a, multi_b, tests,
buildwork, multiwork, testwork, rawwork];
let ci: i32 = 0;
for (ci < cleanpaths.len) {
assert(!directoryhasnew(cleanpaths[ci])
&& !directoryhasfragment(cleanpaths[ci], ".install")
&& !directoryhasfragment(cleanpaths[ci], ".wwtxn."));
ci += 1;
};
si += 1;
};
assert(!os.exists(strings.concat(root, "/="))
&& !os.exists(strings.concat(root, "/=.sepwork")));
clean(root);
};
@test fn compile_artifact_naming() void = {
let root: str = fresh();
let pdir: str = strings.concat(root, "/pkg");