ww: implement recursive local package patterns

This commit is contained in:
2026-08-13 23:42:37 +09:00
parent 5d50788e46
commit d5fdc27c0a
5 changed files with 1976 additions and 360 deletions

View File

@@ -31,6 +31,8 @@ def SEP_LOCAL_IMPORT_PREFIX: str = "__wwlocal";
let selfpath: *u8;
let sepfatalallocation: bool;
@symbol("rt_envp") fn rawenvp() **u8;
// Tool-local (NOT a lib wrapper): messages are built from many
// fragments and we route through os.write to avoid libc stdio.
// .len replaces the error-prone hand-counted byte literals these sites
@@ -77,6 +79,28 @@ fn cstrlen(p: *u8) u64 = {
return n;
};
fn rawenvvalue(name: str) *u8 = {
let env: **u8 = rawenvp();
let i: i32 = 0;
for (env[i] != nil) {
let entry: *u8 = env[i];
let j: i32 = 0;
for (j < name.len && entry[j] == name[j]) { j += 1; };
if (j == name.len && entry[j] == '=': u8) {
return entry + ((j + 1): u64);
};
i += 1;
};
return nil;
};
fn clipathfits(command: str, flag: str, value: *u8) bool = {
if (cstrlen(value) < os.PATH_MAX: u64) { return true; };
cerr("ww "); cerr(command); cerr(": "); cerr(flag);
cerr(" path is too long\n");
return false;
};
// Bridges the driver's argv-style *u8 paths to lib/os entrypoints
// (str post-task-#23).
fn pathstr(p: *u8) str = {
@@ -242,21 +266,39 @@ fn toolpath(selfdir: *u8, envvar: str, name: str) *u8 = {
// package.ww roots stay on runsingletest, which is the recursion boundary when
// wwtest builds its already-aggregated package binaries.
fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
targetindex: i32, resolved: *u8, rootidentity: *u8, adddot: bool) i32 = {
let prog: *u8 = joinpathlit(selfdir, "wwtest");
match (os.getenv("WW_WWTEST")) {
case let p: str => {
if (p.len != 0) { prog = owncstr(p); };
};
case void => void;
targetindex: i32, resolved: *u8, rootidentity: *u8, adddot: bool,
buildonly: bool) i32 = {
let prog: *u8 = rawenvvalue("WW_WWTEST");
if (prog == nil || prog[0u64] == 0u8) {
prog = sepjoinpathlit(selfdir, "wwtest");
if (prog == nil) { return 1; };
};
let cap: i32 = argc - start + 8;
let execargv: []*u8 = alloc([], cap: u64)!;
execargv.len = cap;
if (start < 0 || start > argc || argc - start > SEP_COUNT_MAX - 10) {
sepfailsize();
return 1;
};
let cap: i32 = argc - start + 10;
let execargv: []*u8;
let argvallocation: ([]*u8 | nomem) = sepallocptrs(cap);
match (argvallocation) {
case let value: []*u8 => { execargv = value; execargv.len = cap; };
case nomem => {
if (buildonly) {
cerr("ww build: cannot allocate package coordinator arguments\n");
} else {
cerr("ww test: cannot allocate package coordinator arguments\n");
};
return 1;
};
};
let n: i32 = 0;
execargv[n] = prog; n += 1;
execargv[n] = "package".ptr; n += 1;
if (buildonly) {
execargv[n] = "--ww-operation".ptr; n += 1;
execargv[n] = "build".ptr; n += 1;
};
execargv[n] = "--ww-driver".ptr; n += 1;
execargv[n] = selfpath; n += 1;
if (rootidentity != nil) {
@@ -278,14 +320,9 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
if (adddot && !dotted) { execargv[n] = ".".ptr; n += 1; };
execargv[n] = nil;
let env: []str = os.getenvs();
let envp: []*u8 = alloc([], (env.len + 1): u64)!;
envp.len = env.len + 1;
i = 0;
for (i < env.len) { envp[i] = owncstr(env[i]); i += 1; };
envp[env.len] = nil;
os.execve(pathstr(prog), execargv.ptr, envp.ptr);
cerr("ww test: cannot exec package test coordinator\n");
os.execve(pathstr(prog), execargv.ptr, rawenvp());
if (buildonly) { cerr("ww build: cannot exec package coordinator\n"); }
else { cerr("ww test: cannot exec package coordinator\n"); };
return 1;
};
@@ -534,6 +571,7 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64,
// nlen<=3 guard kept: a bare ".ww" (len 3) is rejected here but
// would pass strings.hassuffix(".ww"); preserves cstage parity.
if (nlen <= 3u64) { return 0; };
if (name[0] == '.' || name[0] == '_') { return 0; };
let s: str;
s.ptr = name;
s.len = nlen: i32;
@@ -545,13 +583,27 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64,
let fi: os.filestat;
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
let regular: bool = false;
let directory: bool = false;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32;
if (t == os.mode.REG: u32) { regular = true; };
if (t == os.mode.REG: u32) { regular = true; }
else if (t == os.mode.LINK: u32) {
let target: os.filestat;
match (os.stat(&target, pathstr(source))) {
case void => {
let tt: u32 = (target.mode: u32) & 61440u32;
if (tt == os.mode.REG: u32) { regular = true; };
if (tt == os.mode.DIR: u32) { directory = true; };
};
case let e: os.oserror => void;
};
};
};
case let e: os.oserror => void;
};
// go/build ignores a source-shaped symlink to a directory.
if (directory) { return 0; };
if (!regular) {
cerr("ww: "); cerr(pathstr(source));
cerr(": package source is not a regular file\n");
@@ -4265,6 +4317,14 @@ fn validatepackageoutputpath(out: *u8) i32 = {
return 0;
};
fn validatecommandoutputpath(out: *u8) i32 = {
if (out == nil || cstrlen(out) + 1u64 > os.PATH_MAX: u64) {
cerr("ww: command output path is too long\n");
return -1;
};
return 0;
};
// The stamp pins the non-content build inputs a unit compare cannot see:
// the -T/-S/root-action shape of the producer pass and the artifact protocol
// revision (bump "fmt" when the unit/archive/commit format changes).
@@ -4374,6 +4434,84 @@ fn invalidateworkdirunits(scratch: *u8) i32 = {
return rc;
};
type sepcreateddirs = struct {
path: [4096]u8,
offset: [2048]u16,
n: i32,
};
fn seprollbackdirs(created: *sepcreateddirs) void = {
for (created.n > 0) {
created.n -= 1;
let at: u64 = created.offset[created.n]: u64;
let saved: u8 = created.path[at];
created.path[at] = 0u8;
let ignored: i32 = os.rmdir(pathstr(&created.path[0]));
created.path[at] = saved;
};
};
// Keep the prefixes created after semantic preflight separately from
// pre-existing caller state so a later setup failure can reclaim only them.
fn sepmkdirsrecord(path: *u8, mode: i32, created: *sepcreateddirs) i32 = {
created.n = 0;
let n: u64 = cstrlen(path);
if (n == 0u64 || n >= os.PATH_MAX: u64) { return -1; };
let buf: [4096]u8;
bytecpy(&buf[0], path, n + 1u64);
for (n > 1u64 && buf[n - 1u64] == 47u8) {
n -= 1u64;
buf[n] = 0u8;
};
bytecpy(&created.path[0], &buf[0], n + 1u64);
let i: u64 = 0u64;
if (buf[0] == 47u8) { i = 1u64; };
for (i <= n) {
if (buf[i] != 47u8 && buf[i] != 0u8) {
i += 1u64;
continue;
};
let saved: u8 = buf[i];
buf[i] = 0u8;
if (buf[0] != 0u8) {
let fi: os.filestat;
let missing: bool = false;
match (os.stat(&fi, pathstr(&buf[0]))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ != os.mode.DIR: u32) {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
};
case let e: os.oserror => {
if ((e: i64) == -2i64) { missing = true; }
else {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
};
};
if (missing) {
if (created.n >= 2048
|| os.mkdir(pathstr(&buf[0]), mode) != 0) {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
created.offset[created.n] = i: u16;
created.n += 1;
};
};
buf[i] = saved;
if (saved == 0u8) { break; };
i += 1u64;
};
return 0;
};
// cerrpath — the "ww: <head><path>\n" diagnostic shape shared by the
// workdir error sites; byte-identical wording to the cstage twin's
// fprintf(..., "%s", path) forms.
@@ -4387,7 +4525,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
out: *u8, objstem: *u8, incs: *u8, lf: *lflags,
publishpackage: i32, requirecommand: i32, istest: i32,
products: *sepproduct, nproducts: i32, emitasm: i32,
workdir: *u8, scratchout: **u8,
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
scratchout: **u8,
graphout: **sepgraph) i32 = {
sepfatalallocation = false;
if (nproducts < 1) { return 1; };
@@ -4461,6 +4600,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let effstem: *u8 = stem;
if (objstem != nil) { effstem = objstem; };
let warm: bool = false;
let workdirexists: bool = false;
if (workdir != nil) {
if (workdir[0u64] != 0u8) { warm = true; };
};
@@ -4468,23 +4608,38 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (warm) {
let wfi: os.filestat;
let wok: bool = false;
let missing: bool = false;
match (os.stat(&wfi, pathstr(workdir))) {
case void => {
let wt: u32 = (wfi.mode: u32) & 61440u32; // S_IFMT
if (wt == os.mode.DIR: u32) { wok = true; };
if (wt == os.mode.DIR: u32) {
wok = true; workdirexists = true;
};
};
case let e: os.oserror => void;
case let e: os.oserror => {
if ((e: i64) == -2i64) { missing = true; };
};
if (!wok) {
};
if (!wok && !(createworkdir && missing)) {
cerrpath("ww: workdir ", workdir,
" is not a directory\n");
return 1;
};
if (cstrlen(workdir) + 1u64 > os.PATH_MAX: u64) {
cerr("ww: workdir path is too long\n");
return 1;
};
// The workdir is caller-owned and persistent: no acquisition,
// no refusal, and scratchout stays nil so the wrapper never
// cleans it.
scratch = workdir;
} else {
let suffix: u64 = ".sepwork".len: u64 + 1u64;
if (suffix > os.PATH_MAX: u64
|| cstrlen(effstem) > os.PATH_MAX: u64 - suffix) {
cerr("ww: scratch path is too long\n");
return 1;
};
scratch = sepappendlit(effstem, ".sepwork");
if (scratch == nil) { return 1; };
};
@@ -4500,6 +4655,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
cerrpath("ww: cannot read driver identity ", selfpath, "\n");
return 1;
};
let toolsuffix: u64 = "/.wwtool.stamp".len: u64 + 1u64;
if (toolsuffix > os.PATH_MAX: u64
|| cstrlen(scratch) > os.PATH_MAX: u64 - toolsuffix) {
cerr("ww: workdir path is too long\n");
return 1;
};
toolw = sepjoinpathlit(scratch, ".wwtool.ww");
toolc = sepjoinpathlit(scratch, ".wwtool.w6c");
toola = sepjoinpathlit(scratch, ".wwtool.w6a");
@@ -4753,11 +4914,21 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
producti += 1;
};
};
let rootpackage: bool = istest == 0
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (!g.pkg[root].failed && seprootiscommand(&g.pkg[root])
&& validatecommandoutputpath(products[producti].out) < 0) {
return 1;
};
producti += 1;
};
let rootpackage: bool = istest == 0 && nproducts == 1
&& !g.pkg[products[0].root].failed
&& !seprootiscommand(&g.pkg[products[0].root]);
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
if (warm && sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
if (warm && workdirexists
&& sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
let ci: i32 = 0;
let order: []i32;
let stack: []i32;
@@ -4836,9 +5007,30 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (!viableproduct) { return 1; };
// Source-derived resolution and contextual legality are complete before
// coordinator completion markers or persistent vouchers are changed.
let createdoutput: sepcreateddirs;
let createdwork: sepcreateddirs;
createdoutput.n = 0;
createdwork.n = 0;
if (createoutputdir != nil
&& sepmkdirsrecord(createoutputdir, 448, &createdoutput) != 0) {
cerrpath("ww: cannot create build output directory ",
createoutputdir, "\n");
return 1;
};
if (warm && !workdirexists) {
if (!createworkdir
|| sepmkdirsrecord(scratch, 448, &createdwork) != 0) {
cerrpath("ww: workdir ", scratch, " is not a directory\n");
seprollbackdirs(&createdoutput);
return 1;
};
workdirexists = true;
};
if (!warm) {
if (os.mkdir(pathstr(scratch), 493i32) != 0) {
cerr("ww: cannot create scratch\n");
cerrpath("ww: cannot create scratch ", scratch, "\n");
seprollbackdirs(&createdwork);
seprollbackdirs(&createdoutput);
return 1;
};
// The wrapper owns only the directory this invocation acquired.
@@ -5179,7 +5371,22 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
};
};
if (emitasm != 0) { if (anyfailed) { return 1; }; return 0; };
if (emitasm != 0) {
let producti: i32 = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (g.pkg[root].failed) {
anyfailed = true;
} else if (recordproductstatus(products[producti].status) != 0) {
cerr("ww: cannot record package-build product\n");
g.pkg[root].failed = true;
anyfailed = true;
};
producti += 1;
};
if (anyfailed) { return 1; };
return 0;
};
if (rootpackage) {
let root: i32 = products[0].root;
if (g.pkg[root].failed) { return 1; };
@@ -5196,6 +5403,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
return 1;
};
};
if (recordproductstatus(products[0].status) != 0) {
cerr("ww: cannot record package-build product\n");
return 1;
};
return 0;
};
@@ -5220,6 +5431,15 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
producti += 1;
continue;
};
if (istest == 0 && !seprootiscommand(&g.pkg[root])) {
if (recordproductstatus(products[producti].status) != 0) {
cerr("ww: cannot record package-build product\n");
g.pkg[root].failed = true;
anyfailed = true;
};
producti += 1;
continue;
};
ci = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let linkorder: []i32;
@@ -5369,7 +5589,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
product.support = -1;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
incs, lf, publishpackage, requirecommand, istest, &product, 1,
emitasm, workdir, &scratch, &g);
emitasm, workdir, false, nil, &scratch, &g);
sepgraphfree(g);
if (keepscratch == 0 && scratch != nil) {
if (cstrendswithlit(scratch, ".sepwork")) {
@@ -5398,20 +5618,21 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
// Build every selected directory/variant root inside one command-owned package
// universe. The first output owns the shared cold sepwork tree.
fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32) i32 = {
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32,
istest: i32, publishpackage: i32, lf: *lflags, emitasm: i32,
createworkdir: bool, createoutputdir: *u8) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
let lf: lflags;
lf.libdirs = nil; lf.nlibdirs = 0;
lf.libs = nil; lf.nlibs = 0;
let i: i32 = 0;
for (i < nproducts) {
products[i].identity = rootidentity;
i += 1;
};
let r: i32 = buildonesepimpl(selfdir, src, 1,
products[0].out, products[0].out, incs, &lf, 0, 0, 1,
products, nproducts, 0, workdir, &scratch, &g);
products[0].out, products[0].out, incs, lf,
publishpackage, 0, istest,
products, nproducts, emitasm, workdir, createworkdir,
createoutputdir, &scratch, &g);
sepgraphfree(g);
return r;
};
@@ -5507,7 +5728,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
};
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] [path] build a local package graph\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 publishes a non-main archive FILE + FILE.wwi\n lib/... every package under lib, recursively (test only)\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] [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 publishes a non-main archive FILE + FILE.wwi\n lib/... every eligible package under lib, recursively\n . build the cwd's <basename>.ww\n";
os.write(fd, s.ptr, s.len: u64);
};
@@ -5548,6 +5769,8 @@ fn defaultoutpath(src: *u8) *u8 = {
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let src: *u8 = nil;
let srcindex: i32 = -1;
let multipleroots: bool = false;
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
let workdir: *u8 = nil; // -w persistent package-artifact workdir
let emitasm: i32 = 0;
@@ -5564,18 +5787,19 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cstrseal(incs.ptr, 0u64);
let maxlflags: i32 = 32;
let libdirs: []*u8;
if (!sepmakeptrs(maxlflags, &libdirs)) { return 1; };
let libdirs: [32]*u8;
let nlibdirs: i32 = 0;
let libs: []*u8;
if (!sepmakeptrs(maxlflags, &libs)) { return 1; };
let libs: [32]*u8;
let nlibs: i32 = 0;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "-S")) {
if (cstreqlit(p, "--")) {
multipleroots = true;
break;
} else { if (cstreqlit(p, "-S")) {
emitasm = 1;
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
@@ -5632,17 +5856,18 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
libs[nlibs] = nm;
nlibs += 1;
} else { if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outflag = p + 2u64;
if (p[2u64] != 0u8) {
outflag = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -o needs an argument\n");
return 2;
};
i += 1;
outflag = argv[i];
};
} else { if (p[1u64] == 119u8) { // '-w'
outflag = argv[i];
};
if (!clipathfits("build", "-o", outflag)) { return 2; };
} else { if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
workdir = p + 2u64;
} else {
@@ -5651,14 +5876,21 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return 2;
};
i += 1;
workdir = argv[i];
};
workdir = argv[i];
};
if (!clipathfits("build", "-w", workdir)) { return 2; };
} else {
cerr("ww build: unknown flag\n");
return 2;
}; }; }; }; }; };
}; }; }; }; }; }; };
} else {
if (src == nil) { src = p; };
if (src == nil) {
src = p;
srcindex = i;
i += 1;
if (i < argc) { multipleroots = true; };
break;
} else { multipleroots = true; break; };
};
i += 1;
};
@@ -5667,6 +5899,10 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
if (multipleroots || strings.contains(pathstr(src), "...")) {
return execpackagetests(selfdir, argv, argc, start, srcindex,
nil, nil, false, true);
};
let requestedliteral: bool = false;
let requestedstat: os.filestat;
match (os.stat(&requestedstat, pathstr(src))) {
@@ -5704,9 +5940,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
out = defaultoutpath(resolved);
}; };
let lf: lflags;
lf.libdirs = libdirs.ptr;
lf.libdirs = &libdirs[0];
lf.nlibdirs = nlibdirs;
lf.libs = libs.ptr;
lf.libs = &libs[0];
lf.nlibs = nlibs;
let rootidentity: *u8 = nil;
if (!requestedliteral && isdir != 0) { rootidentity = src; };
@@ -6085,6 +6321,18 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let requestidentity: *u8 = nil;
let products: []sepproduct;
let packageopts: bool = false;
let packagebuild: bool = false;
let packagepublish: bool = false;
let packagecreateworkdir: bool = false;
let packagecreateoutputdir: *u8 = nil;
let maxpackagelflags: i32 = 32;
let packagelibdirs: [32]*u8;
let packagelibs: [32]*u8;
let packagelinks: lflags;
packagelinks.libdirs = &packagelibdirs[0];
packagelinks.nlibdirs = 0;
packagelinks.libs = &packagelibs[0];
packagelinks.nlibs = 0;
let afterdash: bool = false;
let i: i32 = start;
for (i < argc) {
@@ -6105,6 +6353,44 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
i += 2;
continue;
};
if (cstreqlit(p, "--ww-package-build")) {
if (packagebuild) {
cerr("ww test: invalid --ww-package-build\n");
return 2;
};
packagebuild = true;
compileonly = 1;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-package-publish")) {
if (packagepublish) {
cerr("ww test: invalid --ww-package-publish\n");
return 2;
};
packagepublish = true;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-create-workdir")) {
if (packagecreateworkdir) {
cerr("ww test: invalid --ww-create-workdir\n");
return 2;
};
packagecreateworkdir = true;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-create-output-dir")) {
if (i + 1 >= argc || packagecreateoutputdir != nil
|| argv[i + 1][0u64] == 0u8) {
cerr("ww test: invalid --ww-create-output-dir\n");
return 2;
};
packagecreateoutputdir = argv[i + 1];
i += 2;
continue;
};
if (cstreqlit(p, "--ww-package-test")) {
if (i + 5 >= argc) {
cerr("ww test: --ww-package-test needs kind, package, directory, output, and status\n");
@@ -6178,9 +6464,49 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
if (cstreqlit(p, "-c")) { compileonly = 1; i += 1; continue; };
if (cstreqlit(p, "-S")) { emitasm = 1; i += 1; continue; };
if (cstreqlit(p, "-list")) {
if (!packagebuild && cstreqlit(p, "-list")) {
packageopts = true; i += 1; continue;
};
if (packagebuild && p[1u64] == 76u8) { // '-L'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -L needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (packagelinks.nlibdirs >= maxpackagelflags) {
cerr("ww test: too many -L\n");
return 2;
};
packagelibdirs[packagelinks.nlibdirs] = dir;
packagelinks.nlibdirs += 1;
i += 1; continue;
};
if (packagebuild && p[1u64] == 108u8) { // '-l'
let name: *u8 = nil;
if (p[2u64] != 0u8) {
name = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -l needs an argument\n");
return 2;
};
i += 1;
name = argv[i];
};
if (packagelinks.nlibs >= maxpackagelflags) {
cerr("ww test: too many -l\n");
return 2;
};
packagelibs[packagelinks.nlibs] = name;
packagelinks.nlibs += 1;
i += 1; continue;
};
if (cstreqlit(p, "-j") || cstreqlit(p, "-run")
|| cstreqlit(p, "-filter")) {
if (i + 1 >= argc) {
@@ -6203,9 +6529,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return 2;
};
i += 1;
outstem = argv[i];
};
i += 1; continue;
outstem = argv[i];
};
if (!clipathfits("test", "-o", outstem)) { return 2; };
i += 1; continue;
};
if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
@@ -6216,8 +6543,9 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return 2;
};
i += 1;
workdir = argv[i];
};
workdir = argv[i];
};
if (!clipathfits("test", "-w", workdir)) { return 2; };
i += 1; continue;
};
cerr("ww test: unknown flag\n"); return 2;
@@ -6231,7 +6559,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let dot: [2]u8 = ['.': u8, 0u8];
target = &dot[0];
};
if (emitasm != 0 && outstem == nil) {
if (emitasm != 0 && outstem == nil && products.len == 0) {
cerr("ww test: -S needs -o\n");
return 2;
};
@@ -6267,22 +6595,82 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: package-test variant rejects package options\n");
return 2;
};
if (packagebuild && products.len == 0) {
cerr("ww test: --ww-package-build needs products\n");
return 2;
};
if (packagepublish && (!packagebuild || products.len != 1)) {
cerr("ww test: invalid --ww-package-publish\n");
return 2;
};
if (packagecreateoutputdir != nil && !packagebuild) {
cerr("ww test: invalid private directory creation\n");
return 2;
};
if (packagecreateworkdir && products.len == 0) {
cerr("ww test: invalid private directory creation\n");
return 2;
};
if (packagecreateworkdir && workdir == nil) {
cerr("ww test: --ww-create-workdir needs -w\n");
return 2;
};
if (!packagebuild && (packagelinks.nlibdirs != 0
|| packagelinks.nlibs != 0)) {
cerr("ww test: unknown flag\n");
return 2;
};
if (packagebuild) {
producti = 0;
for (producti < products.len) {
if (products[producti].variant != SEP_VARIANT_PRODUCTION) {
cerr("ww test: package-build products must be production\n");
return 2;
};
producti += 1;
};
};
if (patarg != nil) {
let first: os.filestat;
let firstregular: bool = false;
let firstfound: bool = false;
match (os.stat(&first, pathstr(target))) {
case void => {
firstfound = true;
firstregular = (((first.mode: u32) & 61440u32)
== (os.mode.REG: u32));
};
case let e: os.oserror => void;
};
if (!firstregular) {
let firstresolved: *u8 = nil;
let firstisdir: i32 = 0;
if (!firstfound) {
firstresolved = resolvemodule(selfdir, target, incs.ptr,
&firstisdir);
};
if (firstfound || firstresolved == nil || firstisdir != 0) {
let replacement: *u8 = nil;
let identity: *u8 = requestidentity;
if (firstisdir != 0) {
replacement = firstresolved;
identity = target;
};
return execpackagetests(selfdir, argv, argc, start,
targetindex, replacement, identity, false, false);
};
// A logical import resolving to one source retains patarg as its
// historical test-name filter and follows the single-file path.
};
};
if (products.len != 0 && outstem != nil) {
cerr("ww test: package-test products reject -o\n");
return 2;
};
// Go's ./... form: a trailing "..." element is a package-tree
// request for the coordinator, never a literal path — recognized
// before stat, with the directory-mode rejects.
let tlen: u64 = cstrlen(target);
let istree: bool = cstreqlit(target, "...");
if (!istree && tlen >= 4u64) {
istree = target[tlen - 4u64] == '/'
&& target[tlen - 3u64] == '.'
&& target[tlen - 2u64] == '.'
&& target[tlen - 1u64] == '.';
};
// A local spelling containing "..." enters request selection before
// literal-path or logical-import resolution.
let istree: bool = strings.contains(pathstr(target), "...");
if (istree) {
if (products.len != 0) {
cerr("ww test: package-test variant needs one directory\n");
@@ -6298,14 +6686,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: -o needs -c for a package target\n");
return 2;
};
if (patarg != nil) {
cerr("ww test: pattern needs a single test file\n");
return 2;
};
// -w forwards: the coordinator keys one persistent driver workdir
// for the complete selected test request.
// -w forwards one caller-owned semantic-action store shared by
// the complete selected package universe.
return execpackagetests(selfdir, argv, argc, start,
targetindex, nil, nil, false);
targetindex, nil, nil, false, false);
};
let resolved: *u8 = target;
@@ -6345,7 +6729,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
emitasm, outstem, workdir, patarg);
};
if (emitasm != 0) {
if (emitasm != 0 && products.len == 0) {
cerr("ww test: -S needs a single test file\n");
return 2;
};
@@ -6353,23 +6737,25 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: -o needs -c for a package target\n");
return 2;
};
if (patarg != nil) {
cerr("ww test: pattern needs a single test file\n");
return 2;
};
if (products.len != 0) {
if (compileonly == 0) {
cerr("ww test: package-test products need -c\n");
return 2;
};
let packagemode: i32 = 1;
if (packagebuild) { packagemode = 0; };
let publishmode: i32 = 0;
if (packagepublish) { publishmode = 1; };
return buildpackagetests(selfdir, resolved, rootidentity,
incs.ptr, workdir,
products.ptr, products.len);
products.ptr, products.len, packagemode, publishmode,
&packagelinks, emitasm, packagecreateworkdir,
packagecreateoutputdir);
};
let replacement: *u8 = nil;
if (resolved != target) { replacement = resolved; };
return execpackagetests(selfdir, argv, argc, start, targetindex,
replacement, rootidentity, targetindex < 0);
replacement, rootidentity, targetindex < 0, false);
};
export fn main(argc: i32, argv: **u8) i32 = {