package wwpackage; import os; import os.exec; import strconv; import strings; import temp; import time; type pkgsource = struct { path: str, dir: str, pkg: str, test: bool, }; type pkgfolder = struct { path: str, start: i32, end: i32, prodpkg: str, }; type pkggroup = struct { dir: str, pkg: str, external: bool, plan: i32, root: str, bin: str, buildok: str, runout: str, runerr: str, state: i32, runres: exec.result, }; type pkgplan = struct { dir: str, root: str, workdir: str, buildout: str, builderr: str, start: i32, end: i32, state: i32, buildres: exec.result, }; // Product and directory-plan process states for the bounded coordinator. def PKGQUEUED: i32 = 0; def PKGBUILDING: i32 = 1; def PKGRUNNING: i32 = 2; def PKGDONE: i32 = 3; def pkgpoll: time.duration = 1000000i64: time.duration; type pkgdiscover = struct { paths: []str, errors: i32, }; // Preserve the caller's toolchain environment while pinning the locale and // temporary directory used by the current build plan or test product. fn toolenv(tmpdir: str) []str = { let inherited: []str = os.getenvs(); let env: []str = alloc([], (inherited.len + 2): u64)!; let i: i32 = 0; for (i < inherited.len) { if (!strings.hasprefix(inherited[i], "TMPDIR=") && !strings.hasprefix(inherited[i], "LC_ALL=")) { append(env, inherited[i]); }; i += 1; }; append(env, "LC_ALL=C"); append(env, strings.concat("TMPDIR=", tmpdir)); return env; }; fn pkgwrite(fd: i32, s: str) bool = { let r: (i64 | os.oserror) = os.writeall(fd, s.ptr, s.len: u64); match (r) { case let n: i64 => return n == s.len: i64; case let e: os.oserror => return false; }; }; fn pkgput(fd: i32, s: str) void = { pkgwrite(fd, s); }; fn pkgputln(fd: i32, s: str) void = { pkgput(fd, s); pkgput(fd, "\n"); }; fn pkgfailpath(path: str, reason: str) void = { pkgput(os.STDERR_FILENO, "wwtest package: "); pkgput(os.STDERR_FILENO, path); pkgput(os.STDERR_FILENO, ": "); pkgputln(os.STDERR_FILENO, reason); }; fn pkgusage() void = { let s: str = strings.concat( "usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-w DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/...] [-- GLOB ...]\n", " *_test.ww is the sole test-source form; @test elsewhere is rejected\n", " -c retains the compiled package binaries; -j N runs up to N build or test processes at once\n", " -w DIR keys one persistent shared build workdir for the test request\n"); pkgput(os.STDERR_FILENO, s); }; fn pkgread(path: str, out: *str) bool = { let fd: i32 = os.open(path, os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; let sr: (i64 | os.oserror) = os.filesize(fd); let n: i64 = -1i64; match (sr) { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return false; }; }; if (n < 0i64) { os.close(fd); return false; }; let b: []u8 = alloc([], (n + 1i64): u64)!; b.len = (n + 1i64): i32; let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64); os.close(fd); let got: i64 = -1i64; match (rr) { case let v: i64 => got = v; case let e: os.oserror => return false; }; if (got != n) { return false; }; let ni: i32 = n: i32; b[ni] = 0u8; out.ptr = b.ptr; out.len = n: i32; return true; }; fn pkgidentfirst(c: u8) bool = { if (c >= 'a' && c <= 'z') { return true; }; if (c >= 'A' && c <= 'Z') { return true; }; return c == '_'; }; fn pkgident(c: u8) bool = { if (pkgidentfirst(c)) { return true; }; return c >= '0' && c <= '9'; }; fn pkgskipspace(src: str, start: i32) i32 = { let i: i32 = start; for (i < src.len) { let c: u8 = src[i]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { i += 1; continue; }; if (c == '/' && i + 1 < src.len && src[i + 1] == '/') { i += 2; for (i < src.len && src[i] != '\n') { i += 1; }; continue; }; // The driver's sep_skip_space (cmd/ww/main.c) also skips // /* */ before the package clause; without this arm a source // opening with a block comment built under ww but failed // coordinator discovery ("invalid or missing package // clause"). An unterminated comment runs to EOF and the // clause parse fails loud. if (c == '/' && i + 1 < src.len && src[i + 1] == '*') { i += 2; for (i + 1 < src.len && !(src[i] == '*' && src[i + 1] == '/')) { i += 1; }; if (i + 1 >= src.len) { return src.len; }; i += 2; continue; }; break; }; return i; }; fn pkgclause(src: str, out: *str) bool = { let i: i32 = pkgskipspace(src, 0); let word: str = "package"; if (i + word.len >= src.len) { return false; }; let j: i32 = 0; for (j < word.len) { if (src[i + j] != word[j]) { return false; }; j += 1; }; i += word.len; if (i >= src.len) { return false; }; if (!(src[i] == ' ' || src[i] == '\t' || src[i] == '\n' || src[i] == '\r')) { return false; }; i = pkgskipspace(src, i); if (i >= src.len || !pkgidentfirst(src[i])) { return false; }; let begin: i32 = i; i += 1; for (i < src.len && pkgident(src[i])) { i += 1; }; let end: i32 = i; i = pkgskipspace(src, i); if (i >= src.len || src[i] != ';') { return false; }; out.ptr = src.ptr + (begin: u64); out.len = end - begin; return true; }; fn pkgdirname(path: str) str = { let i: i32 = path.len - 1; for (i >= 0) { if (path[i] == '/') { if (i == 0) { return "/"; }; let r: str; r.ptr = path.ptr; r.len = i; return r; }; i -= 1; }; return "."; }; fn pkgbase(path: str) str = { let i: i32 = path.len - 1; for (i >= 0) { if (path[i] == '/') { let r: str; r.ptr = path.ptr + ((i + 1): u64); r.len = path.len - i - 1; return r; }; i -= 1; }; return path; }; fn pkgmodeis(m: os.mode, want: os.mode) bool = { return (((m: u32) & 61440u32) == (want: u32)); }; fn pkgisdir(path: str) bool = { let fi: os.filestat; match (os.lstat(&fi, path)) { case void => return pkgmodeis(fi.mode, os.mode.DIR); case let e: os.oserror => return false; }; }; fn pkgisreg(path: str) bool = { let fi: os.filestat; match (os.lstat(&fi, path)) { case void => return pkgmodeis(fi.mode, os.mode.REG); case let e: os.oserror => return false; }; }; fn pkgkeepfile(name: str) bool = { if (!strings.hassuffix(name, ".ww")) { return false; }; return true; }; // 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; case let e: os.oserror => { pkgfailpath(path, "cannot stat package directory"); st.errors += 1; return; }; }; if (pkgmodeis(rootstat.mode, os.mode.LINK)) { pkgfailpath(path, "symlink traversal is not allowed"); st.errors += 1; return; }; if (!pkgmodeis(rootstat.mode, os.mode.DIR)) { pkgfailpath(path, "expected one package directory"); st.errors += 1; return; }; let fd: i32 = os.open(path, os.flag.RDONLY, 0i32); if (fd < 0) { pkgfailpath(path, "cannot open directory"); st.errors += 1; return; }; let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let n: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (n > 0i64) { let off: u64 = 0u64; for (off < n: u64) { let reclen: u64 = (buf[off + 16u64]: u64) + (buf[off + 17u64]: u64) * 256u64; if (reclen < 20u64 || off + reclen > n: u64) { pkgfailpath(path, "malformed directory entry"); st.errors += 1; off = n: u64; continue; }; let np: *u8 = buf.ptr + off + 19u64; let nl: i32 = 0; for (nl: u64 + 19u64 < reclen && np[nl] != 0u8) { nl += 1; }; let name: str; name.ptr = np; name.len = nl; if (pkgkeepfile(name)) { let child: str = strings.concat(path, "/", name); let fi: os.filestat; match (os.lstat(&fi, child)) { case void => { if (pkgmodeis(fi.mode, os.mode.LINK)) { pkgfailpath(child, "symlink source is not allowed"); st.errors += 1; } else if (pkgmodeis(fi.mode, os.mode.REG)) { append(st.paths, child); }; }; case let e: os.oserror => { pkgfailpath(child, "cannot stat source"); 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; }; n = os.getdents64(fd, buf.ptr, 8192u64); }; if (n < 0i64) { pkgfailpath(path, "directory read failed"); st.errors += 1; }; os.close(fd); }; // Group paths by containing directory before filename order. A plain full-path // sort can interleave a child directory between two files in its parent and // split one package into multiple folder records during recursive discovery. fn pkgsort(ss: []str) void = { let i: i32 = 1; for (i < ss.len) { let j: i32 = i; for (j > 0) { let a: str = pkgdirname(ss[j - 1]); let b: str = pkgdirname(ss[j]); let c: int = strings.compare(a, b); if (c == 0) { c = strings.compare(ss[j - 1], ss[j]); }; if (c <= 0) { break; }; let t: str = ss[j]; ss[j] = ss[j - 1]; ss[j - 1] = t; j -= 1; }; i += 1; }; }; fn pkgsortgroups(gs: []pkggroup) void = { let i: i32 = 1; for (i < gs.len) { let j: i32 = i; for (j > 0) { let c: int = strings.compare(gs[j - 1].dir, gs[j].dir); if (c == 0) { c = strings.compare(gs[j - 1].pkg, gs[j].pkg); }; if (c <= 0) { break; }; let t: pkggroup = gs[j]; gs[j] = gs[j - 1]; gs[j - 1] = t; j -= 1; }; i += 1; }; }; fn pkgdedup(ss: []str) []str = { if (ss.len == 0) { return ss; }; let out: []str = alloc([], ss.len: u64)!; let i: i32 = 0; for (i < ss.len) { if (i == 0 || strings.compare(ss[i - 1], ss[i]) != 0) { append(out, ss[i]); }; i += 1; }; return out; }; fn pkgparsedec(s: str, max: i64) i64 = { if (s.len == 0) { return -1i64; }; let i: i32 = 0; let n: i64 = 0i64; for (i < s.len) { if (s[i] < '0' || s[i] > '9') { return -1i64; }; let digit: i64 = (s[i] - '0'): i64; if (n > (max - digit) / 10i64) { return -1i64; }; n = n * 10i64 + digit; i += 1; }; return n; }; fn pkgdefaultbuilder() str = { let av: []str = os.args(); if (av.len == 0 || av[0].len == 0) { return "ww"; }; return strings.concat(pkgdirname(av[0]), "/ww"); }; fn pkgmakedir(path: str) bool = { return os.mkdir(path, 448i32) == 0; }; // The coordinator removes only its minted temp root; validating the opened // inode keeps a replaced path from carrying cleanup outside that ownership. fn pkgremoveall(path: str) bool = { let st: os.filestat; match (os.lstat(&st, path)) { case void => void; case let e: os.oserror => return false; }; if (!pkgmodeis(st.mode, os.mode.DIR)) { return os.remove(path) == 0; }; let fd: i32 = os.open(path, os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; let opened: os.filestat; match (os.fstat(&opened, fd)) { case void => void; case let e: os.oserror => { os.close(fd); return false; }; }; if (!pkgmodeis(opened.mode, os.mode.DIR) || opened.inode != st.inode) { os.close(fd); return false; }; let ok: bool = true; let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let n: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (n > 0i64) { let off: u64 = 0u64; for (off < n: u64) { let reclen: u64 = (buf[off + 16u64]: u64) + (buf[off + 17u64]: u64) * 256u64; if (reclen < 20u64 || off + reclen > n: u64) { ok = false; off = n: u64; continue; }; let np: *u8 = buf.ptr + off + 19u64; let nl: i32 = 0; for (nl: u64 + 19u64 < reclen && np[nl] != 0u8) { nl += 1; }; let name: str; name.ptr = np; name.len = nl; if (!(strings.compare(name, ".") == 0 || strings.compare(name, "..") == 0)) { let child: str = strings.concat(path, "/", name); let dtype: u8 = buf[off + 18u64]; if (dtype == 4u8 || (dtype == 0u8 && pkgisdir(child))) { if (!pkgremoveall(child)) { ok = false; }; } else if (os.remove(child) != 0) { ok = false; }; }; off += reclen; }; n = os.getdents64(fd, buf.ptr, 8192u64); }; if (n < 0i64) { ok = false; }; if (os.close(fd) != 0) { ok = false; }; if (os.rmdir(path) != 0) { ok = false; }; return ok; }; fn pkgemitfile(path: str, fd: i32) bool = { let s: str; if (!pkgread(path, &s)) { return false; }; if (s.len != 0) { pkgput(fd, s); if (s[s.len - 1] != '\n') { pkgput(fd, "\n"); }; }; return true; }; fn pkgworkescape(s: str) str = { let out: []u8 = alloc([], (s.len * 2 + 1): u64)!; let i: i32 = 0; for (i < s.len) { if (s[i] == '_') { append(out, '_'); append(out, 'u'); } else { if (s[i] == '/') { append(out, '_'); append(out, 's'); } else { append(out, s[i]); }; }; i += 1; }; return strings.frombytes(out); }; fn pkgworkkey(dir: str, pkg: str) str = { return strings.concat("d_", pkgworkescape(dir), "_p_", pkgworkescape(pkg)); }; fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32, compileonly: bool, outname: str, workroot: str) bool = { let num: str = strconv.i32tos(index, strconv.base.DEC); p.root = strings.concat(root, "/plan-", num); if (!pkgmakedir(p.root)) { return false; }; p.workdir = ""; if (workroot.len != 0) { // The request root and first byte-sorted product name make one stable // escaped key for the command-scoped driver workdir. p.workdir = strings.concat(workroot, "/", pkgworkkey(p.dir, groups[p.start].pkg)); match (os.mkdirs(p.workdir, 448)) { case void => void; case let e: os.oserror => return false; }; }; p.buildout = strings.concat(p.root, "/build.stdout"); p.builderr = strings.concat(p.root, "/build.stderr"); let i: i32 = p.start; for (i < p.end) { let g: *pkggroup = &groups[i]; g.plan = index; g.root = strings.concat(p.root, "/product-", strconv.i32tos(i - p.start, strconv.base.DEC)); if (!pkgmakedir(g.root)) { return false; }; if (compileonly) { if (outname.len != 0) { g.bin = outname; } else { g.bin = strings.concat(g.dir, "/", g.pkg, ".test"); }; } else { g.bin = strings.concat(g.root, "/package.test"); }; g.runout = strings.concat(g.root, "/test.stdout"); g.runerr = strings.concat(g.root, "/test.stderr"); g.buildok = strings.concat(g.root, "/build.ok"); i += 1; }; return true; }; fn pkglabel(g: *pkggroup) void = { pkgput(os.STDOUT_FILENO, g.dir); pkgput(os.STDOUT_FILENO, " ["); pkgput(os.STDOUT_FILENO, g.pkg); if (g.external) { pkgput(os.STDOUT_FILENO, ", external"); } else { pkgput(os.STDOUT_FILENO, ", same-package"); }; pkgput(os.STDOUT_FILENO, "]"); }; fn pkgreportcommand(kind: str, g: *pkggroup, r: *exec.result) void = { pkgput(os.STDERR_FILENO, "FAIL "); pkgput(os.STDERR_FILENO, g.dir); pkgput(os.STDERR_FILENO, " ["); pkgput(os.STDERR_FILENO, g.pkg); pkgput(os.STDERR_FILENO, "] ("); pkgput(os.STDERR_FILENO, kind); if (r.errno != 0 || r.cleanuperrno != 0 || r.termination == exec.termination.ERROR) { pkgput(os.STDERR_FILENO, " harness error "); let code: i32 = r.errno; if (code == 0) { code = r.cleanuperrno; }; pkgput(os.STDERR_FILENO, strconv.i32tos(code, strconv.base.DEC)); } else if (r.termination == exec.termination.SIGNAL) { pkgput(os.STDERR_FILENO, " signal "); pkgput(os.STDERR_FILENO, strconv.i32tos(r.code, strconv.base.DEC)); } else { pkgput(os.STDERR_FILENO, " exit "); pkgput(os.STDERR_FILENO, strconv.i32tos(r.code, strconv.base.DEC)); }; pkgputln(os.STDERR_FILENO, ")"); }; fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, h: *exec.process) void = { let ba: []str = alloc([], (8 + (p.end - p.start) * 6 + includes.len * 2): u64)!; append(ba, builder); append(ba, "test"); append(ba, "-c"); let i: i32 = p.start; for (i < p.end) { let g: *pkggroup = &groups[i]; append(ba, "--ww-package-test"); if (g.external) { append(ba, "external"); } else { append(ba, "same"); }; append(ba, g.pkg); append(ba, g.dir); append(ba, g.bin); append(ba, g.buildok); i += 1; }; let ii: i32 = 0; for (ii < includes.len) { append(ba, "-I"); append(ba, includes[ii]); ii += 1; }; if (p.workdir.len != 0) { append(ba, "-w"); append(ba, p.workdir); }; append(ba, p.dir); let bcmd: exec.command; bcmd.path = builder; bcmd.argv = ba; bcmd.env = toolenv(p.root); bcmd.dir = ""; bcmd.stdoutpath = p.buildout; bcmd.stderrpath = p.builderr; bcmd.deadline.sec = 0i64; bcmd.deadline.nsec = 0i64; bcmd.grace = 0i64: time.duration; exec.start(h, &bcmd); }; fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str, list: bool, h: *exec.process) void = { let ra: []str = alloc([], (filters.len + 4): u64)!; append(ra, g.bin); append(ra, strings.concat("-package=", g.pkg)); if (list) { append(ra, "-list"); }; if (timeoutarg.len != 0) { append(ra, timeoutarg); }; let i: i32 = 0; for (i < filters.len) { append(ra, filters[i]); i += 1; }; let rcmd: exec.command; rcmd.path = g.bin; rcmd.argv = ra; rcmd.env = toolenv(g.root); rcmd.dir = ""; rcmd.stdoutpath = g.runout; rcmd.stderrpath = g.runerr; rcmd.deadline.sec = 0i64; rcmd.deadline.nsec = 0i64; rcmd.grace = 0i64: time.duration; exec.start(h, &rcmd); }; fn pkgproductbuilt(g: *pkggroup) bool = { return pkgisreg(g.buildok) && pkgisreg(g.bin); }; fn pkgrunok(g: *pkggroup) bool = { return g.runres.errno == 0 && g.runres.cleanuperrno == 0 && g.runres.termination == exec.termination.EXIT && g.runres.code == 0; }; fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = { if (compileonly) { pkgput(os.STDOUT_FILENO, "built "); pkglabel(g); pkgput(os.STDOUT_FILENO, " -> "); pkgputln(os.STDOUT_FILENO, g.bin); return true; }; let runstdout: str; let runstderr: str; if (!pkgread(g.runout, &runstdout) || !pkgread(g.runerr, &runstderr)) { pkgfailpath(g.root, "cannot read test capture"); return false; }; // The compiler-owned dispatcher is the authority on whether a variant // contains runnable tests. With an empty generated table, test.run returns // zero without output; no coordinator-side source scan is involved. if (pkgrunok(g) && runstdout.len == 0 && runstderr.len == 0) { pkgput(os.STDOUT_FILENO, "? "); pkgput(os.STDOUT_FILENO, g.dir); pkgputln(os.STDOUT_FILENO, " [no tests]"); return true; }; pkgput(os.STDOUT_FILENO, runstdout); pkgput(os.STDERR_FILENO, runstderr); if (!pkgrunok(g)) { pkgreportcommand("test", g, &g.runres); return false; }; pkgput(os.STDOUT_FILENO, "ok "); pkglabel(g); pkgput(os.STDOUT_FILENO, "\n"); return true; }; // A directory build capture is emitted once, followed by its independently // executed products in their existing byte-sorted group order. fn pkgemitplan(p: *pkgplan, groups: []pkggroup, compileonly: bool) i32 = { let buildcaptures: bool = pkgemitfile(p.buildout, os.STDOUT_FILENO); buildcaptures = pkgemitfile(p.builderr, os.STDERR_FILENO) && buildcaptures; if (!buildcaptures) { pkgfailpath(p.root, "cannot read build capture"); return 1; }; let failed: i32 = 0; let i: i32 = p.start; for (i < p.end) { if (!pkgproductbuilt(&groups[i])) { pkgreportcommand("build", &groups[i], &p.buildres); failed += 1; } else if (!pkgemitgroup(&groups[i], compileonly)) { failed += 1; }; i += 1; }; return failed; }; export fn packagecommand(args: []str) int = { let compileonly: bool = false; let list: bool = false; let jobs: i32 = 1; let afterdash: bool = false; let roots: []str = alloc([], 2u64)!; let filters: []str = alloc([], (args.len + 1): u64)!; let includes: []str = alloc([], (args.len + 1): u64)!; let timeoutarg: str = ""; let outname: str = ""; let workroot: str = ""; let builder: str = pkgdefaultbuilder(); let i: i32 = 0; for (i < args.len) { let a: str = args[i]; if (afterdash) { append(filters, a); i += 1; continue; }; if (strings.compare(a, "--") == 0) { afterdash = true; i += 1; continue; }; if (strings.compare(a, "-c") == 0) { compileonly = true; i += 1; continue; }; if (strings.compare(a, "-list") == 0) { list = true; i += 1; continue; }; if (strings.compare(a, "-I") == 0) { if (i + 1 >= args.len) { pkgusage(); return 2; }; append(includes, args[i + 1]); i += 2; continue; }; if (strings.hasprefix(a, "-I") && a.len > 2) { append(includes, a[2:a.len]); i += 1; continue; }; if (strings.compare(a, "-o") == 0) { if (i + 1 >= args.len) { pkgusage(); return 2; }; outname = args[i + 1]; i += 2; continue; }; if (strings.hasprefix(a, "-o") && a.len > 2) { outname = a[2:a.len]; i += 1; continue; }; if (strings.compare(a, "-w") == 0) { if (i + 1 >= args.len) { pkgusage(); return 2; }; workroot = args[i + 1]; i += 2; continue; }; if (strings.hasprefix(a, "-w") && a.len > 2) { workroot = a[2:a.len]; i += 1; continue; }; if (strings.hasprefix(a, "-timeout-ms=")) { if (timeoutarg.len != 0 || a.len == 12 || pkgparsedec(a[12:a.len], 3600000i64) <= 0i64) { pkgusage(); return 2; }; timeoutarg = a; i += 1; continue; }; if (strings.compare(a, "-j") == 0) { if (i + 1 >= args.len) { pkgusage(); return 2; }; let v: i64 = pkgparsedec(args[i + 1], 2147483647i64); if (v <= 0i64) { pkgusage(); return 2; }; jobs = v: i32; i += 2; continue; }; if (strings.compare(a, "-filter") == 0 || strings.compare(a, "-run") == 0) { if (i + 1 >= args.len) { pkgusage(); return 2; }; append(filters, args[i + 1]); i += 2; continue; }; if (strings.compare(a, "--ww-driver") == 0) { if (i + 1 >= args.len || args[i + 1].len == 0) { pkgusage(); return 2; }; builder = args[i + 1]; i += 2; continue; }; if (a.len != 0 && a[0] == '-') { pkgusage(); return 2; }; append(roots, a); i += 1; }; if (roots.len == 0) { append(roots, "."); }; if (roots.len != 1) { pkgputln(os.STDERR_FILENO, "wwtest package: exactly one package directory is supported"); return 2; }; if (compileonly && (list || filters.len != 0 || timeoutarg.len != 0)) { pkgusage(); return 2; }; // -o names the -c artifact; a plain run always executes from the // temp root, so a caller-owned name has nothing to name. if (outname.len != 0 && !compileonly) { pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c"); return 2; }; // -c publishes caller-owned sepwork artifacts; mixing that // contract with a persistent workdir is unwired — reject rather // than guess which tree the caller owns. if (workroot.len != 0 && compileonly) { pkgputln(os.STDERR_FILENO, "wwtest package: -w conflicts with -c"); return 2; }; // 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(discoverroot, &ds, recurse); if (ds.errors != 0) { return 1; }; pkgsort(ds.paths); if (ds.paths.len == 0) { pkgfailpath(discoverroot, "directory contains no WW package sources"); return 1; }; let srcs: []pkgsource = alloc([], ds.paths.len: u64)!; i = 0; for (i < ds.paths.len) { let body: str; let pn: str; if (!pkgread(ds.paths[i], &body) || !pkgclause(body, &pn)) { pkgfailpath(ds.paths[i], "invalid or missing package clause"); return 1; }; let s: pkgsource; s.path = ds.paths[i]; s.dir = strings.dup(pkgdirname(ds.paths[i])); s.pkg = strings.dup(pn); s.test = strings.hassuffix(pkgbase(ds.paths[i]), "_test.ww"); append(srcs, s); i += 1; }; let folders: []pkgfolder = alloc([], srcs.len: u64)!; i = 0; for (i < srcs.len) { let f: pkgfolder; f.path = srcs[i].dir; f.start = i; f.end = i; f.prodpkg = ""; for (f.end < srcs.len && strings.compare(srcs[f.end].dir, f.path) == 0) { if (!srcs[f.end].test && f.prodpkg.len == 0) { f.prodpkg = srcs[f.end].pkg; }; f.end += 1; }; append(folders, f); i = f.end; }; let groups: []pkggroup = alloc([], srcs.len: u64)!; i = 0; for (i < folders.len) { let f: pkgfolder = folders[i]; let externalpkg: str = ""; if (f.prodpkg.len != 0) { externalpkg = strings.concat(f.prodpkg, "_test"); }; let standalonepkg: str = ""; let sawtestfile: bool = false; let j: i32 = f.start; for (j < f.end) { if (srcs[j].test) { sawtestfile = true; if (f.prodpkg.len != 0 && strings.compare(srcs[j].pkg, f.prodpkg) != 0 && strings.compare(srcs[j].pkg, externalpkg) != 0) { pkgfailpath(srcs[j].path, "test package must match production package or _test"); return 1; }; if (f.prodpkg.len == 0) { if (standalonepkg.len == 0) { standalonepkg = srcs[j].pkg; } else if (strings.compare(standalonepkg, srcs[j].pkg) != 0) { pkgfailpath(f.path, "test-only directory declares conflicting packages"); return 1; }; }; let found: bool = false; let k: i32 = 0; for (k < groups.len) { if (strings.compare(groups[k].dir, f.path) == 0 && strings.compare(groups[k].pkg, srcs[j].pkg) == 0) { found = true; break; }; k += 1; }; if (!found) { let g: pkggroup; g.dir = f.path; g.pkg = srcs[j].pkg; g.external = f.prodpkg.len != 0 && strings.compare(f.prodpkg, g.pkg) != 0; append(groups, g); }; }; j += 1; }; if (!sawtestfile) { let g: pkggroup; g.dir = f.path; g.pkg = f.prodpkg; if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; }; g.external = false; append(groups, g); }; i += 1; }; if (groups.len == 0) { return 0; }; pkgsortgroups(groups); // One caller-owned name cannot fan out (Go: `go test -o` with // multiple packages is an error). if (outname.len != 0 && groups.len > 1) { pkgputln(os.STDERR_FILENO, "wwtest package: cannot use -o with multiple packages"); return 2; }; let plans: []pkgplan = alloc([], 1u64)!; let plan: pkgplan; plan.dir = discoverroot; plan.start = 0; plan.end = groups.len; plan.state = PKGQUEUED; append(plans, plan); let borrowed: str = temp.dir(); let tmproot: str = strings.dup(borrowed); let failed: i32 = 0; i = 0; for (i < plans.len) { if (!pkgsetplanpaths(&plans[i], groups, tmproot, i, compileonly, outname, workroot)) { pkgfailpath(tmproot, "cannot create directory-plan temporary path"); if (!pkgremoveall(tmproot)) { pkgput(os.STDERR_FILENO, "wwtest package: cleanup failed; retained "); pkgputln(os.STDERR_FILENO, tmproot); }; return 1; }; i += 1; }; // Build the complete request in one command-owned package universe. Once // the union build completes, successful products run under the same global // -j bound. Emission stays in byte-sorted group order below. let handles: []exec.process = alloc([], plans.len: u64)!; i = 0; for (i < plans.len) { let h: exec.process; append(handles, h); i += 1; }; let runhandles: []exec.process = alloc([], groups.len: u64)!; i = 0; for (i < groups.len) { let h: exec.process; append(runhandles, h); groups[i].state = PKGQUEUED; i += 1; }; let planlaunched: i32 = 0; let plancompleted: i32 = 0; let productcompleted: i32 = 0; if (compileonly) { productcompleted = groups.len; }; let active: i32 = 0; for (plancompleted < plans.len || productcompleted < groups.len) { // Fill free slots with already-built products first, then the next // byte-sorted directory build. Missing product markers are completed // build failures and consume no process slot. let filling: bool = true; for (filling && active < jobs) { filling = false; if (!compileonly) { let gi: i32 = 0; for (gi < groups.len) { let g: *pkggroup = &groups[gi]; if (g.state == PKGQUEUED && plans[g.plan].state == PKGDONE) { if (!pkgproductbuilt(g)) { g.state = PKGDONE; productcompleted += 1; } else { pkgstartrun(g, filters, timeoutarg, list, &runhandles[gi]); g.state = PKGRUNNING; active += 1; }; filling = true; break; }; gi += 1; }; }; if (!filling && planlaunched < plans.len) { pkgstartbuild(&plans[planlaunched], groups, builder, includes, &handles[planlaunched]); plans[planlaunched].state = PKGBUILDING; active += 1; planlaunched += 1; filling = true; }; }; let k: i32 = 0; for (k < planlaunched) { let p: *pkgplan = &plans[k]; if (p.state == PKGBUILDING && exec.poll(&handles[k])) { p.buildres = handles[k].result; p.state = PKGDONE; active -= 1; plancompleted += 1; }; k += 1; }; if (!compileonly) { k = 0; for (k < groups.len) { let g: *pkggroup = &groups[k]; if (g.state == PKGRUNNING && exec.poll(&runhandles[k])) { g.runres = runhandles[k].result; g.state = PKGDONE; active -= 1; productcompleted += 1; }; k += 1; }; }; if (active > 0) { time.sleep(pkgpoll, time.clock.monotonic); }; }; i = 0; for (i < plans.len) { failed += pkgemitplan(&plans[i], groups, compileonly); i += 1; }; if (!pkgremoveall(tmproot)) { pkgput(os.STDERR_FILENO, "wwtest package: cleanup failed; retained "); pkgputln(os.STDERR_FILENO, tmproot); failed += 1; }; if (failed != 0) { return 1; }; return 0; };