ww build: name current directory output

This commit is contained in:
2026-08-21 17:38:00 +09:00
parent 17b2879c3e
commit b03bbb1428
5 changed files with 436 additions and 23 deletions

View File

@@ -14074,6 +14074,227 @@ fn runtimepath(relative: str) str = {
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
// every non-null -o spelling as one file. Exercise the Go output-directory
// branch at that dispatch boundary while the established graph transaction