Files
ww/internal/wwpackage/package.ww
Hojun-Cho f28843f2f5 ww test: preserve combined product output order
Go 1.26.5 maps each test binary's stdout and stderr to the same product writer. Teach the captured executor to share one open file description for byte-equal output paths, then make directory test products emit that one ordered capture on coordinator stdout. Build captures and inherited-stdio routes remain unchanged.\n\nKeep the executor mechanism, package policy, native Cstage/WWstage proof, and normative contract together so every commit preserves the observable test-output behavior.
2026-08-20 18:29:09 +09:00

2441 lines
66 KiB
Plaintext

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,
prodpkg: str,
samepkg: str,
externalpkg: str,
hassame: bool,
hasexternal: bool,
notests: bool,
production: bool,
plan: i32,
root: str,
bin: str,
buildok: str,
runoutput: str,
state: i32,
runstartfailed: bool,
runres: exec.result,
};
type pkgplan = struct {
dir: str,
identity: str,
root: str,
workdir: str,
outputdir: str,
buildout: str,
builderr: str,
start: i32,
end: i32,
state: i32,
buildres: exec.result,
buildonly: bool,
publish: bool,
emitasm: bool,
};
def PKG_COUNT_MAX: i32 = 2147483647;
def PKG_INITIAL_CAP: i32 = 8;
// strings.concat/dup abort on allocation failure. Request selection and plan
// construction instead keep every new allocation fallible and diagnostic.
fn pkgstring(out: *str, values: str...) bool = {
let total: i64 = 0i64;
let i: i32 = 0;
for (i < values.len) {
total += values[i].len: i64;
if (total > PKG_COUNT_MAX: i64) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
i += 1;
};
if (total == 0i64) { *out = ""; return true; };
let allocation: ([]u8 | nomem) = pkgallocbytes(total: i32);
let bytes: []u8;
match (allocation) {
case let value: []u8 => bytes = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
i = 0;
for (i < values.len) {
let j: i32 = 0;
for (j < values[i].len) {
append(bytes, values[i][j]);
j += 1;
};
i += 1;
};
*out = strings.frombytes(bytes);
return true;
};
fn pkgallocstrs(cap: i32) ([]str | nomem) = {
let value: []str = alloc([], cap: u64)?;
return value;
};
fn pkgallocbytes(cap: i32) ([]u8 | nomem) = {
let value: []u8 = alloc([], cap: u64)?;
return value;
};
fn pkgallocsources(cap: i32) ([]pkgsource | nomem) = {
let value: []pkgsource = alloc([], cap: u64)?;
return value;
};
fn pkgallocfolders(cap: i32) ([]pkgfolder | nomem) = {
let value: []pkgfolder = alloc([], cap: u64)?;
return value;
};
fn pkgallocgroups(cap: i32) ([]pkggroup | nomem) = {
let value: []pkggroup = alloc([], cap: u64)?;
return value;
};
fn pkgallocplans(cap: i32) ([]pkgplan | nomem) = {
let value: []pkgplan = alloc([], cap: u64)?;
return value;
};
fn pkgallocprocesses(cap: i32) ([]exec.process | nomem) = {
let value: []exec.process = alloc([], cap: u64)?;
return value;
};
// 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,
fatal: bool,
};
fn pkggrowcap(current: i32, need: i32) i32 = {
if (need < 0) { return -1; };
if (need <= current) { return current; };
let cap: i32 = current;
if (cap == 0) { cap = PKG_INITIAL_CAP; };
for (cap < need) {
if (cap > PKG_COUNT_MAX / 2) {
cap = PKG_COUNT_MAX;
break;
};
cap *= 2;
};
if (cap < need) { return -1; };
return cap;
};
fn pkgappenddiscovered(st: *pkgdiscover, path: str) bool = {
if (st.paths.len == PKG_COUNT_MAX) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
st.errors += 1;
st.fatal = true;
return false;
};
let need: i32 = st.paths.len + 1;
if (need > st.paths.cap) {
let cap: i32 = pkggrowcap(st.paths.cap, need);
if (cap < 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
st.errors += 1;
st.fatal = true;
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(cap);
let next: []str;
match (allocation) {
case let value: []str => next = value;
case nomem => {
pkgputln(os.STDERR_FILENO,
"wwtest package: out of memory");
st.errors += 1;
st.fatal = true;
return false;
};
};
let n: i32 = st.paths.len;
next.len = cap;
let i: i32 = 0;
for (i < n) { next[i] = st.paths[i]; i += 1; };
next.len = n;
if (st.paths.ptr != nil) {
os.free(st.paths.ptr: *void,
(st.paths.cap: u64) * (size(str): u64));
};
st.paths = next;
};
append(st.paths, path);
return true;
};
// Preserve the caller's toolchain environment while pinning the locale and
// temporary directory used by the current build plan.
fn toolenv(tmpdir: str, out: *[]str) bool = {
let inherited: []str = os.getenvs();
if (inherited.len > PKG_COUNT_MAX - 2) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 2);
let env: []str;
match (allocation) {
case let value: []str => env = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
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");
let tmpenv: str;
if (!pkgstring(&tmpenv, "TMPDIR=", tmpdir)) { return false; };
append(env, tmpenv);
*out = env;
return true;
};
fn pkgfreeownedstr(value: str) void = {
if (value.ptr != nil && value.len != 0) {
os.free(value.ptr: *void, value.len: u64);
};
};
fn pkgfreestrs(values: []str) void = {
if (values.ptr != nil && values.cap != 0) {
os.free(values.ptr: *void,
(values.cap: u64) * (size(str): u64));
};
};
// Go's AppendPWD appends, then os/exec keeps the last duplicate value. WW's
// executor preserves duplicates, so discard earlier exact PWD entries here.
fn runenv(tmpdir: str, pwd: str, out: *[]str, tmpowned: *str,
pwdowned: *str) bool = {
let inherited: []str = os.getenvs();
if (inherited.len > PKG_COUNT_MAX - 3) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 3);
let env: []str;
match (allocation) {
case let value: []str => env = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
let i: i32 = 0;
for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "TMPDIR=")
&& !strings.hasprefix(inherited[i], "LC_ALL=")
&& !strings.hasprefix(inherited[i], "PWD=")) {
append(env, inherited[i]);
};
i += 1;
};
append(env, "LC_ALL=C");
let tmpenv: str;
if (!pkgstring(&tmpenv, "TMPDIR=", tmpdir)) {
pkgfreestrs(env);
return false;
};
let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) {
pkgfreeownedstr(tmpenv);
pkgfreestrs(env);
return false;
};
append(env, tmpenv);
append(env, pwdenv);
*out = env;
*tmpowned = tmpenv;
*pwdowned = pwdenv;
return true;
};
fn freerunenv(env: []str, tmpowned: str, pwdowned: str) void = {
pkgfreeownedstr(tmpowned);
pkgfreeownedstr(pwdowned);
pkgfreestrs(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 pkgputhexbyte(fd: i32, value: u8) void = {
let digits: str = "0123456789abcdef";
let encoded: [2]u8;
let high: i32 = (value / 16u8): i32;
let low: i32 = (value % 16u8): i32;
encoded[0] = digits[high];
encoded[1] = digits[low];
os.write(fd, &encoded[0], 2u64);
};
fn pkgputhexrune(fd: i32, value: u32, digits: i32) void = {
let alphabet: str = "0123456789abcdef";
let encoded: [8]u8;
let i: i32 = digits - 1;
for (i >= 0) {
let shift: u32 = (i: u32) * 4u32;
let d: i32 = ((value >> shift) & 15u32): i32;
encoded[digits - 1 - i] = alphabet[d];
i -= 1;
};
os.write(fd, &encoded[0], digits: u64);
};
fn pkgutf8width(value: str, index: i32) i32 = {
let c: u8 = value[index];
if (c < 128u8) { return 1; };
if (c >= 194u8 && c <= 223u8 && index + 1 < value.len
&& value[index + 1] >= 128u8 && value[index + 1] <= 191u8) {
return 2;
};
if (index + 2 < value.len
&& value[index + 2] >= 128u8 && value[index + 2] <= 191u8) {
let c1: u8 = value[index + 1];
if ((c == 224u8 && c1 >= 160u8 && c1 <= 191u8)
|| (c >= 225u8 && c <= 236u8 && c1 >= 128u8 && c1 <= 191u8)
|| (c == 237u8 && c1 >= 128u8 && c1 <= 159u8)
|| (c >= 238u8 && c <= 239u8 && c1 >= 128u8 && c1 <= 191u8)) {
return 3;
};
};
if (index + 3 < value.len
&& value[index + 2] >= 128u8 && value[index + 2] <= 191u8
&& value[index + 3] >= 128u8 && value[index + 3] <= 191u8) {
let c1: u8 = value[index + 1];
if ((c == 240u8 && c1 >= 144u8 && c1 <= 191u8)
|| (c >= 241u8 && c <= 243u8 && c1 >= 128u8 && c1 <= 191u8)
|| (c == 244u8 && c1 >= 128u8 && c1 <= 143u8)) {
return 4;
};
};
return 1;
};
// Decode one filesystem-name rune with the same malformed-byte behavior as
// utf8.DecodeRuneInString: each malformed byte is one U+FFFD rune.
fn pkgutf8rune(value: str, index: i32) u32 = {
let width: i32 = pkgutf8width(value, index);
let c0: u32 = value[index]: u32;
if (width == 1) {
if (c0 >= 128u32) { return 0xfffdu32; };
return c0;
};
let c1: u32 = value[index + 1]: u32;
if (width == 2) {
return ((c0 & 31u32) << 6u32) | (c1 & 63u32);
};
let c2: u32 = value[index + 2]: u32;
if (width == 3) {
return ((c0 & 15u32) << 12u32)
| ((c1 & 63u32) << 6u32) | (c2 & 63u32);
};
let c3: u32 = value[index + 3]: u32;
return ((c0 & 7u32) << 18u32) | ((c1 & 63u32) << 12u32)
| ((c2 & 63u32) << 6u32) | (c3 & 63u32);
};
fn pkgvalidutf8(value: str) bool = {
let i: i32 = 0;
for (i < value.len) {
let width: i32 = pkgutf8width(value, i);
if (value[i] >= 128u8 && width == 1) { return false; };
i += width;
};
return true;
};
// Go's unmatched-pattern diagnostic uses strconv.Quote. Filesystem arguments
// are emitted byte-exactly for Go-printable UTF-8, with Go escapes for
// controls, non-printing runes, and malformed bytes.
fn pkgputquoted(fd: i32, value: str) void = {
pkgput(fd, "\"");
let i: i32 = 0;
for (i < value.len) {
let c: u8 = value[i];
let width: i32 = pkgutf8width(value, i);
if (c >= 128u8 && width == 1) {
pkgput(fd, "\\x"); pkgputhexbyte(fd, c); i += 1; continue;
};
if (width > 1) {
let r: u32 = pkgutf8rune(value, i);
if (pkgisprint(r)) {
os.write(fd, value.ptr + (i: u64), width: u64);
} else if (r < 0x10000u32) {
pkgput(fd, "\\u"); pkgputhexrune(fd, r, 4);
} else {
pkgput(fd, "\\U"); pkgputhexrune(fd, r, 8);
};
i += width;
continue;
};
if (c == '"') { pkgput(fd, "\\\""); }
else { if (c == '\\') { pkgput(fd, "\\\\"); }
else { if (c == 7u8) { pkgput(fd, "\\a"); }
else { if (c == 8u8) { pkgput(fd, "\\b"); }
else { if (c == 12u8) { pkgput(fd, "\\f"); }
else { if (c == '\n') { pkgput(fd, "\\n"); }
else { if (c == '\r') { pkgput(fd, "\\r"); }
else { if (c == '\t') { pkgput(fd, "\\t"); }
else { if (c == 11u8) { pkgput(fd, "\\v"); }
else { if (c < 32u8 || c == 127u8) {
pkgput(fd, "\\x"); pkgputhexbyte(fd, c);
} else { os.write(fd, &value[i], 1u64); };
};};};};};};};};};
i += 1;
};
pkgput(fd, "\"");
};
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 = {
pkgput(os.STDERR_FILENO,
"usage: wwtest package [-c] [-S] [-list] [-j N] [-I DIR] [-L DIR] [-l LIB] [-w DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/... ...] [-- GLOB ...]\n");
pkgput(os.STDERR_FILENO,
" *_test.ww is the sole test-source form; @test elsewhere is rejected\n");
pkgput(os.STDERR_FILENO,
" -c retains the compiled package binaries; -j N runs up to N build or test processes at once\n");
pkgput(os.STDERR_FILENO,
" -w DIR is one persistent semantic-action store shared by the selected packages\n");
};
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 || n >= PKG_COUNT_MAX: i64) {
os.close(fd);
if (n >= PKG_COUNT_MAX: i64) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
};
return false;
};
let allocation: ([]u8 | nomem) = pkgallocbytes((n + 1i64): i32);
let b: []u8;
match (allocation) {
case let value: []u8 => b = value;
case nomem => {
os.close(fd);
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
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 pkgstatdir(path: str) bool = {
let fi: os.filestat;
match (os.stat(&fi, path)) {
case void => return pkgmodeis(fi.mode, os.mode.DIR);
case let e: os.oserror => return false;
};
};
fn pkgoutputdir(path: str) bool = {
return pkgstatdir(path) || strings.hassuffix(path, "/");
};
fn pkgjoinpath(dir: str, name: str, out: *str) bool = {
if (strings.hassuffix(dir, "/")) { return pkgstring(out, dir, name); };
return pkgstring(out, dir, "/", name);
};
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 pkgnamerangeis(name: str, start: i32, end: i32, word: str) bool = {
if (end < start || end - start != word.len) { return false; };
let i: i32 = 0;
for (i < end - start) {
if (name[start + i] != word[i]) { return false; };
i += 1;
};
return true;
};
fn pkgknownos(name: str, start: i32, end: i32) bool = {
return pkgnamerangeis(name, start, end, "aix")
|| pkgnamerangeis(name, start, end, "android")
|| pkgnamerangeis(name, start, end, "darwin")
|| pkgnamerangeis(name, start, end, "dragonfly")
|| pkgnamerangeis(name, start, end, "freebsd")
|| pkgnamerangeis(name, start, end, "hurd")
|| pkgnamerangeis(name, start, end, "illumos")
|| pkgnamerangeis(name, start, end, "ios")
|| pkgnamerangeis(name, start, end, "js")
|| pkgnamerangeis(name, start, end, "linux")
|| pkgnamerangeis(name, start, end, "nacl")
|| pkgnamerangeis(name, start, end, "netbsd")
|| pkgnamerangeis(name, start, end, "openbsd")
|| pkgnamerangeis(name, start, end, "plan9")
|| pkgnamerangeis(name, start, end, "solaris")
|| pkgnamerangeis(name, start, end, "wasip1")
|| pkgnamerangeis(name, start, end, "windows")
|| pkgnamerangeis(name, start, end, "zos");
};
fn pkgknownarch(name: str, start: i32, end: i32) bool = {
return pkgnamerangeis(name, start, end, "386")
|| pkgnamerangeis(name, start, end, "amd64")
|| pkgnamerangeis(name, start, end, "amd64p32")
|| pkgnamerangeis(name, start, end, "arm")
|| pkgnamerangeis(name, start, end, "armbe")
|| pkgnamerangeis(name, start, end, "arm64")
|| pkgnamerangeis(name, start, end, "arm64be")
|| pkgnamerangeis(name, start, end, "loong64")
|| pkgnamerangeis(name, start, end, "mips")
|| pkgnamerangeis(name, start, end, "mipsle")
|| pkgnamerangeis(name, start, end, "mips64")
|| pkgnamerangeis(name, start, end, "mips64le")
|| pkgnamerangeis(name, start, end, "mips64p32")
|| pkgnamerangeis(name, start, end, "mips64p32le")
|| pkgnamerangeis(name, start, end, "ppc")
|| pkgnamerangeis(name, start, end, "ppc64")
|| pkgnamerangeis(name, start, end, "ppc64le")
|| pkgnamerangeis(name, start, end, "riscv")
|| pkgnamerangeis(name, start, end, "riscv64")
|| pkgnamerangeis(name, start, end, "s390")
|| pkgnamerangeis(name, start, end, "s390x")
|| pkgnamerangeis(name, start, end, "sparc")
|| pkgnamerangeis(name, start, end, "sparc64")
|| pkgnamerangeis(name, start, end, "wasm");
};
fn pkgtargettag(name: str, start: i32, end: i32) bool = {
return pkgnamerangeis(name, start, end, "linux")
|| pkgnamerangeis(name, start, end, "amd64");
};
// Recursive discovery must decide platform eligibility before a source can
// enter any production or test package graph.
fn pkgsourceplatform(name: str) bool = {
let stem: i32 = 0;
let hasunderscore: bool = false;
for (stem < name.len && name[stem] != '.') {
if (name[stem] == '_') { hasunderscore = true; };
stem += 1;
};
if (!hasunderscore) { return true; };
let end: i32 = stem;
let last: i32 = end;
for (last > 0 && name[last - 1] != '_') { last -= 1; };
if (pkgnamerangeis(name, last, end, "test")) {
if (last == 0) { return true; };
end = last - 1;
last = end;
for (last > 0 && name[last - 1] != '_') { last -= 1; };
};
if (last == 0) { return true; };
let prevend: i32 = last - 1;
let prev: i32 = prevend;
for (prev > 0 && name[prev - 1] != '_') { prev -= 1; };
if (prev < prevend && pkgknownos(name, prev, prevend)
&& pkgknownarch(name, last, end)) {
return pkgtargettag(name, last, end)
&& pkgtargettag(name, prev, prevend);
};
if (pkgknownos(name, last, end) || pkgknownarch(name, last, end)) {
return pkgtargettag(name, last, end);
};
return true;
};
fn pkgkeepfile(name: str) bool = {
if (name.len <= 3 || name[0] == '.' || name[0] == '_') { return false; };
return strings.hassuffix(name, ".ww") && pkgsourceplatform(name);
};
type pkgmatcher = struct {
full: str,
base: str,
fullcap: u64,
basecap: u64,
trailing: bool,
valid: bool,
};
fn pkgsortstrings(ss: []str) void = {
let i: i32 = 1;
for (i < ss.len) {
let j: i32 = i;
for (j > 0 && strings.compare(ss[j - 1], ss[j]) > 0) {
let value: str = ss[j];
ss[j] = ss[j - 1];
ss[j - 1] = value;
j -= 1;
};
i += 1;
};
};
fn pkgcleanpath(spelling: str, out: *str) bool = {
if (spelling.len > PKG_COUNT_MAX - 2) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let componentallocation: ([]str | nomem) =
pkgallocstrs(spelling.len + 1);
let components: []str;
match (componentallocation) {
case let value: []str => components = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
let absolute: bool = spelling.len != 0 && spelling[0] == '/';
let start: i32 = 0;
for (start <= spelling.len) {
let end: i32 = start;
for (end < spelling.len && spelling[end] != '/') { end += 1; };
let component: str = spelling[start:end];
if (component.len == 0 || strings.compare(component, ".") == 0) {
void;
} else if (strings.compare(component, "..") == 0) {
if (components.len != 0
&& strings.compare(components[components.len - 1], "..") != 0) {
components.len -= 1;
} else if (!absolute) {
append(components, component);
};
} else {
append(components, component);
};
if (end == spelling.len) { break; };
start = end + 1;
};
let byteallocation: ([]u8 | nomem) = pkgallocbytes(spelling.len + 2);
let cleaned: []u8;
match (byteallocation) {
case let value: []u8 => cleaned = value;
case nomem => {
os.free(components.ptr: *void,
((spelling.len + 1): u64) * (size(str): u64));
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
if (absolute) { append(cleaned, '/': u8); };
let i: i32 = 0;
for (i < components.len) {
if (i != 0) { append(cleaned, '/': u8); };
let j: i32 = 0;
for (j < components[i].len) {
append(cleaned, components[i][j]);
j += 1;
};
i += 1;
};
if (cleaned.len == 0) { append(cleaned, '.': u8); };
*out = strings.frombytes(cleaned);
os.free(components.ptr: *void,
((spelling.len + 1): u64) * (size(str): u64));
return true;
};
fn pkgfirstellipsis(value: str) i32 = {
let i: i32 = 0;
for (i + 2 < value.len) {
if (value[i] == '.' && value[i + 1] == '.'
&& value[i + 2] == '.') { return i; };
i += 1;
};
return -1;
};
// Non-terminal vendor elements become a byte that a wildcard cannot consume.
// Explicit vendor elements receive the same byte and therefore still match.
fn pkgvendorform(value: str, out: *str, capacity: *u64) bool = {
*capacity = value.len: u64;
if (value.len == 0) { *out = ""; return true; };
let allocation: ([]u8 | nomem) = pkgallocbytes(value.len);
let encoded: []u8;
match (allocation) {
case let bytes: []u8 => encoded = bytes;
case nomem => return false;
};
let start: i32 = 0;
for (start < value.len) {
let end: i32 = start;
for (end < value.len && value[end] != '/') { end += 1; };
if (end < value.len && end - start == 6
&& strings.compare(value[start:end], "vendor") == 0) {
append(encoded, 0u8);
} else {
let i: i32 = start;
for (i < end) { append(encoded, value[i]); i += 1; };
};
if (end == value.len) { break; };
append(encoded, '/': u8);
start = end + 1;
};
*out = strings.frombytes(encoded);
return true;
};
fn pkgfreeform(value: str, capacity: u64) void = {
if (value.ptr != nil && capacity != 0u64) {
os.free(value.ptr: *void, capacity);
};
};
fn pkgglob(pattern: str, name: str) bool = {
let pi: i32 = 0;
let ni: i32 = 0;
let star: i32 = -1;
let resume: i32 = 0;
for (ni < name.len) {
if (pi + 2 < pattern.len && pattern[pi] == '.'
&& pattern[pi + 1] == '.' && pattern[pi + 2] == '.') {
star = pi;
pi += 3;
resume = ni;
continue;
};
if (pi < pattern.len
&& pkgutf8rune(pattern, pi) == pkgutf8rune(name, ni)) {
pi += pkgutf8width(pattern, pi);
ni += pkgutf8width(name, ni);
continue;
};
if (star >= 0 && resume < name.len && name[resume] != 0u8
&& name[resume] != '\n') {
resume += pkgutf8width(name, resume);
ni = resume;
pi = star + 3;
continue;
};
return false;
};
for (pi + 2 < pattern.len && pattern[pi] == '.'
&& pattern[pi + 1] == '.' && pattern[pi + 2] == '.') {
pi += 3;
};
return pi == pattern.len;
};
fn pkgmakematcher(pattern: str, out: *pkgmatcher) bool = {
out.full = "";
out.base = "";
out.fullcap = 0u64;
out.basecap = 0u64;
out.trailing = strings.hassuffix(pattern, "/...");
out.valid = pkgvalidutf8(pattern);
if (!out.valid) { return true; };
if (!pkgvendorform(pattern, &out.full, &out.fullcap)) { return false; };
if (out.trailing && !pkgvendorform(pattern[0:pattern.len - 4],
&out.base, &out.basecap)) {
pkgfreeform(out.full, out.fullcap);
return false;
};
return true;
};
fn pkgfreematcher(m: *pkgmatcher) void = {
pkgfreeform(m.full, m.fullcap);
pkgfreeform(m.base, m.basecap);
};
fn pkgmatchdir(m: *pkgmatcher, name: str, out: *bool) bool = {
if (!m.valid) { *out = false; return true; };
// pkgcleanpath removes a leading ./ from a relative pattern. Descent from
// its implicit "." traversal root produces ./child spellings, so compare
// both values in the same cleaned coordinate system.
if (strings.hasprefix(name, "./") && name.len > 2) { name = name[2:name.len]; };
let candidate: str;
let capacity: u64;
if (!pkgvendorform(name, &candidate, &capacity)) { return false; };
*out = pkgglob(m.full, candidate)
|| (m.trailing && pkgglob(m.base, candidate));
pkgfreeform(candidate, capacity);
return true;
};
fn pkgexcluded(name: str) bool = {
return name.len != 0 && strings.compare(name, ".") != 0
&& strings.compare(name, "..") != 0
&& (name[0] == '.' || name[0] == '_'
|| strings.compare(name, "testdata") == 0);
};
// A wildcard walk follows an explicitly selected root symlink but never a
// child symlink. Selection is separate from descent: vendor subtrees are
// walked, while the matcher prevents an implicit wildcard from consuming a
// non-terminal vendor element.
fn pkgdiscoverdir(pathname: str, st: *pkgdiscover,
matcher: *pkgmatcher) void = {
if (st.fatal) { return; };
let rootstat: os.filestat;
match (os.lstat(&rootstat, pathname)) {
case void => void;
case let e: os.oserror => {
pkgfailpath(pathname, "cannot stat package directory");
st.errors += 1;
return;
};
};
if (pkgmodeis(rootstat.mode, os.mode.LINK)) {
match (os.stat(&rootstat, pathname)) {
case void => void;
case let e: os.oserror => {
pkgfailpath(pathname, "cannot stat package directory");
st.errors += 1;
return;
};
};
};
if (!pkgmodeis(rootstat.mode, os.mode.DIR)) {
pkgfailpath(pathname, "expected one package directory");
st.errors += 1;
return;
};
let selected: bool = matcher == nil;
if (matcher != nil && !pkgmatchdir(matcher, pathname, &selected)) {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
st.errors += 1;
st.fatal = true;
return;
};
let fd: i32 = os.open(pathname, os.flag.RDONLY, 0i32);
if (fd < 0) {
pkgfailpath(pathname, "cannot open directory");
st.errors += 1;
return;
};
let bufallocation: ([]u8 | nomem) = pkgallocbytes(8192);
let buf: []u8;
match (bufallocation) {
case let value: []u8 => buf = value;
case nomem => {
os.close(fd);
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
st.errors += 1;
st.fatal = true;
return;
};
};
buf.len = 8192;
let names: pkgdiscover;
let emptynames: []str;
names.paths = emptynames;
names.errors = 0;
names.fatal = false;
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(pathname, "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 (!(strings.compare(name, ".") == 0
|| strings.compare(name, "..") == 0)) {
let ownedname: str;
if (!pkgstring(&ownedname, name)) {
st.errors += 1;
st.fatal = true;
os.close(fd);
return;
};
if (!pkgappenddiscovered(&names, ownedname)) {
st.errors += names.errors;
st.fatal = names.fatal;
os.close(fd);
return;
};
};
off += reclen;
};
n = os.getdents64(fd, buf.ptr, 8192u64);
};
if (n < 0i64) {
pkgfailpath(pathname, "directory read failed");
st.errors += 1;
};
os.close(fd);
pkgsortstrings(names.paths);
let ni: i32 = 0;
for (ni < names.paths.len) {
let name: str = names.paths[ni];
let child: str;
if (!pkgstring(&child, pathname, "/", name)) {
st.errors += 1; st.fatal = true; return;
};
let fi: os.filestat;
match (os.lstat(&fi, child)) {
case void => {
if (pkgmodeis(fi.mode, os.mode.DIR)) {
if (matcher != nil && !pkgexcluded(name)) {
pkgdiscoverdir(child, st, matcher);
if (st.fatal) { return; };
};
} else if (selected && pkgkeepfile(name)) {
let sourceisregular: bool = pkgmodeis(fi.mode, os.mode.REG);
if (pkgmodeis(fi.mode, os.mode.LINK)) {
let target: os.filestat;
match (os.stat(&target, child)) {
case void => {
// Match go/build: a file symlink is a source under
// its directory-entry name; a directory target is not.
sourceisregular = pkgmodeis(target.mode, os.mode.REG);
};
case let e: os.oserror => {
pkgfailpath(child, "cannot stat source");
st.errors += 1;
};
};
};
if (sourceisregular && !pkgappenddiscovered(st, child)) {
return;
};
};
};
case let e: os.oserror => {
if (selected && pkgkeepfile(name)) {
pkgfailpath(child, "cannot stat source");
} else {
pkgfailpath(child, "cannot stat directory entry");
};
st.errors += 1;
};
};
ni += 1;
};
};
// 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 n: i32 = 1;
let i: i32 = 1;
for (i < ss.len) {
if (strings.compare(ss[n - 1], ss[i]) != 0) {
ss[n] = ss[i];
n += 1;
};
i += 1;
};
ss.len = n;
return ss;
};
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(out: *str) bool = {
let av: []str = os.args();
if (av.len == 0 || av[0].len == 0) { *out = "ww"; return true; };
return pkgstring(out, 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, oom: *bool) bool = {
*oom = false;
let beforeallocation: ([]u8 | nomem) = pkgallocbytes(os.PATH_MAX);
let before: []u8;
match (beforeallocation) {
case let value: []u8 => before = value;
case nomem => {
*oom = true;
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
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 afterallocation: ([]u8 | nomem) = pkgallocbytes(os.PATH_MAX);
let after: []u8;
match (afterallocation) {
case let value: []u8 => after = value;
case nomem => {
*oom = true;
let restored: i32 = os.chdir(strings.frombytes(
before[0:(bn - 1i64): i32]));
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
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; };
after.len = (an - 1i64): i32;
*out = strings.frombytes(after);
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 allocation: ([]u8 | nomem) = pkgallocbytes(8192);
let buf: []u8;
match (allocation) {
case let value: []u8 => buf = value;
case nomem => { os.close(fd); return false; };
};
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;
if (!pkgstring(&child, path, "/", name)) {
ok = false; off = n: u64; continue;
};
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 pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
compileonly: bool, buildonly: bool, outname: str, outputdir: bool,
workroot: str) bool = {
let num: str = strconv.i32tos(index, strconv.base.DEC);
if (!pkgstring(&p.root, root, "/plan-", num)) { return false; };
if (!pkgmakedir(p.root)) {
pkgfailpath(p.root, "cannot create directory-plan temporary path");
return false;
};
// The caller's -w directory is the semantic-action store itself. A pattern
// spelling or traversal prefix must never select another cache container.
// The private driver creates it only after graph preflight succeeds.
p.workdir = workroot;
p.outputdir = "";
if (outputdir) { p.outputdir = outname; };
if (!pkgstring(&p.buildout, p.root, "/build.stdout")
|| !pkgstring(&p.builderr, p.root, "/build.stderr")) { return false; };
let i: i32 = p.start;
for (i < p.end) {
let g: *pkggroup = &groups[i];
g.plan = index;
if (!pkgstring(&g.root, p.root, "/product-",
strconv.i32tos(i - p.start, strconv.base.DEC))) { return false; };
if (!pkgmakedir(g.root)) {
pkgfailpath(g.root, "cannot create product temporary path");
return false;
};
if (buildonly) {
if (outputdir && strings.compare(g.pkg, "main") == 0) {
if (!pkgjoinpath(outname, pkgbase(g.dir), &g.bin)) { return false; };
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
} else if (compileonly) {
if (outname.len != 0) {
g.bin = outname;
} else {
if (!pkgstring(&g.bin, g.dir, "/", g.pkg, ".test")) { return false; };
};
} else {
if (!pkgstring(&g.bin, g.root, "/package.test")) { return false; };
};
if (!pkgstring(&g.runoutput, g.root, "/test.output")
|| !pkgstring(&g.buildok, g.root, "/build.ok")) { return false; };
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);
pkgput(os.STDOUT_FILENO, "]");
};
fn pkgreportcommand(fd: i32, kind: str, g: *pkggroup,
r: *exec.result) void = {
pkgput(fd, "FAIL ");
pkgput(fd, g.dir);
pkgput(fd, " [");
pkgput(fd, g.pkg);
pkgput(fd, "] (");
pkgput(fd, kind);
if (r.errno != 0 || r.cleanuperrno != 0
|| r.termination == exec.termination.ERROR) {
pkgput(fd, " harness error ");
let code: i32 = r.errno;
if (code == 0) { code = r.cleanuperrno; };
pkgput(fd, strconv.i32tos(code, strconv.base.DEC));
} else if (r.termination == exec.termination.SIGNAL) {
pkgput(fd, " signal ");
pkgput(fd, strconv.i32tos(r.code, strconv.base.DEC));
} else {
pkgput(fd, " exit ");
pkgput(fd, strconv.i32tos(r.code, strconv.base.DEC));
};
pkgputln(fd, ")");
};
fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
libdirs: []str, libs: []str, h: *exec.process) bool = {
let nproducts: i32 = p.end - p.start;
let capacity: i32 = 16;
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 9) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += nproducts * 9;
if (includes.len > (PKG_COUNT_MAX - capacity) / 2) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += includes.len * 2;
if (libdirs.len > (PKG_COUNT_MAX - capacity) / 2) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += libdirs.len * 2;
if (libs.len > (PKG_COUNT_MAX - capacity) / 2) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += libs.len * 2;
let allocation: ([]str | nomem) = pkgallocstrs(capacity);
let ba: []str;
match (allocation) {
case let value: []str => ba = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
append(ba, builder);
append(ba, "test");
append(ba, "-c");
if (p.buildonly) { append(ba, "--ww-package-build"); };
if (p.publish) { append(ba, "--ww-package-publish"); };
if (p.emitasm) { append(ba, "-S"); };
if (p.workdir.len != 0) { append(ba, "--ww-create-workdir"); };
if (p.outputdir.len != 0) {
append(ba, "--ww-create-output-dir");
append(ba, p.outputdir);
};
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 (p.buildonly) { append(ba, "build"); }
else { append(ba, "test"); };
append(ba, g.pkg);
if (g.prodpkg.len != 0) { append(ba, g.prodpkg); }
else { append(ba, "-"); };
if (g.hassame) { append(ba, g.samepkg); }
else { append(ba, "-"); };
if (g.hasexternal) { append(ba, g.externalpkg); }
else { append(ba, "-"); };
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;
};
ii = 0;
for (ii < libdirs.len) {
append(ba, "-L");
append(ba, libdirs[ii]);
ii += 1;
};
ii = 0;
for (ii < libs.len) {
append(ba, "-l");
append(ba, libs[ii]);
ii += 1;
};
if (p.workdir.len != 0) {
append(ba, "-w");
append(ba, p.workdir);
};
append(ba, p.dir);
let env: []str;
if (!toolenv(p.root, &env)) { return false; };
let bcmd: exec.command;
bcmd.path = builder;
bcmd.argv = ba;
bcmd.env = env;
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);
return true;
};
fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
list: bool, h: *exec.process) bool = {
if (filters.len > PKG_COUNT_MAX - 4) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(filters.len + 4);
let ra: []str;
match (allocation) {
case let value: []str => ra = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false;
};
};
append(ra, g.bin);
let packagearg: str;
if (!pkgstring(&packagearg, "-package=", g.pkg)) {
pkgfreestrs(ra);
return false;
};
append(ra, packagearg);
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 env: []str;
let tmpowned: str;
let pwdowned: str;
if (!runenv(g.root, g.dir, &env, &tmpowned, &pwdowned)) {
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return false;
};
let rcmd: exec.command;
rcmd.path = g.bin;
rcmd.argv = ra;
rcmd.env = env;
rcmd.dir = g.dir;
rcmd.stdoutpath = g.runoutput;
rcmd.stderrpath = g.runoutput;
rcmd.deadline.sec = 0i64;
rcmd.deadline.nsec = 0i64;
rcmd.grace = 0i64: time.duration;
exec.start(h, &rcmd);
freerunenv(env, tmpowned, pwdowned);
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return true;
};
fn pkgproductbuilt(g: *pkggroup, buildonly: bool) bool = {
if (buildonly || g.notests) { return pkgisreg(g.buildok); };
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 (g.notests) {
pkgput(os.STDOUT_FILENO, "? ");
pkgput(os.STDOUT_FILENO, g.dir);
pkgputln(os.STDOUT_FILENO, " [no tests]");
return true;
};
if (compileonly) {
pkgput(os.STDOUT_FILENO, "built ");
pkglabel(g);
pkgput(os.STDOUT_FILENO, " -> ");
pkgputln(os.STDOUT_FILENO, g.bin);
return true;
};
if (g.runstartfailed) { return false; };
let runoutput: str;
if (!pkgread(g.runoutput, &runoutput)) {
pkgfailpath(g.root, "cannot read test capture");
return false;
};
pkgput(os.STDOUT_FILENO, runoutput);
if (!pkgrunok(g)) {
pkgreportcommand(os.STDOUT_FILENO, "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, buildonly: 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], buildonly)) {
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
&p.buildres);
failed += 1;
} else if (buildonly) {
void;
} else if (!pkgemitgroup(&groups[i], compileonly)) {
failed += 1;
};
i += 1;
};
return failed;
};
export fn packagecommand(args: []str) int = {
let compileonly: bool = false;
let buildonly: bool = false;
let list: bool = false;
let jobs: i32 = 1;
let afterdash: bool = false;
let buildrootseen: bool = false;
if (args.len == PKG_COUNT_MAX) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return 1;
};
let argcapacity: i32 = args.len + 1;
let rootallocation: ([]str | nomem) = pkgallocstrs(argcapacity);
let roots: []str;
match (rootallocation) {
case let value: []str => roots = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let filterallocation: ([]str | nomem) = pkgallocstrs(argcapacity);
let filters: []str;
match (filterallocation) {
case let value: []str => filters = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let includeallocation: ([]str | nomem) = pkgallocstrs(argcapacity);
let includes: []str;
match (includeallocation) {
case let value: []str => includes = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let libdirallocation: ([]str | nomem) = pkgallocstrs(argcapacity);
let libdirs: []str;
match (libdirallocation) {
case let value: []str => libdirs = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let liballocation: ([]str | nomem) = pkgallocstrs(argcapacity);
let libs: []str;
match (liballocation) {
case let value: []str => libs = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let emitasm: bool = false;
let timeoutarg: str = "";
let outname: str = "";
let explicitout: bool = false;
let workroot: str = "";
let requestidentity: str = "";
let builder: str;
if (!pkgdefaultbuilder(&builder)) { return 1; };
let i: i32 = 0;
for (i < args.len) {
let a: str = args[i];
if (buildonly && buildrootseen) {
append(roots, a);
i += 1;
continue;
};
if (afterdash) {
if (buildonly) { append(roots, a); }
else { 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, "-S") == 0) { emitasm = true; i += 1; continue; };
if (!buildonly && 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, "-L") == 0) {
if (i + 1 >= args.len) { pkgusage(); return 2; };
append(libdirs, args[i + 1]);
i += 2;
continue;
};
if (strings.hasprefix(a, "-L") && a.len > 2) {
append(libdirs, a[2:a.len]);
i += 1;
continue;
};
if (strings.compare(a, "-l") == 0) {
if (i + 1 >= args.len) { pkgusage(); return 2; };
append(libs, args[i + 1]);
i += 2;
continue;
};
if (strings.hasprefix(a, "-l") && a.len > 2) {
append(libs, 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];
explicitout = true;
i += 2;
continue;
};
if (strings.hasprefix(a, "-o") && a.len > 2) {
outname = a[2:a.len];
explicitout = true;
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-operation") == 0) {
if (i + 1 >= args.len || strings.compare(args[i + 1], "build") != 0
|| buildonly) {
pkgusage();
return 2;
};
buildonly = true;
compileonly = true;
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);
if (buildonly) { buildrootseen = true; };
i += 1;
};
if (roots.len == 0) { append(roots, "."); };
pkgsortstrings(roots);
if (compileonly && !buildonly
&& (list || filters.len != 0 || timeoutarg.len != 0)) {
pkgusage();
return 2;
};
if (!buildonly && (emitasm || libdirs.len != 0 || libs.len != 0)) {
pkgusage();
return 2;
};
// A non-compile run leaves no caller-owned artifact for -o 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 && !buildonly) {
pkgputln(os.STDERR_FILENO,
"wwtest package: -w conflicts with -c");
return 2;
};
let ds: pkgdiscover;
let emptypaths: []str;
ds.paths = emptypaths;
ds.errors = 0;
ds.fatal = false;
let directroots: pkgdiscover;
let emptydirect: []str;
directroots.paths = emptydirect;
directroots.errors = 0;
directroots.fatal = false;
let anyrecurse: bool = false;
i = 0;
for (i < roots.len) {
let requested: str = roots[i];
let cleaned: str;
if (!pkgcleanpath(requested, &cleaned)) {
return 1;
};
let ellipsis: i32 = pkgfirstellipsis(cleaned);
let recurse: bool = ellipsis >= 0;
let discoverroot: str = cleaned;
let matcher: pkgmatcher;
if (recurse) {
let slash: i32 = ellipsis - 1;
for (slash >= 0 && cleaned[slash] != '/') { slash -= 1; };
if (slash < 0) { discoverroot = "."; }
else if (slash == 0) { discoverroot = "/"; }
else { discoverroot = cleaned[0:slash]; };
if (!pkgmakematcher(cleaned, &matcher)) {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
if (recurse) { anyrecurse = true; };
let canonical: str;
let canonicaloom: bool = false;
if (!pkgcanonicaldir(discoverroot, &canonical, &canonicaloom)) {
if (canonicaloom) { return 1; };
pkgfailpath(discoverroot, "cannot canonicalize package directory");
ds.errors += 1;
if (recurse) { pkgfreematcher(&matcher); };
i += 1;
continue;
};
if (!recurse && !pkgappenddiscovered(&directroots, canonical)) {
return 1;
};
let before: i32 = ds.paths.len;
let errorsbefore: i32 = ds.errors;
if (!recurse) {
pkgdiscoverdir(discoverroot, &ds, nil);
} else {
// MatchDirs applies .*, _*, and testdata pruning to the walk root
// itself as well as descendants. Literal direct selection stays legal.
if (!pkgexcluded(pkgbase(discoverroot))) {
pkgdiscoverdir(discoverroot, &ds, &matcher);
};
pkgfreematcher(&matcher);
};
if (recurse && ds.paths.len == before && ds.errors == errorsbefore) {
pkgput(os.STDERR_FILENO, "ww: warning: ");
pkgputquoted(os.STDERR_FILENO, requested);
pkgputln(os.STDERR_FILENO, " matched no packages");
} else if (!recurse && ds.paths.len == before
&& ds.errors == errorsbefore) {
pkgfailpath(discoverroot, "directory contains no WW package sources");
ds.errors += 1;
};
i += 1;
};
if (ds.errors != 0) { return 1; };
pkgsortstrings(directroots.paths);
directroots.paths = pkgdedup(directroots.paths);
i = 0;
for (i < ds.paths.len) {
let canonicaldir: str;
let canonicaloom: bool = false;
if (!pkgcanonicaldir(pkgdirname(ds.paths[i]), &canonicaldir,
&canonicaloom)) {
if (canonicaloom) { return 1; };
pkgfailpath(ds.paths[i], "cannot canonicalize package directory");
return 1;
};
let canonicalsource: str;
if (!pkgstring(&canonicalsource, canonicaldir, "/",
pkgbase(ds.paths[i]))) { return 1; };
ds.paths[i] = canonicalsource;
i += 1;
};
pkgsort(ds.paths);
ds.paths = pkgdedup(ds.paths);
if (ds.paths.len == 0) {
if (buildonly) {
if (explicitout && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1;
};
if (explicitout) {
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
return 1;
};
return 0;
};
pkgputln(os.STDERR_FILENO, "ww test: no packages to test");
return 1;
};
let sourceallocation: ([]pkgsource | nomem) =
pkgallocsources(ds.paths.len);
let srcs: []pkgsource;
match (sourceallocation) {
case let value: []pkgsource => srcs = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
i = 0;
for (i < ds.paths.len) {
let s: pkgsource;
s.path = ds.paths[i];
if (!pkgstring(&s.dir, pkgdirname(ds.paths[i]))) { return 1; };
s.test = strings.hassuffix(pkgbase(ds.paths[i]), "_test.ww");
if (buildonly && s.test) {
s.pkg = "";
append(srcs, s);
i += 1;
continue;
};
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;
};
if (!pkgstring(&s.pkg, pn)) { return 1; };
append(srcs, s);
i += 1;
};
let folderallocation: ([]pkgfolder | nomem) =
pkgallocfolders(srcs.len);
let folders: []pkgfolder;
match (folderallocation) {
case let value: []pkgfolder => folders = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
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 groupallocation: ([]pkggroup | nomem) =
pkgallocgroups(srcs.len);
let groups: []pkggroup;
match (groupallocation) {
case let value: []pkggroup => groups = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
i = 0;
for (i < folders.len) {
let f: pkgfolder = folders[i];
if (buildonly) {
if (f.prodpkg.len != 0) {
let g: pkggroup;
g.dir = f.path;
g.pkg = f.prodpkg;
g.prodpkg = f.prodpkg;
g.samepkg = "";
g.externalpkg = "";
g.hassame = false;
g.hasexternal = false;
g.notests = false;
g.production = true;
append(groups, g);
} else {
let explicit: bool = false;
let di: i32 = 0;
for (di < directroots.paths.len) {
if (strings.compare(directroots.paths[di], f.path) == 0) {
explicit = true;
break;
};
di += 1;
};
if (explicit) {
pkgfailpath(f.path,
"directory contains no WW package sources");
return 1;
};
};
i += 1;
continue;
};
let family: str = f.prodpkg;
let samepkg: str = "";
let externalpkg: str = "";
let hassame: bool = false;
let hasexternal: bool = false;
let sawtestfile: bool = false;
if (f.prodpkg.len != 0) {
let expectedexternal: str;
if (!pkgstring(&expectedexternal, f.prodpkg, "_test")) {
return 1;
};
let j: i32 = f.start;
for (j < f.end) {
if (srcs[j].test) {
sawtestfile = true;
if (strings.compare(srcs[j].pkg, f.prodpkg) == 0) {
hassame = true;
samepkg = srcs[j].pkg;
} else if (strings.compare(srcs[j].pkg,
expectedexternal) == 0) {
hasexternal = true;
externalpkg = srcs[j].pkg;
} else {
pkgfailpath(srcs[j].path,
"test package must match production package or <package>_test");
return 1;
};
};
j += 1;
};
} else {
let firstpkg: str = "";
let secondpkg: str = "";
let j: i32 = f.start;
for (j < f.end) {
if (srcs[j].test) {
sawtestfile = true;
if (firstpkg.len == 0) {
firstpkg = srcs[j].pkg;
} else if (strings.compare(srcs[j].pkg, firstpkg) != 0
&& secondpkg.len == 0) {
secondpkg = srcs[j].pkg;
} else if (strings.compare(srcs[j].pkg, firstpkg) != 0
&& strings.compare(srcs[j].pkg, secondpkg) != 0) {
pkgfailpath(srcs[j].path,
"test package must match production package or <package>_test");
return 1;
};
};
j += 1;
};
if (secondpkg.len == 0) {
if (strings.hassuffix(firstpkg, "_test")) {
family = firstpkg[0:(firstpkg.len - 5)];
if (family.len == 0) {
pkgfailpath(f.path,
"external test package has empty base name");
return 1;
};
hasexternal = true;
externalpkg = firstpkg;
} else {
family = firstpkg;
hassame = true;
samepkg = firstpkg;
};
} else {
let firstexternal: str;
let secondexternal: str;
if (!pkgstring(&firstexternal, firstpkg, "_test")
|| !pkgstring(&secondexternal, secondpkg, "_test")) {
return 1;
};
if (strings.compare(secondpkg, firstexternal) == 0) {
family = firstpkg;
hassame = true;
samepkg = firstpkg;
hasexternal = true;
externalpkg = secondpkg;
} else if (strings.compare(firstpkg, secondexternal) == 0) {
family = secondpkg;
hassame = true;
samepkg = secondpkg;
hasexternal = true;
externalpkg = firstpkg;
} else {
pkgfailpath(f.path,
"test package must match production package or <package>_test");
return 1;
};
};
};
let g: pkggroup;
g.dir = f.path;
g.pkg = family;
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
g.prodpkg = f.prodpkg;
g.samepkg = samepkg;
g.externalpkg = externalpkg;
g.hassame = hassame;
g.hasexternal = hasexternal;
g.notests = !sawtestfile;
g.production = f.prodpkg.len != 0;
append(groups, g);
i += 1;
};
if (groups.len == 0) {
if (buildonly) {
if (explicitout && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1;
};
if (explicitout) {
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
return 1;
};
return 0;
};
pkgputln(os.STDERR_FILENO, "ww test: no packages to test");
return 1;
};
pkgsortgroups(groups);
let defaultout: bool = false;
if (buildonly && outname.len == 0 && folders.len == 1
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
outname = pkgbase(groups[0].dir);
defaultout = true;
};
if (defaultout && pkgstatdir(outname)) {
pkgput(os.STDERR_FILENO, "ww: build output \"");
pkgput(os.STDERR_FILENO, outname);
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
return 1;
};
let outputdir: bool = buildonly && explicitout && pkgoutputdir(outname);
if (outputdir) {
let mainpackages: i32 = 0;
i = 0;
for (i < groups.len) {
if (strings.compare(groups[i].pkg, "main") == 0) {
mainpackages += 1;
};
i += 1;
};
if (mainpackages == 0) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1;
};
i = 0;
for (i < groups.len) {
if (strings.compare(groups[i].pkg, "main") == 0) {
let slash: i64 = 1i64;
if (strings.hassuffix(outname, "/")) { slash = 0i64; };
let outputlen: i64 = outname.len: i64 + slash
+ pkgbase(groups[i].dir).len: i64;
if (outputlen + 1i64 > os.PATH_MAX: i64) {
pkgputln(os.STDERR_FILENO,
"ww: command output path is too long");
return 1;
};
let j: i32 = 0;
for (j < i) {
if (strings.compare(groups[j].pkg, "main") == 0
&& strings.compare(pkgbase(groups[j].dir),
pkgbase(groups[i].dir)) == 0) {
pkgput(os.STDERR_FILENO,
"ww: multiple commands produce output basename ");
pkgputquoted(os.STDERR_FILENO, pkgbase(groups[i].dir));
pkgput(os.STDERR_FILENO, " in directory ");
pkgputquoted(os.STDERR_FILENO, outname);
pkgputln(os.STDERR_FILENO, "");
return 1;
};
j += 1;
};
};
i += 1;
};
};
// A non-directory caller-owned name cannot fan out. A directory output
// publishes each selected command under its canonical directory basename.
if (outname.len != 0 && !outputdir && groups.len > 1) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot use -o with multiple packages");
return 2;
};
// Recursive/multi-root -S needs a caller-owned artifact tree. Without -w
// every assembly file would otherwise live only in the coordinator's
// temporary plan and disappear on successful return.
if (buildonly && emitasm && workroot.len == 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: recursive -S needs -w");
return 2;
};
let planallocation: ([]pkgplan | nomem) = pkgallocplans(1);
let plans: []pkgplan;
match (planallocation) {
case let value: []pkgplan => plans = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let plan: pkgplan;
plan.dir = groups[0].dir;
plan.identity = "";
if (roots.len == 1 && !anyrecurse) { plan.identity = requestidentity; };
plan.start = 0;
plan.end = groups.len;
plan.state = PKGQUEUED;
plan.buildonly = buildonly;
plan.publish = buildonly && explicitout && outname.len != 0
&& groups.len == 1
&& strings.compare(groups[0].pkg, "main") != 0;
plan.emitasm = emitasm;
append(plans, plan);
let tmproot: str = temp.dir();
let failed: i32 = 0;
i = 0;
for (i < plans.len) {
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
compileonly, buildonly, outname, outputdir, workroot)) {
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 handleallocation: ([]exec.process | nomem) =
pkgallocprocesses(plans.len);
let handles: []exec.process;
match (handleallocation) {
case let value: []exec.process => handles = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO,
"wwtest package: cleanup failed; retained ");
pkgputln(os.STDERR_FILENO, tmproot);
};
return 1;
};
};
i = 0;
for (i < plans.len) {
let h: exec.process;
append(handles, h);
i += 1;
};
let runhandleallocation: ([]exec.process | nomem) =
pkgallocprocesses(groups.len);
let runhandles: []exec.process;
match (runhandleallocation) {
case let value: []exec.process => runhandles = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO,
"wwtest package: cleanup failed; retained ");
pkgputln(os.STDERR_FILENO, tmproot);
};
return 1;
};
};
i = 0;
for (i < groups.len) {
let h: exec.process;
append(runhandles, h);
groups[i].state = PKGQUEUED;
groups[i].runstartfailed = false;
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, buildonly)) {
g.state = PKGDONE;
productcompleted += 1;
} else if (g.notests) {
g.state = PKGDONE;
productcompleted += 1;
} else {
if (!pkgstartrun(g, filters, timeoutarg, list,
&runhandles[gi])) {
g.runstartfailed = true;
g.state = PKGDONE;
productcompleted += 1;
} else {
g.state = PKGRUNNING;
active += 1;
};
};
filling = true;
break;
};
gi += 1;
};
};
if (!filling && planlaunched < plans.len) {
if (!pkgstartbuild(&plans[planlaunched], groups, builder,
includes, libdirs, libs, &handles[planlaunched])) {
if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO,
"wwtest package: cleanup failed; retained ");
pkgputln(os.STDERR_FILENO, tmproot);
};
return 1;
};
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, buildonly);
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;
};