ww: protect public install outputs

This commit is contained in:
2026-08-21 04:11:01 +09:00
parent ce91f6662c
commit 1b71250ad5
7 changed files with 1064 additions and 90 deletions

View File

@@ -1309,6 +1309,7 @@ struct sepproduct {
int directory_product;
int no_tests;
int build_action; /* loaded product retained in the action list */
int public_out;
int context;
int root;
int variant_root; /* retained single-unit root outside directory products */
@@ -4983,6 +4984,7 @@ struct septxnentry {
char *backup;
int had_old;
int installed;
int public_output;
};
struct septxn {
@@ -4991,7 +4993,8 @@ struct septxn {
};
static int
sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
sep_txn_add_mode(struct septxn *tx, const char *stage, const char *dst,
int public_output)
{
if (strcmp(stage, dst) == 0) {
fprintf(stderr, "ww: transaction path collision: %s\n", dst);
@@ -5013,6 +5016,7 @@ sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
e->stage = strdup(stage);
e->dst = strdup(dst);
e->backup = sep_sprintf("%s.wwtxn.%ld.old", dst, (long)getpid());
e->public_output = public_output;
if (e->stage == NULL || e->dst == NULL || e->backup == NULL) {
sep_fail_nomem();
free(e->backup); free(e->dst); free(e->stage);
@@ -5029,6 +5033,18 @@ sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
return 0;
}
static int
sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
{
return sep_txn_add_mode(tx, stage, dst, 0);
}
static int
sep_txn_add_public(struct septxn *tx, const char *stage, const char *dst)
{
return sep_txn_add_mode(tx, stage, dst, 1);
}
static void
sep_txn_discard(struct septxn *tx)
{
@@ -5049,8 +5065,87 @@ sep_txn_free(struct septxn *tx)
memset(tx, 0, sizeof *tx);
}
struct sep_output_magic {
const unsigned char *bytes;
size_t n;
};
#define SEP_OUTPUT_MAGIC(s) { (const unsigned char *)(s), sizeof(s) - 1 }
/* Go 1.26.5 work.objectMagic. WW interfaces are an additional public output
* kind and always begin with the compiler-owned module directive. */
static const struct sep_output_magic sep_output_magic[] = {
SEP_OUTPUT_MAGIC("!<arch>\n"),
SEP_OUTPUT_MAGIC("<bigaf>\n"),
SEP_OUTPUT_MAGIC("\x7f" "ELF"),
SEP_OUTPUT_MAGIC("\xfe\xed\xfa\xce"),
SEP_OUTPUT_MAGIC("\xfe\xed\xfa\xcf"),
SEP_OUTPUT_MAGIC("\xce\xfa\xed\xfe"),
SEP_OUTPUT_MAGIC("\xcf\xfa\xed\xfe"),
SEP_OUTPUT_MAGIC("\x4d\x5a\x90\x00\x03\x00"),
SEP_OUTPUT_MAGIC("\x4d\x5a\x78\x00\x01\x00"),
SEP_OUTPUT_MAGIC("\x00\x00\x01\xeb"),
SEP_OUTPUT_MAGIC("\x00\x00\x8a\x97"),
SEP_OUTPUT_MAGIC("\x00\x00\x06\x47"),
SEP_OUTPUT_MAGIC("\x00\x61\x73\x6d"),
SEP_OUTPUT_MAGIC("\x01\xdf"),
SEP_OUTPUT_MAGIC("\x01\xf7"),
SEP_OUTPUT_MAGIC("//ww:module "),
};
#undef SEP_OUTPUT_MAGIC
static int
sep_is_object_output(const char *path)
{
unsigned char buf[64] = {0};
int fd = open(path, O_RDONLY);
if (fd < 0) return 0;
size_t got = 0;
int bad = 0;
while (got < sizeof buf) {
ssize_t n = read(fd, buf + got, sizeof buf - got);
if (n > 0) got += (size_t)n;
else if (n == 0) break;
else if (errno != EINTR) { bad = 1; break; }
}
(void)close(fd);
if (bad) return 0;
for (size_t i = 0; i < nelem(sep_output_magic); i++)
if (got >= sep_output_magic[i].n
&& memcmp(buf, sep_output_magic[i].bytes,
sep_output_magic[i].n) == 0)
return 1;
return 0;
}
/* Pinned Go's checkDstOverwrite follows symlinks with stat and protects only
* directories and nonempty regular non-object files. Other file kinds remain
* eligible for the install operation itself to replace. */
static int
sep_check_dst_overwrite(const char *dst)
{
struct stat st;
if (stat(dst, &st) != 0) return 0;
if (S_ISDIR(st.st_mode)) {
fputs("ww: build output ", stderr);
sep_put_quoted(dst);
fputs(" already exists and is a directory\n", stderr);
return -1;
}
if (S_ISREG(st.st_mode) && st.st_size != 0
&& !sep_is_object_output(dst)) {
fputs("ww: build output ", stderr);
sep_put_quoted(dst);
fputs(" already exists and is not an object file\n", stderr);
return -1;
}
return 0;
}
/* One request-wide rollback group: producers and linkers finish first; only
* then are old destinations parked and all staged files installed. */
* then are public destinations checked, old destinations parked, and all
* staged files installed. */
static int
sep_txn_commit(struct septxn *tx)
{
@@ -5060,6 +5155,10 @@ sep_txn_commit(struct septxn *tx)
tx->v[i].stage);
goto rollback;
}
for (int i = 0; i < tx->n; i++)
if (tx->v[i].public_output
&& sep_check_dst_overwrite(tx->v[i].dst) != 0)
goto rollback;
for (int i = 0; i < tx->n; i++)
if (path_exists_nofollow(tx->v[i].backup) != 0) {
fprintf(stderr, "ww: transaction backup already exists: %s\n",
@@ -5476,6 +5575,52 @@ sep_mkdirs(const char *path, mode_t mode, struct sep_created_dirs *created)
return 0;
}
/* A retained running test is built and executed from request-private storage.
* Its Go-like install action runs only after that test succeeds, but reuses
* the same guarded publisher as build and compile-only test products. */
static int
sep_install_test_output(const char *stage, const char *dst)
{
char *install_stage = sep_sprintf("%s.install", stage);
if (install_stage == NULL) return 1;
if (strlen(install_stage) + 1 > (size_t)PATH_MAX
|| path_exists_nofollow(install_stage) != 0
|| copy_executable_stage(stage, install_stage) != 0) {
fputs("ww: cannot stage retained test output\n", stderr);
free(install_stage);
return 1;
}
char *parent = sep_parent_path(dst);
if (parent == NULL) {
(void)unlink(install_stage);
free(install_stage);
return 1;
}
struct sep_created_dirs created = {0};
if (sep_mkdirs(parent, 0777, &created) != 0) {
fprintf(stderr, "ww: cannot create test output directory %s\n",
parent);
free(parent);
(void)unlink(install_stage);
free(install_stage);
return 1;
}
free(parent);
struct septxn tx = {0};
if (sep_txn_add_public(&tx, install_stage, dst) != 0
|| sep_txn_commit(&tx) != 0) {
sep_txn_discard(&tx);
sep_txn_free(&tx);
sep_rollback_dirs(&created);
(void)unlink(install_stage);
free(install_stage);
return 1;
}
sep_txn_free(&tx);
free(install_stage);
return 0;
}
/* build_sep_plan — discover dependencies for every requested product in one
* package universe, compile the dependency-first union once, then link each
* root from its own complete reachable archive closure. The dependency-first
@@ -6713,11 +6858,14 @@ prepare_transaction:
}
for (int i = 0; i < nproducts; i++) {
if (products[i].stage_out != NULL
&& sep_txn_add(&tx, products[i].stage_out,
products[i].out) < 0)
&& (products[i].public_out
? sep_txn_add_public(&tx, products[i].stage_out,
products[i].out)
: sep_txn_add(&tx, products[i].stage_out,
products[i].out)) < 0)
goto request_fail;
if (products[i].stage_publish != NULL
&& sep_txn_add(&tx, products[i].stage_publish,
&& sep_txn_add_public(&tx, products[i].stage_publish,
products[i].publish) < 0)
goto request_fail;
if (products[i].stage_iface != NULL) {
@@ -6725,7 +6873,7 @@ prepare_transaction:
int on = snprintf(outiface, sizeof outiface, "%s.wwi",
products[i].out);
if (on < 0 || (size_t)on >= sizeof outiface
|| sep_txn_add(&tx, products[i].stage_iface,
|| sep_txn_add_public(&tx, products[i].stage_iface,
outiface) < 0)
goto request_fail;
}
@@ -6771,7 +6919,8 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
const struct seplinkflags *linkflags, int publish_package,
int require_command, int is_test,
int root_variant, const char *test_package, int emit_asm,
int keepscratch, const char *workdir, const char *create_output_dir,
int public_output, int keepscratch, const char *workdir,
const char *create_output_dir,
const char *default_output_dir, int output_path_error)
{
char scratch[PATH_MAX] = {0};
@@ -6786,6 +6935,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
.artifact = NULL,
.variant = root_variant,
.build_action = 1,
.public_out = public_output,
.root = -1,
.variant_root = -1,
.support = -1,
@@ -7225,7 +7375,7 @@ do_build(int argc, char **argv)
* library compilation still need request-private product paths. */
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 0, workdir, NULL, NULL, 0);
emit_asm, 0, 0, workdir, NULL, NULL, 0);
int cleanfail = 0;
if (unlink(tmp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7241,7 +7391,7 @@ do_build(int argc, char **argv)
}
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 1, workdir, create_output_dir, default_output_dir,
emit_asm, 1, 1, workdir, create_output_dir, default_output_dir,
output_path_error);
free(incs);
return rc;
@@ -7287,7 +7437,7 @@ do_run(int argc, char **argv)
const char *root_identity = !literal && is_dir ? src : NULL;
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags,
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL, NULL, NULL, 0);
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, 0, NULL, NULL, NULL, 0);
free(incs);
if (buildrc != 0) {
if (unlink(tmp) != 0 && errno != ENOENT)
@@ -7334,6 +7484,9 @@ do_run(int argc, char **argv)
static int
do_test(int argc, char **argv)
{
if (argc == 3
&& strcmp(argv[0], "--ww-install-test-output") == 0)
return sep_install_test_output(argv[1], argv[2]);
const char *src = NULL;
struct sepproduct *products = NULL;
int nproducts = 0, productcap = 0;
@@ -7475,7 +7628,10 @@ do_test(int argc, char **argv)
const char *publish = argv[++i];
const char *status = argv[++i];
size_t pn = strlen(name);
int build_product = strcmp(kind, "build") == 0;
int build_product = strcmp(kind, "build") == 0
|| strcmp(kind, "build-public") == 0;
int public_build_product =
strcmp(kind, "build-public") == 0;
int test_product = strcmp(kind, "test") == 0;
int has_production = strcmp(production, "-") != 0;
int has_internal = strcmp(internal, "-") != 0;
@@ -7527,6 +7683,7 @@ do_test(int argc, char **argv)
products[nproducts].no_tests = test_product
&& !has_internal && !has_external;
products[nproducts].build_action = 1;
products[nproducts].public_out = public_build_product;
products[nproducts].root = -1;
products[nproducts].variant_root = -1;
products[nproducts].production_root = -1;
@@ -7788,9 +7945,12 @@ do_test(int argc, char **argv)
}
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) {
int retain_output = outstem[0] && !discard_output;
int deferred_install = retain_output && !compileonly && !emit_asm;
int owntmp = deferred_install
|| ((!outstem[0] || discard_output) && !workdir[0]);
if (retain_output && !deferred_install) outp = outstem;
else if (workdir[0] && !deferred_install) {
/* The workdir owns the persistent test binary the same
* way it owns the package artifacts. */
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
@@ -7816,7 +7976,8 @@ do_test(int argc, char **argv)
outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1,
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
retain_output && !deferred_install, retain_output ? 1 : 0,
workdir, NULL, NULL, 0);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7837,6 +7998,9 @@ do_test(int argc, char **argv)
return cleanfail ? 1 : 0;
}
int rc = run_test_bin(outp, pattern);
if (rc == 0 && deferred_install
&& sep_install_test_output(outp, outstem) != 0)
rc = 1;
if (owntmp && unlink(outp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
if (rc == 0) rc = 1;
@@ -7860,9 +8024,12 @@ do_test(int argc, char **argv)
}
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) {
int retain_output = outstem[0] && !discard_output;
int deferred_install = retain_output && !compileonly && !emit_asm;
int owntmp = deferred_install
|| ((!outstem[0] || discard_output) && !workdir[0]);
if (retain_output && !deferred_install) outp = outstem;
else if (workdir[0] && !deferred_install) {
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
if (tn < 0 || (size_t)tn >= sizeof tmp) {
fputs("ww: workdir path is too long\n", stderr);
@@ -7884,7 +8051,8 @@ do_test(int argc, char **argv)
int br = build_one_sep(target, 0, NULL, outp,
outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
retain_output && !deferred_install, retain_output ? 1 : 0,
workdir, NULL, NULL, 0);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7905,6 +8073,9 @@ do_test(int argc, char **argv)
return cleanfail ? 1 : 0;
}
int rc = run_test_bin(outp, pattern);
if (rc == 0 && deferred_install
&& sep_install_test_output(outp, outstem) != 0)
rc = 1;
if (owntmp && unlink(outp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
if (rc == 0) rc = 1;

View File

@@ -6214,11 +6214,10 @@ The authority is official Go 1.26.5 at commit
The separation between saved and executed paths is a conclusion derived from
the pinned action dependencies: the run consumes the temporary link action,
not the install action. Duplicate names are likewise a materialization
collision, not package identity. Go's sources do not assert that a set of
retained binaries is installed as one rollback transaction; WW keeps its
existing stronger request-wide transaction while matching the observable
accepted, rejected, and preserved outputs. No installed host Go behavior was
used as authority.
collision, not package identity. Section 11.31 completes the later install
dependency: compile-only products retain the request transaction, while a
running retained product installs independently only after its successful run.
No installed host Go behavior was used as authority.
Before this slice, direct native measurements of both Cstage and WWstage showed
that single-package `-c -o FILE` retained and did not run, but `-o FILE`
@@ -6268,23 +6267,24 @@ performs ordinary production validation, reports `[no tests]`, and creates no
binary or otherwise-unused output directory. Successful compile-only products
are silent, matching Go's no-op print action.
After linking, the driver copies the private runnable bytes to a distinct
`.new` inode opened with executable mode `0777` subject to umask. Temporary
runnable, retained copy, statuses, changed persistent actions, tool records,
and stamp then enter the existing one-request transaction. All producers and
linkers complete before installation. Any load, compile, assemble, archive,
link, stage, or install failure preserves old retained binaries and persistent
bytes, discards all stages, removes cold scratch, and rolls back only output
prefixes created by that request. Occupied or dangling `.new` paths reject
before tools and are never overwritten.
For `-c`, the driver copies the private runnable bytes to a distinct `.new`
inode opened with executable mode `0777` subject to umask. Temporary runnable,
retained copy, statuses, changed persistent actions, tool records, and stamp
then enter the existing one-request transaction. All producers and linkers
complete before installation. Any load, compile, assemble, archive, link,
stage, or install failure preserves old retained binaries and persistent bytes,
discards all stages, removes cold scratch, and rolls back only output prefixes
created by that request. Occupied or dangling `.new` paths reject before tools
and are never overwritten.
Execution begins only after publication commits. Assertion failure, signal,
timeout, or child-setup failure therefore leaves an explicitly retained binary
while retaining the established stdout/stderr result routing and sibling
isolation. Parallel products stage independent runnable/copy pairs; the shared
transaction prevents partial sibling publication and canonical result emission
order remains unchanged. Direct invocation of a retained binary continues to
inherit caller cwd, environment, and separate standard descriptors.
For running `-o`, the build transaction commits only the private runnable,
status, and semantic actions. The coordinator executes that runnable and, on a
successful result, invokes the selected driver stage's public install action.
Assertion failure, signal, timeout, interruption, or child-setup failure skips
that action and preserves any prior binary. Successful parallel products
install independently after their runs; canonical result emission order stays
unchanged. Direct invocation of a retained binary continues to inherit caller
cwd, environment, and separate standard descriptors.
`-c` and `-o` can accompany `-w`: the workdir owns only semantic actions while
the invocation/output path owns only the retained copy. Unchanged actions are
@@ -6302,9 +6302,9 @@ declared-name versus import-leaf naming; executable mode and direct execution;
temporary argv versus retained path; no-test omission; duplicate and
non-directory rejection; occupied stages; serial and parallel sibling
publication; injected late-link rollback over old files and newly created
parents; runtime-failure retention; persistent cold/warm/invalidation behavior;
diagnostic equality; retained binary byte identity; and absence of `.new`
residue. Existing package tests continue to own all action/test variants,
parents; runtime-failure preservation; persistent cold/warm/invalidation
behavior; diagnostic equality; retained binary byte identity; and absence of
`.new` residue. Existing package tests continue to own all action/test variants,
graph identity, output ordering, cwd/environment/stdin, timeout, and broader
transaction behavior.
@@ -6760,6 +6760,150 @@ output umasks, occupied stages, generalized multi-product transactions, test
runtime failure and timeout, null discard, and broader package/import graph
matrices.
### 11.31 Implemented Go-like public-output overwrite safety
Every caller-visible build and retained-test install now protects an existing
destination at the same late boundary as Go 1.26.5. After applicable producers
finish, ordinary `stat` rejects a directory and rejects a nonempty regular file
whose leading bytes do not identify a toolchain output. Absent paths, empty
regular reservations, recognized outputs, and non-directory non-regular paths
remain replaceable. Exact `/dev/null` and assembly-only `-S` have no install
action and never enter this rule.
#### Pinned Go evidence and classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `Shell.moveOrCopyFile` and `Shell.CopyFile` call `checkDstOverwrite` before
replacing the destination
([`cmd/go/internal/work/shell.go`, lines 119235](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L119-L235)).
`checkDstOverwrite` uses `os.Stat`, rejects a directory, and—unless forced—
rejects a nonempty regular file for which `isObject` is false
([lines 248261](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L248-L261)).
- `BuildInstallFunc` creates the destination parent and reaches
`moveOrCopyFile(..., false)` only after its build producer
([`cmd/go/internal/work/exec.go`, lines 19042000](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1904-L2000)).
`objectMagic` and `isObject` read the first 64 bytes and recognize archive,
ELF, Mach-O, PE, Plan 9, WASM, and XCOFF prefixes without consulting a file
extension
([lines 21182150](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L2118-L2150)).
- `runBuild` loads and checks every selected package before constructing the
output/install action
([`cmd/go/internal/work/build.go`, lines 459558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L558)).
- `builderTest` makes `-c` depend directly on the install action. For a running
retained test, the run consumes the private build action and the install
action additionally depends on that run
([`cmd/go/internal/test/test.go`, lines 12571364](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1257-L1364)).
`Builder.Do` invokes an actor only when dependency failure has not propagated
(unless the action explicitly ignores failure), so a failed test run skips
`BuildInstallFunc`
([`cmd/go/internal/work/exec.go`, lines 72207](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L72-L207)).
- Official `build_output_overwrite.txt` requires refusal to replace a
nonempty source file and preservation of its contents
([lines 120](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_output_overwrite.txt#L1-L20)).
Official `test_compile_tempfile.txt` requires an existing empty reservation
to be accepted and replaced
([lines 111](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_compile_tempfile.txt#L1-L11)).
Official `build_output.txt` separately pins executable command and archive
products
([lines 4757 and 6476](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_output.txt#L47-L76)).
The destination predicate, complete magic table, load-before-install ordering,
producer-before-check ordering, and run-before-install dependency are directly
implemented by the pinned source. Non-overwrite and empty-file acceptance are
directly asserted by official testdata. Applying ELF and archive recognition
to WW's byte-identical output forms is derived from that implementation. Go
has no WW interface sidecar; recognizing only the compiler-owned
`//ww:module ` prefix is the derived local application that permits ordinary
repeat publication without letting arbitrary sidecar text be overwritten. No
installed host Go behavior was used as authority.
#### Fresh four-axis audit and pre-fix measurements
The bounded audit selected this one gap on the build axis and the shared
retained-test install axis. The package control selected the same canonical
command root twice and measured one deduplicated command action plus one
dependency action, with byte-identical Cstage/WWstage units, interfaces, and
archives. The import control placed a used alias in one source file and an
unused alias for the same dependency in a sibling; both stages emitted the
same file-local unused-import diagnostic and committed no work. Those package
and import candidates were aligned and were not changed.
Fresh public Cstage and WWstage probes directly measured the same pre-fix
behavior. Explicit command, raw command, output-directory child, library
archive/interface, compile-only test, and running retained-test destinations
containing arbitrary nonempty text were replaced successfully. A nonempty
directory at a build or test child destination was renamed to a PID-bearing
transaction backup, replaced by the executable, and left stranded because
backup cleanup could not unlink the directory. Empty reservations were already
accepted. Missing-import rejection already preceded destination handling. All
measured successful executables, archives, interfaces, diagnostics, runtime
results, and semantic artifacts were stage-identical. Those are directly
measured WW facts, not source inferences.
#### Ownership, action order, rollback, and identity
The Cstage `sep_txn_commit` and WWstage `septxncommit` publishers own the byte
predicate. Each transaction entry now explicitly distinguishes a public
install from internal status, tool-identity, stamp, and persistent-action
state. Only command/archive output, retained-copy, and published `.wwi` entries
are checked. Producers still finish before transaction commit; a rejected
destination discards all staged outputs and preserves every prior public and
persistent byte. A library archive and interface remain one rollback group, so
arbitrary text in either destination changes neither.
`internal/wwpackage.packagecommand` continues to own directory-test naming and
scheduling. A running retained descriptor withholds its public destination
from the build child. After a successful private run, the coordinator invokes
a private action in the selected driver, which stages an executable copy and
re-enters the same guarded publisher. A failed, signalled, timed-out,
interrupted, or unstartable run never invokes that action. Successful products
in a multi-package running request install independently; compile-only products
retain the established request transaction. The driver-owned raw single-file
route applies the same private build, run, and guarded-install sequence.
Package-build descriptors use `build-public` only for caller-visible command or
archive products. Private package-build placeholders, test runnables, `ww run`,
workdir-owned test binaries, null-discard products, and assembly-only products
remain internal entries. Destination path, file kind, magic, declared name,
requested alias, import leaf, physical directory, and publication order do not
enter package/import identity, graph edges, action keys, symbols, artifacts,
`.wwi` contents, or persistence keys.
The guard follows symlinks for classification, matching `os.Stat`; the existing
transaction still replaces the destination directory entry itself. It permits
FIFO and other non-directory non-regular destinations because the pinned guard
does. Diagnostics are exactly
`ww: build output "PATH" already exists and is a directory` and
`ww: build output "PATH" already exists and is not an object file` in both
stages. No guard is preflighted during loading: package/import errors still win,
and compiler, assembler, archive, or linker failure prevents the install action
from being reached.
Build runtime is inapplicable because `ww build` starts no program. Test
runtime is applicable and owns the post-run dependency above. Producer failure,
linker interruption, output-parent rollback, concurrency, occupied stages,
prior-state preservation, and residue cleanup remain governed by the existing
request/private-action transactions; the new check adds no process-global
state. Public artifact bytes and modes are unchanged on accepted installs.
There is no persisted-byte contract change: build workdir format remains `18`,
test workdir format remains `19`, and semantic storage format remains `3`.
The WW-native `public_output_overwrite_safety` observer covers both stages:
direct, default, raw, package-output-directory, library, compile-only test, and
running-test routes; late linker activity and load precedence; absent/empty,
ELF, archive, interface, arbitrary regular, directory, symlink, and FIFO
destinations; cold, warm, and invalidated persistent rollback; run-before-check
and failed-run no-install behavior; exact null and assembly-only exclusions;
runtime results; modes; diagnostic identity; public and semantic artifact-byte
identity; and `.new`, install-stage, and transaction-backup cleanup.
`test_binary_publication_transaction` pins the changed failed-run behavior and
the existing linker failure, output-parent rollback, multi-product, persistent,
and retained-binary contracts. Existing request-transaction, timeout,
interruption, and concurrent-driver owners continue to cover those unchanged
dimensions.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -335,6 +335,22 @@ ImportPath = ident { "." ident } .
output. Output paths and directory metadata never become package, import,
graph, action,
symbol, artifact, `.wwi`, or persistence identity.
Every caller-visible build installation checks its destination after all
applicable compile, assemble, archive, and link producers finish. Ordinary
`stat` follows symlinks. An existing directory rejects as
`ww: build output "PATH" already exists and is a directory`; an existing
nonempty regular file rejects as `... is not an object file` unless its
leading bytes identify a Go 1.26.5 object/output form. The recognized table
is archive, ELF, Mach-O, PE, Plan 9, WASM, and XCOFF magic; WW additionally
recognizes its compiler-owned `//ww:module ` interface prefix. An absent
path, an empty regular reservation, or a non-directory non-regular path may
be replaced. A published non-main archive and its `.wwi` sidecar are checked
as one WW request transaction, so arbitrary caller text in either
destination preserves both old outputs and the committed persistent
generation. This safety check is output disposition only: it does not enter
package/import loading, graph or action identity, artifact bytes, or
invalidation.
Assembly-only `-S` retains the directory form's command-action selection and
no-main rejection, but it reaches no install action: destination length,
duplicate publication names, implicit destination collision, and output
@@ -714,12 +730,17 @@ paths, and retained binary names remain presentation or loader metadata and do
not become canonical package or action identity.
The retained file is an executable, byte-identical copy of the private
runnable. It joins package artifacts and statuses in the request-wide atomic
publication transaction. Build, link, stage, or install failure preserves old
destinations and removes temporary stages and invocation-created output
prefixes. Test execution starts only after that transaction commits, so a
runtime failure leaves an explicitly retained binary. A no-test product
publishes no binary and does not create a directory solely for one.
runnable. A compile-only retained binary joins package artifacts and statuses
in the request-wide atomic publication transaction. Build, link, stage, or
install failure preserves old destinations and removes temporary stages and
invocation-created output prefixes. For a running `-o` request, the private
binary executes first. Only a successful run enters the guarded install
action; a failed, signalled, timed-out, interrupted, or unstartable run
publishes no new copy and preserves any prior destination. The post-run guard
uses the same directory/nonempty-regular/object-magic rule as `ww build`.
Successful products in a multi-package running request install independently;
their visible result order remains package order. A no-test product publishes
no binary and does not create a directory solely for one.
`-w` may persist the unchanged semantic actions for either `-c` or running
retention without changing publication identity or introducing a test-result
cache.

View File

@@ -258,11 +258,25 @@ install-only destination validation or output-directory creation.
The retained executable is byte-identical to the temporary runnable and has
executable mode `0777` filtered by the caller's umask, but it is never the path
executed by the coordinator. Publication participates in the driver's one
request-wide transaction: producer, linker, staging, or installation failure
preserves every prior destination and removes stages and newly created output
prefixes. A runtime failure occurs after commit and therefore leaves the saved
binary. The language runtime owns individual `@test` functions.
executed by the coordinator. Compile-only publication participates in the
driver's one request-wide transaction: producer, linker, staging, or
installation failure preserves every prior destination and removes stages and
newly created output prefixes. A running retained request instead withholds
the public path from that build transaction. The coordinator runs the private
binary, then invokes the selected stage driver's guarded install action only
after a successful process result. Failure, signal, timeout, interruption, or
child-start failure therefore preserves any prior retained binary and creates
no new one. Successful products install independently after their runs. The
language runtime owns individual `@test` functions.
Every build or retained-test public install follows Go 1.26.5's late
destination safety rule. After applicable producers (and, for running tests,
after the successful run), ordinary `stat` rejects a directory and rejects a
nonempty regular non-object file. Empty reservations, recognized prior
outputs, and non-directory non-regular paths remain replaceable. Recognition
uses Go's archive/ELF/Mach-O/PE/Plan 9/WASM/XCOFF magic plus WW's narrow
`//ww:module ` interface prefix. Package/import rejection keeps its earlier
diagnostic precedence; exact `/dev/null` and `-S` never enter the guard.
Every actually executed directory product gives its single generated binary
the product's canonical absolute physical source directory as child cwd. A
@@ -468,8 +482,9 @@ with one fresh `mkdir` and refuses an existing path; it never clears a
collision. A caller keeps only the exact artifacts it observes and removes
that exact tree on every later success or failure. Directory-package test
plans instead keep their cold semantic-action scratch and runnable binary
inside the coordinator's temporary root; only the optional retained executable
escapes through the transaction above. `ww run` and no-output single-file
inside the coordinator's temporary root; a compile-only retained executable
escapes through the build transaction, while a running retained executable
uses the post-run install action above. `ww run` and no-output single-file
`ww test` use driver-owned scratch instead; both driver stages place that
scratch and their temporary executable beneath one freshly acquired directory,
remove both after every build result, and make cleanup failure fail the

View File

@@ -39,9 +39,13 @@ type pkggroup = struct {
bin: str,
buildok: str,
runoutput: str,
installoutput: str,
state: i32,
runstartfailed: bool,
runres: exec.result,
publicbin: bool,
installattempted: bool,
installres: exec.result,
};
type pkgplan = struct {
@@ -64,6 +68,7 @@ type pkgplan = struct {
outputcollisionbase: str,
outputcollisiondir: str,
suppressbuildreports: bool,
deferpublish: bool,
};
def PKG_COUNT_MAX: i32 = 2147483647;
@@ -1373,16 +1378,23 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
pkgfailpath(g.root, "cannot create product temporary path");
return false;
};
g.publicbin = false;
g.installattempted = false;
if (buildonly) {
if (outputdir && strings.compare(g.pkg, "main") == 0
&& !p.outputpatherror && !p.emitasm) {
if (!pkgjoinpath(outname, g.basename, &g.bin)) { return false; };
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
g.publicbin = true;
} else if (outname.len != 0 && !outputdir) {
g.bin = outname;
g.publicbin = true;
}
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
} else {
if (!pkgstring(&g.bin, g.root, "/package.test")) { return false; };
};
if (!pkgstring(&g.runoutput, g.root, "/test.output")
|| !pkgstring(&g.installoutput, g.root, "/install.output")
|| !pkgstring(&g.buildok, g.root, "/build.ok")) { return false; };
i += 1;
};
@@ -1484,7 +1496,8 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
for (i < p.end) {
let g: *pkggroup = &groups[i];
append(ba, "--ww-package-test");
if (p.buildonly) { append(ba, "build"); }
if (p.buildonly && g.publicbin) { append(ba, "build-public"); }
else if (p.buildonly) { append(ba, "build"); }
else { append(ba, "test"); };
append(ba, g.pkg);
if (g.prodpkg.len != 0) { append(ba, g.prodpkg); }
@@ -1495,7 +1508,7 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
else { append(ba, "-"); };
append(ba, g.dir);
append(ba, g.bin);
if (g.publish.len != 0) { append(ba, g.publish); }
if (g.publish.len != 0 && !p.deferpublish) { append(ba, g.publish); }
else { append(ba, "-"); };
append(ba, g.buildok);
i += 1;
@@ -1591,6 +1604,31 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
return true;
};
// Pinned builderTest orders a retained running test as build -> run ->
// BuildInstallFunc. Re-enter the selected stage driver for the final action so
// build and test share one overwrite predicate instead of copying object magic
// into the coordinator.
fn pkginstalloutput(g: *pkggroup, builder: str) void = {
let ia: []str = [builder, "test", "--ww-install-test-output",
g.bin, g.publish];
let env: []str = os.getenvs();
let icmd: exec.command;
icmd.path = builder;
icmd.argv = ia;
icmd.env = env;
icmd.dir = "";
icmd.stdoutpath = g.installoutput;
icmd.stderrpath = g.installoutput;
icmd.deadline.sec = 0i64;
icmd.deadline.nsec = 0i64;
icmd.grace = 0i64: time.duration;
let h: exec.process;
g.installattempted = true;
exec.start(&h, &icmd);
for (!exec.poll(&h)) { time.sleep(pkgpoll, time.clock.monotonic); };
g.installres = h.result;
};
fn pkgproductbuilt(g: *pkggroup, buildonly: bool) bool = {
if (buildonly || g.notests) { return pkgisreg(g.buildok); };
return pkgisreg(g.buildok) && pkgisreg(g.bin);
@@ -1602,6 +1640,35 @@ fn pkgrunok(g: *pkggroup) bool = {
&& g.runres.code == 0;
};
fn pkginstallok(g: *pkggroup) bool = {
return g.installres.errno == 0 && g.installres.cleanuperrno == 0
&& g.installres.termination == exec.termination.EXIT
&& g.installres.code == 0;
};
fn pkgemitinstall(g: *pkggroup) bool = {
if (!g.installattempted) { return true; };
let output: str;
if (!pkgread(g.installoutput, &output)) {
pkgfailpath(g.root, "cannot read install capture");
return false;
};
if (output.len != 0) {
pkgput(os.STDERR_FILENO, output);
if (output[output.len - 1] != '\n') {
pkgput(os.STDERR_FILENO, "\n");
};
};
if (!pkginstallok(g)) {
if (output.len == 0) {
pkgreportcommand(os.STDERR_FILENO, "install", g,
&g.installres);
};
return false;
};
return true;
};
fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
if (g.notests) {
pkgput(os.STDOUT_FILENO, "? ");
@@ -1653,8 +1720,9 @@ fn pkgemitplan(p: *pkgplan, groups: []pkggroup,
failed += 1;
} else if (buildonly) {
void;
} else if (!pkgemitgroup(&groups[i], compileonly)) {
failed += 1;
} else {
if (!pkgemitgroup(&groups[i], compileonly)) { failed += 1; };
if (!compileonly && !pkgemitinstall(&groups[i])) { failed += 1; };
};
i += 1;
};
@@ -2385,11 +2453,12 @@ export fn packagecommand(args: []str) int = {
plan.outputcollisiondir = outputcollisiondir;
plan.suppressbuildreports = buildonly
&& (outputdir || defaultoutputdir.len != 0);
plan.deferpublish = !buildonly && !compileonly && testretain && !testnull;
append(plans, plan);
let createdir: str = "";
if (buildonly && outputdir) {
createdir = outname;
} else if (!buildonly && testretain && !testnull) {
} else if (!buildonly && compileonly && testretain && !testnull) {
i = 0;
for (i < groups.len) {
if (groups[i].publish.len != 0) {
@@ -2550,6 +2619,22 @@ export fn packagecommand(args: []str) int = {
if (active > 0) { time.sleep(pkgpoll, time.clock.monotonic); };
};
// A failed, signalled, timed-out, or unstartable run propagates to its Go
// install action, so it preserves any prior retained binary. Successful
// products install independently after their private execution.
if (!compileonly) {
i = 0;
for (i < groups.len) {
let g: *pkggroup = &groups[i];
if (g.publish.len != 0 && !g.notests
&& pkgproductbuilt(g, buildonly) && !g.runstartfailed
&& pkgrunok(g)) {
pkginstalloutput(g, builder);
};
i += 1;
};
};
i = 0;
for (i < plans.len) {
failed += pkgemitplan(&plans[i], groups, compileonly, buildonly);

View File

@@ -1515,6 +1515,7 @@ type sepproduct = struct {
directoryproduct: bool,
notests: bool,
buildaction: bool, // loaded product retained in the action list
publicout: bool,
context: i32,
root: i32,
variantroot: i32,
@@ -6134,6 +6135,7 @@ type septxnentry = struct {
backup: *u8,
hadold: bool,
installed: bool,
publicoutput: bool,
};
fn sepalloctxnentries(cap: i32) ([]septxnentry | nomem) = {
@@ -6207,8 +6209,8 @@ fn septxnbackup(dst: *u8) *u8 = {
return buf.ptr;
};
fn septxnadd(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8) bool = {
fn septxnaddmode(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8, publicoutput: bool) bool = {
if (cstreq(stage, dst)) {
cerrpath("ww: transaction path collision: ", dst, "\n");
return false;
@@ -6253,10 +6255,21 @@ fn septxnadd(entries: *[]septxnentry, n: *i32,
(*entries)[*n].backup = backup;
(*entries)[*n].hadold = false;
(*entries)[*n].installed = false;
(*entries)[*n].publicoutput = publicoutput;
*n += 1;
return true;
};
fn septxnadd(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8) bool = {
return septxnaddmode(entries, n, stage, dst, false);
};
fn septxnaddpublic(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8) bool = {
return septxnaddmode(entries, n, stage, dst, true);
};
fn septxnaddpkgsuffix(entries: *[]septxnentry, n: *i32,
g: *sepgraph, pi: i32, scratch: *u8,
stagesuffix: str, dstsuffix: str) bool = {
@@ -6271,6 +6284,91 @@ fn septxndiscard(entries: []septxnentry, n: i32) void = {
for (i < n) { os.remove(pathstr(entries[i].stage)); i += 1; };
};
fn sepoutputprefix(buf: *u8, n: i64, magic: str) bool = {
if (n < 0i64 || n < magic.len: i64) { return false; };
let i: i32 = 0;
for (i < magic.len) {
if (buf[i] != magic.ptr[i]) { return false; };
i += 1;
};
return true;
};
fn sepoutput2(buf: *u8, n: i64, a: u8, b: u8) bool = {
return n >= 2i64 && buf[0u64] == a && buf[1u64] == b;
};
fn sepoutput4(buf: *u8, n: i64, a: u8, b: u8, c: u8, d: u8) bool = {
return n >= 4i64 && buf[0u64] == a && buf[1u64] == b
&& buf[2u64] == c && buf[3u64] == d;
};
fn sepoutput6(buf: *u8, n: i64, a: u8, b: u8, c: u8, d: u8,
e: u8, f: u8) bool = {
return n >= 6i64 && buf[0u64] == a && buf[1u64] == b
&& buf[2u64] == c && buf[3u64] == d
&& buf[4u64] == e && buf[5u64] == f;
};
// Go 1.26.5 work.objectMagic, plus WW's compiler-owned interface prefix.
fn sepisobjectoutput(path: *u8) bool = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return false; };
let buf: [64]u8;
let got: u64 = 0u64;
let bad: bool = false;
for (got < 64u64) {
let n: i64 = os.read(fd, &buf[0] + got, 64u64 - got);
if (n > 0) { got += n: u64; }
else if (n == 0) { break; }
else if (n != -4i64) { bad = true; break; };
};
os.close(fd);
if (bad) { return false; };
let n: i64 = got: i64;
if (sepoutputprefix(&buf[0], n, "!<arch>\n")
|| sepoutputprefix(&buf[0], n, "<bigaf>\n")
|| sepoutput4(&buf[0], n, 127u8, 69u8, 76u8, 70u8)
|| sepoutput4(&buf[0], n, 254u8, 237u8, 250u8, 206u8)
|| sepoutput4(&buf[0], n, 254u8, 237u8, 250u8, 207u8)
|| sepoutput4(&buf[0], n, 206u8, 250u8, 237u8, 254u8)
|| sepoutput4(&buf[0], n, 207u8, 250u8, 237u8, 254u8)
|| sepoutput6(&buf[0], n, 77u8, 90u8, 144u8, 0u8, 3u8, 0u8)
|| sepoutput6(&buf[0], n, 77u8, 90u8, 120u8, 0u8, 1u8, 0u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 1u8, 235u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 138u8, 151u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 6u8, 71u8)
|| sepoutput4(&buf[0], n, 0u8, 97u8, 115u8, 109u8)
|| sepoutput2(&buf[0], n, 1u8, 223u8)
|| sepoutput2(&buf[0], n, 1u8, 247u8)
|| sepoutputprefix(&buf[0], n, "//ww:module ")) {
return true;
};
return false;
};
fn sepcheckdstoverwrite(dst: *u8) bool = {
let fi: os.filestat;
match (os.stat(&fi, pathstr(dst))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ == os.mode.DIR: u32) {
cerr("ww: build output "); sepputquoted(dst);
cerr(" already exists and is a directory\n");
return false;
};
if (typ == os.mode.REG: u32 && fi.sz != 0u64
&& !sepisobjectoutput(dst)) {
cerr("ww: build output "); sepputquoted(dst);
cerr(" already exists and is not an object file\n");
return false;
};
};
case let e: os.oserror => void;
};
return true;
};
fn septxncommit(entries: []septxnentry, n: i32) bool = {
let i: i32 = 0;
let valid: bool = true;
@@ -6283,6 +6381,17 @@ fn septxncommit(entries: []septxnentry, n: i32) bool = {
};
i += 1;
};
if (valid) {
i = 0;
for (i < n) {
if (entries[i].publicoutput
&& !sepcheckdstoverwrite(entries[i].dst)) {
valid = false;
break;
};
i += 1;
};
};
if (valid) {
i = 0;
for (i < n) {
@@ -6614,6 +6723,50 @@ fn septxnrelease(entries: *[]septxnentry, n: i32) void = {
entries.cap = 0;
};
// Running retained tests enter this Go-like install action only after their
// request-private executable has returned successfully.
fn sepinstalltestoutput(stage: *u8, dst: *u8) i32 = {
let installstage: *u8 = sepappendlit(stage, ".install");
if (installstage == nil) { return 1; };
if (cstrlen(installstage) + 1u64 > os.PATH_MAX: u64
|| pathexistsnofollow(installstage) != 0
|| copyexecutablestage(stage, installstage) != 0) {
cerr("ww: cannot stage retained test output\n");
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
let parent: *u8 = seplexicalparent(dst);
if (parent == nil) {
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
let created: sepcreateddirs;
created.n = 0;
if (sepmkdirsrecord(parent, 511, &created) != 0) {
cerrpath("ww: cannot create test output directory ", parent, "\n");
os.free(parent: *void, cstrlen(parent) + 1u64);
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
os.free(parent: *void, cstrlen(parent) + 1u64);
let entries: []septxnentry;
let n: i32 = 0;
if (!septxnaddpublic(&entries, &n, installstage, dst)
|| !septxncommit(entries, n)) {
septxndiscard(entries, n);
septxnrelease(&entries, n);
seprollbackdirs(&created);
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
septxnrelease(&entries, n);
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 0;
};
fn sepfinishfail(entries: *[]septxnentry, n: i32) i32 = {
septxndiscard(*entries, n);
septxnrelease(entries, n);
@@ -6924,19 +7077,24 @@ fn sepfinishrequest(selfdir: *u8, l6: *u8, c6: *u8, a6: *u8,
producti = 0;
for (producti < nproducts) {
if (products[producti].stageout != nil
&& !septxnadd(&entries, &ntxn, products[producti].stageout,
products[producti].out)) {
return sepfinishfail(&entries, ntxn);
&& ((products[producti].publicout
&& !septxnaddpublic(&entries, &ntxn,
products[producti].stageout, products[producti].out))
|| (!products[producti].publicout
&& !septxnadd(&entries, &ntxn,
products[producti].stageout, products[producti].out)))) {
return sepfinishfail(&entries, ntxn);
};
if (products[producti].stagepublish != nil
&& !septxnadd(&entries, &ntxn, products[producti].stagepublish,
&& !septxnaddpublic(&entries, &ntxn,
products[producti].stagepublish,
products[producti].publish)) {
return sepfinishfail(&entries, ntxn);
};
if (products[producti].stageiface != nil) {
let outiface: *u8 = sepappendlit(products[producti].out, ".wwi");
if (outiface == nil
|| !septxnadd(&entries, &ntxn,
|| !septxnaddpublic(&entries, &ntxn,
products[producti].stageiface, outiface)) {
return sepfinishfail(&entries, ntxn);
};
@@ -8213,7 +8371,8 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
requirecommand: i32, istest: i32,
rootvariant: i32, testpackage: *u8, emitasm: i32,
keepscratch: i32, workdir: *u8, createoutputdir: *u8,
publicoutput: bool, keepscratch: i32, workdir: *u8,
createoutputdir: *u8,
defaultoutputdir: *u8, outputpatherror: bool) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
@@ -8235,6 +8394,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
product.directoryproduct = false;
product.notests = false;
product.buildaction = true;
product.publicout = publicoutput;
product.root = -1;
product.variantroot = -1;
product.support = -1;
@@ -8691,7 +8851,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, 0i32, workdir, nil, nil, false);
emitasm, false, 0i32, workdir, nil, nil, false);
let cleanbad: bool = false;
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
@@ -8710,7 +8870,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return buildonesep(selfdir, resolved, isdir, rootidentity,
out, objstem, incs.ptr, &lf,
publishpackage, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, 1i32, workdir, createoutputdir, defaultoutputdir,
emitasm, true, 1i32, workdir, createoutputdir, defaultoutputdir,
outputpatherror);
};
@@ -8893,7 +9053,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 1i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
0i32, 0i32, nil, nil, nil, false) != 0) {
0i32, false, 0i32, nil, nil, nil, false) != 0) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
@@ -8958,10 +9118,12 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
let owntmp: bool = false;
let retainout: bool = outstem != nil
&& !cstreqlit(outstem, "/dev/null");
if (retainout) {
let deferredinstall: bool = retainout && compileonly == 0
&& emitasm == 0;
if (retainout && !deferredinstall) {
outp = outstem;
objstem = outstem;
} else { if (workdir != nil) {
} else { if (workdir != nil && !deferredinstall) {
// The workdir owns the persistent test binary the same way it
// owns the package artifacts.
outp = joinpathlit(workdir, "main");
@@ -8975,6 +9137,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
};
outp = joinpathlit(tmp.ptr, "main");
objstem = outp;
if (retainout) { objstem = outstem; };
}; };
// E3-C1: separate compilation is the sole build path (task #87).
let lf: lflags;
@@ -8986,7 +9149,8 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
if (retainout) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, keep, workdir, nil, nil, false);
emitasm, retainout && !deferredinstall, keep, workdir,
nil, nil, false);
if (bres != 0) {
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));
@@ -9037,6 +9201,8 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
rc = -1;
};
}; };
if (rc == 0 && deferredinstall
&& sepinstalltestoutput(outp, outstem) != 0) { rc = 1; };
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
@@ -9052,6 +9218,10 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
};
fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (argc - start == 3
&& cstreqlit(argv[start], "--ww-install-test-output")) {
return sepinstalltestoutput(argv[start + 1], argv[start + 2]);
};
// -I parsing mirrors dobuild/dorun so a single-file test can resolve
// transitive imports (e.g. 905_nkname asttest → tok); coupled to the
// -T flip (task #5/#10).
@@ -9203,7 +9373,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let publish: *u8 = argv[i + 8];
let status: *u8 = argv[i + 9];
let pn: u64 = cstrlen(name);
let buildproduct: bool = cstreqlit(kind, "build");
let publicbuildproduct: bool =
cstreqlit(kind, "build-public");
let buildproduct: bool = cstreqlit(kind, "build")
|| publicbuildproduct;
let testproduct: bool = cstreqlit(kind, "test");
let hasproduction: bool = !cstreqlit(production, "-");
let hasinternal: bool = !cstreqlit(internal, "-");
@@ -9248,6 +9421,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
product.directoryproduct = true;
product.notests = testproduct && !hasinternal && !hasexternal;
product.buildaction = true;
product.publicout = publicbuildproduct;
product.root = -1;
product.variantroot = -1;
product.productionroot = -1;

View File

@@ -12443,7 +12443,6 @@ fn runtimepath(relative: str) str = {
let rollbackdiag: str = "";
let missingdiag: str = "";
let occupieddiag: str = "";
let failurebytes: str = "";
let persistbasebytes: str = "";
let persistchangedbytes: str = "";
let baseenv: []str = os.getenvs();
@@ -12595,29 +12594,23 @@ fn runtimepath(relative: str) str = {
" [no tests]\n")));
assert(out.stderr.len == 0 && !os.exists(noneroot));
// Publication commits before execution. A failing test therefore leaves
// a runnable retained binary while its product failure stays on stdout.
// Pinned builderTest makes the install action depend on the run action.
// A failing run therefore preserves a prior destination and never enters
// the retained-output overwrite guard.
let failurebin: str = strings.concat(root, "/failure-", tags[si],
".test");
writefile(failurebin, "failure-sentinel\n");
let failureav: []str = [driver(stages[si]), "test", "-I", suite,
"-o", failurebin, failed];
runcommandenvdir(root, strings.concat("publish-runtime-", tags[si]),
failureav, env, root,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(os.exists(failurebin) && !os.exists(strings.concat(failurebin,
".new")));
assert(same(readfile(failurebin), "failure-sentinel\n"));
assert(!os.exists(strings.concat(failurebin, ".new")));
assert(has(out.stdout,
"failed_decl.runtime_failure ... FAIL (exit 1)\n"));
assert(out.stderr.len == 0);
let fi: os.filestat;
match (os.stat(&fi, failurebin)) {
case void => void;
case let e: os.oserror => abort("retained failure binary missing");
};
assert(((fi.mode: u32) & 73u32) != 0u32);
if (si == 0) { failurebytes = readfile(failurebin); }
else { assert(same(failurebytes, readfile(failurebin))); };
// -w keeps semantic actions persistent while -c and -o remain execution
// and presentation policy. Compile-only can cold-build and invalidate;
@@ -12681,6 +12674,377 @@ fn runtimepath(relative: str) str = {
clean(root);
};
// Go 1.26.5's BuildInstallFunc protects caller data at the install boundary:
// after producers, and after a successful run for retained running tests.
// Exercise that one predicate through direct, raw, package, and test routes.
@test fn public_output_overwrite_safety() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let dep: str = strings.concat(source, "/lib/dep");
let app: str = strings.concat(source, "/cmd/app");
let bad: str = strings.concat(source, "/cmd/bad");
let checked: str = strings.concat(source, "/pkg/check");
let failed: str = strings.concat(source, "/pkg/failed");
let invocation: str = strings.concat(root, "/invoke");
mkdirall(dep); mkdirall(app); mkdirall(bad); mkdirall(checked);
mkdirall(failed); mkdirall(invocation);
writefile(strings.concat(dep, "/dep.ww"), strings.concat(
"package renamed;\n",
"export fn value() int = { return 9; };\n"));
let appsource: str = strings.concat(app, "/main.ww");
let appbase: str = strings.concat(
"package main;\n",
"import stable lib.dep;\n",
"export fn main() int = { return stable.value(); };\n");
let appchanged: str = strings.concat(
"package main;\n",
"import stable lib.dep;\n",
"export fn main() int = { return stable.value() + 1; };\n");
writefile(appsource, appbase);
writefile(strings.concat(bad, "/main.ww"), strings.concat(
"package main;\n",
"import missing nowhere.present;\n",
"export fn main() int = { return missing.value(); };\n"));
writefile(strings.concat(checked, "/check.ww"),
"package checked;\nfn value() int = { return 4; };\n");
writefile(strings.concat(checked, "/check_test.ww"), strings.concat(
"package checked;\n",
"@test fn guarded_run() void = { assert(value() == 4); };\n"));
writefile(strings.concat(failed, "/failed_test.ww"), strings.concat(
"package failed;\n",
"@test fn failed_run() void = { assert(false); };\n"));
let raw: str = strings.concat(root, "/raw.ww");
writefile(raw,
"package main;\nexport fn main() int = { return 3; };\n");
let linkerwrapper: str = strings.concat(root, "/overwrite-w6l.sh");
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\n",
"printf 'link\\n' >> \"$WW_OVERWRITE_TRACE\"\n",
"exec \"$WW_OVERWRITE_REAL_L\" \"$@\"\n"));
let drivers: []str = ["ww", "ww_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let tags: []str = ["c", "ww"];
let commandout: str = strings.concat(root, "/command.bin");
let rawout: str = strings.concat(root, "/raw.bin");
let defaultout: str = strings.concat(invocation, "/app");
let libout: str = strings.concat(root, "/library.a");
let testbin: str = strings.concat(root, "/check.test");
let symlinktarget: str = strings.concat(root, "/symlink-target");
let symlinkout: str = strings.concat(root, "/symlink.bin");
let fifoout: str = strings.concat(root, "/fifo.bin");
writefile(symlinktarget, "symlink-sentinel\n");
assert(os.symlink(symlinktarget, symlinkout) == 0);
let builddir: str = strings.concat(root, "/build-output");
let buildchild: str = strings.concat(builddir, "/app");
let testdir: str = strings.concat(root, "/test-output");
let testchild: str = strings.concat(testdir, "/check.test");
mkdirall(buildchild); mkdirall(testchild);
writefile(strings.concat(buildchild, "/marker"), "build-directory\n");
writefile(strings.concat(testchild, "/marker"), "test-directory\n");
let commanddiag: str = "";
let rawdiag: str = "";
let defaultdiag: str = "";
let symlinkdiag: str = "";
let builddirdiag: str = "";
let librarydiag: str = "";
let libraryarchivediag: str = "";
let testcompilediag: str = "";
let testrundiag: str = "";
let testdirdiag: str = "";
let loaddiag: str = "";
let commandbytes: str = "";
let librarybytes: str = "";
let interfacebytes: str = "";
let testbytes: str = "";
let unitbytes: str = "";
let baseenv: []str = os.getenvs();
let out: commandout;
let si: i32 = 0;
for (si < drivers.len) {
let trace: str = strings.concat(root, "/overwrite-", 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_W6L=")
&& !strings.hasprefix(baseenv[ei], "WW_OVERWRITE_TRACE=")
&& !strings.hasprefix(baseenv[ei], "WW_OVERWRITE_REAL_L=")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6L=", linkerwrapper));
append(env, strings.concat("WW_OVERWRITE_TRACE=", trace));
append(env, strings.concat("WW_OVERWRITE_REAL_L=",
driver(linkers[si])));
let work: str = strings.concat(root, "/command-work-", tags[si]);
let badwork: str = strings.concat(root, "/bad-work-", tags[si]);
let rawwork: str = strings.concat(root, "/raw-work-", tags[si]);
let defaultwork: str = strings.concat(root, "/default-work-", tags[si]);
let symlinkwork: str = strings.concat(root, "/symlink-work-", tags[si]);
let fifowork: str = strings.concat(root, "/fifo-work-", tags[si]);
let dirwork: str = strings.concat(root, "/dir-work-", tags[si]);
let libwork: str = strings.concat(root, "/lib-work-", tags[si]);
let testwork: str = strings.concat(root, "/test-work-", tags[si]);
let testdirwork: str = strings.concat(root, "/test-dir-work-", tags[si]);
mkdirall(work); mkdirall(badwork); mkdirall(rawwork);
mkdirall(defaultwork); mkdirall(symlinkwork); mkdirall(fifowork);
mkdirall(dirwork);
mkdirall(libwork); mkdirall(testwork); mkdirall(testdirwork);
// A nonempty caller file rejects only after the linker ran, and a cold
// persistent generation remains unpublished.
if (os.exists(commandout)) { assert(os.remove(commandout) == 0); };
writefile(commandout, "caller-command\n");
rewritefile(trace, "");
let commandav: []str = [driver(drivers[si]), "build", "-w", work,
"-I", source, "-o", commandout, "cmd.app"];
runcommandenv(root, strings.concat("overwrite-command-", tags[si]),
commandav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, strings.concat("ww: build output \"", commandout,
"\" already exists and is not an object file\n")));
assert(same(readfile(commandout), "caller-command\n"));
assert(permissionmode(commandout) == 384u32);
assert(readfile(trace).len != 0 && directoryisempty(work));
assert(!os.exists(strings.concat(commandout, ".new"))
&& !directoryhasfragment(root, "command.bin.wwtxn."));
if (si == 0) { commanddiag = strings.dup(out.stderr); }
else { assert(same(commanddiag, out.stderr)); };
// Package/import failure keeps load precedence and starts no linker.
rewritefile(trace, "");
let badav: []str = [driver(drivers[si]), "build", "-w", badwork,
"-I", source, "-o", commandout, "cmd.bad"];
runcommandenv(root, strings.concat("overwrite-load-", tags[si]), badav,
env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "cannot find package nowhere.present"));
assert(!has(out.stderr, "already exists and is not an object file"));
assert(readfile(trace).len == 0 && directoryisempty(badwork));
assert(same(readfile(commandout), "caller-command\n"));
if (si == 0) { loaddiag = strings.dup(out.stderr); }
else { assert(same(loaddiag, out.stderr)); };
// Empty reservations and recognized ELF outputs are replaceable. A
// rejected invalidation preserves the prior persistent generation.
rewritefile(commandout, ""); rewritefile(trace, "");
runcommandenv(root, strings.concat("overwrite-empty-", tags[si]),
commandav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let built: str = readfile(commandout);
assert(strings.hasprefix(built, "\x7fELF"));
let unit: str = readfile(strings.concat(work, "/cmd.app.unit.ww"));
if (si == 0) {
commandbytes = strings.dup(built);
unitbytes = strings.dup(unit);
} else {
assert(same(commandbytes, built) && same(unitbytes, unit));
};
rewritefile(appsource, appchanged);
rewritefile(commandout, "caller-after-change\n");
rewritefile(trace, "");
runcommandenv(root, strings.concat("overwrite-invalidated-", tags[si]),
commandav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(readfile(trace).len != 0);
assert(same(readfile(commandout), "caller-after-change\n"));
assert(same(readfile(strings.concat(work, "/cmd.app.unit.ww")), unit));
rewritefile(appsource, appbase);
rewritefile(commandout, built);
runcommandenv(root, strings.concat("overwrite-elf-", tags[si]),
commandav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let runav: []str = [commandout];
runcommand(root, strings.concat("overwrite-command-run-", tags[si]),
runav, (10i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 9);
// Default, raw, symlink-following, and output-directory child routes
// use the same late guard and never strand rollback backups.
if (os.exists(defaultout)) { rewritefile(defaultout, "default-sentinel\n"); }
else { writefile(defaultout, "default-sentinel\n"); };
let defaultav: []str = [driver(drivers[si]), "build", "-w",
defaultwork, "-I", source, "cmd.app"];
runcommandenvdir(root, strings.concat("overwrite-default-", tags[si]),
defaultav, env, invocation,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(defaultout), "default-sentinel\n"));
if (si == 0) { defaultdiag = strings.dup(out.stderr); }
else { assert(same(defaultdiag, out.stderr)); };
if (os.exists(rawout)) { rewritefile(rawout, "raw-sentinel\n"); }
else { writefile(rawout, "raw-sentinel\n"); };
let rawav: []str = [driver(drivers[si]), "build", "-w", rawwork,
"-o", rawout, raw];
runcommandenv(root, strings.concat("overwrite-raw-", tags[si]), rawav,
env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(rawout), "raw-sentinel\n"));
if (si == 0) { rawdiag = strings.dup(out.stderr); }
else { assert(same(rawdiag, out.stderr)); };
let symlinkav: []str = [driver(drivers[si]), "build", "-w",
symlinkwork, "-I", source, "-o", symlinkout, "cmd.app"];
runcommandenv(root, strings.concat("overwrite-symlink-", tags[si]),
symlinkav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(symlinkout), "symlink-sentinel\n"));
if (si == 0) { symlinkdiag = strings.dup(out.stderr); }
else { assert(same(symlinkdiag, out.stderr)); };
if (os.exists(fifoout)) { assert(os.remove(fifoout) == 0); };
let fifoav: []str = ["/usr/bin/mkfifo", fifoout];
runcommand(root, strings.concat("overwrite-mkfifo-", tags[si]), fifoav,
(10i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let fifobuildav: []str = [driver(drivers[si]), "build", "-w",
fifowork, "-I", source, "-o", fifoout, "cmd.app"];
runcommandenv(root, strings.concat("overwrite-fifo-", tags[si]),
fifobuildav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(strings.hasprefix(readfile(fifoout), "\x7fELF"));
let dirav: []str = [driver(drivers[si]), "build", "-w", dirwork,
"-I", source, "-o", strings.concat(builddir, "/"), "cmd.app"];
runcommandenv(root, strings.concat("overwrite-build-dir-", tags[si]),
dirav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "already exists and is a directory"));
assert(same(readfile(strings.concat(buildchild, "/marker")),
"build-directory\n"));
assert(!directoryhasfragment(builddir, ".wwtxn."));
if (si == 0) { builddirdiag = strings.dup(out.stderr); }
else { assert(same(builddirdiag, out.stderr)); };
// Archive and WW-interface outputs each participate in the guard. An
// ordinary repeat accepts both recognized prior outputs; arbitrary
// interface or archive text rejects without changing either member.
if (os.exists(libout)) { assert(os.remove(libout) == 0); };
if (os.exists(strings.concat(libout, ".wwi"))) {
assert(os.remove(strings.concat(libout, ".wwi")) == 0);
};
let libav: []str = [driver(drivers[si]), "build", "-w", libwork,
"-I", source, "-o", libout, "lib.dep"];
runcommandenv(root, strings.concat("overwrite-library-new-", tags[si]),
libav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let archive: str = readfile(libout);
let iface: str = readfile(strings.concat(libout, ".wwi"));
assert(strings.hasprefix(archive, "!<arch>\n"));
assert(strings.hasprefix(iface, "//ww:module "));
runcommandenv(root, strings.concat("overwrite-library-repeat-", tags[si]),
libav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(same(archive, readfile(libout))
&& same(iface, readfile(strings.concat(libout, ".wwi"))));
if (si == 0) {
librarybytes = strings.dup(archive);
interfacebytes = strings.dup(iface);
} else {
assert(same(librarybytes, archive)
&& same(interfacebytes, iface));
};
rewritefile(strings.concat(libout, ".wwi"), "caller-interface\n");
runcommandenv(root, strings.concat("overwrite-library-reject-", tags[si]),
libav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(libout), archive));
assert(same(readfile(strings.concat(libout, ".wwi")),
"caller-interface\n"));
if (si == 0) { librarydiag = strings.dup(out.stderr); }
else { assert(same(librarydiag, out.stderr)); };
rewritefile(libout, "caller-archive\n");
rewritefile(strings.concat(libout, ".wwi"), iface);
runcommandenv(root, strings.concat("overwrite-library-archive-",
tags[si]), libav, env,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(libout), "caller-archive\n"));
assert(same(readfile(strings.concat(libout, ".wwi")), iface));
if (si == 0) { libraryarchivediag = strings.dup(out.stderr); }
else { assert(same(libraryarchivediag, out.stderr)); };
// Compile-only installs check after linking. Running retention executes
// the private test first; a successful run then checks, while a failed
// run never installs or diagnoses the caller destination.
if (os.exists(testbin)) { rewritefile(testbin, "caller-test\n"); }
else { writefile(testbin, "caller-test\n"); };
let testcompileav: []str = [driver(drivers[si]), "test", "-c", "-w",
testwork, "-I", source, "-o", testbin, "pkg.check"];
runcommandenv(root, strings.concat("overwrite-test-c-", tags[si]),
testcompileav, env,
(90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(readfile(testbin), "caller-test\n"));
if (si == 0) { testcompilediag = strings.dup(out.stderr); }
else { assert(same(testcompilediag, out.stderr)); };
rewritefile(testbin, "");
runcommandenv(root, strings.concat("overwrite-test-empty-", tags[si]),
testcompileav, env,
(90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let compiledtest: str = readfile(testbin);
assert(strings.hasprefix(compiledtest, "\x7fELF"));
if (si == 0) { testbytes = strings.dup(compiledtest); }
else { assert(same(testbytes, compiledtest)); };
rewritefile(testbin, "caller-running-test\n");
let testrunav: []str = [driver(drivers[si]), "test", "-w", testwork,
"-I", source, "-o", testbin, "pkg.check"];
runcommandenv(root, strings.concat("overwrite-test-run-", tags[si]),
testrunav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stdout, "checked.guarded_run ... ok\n"));
assert(same(readfile(testbin), "caller-running-test\n"));
if (si == 0) { testrundiag = strings.dup(out.stderr); }
else { assert(same(testrundiag, out.stderr)); };
rewritefile(testbin, compiledtest);
runcommandenv(root, strings.concat("overwrite-test-elf-", tags[si]),
testrunav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "checked.guarded_run ... ok\n"));
let beforefailure: str = readfile(testbin);
let failav: []str = [driver(drivers[si]), "test", "-w", testwork,
"-I", source, "-o", testbin, "pkg.failed"];
runcommandenv(root, strings.concat("overwrite-test-fail-", tags[si]),
failav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stdout, "failed.failed_run ... FAIL (exit 1)\n"));
assert(out.stderr.len == 0 && same(readfile(testbin), beforefailure));
assert(!os.exists(strings.concat(testbin, ".new"))
&& !os.exists(strings.concat(testbin, ".install")));
let testdirav: []str = [driver(drivers[si]), "test", "-c", "-w",
testdirwork, "-I", source, "-o", strings.concat(testdir, "/"),
"pkg.check"];
runcommandenv(root, strings.concat("overwrite-test-dir-", tags[si]),
testdirav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "already exists and is a directory"));
assert(same(readfile(strings.concat(testchild, "/marker")),
"test-directory\n"));
assert(!directoryhasfragment(testdir, ".wwtxn."));
if (si == 0) { testdirdiag = strings.dup(out.stderr); }
else { assert(same(testdirdiag, out.stderr)); };
// Assembly and exact-null routes have no public install action.
let asmout: str = strings.concat(root, "/assembly-", tags[si]);
writefile(asmout, "assembly-sentinel\n");
let asmav: []str = [driver(drivers[si]), "build", "-S", "-w",
work, "-I", source, "-o", asmout, "cmd.app"];
runcommandenv(root, strings.concat("overwrite-asm-", tags[si]), asmav,
env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(same(readfile(asmout), "assembly-sentinel\n"));
let nullav: []str = [driver(drivers[si]), "build", "-I", source,
"-o", "/dev/null", "cmd.app"];
runcommandenv(root, strings.concat("overwrite-null-", tags[si]), nullav,
env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
si += 1;
};
clean(root);
};
// A single directory root used to bypass the package coordinator and treat
// every non-null -o spelling as one file. Exercise the Go output-directory
// branch at that dispatch boundary while the established graph transaction