ww: recursive DIR/... package discovery for ww test

This commit is contained in:
2026-08-08 13:35:34 +09:00
parent 81e7f95548
commit 0f0fd36563
5 changed files with 193 additions and 10 deletions

View File

@@ -88,7 +88,7 @@ fn pkgfailpath(path: str, reason: str) void = {
fn pkgusage() void = {
let s: str = strings.concat(
"usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR] [-- GLOB ...]\n",
"usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/...] [-- GLOB ...]\n",
" *_test.ww is canonical; noncanonical files require an actual @test declaration\n",
" -c retains the compiled package binaries; -j is reserved by sequential v1\n");
pkgput(os.STDERR_FILENO, s);
@@ -240,10 +240,12 @@ fn pkgkeepfile(name: str) bool = {
return true;
};
// The first vertical slice owns exactly one explicit package directory. It
// never recurses, and lstat is used both for the root and each candidate so a
// symlink cannot be used to cross that boundary (including DT_UNKNOWN files).
fn pkgdiscoverdir(path: str, st: *pkgdiscover) void = {
// One explicit package directory; with recurse (the DIR/... form) every
// subdirectory whose name does not begin with '.' or '_' is descended as
// well, Go's ./... convention. lstat is used for the root and each
// candidate so a symlink cannot be used to cross the boundary (including
// DT_UNKNOWN files); a symlinked subdirectory is skipped, not followed.
fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
let rootstat: os.filestat;
match (os.lstat(&rootstat, path)) {
case void => void;
@@ -307,6 +309,22 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover) void = {
st.errors += 1;
};
};
} else if (recurse && name.len != 0
&& name[0] != '.' && name[0] != '_') {
let child: str = strings.concat(path, "/", name);
let fi: os.filestat;
match (os.lstat(&fi, child)) {
case void => {
if (pkgmodeis(fi.mode, os.mode.DIR)) {
pkgdiscoverdir(child, st, true);
};
};
case let e: os.oserror => {
pkgfailpath(child,
"cannot stat directory entry");
st.errors += 1;
};
};
};
off += reclen;
};
@@ -690,15 +708,28 @@ export fn packagecommand(args: []str) int = {
"wwtest package: -j is reserved; sequential package scheduling is active");
};
// Go's ./... form: a trailing "..." path element walks the tree
// rooted at the prefix instead of one explicit directory.
let discoverroot: str = roots[0];
let recurse: bool = false;
if (strings.compare(discoverroot, "...") == 0) {
discoverroot = ".";
recurse = true;
} else if (strings.hassuffix(discoverroot, "/...")) {
discoverroot = discoverroot[0:discoverroot.len - 4];
if (discoverroot.len == 0) { discoverroot = "/"; };
recurse = true;
};
let ds: pkgdiscover;
let discoveredpaths: []str = alloc([], 64u64)!;
ds.paths = discoveredpaths;
ds.errors = 0;
pkgdiscoverdir(roots[0], &ds);
pkgdiscoverdir(discoverroot, &ds, recurse);
if (ds.errors != 0) { return 1; };
pkgsort(ds.paths);
if (ds.paths.len == 0) {
pkgfailpath(roots[0], "directory contains no WW package sources");
pkgfailpath(discoverroot, "directory contains no WW package sources");
return 1;
};