ww build: name current directory output
This commit is contained in:
@@ -38,7 +38,7 @@ static const char *usage =
|
|||||||
" -o FILE publishes a non-main archive FILE + FILE.wwi\n"
|
" -o FILE publishes a non-main archive FILE + FILE.wwi\n"
|
||||||
" -o DIR publishes each selected command beneath DIR\n"
|
" -o DIR publishes each selected command beneath DIR\n"
|
||||||
" lib/... every eligible package under lib, recursively\n"
|
" lib/... every eligible package under lib, recursively\n"
|
||||||
" . build the cwd's <basename>.ww\n";
|
" . current directory package (default when no path is given)\n";
|
||||||
|
|
||||||
static char *self_dir;
|
static char *self_dir;
|
||||||
static const char *self_path;
|
static const char *self_path;
|
||||||
@@ -7158,6 +7158,36 @@ build_import_leaf(const char *identity, char *out, size_t outsz)
|
|||||||
snprintf(out, outsz, "%s", leaf);
|
snprintf(out, outsz, "%s", leaf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
build_current_dir_spelling(const char *path)
|
||||||
|
{
|
||||||
|
if (path[0] == '\0' || path[0] == '/') return 0;
|
||||||
|
const char *p = path;
|
||||||
|
int found = 0;
|
||||||
|
for (;;) {
|
||||||
|
while (*p == '/') p++;
|
||||||
|
if (*p == '\0') return found;
|
||||||
|
if (*p++ != '.' || (*p != '\0' && *p != '/')) return 0;
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
build_local_dir_leaf(const char *resolved, char *out, size_t outsz)
|
||||||
|
{
|
||||||
|
char canonical[PATH_MAX];
|
||||||
|
const char *selected = resolved;
|
||||||
|
if (build_current_dir_spelling(resolved)
|
||||||
|
&& realpath(resolved, canonical) != NULL)
|
||||||
|
selected = canonical;
|
||||||
|
char tmp[PATH_MAX];
|
||||||
|
memcpy(tmp, selected, strlen(selected) + 1);
|
||||||
|
size_t n = strlen(tmp);
|
||||||
|
while (n > 1 && tmp[n - 1] == '/') tmp[--n] = '\0';
|
||||||
|
const char *base = strrchr(tmp, '/');
|
||||||
|
snprintf(out, outsz, "%s", base ? base + 1 : tmp);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||||
int *is_dir)
|
int *is_dir)
|
||||||
@@ -7428,15 +7458,12 @@ do_build(int argc, char **argv)
|
|||||||
} else if (is_dir && root_identity != NULL) {
|
} else if (is_dir && root_identity != NULL) {
|
||||||
build_import_leaf(root_identity, out, sizeof out);
|
build_import_leaf(root_identity, out, sizeof out);
|
||||||
} else if (is_dir) {
|
} else if (is_dir) {
|
||||||
char tmp[PATH_MAX];
|
build_local_dir_leaf(resolved, out, sizeof out);
|
||||||
memcpy(tmp, resolved, strlen(resolved) + 1);
|
|
||||||
size_t n = strlen(tmp);
|
|
||||||
while (n > 1 && tmp[n-1] == '/') tmp[--n] = '\0';
|
|
||||||
const char *b = strrchr(tmp, '/');
|
|
||||||
snprintf(out, sizeof out, "%s", b ? b + 1 : tmp);
|
|
||||||
} else {
|
} else {
|
||||||
basename_no_ext(resolved, out, sizeof out);
|
basename_no_ext(resolved, out, sizeof out);
|
||||||
}
|
}
|
||||||
|
if (!outflag[0] && is_dir && build_current_dir_spelling(resolved))
|
||||||
|
objstem = out;
|
||||||
const char *default_output_dir = !outflag[0] && build_output_dir(out)
|
const char *default_output_dir = !outflag[0] && build_output_dir(out)
|
||||||
? out : NULL;
|
? out : NULL;
|
||||||
if (discard_output) {
|
if (discard_output) {
|
||||||
|
|||||||
@@ -8119,6 +8119,133 @@ This is runtime/coordinator presentation, not a persisted-byte contract. Build
|
|||||||
workdir format remains `18`, test workdir format remains `19`, and semantic
|
workdir format remains `18`, test workdir format remains `19`, and semantic
|
||||||
storage format remains `3`.
|
storage format remains `3`.
|
||||||
|
|
||||||
|
### 11.40 Implemented current-directory default build names
|
||||||
|
|
||||||
|
An empty `ww build` package list selects the current directory just as an
|
||||||
|
explicit `.`, `./`, or equivalent sequence of single-dot components does. For
|
||||||
|
one command package and no explicit `-o`, the public executable is now named by
|
||||||
|
the selected directory's final component. The selector is loading syntax; it
|
||||||
|
is not the literal output pathname. Thus a command built while the current
|
||||||
|
directory is `tool` publishes `tool`, not `.`, and cold private build artifacts
|
||||||
|
use `tool.sepwork`, not `..sepwork`.
|
||||||
|
|
||||||
|
A non-main current-directory package uses the same corrected cold scratch stem,
|
||||||
|
but still has no link or public installation action. An explicit `-o`, exact
|
||||||
|
`-o /dev/null`, an ordinary non-current directory operand, and a contextual
|
||||||
|
dotted package request keep their established output rules.
|
||||||
|
|
||||||
|
#### Pinned Go evidence and fact classification
|
||||||
|
|
||||||
|
The sole semantic authority is official Go 1.26.5 at commit
|
||||||
|
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||||
|
|
||||||
|
- `search.CleanPatterns` turns an empty package-pattern list into exactly `.`;
|
||||||
|
`ImportPathsQuiet` then treats that local pattern as a directory selection
|
||||||
|
([`cmd/go/internal/search/search.go`, lines 441–480](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/search/search.go#L441-L480)).
|
||||||
|
- `work.runBuild` loads packages and checks load errors before output planning.
|
||||||
|
With exactly one loaded `main` package and no `-o`, it selects
|
||||||
|
`DefaultExecName`
|
||||||
|
([`cmd/go/internal/work/build.go`, lines 459–478](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L478)).
|
||||||
|
- `load.(*Package).exeFromImportPath` takes the final loaded import-path
|
||||||
|
element, while `DefaultExecName` uses a source basename only for a
|
||||||
|
command-line-files package
|
||||||
|
([`cmd/go/internal/load/pkg.go`, lines 1727–1769](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1727-L1769)).
|
||||||
|
- Official command testdata distinguishes module naming from the manifest-free
|
||||||
|
GOPATH case and requires bare `go build` to create the directory-named `src`
|
||||||
|
executable
|
||||||
|
([`clean_binary.txt`, lines 15–28](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/clean_binary.txt#L15-L28)).
|
||||||
|
A second manifest-free script changes to `m`, runs bare `go build`, and
|
||||||
|
requires executable `m`
|
||||||
|
([`gccgo_m.txt`, lines 4–14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/gccgo_m.txt#L4-L14));
|
||||||
|
`build_static.txt` likewise builds and executes the default `hello`
|
||||||
|
([lines 11–14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_static.txt#L11-L14)).
|
||||||
|
|
||||||
|
Those selection, loading, default-name branches and script assertions are
|
||||||
|
**behavior directly implemented or asserted by pinned Go**. WW has no module
|
||||||
|
or manifest identity for a literal root, so using the selected local directory
|
||||||
|
leaf as its already-specified presentation fallback is **behavior derived from
|
||||||
|
the pinned implementation**. The directory remains loader metadata and does
|
||||||
|
not become canonical package or import identity.
|
||||||
|
|
||||||
|
#### Fresh four-axis audit and direct pre-fix measurements
|
||||||
|
|
||||||
|
The bounded audit examined every permanent axis before choosing this build
|
||||||
|
presentation difference. Both public stages were measured at the same source
|
||||||
|
paths:
|
||||||
|
|
||||||
|
- **Go-like build:** multi-command directory selection without `-o` was already
|
||||||
|
aligned. For a valid command in directory `a`, however, bare `build`,
|
||||||
|
`build .`, and `build ./` each exited 1 in both stages with empty stdout and
|
||||||
|
the same 55 stderr bytes (SHA-256
|
||||||
|
`2e8030a9eeb7187fbc1cb4ee9d794c352f1c31c92c884f8ae09a57e39bfe86db`):
|
||||||
|
`ww: build output "." already exists and is a directory`. Explicit `-o`
|
||||||
|
succeeded and produced byte-identical runnable binaries, proving that only
|
||||||
|
default presentation was wrong. This was the selected gap.
|
||||||
|
- **Go-like test:** an ordinary valid zero-match directory run exited 0 in both
|
||||||
|
stages with identical warning, accounting, and `[no tests to run]` result
|
||||||
|
bytes. The selected build branch does not enter test product naming,
|
||||||
|
filtering, capture, execution, result annotation, or retained test output.
|
||||||
|
- **Go-like package:** a directory containing two selected declared package
|
||||||
|
names was rejected in both stages with identical diagnostics. The selected
|
||||||
|
change occurs after package loading and does not alter source eligibility,
|
||||||
|
declaration checks, package kind, graph nodes, or actions.
|
||||||
|
- **Go-like import:** a dotted `cyclea -> cycleb -> cyclea` graph was rejected
|
||||||
|
in both stages with identical cycle diagnostics. The selected change does
|
||||||
|
not alter spelling, search, visibility, resolution, canonical identity, or
|
||||||
|
graph edges.
|
||||||
|
|
||||||
|
The pre-fix statuses, streams, hashes, diagnostics, artifacts, and runtime
|
||||||
|
results are **directly measured WW behavior**. The cited implementation and
|
||||||
|
testdata are **behavior directly implemented or asserted by pinned Go**. The
|
||||||
|
cross-axis non-effects follow from the bounded post-load output branch and are
|
||||||
|
**behavior derived from the pinned implementation**.
|
||||||
|
|
||||||
|
#### Ownership, final behavior, and preserved boundaries
|
||||||
|
|
||||||
|
`cmd/ww.do_build` and `selfhost/cmd/ww.dobuild` are semantic twins and the sole
|
||||||
|
owners of this rule. Their current-directory predicate accepts only relative
|
||||||
|
paths whose components are all exactly `.`. Only in that branch do they
|
||||||
|
canonicalize the selected directory and take its final component for output and
|
||||||
|
cold scratch presentation. Ordinary literal directory operands retain their
|
||||||
|
lexical leaf, and contextual roots retain their dotted identity leaf.
|
||||||
|
|
||||||
|
Direct post-fix probes through both stages show that bare, dot, and dot-slash
|
||||||
|
builds exit 0 with empty stdout/stderr, publish mode-executable binaries named
|
||||||
|
`a`, and produce `a.sepwork` with no `..sepwork`. All six binaries are
|
||||||
|
byte-identical (SHA-256
|
||||||
|
`866c1eb875dad271d37572f43fb9d9b0eb6a2344d2e61646e655bb09f7909bf6`).
|
||||||
|
Their unit, interface, assembly, object, archive, and init artifacts are also
|
||||||
|
byte-identical between stages and spellings. Current-directory library builds
|
||||||
|
still publish nothing and link nothing; their unit, interface, assembly,
|
||||||
|
object, and archive bytes remain stage-identical under `libcurrent.sepwork`.
|
||||||
|
|
||||||
|
Loading and source selection precede this branch. A missing dotted import
|
||||||
|
therefore retains its byte-identical diagnostic and creates neither output nor
|
||||||
|
scratch. Graph and action construction, compiler/assembler/archiver/linker
|
||||||
|
semantics, runtime behavior, and artifact content are unchanged. A genuine
|
||||||
|
directory occupying the derived output still rejects before tools and names
|
||||||
|
the derived leaf in its diagnostic.
|
||||||
|
|
||||||
|
Cold and warm persistent builds retain their existing keys and reuse rules. A
|
||||||
|
source invalidation reruns producers; an injected compiler failure preserves
|
||||||
|
the prior executable and committed persistent generation, publishes no stage,
|
||||||
|
and leaves no `.new`, `.install`, or `.wwtxn.*` residue. Restoring producer
|
||||||
|
success installs the changed executable. Existing concurrency, interruption,
|
||||||
|
process-group, rollback, publication, and cleanup owners gain no shared state or
|
||||||
|
new process path.
|
||||||
|
|
||||||
|
The WW-native `current_directory_build_default_output` observer proves both
|
||||||
|
stages, all three current-directory spellings, executable mode/runtime/byte
|
||||||
|
parity, explicit and null output controls, non-main non-publication, corrected
|
||||||
|
cold scratch, missing-import precedence, cold/warm reuse, invalidation,
|
||||||
|
producer-failure rollback, prior-output preservation, genuine collision, and
|
||||||
|
residue absence. Existing package/import graph, byte-identity, concurrent
|
||||||
|
transaction, and interruption observers remain authoritative for mechanisms
|
||||||
|
this presentation rule does not change.
|
||||||
|
|
||||||
|
No persisted-byte contract changed. Build workdir format remains `18`, test
|
||||||
|
workdir format remains `19`, and semantic storage format remains `3`.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
13
docs/spec.md
13
docs/spec.md
@@ -340,8 +340,17 @@ ImportPath = ident { "." ident } .
|
|||||||
command's synthesized default basename already names a directory, loading
|
command's synthesized default basename already names a directory, loading
|
||||||
and graph validation complete and the build rejects before tools without
|
and graph validation complete and the build rejects before tools without
|
||||||
changing that directory; a non-main package synthesizes no default public
|
changing that directory; a non-main package synthesizes no default public
|
||||||
output. Output paths and directory metadata never become package, import,
|
output. With no path operand, the current directory is selected as if `.` had
|
||||||
graph, action,
|
been supplied. For a lone literal current-directory command and no explicit
|
||||||
|
`-o`, `.`, `./`, and equivalent single-dot-component spellings derive the
|
||||||
|
default executable name from the canonical selected directory's final
|
||||||
|
component; cold adjacent scratch uses that same leaf plus `.sepwork`. The dot
|
||||||
|
spelling itself is never an output name. A current-directory non-main package
|
||||||
|
uses the corrected scratch presentation but still links and publishes
|
||||||
|
nothing. This physical leaf is fallback presentation metadata only: it never
|
||||||
|
displaces a requested contextual dotted identity or becomes package, import,
|
||||||
|
graph, action, artifact, symbol, `.wwi`, or persistence identity.
|
||||||
|
Output paths and directory metadata never become package, import, graph, action,
|
||||||
symbol, artifact, `.wwi`, or persistence identity.
|
symbol, artifact, `.wwi`, or persistence identity.
|
||||||
|
|
||||||
Every caller-visible build installation checks its destination after all
|
Every caller-visible build installation checks its destination after all
|
||||||
|
|||||||
@@ -8629,7 +8629,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn writeusage(fd: i32) void = {
|
fn writeusage(fd: i32) void = {
|
||||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-I DIR] [-o FILE|DIR] [path ...] build local package graphs\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path ...] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then the source library for foo.ww or foo/\n lib/foo directory: build its package sources\n -o FILE publishes a non-main archive FILE + FILE.wwi\n -o DIR publishes each selected command beneath DIR\n lib/... every eligible package under lib, recursively\n . build the cwd's <basename>.ww\n";
|
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-I DIR] [-o FILE|DIR] [path ...] build local package graphs\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path ...] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then the source library for foo.ww or foo/\n lib/foo directory: build its package sources\n -o FILE publishes a non-main archive FILE + FILE.wwi\n -o DIR publishes each selected command beneath DIR\n lib/... every eligible package under lib, recursively\n . current directory package (default when no path is given)\n";
|
||||||
os.write(fd, s.ptr, s.len: u64);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8716,6 +8716,43 @@ fn defaultimportoutpath(identity: *u8) *u8 = {
|
|||||||
return out.ptr;
|
return out.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn buildcurrentdirspelling(path: *u8) bool = {
|
||||||
|
if (path[0u64] == 0u8 || path[0u64] == '/': u8) { return false; };
|
||||||
|
let i: u64 = 0u64;
|
||||||
|
let found: bool = false;
|
||||||
|
for (true) {
|
||||||
|
for (path[i] == '/': u8) { i += 1u64; };
|
||||||
|
if (path[i] == 0u8) { return found; };
|
||||||
|
if (path[i] != '.': u8) { return false; };
|
||||||
|
i += 1u64;
|
||||||
|
if (path[i] != 0u8 && path[i] != '/': u8) { return false; };
|
||||||
|
found = true;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn buildlocaldirleaf(resolved: *u8) *u8 = {
|
||||||
|
let selected: *u8 = resolved;
|
||||||
|
if (buildcurrentdirspelling(resolved)) {
|
||||||
|
let canonical: *u8 = canonicaldir(pathstr(resolved));
|
||||||
|
if (canonical != nil) { selected = canonical; };
|
||||||
|
};
|
||||||
|
let rlen: u64 = cstrlen(selected);
|
||||||
|
for (rlen > 1u64) {
|
||||||
|
if (selected[rlen - 1u64] != '/': u8) { break; };
|
||||||
|
rlen -= 1u64;
|
||||||
|
};
|
||||||
|
let bo: u64 = basenameoff(selected, rlen);
|
||||||
|
let outbuf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
|
outbuf.len = os.PATH_MAX;
|
||||||
|
let out: *u8 = outbuf.ptr;
|
||||||
|
let i: u64 = bo;
|
||||||
|
let off: u64 = 0u64;
|
||||||
|
for (i < rlen) { out[off] = selected[i]; off += 1u64; i += 1u64; };
|
||||||
|
cstrseal(out, off);
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||||
let src: *u8 = nil;
|
let src: *u8 = nil;
|
||||||
let srcindex: i32 = -1;
|
let srcindex: i32 = -1;
|
||||||
@@ -8897,22 +8934,14 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
} else { if (isdir != 0 && rootidentity != nil) {
|
} else { if (isdir != 0 && rootidentity != nil) {
|
||||||
out = defaultimportoutpath(rootidentity);
|
out = defaultimportoutpath(rootidentity);
|
||||||
} else { if (isdir != 0) {
|
} else { if (isdir != 0) {
|
||||||
let rlen: u64 = cstrlen(resolved);
|
out = buildlocaldirleaf(resolved);
|
||||||
for (rlen > 1u64) {
|
|
||||||
if (resolved[rlen - 1u64] != 47u8) { break; };
|
|
||||||
rlen -= 1u64;
|
|
||||||
};
|
|
||||||
let bo: u64 = basenameoff(resolved, rlen);
|
|
||||||
let outbuf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
outbuf.len = os.PATH_MAX;
|
|
||||||
out = outbuf.ptr;
|
|
||||||
let i: u64 = bo;
|
|
||||||
let off: u64 = 0u64;
|
|
||||||
for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; };
|
|
||||||
cstrseal(out, off);
|
|
||||||
} else {
|
} else {
|
||||||
out = defaultoutpath(resolved);
|
out = defaultoutpath(resolved);
|
||||||
}; }; };
|
}; }; };
|
||||||
|
if ((outflag == nil || outflag[0u64] == 0u8) && isdir != 0
|
||||||
|
&& buildcurrentdirspelling(resolved)) {
|
||||||
|
objstem = out;
|
||||||
|
};
|
||||||
let defaultoutputdir: *u8 = nil;
|
let defaultoutputdir: *u8 = nil;
|
||||||
if ((outflag == nil || outflag[0u64] == 0u8) && buildoutputdir(out)) {
|
if ((outflag == nil || outflag[0u64] == 0u8) && buildoutputdir(out)) {
|
||||||
defaultoutputdir = out;
|
defaultoutputdir = out;
|
||||||
|
|||||||
@@ -14074,6 +14074,227 @@ fn runtimepath(relative: str) str = {
|
|||||||
clean(root);
|
clean(root);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Empty build patterns select the current directory. A literal dot is
|
||||||
|
// loading syntax, not the caller-visible output name: command publication and
|
||||||
|
// cold scratch use the selected directory leaf without making that physical
|
||||||
|
// leaf package or import identity.
|
||||||
|
@test fn current_directory_build_default_output() void = {
|
||||||
|
let root: str = fresh();
|
||||||
|
let command: str = strings.concat(root, "/current-command");
|
||||||
|
let library: str = strings.concat(root, "/current-library");
|
||||||
|
let bad: str = strings.concat(root, "/bad-current");
|
||||||
|
mkdirall(command); mkdirall(library); mkdirall(bad);
|
||||||
|
let commandfile: str = strings.concat(command, "/main.ww");
|
||||||
|
let original: str = "package main;\nfn main() i32 = { return 23; };\n";
|
||||||
|
let changed: str = "package main;\nfn main() i32 = { return 24; };\n";
|
||||||
|
writefile(commandfile, original);
|
||||||
|
writefile(strings.concat(library, "/library.ww"), strings.concat(
|
||||||
|
"package declared_library;\n",
|
||||||
|
"export fn value() i32 = { return 7; };\n"));
|
||||||
|
writefile(strings.concat(bad, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport missing.current;\n",
|
||||||
|
"fn main() i32 = { return missing.current.value(); };\n"));
|
||||||
|
|
||||||
|
let compilerwrapper: str = strings.concat(root, "/current-w6c.sh");
|
||||||
|
writeexecutable(compilerwrapper, strings.concat(
|
||||||
|
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_CURRENT_TRACE\"\n",
|
||||||
|
"if test \"$WW_CURRENT_FAIL\" = 1; then\n",
|
||||||
|
" printf 'injected current-directory compiler failure\\n' >&2\n",
|
||||||
|
" exit 97\nfi\nexec \"$WW_CURRENT_REAL_C\" \"$@\"\n"));
|
||||||
|
|
||||||
|
let stages: []str = ["ww", "ww_ww"];
|
||||||
|
let compilers: []str = ["w6c", "w6c_ww"];
|
||||||
|
let tags: []str = ["c", "ww"];
|
||||||
|
let spellings: []str = ["", ".", "./"];
|
||||||
|
let spellingtags: []str = ["bare", "dot", "slash"];
|
||||||
|
let baseenv: []str = os.getenvs();
|
||||||
|
let commandref: str = "";
|
||||||
|
let changedref: str = "";
|
||||||
|
let missingref: str = "";
|
||||||
|
let failref: str = "";
|
||||||
|
let collisionref: str = "";
|
||||||
|
let out: commandout;
|
||||||
|
let si: i32 = 0;
|
||||||
|
for (si < stages.len) {
|
||||||
|
rewritefile(commandfile, original);
|
||||||
|
let defaultbin: str = strings.concat(command, "/current-command");
|
||||||
|
let defaultscratch: str = strings.concat(defaultbin, ".sepwork");
|
||||||
|
let legacyscratch: str = strings.concat(command, "/..sepwork");
|
||||||
|
|
||||||
|
// Bare, dot, and dot-slash spellings select the same package and
|
||||||
|
// produce the same directory-named executable and cold artifacts.
|
||||||
|
let pi: i32 = 0;
|
||||||
|
for (pi < spellings.len) {
|
||||||
|
let av: []str = [driver(stages[si]), "build"];
|
||||||
|
if (spellings[pi].len != 0) { append(av, spellings[pi]); };
|
||||||
|
runcommanddir(root, strings.concat("current-", tags[si], "-",
|
||||||
|
spellingtags[pi]), command, av,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(os.exists(defaultbin) && os.exists(defaultscratch));
|
||||||
|
assert(!os.exists(legacyscratch));
|
||||||
|
assert((permissionmode(defaultbin) & 73u32) == 73u32);
|
||||||
|
let built: str = readfile(defaultbin);
|
||||||
|
if (commandref.len == 0) { commandref = strings.dup(built); }
|
||||||
|
else { assert(same(commandref, built)); };
|
||||||
|
let runav: []str = [defaultbin];
|
||||||
|
runcommand(root, strings.concat("current-run-", tags[si], "-",
|
||||||
|
spellingtags[pi]), runav, time.second, &out);
|
||||||
|
expectexit(&out, 23);
|
||||||
|
assert(!directoryhasnew(command)
|
||||||
|
&& !directoryhasfragment(command, ".install")
|
||||||
|
&& !directoryhasfragment(command, ".wwtxn."));
|
||||||
|
assert(os.remove(defaultbin) == 0);
|
||||||
|
clean(defaultscratch);
|
||||||
|
pi += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Explicit output and exact null output keep their established
|
||||||
|
// disposition and never create the default current-directory name.
|
||||||
|
let explicitout: str = strings.concat(root, "/explicit-", tags[si]);
|
||||||
|
let explicitav: []str = [driver(stages[si]), "build", "-o",
|
||||||
|
explicitout];
|
||||||
|
runcommanddir(root, strings.concat("current-explicit-", tags[si]),
|
||||||
|
command, explicitav,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(same(commandref, readfile(explicitout))
|
||||||
|
&& !os.exists(defaultbin) && !os.exists(defaultscratch));
|
||||||
|
assert(os.remove(explicitout) == 0);
|
||||||
|
clean(strings.concat(explicitout, ".sepwork"));
|
||||||
|
let nullav: []str = [driver(stages[si]), "build", "-o", "/dev/null"];
|
||||||
|
runcommanddir(root, strings.concat("current-null-", tags[si]), command,
|
||||||
|
nullav, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(!os.exists(defaultbin) && !os.exists(defaultscratch)
|
||||||
|
&& !os.exists(legacyscratch));
|
||||||
|
|
||||||
|
// A non-main current-directory build compiles and archives privately,
|
||||||
|
// but neither links nor publishes a directory-named file.
|
||||||
|
let librarybin: str = strings.concat(library, "/current-library");
|
||||||
|
let libraryscratch: str = strings.concat(librarybin, ".sepwork");
|
||||||
|
let librarylegacy: str = strings.concat(library, "/..sepwork");
|
||||||
|
let libraryav: []str = [driver(stages[si]), "build"];
|
||||||
|
runcommanddir(root, strings.concat("current-library-", tags[si]),
|
||||||
|
library, libraryav,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
assert(!os.exists(librarybin) && os.exists(libraryscratch)
|
||||||
|
&& !os.exists(librarylegacy));
|
||||||
|
assert(!directoryhasnew(library)
|
||||||
|
&& !directoryhasfragment(library, ".wwtxn."));
|
||||||
|
clean(libraryscratch);
|
||||||
|
|
||||||
|
// Import loading retains precedence over output planning. Failure
|
||||||
|
// creates neither the corrected output nor either scratch spelling.
|
||||||
|
let badbin: str = strings.concat(bad, "/bad-current");
|
||||||
|
let badav: []str = [driver(stages[si]), "build"];
|
||||||
|
runcommanddir(root, strings.concat("current-missing-", tags[si]), bad,
|
||||||
|
badav, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(out.stdout.len == 0
|
||||||
|
&& has(out.stderr, "cannot find package missing.current\n"));
|
||||||
|
assert(!os.exists(badbin)
|
||||||
|
&& !os.exists(strings.concat(badbin, ".sepwork"))
|
||||||
|
&& !os.exists(strings.concat(bad, "/..sepwork")));
|
||||||
|
if (si == 0) { missingref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(missingref, out.stderr)); };
|
||||||
|
|
||||||
|
// Persistent cold/warm reuse keeps the same output. An invalidated
|
||||||
|
// producer failure preserves both that prior output and clean public
|
||||||
|
// state; a later successful invalidation publishes the changed binary.
|
||||||
|
let work: str = strings.concat(root, "/current-work-", tags[si]);
|
||||||
|
let trace: str = strings.concat(root, "/current-trace-", tags[si]);
|
||||||
|
mkdirall(work); writefile(trace, "");
|
||||||
|
let env: []str = alloc([], (baseenv.len + 4): u64)!;
|
||||||
|
let ei: i32 = 0;
|
||||||
|
for (ei < baseenv.len) {
|
||||||
|
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||||
|
&& !strings.hasprefix(baseenv[ei], "WW_CURRENT_")) {
|
||||||
|
append(env, baseenv[ei]);
|
||||||
|
};
|
||||||
|
ei += 1;
|
||||||
|
};
|
||||||
|
append(env, strings.concat("WW_W6C=", compilerwrapper));
|
||||||
|
append(env, strings.concat("WW_CURRENT_REAL_C=",
|
||||||
|
driver(compilers[si])));
|
||||||
|
append(env, strings.concat("WW_CURRENT_TRACE=", trace));
|
||||||
|
let failindex: i32 = env.len;
|
||||||
|
append(env, "WW_CURRENT_FAIL=");
|
||||||
|
let persistav: []str = [driver(stages[si]), "build", "-w", work];
|
||||||
|
runcommandenvdir(root, strings.concat("current-cold-", tags[si]),
|
||||||
|
persistav, env, command,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& readfile(trace).len != 0);
|
||||||
|
let prior: str = readfile(defaultbin);
|
||||||
|
assert(same(commandref, prior));
|
||||||
|
rewritefile(trace, "");
|
||||||
|
runcommandenvdir(root, strings.concat("current-warm-", tags[si]),
|
||||||
|
persistav, env, command,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& readfile(trace).len == 0 && same(prior, readfile(defaultbin)));
|
||||||
|
rewritefile(commandfile, changed); rewritefile(trace, "");
|
||||||
|
env[failindex] = "WW_CURRENT_FAIL=1";
|
||||||
|
runcommandenvdir(root, strings.concat("current-fail-", tags[si]),
|
||||||
|
persistav, env, command,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(readfile(trace).len != 0
|
||||||
|
&& has(out.stderr, "injected current-directory compiler failure\n")
|
||||||
|
&& has(out.stderr, "ww: w6c failed for ")
|
||||||
|
&& same(prior, readfile(defaultbin)));
|
||||||
|
assert(!directoryhasnew(command) && !directoryhasnew(work)
|
||||||
|
&& !directoryhasfragment(command, ".install")
|
||||||
|
&& !directoryhasfragment(command, ".wwtxn.")
|
||||||
|
&& !directoryhasfragment(work, ".wwtxn."));
|
||||||
|
if (si == 0) { failref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(failref, out.stderr)); };
|
||||||
|
env[failindex] = "WW_CURRENT_FAIL="; rewritefile(trace, "");
|
||||||
|
runcommandenvdir(root, strings.concat("current-changed-", tags[si]),
|
||||||
|
persistav, env, command,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& readfile(trace).len != 0);
|
||||||
|
let changedbytes: str = readfile(defaultbin);
|
||||||
|
assert(!same(prior, changedbytes));
|
||||||
|
if (changedref.len == 0) { changedref = strings.dup(changedbytes); }
|
||||||
|
else { assert(same(changedref, changedbytes)); };
|
||||||
|
let changedrun: []str = [defaultbin];
|
||||||
|
runcommand(root, strings.concat("current-changed-run-", tags[si]),
|
||||||
|
changedrun, time.second, &out);
|
||||||
|
expectexit(&out, 24);
|
||||||
|
assert(os.remove(defaultbin) == 0);
|
||||||
|
clean(work);
|
||||||
|
rewritefile(commandfile, original);
|
||||||
|
|
||||||
|
// A genuine destination-directory collision still rejects at output
|
||||||
|
// preflight, now naming the derived leaf rather than the dot selector.
|
||||||
|
mkdirall(defaultbin);
|
||||||
|
runcommanddir(root, strings.concat("current-collision-", tags[si]),
|
||||||
|
command, libraryav,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(
|
||||||
|
"ww: build output \"current-command\" already exists and is a directory\n")));
|
||||||
|
assert(!os.exists(defaultscratch) && !os.exists(legacyscratch));
|
||||||
|
if (si == 0) { collisionref = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(collisionref, out.stderr)); };
|
||||||
|
clean(defaultbin);
|
||||||
|
si += 1;
|
||||||
|
};
|
||||||
|
assert(!directoryhasnew(root) && !directoryhasfragment(root, ".wwtxn."));
|
||||||
|
clean(root);
|
||||||
|
};
|
||||||
|
|
||||||
// A single directory root used to bypass the package coordinator and treat
|
// 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
|
// every non-null -o spelling as one file. Exercise the Go output-directory
|
||||||
// branch at that dispatch boundary while the established graph transaction
|
// branch at that dispatch boundary while the established graph transaction
|
||||||
|
|||||||
Reference in New Issue
Block a user