Files
ww/internal/wwpackage/package.ww

2882 lines
79 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,
basename: str,
testname: str,
publish: 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,
installoutput: str,
state: i32,
runstartfailed: bool,
runres: exec.result,
publicbin: bool,
installattempted: bool,
installres: 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,
outputpatherror: bool,
defaultoutputdir: str,
outputcollisionbase: str,
outputcollisiondir: str,
suppressbuildreports: bool,
deferpublish: 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 pkgalloci32(cap: i32) ([]i32 | nomem) = {
let value: []i32 = 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));
};
};
fn pkgenvkeylen(entry: str) i32 = {
let i: i32 = 0;
for (i < entry.len) {
if (entry[i] == '=') { return i; };
i += 1;
};
return -1;
};
fn pkgenvkeyequal(a: str, an: i32, b: str, bn: i32) bool = {
if (an != bn) { return false; };
let i: i32 = 0;
for (i < an) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn pkgenvkeyis(entry: str, n: i32, name: str) bool = {
return pkgenvkeyequal(entry, n, name, name.len);
};
fn pkgenvhash(entry: str, n: i32) u64 = {
let h: u64 = 14695981039346656037u64;
let i: i32 = 0;
for (i < n) {
h = h ^ (entry[i]: u64);
h = h * 1099511628211u64;
i += 1;
};
return h;
};
fn pkgenvtablecap(entries: i32) i32 = {
if (entries == 0) { return 0; };
if (entries > PKG_COUNT_MAX / 2) { return -1; };
let need: i32 = entries * 2;
let cap: i32 = PKG_INITIAL_CAP;
for (cap < need) {
if (cap > PKG_COUNT_MAX / 2) { return -1; };
cap *= 2;
};
return cap;
};
// Go snapshots user-test variables through Unix os.Environ before appending
// PATH and PWD. WW's raw environment walker and executor do no such duplicate
// normalization, so the launch owner must materialize that snapshot itself.
fn pkgenvfirst(slots: []i32, inherited: []str, at: i32, n: i32) bool = {
let slot: i32 = (pkgenvhash(inherited[at], n)
% (slots.len: u64)): i32;
let probes: i32 = 0;
for (probes < slots.len) {
let prior: i32 = slots[slot];
if (prior < 0) {
slots[slot] = at;
return true;
};
let priorn: i32 = pkgenvkeylen(inherited[prior]);
if (pkgenvkeyequal(inherited[at], n,
inherited[prior], priorn)) {
return false;
};
slot += 1;
if (slot == slots.len) { slot = 0; };
probes += 1;
};
return false;
};
// Go's appended PATH and PWD survive os/exec's later-value selection. WW's
// executor preserves its concrete vector, so inherited copies are excluded.
fn runenv(pwd: str, builder: str, out: *[]str, pathowned: *str,
pwdowned: *str) bool = {
let toolbin: str;
let oom: bool = false;
if (!pkgcanonicaldir(pkgdirname(builder), &toolbin, &oom)) {
if (!oom) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot determine toolchain directory");
};
return false;
};
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 keyed: i32 = 0;
let i: i32 = 0;
for (i < inherited.len) {
let n: i32 = pkgenvkeylen(inherited[i]);
if (n >= 0 && !pkgenvkeyis(inherited[i], n, "PATH")
&& !pkgenvkeyis(inherited[i], n, "PWD")) {
keyed += 1;
};
i += 1;
};
let tablecap: i32 = pkgenvtablecap(keyed);
if (tablecap < 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
pkgfreestrs(env);
return false;
};
let slots: []i32;
slots.ptr = nil: *i32;
slots.len = 0;
slots.cap = 0;
if (tablecap != 0) {
let tableallocation: ([]i32 | nomem) = pkgalloci32(tablecap);
match (tableallocation) {
case let value: []i32 => slots = value;
case nomem => {
pkgputln(os.STDERR_FILENO,
"wwtest package: out of memory");
pkgfreestrs(env);
return false;
};
};
slots.len = tablecap;
i = 0;
for (i < slots.len) { slots[i] = -1; i += 1; };
};
i = 0;
for (i < inherited.len) {
let n: i32 = pkgenvkeylen(inherited[i]);
if (n < 0) {
if (inherited[i].len != 0) { append(env, inherited[i]); };
} else if (!pkgenvkeyis(inherited[i], n, "PATH")
&& !pkgenvkeyis(inherited[i], n, "PWD")
&& pkgenvfirst(slots, inherited, i, n)) {
append(env, inherited[i]);
};
i += 1;
};
if (slots.ptr != nil) {
os.free(slots.ptr: *void,
(slots.cap: u64) * (size(i32): u64));
};
let pathenv: str;
let pathok: bool = false;
match (os.getenv("PATH")) {
case let inheritedpath: str => {
if (inheritedpath.len == 0) {
pathok = pkgstring(&pathenv, "PATH=", toolbin);
} else {
pathok = pkgstring(&pathenv, "PATH=", toolbin, ":",
inheritedpath);
};
};
case void => pathok = pkgstring(&pathenv, "PATH=", toolbin);
};
if (!pathok) {
pkgfreestrs(env);
return false;
};
let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) {
pkgfreeownedstr(pathenv);
pkgfreestrs(env);
return false;
};
append(env, pathenv);
append(env, pwdenv);
*out = env;
*pathowned = pathenv;
*pwdowned = pwdenv;
return true;
};
fn freerunenv(env: []str, pathowned: str, pwdowned: str) void = {
pkgfreeownedstr(pathowned);
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");
};
// Go's final test-status action is command-owned: after an explicit ordinary
// package request records a setup, build, or run failure, it prints one final
// FAIL after the ordered package results. Implicit current-directory testing
// and compile-only testing deliberately do not use this action.
fn pkgteststatusfail(explicitstatus: bool, compileonly: bool) int = {
if (explicitstatus && !compileonly) {
pkgputln(os.STDOUT_FILENO, "FAIL");
};
return 1;
};
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 pkgfailsource(path: str, line: i32, col: i32, reason: str) void = {
pkgput(os.STDERR_FILENO, path);
pkgput(os.STDERR_FILENO, ":");
pkgput(os.STDERR_FILENO, strconv.i32tos(line, strconv.base.DEC));
pkgput(os.STDERR_FILENO, ":");
pkgput(os.STDERR_FILENO, strconv.i32tos(col, strconv.base.DEC));
pkgput(os.STDERR_FILENO, ": error: ");
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 without running; -o retains and still runs unless -c is present\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 pkgnulerrors(path: str, src: str) bool = {
let i: i32 = 0;
let ln: i32 = 1;
let cl: i32 = 1;
let found: bool = false;
for (i < src.len) {
if (src[i] == 0u8) {
pkgfailsource(path, ln, cl, "invalid NUL character");
found = true;
};
if (src[i] == '\n') {
ln += 1;
cl = 1;
} else {
cl += 1;
};
i += 1;
};
return found;
};
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 = {
// Match the compiler readers: one UTF-8 BOM is invisible only at the
// first raw source position. The full stage driver owns later-BOM errors.
let start: i32 = 0;
if (src.len >= 3 && src[0] == 0xefu8 && src[1] == 0xbbu8
&& src[2] == 0xbfu8) { start = 3; };
let i: i32 = pkgskipspace(src, start);
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;
};
// Directory test binaries are presentation artifacts. Their Go-like visible
// basename comes from the selected canonical import spelling, never from the
// declared package name or any test variant. A physical root is the fallback
// when the request has no explicit logical identity.
fn pkgimportbase(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 pkgisabs(path: str) bool = {
return path.len != 0 && path[0] == '/';
};
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; each temporary cwd change is restored before
// it plans further paths or launches another child.
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,
createdir: str, 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 = createdir;
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;
};
g.publicbin = false;
g.installattempted = false;
if (buildonly) {
if (outputdir && strings.compare(g.pkg, "main") == 0
&& !p.outputpatherror && !p.emitasm) {
if (!pkgjoinpath(outname, g.basename, &g.bin)) { return false; };
g.publicbin = true;
} else if (outname.len != 0 && !outputdir) {
g.bin = outname;
g.publicbin = true;
}
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
} else {
if (!pkgstring(&g.bin, g.root, "/package.test")) { return false; };
};
if (!pkgstring(&g.runoutput, g.root, "/test.output")
|| !pkgstring(&g.installoutput, g.root, "/install.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 = 24;
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += nproducts * 10;
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.outputpatherror) {
append(ba, "--ww-command-output-path-error");
};
if (p.defaultoutputdir.len != 0) {
append(ba, "--ww-default-output-dir");
append(ba, p.defaultoutputdir);
};
if (p.outputcollisionbase.len != 0) {
append(ba, "--ww-command-output-collision");
append(ba, p.outputcollisionbase);
append(ba, p.outputcollisiondir);
};
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 && g.publicbin) { append(ba, "build-public"); }
else 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);
if (g.publish.len != 0 && !p.deferpublish) { append(ba, g.publish); }
else { append(ba, "-"); };
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, builder: str, 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 pathowned: str;
let pwdowned: str;
if (!runenv(g.dir, builder, &env, &pathowned, &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, pathowned, pwdowned);
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return true;
};
// Pinned builderTest orders a retained running test as build -> run ->
// BuildInstallFunc. Re-enter the selected stage driver for the final action so
// build and test share one overwrite predicate instead of copying object magic
// into the coordinator.
fn pkginstalloutput(g: *pkggroup, builder: str) void = {
let ia: []str = [builder, "test", "--ww-install-test-output",
g.bin, g.publish];
let env: []str = os.getenvs();
let icmd: exec.command;
icmd.path = builder;
icmd.argv = ia;
icmd.env = env;
icmd.dir = "";
icmd.stdoutpath = g.installoutput;
icmd.stderrpath = g.installoutput;
icmd.deadline.sec = 0i64;
icmd.deadline.nsec = 0i64;
icmd.grace = 0i64: time.duration;
let h: exec.process;
g.installattempted = true;
exec.start(&h, &icmd);
for (!exec.poll(&h)) { time.sleep(pkgpoll, time.clock.monotonic); };
g.installres = h.result;
};
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 pkginstallok(g: *pkggroup) bool = {
return g.installres.errno == 0 && g.installres.cleanuperrno == 0
&& g.installres.termination == exec.termination.EXIT
&& g.installres.code == 0;
};
fn pkgemitinstall(g: *pkggroup) bool = {
if (!g.installattempted) { return true; };
let output: str;
if (!pkgread(g.installoutput, &output)) {
pkgfailpath(g.root, "cannot read install capture");
return false;
};
if (output.len != 0) {
pkgput(os.STDERR_FILENO, output);
if (output[output.len - 1] != '\n') {
pkgput(os.STDERR_FILENO, "\n");
};
};
if (!pkginstallok(g)) {
if (output.len == 0) {
pkgreportcommand(os.STDERR_FILENO, "install", g,
&g.installres);
};
return false;
};
return true;
};
// Pinned cmd/go recognizes the testing package's exact no-tests warning only
// at capture byte zero or after a newline. The harness owns whether a test ran;
// this coordinator owns only the corresponding package-result suffix.
fn pkgnoteststorun(output: str) bool = {
let first: str = "testing: warning: no tests to run\n";
let later: str = "\ntesting: warning: no tests to run\n";
return strings.hasprefix(output, first) || strings.contains(output, later);
};
fn pkgemitgroup(g: *pkggroup, compileonly: bool,
statusfailed: *bool) bool = {
if (g.notests) {
pkgput(os.STDOUT_FILENO, "? ");
pkgput(os.STDOUT_FILENO, g.dir);
pkgputln(os.STDOUT_FILENO, " [no test files]");
return true;
};
if (compileonly) {
// Go's compile-only print action is a nop after the retained binary
// install completes. Build and publication failures still diagnose on
// stderr through the plan result above.
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 (runoutput.len != 0 && runoutput[runoutput.len - 1] != '\n') {
pkgput(os.STDOUT_FILENO, "\n");
};
if (!pkgrunok(g)) {
*statusfailed = true;
pkgreportcommand(os.STDOUT_FILENO, "test", g, &g.runres);
return false;
};
pkgput(os.STDOUT_FILENO, "ok ");
pkglabel(g);
if (pkgnoteststorun(runoutput)) {
pkgput(os.STDOUT_FILENO, " [no tests to run]");
};
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, statusfailed: *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)) {
if (!p.suppressbuildreports) {
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
&p.buildres);
};
if (!buildonly && !compileonly) { *statusfailed = true; };
failed += 1;
} else if (buildonly) {
void;
} else {
if (!pkgemitgroup(&groups[i], compileonly, statusfailed)) {
failed += 1;
};
if (!compileonly && !pkgemitinstall(&groups[i])) { 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;
let explicitstatus: 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-explicit-test-target") == 0) {
if (explicitstatus || buildonly) { pkgusage(); return 2; };
explicitstatus = true;
i += 1;
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);
let buildnull: bool = buildonly && explicitout
&& strings.compare(outname, "/dev/null") == 0;
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;
};
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) {
if (ds.fatal) { return 1; };
return pkgteststatusfail(explicitstatus, compileonly);
};
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 pkgteststatusfail(explicitstatus, compileonly);
};
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 && !buildnull && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1;
};
if (explicitout && !buildnull) {
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)) {
pkgfailpath(ds.paths[i], "invalid or missing package clause");
return pkgteststatusfail(explicitstatus, compileonly);
};
if (pkgnulerrors(ds.paths[i], body)) {
return pkgteststatusfail(explicitstatus, compileonly);
};
if (!pkgclause(body, &pn)) {
pkgfailpath(ds.paths[i], "invalid or missing package clause");
return pkgteststatusfail(explicitstatus, compileonly);
};
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.testname = "";
g.publish = "";
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 pkgteststatusfail(explicitstatus,
compileonly);
};
};
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 pkgteststatusfail(explicitstatus,
compileonly);
};
};
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 pkgteststatusfail(explicitstatus,
compileonly);
};
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 pkgteststatusfail(explicitstatus, compileonly);
};
};
};
let g: pkggroup;
g.dir = f.path;
g.pkg = family;
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
g.basename = "";
g.testname = "";
g.publish = "";
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 && !buildnull && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1;
};
if (explicitout && !buildnull) {
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);
i = 0;
for (i < groups.len) {
groups[i].basename = pkgbase(groups[i].dir);
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
groups[i].basename = pkgimportbase(requestidentity);
};
i += 1;
};
let defaultout: bool = false;
if (buildonly && !buildnull && outname.len == 0 && folders.len == 1
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
outname = groups[0].basename;
defaultout = true;
};
let defaultoutputdir: str = "";
if (defaultout && pkgstatdir(outname)) {
defaultoutputdir = outname;
};
let outputdir: bool = buildonly && explicitout && !buildnull
&& pkgoutputdir(outname);
let outputpatherror: bool = false;
let outputcollisionbase: str = "";
let outputcollisiondir: str = "";
if (outputdir) {
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
+ groups[i].basename.len: i64;
if (outputlen + 1i64 > os.PATH_MAX: i64) {
outputpatherror = true;
};
let j: i32 = 0;
for (j < i) {
if (strings.compare(groups[j].pkg, "main") == 0
&& strings.compare(groups[j].basename,
groups[i].basename) == 0
&& outputcollisionbase.len == 0) {
outputcollisionbase = groups[i].basename;
outputcollisiondir = outname;
};
j += 1;
};
};
i += 1;
};
};
// Go's test binary is always linked into request-private storage. -c and
// -o independently request a caller-visible executable copy; only -c
// suppresses execution. Visible names are import-leaf metadata and never
// action, package, variant, symbol, or persistence identity.
let testretain: bool = !buildonly && (compileonly || explicitout);
let testnull: bool = !buildonly && explicitout
&& strings.compare(outname, "/dev/null") == 0;
let testoutdir: bool = !buildonly && explicitout && !testnull
&& pkgoutputdir(outname);
let invocationdir: str = "";
if (testretain) {
let cwdoom: bool = false;
if (!pkgcanonicaldir(".", &invocationdir, &cwdoom)) {
if (!cwdoom) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot determine invocation directory");
};
return 1;
};
};
i = 0;
for (i < groups.len) {
if (!pkgstring(&groups[i].testname, groups[i].basename, ".test")) {
return 1;
};
groups[i].publish = "";
if (testretain && !groups[i].notests && !testnull) {
if (!explicitout) {
if (!pkgjoinpath(invocationdir, groups[i].testname,
&groups[i].publish)) { return 1; };
} else if (testoutdir) {
let targetdir: str = outname;
if (!pkgisabs(outname)
&& !pkgjoinpath(invocationdir, outname, &targetdir)) {
return 1;
};
if (!pkgjoinpath(targetdir, groups[i].testname,
&groups[i].publish)) { return 1; };
} else if (pkgisabs(outname)) {
groups[i].publish = outname;
} else if (!pkgjoinpath(invocationdir, outname,
&groups[i].publish)) { return 1; };
};
i += 1;
};
if (!buildonly && explicitout && groups.len > 1
&& !testnull && !testoutdir) {
pkgputln(os.STDERR_FILENO,
"ww test: with multiple packages, -o must refer to a directory or /dev/null");
return 1;
};
if (!buildonly && groups.len > 1 && testretain && !testnull) {
i = 0;
for (i < groups.len) {
let j: i32 = 0;
for (j < i) {
if (strings.compare(groups[j].testname,
groups[i].testname) == 0) {
pkgput(os.STDERR_FILENO,
"ww test: cannot write test binary ");
pkgput(os.STDERR_FILENO, groups[i].testname);
pkgputln(os.STDERR_FILENO,
" for multiple packages:");
let k: i32 = 0;
for (k < groups.len) {
if (strings.compare(groups[k].testname,
groups[i].testname) == 0) {
pkgputln(os.STDERR_FILENO, groups[k].dir);
};
k += 1;
};
return 1;
};
j += 1;
};
i += 1;
};
};
// A non-directory caller-owned build name cannot fan out. A directory
// build output publishes each selected command under its directory leaf.
if (buildonly && !buildnull && 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 && !buildnull) {
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 && !buildnull && outname.len != 0
&& groups.len == 1
&& strings.compare(groups[0].pkg, "main") != 0;
plan.emitasm = emitasm;
plan.outputpatherror = outputpatherror;
plan.defaultoutputdir = defaultoutputdir;
plan.outputcollisionbase = outputcollisionbase;
plan.outputcollisiondir = outputcollisiondir;
plan.suppressbuildreports = buildonly
&& (outputdir || defaultoutputdir.len != 0);
plan.deferpublish = !buildonly && !compileonly && testretain && !testnull;
append(plans, plan);
let createdir: str = "";
if (buildonly && outputdir) {
createdir = outname;
} else if (!buildonly && compileonly && testretain && !testnull) {
i = 0;
for (i < groups.len) {
if (groups[i].publish.len != 0) {
createdir = pkgdirname(groups[i].publish);
break;
};
i += 1;
};
};
let tmproot: str = temp.dir();
let planout: str = outname;
if (buildnull) { planout = ""; };
let failed: i32 = 0;
let statusfailed: bool = false;
i = 0;
for (i < plans.len) {
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
compileonly, buildonly, planout, outputdir, createdir,
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,
builder, &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); };
};
// A failed, signalled, timed-out, or unstartable run propagates to its Go
// install action, so it preserves any prior retained binary. Successful
// products install independently after their private execution.
if (!compileonly) {
i = 0;
for (i < groups.len) {
let g: *pkggroup = &groups[i];
if (g.publish.len != 0 && !g.notests
&& pkgproductbuilt(g, buildonly) && !g.runstartfailed
&& pkgrunok(g)) {
pkginstalloutput(g, builder);
};
i += 1;
};
};
i = 0;
for (i < plans.len) {
failed += pkgemitplan(&plans[i], groups, compileonly, buildonly,
&statusfailed);
i += 1;
};
if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO, "wwtest package: cleanup failed; retained ");
pkgputln(os.STDERR_FILENO, tmproot);
failed += 1;
};
if (statusfailed && explicitstatus && !compileonly) {
pkgputln(os.STDOUT_FILENO, "FAIL");
};
if (failed != 0) { return 1; };
return 0;
};