Files
ww/internal/wwpackage/package.ww

1181 lines
32 KiB
Plaintext

package wwpackage;
import crypto.sha256;
import hash;
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,
production: bool,
plan: i32,
root: str,
bin: str,
buildok: str,
runout: str,
runerr: str,
state: i32,
runres: exec.result,
};
type pkgplan = struct {
dir: str,
sourceroot: str,
identity: 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. An explicitly selected root may be a symlink;
// canonical planning coalesces it with the target directory. Recursive child
// symlinks remain skipped and symlink source files remain rejected.
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)) {
match (os.stat(&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.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;
};
// Resolve a directory to the kernel's symlink-free absolute spelling. The
// coordinator is single-threaded while planning, so the temporary cwd change
// cannot race a child process launch.
fn pkgcanonicaldir(path: str, out: *str) bool = {
let before: []u8 = alloc([], os.PATH_MAX: u64)!;
before.len = os.PATH_MAX;
let bn: i64 = os.getcwd(before.ptr, before.len: u64);
if (bn <= 1i64 || bn > before.len: i64) { return false; };
if (os.chdir(path) != 0) { return false; };
let after: []u8 = alloc([], os.PATH_MAX: u64)!;
after.len = os.PATH_MAX;
let an: i64 = os.getcwd(after.ptr, after.len: u64);
let restored: i32 = os.chdir(strings.frombytes(before[0:(bn - 1i64): i32]));
if (restored != 0 || an <= 1i64 || an > after.len: i64) { return false; };
*out = strings.dup(strings.frombytes(after[0:(an - 1i64): i32]));
return true;
};
// 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) str = {
let escaped: str = strings.concat("d_", pkgworkescape(dir));
if (escaped.len <= 255) { return escaped; };
let state: sha256.state = sha256.sha256();
let h: *hash.hash = (&state): *hash.hash;
hash.write(h, strings.toutf8("ww-request-workdir-v1:"));
hash.write(h, strings.toutf8(dir));
let digest: [32]u8;
hash.sum(h, digest[0:32]);
let out: []u8 = alloc([], 65u64)!;
let digits: str = "0123456789abcdef";
let i: i32 = 0;
for (i < 32) {
let high: i32 = (digest[i] / 16u8): i32;
let low: i32 = (digest[i] % 16u8): i32;
append(out, digits[high]);
append(out, digits[low]);
i += 1;
};
return strings.concat("d_", strings.frombytes(out));
};
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) {
// Canonical request-directory identity makes equivalent spellings and
// product reorderings select one command-scoped driver workdir.
p.workdir = strings.concat(workroot, "/",
pkgworkkey(p.sourceroot));
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([],
(12 + (p.end - p.start) * 6 + includes.len * 2): u64)!;
append(ba, builder);
append(ba, "test");
append(ba, "-c");
if (p.identity.len != 0) {
append(ba, "--ww-root-identity");
append(ba, p.identity);
};
let i: i32 = p.start;
for (i < p.end) {
let g: *pkggroup = &groups[i];
append(ba, "--ww-package-test");
if (g.production) { append(ba, "production"); }
else { 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;
};
// Recursive descendants derive full identities relative to the canonical
// discovery root using the driver's ordinary ordered import lookup.
append(ba, "-I");
append(ba, p.sourceroot);
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 requestidentity: 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 (strings.compare(a, "--ww-root-identity") == 0) {
if (i + 1 >= args.len || args[i + 1].len == 0
|| requestidentity.len != 0) {
pkgusage();
return 2;
};
requestidentity = 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 canonicalroot: str;
if (!pkgcanonicaldir(discoverroot, &canonicalroot)) {
pkgfailpath(discoverroot, "cannot canonicalize package directory");
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 <package>_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;
g.production = false;
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;
g.production = true;
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.sourceroot = canonicalroot;
plan.identity = requestidentity;
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;
};