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, 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, 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 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 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; }; // 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 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, 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; }; 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 (!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) / 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.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); if (g.publish.len != 0) { 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, 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) { // 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 (!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; }; // -c suppresses execution and requests a caller-visible binary. Keep it // separate from -w until compile-only persistent-action ownership is wired; // -o without -c already permits a retained copy beside a persistent store. 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.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 _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 _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 _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.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 && 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; }; }; // 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) { let leaf: str = pkgbase(groups[i].dir); if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) { leaf = pkgimportbase(requestidentity); }; if (!pkgstring(&groups[i].testname, leaf, ".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 && 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 createdir: str = ""; if (buildonly && outputdir) { createdir = outname; } else if (!buildonly && 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 failed: i32 = 0; i = 0; for (i < plans.len) { if (!pkgsetplanpaths(&plans[i], groups, tmproot, i, compileonly, buildonly, outname, 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, &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; };