ww build: discard exact null outputs
This commit is contained in:
@@ -7059,7 +7059,8 @@ do_build(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
char out[PATH_MAX];
|
char out[PATH_MAX];
|
||||||
const char *objstem = NULL;
|
const char *objstem = NULL;
|
||||||
if (outflag[0]) {
|
int discard_output = strcmp(outflag, "/dev/null") == 0;
|
||||||
|
if (outflag[0] && !discard_output) {
|
||||||
/* -o sets both the binary path and the intermediate stem so
|
/* -o sets both the binary path and the intermediate stem so
|
||||||
* artifacts land beside the requested output (T3). */
|
* artifacts land beside the requested output (T3). */
|
||||||
memcpy(out, outflag, strlen(outflag) + 1);
|
memcpy(out, outflag, strlen(outflag) + 1);
|
||||||
@@ -7075,6 +7076,39 @@ do_build(int argc, char **argv)
|
|||||||
basename_no_ext(resolved, out, sizeof out);
|
basename_no_ext(resolved, out, sizeof out);
|
||||||
}
|
}
|
||||||
const char *root_identity = !literal && is_dir ? src : NULL;
|
const char *root_identity = !literal && is_dir ? src : NULL;
|
||||||
|
if (discard_output) {
|
||||||
|
char tmpdir[PATH_MAX], tmp[PATH_MAX];
|
||||||
|
int dn = snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_build_%d", getpid());
|
||||||
|
if (dn < 0 || (size_t)dn >= sizeof tmpdir
|
||||||
|
|| mkdir(tmpdir, 0700) != 0) {
|
||||||
|
fputs("ww: cannot create temporary directory\n", stderr);
|
||||||
|
free(incs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int tn = snprintf(tmp, sizeof tmp, "%s/main", tmpdir);
|
||||||
|
if (tn < 0 || (size_t)tn >= sizeof tmp) {
|
||||||
|
(void)rmdir(tmpdir);
|
||||||
|
free(incs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* Go's null output removes installation, while command linking and
|
||||||
|
* 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);
|
||||||
|
int cleanfail = 0;
|
||||||
|
if (unlink(tmp) != 0 && errno != ENOENT) {
|
||||||
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
|
cleanfail = 1;
|
||||||
|
}
|
||||||
|
if (rmdir(tmpdir) != 0) {
|
||||||
|
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||||
|
cleanfail = 1;
|
||||||
|
}
|
||||||
|
free(incs);
|
||||||
|
if (cleanfail && rc == 0) rc = 1;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
|
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
|
||||||
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
||||||
emit_asm, 1, workdir);
|
emit_asm, 1, workdir);
|
||||||
@@ -7491,6 +7525,7 @@ do_test(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int discard_output = strcmp(outstem, "/dev/null") == 0;
|
||||||
if (pattern != NULL) {
|
if (pattern != NULL) {
|
||||||
struct stat first;
|
struct stat first;
|
||||||
if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) {
|
if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) {
|
||||||
@@ -7580,8 +7615,8 @@ do_test(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
||||||
const char *outp;
|
const char *outp;
|
||||||
int owntmp = !outstem[0] && !workdir[0];
|
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
|
||||||
if (outstem[0]) outp = outstem;
|
if (outstem[0] && !discard_output) outp = outstem;
|
||||||
else if (workdir[0]) {
|
else if (workdir[0]) {
|
||||||
/* The workdir owns the persistent test binary the same
|
/* The workdir owns the persistent test binary the same
|
||||||
* way it owns the package artifacts. */
|
* way it owns the package artifacts. */
|
||||||
@@ -7602,12 +7637,13 @@ do_test(int argc, char **argv)
|
|||||||
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
|
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
|
||||||
outp = tmp;
|
outp = tmp;
|
||||||
}
|
}
|
||||||
/* No-o redirects internal scratch to /tmp rather than beside the
|
/* No retained output keeps scratch private; exact /dev/null follows
|
||||||
* source. An explicit -o names the caller-owned artifact stem. */
|
* Go's no-install test action rather than naming an artifact stem. */
|
||||||
int br = build_one_sep(resolved, is_dir, NULL, outp,
|
int br = build_one_sep(resolved, is_dir, NULL, outp,
|
||||||
outstem[0] ? outstem : tmp, incs, NULL, 0, 0, 1,
|
outstem[0] && !discard_output ? outstem : tmp,
|
||||||
|
incs, NULL, 0, 0, 1,
|
||||||
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
||||||
outstem[0] ? 1 : 0, workdir);
|
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||||
if (br != 0) {
|
if (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
@@ -7651,8 +7687,8 @@ do_test(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
||||||
const char *outp;
|
const char *outp;
|
||||||
int owntmp = !outstem[0] && !workdir[0];
|
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
|
||||||
if (outstem[0]) outp = outstem;
|
if (outstem[0] && !discard_output) outp = outstem;
|
||||||
else if (workdir[0]) {
|
else if (workdir[0]) {
|
||||||
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
|
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
|
||||||
if (tn < 0 || (size_t)tn >= sizeof tmp) {
|
if (tn < 0 || (size_t)tn >= sizeof tmp) {
|
||||||
@@ -7671,10 +7707,11 @@ do_test(int argc, char **argv)
|
|||||||
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
|
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
|
||||||
outp = tmp;
|
outp = tmp;
|
||||||
}
|
}
|
||||||
/* See module-mode note: no-o scratch is redirected to /tmp. */
|
/* See module-mode note: an unretained output uses private scratch. */
|
||||||
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp,
|
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,
|
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
|
||||||
outstem[0] ? 1 : 0, workdir);
|
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||||
if (br != 0) {
|
if (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
|
|||||||
@@ -6405,6 +6405,134 @@ injected late-link request rollback over old files and modes; occupied-stage
|
|||||||
rejection; simultaneous builds with different umasks; diagnostic parity;
|
rejection; simultaneous builds with different umasks; diagnostic parity;
|
||||||
artifact and binary byte identity; and absence of `.new` or umask-probe residue.
|
artifact and binary byte identity; and absence of `.new` or umask-probe residue.
|
||||||
|
|
||||||
|
### 11.29 Implemented exact null-output discard for builds
|
||||||
|
|
||||||
|
Exact `ww build -o /dev/null` now removes output installation while preserving
|
||||||
|
the ordinary load and action graph. It is not a request to create an archive,
|
||||||
|
executable, interface, or scratch tree at the null-device pathname.
|
||||||
|
|
||||||
|
#### Pinned Go evidence and fact classification
|
||||||
|
|
||||||
|
The sole authority is official Go 1.26.5 at commit
|
||||||
|
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||||
|
|
||||||
|
- `runBuild` loads packages and reports load errors first, then recognizes the
|
||||||
|
null output and clears `BuildO` before any output-directory, multi-package,
|
||||||
|
or install-action branch. It finally constructs ordinary `ModeBuild` actions
|
||||||
|
for every selected package
|
||||||
|
([`cmd/go/internal/work/build.go`, lines 459–558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L558)).
|
||||||
|
- `AutoAction` maps a main package in that ordinary mode to a link action and a
|
||||||
|
non-main package to a compile action
|
||||||
|
([`cmd/go/internal/work/action.go`, lines 450–455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L450-L455)).
|
||||||
|
- On the pinned Unix target, `IsNull` accepts exact `os.DevNull`, which is
|
||||||
|
`/dev/null`; its only additional spelling rule is the Windows-only
|
||||||
|
case-insensitive `NUL` exception
|
||||||
|
([`cmd/go/internal/base/path.go`, lines 81–92](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/path.go#L81-L92)).
|
||||||
|
- The test builder treats a nonempty `-o` as a binary-retention request, exempts
|
||||||
|
the null device from multi-package output rejection, and makes a null target
|
||||||
|
use the private build action instead of an install action
|
||||||
|
([`cmd/go/internal/test/test.go`, lines 631–646, 771–804, and 1259–1294](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1259-L1294)).
|
||||||
|
- Official `devnull.txt` requires `go test -c -o $devnull` and a non-main
|
||||||
|
package `go build -o $devnull` to succeed without changing the device
|
||||||
|
([lines 3–25](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/devnull.txt#L3-L25)).
|
||||||
|
`build_dash_o_dev_null.txt` requires a command-line source build to succeed
|
||||||
|
without its default executable
|
||||||
|
([lines 1–12](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_dash_o_dev_null.txt#L1-L12)).
|
||||||
|
`build_cache_link.txt` requires a cold null build to compile and link and an
|
||||||
|
unchanged warm null build to skip compilation but link again
|
||||||
|
([lines 4–22](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_cache_link.txt#L4-L22)).
|
||||||
|
`TestRemoveDevNull` requires cleanup never to remove the device
|
||||||
|
([`cmd/go/internal/work/build_test.go`, lines 22–35](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build_test.go#L22-L35)).
|
||||||
|
|
||||||
|
The load-before-output order, exact spelling, absence of installation, normal
|
||||||
|
command link, normal non-command compile, cold/warm link behavior, raw-source
|
||||||
|
behavior, and device preservation are directly implemented or asserted by the
|
||||||
|
pinned sources above. Acceptance of multiple mixed package roots and an empty
|
||||||
|
matched set is derived from clearing `BuildO` before the output-cardinality
|
||||||
|
branches and then iterating the ordinary package action list. That conclusion
|
||||||
|
applies to WW's manifest-free local package set without importing Go's module,
|
||||||
|
cache, or distribution model. No installed host Go behavior is authority.
|
||||||
|
|
||||||
|
#### Direct pre-fix measurements
|
||||||
|
|
||||||
|
Fresh public-driver probes measured both stages before production edits:
|
||||||
|
|
||||||
|
- raw source, one command directory, and one non-main library directory each
|
||||||
|
failed with exit 1 and `ww: cannot create scratch /dev/null.sepwork`;
|
||||||
|
- a two-command-plus-library request failed with exit 2 and
|
||||||
|
`wwtest package: cannot use -o with multiple packages`;
|
||||||
|
- a persistent command request reached the linker but failed through
|
||||||
|
`/dev/null.new`: Cstage reported `w6l: cannot open /dev/null.new`, WWstage
|
||||||
|
reported `w6l: cannot open output`, and both left the caller workdir empty;
|
||||||
|
- raw `ww test -c -o /dev/null FILE` and its running form failed at the same
|
||||||
|
adjacent-scratch acquisition, while directory `test -c` and running test
|
||||||
|
requests already built privately, discarded the retained copy, and preserved
|
||||||
|
their compile-only versus run distinction;
|
||||||
|
- a blank import of a missing package produced the same positioned
|
||||||
|
`cannot find package missing.pkg` diagnostic in Cstage and WWstage before
|
||||||
|
output setup; and
|
||||||
|
- `/dev/null` remained the same character device, mode, device/inode, and size
|
||||||
|
throughout the failed probes.
|
||||||
|
|
||||||
|
Those are directly measured WW facts. The externally observable gap was thus
|
||||||
|
the build/raw-driver interpretation of exact null as an artifact stem, plus the
|
||||||
|
coordinator's ordinary multi-output rejection, rather than a loader, compiler,
|
||||||
|
linker, or device-write defect.
|
||||||
|
|
||||||
|
#### Ownership, actions, publication, and identity
|
||||||
|
|
||||||
|
`internal/wwpackage.packagecommand` owns the shared package-request output
|
||||||
|
policy. After argument parsing and before output planning it records exact Unix
|
||||||
|
null discard. Loading, source classification, package/import validation,
|
||||||
|
canonical grouping, graph construction, and diagnostic precedence remain
|
||||||
|
unchanged. The coordinator suppresses default names, output-directory setup,
|
||||||
|
ordinary non-directory fan-out rejection, caller publication, and the
|
||||||
|
visibility-only `-S` workdir requirement, then gives every selected group a
|
||||||
|
request-private plan product. Commands still link; libraries still compile and
|
||||||
|
archive; mixed and repeated roots still use their canonical graph/action
|
||||||
|
deduplication. A recursive pattern matching no package emits its ordinary
|
||||||
|
warning and has no output-cardinality error.
|
||||||
|
|
||||||
|
The direct Cstage and WWstage drivers own raw or single-directory requests that
|
||||||
|
do not enter the coordinator. For exact null they allocate a private command
|
||||||
|
product, run the unchanged separate-compilation pipeline, and remove that
|
||||||
|
product and its scratch on every return. Their raw-test paths use the existing
|
||||||
|
unretained private test binary rather than setting `/dev/null` as output and
|
||||||
|
object stem. `-c` still suppresses execution; a running request still reports
|
||||||
|
ordinary pass, assertion, signal, and harness outcomes. Directory tests retain
|
||||||
|
their previously established private-runnable/null-publication behavior.
|
||||||
|
|
||||||
|
There is no caller-visible stage or destination to commit, occupy, replace, or
|
||||||
|
chmod. Producer failure or signal removes private plan state; persistent action
|
||||||
|
rollback preserves every prior unit, interface, assembly, object, archive,
|
||||||
|
tool record, and stamp. Successful persistent requests commit semantic actions
|
||||||
|
normally, unchanged actions are warm-reused, source changes invalidate their
|
||||||
|
owners, and command links still run for each request. Separate simultaneous
|
||||||
|
Cstage and WWstage requests own disjoint private products and workdirs. Exact
|
||||||
|
lookalikes remain normal caller-owned outputs and retain their existing
|
||||||
|
fan-out, `.sepwork`, permission, occupied-stage, transaction, and diagnostic
|
||||||
|
rules.
|
||||||
|
|
||||||
|
Output disposition remains request metadata. Dotted package identity, declared
|
||||||
|
name, physical source directory, import binding, graph edges, action/storage
|
||||||
|
keys, compiler/assembler/linker semantic argv, symbols, `.wwi`, unit and
|
||||||
|
artifact bytes, and persistent invalidation are unchanged. No persisted byte
|
||||||
|
contract changed: build workdir format remains `18`, test workdir format remains
|
||||||
|
`19`, and semantic storage remains `3`.
|
||||||
|
|
||||||
|
The WW-native owner `exact_null_output_discards_build_products` covers Cstage
|
||||||
|
and WWstage command, library, mixed-root, raw-build, assembly-only, and raw-test
|
||||||
|
routes; load/import precedence; empty-pattern warning; exact lookalike
|
||||||
|
rejection/publication; cold, warm, and invalidated persistence; normal link
|
||||||
|
actions and captured runnable bytes; injected linker failure and signal;
|
||||||
|
persistent rollback; concurrent stage isolation; device preservation; private
|
||||||
|
path and `.new` cleanup; semantic-artifact and captured-binary byte identity;
|
||||||
|
and diagnostic/output parity. Existing directory-test owners cover test-product
|
||||||
|
parallelism, timeout, child-setup failure, retained-output transactions, and
|
||||||
|
runtime cwd/environment/stdin. Build runtime behavior is inapplicable, and
|
||||||
|
caller-output rollback, occupied caller stages, and output permissions are
|
||||||
|
inapplicable to the exact discard branch because it creates no public inode.
|
||||||
|
|
||||||
## 12. Candidate architectures and hard-gate decision
|
## 12. Candidate architectures and hard-gate decision
|
||||||
|
|
||||||
Five candidates were developed as coherent systems, not as feature bins.
|
Five candidates were developed as coherent systems, not as feature bins.
|
||||||
|
|||||||
19
docs/spec.md
19
docs/spec.md
@@ -319,6 +319,19 @@ ImportPath = ident { "." ident } .
|
|||||||
build therefore reuses unchanged semantic actions while refreshing the
|
build therefore reuses unchanged semantic actions while refreshing the
|
||||||
caller-visible output with the current invocation's mode. Assembly-only
|
caller-visible output with the current invocation's mode. Assembly-only
|
||||||
builds publish no executable or archive.
|
builds publish no executable or archive.
|
||||||
|
- On the Unix target, exact `-o /dev/null` is a build-output discard request,
|
||||||
|
not an ordinary output filename. Package loading, import validation, graph
|
||||||
|
construction, compilation, assembly, archiving, command linking, failure,
|
||||||
|
and persistent invalidation proceed normally; only installation of a
|
||||||
|
caller-visible command, archive, interface, default basename, or adjacent
|
||||||
|
scratch tree is omitted. One such request may select any number of command
|
||||||
|
and non-command packages, and an empty recursive match is successful after
|
||||||
|
its normal warning. `-S` likewise needs no caller workdir merely to retain
|
||||||
|
discarded assembly. Raw-file and directory roots use the same rule. A
|
||||||
|
non-exact spelling is an ordinary output and keeps all established fan-out,
|
||||||
|
rejection, permission, scratch, and publication behavior. Output discard is
|
||||||
|
request metadata; it does not change package, import, graph, action,
|
||||||
|
artifact, symbol, `.wwi`, or persistence identity.
|
||||||
- Only names marked `export` (§5) are visible across module boundaries.
|
- Only names marked `export` (§5) are visible across module boundaries.
|
||||||
|
|
||||||
Import paths remain unquoted and dotted. Grouped imports, quoted import paths,
|
Import paths remain unquoted and dotted. Grouped imports, quoted import paths,
|
||||||
@@ -662,7 +675,11 @@ directory. An output ending in `/` or naming an existing directory receives
|
|||||||
that basename and may have missing parent directories created. One
|
that basename and may have missing parent directories created. One
|
||||||
non-directory output may name only one package. Multiple packages whose
|
non-directory output may name only one package. Multiple packages whose
|
||||||
visible import leaves would produce the same test-binary name reject before
|
visible import leaves would produce the same test-binary name reject before
|
||||||
tools or output creation; exact `/dev/null` is the discard exception. Declared
|
tools or output creation; exact `/dev/null` is the discard exception. It keeps
|
||||||
|
the ordinary private link and, unless `-c` is present, the ordinary execution,
|
||||||
|
but installs no retained copy. The raw single-file compatibility route uses
|
||||||
|
the same private-output rule and never treats `/dev/null` as an explicit
|
||||||
|
artifact or adjacent-scratch stem. Declared
|
||||||
package names, test variants, source filenames, physical directories, output
|
package names, test variants, source filenames, physical directories, output
|
||||||
paths, and retained binary names remain presentation or loader metadata and do
|
paths, and retained binary names remain presentation or loader metadata and do
|
||||||
not become canonical package or action identity.
|
not become canonical package or action identity.
|
||||||
|
|||||||
@@ -227,7 +227,10 @@ naming an existing directory receives one such name per selected package and
|
|||||||
missing parents are created. A single non-directory output is legal for one
|
missing parents are created. A single non-directory output is legal for one
|
||||||
package only. Multi-package non-directory output and duplicate visible binary
|
package only. Multi-package non-directory output and duplicate visible binary
|
||||||
names reject before tools; exact `/dev/null` discards every copy and permits
|
names reject before tools; exact `/dev/null` discards every copy and permits
|
||||||
duplicate names. A package with no selected test source validates production
|
duplicate names. The private test link still runs unless `-c` suppresses it;
|
||||||
|
the raw single-file compatibility route also links into driver-owned private
|
||||||
|
storage instead of using `/dev/null` as an artifact or `.sepwork` stem. A
|
||||||
|
package with no selected test source validates production
|
||||||
but publishes nothing and does not create an otherwise unneeded output
|
but publishes nothing and does not create an otherwise unneeded output
|
||||||
directory. Successful `-c` output is silent apart from no-test reporting.
|
directory. Successful `-c` output is silent apart from no-test reporting.
|
||||||
|
|
||||||
|
|||||||
@@ -1844,6 +1844,8 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
if (roots.len == 0) { append(roots, "."); };
|
if (roots.len == 0) { append(roots, "."); };
|
||||||
pkgsortstrings(roots);
|
pkgsortstrings(roots);
|
||||||
|
let buildnull: bool = buildonly && explicitout
|
||||||
|
&& strings.compare(outname, "/dev/null") == 0;
|
||||||
if (compileonly && !buildonly
|
if (compileonly && !buildonly
|
||||||
&& (list || filters.len != 0 || timeoutarg.len != 0)) {
|
&& (list || filters.len != 0 || timeoutarg.len != 0)) {
|
||||||
pkgusage();
|
pkgusage();
|
||||||
@@ -1946,11 +1948,11 @@ export fn packagecommand(args: []str) int = {
|
|||||||
ds.paths = pkgdedup(ds.paths);
|
ds.paths = pkgdedup(ds.paths);
|
||||||
if (ds.paths.len == 0) {
|
if (ds.paths.len == 0) {
|
||||||
if (buildonly) {
|
if (buildonly) {
|
||||||
if (explicitout && pkgoutputdir(outname)) {
|
if (explicitout && !buildnull && pkgoutputdir(outname)) {
|
||||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
if (explicitout) {
|
if (explicitout && !buildnull) {
|
||||||
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -2177,11 +2179,11 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
if (groups.len == 0) {
|
if (groups.len == 0) {
|
||||||
if (buildonly) {
|
if (buildonly) {
|
||||||
if (explicitout && pkgoutputdir(outname)) {
|
if (explicitout && !buildnull && pkgoutputdir(outname)) {
|
||||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
if (explicitout) {
|
if (explicitout && !buildnull) {
|
||||||
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -2192,7 +2194,7 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
pkgsortgroups(groups);
|
pkgsortgroups(groups);
|
||||||
let defaultout: bool = false;
|
let defaultout: bool = false;
|
||||||
if (buildonly && outname.len == 0 && folders.len == 1
|
if (buildonly && !buildnull && outname.len == 0 && folders.len == 1
|
||||||
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
|
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
|
||||||
outname = pkgbase(groups[0].dir);
|
outname = pkgbase(groups[0].dir);
|
||||||
defaultout = true;
|
defaultout = true;
|
||||||
@@ -2203,7 +2205,8 @@ export fn packagecommand(args: []str) int = {
|
|||||||
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
|
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
let outputdir: bool = buildonly && explicitout && pkgoutputdir(outname);
|
let outputdir: bool = buildonly && explicitout && !buildnull
|
||||||
|
&& pkgoutputdir(outname);
|
||||||
if (outputdir) {
|
if (outputdir) {
|
||||||
let mainpackages: i32 = 0;
|
let mainpackages: i32 = 0;
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -2330,7 +2333,8 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
// A non-directory caller-owned build name cannot fan out. A directory
|
// A non-directory caller-owned build name cannot fan out. A directory
|
||||||
// build output publishes each selected command under its directory leaf.
|
// build output publishes each selected command under its directory leaf.
|
||||||
if (buildonly && outname.len != 0 && !outputdir && groups.len > 1) {
|
if (buildonly && !buildnull && outname.len != 0
|
||||||
|
&& !outputdir && groups.len > 1) {
|
||||||
pkgputln(os.STDERR_FILENO,
|
pkgputln(os.STDERR_FILENO,
|
||||||
"wwtest package: cannot use -o with multiple packages");
|
"wwtest package: cannot use -o with multiple packages");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -2338,7 +2342,7 @@ export fn packagecommand(args: []str) int = {
|
|||||||
// Recursive/multi-root -S needs a caller-owned artifact tree. Without -w
|
// Recursive/multi-root -S needs a caller-owned artifact tree. Without -w
|
||||||
// every assembly file would otherwise live only in the coordinator's
|
// every assembly file would otherwise live only in the coordinator's
|
||||||
// temporary plan and disappear on successful return.
|
// temporary plan and disappear on successful return.
|
||||||
if (buildonly && emitasm && workroot.len == 0) {
|
if (buildonly && emitasm && workroot.len == 0 && !buildnull) {
|
||||||
pkgputln(os.STDERR_FILENO,
|
pkgputln(os.STDERR_FILENO,
|
||||||
"wwtest package: recursive -S needs -w");
|
"wwtest package: recursive -S needs -w");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -2360,7 +2364,7 @@ export fn packagecommand(args: []str) int = {
|
|||||||
plan.end = groups.len;
|
plan.end = groups.len;
|
||||||
plan.state = PKGQUEUED;
|
plan.state = PKGQUEUED;
|
||||||
plan.buildonly = buildonly;
|
plan.buildonly = buildonly;
|
||||||
plan.publish = buildonly && explicitout && outname.len != 0
|
plan.publish = buildonly && explicitout && !buildnull && outname.len != 0
|
||||||
&& groups.len == 1
|
&& groups.len == 1
|
||||||
&& strings.compare(groups[0].pkg, "main") != 0;
|
&& strings.compare(groups[0].pkg, "main") != 0;
|
||||||
plan.emitasm = emitasm;
|
plan.emitasm = emitasm;
|
||||||
@@ -2380,11 +2384,13 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let tmproot: str = temp.dir();
|
let tmproot: str = temp.dir();
|
||||||
|
let planout: str = outname;
|
||||||
|
if (buildnull) { planout = ""; };
|
||||||
let failed: i32 = 0;
|
let failed: i32 = 0;
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < plans.len) {
|
for (i < plans.len) {
|
||||||
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
|
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
|
||||||
compileonly, buildonly, outname, outputdir, createdir,
|
compileonly, buildonly, planout, outputdir, createdir,
|
||||||
workroot)) {
|
workroot)) {
|
||||||
if (!pkgremoveall(tmproot)) {
|
if (!pkgremoveall(tmproot)) {
|
||||||
pkgput(os.STDERR_FILENO,
|
pkgput(os.STDERR_FILENO,
|
||||||
|
|||||||
@@ -8502,7 +8502,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
};
|
};
|
||||||
let out: *u8 = nil;
|
let out: *u8 = nil;
|
||||||
let objstem: *u8 = nil;
|
let objstem: *u8 = nil;
|
||||||
if (outflag != nil && outflag[0u64] != 0u8) {
|
let discardoutput: bool = outflag != nil
|
||||||
|
&& cstreqlit(outflag, "/dev/null");
|
||||||
|
if (outflag != nil && outflag[0u64] != 0u8 && !discardoutput) {
|
||||||
// -o sets both the binary path and the intermediate stem so
|
// -o sets both the binary path and the intermediate stem so
|
||||||
// artifacts land beside the requested output (T3).
|
// artifacts land beside the requested output (T3).
|
||||||
out = outflag;
|
out = outflag;
|
||||||
@@ -8531,6 +8533,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
let rootidentity: *u8 = nil;
|
let rootidentity: *u8 = nil;
|
||||||
if (!requestedliteral && isdir != 0) { rootidentity = src; };
|
if (!requestedliteral && isdir != 0) { rootidentity = src; };
|
||||||
|
if (discardoutput) {
|
||||||
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
|
tmp.len = os.PATH_MAX;
|
||||||
|
makedrivertmp(tmp.ptr, "ww_build_");
|
||||||
|
if (os.mkdir(pathstr(tmp.ptr), 448i32) != 0) {
|
||||||
|
cerr("ww: cannot create temporary directory\n");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
let outp: *u8 = joinpathlit(tmp.ptr, "main");
|
||||||
|
// Go's null output removes installation, while command linking and
|
||||||
|
// library compilation still need request-private product paths.
|
||||||
|
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
|
outp, outp, incs.ptr, &lf,
|
||||||
|
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||||
|
emitasm, 0i32, workdir);
|
||||||
|
let cleanbad: bool = false;
|
||||||
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
|
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||||
|
cerr("ww: cannot remove temporary output\n");
|
||||||
|
cleanbad = true;
|
||||||
|
};
|
||||||
|
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
|
||||||
|
cerr("ww: cannot remove temporary directory\n");
|
||||||
|
cleanbad = true;
|
||||||
|
};
|
||||||
|
if (cleanbad && rc == 0) { rc = 1; };
|
||||||
|
return rc;
|
||||||
|
};
|
||||||
let publishpackage: i32 = 0;
|
let publishpackage: i32 = 0;
|
||||||
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
|
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
|
||||||
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
@@ -8774,14 +8804,16 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
|||||||
emitasm: i32, outstem: *u8, workdir: *u8, pattern: *u8) i32 = {
|
emitasm: i32, outstem: *u8, workdir: *u8, pattern: *u8) i32 = {
|
||||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
tmp.len = os.PATH_MAX;
|
||||||
// -o redirects the binary + its caller-owned sepwork intermediates
|
// A retained -o redirects the binary and sepwork intermediates to its stem;
|
||||||
// (objstem, T3) to <stem>; without -o both are driver-owned /tmp paths.
|
// exact /dev/null needs the no-install path used by Go's test builder.
|
||||||
let outp: *u8 = nil;
|
let outp: *u8 = nil;
|
||||||
let objstem: *u8 = nil;
|
let objstem: *u8 = nil;
|
||||||
// owntmp: the driver owns (and must clean) the /tmp workspace; with
|
// A retained output or -w has caller-owned storage; every other binary is
|
||||||
// -o or -w the binary lands in a caller-owned location instead.
|
// request-private and must be removed here.
|
||||||
let owntmp: bool = false;
|
let owntmp: bool = false;
|
||||||
if (outstem != nil) {
|
let retainout: bool = outstem != nil
|
||||||
|
&& !cstreqlit(outstem, "/dev/null");
|
||||||
|
if (retainout) {
|
||||||
outp = outstem;
|
outp = outstem;
|
||||||
objstem = outstem;
|
objstem = outstem;
|
||||||
} else { if (workdir != nil) {
|
} else { if (workdir != nil) {
|
||||||
@@ -8806,7 +8838,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
|||||||
lf.libs = nil;
|
lf.libs = nil;
|
||||||
lf.nlibs = 0;
|
lf.nlibs = 0;
|
||||||
let keep: i32 = 0;
|
let keep: i32 = 0;
|
||||||
if (outstem != nil) { keep = 1; };
|
if (retainout) { keep = 1; };
|
||||||
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||||
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
||||||
emitasm, keep, workdir);
|
emitasm, keep, workdir);
|
||||||
|
|||||||
@@ -542,6 +542,16 @@ fn directoryisempty(path: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn discardedlinkcleaned(marker: str) void = {
|
||||||
|
let output: str = readfile(marker);
|
||||||
|
assert(output.len != 0);
|
||||||
|
assert(!has(output, "/dev/null"));
|
||||||
|
let slash: i32 = output.len - 1;
|
||||||
|
for (slash >= 0 && output[slash] != '/') { slash -= 1; };
|
||||||
|
assert(slash > 0);
|
||||||
|
assert(!os.exists(strings.sub(output, 0, slash)));
|
||||||
|
};
|
||||||
|
|
||||||
fn byteshasat(s: str, off: i32, want: str) bool = {
|
fn byteshasat(s: str, off: i32, want: str) bool = {
|
||||||
if (off < 0 || off + want.len > s.len) { return false; };
|
if (off < 0 || off + want.len > s.len) { return false; };
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -12990,3 +13000,490 @@ fn runtimepath(relative: str) str = {
|
|||||||
assert(!directoryhasnew(parallelwwwork));
|
assert(!directoryhasnew(parallelwwwork));
|
||||||
clean(root);
|
clean(root);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@test fn exact_null_output_discards_build_products() void = {
|
||||||
|
let root: str = fresh();
|
||||||
|
let source: str = strings.concat(root, "/source");
|
||||||
|
let base: str = strings.concat(source, "/base");
|
||||||
|
let alpha: str = strings.concat(source, "/alpha");
|
||||||
|
let beta: str = strings.concat(source, "/beta");
|
||||||
|
let library: str = strings.concat(source, "/library");
|
||||||
|
let bad: str = strings.concat(source, "/bad");
|
||||||
|
let absent: str = strings.concat(source, "/absent");
|
||||||
|
mkdirall(base); mkdirall(alpha); mkdirall(beta);
|
||||||
|
mkdirall(library); mkdirall(bad); mkdirall(absent);
|
||||||
|
writefile(strings.concat(base, "/base.ww"), strings.concat(
|
||||||
|
"package base;\n",
|
||||||
|
"export fn value() i32 = { return 4; };\n"));
|
||||||
|
let alphafile: str = strings.concat(alpha, "/main.ww");
|
||||||
|
let alphaoriginal: str = strings.concat(
|
||||||
|
"package main;\nimport base;\n",
|
||||||
|
"fn main() i32 = { return base.value(); };\n");
|
||||||
|
let alphachanged: str = strings.concat(
|
||||||
|
"package main;\nimport base;\n",
|
||||||
|
"fn main() i32 = { return base.value() + 1; };\n");
|
||||||
|
let alphafailing: str = strings.concat(
|
||||||
|
"package main;\nimport base;\n",
|
||||||
|
"fn main() i32 = { return base.value() + 2; };\n");
|
||||||
|
writefile(alphafile, alphaoriginal);
|
||||||
|
writefile(strings.concat(beta, "/main.ww"),
|
||||||
|
"package main;\nfn main() i32 = { return 6; };\n");
|
||||||
|
writefile(strings.concat(library, "/library.ww"), strings.concat(
|
||||||
|
"package odd;\n",
|
||||||
|
"export fn value() i32 = { return 7; };\n"));
|
||||||
|
writefile(strings.concat(bad, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport _ missing.pkg;\n",
|
||||||
|
"fn main() i32 = { return 0; };\n"));
|
||||||
|
let raw: str = strings.concat(source, "/raw.ww");
|
||||||
|
writefile(raw, "package main;\nfn main() i32 = { return 9; };\n");
|
||||||
|
|
||||||
|
let compilerwrapper: str = strings.concat(root, "/null-w6c.sh");
|
||||||
|
let assemblerwrapper: str = strings.concat(root, "/null-w6a.sh");
|
||||||
|
let linkerwrapper: str = strings.concat(root, "/null-w6l.sh");
|
||||||
|
writeexecutable(compilerwrapper, strings.concat(
|
||||||
|
"#!/bin/sh\n",
|
||||||
|
"printf '%s\\n' \"$*\" >> \"$WW_NULL_CTRACE\"\n",
|
||||||
|
"exec \"$WW_NULL_REAL_C\" \"$@\"\n"));
|
||||||
|
writeexecutable(assemblerwrapper, strings.concat(
|
||||||
|
"#!/bin/sh\n",
|
||||||
|
"printf '%s\\n' \"$*\" >> \"$WW_NULL_ATRACE\"\n",
|
||||||
|
"exec \"$WW_NULL_REAL_A\" \"$@\"\n"));
|
||||||
|
writeexecutable(linkerwrapper, strings.concat(
|
||||||
|
"#!/bin/sh\n",
|
||||||
|
"printf '%s\\n' \"$*\" >> \"$WW_NULL_LTRACE\"\n",
|
||||||
|
"out= take=\n",
|
||||||
|
"for arg do\n",
|
||||||
|
" if test \"$take\" = yes; then out=$arg; take=; continue; fi\n",
|
||||||
|
" if test \"$arg\" = -o; then take=yes; fi\n",
|
||||||
|
"done\n",
|
||||||
|
"printf '%s' \"$out\" > \"$WW_NULL_LAST_OUTPUT\"\n",
|
||||||
|
"case \"$WW_NULL_LINK_MODE\" in\n",
|
||||||
|
" fail) printf 'injected null-output linker failure\\n' >&2; exit 97;;\n",
|
||||||
|
" signal) kill -TERM \"$$\";;\n",
|
||||||
|
"esac\n",
|
||||||
|
"\"$WW_NULL_REAL_L\" \"$@\"\n",
|
||||||
|
"rc=$?\n",
|
||||||
|
"if test \"$rc\" -eq 0 && test -n \"$WW_NULL_CAPTURE\"; then\n",
|
||||||
|
" cp \"$out\" \"$WW_NULL_CAPTURE\" || exit 98\n",
|
||||||
|
"fi\n",
|
||||||
|
"exit \"$rc\"\n"));
|
||||||
|
|
||||||
|
let nullbefore: os.filestat;
|
||||||
|
match (os.stat(&nullbefore, "/dev/null")) {
|
||||||
|
case void => void;
|
||||||
|
case let e: os.oserror => abort("cannot stat /dev/null");
|
||||||
|
};
|
||||||
|
assert(((nullbefore.mode as u32) & 61440u32)
|
||||||
|
== (os.mode.CHR as u32));
|
||||||
|
let stages: []str = ["ww", "ww_ww"];
|
||||||
|
let compilers: []str = ["w6c", "w6c_ww"];
|
||||||
|
let assemblers: []str = ["w6a", "w6a_ww"];
|
||||||
|
let linkers: []str = ["w6l", "w6l_ww"];
|
||||||
|
let tags: []str = ["c", "ww"];
|
||||||
|
let coldref: str = "";
|
||||||
|
let changedref: str = "";
|
||||||
|
let rawref: str = "";
|
||||||
|
let lookalikeref: str = "";
|
||||||
|
let faildiagref: str = "";
|
||||||
|
let signaldiagref: str = "";
|
||||||
|
let missingdiagref: str = "";
|
||||||
|
let emptydiagref: str = "";
|
||||||
|
let rejectdiagref: str = "";
|
||||||
|
let testcompileref: str = "";
|
||||||
|
let testoutrefs: []str = ["", "", ""];
|
||||||
|
let testerrrefs: []str = ["", "", ""];
|
||||||
|
let baseenv: []str = os.getenvs();
|
||||||
|
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"];
|
||||||
|
let out: commandout;
|
||||||
|
let si: i32 = 0;
|
||||||
|
for (si < stages.len) {
|
||||||
|
rewritefile(alphafile, alphaoriginal);
|
||||||
|
let work: str = strings.concat(root, "/work-", tags[si]);
|
||||||
|
mkdirall(work);
|
||||||
|
let ctrace: str = strings.concat(root, "/", tags[si], "-c.trace");
|
||||||
|
let atrace: str = strings.concat(root, "/", tags[si], "-a.trace");
|
||||||
|
let ltrace: str = strings.concat(root, "/", tags[si], "-l.trace");
|
||||||
|
let lastout: str = strings.concat(root, "/", tags[si], "-last.out");
|
||||||
|
let capture: str = strings.concat(root, "/", tags[si], "-capture");
|
||||||
|
writefile(ctrace, ""); writefile(atrace, "");
|
||||||
|
writefile(ltrace, ""); writefile(lastout, "");
|
||||||
|
let env: []str = alloc([], (baseenv.len + 12): u64)!;
|
||||||
|
let ei: i32 = 0;
|
||||||
|
for (ei < baseenv.len) {
|
||||||
|
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_NULL_")) {
|
||||||
|
append(env, baseenv[ei]);
|
||||||
|
};
|
||||||
|
ei += 1;
|
||||||
|
};
|
||||||
|
append(env, strings.concat("WW_W6C=", compilerwrapper));
|
||||||
|
append(env, strings.concat("WW_W6A=", assemblerwrapper));
|
||||||
|
append(env, strings.concat("WW_W6L=", linkerwrapper));
|
||||||
|
append(env, strings.concat("WW_NULL_CTRACE=", ctrace));
|
||||||
|
append(env, strings.concat("WW_NULL_ATRACE=", atrace));
|
||||||
|
append(env, strings.concat("WW_NULL_LTRACE=", ltrace));
|
||||||
|
append(env, strings.concat("WW_NULL_REAL_C=",
|
||||||
|
driver(compilers[si])));
|
||||||
|
append(env, strings.concat("WW_NULL_REAL_A=",
|
||||||
|
driver(assemblers[si])));
|
||||||
|
append(env, strings.concat("WW_NULL_REAL_L=",
|
||||||
|
driver(linkers[si])));
|
||||||
|
let linkmodeindex: i32 = env.len;
|
||||||
|
append(env, "WW_NULL_LINK_MODE=");
|
||||||
|
let captureindex: i32 = env.len;
|
||||||
|
append(env, strings.concat("WW_NULL_CAPTURE=", capture));
|
||||||
|
append(env, strings.concat("WW_NULL_LAST_OUTPUT=", lastout));
|
||||||
|
|
||||||
|
// Exact null is no-install policy. Loading, dependency production,
|
||||||
|
// assembly, archiving, and the command link still occur normally.
|
||||||
|
let coldav: []str = [driver(stages[si]), "build", "-w", work,
|
||||||
|
"-I", source, "-o", "/dev/null", alpha];
|
||||||
|
runcommandenvdir(root, strings.concat("null-cold-", tags[si]), coldav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(occurrences(readfile(ctrace), "\n") == 2);
|
||||||
|
assert(occurrences(readfile(atrace), "\n") == 3);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
assert(has(readfile(ctrace), "/base.unit.new"));
|
||||||
|
assert(has(readfile(ctrace), "/alpha.unit.new"));
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
let coldbytes: str = readfile(capture);
|
||||||
|
if (si == 0) { coldref = strings.dup(coldbytes); }
|
||||||
|
else { assert(same(coldref, coldbytes)); };
|
||||||
|
let runav: []str = [capture];
|
||||||
|
runcommand(root, strings.concat("null-cold-run-", tags[si]), runav,
|
||||||
|
time.second, &out);
|
||||||
|
expectexit(&out, 4);
|
||||||
|
let originalunit: str = readfile(strings.concat(work,
|
||||||
|
"/alpha.unit.ww"));
|
||||||
|
|
||||||
|
// Unchanged actions are warm-reused, but the command links again into
|
||||||
|
// fresh private storage because null output is not a result cache.
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
rewritefile(capture, "");
|
||||||
|
runcommandenvdir(root, strings.concat("null-warm-", tags[si]), coldav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
assert(same(coldbytes, readfile(capture)));
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
|
||||||
|
// Source change invalidates the owning action and linked bytes while
|
||||||
|
// preserving the same action names and persistent representation.
|
||||||
|
rewritefile(alphafile, alphachanged);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
rewritefile(capture, "");
|
||||||
|
runcommandenvdir(root, strings.concat("null-change-", tags[si]), coldav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(occurrences(readfile(ctrace), "\n") == 1);
|
||||||
|
assert(occurrences(readfile(atrace), "\n") == 1);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
assert(!same(originalunit, readfile(strings.concat(work,
|
||||||
|
"/alpha.unit.ww"))));
|
||||||
|
let changedbytes: str = readfile(capture);
|
||||||
|
assert(!same(coldbytes, changedbytes));
|
||||||
|
if (si == 0) { changedref = strings.dup(changedbytes); }
|
||||||
|
else { assert(same(changedref, changedbytes)); };
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
let kept: []str = alloc([], suffixes.len: u64)!;
|
||||||
|
let xi: i32 = 0;
|
||||||
|
for (xi < suffixes.len) {
|
||||||
|
append(kept, strings.dup(readfile(strings.concat(work,
|
||||||
|
"/alpha", suffixes[xi]))));
|
||||||
|
xi += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A late producer failure and a producer signal cannot publish and
|
||||||
|
// cannot commit an invalidated persistent generation.
|
||||||
|
rewritefile(alphafile, alphafailing);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
env[linkmodeindex] = "WW_NULL_LINK_MODE=fail";
|
||||||
|
runcommandenvdir(root, strings.concat("null-fail-", tags[si]), coldav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(has(out.stderr, "injected null-output linker failure\n"));
|
||||||
|
assert(has(out.stderr, "ww: w6l failed\n"));
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
xi = 0;
|
||||||
|
for (xi < suffixes.len) {
|
||||||
|
assert(same(kept[xi], readfile(strings.concat(work,
|
||||||
|
"/alpha", suffixes[xi]))));
|
||||||
|
xi += 1;
|
||||||
|
};
|
||||||
|
assert(!directoryhasnew(work));
|
||||||
|
if (si == 0) { faildiagref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(faildiagref, out.stderr)); };
|
||||||
|
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
env[linkmodeindex] = "WW_NULL_LINK_MODE=signal";
|
||||||
|
runcommandenvdir(root, strings.concat("null-signal-", tags[si]), coldav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(has(out.stderr, "ww: w6l failed\n"));
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
xi = 0;
|
||||||
|
for (xi < suffixes.len) {
|
||||||
|
assert(same(kept[xi], readfile(strings.concat(work,
|
||||||
|
"/alpha", suffixes[xi]))));
|
||||||
|
xi += 1;
|
||||||
|
};
|
||||||
|
assert(!directoryhasnew(work));
|
||||||
|
if (si == 0) { signaldiagref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(signaldiagref, out.stderr)); };
|
||||||
|
env[linkmodeindex] = "WW_NULL_LINK_MODE=";
|
||||||
|
|
||||||
|
// Import loading and empty-pattern handling precede output planning.
|
||||||
|
// The exact null exception changes neither diagnostic nor tool order.
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
let badav: []str = [driver(stages[si]), "build", "-I", source,
|
||||||
|
"-o", "/dev/null", bad];
|
||||||
|
runcommandenvdir(root, strings.concat("null-import-", tags[si]), badav,
|
||||||
|
env, root, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(has(out.stderr, "cannot find package missing.pkg\n"));
|
||||||
|
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len == 0 && readfile(lastout).len == 0);
|
||||||
|
if (si == 0) { missingdiagref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(missingdiagref, out.stderr)); };
|
||||||
|
|
||||||
|
let emptyroot: str = strings.concat(source, "/absent/...");
|
||||||
|
let emptyav: []str = [driver(stages[si]), "build", "-o",
|
||||||
|
"/dev/null", emptyroot];
|
||||||
|
runcommandenvdir(root, strings.concat("null-empty-", tags[si]), emptyav,
|
||||||
|
env, root, time.second, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(has(out.stderr, "matched no packages\n"));
|
||||||
|
assert(readfile(ctrace).len == 0 && readfile(ltrace).len == 0);
|
||||||
|
if (si == 0) { emptydiagref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(emptydiagref, out.stderr)); };
|
||||||
|
|
||||||
|
// One discard request may contain commands and a non-main library.
|
||||||
|
// Every action runs, only commands link, and no basename is published.
|
||||||
|
rewritefile(alphafile, alphaoriginal);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
let multiav: []str = [driver(stages[si]), "build", "-I", source,
|
||||||
|
"-o", "/dev/null", alpha, beta, library];
|
||||||
|
runcommandenvdir(root, strings.concat("null-multi-", tags[si]), multiav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(occurrences(readfile(ctrace), "\n") == 4);
|
||||||
|
assert(occurrences(readfile(atrace), "\n") == 6);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 2);
|
||||||
|
assert(!os.exists(strings.concat(root, "/alpha")));
|
||||||
|
assert(!os.exists(strings.concat(root, "/beta")));
|
||||||
|
assert(!os.exists(strings.concat(root, "/library")));
|
||||||
|
assert(!os.exists("/dev/null.new"));
|
||||||
|
assert(!os.exists("/dev/null.sepwork"));
|
||||||
|
|
||||||
|
// Assembly-only keeps the same loading/action exception, has no link,
|
||||||
|
// and needs no persistent workdir solely to make discarded files visible.
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
let asmav: []str = [driver(stages[si]), "build", "-S", "-I", source,
|
||||||
|
"-o", "/dev/null", alpha, beta, library];
|
||||||
|
runcommandenvdir(root, strings.concat("null-asm-", tags[si]), asmav,
|
||||||
|
env, root, (120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(occurrences(readfile(ctrace), "\n") == 4);
|
||||||
|
assert(readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||||
|
|
||||||
|
// The direct raw-file route links privately too. A wrapper capture proves
|
||||||
|
// the linked bytes are runnable before the driver's cleanup.
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
rewritefile(capture, "");
|
||||||
|
let rawav: []str = [driver(stages[si]), "build", "-o",
|
||||||
|
"/dev/null", raw];
|
||||||
|
runcommandenvdir(root, strings.concat("null-raw-", tags[si]), rawav,
|
||||||
|
env, root, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
let rawbytes: str = readfile(capture);
|
||||||
|
if (si == 0) { rawref = strings.dup(rawbytes); }
|
||||||
|
else { assert(same(rawref, rawbytes)); };
|
||||||
|
let rawrun: []str = [capture];
|
||||||
|
runcommand(root, strings.concat("null-raw-run-", tags[si]), rawrun,
|
||||||
|
time.second, &out);
|
||||||
|
expectexit(&out, 9);
|
||||||
|
|
||||||
|
// Null recognition is exact. A regular lookalike is retained with its
|
||||||
|
// established adjacent scratch; a multi-root lookalike still rejects.
|
||||||
|
let lookalike: str = strings.concat(root, "/null-like-", tags[si]);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
env[captureindex] = "WW_NULL_CAPTURE=";
|
||||||
|
let lookav: []str = [driver(stages[si]), "build", "-o",
|
||||||
|
lookalike, raw];
|
||||||
|
runcommandenvdir(root, strings.concat("null-look-", tags[si]), lookav,
|
||||||
|
env, root, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(os.exists(lookalike));
|
||||||
|
assert(os.exists(strings.concat(lookalike, ".sepwork")));
|
||||||
|
let lookbytes: str = readfile(lookalike);
|
||||||
|
if (si == 0) { lookalikeref = strings.dup(lookbytes); }
|
||||||
|
else { assert(same(lookalikeref, lookbytes)); };
|
||||||
|
let rejectout: str = strings.concat(root, "/multi-like-", tags[si]);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, "");
|
||||||
|
let rejectav: []str = [driver(stages[si]), "build", "-I", source,
|
||||||
|
"-o", rejectout, alpha, beta];
|
||||||
|
runcommandenvdir(root, strings.concat("null-reject-", tags[si]),
|
||||||
|
rejectav, env, root, time.second, &out);
|
||||||
|
expectexit(&out, 2);
|
||||||
|
assert(has(out.stderr, "cannot use -o with multiple packages\n"));
|
||||||
|
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len == 0);
|
||||||
|
if (si == 0) { rejectdiagref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(rejectdiagref, out.stderr)); };
|
||||||
|
env[captureindex] = strings.concat("WW_NULL_CAPTURE=", capture);
|
||||||
|
|
||||||
|
// Raw test compilation has the same private-output rule. Running forms
|
||||||
|
// preserve ordinary success, assertion, and signal outcomes.
|
||||||
|
let compileav: []str = [driver(stages[si]), "test", "-c", "-o",
|
||||||
|
"/dev/null", runtimepath("success_test.ww")];
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
rewritefile(capture, "");
|
||||||
|
runcommandenvdir(root, strings.concat("null-test-c-", tags[si]),
|
||||||
|
compileav, env, root,
|
||||||
|
(120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
let testbytes: str = readfile(capture);
|
||||||
|
if (si == 0) { testcompileref = strings.dup(testbytes); }
|
||||||
|
else { assert(same(testcompileref, testbytes)); };
|
||||||
|
|
||||||
|
let runtargets: []str = ["success_test.ww", "fail_test.ww",
|
||||||
|
"signal_test.ww"];
|
||||||
|
let exitcodes: []i32 = [0, 1, 1];
|
||||||
|
let needles: []str = ["alpha_pass ... ok\n",
|
||||||
|
"assertion_failure ... FAIL (exit 1)\n",
|
||||||
|
"signal_is_not_exit ... FAIL (signal 15)\n"];
|
||||||
|
let ri: i32 = 0;
|
||||||
|
for (ri < runtargets.len) {
|
||||||
|
let testav: []str = [driver(stages[si]), "test"];
|
||||||
|
append(testav, "-o"); append(testav, "/dev/null");
|
||||||
|
append(testav, runtimepath(runtargets[ri]));
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, "");
|
||||||
|
rewritefile(ltrace, ""); rewritefile(lastout, "");
|
||||||
|
runcommandenvdir(root, strings.concat("null-test-run-", tags[si],
|
||||||
|
"-", boundarypkgname(ri)), testav, env, root,
|
||||||
|
(120i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, exitcodes[ri]);
|
||||||
|
assert(has(out.stdout, needles[ri]));
|
||||||
|
assert(occurrences(readfile(ltrace), "\n") == 1);
|
||||||
|
discardedlinkcleaned(lastout);
|
||||||
|
if (si == 0) {
|
||||||
|
testoutrefs[ri] = strings.dup(out.stdout);
|
||||||
|
testerrrefs[ri] = strings.dup(out.stderr);
|
||||||
|
} else {
|
||||||
|
assert(same(testoutrefs[ri], out.stdout));
|
||||||
|
assert(same(testerrrefs[ri], out.stderr));
|
||||||
|
};
|
||||||
|
ri += 1;
|
||||||
|
};
|
||||||
|
assert(!os.exists("/dev/null.new"));
|
||||||
|
assert(!os.exists("/dev/null.sepwork"));
|
||||||
|
si += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Persisted semantic artifacts remain byte-identical across stages. Output
|
||||||
|
// disposition is request metadata and does not enter these identities.
|
||||||
|
let actions: []str = ["base", "alpha"];
|
||||||
|
let ai: i32 = 0;
|
||||||
|
for (ai < actions.len) {
|
||||||
|
let xi: i32 = 0;
|
||||||
|
for (xi < suffixes.len) {
|
||||||
|
assert(same(readfile(strings.concat(root, "/work-c/",
|
||||||
|
actions[ai], suffixes[xi])), readfile(strings.concat(root,
|
||||||
|
"/work-ww/", actions[ai], suffixes[xi]))));
|
||||||
|
xi += 1;
|
||||||
|
};
|
||||||
|
ai += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Independent Cstage and WWstage discard requests can execute together.
|
||||||
|
// Their private products and persistent generations remain isolated.
|
||||||
|
let pcwork: str = strings.concat(root, "/parallel-c-work");
|
||||||
|
let pwwork: str = strings.concat(root, "/parallel-ww-work");
|
||||||
|
mkdirall(pcwork); mkdirall(pwwork);
|
||||||
|
let pcav: []str = [driver("ww"), "build", "-w", pcwork,
|
||||||
|
"-I", source, "-o", "/dev/null", alpha, beta, library];
|
||||||
|
let pwav: []str = [driver("ww_ww"), "build", "-w", pwwork,
|
||||||
|
"-I", source, "-o", "/dev/null", alpha, beta, library];
|
||||||
|
let pc: exec.command;
|
||||||
|
pc.path = pcav[0]; pc.argv = pcav; pc.env = os.getenvs(); pc.dir = root;
|
||||||
|
pc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
|
||||||
|
pc.stderrpath = strings.concat(root, "/parallel-c.stderr");
|
||||||
|
pc.deadline = time.add(time.now(time.clock.monotonic),
|
||||||
|
(120i64 * (time.second: i64)): time.duration);
|
||||||
|
pc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let pw: exec.command;
|
||||||
|
pw.path = pwav[0]; pw.argv = pwav; pw.env = os.getenvs(); pw.dir = root;
|
||||||
|
pw.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
|
||||||
|
pw.stderrpath = strings.concat(root, "/parallel-ww.stderr");
|
||||||
|
pw.deadline = time.add(time.now(time.clock.monotonic),
|
||||||
|
(120i64 * (time.second: i64)): time.duration);
|
||||||
|
pw.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let pcp: exec.process;
|
||||||
|
let pwp: exec.process;
|
||||||
|
exec.start(&pcp, &pc); exec.start(&pwp, &pw);
|
||||||
|
let pcdone: bool = false;
|
||||||
|
let pwdone: bool = false;
|
||||||
|
for (!pcdone || !pwdone) {
|
||||||
|
if (!pcdone) { pcdone = exec.poll(&pcp); };
|
||||||
|
if (!pwdone) { pwdone = exec.poll(&pwp); };
|
||||||
|
if (!pcdone || !pwdone) {
|
||||||
|
time.sleep(time.millisecond, time.clock.monotonic);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
assert(pcp.result.errno == 0 && pcp.result.cleanuperrno == 0);
|
||||||
|
assert(pwp.result.errno == 0 && pwp.result.cleanuperrno == 0);
|
||||||
|
assert(pcp.result.termination == exec.termination.EXIT
|
||||||
|
&& pcp.result.code == 0);
|
||||||
|
assert(pwp.result.termination == exec.termination.EXIT
|
||||||
|
&& pwp.result.code == 0);
|
||||||
|
assert(readfile(pc.stdoutpath).len == 0 && readfile(pc.stderrpath).len == 0);
|
||||||
|
assert(readfile(pw.stdoutpath).len == 0 && readfile(pw.stderrpath).len == 0);
|
||||||
|
assert(!directoryhasnew(pcwork) && !directoryhasnew(pwwork));
|
||||||
|
ai = 0;
|
||||||
|
let parallel: []str = ["base", "alpha", "beta", "library"];
|
||||||
|
for (ai < parallel.len) {
|
||||||
|
let xi: i32 = 0;
|
||||||
|
for (xi < suffixes.len) {
|
||||||
|
assert(same(readfile(strings.concat(pcwork, "/", parallel[ai],
|
||||||
|
suffixes[xi])), readfile(strings.concat(pwwork, "/",
|
||||||
|
parallel[ai], suffixes[xi]))));
|
||||||
|
xi += 1;
|
||||||
|
};
|
||||||
|
ai += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let nullafter: os.filestat;
|
||||||
|
match (os.stat(&nullafter, "/dev/null")) {
|
||||||
|
case void => void;
|
||||||
|
case let e: os.oserror => abort("cannot restat /dev/null");
|
||||||
|
};
|
||||||
|
assert((nullafter.mode as u32) == (nullbefore.mode as u32));
|
||||||
|
assert(nullafter.inode == nullbefore.inode);
|
||||||
|
assert(nullafter.sz == nullbefore.sz);
|
||||||
|
assert(!os.exists("/dev/null.new"));
|
||||||
|
assert(!os.exists("/dev/null.sepwork"));
|
||||||
|
clean(root);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user