Files
ww/selfhost/cmd/ww/main.ww

9934 lines
312 KiB
Plaintext

// selfhost/cmd/ww/main.ww — port of cmd/ww/main.c.
//
// The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue:
//
// ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ;
// w6l -o foo foo.o libwwrt.a
// ww run foo.ww → build then exec
// ww version → print version
//
// Tool paths default to siblings of $0 so a fresh build runs out of
// out/bin/. WW_W6C / WW_W6A / WW_W6L select exact executable paths.
// WW_SRCLIB selects package sources and WW_LIB selects runtime artifacts.
package main;
import crypto.sha256;
import hash;
import os;
import os.exec;
import strings;
import syntax;
// All path/string scratch buffers go on the runtime page allocator.
// One page is plenty for any path we build. PATH_MAX lives in lib/os
// (os.PATH_MAX: i32 = 4096) — the duplicate u64 def was dropped to close
// the cgen #127 mod-mangle attribution bug consumer per rule-7.
def CMD_MAX: u64 = 8192u64;
def SEP_LOCAL_IMPORT_PREFIX: str = "__wwlocal";
let selfpath: *u8;
let sepfatalallocation: bool;
@symbol("rt_envp") fn rawenvp() **u8;
// Tool-local (NOT a lib wrapper): messages are built from many
// fragments and we route through os.write to avoid libc stdio.
// .len replaces the error-prone hand-counted byte literals these sites
// carried. Lives here (the selfhost ww-driver build is a single main.ww;
// err.c's bare-message path is not ported into this tree).
fn cerr(m: str) void = {
os.write(2, m.ptr, m.len: u64);
};
fn sepfailsize() bool = {
sepfatalallocation = true;
cerr("ww: package graph is too large\n");
return false;
};
fn sepfailnomem() bool = {
sepfatalallocation = true;
cerr("ww: out of memory\n");
return false;
};
fn cerrnum(v: i32) void = {
let digits: [16]u8;
let n: i32 = 0;
let x: i32 = v;
if (x <= 0) { digits[n] = '0'; n += 1; }
else {
for (x > 0) {
digits[n] = ((x % 10) + ('0': i32)): u8;
n += 1;
x = x / 10;
};
};
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
};
fn cerrpos(file: str, line: i32, col: i32) void = {
cerr(file); cerr(":"); cerrnum(line); cerr(":"); cerrnum(col);
};
fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
return n;
};
fn rawenvvalue(name: str) *u8 = {
let env: **u8 = rawenvp();
let i: i32 = 0;
for (env[i] != nil) {
let entry: *u8 = env[i];
let j: i32 = 0;
for (j < name.len && entry[j] == name[j]) { j += 1; };
if (j == name.len && entry[j] == '=': u8) {
return entry + ((j + 1): u64);
};
i += 1;
};
return nil;
};
fn clipathfits(command: str, flag: str, value: *u8) bool = {
if (cstrlen(value) < os.PATH_MAX: u64) { return true; };
cerr("ww "); cerr(command); cerr(": "); cerr(flag);
cerr(" path is too long\n");
return false;
};
// Bridges the driver's argv-style *u8 paths to lib/os entrypoints
// (str post-task-#23).
fn pathstr(p: *u8) str = {
let r: str;
r.ptr = p;
r.len = cstrlen(p): i32;
return r;
};
fn cstreq(a: *u8, b: *u8) bool = {
return strings.compare(pathstr(a), pathstr(b)) == 0;
};
fn cstreqlit(a: *u8, lit: str) bool = {
return strings.compare(pathstr(a), lit) == 0;
};
fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
let i: u64 = 0u64;
for (i < n) {
dst[i] = src[i];
i += 1u64;
};
};
fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
let i: u64 = 0u64;
for (src[i] != 0u8) {
dst[off + i] = src[i];
i += 1u64;
};
return off + i;
};
fn strinto(dst: *u8, off: u64, src: str) u64 = {
let n: i32 = src.len;
let i: i32 = 0;
for (i < n) {
let iu: u64 = i: u64;
dst[off + iu] = src[i];
i += 1;
};
let nu: u64 = n: u64;
return off + nu;
};
fn byteinto(dst: *u8, off: u64, c: u8) u64 = {
dst[off] = c;
return off + 1u64;
};
fn cstrseal(dst: *u8, off: u64) void = {
dst[off] = 0u8;
};
fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
let n: u64 = cstrlen(argv0);
let cut: u64 = n;
let i: u64 = 0u64;
for (i < n) {
if (argv0[i] == 47u8) { cut = i; };
i += 1u64;
};
if (cut == n) {
dst[0u64] = 46u8;
dst[1u64] = 0u8;
return;
};
if (cut == 0u64) { cut = 1u64; };
if (cut + 1u64 >= dstsz) { cut = dstsz - 2u64; };
bytecpy(dst, argv0, cut);
dst[cut] = 0u8;
};
fn joinpath(dir: *u8, name: *u8) *u8 = {
let need: u64 = cstrlen(dir) + 1u64 + cstrlen(name) + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, dir);
off = byteinto(buf.ptr, off, 47u8);
off = cstrinto(buf.ptr, off, name);
cstrseal(buf.ptr, off);
return buf.ptr;
};
fn joinpathlit(dir: *u8, name: str) *u8 = {
let need: u64 = cstrlen(dir) + 1u64 + name.len: u64 + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, dir);
off = byteinto(buf.ptr, off, 47u8);
off = strinto(buf.ptr, off, name);
cstrseal(buf.ptr, off);
return buf.ptr;
};
fn owncstr(s: str) *u8 = {
let b: []u8 = alloc([], (s.len + 1): u64)!;
b.len = s.len + 1;
let i: i32 = 0;
for (i < s.len) { b[i] = s[i]; i += 1; };
b[s.len] = 0u8;
return b.ptr;
};
fn reservedimport(s: str) bool = {
let prefix: str = SEP_LOCAL_IMPORT_PREFIX;
if (s.len < prefix.len) { return false; };
let i: i32 = 0;
for (i < prefix.len) {
if (s[i] != prefix[i]) { return false; };
i += 1;
};
return s.len == prefix.len || s[prefix.len] == '.': u8;
};
fn reservedimportpath(p: *u8) bool = {
return reservedimport(pathstr(p));
};
// The driver is single-threaded while constructing a graph. Saving and
// restoring cwd gives WWstage the same symlink-resolved absolute directory
// spelling that Cstage obtains from realpath, without adding a library API.
fn canonicaldir(path: str) *u8 = {
let before: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &before)) { return nil; };
let bn: i64 = os.getcwd(before.ptr, before.len: u64);
if (bn <= 1i64 || bn > before.len: i64) { return nil; };
if (os.chdir(path) != 0) { return nil; };
let after: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &after)) {
os.chdir(pathstr(before.ptr));
return nil;
};
let an: i64 = os.getcwd(after.ptr, after.len: u64);
let restored: i32 = os.chdir(pathstr(before.ptr));
if (restored != 0) {
cerr("ww: cannot restore current directory\n");
return nil;
};
if (an <= 1i64 || an > after.len: i64) { return nil; };
return sepdupcstr(after.ptr, (an - 1i64): u64);
};
fn envpath(name: str) *u8 = {
match (os.getenv(name)) {
case let p: str => {
if (p.len != 0) { return sepdupcstr(p.ptr, p.len: u64); };
};
case void => void;
};
return nil;
};
fn toolpath(selfdir: *u8, envvar: str, name: str) *u8 = {
let p: *u8 = envpath(envvar);
if (p != nil) { return p; };
return sepjoinpathlit(selfdir, name);
};
// execpackagetests — replace the driver with the native WW package
// coordinator for directory/default invocations. Explicit generated
// package.ww roots stay on runsingletest, which is the recursion boundary when
// wwtest builds its already-aggregated package binaries.
fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
targetindex: i32, resolved: *u8, rootidentity: *u8, adddot: bool,
buildonly: bool) i32 = {
let prog: *u8 = rawenvvalue("WW_WWTEST");
if (prog == nil || prog[0u64] == 0u8) {
prog = sepjoinpathlit(selfdir, "wwtest");
if (prog == nil) { return 1; };
};
if (start < 0 || start > argc || argc - start > SEP_COUNT_MAX - 11) {
sepfailsize();
return 1;
};
let cap: i32 = argc - start + 11;
let execargv: []*u8;
let argvallocation: ([]*u8 | nomem) = sepallocptrs(cap);
match (argvallocation) {
case let value: []*u8 => { execargv = value; execargv.len = cap; };
case nomem => {
if (buildonly) {
cerr("ww build: cannot allocate package coordinator arguments\n");
} else {
cerr("ww test: cannot allocate package coordinator arguments\n");
};
return 1;
};
};
let n: i32 = 0;
execargv[n] = prog; n += 1;
execargv[n] = "package".ptr; n += 1;
if (buildonly) {
execargv[n] = "--ww-operation".ptr; n += 1;
execargv[n] = "build".ptr; n += 1;
};
execargv[n] = "--ww-driver".ptr; n += 1;
execargv[n] = selfpath; n += 1;
if (!buildonly && !adddot) {
execargv[n] = "--ww-explicit-test-target".ptr; n += 1;
};
if (rootidentity != nil) {
execargv[n] = "--ww-root-identity".ptr; n += 1;
execargv[n] = rootidentity; n += 1;
};
let dotted: bool = false;
let i: i32 = start;
for (i < argc) {
if (adddot && !dotted && cstreqlit(argv[i], "--")) {
execargv[n] = ".".ptr; n += 1;
dotted = true;
};
if (resolved != nil && i == targetindex) { execargv[n] = resolved; }
else { execargv[n] = argv[i]; };
n += 1;
i += 1;
};
if (adddot && !dotted) { execargv[n] = ".".ptr; n += 1; };
execargv[n] = nil;
os.execve(pathstr(prog), execargv.ptr, rawenvp());
if (buildonly) { cerr("ww build: cannot exec package coordinator\n"); }
else { cerr("ww test: cannot exec package coordinator\n"); };
return 1;
};
// The separate-compilation producer scans each unit's top-of-file
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
// search path. A per-scan visited set (linear; typical builds visit a
// handful of modules) breaks cycles. (E3-C1: the legacy single-file
// source concatenator was deleted — sep is the sole path. Task #87.)
type strnode = struct {
s: str,
snext: *strnode,
};
type expctx = struct {
out: i32, // fd we're writing the sep unit source to
dirs: *u8, // ":"-separated search path (NUL-terminated)
visit: *strnode,
};
fn visitseen(c: *expctx, path: str) bool = {
let n: *strnode = c.visit;
for (n != nil) {
if (n.s.len == path.len) {
let i: i32 = 0;
let eq: bool = true;
for (i < path.len) {
if (n.s[i] != path[i]) { eq = false; i = path.len; }
else { i += 1; };
};
if (eq) { return true; };
};
n = n.snext;
};
return false;
};
fn visitalloc(c: *expctx, path: str) (*strnode | nomem) = {
let n: *strnode = alloc(strnode{s=path, snext=c.visit})?;
return n;
};
fn visitadd(c: *expctx, path: str) bool = {
let allocation: (*strnode | nomem) = visitalloc(c, path);
match (allocation) {
case let n: *strnode => { c.visit = n; return true; };
case nomem => { sepfailnomem(); return false; };
};
return false;
};
// Translate dots in an `import` name to slashes for path lookup.
// `encoding.utf8` → `encoding/utf8`. Mirrors Hare's hare(1)
// use-path → fs-path mapping
// (ref/hare/hare/module/srcs.ha:78 builds the same shape via
// path::push per ident part).
fn importpathform(name: *u8, namelen: u64) *u8 = {
let buf: []u8 = alloc([], namelen + 1u64)!;
let i: u64 = 0u64;
for (i < namelen) {
if (name[i] == 46u8) { buf[i] = 47u8; } // '.' -> '/'
else { buf[i] = name[i]; };
i += 1u64;
};
buf[namelen] = 0u8;
return buf.ptr;
};
// Try <dir>/<path>/ as a directory (wantdir != 0), else
// <dir>/<path>.ww as a CLI target. Source-import loading calls only the
// directory arm; the file arm belongs exclusively to locatemodule.
fn locatein(dir: *u8, dirlen: u64,
pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = {
let tail: u64 = 1u64; // NUL for the directory form
if (wantdir == 0i32) { tail = 4u64; }; // ".ww" + NUL
if (dirlen + 1u64 + pflen + tail > os.PATH_MAX: u64) {
return nil;
};
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
let off: u64 = 0u64;
let i: u64 = 0u64;
for (i < dirlen) { buf[off + i] = dir[i]; i += 1u64; };
off += dirlen;
buf[off] = 47u8; off += 1u64; // '/'
i = 0u64;
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
off += pflen;
if (wantdir != 0i32) {
buf[off] = 0u8;
let fi: os.filestat;
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr));
match (r) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) {
*isdir = 1;
return buf.ptr;
};
};
case let e: os.oserror => void;
};
return nil;
};
buf[off] = 46u8; off += 1u64; // '.'
buf[off] = 119u8; off += 1u64; // 'w'
buf[off] = 119u8; off += 1u64; // 'w'
buf[off] = 0u8;
if (os.access(pathstr(buf.ptr), 0i32) == 0) {
*isdir = 0;
return buf.ptr;
};
return nil;
};
// Walk every ordered root for <root>/<path>/ only. A sibling or earlier-root
// <path>.ww is neither a match nor a shadow: every source import identifies
// one canonical directory package.
fn locateimport(dirs: *u8, name: *u8, namelen: u64) *u8 = {
let pathform: *u8 = importpathform(name, namelen);
let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
let q: u64 = p;
for (q < total) {
if (dirs[q] == ':') { break; };
q += 1u64;
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
let isdir: i32 = 0;
let hit: *u8 = locatein(dirs + p, seglen,
pathform, pflen, &isdir, 1i32);
if (hit != nil) { return hit; };
};
p = q + 1u64;
};
return nil;
};
// CLI target compatibility: directory packages win globally, then a bare
// target may resolve to <root>/<path>.ww. Never called for a source import.
fn locatemodule(dirs: *u8, name: *u8, namelen: u64,
isdir: *i32) *u8 = {
let hit: *u8 = locateimport(dirs, name, namelen);
if (hit != nil) { *isdir = 1; return hit; };
let pathform: *u8 = importpathform(name, namelen);
let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
let q: u64 = p;
for (q < total) {
if (dirs[q] == ':') { break; };
q += 1u64;
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
hit = locatein(dirs + p, seglen, pathform, pflen,
isdir, 0i32);
if (hit != nil) { return hit; };
};
p = q + 1u64;
};
return nil;
};
// Go's contract: only *_test.ww is a test source. Ask the compiler parser,
// rather than a textual attribute scan, whether a production source contains
// @test; otherwise valid whitespace/comments could silently drop a test.
fn dirfileattest(dirpath: *u8, name: *u8) i32 = {
let path: *u8 = joinpath(dirpath, name);
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return -1; };
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 -1; };
};
if (n < 0i64) { os.close(fd); return -1; };
let b: []u8 = alloc([], n: u64)!;
b.len = n: 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 -1;
};
if (got != n) { return -1; };
// Preserve the directory loader's normalized package-clause diagnostic
// before the full parser performs language-level recovery. This is also
// the C/WW parity boundary for malformed clauses.
let il: syntax.lex;
syntax.lexinit(&il, pathstr(path), b.ptr, n: u64);
let ips: syntax.parser;
syntax.parserinit(&ips, &il);
let imports: *syntax.node = syntax.parseimports(&ips);
if (il.errs > 0 || ips.errs > 0) { return -1; };
if (imports.nmod.len == 0) {
cerrpos(pathstr(path), 1, 1);
cerr(": error: invalid or missing package clause\n");
return -1;
};
let l: syntax.lex;
syntax.lexinit(&l, pathstr(path), b.ptr, n: u64);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let f: *syntax.node = syntax.parsefile(&ps);
if (l.errs > 0 || ps.errs > 0) { return -1; };
let d: *syntax.node = f.list;
for (d != nil) {
if (d.kind == syntax.nkind.N_FNDECL) {
let at: *syntax.node = d.attr;
for (at != nil) {
if (at.kind == syntax.nkind.N_ATTR
&& syntax.streq(at.str, "test")) {
return 1;
};
at = at.next;
};
};
d = d.next;
};
return 0;
};
def SEP_VARIANT_PRODUCTION: i32 = 0;
def SEP_VARIANT_SAME_TEST: i32 = 1;
def SEP_VARIANT_EXTERNAL: i32 = 2;
def SEP_VARIANT_TEST_MAIN: i32 = 3;
def SEP_VARIANT_TEST_COPY: i32 = 4;
def SEP_ROLE_NORMAL: i32 = 0;
def SEP_ROLE_TEST_SUPPORT: i32 = 1;
def SEP_ROLE_GENERATED_MAIN: i32 = 2;
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
def SEP_LOAD_INTERNAL: i32 = -3;
def SEP_LOAD_VENDOR: i32 = -4;
def SEP_INITIAL_CAP: i32 = 8;
def SEP_COUNT_MAX: i32 = 2147483647;
fn sepnamerangeis(name: *u8, start: u64, end: u64, word: str) bool = {
if (end < start || end - start != word.len: u64) { return false; };
let i: u64 = 0u64;
for (i < end - start) {
if (name[start + i] != word.ptr[i]) { return false; };
i += 1u64;
};
return true;
};
fn sepknownos(name: *u8, start: u64, end: u64) bool = {
return sepnamerangeis(name, start, end, "aix")
|| sepnamerangeis(name, start, end, "android")
|| sepnamerangeis(name, start, end, "darwin")
|| sepnamerangeis(name, start, end, "dragonfly")
|| sepnamerangeis(name, start, end, "freebsd")
|| sepnamerangeis(name, start, end, "hurd")
|| sepnamerangeis(name, start, end, "illumos")
|| sepnamerangeis(name, start, end, "ios")
|| sepnamerangeis(name, start, end, "js")
|| sepnamerangeis(name, start, end, "linux")
|| sepnamerangeis(name, start, end, "nacl")
|| sepnamerangeis(name, start, end, "netbsd")
|| sepnamerangeis(name, start, end, "openbsd")
|| sepnamerangeis(name, start, end, "plan9")
|| sepnamerangeis(name, start, end, "solaris")
|| sepnamerangeis(name, start, end, "wasip1")
|| sepnamerangeis(name, start, end, "windows")
|| sepnamerangeis(name, start, end, "zos");
};
fn sepknownarch(name: *u8, start: u64, end: u64) bool = {
return sepnamerangeis(name, start, end, "386")
|| sepnamerangeis(name, start, end, "amd64")
|| sepnamerangeis(name, start, end, "amd64p32")
|| sepnamerangeis(name, start, end, "arm")
|| sepnamerangeis(name, start, end, "armbe")
|| sepnamerangeis(name, start, end, "arm64")
|| sepnamerangeis(name, start, end, "arm64be")
|| sepnamerangeis(name, start, end, "loong64")
|| sepnamerangeis(name, start, end, "mips")
|| sepnamerangeis(name, start, end, "mipsle")
|| sepnamerangeis(name, start, end, "mips64")
|| sepnamerangeis(name, start, end, "mips64le")
|| sepnamerangeis(name, start, end, "mips64p32")
|| sepnamerangeis(name, start, end, "mips64p32le")
|| sepnamerangeis(name, start, end, "ppc")
|| sepnamerangeis(name, start, end, "ppc64")
|| sepnamerangeis(name, start, end, "ppc64le")
|| sepnamerangeis(name, start, end, "riscv")
|| sepnamerangeis(name, start, end, "riscv64")
|| sepnamerangeis(name, start, end, "s390")
|| sepnamerangeis(name, start, end, "s390x")
|| sepnamerangeis(name, start, end, "sparc")
|| sepnamerangeis(name, start, end, "sparc64")
|| sepnamerangeis(name, start, end, "wasm");
};
fn septargettag(name: *u8, start: u64, end: u64) bool = {
return sepnamerangeis(name, start, end, "linux")
|| sepnamerangeis(name, start, end, "amd64");
};
// Go 1.26.5 build.go:1980-2027 filters known platform suffixes before
// opening a source. WW currently has one honest target, linux/amd64.
fn sepsourcematchestarget(name: *u8, nlen: u64) bool = {
let stem: u64 = 0u64;
let hasunderscore: bool = false;
for (stem < nlen && name[stem] != '.') {
if (name[stem] == '_') { hasunderscore = true; };
stem += 1u64;
};
if (!hasunderscore) { return true; };
let end: u64 = stem;
let last: u64 = end;
for (last > 0u64 && name[last - 1u64] != '_') { last -= 1u64; };
if (sepnamerangeis(name, last, end, "test")) {
if (last == 0u64) { return true; };
end = last - 1u64;
last = end;
for (last > 0u64 && name[last - 1u64] != '_') { last -= 1u64; };
};
if (last == 0u64) { return true; };
let prevend: u64 = last - 1u64;
let prev: u64 = prevend;
for (prev > 0u64 && name[prev - 1u64] != '_') { prev -= 1u64; };
if (prev < prevend && sepknownos(name, prev, prevend)
&& sepknownarch(name, last, end)) {
return septargettag(name, last, end)
&& septargettag(name, prev, prevend);
};
if (sepknownos(name, last, end) || sepknownarch(name, last, end)) {
return septargettag(name, last, end);
};
return true;
};
// Classify a selected directory entry: 1 production, 2 test, 0 skipped,
// -1 @test outside *_test.ww, -2 non-regular source.
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64,
variant: i32) i32 = {
// nlen<=3 guard kept: a bare ".ww" (len 3) is rejected here but
// would pass strings.hassuffix(".ww"); preserves cstage parity.
if (nlen <= 3u64) { return 0; };
if (name[0] == '.' || name[0] == '_') { return 0; };
let s: str;
s.ptr = name;
s.len = nlen: i32;
if (!strings.hassuffix(s, ".ww")) { return 0; };
if (!sepsourcematchestarget(name, nlen)) { return 0; };
let istest: bool = strings.hassuffix(s, "_test.ww");
if (istest && variant == SEP_VARIANT_PRODUCTION) { return 0; };
if (!istest && variant == SEP_VARIANT_EXTERNAL) { return 0; };
let source: *u8 = sepjoinpath(dirpath, name);
if (source == nil) { return -2; };
let fi: os.filestat;
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
let regular: bool = false;
let directory: bool = false;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32;
if (t == os.mode.REG: u32) { regular = true; }
else if (t == os.mode.LINK: u32) {
let target: os.filestat;
match (os.stat(&target, pathstr(source))) {
case void => {
let tt: u32 = (target.mode: u32) & 61440u32;
if (tt == os.mode.REG: u32) { regular = true; };
if (tt == os.mode.DIR: u32) { directory = true; };
};
case let e: os.oserror => void;
};
};
};
case let e: os.oserror => void;
};
// go/build ignores a source-shaped symlink to a directory.
if (directory) { return 0; };
if (!regular) {
cerr("ww: "); cerr(pathstr(source));
cerr(": package source is not a regular file\n");
return -2;
};
if (!istest) {
let attest: i32 = dirfileattest(dirpath, name);
if (attest < 0) { return -2; };
if (attest > 0) { return -1; };
};
if (istest) { return 2; };
return 1;
};
fn dirpackagename(path: *u8) *u8 = {
let view: str;
view.ptr = path;
view.len = cstrlen(path): i32;
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(path);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return nil;
};
let l: syntax.lex;
syntax.lexinit(&l, view, bufp, blen);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let imports: *syntax.node = syntax.parseimports(&ps);
if (l.errs > 0 || ps.errs > 0) { return nil; };
if (imports.nmod.len == 0) {
cerrpos(view, 1, 1);
cerr(": error: invalid or missing package clause\n");
return nil;
};
return sepdupcstr(imports.nmod.ptr, imports.nmod.len: u64);
};
// Rule-10 byte-id requires cstage and wwstage sort the same way;
// memcmp is the locale-independent total order (mirrors
// ref/hare/sort/cmp/cmp.ha strs).
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
let n: u64 = alen;
if (blen < n) { n = blen; };
let i: u64 = 0u64;
for (i < n) {
let av: i32 = (a[i]): i32;
let bv: i32 = (b[i]): i32;
if (av < bv) { return -1; };
if (av > bv) { return 1; };
i += 1u64;
};
if (alen < blen) { return -1; };
if (alen > blen) { return 1; };
return 0;
};
// Enumerate a production, same-test, or external-test directory variant.
// Production files precede matching test files; each partition is byte-sorted.
fn enumeratedir(dirpath: *u8, variant: i32,
testpackage: *u8) (**u8, i32) = {
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil: **u8, -1; };
// #65: grow-dynamic (mirror cstage enumerate_dir_ww realloc-doubling,
// cmd/ww/main.c:209). The old fixed 256-name cap silently dropped every
// eligible file past it, diverging the package unit from cstage on a
// module dir with >256 sources.
let names: []*u8 = [];
let nlens: []u64 = [];
let kinds: []i32 = [];
let n: i32 = 0;
if (!sepreservesources(&names, &nlens, &kinds, n,
SEP_INITIAL_CAP)) {
os.close(fd);
return nil: **u8, -2;
};
let buf: []u8;
if (!sepmakebytes(8192u64, &buf)) {
os.close(fd);
return nil: **u8, -2;
};
let r: i64 = os.getdents64(fd, buf.ptr, 8192u64);
for (r > 0i64) {
let off: u64 = 0u64;
let ru: u64 = r: u64;
for (off < ru) {
let blo: u64 = (buf[off + 16u64]): u64;
let bhi: u64 = (buf[off + 17u64]): u64;
let reclen: u64 = blo + (bhi * 256u64);
let nm: *u8 = buf.ptr + off + 19u64;
let nl: u64 = cstrlen(nm);
if (nl > 3u64 && nm[0] != '.' && nm[0] != '_') {
let view: str;
view.ptr = nm;
view.len = nl: i32;
if (strings.hassuffix(view, ".ww")) {
if (n == SEP_COUNT_MAX) {
sepfailsize();
os.close(fd);
return nil: **u8, -2;
};
if (!sepreservesources(&names, &nlens, &kinds, n,
n + 1)) {
os.close(fd);
return nil: **u8, -2;
};
let owned: *u8 = sepdupcstr(nm, nl);
if (owned == nil) {
os.close(fd);
return nil: **u8, -2;
};
names[n] = owned;
nlens[n] = nl;
kinds[n] = 0;
n += 1;
};
};
off += reclen;
};
r = os.getdents64(fd, buf.ptr, 8192u64);
};
os.close(fd);
// A failed directory read is an ERROR, not EOF: mid-walk it
// silently truncated the package source list, and on the first
// read it was misdiagnosed as "directory contains no WW package
// sources". -1 routes the caller's "cannot read directory" arm
// (the cstage caller mapping).
if (r < 0i64) {
return nil: **u8, -1;
};
// Go's directory reader presents a byte-sorted name list to the loader.
let i: i32 = 1;
for (i < n) {
let j: i32 = i;
for (j > 0) {
let c: i32 = bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]);
if (c <= 0) { j = 0; }
else {
let t: *u8 = names[j];
names[j] = names[j - 1];
names[j - 1] = t;
let tl: u64 = nlens[j];
nlens[j] = nlens[j - 1];
nlens[j - 1] = tl;
let tk: i32 = kinds[j];
kinds[j] = kinds[j - 1];
kinds[j - 1] = tk;
j -= 1;
};
};
i += 1;
};
let raw: i32 = n;
let selected: i32 = 0;
let ri: i32 = 0;
for (ri < raw) {
let nm: *u8 = names[ri];
let nl: u64 = nlens[ri];
let cls: i32 = dirfileclass(dirpath, nm, nl, variant);
if (cls == -1) {
let badpath: *u8 = sepjoinpath(dirpath, nm);
if (badpath == nil) { return nil: **u8, -2; };
cerr("ww: ");
cerr(pathstr(badpath));
cerr(": @test declaration outside *_test.ww\n");
return nil: **u8, -2;
};
if (cls == -2) { return nil: **u8, -2; };
if (cls > 0) {
let full: *u8 = sepjoinpath(dirpath, nm);
if (full == nil) { return nil: **u8, -2; };
let keep: bool = true;
if (cls == 2) {
let pn: *u8 = dirpackagename(full);
if (pn == nil) { return nil: **u8, -2; };
if (testpackage == nil || !cstreq(pn, testpackage)) {
keep = false;
};
};
if (keep) {
names[selected] = full;
nlens[selected] = cstrlen(full);
kinds[selected] = cls;
selected += 1;
};
};
ri += 1;
};
n = selected;
// Production files precede test files; each partition remains byte-sorted.
i = 1;
for (i < n) {
let j: i32 = i;
for (j > 0) {
let c: i32 = kinds[j - 1] - kinds[j];
if (c == 0) {
c = bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]);
};
if (c <= 0) { j = 0; }
else {
let t: *u8 = names[j];
names[j] = names[j - 1];
names[j - 1] = t;
let tl: u64 = nlens[j];
nlens[j] = nlens[j - 1];
nlens[j - 1] = tl;
let tk: i32 = kinds[j];
kinds[j] = kinds[j - 1];
kinds[j - 1] = tk;
j -= 1;
};
};
i += 1;
};
if (n == 0) {
os.free(names.ptr: *void, (names.cap: u64) * (size(*u8): u64));
os.free(nlens.ptr: *void, (nlens.cap: u64) * (size(u64): u64));
os.free(kinds.ptr: *void, (kinds.cap: u64) * (size(i32): u64));
return nil: **u8, 0;
};
let exactallocation: ([]*u8 | nomem) = sepallocptrs(n);
let exact: []*u8;
match (exactallocation) {
case let value: []*u8 => exact = value;
case nomem => {
sepfailnomem();
return nil: **u8, -2;
};
};
exact.len = n;
let k: i32 = 0;
for (k < n) { exact[k] = names[k]; k += 1; };
// rt_free is currently a no-op, but keep the concrete owner/release
// shape correct for the driver's allocations.
os.free(names.ptr: *void, (names.cap: u64) * (size(*u8): u64));
os.free(nlens.ptr: *void, (nlens.cap: u64) * (size(u64): u64));
os.free(kinds.ptr: *void, (kinds.cap: u64) * (size(i32): u64));
return exact.ptr, n;
};
fn slurp(pathcs: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
let szr: (i64 | os.oserror) = os.filesize(fd);
let n: i64 = 0i64;
match (szr) {
case let v: i64 => n = v;
case let e: os.oserror => { os.close(fd); return nil, 0u64; };
};
let nu: u64 = n: u64;
if (nu >= SEP_COUNT_MAX: u64) {
sepfailsize();
os.close(fd);
return nil, 0u64;
};
let allocation: ([]u8 | nomem) = sepallocbytes((nu + 1u64): i32);
let buf: []u8;
match (allocation) {
case let value: []u8 => buf = value;
case nomem => {
sepfailnomem();
os.close(fd);
return nil, 0u64;
};
};
buf.len = (nu + 1u64): i32;
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu);
let closed: i32 = os.close(fd);
let got: i64 = 0i64;
match (rr) {
case let v: i64 => got = v;
case let e: os.oserror => return nil, 0u64;
};
if (got != n || closed != 0) { return nil, 0u64; };
buf[nu] = 0u8;
return buf.ptr, nu;
};
fn makestem(stem: *u8, src: *u8) void = {
let n: u64 = cstrlen(src);
let stop: u64 = n;
if (n >= 3u64) {
if (src[n - 3u64] == 46u8) { // '.'
if (src[n - 2u64] == 119u8) { // 'w'
if (src[n - 1u64] == 119u8) { // 'w'
stop = n - 3u64;
};
};
};
};
let i: u64 = 0u64;
for (i < stop) { stem[i] = src[i]; i += 1u64; };
stem[stop] = 0u8;
};
fn appendlit(stem: *u8, suffix: str) *u8 = {
let need: u64 = cstrlen(stem) + suffix.len: u64 + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, stem);
off = strinto(buf.ptr, off, suffix);
cstrseal(buf.ptr, off);
return buf.ptr;
};
// linker flags (-L<dir>, -l<name>) grouped as one struct so buildonesep's
// param list stays readable. (The earlier note here claimed a 6-argument
// wwstage calling-convention cap; that is stale — emittuplerowrelocs in
// cgen.ww takes 7 params and self-compiles green.)
type lflags = struct {
libdirs: **u8,
nlibdirs: i32,
libs: **u8,
nlibs: i32,
};
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path
// materializes each imported package's `.wwi` interface and compiles
// every package on its own (`w6c -c`), then flat-links the `.o` set.
// Separate compilation is the SOLE build path (E3-C1 flip, task #87).
//
// Each w6c pass is BOTH consumer (reads each direct dep `.wwi` through a
// separate --import argument) AND producer (writes this package's `.wwi`
// via -I). Reverse-topo order guarantees a package's deps' `.wwi` exist.
// The canonical import path paired with each self-contained export preserves
// qualified symbol identity; transitive exports remain outside the compile
// action. Unit composition is byte-identical to the cstage driver.
type sepbind = struct {
kind: u8,
name: str,
source: str,
line: i32,
col: i32,
dep: i32,
};
type sepchild = struct {
pkg: i32,
context: i32,
};
type sepfoldrange = struct {
lo: u32,
hi: u32,
stride: u32,
delta: i32,
};
// Unicode 15.0 simple-fold minima generated from Go 1.26.5's byte-pinned
// tables. The folded spelling is request-only collision state.
let sepfoldranges: [210]sepfoldrange = [
sepfoldrange { lo = 0x000041u32, hi = 0x00005au32, stride = 1u32, delta = 32i32 },
sepfoldrange { lo = 0x0000e0u32, hi = 0x0000f6u32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x0000f8u32, hi = 0x0000feu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x000101u32, hi = 0x00012fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000133u32, hi = 0x000137u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00013au32, hi = 0x000148u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00014bu32, hi = 0x000177u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000178u32, hi = 0x000178u32, stride = 1u32, delta = -121i32 },
sepfoldrange { lo = 0x00017au32, hi = 0x00017eu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00017fu32, hi = 0x00017fu32, stride = 1u32, delta = -268i32 },
sepfoldrange { lo = 0x000183u32, hi = 0x000185u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000188u32, hi = 0x000188u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x00018cu32, hi = 0x00018cu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x000192u32, hi = 0x000192u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x000199u32, hi = 0x000199u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001a1u32, hi = 0x0001a5u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0001a8u32, hi = 0x0001a8u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001adu32, hi = 0x0001adu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001b0u32, hi = 0x0001b0u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001b4u32, hi = 0x0001b6u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0001b9u32, hi = 0x0001b9u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001bdu32, hi = 0x0001bdu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001c5u32, hi = 0x0001c5u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001c6u32, hi = 0x0001c6u32, stride = 1u32, delta = -2i32 },
sepfoldrange { lo = 0x0001c8u32, hi = 0x0001c8u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001c9u32, hi = 0x0001c9u32, stride = 1u32, delta = -2i32 },
sepfoldrange { lo = 0x0001cbu32, hi = 0x0001cbu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001ccu32, hi = 0x0001ccu32, stride = 1u32, delta = -2i32 },
sepfoldrange { lo = 0x0001ceu32, hi = 0x0001dcu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0001ddu32, hi = 0x0001ddu32, stride = 1u32, delta = -79i32 },
sepfoldrange { lo = 0x0001dfu32, hi = 0x0001efu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0001f2u32, hi = 0x0001f2u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001f3u32, hi = 0x0001f3u32, stride = 1u32, delta = -2i32 },
sepfoldrange { lo = 0x0001f5u32, hi = 0x0001f5u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0001f6u32, hi = 0x0001f6u32, stride = 1u32, delta = -97i32 },
sepfoldrange { lo = 0x0001f7u32, hi = 0x0001f7u32, stride = 1u32, delta = -56i32 },
sepfoldrange { lo = 0x0001f9u32, hi = 0x00021fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000220u32, hi = 0x000220u32, stride = 1u32, delta = -130i32 },
sepfoldrange { lo = 0x000223u32, hi = 0x000233u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00023cu32, hi = 0x00023cu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x00023du32, hi = 0x00023du32, stride = 1u32, delta = -163i32 },
sepfoldrange { lo = 0x000242u32, hi = 0x000242u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x000243u32, hi = 0x000243u32, stride = 1u32, delta = -195i32 },
sepfoldrange { lo = 0x000247u32, hi = 0x00024fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000253u32, hi = 0x000253u32, stride = 1u32, delta = -210i32 },
sepfoldrange { lo = 0x000254u32, hi = 0x000254u32, stride = 1u32, delta = -206i32 },
sepfoldrange { lo = 0x000256u32, hi = 0x000257u32, stride = 1u32, delta = -205i32 },
sepfoldrange { lo = 0x000259u32, hi = 0x000259u32, stride = 1u32, delta = -202i32 },
sepfoldrange { lo = 0x00025bu32, hi = 0x00025bu32, stride = 1u32, delta = -203i32 },
sepfoldrange { lo = 0x000260u32, hi = 0x000260u32, stride = 1u32, delta = -205i32 },
sepfoldrange { lo = 0x000263u32, hi = 0x000263u32, stride = 1u32, delta = -207i32 },
sepfoldrange { lo = 0x000268u32, hi = 0x000268u32, stride = 1u32, delta = -209i32 },
sepfoldrange { lo = 0x000269u32, hi = 0x000269u32, stride = 1u32, delta = -211i32 },
sepfoldrange { lo = 0x00026fu32, hi = 0x00026fu32, stride = 1u32, delta = -211i32 },
sepfoldrange { lo = 0x000272u32, hi = 0x000272u32, stride = 1u32, delta = -213i32 },
sepfoldrange { lo = 0x000275u32, hi = 0x000275u32, stride = 1u32, delta = -214i32 },
sepfoldrange { lo = 0x000280u32, hi = 0x000280u32, stride = 1u32, delta = -218i32 },
sepfoldrange { lo = 0x000283u32, hi = 0x000283u32, stride = 1u32, delta = -218i32 },
sepfoldrange { lo = 0x000288u32, hi = 0x000288u32, stride = 1u32, delta = -218i32 },
sepfoldrange { lo = 0x000289u32, hi = 0x000289u32, stride = 1u32, delta = -69i32 },
sepfoldrange { lo = 0x00028au32, hi = 0x00028bu32, stride = 1u32, delta = -217i32 },
sepfoldrange { lo = 0x00028cu32, hi = 0x00028cu32, stride = 1u32, delta = -71i32 },
sepfoldrange { lo = 0x000292u32, hi = 0x000292u32, stride = 1u32, delta = -219i32 },
sepfoldrange { lo = 0x000371u32, hi = 0x000373u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000377u32, hi = 0x000377u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x000399u32, hi = 0x000399u32, stride = 1u32, delta = -84i32 },
sepfoldrange { lo = 0x00039cu32, hi = 0x00039cu32, stride = 1u32, delta = -743i32 },
sepfoldrange { lo = 0x0003acu32, hi = 0x0003acu32, stride = 1u32, delta = -38i32 },
sepfoldrange { lo = 0x0003adu32, hi = 0x0003afu32, stride = 1u32, delta = -37i32 },
sepfoldrange { lo = 0x0003b1u32, hi = 0x0003b8u32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x0003b9u32, hi = 0x0003b9u32, stride = 1u32, delta = -116i32 },
sepfoldrange { lo = 0x0003bau32, hi = 0x0003bbu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x0003bcu32, hi = 0x0003bcu32, stride = 1u32, delta = -775i32 },
sepfoldrange { lo = 0x0003bdu32, hi = 0x0003c1u32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x0003c2u32, hi = 0x0003c2u32, stride = 1u32, delta = -31i32 },
sepfoldrange { lo = 0x0003c3u32, hi = 0x0003cbu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x0003ccu32, hi = 0x0003ccu32, stride = 1u32, delta = -64i32 },
sepfoldrange { lo = 0x0003cdu32, hi = 0x0003ceu32, stride = 1u32, delta = -63i32 },
sepfoldrange { lo = 0x0003d0u32, hi = 0x0003d0u32, stride = 1u32, delta = -62i32 },
sepfoldrange { lo = 0x0003d1u32, hi = 0x0003d1u32, stride = 1u32, delta = -57i32 },
sepfoldrange { lo = 0x0003d5u32, hi = 0x0003d5u32, stride = 1u32, delta = -47i32 },
sepfoldrange { lo = 0x0003d6u32, hi = 0x0003d6u32, stride = 1u32, delta = -54i32 },
sepfoldrange { lo = 0x0003d7u32, hi = 0x0003d7u32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x0003d9u32, hi = 0x0003efu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0003f0u32, hi = 0x0003f0u32, stride = 1u32, delta = -86i32 },
sepfoldrange { lo = 0x0003f1u32, hi = 0x0003f1u32, stride = 1u32, delta = -80i32 },
sepfoldrange { lo = 0x0003f3u32, hi = 0x0003f3u32, stride = 1u32, delta = -116i32 },
sepfoldrange { lo = 0x0003f4u32, hi = 0x0003f4u32, stride = 1u32, delta = -92i32 },
sepfoldrange { lo = 0x0003f5u32, hi = 0x0003f5u32, stride = 1u32, delta = -96i32 },
sepfoldrange { lo = 0x0003f8u32, hi = 0x0003f8u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0003f9u32, hi = 0x0003f9u32, stride = 1u32, delta = -7i32 },
sepfoldrange { lo = 0x0003fbu32, hi = 0x0003fbu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0003fdu32, hi = 0x0003ffu32, stride = 1u32, delta = -130i32 },
sepfoldrange { lo = 0x000430u32, hi = 0x00044fu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x000450u32, hi = 0x00045fu32, stride = 1u32, delta = -80i32 },
sepfoldrange { lo = 0x000461u32, hi = 0x000481u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00048bu32, hi = 0x0004bfu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0004c2u32, hi = 0x0004ceu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x0004cfu32, hi = 0x0004cfu32, stride = 1u32, delta = -15i32 },
sepfoldrange { lo = 0x0004d1u32, hi = 0x00052fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x000561u32, hi = 0x000586u32, stride = 1u32, delta = -48i32 },
sepfoldrange { lo = 0x0013f8u32, hi = 0x0013fdu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001c80u32, hi = 0x001c80u32, stride = 1u32, delta = -6254i32 },
sepfoldrange { lo = 0x001c81u32, hi = 0x001c81u32, stride = 1u32, delta = -6253i32 },
sepfoldrange { lo = 0x001c82u32, hi = 0x001c82u32, stride = 1u32, delta = -6244i32 },
sepfoldrange { lo = 0x001c83u32, hi = 0x001c84u32, stride = 1u32, delta = -6242i32 },
sepfoldrange { lo = 0x001c85u32, hi = 0x001c85u32, stride = 1u32, delta = -6243i32 },
sepfoldrange { lo = 0x001c86u32, hi = 0x001c86u32, stride = 1u32, delta = -6236i32 },
sepfoldrange { lo = 0x001c87u32, hi = 0x001c87u32, stride = 1u32, delta = -6181i32 },
sepfoldrange { lo = 0x001c90u32, hi = 0x001cbau32, stride = 1u32, delta = -3008i32 },
sepfoldrange { lo = 0x001cbdu32, hi = 0x001cbfu32, stride = 1u32, delta = -3008i32 },
sepfoldrange { lo = 0x001e01u32, hi = 0x001e95u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x001e9bu32, hi = 0x001e9bu32, stride = 1u32, delta = -59i32 },
sepfoldrange { lo = 0x001e9eu32, hi = 0x001e9eu32, stride = 1u32, delta = -7615i32 },
sepfoldrange { lo = 0x001ea1u32, hi = 0x001effu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x001f08u32, hi = 0x001f0fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f18u32, hi = 0x001f1du32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f28u32, hi = 0x001f2fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f38u32, hi = 0x001f3fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f48u32, hi = 0x001f4du32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f59u32, hi = 0x001f5fu32, stride = 2u32, delta = -8i32 },
sepfoldrange { lo = 0x001f68u32, hi = 0x001f6fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f88u32, hi = 0x001f8fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001f98u32, hi = 0x001f9fu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001fa8u32, hi = 0x001fafu32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001fb8u32, hi = 0x001fb9u32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001fbau32, hi = 0x001fbbu32, stride = 1u32, delta = -74i32 },
sepfoldrange { lo = 0x001fbcu32, hi = 0x001fbcu32, stride = 1u32, delta = -9i32 },
sepfoldrange { lo = 0x001fbeu32, hi = 0x001fbeu32, stride = 1u32, delta = -7289i32 },
sepfoldrange { lo = 0x001fc8u32, hi = 0x001fcbu32, stride = 1u32, delta = -86i32 },
sepfoldrange { lo = 0x001fccu32, hi = 0x001fccu32, stride = 1u32, delta = -9i32 },
sepfoldrange { lo = 0x001fd8u32, hi = 0x001fd9u32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001fdau32, hi = 0x001fdbu32, stride = 1u32, delta = -100i32 },
sepfoldrange { lo = 0x001fe8u32, hi = 0x001fe9u32, stride = 1u32, delta = -8i32 },
sepfoldrange { lo = 0x001feau32, hi = 0x001febu32, stride = 1u32, delta = -112i32 },
sepfoldrange { lo = 0x001fecu32, hi = 0x001fecu32, stride = 1u32, delta = -7i32 },
sepfoldrange { lo = 0x001ff8u32, hi = 0x001ff9u32, stride = 1u32, delta = -128i32 },
sepfoldrange { lo = 0x001ffau32, hi = 0x001ffbu32, stride = 1u32, delta = -126i32 },
sepfoldrange { lo = 0x001ffcu32, hi = 0x001ffcu32, stride = 1u32, delta = -9i32 },
sepfoldrange { lo = 0x002126u32, hi = 0x002126u32, stride = 1u32, delta = -7549i32 },
sepfoldrange { lo = 0x00212au32, hi = 0x00212au32, stride = 1u32, delta = -8383i32 },
sepfoldrange { lo = 0x00212bu32, hi = 0x00212bu32, stride = 1u32, delta = -8294i32 },
sepfoldrange { lo = 0x00214eu32, hi = 0x00214eu32, stride = 1u32, delta = -28i32 },
sepfoldrange { lo = 0x002170u32, hi = 0x00217fu32, stride = 1u32, delta = -16i32 },
sepfoldrange { lo = 0x002184u32, hi = 0x002184u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x0024d0u32, hi = 0x0024e9u32, stride = 1u32, delta = -26i32 },
sepfoldrange { lo = 0x002c30u32, hi = 0x002c5fu32, stride = 1u32, delta = -48i32 },
sepfoldrange { lo = 0x002c61u32, hi = 0x002c61u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x002c62u32, hi = 0x002c62u32, stride = 1u32, delta = -10743i32 },
sepfoldrange { lo = 0x002c63u32, hi = 0x002c63u32, stride = 1u32, delta = -3814i32 },
sepfoldrange { lo = 0x002c64u32, hi = 0x002c64u32, stride = 1u32, delta = -10727i32 },
sepfoldrange { lo = 0x002c65u32, hi = 0x002c65u32, stride = 1u32, delta = -10795i32 },
sepfoldrange { lo = 0x002c66u32, hi = 0x002c66u32, stride = 1u32, delta = -10792i32 },
sepfoldrange { lo = 0x002c68u32, hi = 0x002c6cu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x002c6du32, hi = 0x002c6du32, stride = 1u32, delta = -10780i32 },
sepfoldrange { lo = 0x002c6eu32, hi = 0x002c6eu32, stride = 1u32, delta = -10749i32 },
sepfoldrange { lo = 0x002c6fu32, hi = 0x002c6fu32, stride = 1u32, delta = -10783i32 },
sepfoldrange { lo = 0x002c70u32, hi = 0x002c70u32, stride = 1u32, delta = -10782i32 },
sepfoldrange { lo = 0x002c73u32, hi = 0x002c73u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x002c76u32, hi = 0x002c76u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x002c7eu32, hi = 0x002c7fu32, stride = 1u32, delta = -10815i32 },
sepfoldrange { lo = 0x002c81u32, hi = 0x002ce3u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x002cecu32, hi = 0x002ceeu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x002cf3u32, hi = 0x002cf3u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x002d00u32, hi = 0x002d25u32, stride = 1u32, delta = -7264i32 },
sepfoldrange { lo = 0x002d27u32, hi = 0x002d27u32, stride = 1u32, delta = -7264i32 },
sepfoldrange { lo = 0x002d2du32, hi = 0x002d2du32, stride = 1u32, delta = -7264i32 },
sepfoldrange { lo = 0x00a641u32, hi = 0x00a649u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a64au32, hi = 0x00a64au32, stride = 1u32, delta = -35266i32 },
sepfoldrange { lo = 0x00a64bu32, hi = 0x00a64bu32, stride = 1u32, delta = -35267i32 },
sepfoldrange { lo = 0x00a64du32, hi = 0x00a66du32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a681u32, hi = 0x00a69bu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a723u32, hi = 0x00a72fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a733u32, hi = 0x00a76fu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a77au32, hi = 0x00a77cu32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a77du32, hi = 0x00a77du32, stride = 1u32, delta = -35332i32 },
sepfoldrange { lo = 0x00a77fu32, hi = 0x00a787u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a78cu32, hi = 0x00a78cu32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x00a78du32, hi = 0x00a78du32, stride = 1u32, delta = -42280i32 },
sepfoldrange { lo = 0x00a791u32, hi = 0x00a793u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a797u32, hi = 0x00a7a9u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a7aau32, hi = 0x00a7aau32, stride = 1u32, delta = -42308i32 },
sepfoldrange { lo = 0x00a7abu32, hi = 0x00a7abu32, stride = 1u32, delta = -42319i32 },
sepfoldrange { lo = 0x00a7acu32, hi = 0x00a7acu32, stride = 1u32, delta = -42315i32 },
sepfoldrange { lo = 0x00a7adu32, hi = 0x00a7adu32, stride = 1u32, delta = -42305i32 },
sepfoldrange { lo = 0x00a7aeu32, hi = 0x00a7aeu32, stride = 1u32, delta = -42308i32 },
sepfoldrange { lo = 0x00a7b0u32, hi = 0x00a7b0u32, stride = 1u32, delta = -42258i32 },
sepfoldrange { lo = 0x00a7b1u32, hi = 0x00a7b1u32, stride = 1u32, delta = -42282i32 },
sepfoldrange { lo = 0x00a7b2u32, hi = 0x00a7b2u32, stride = 1u32, delta = -42261i32 },
sepfoldrange { lo = 0x00a7b5u32, hi = 0x00a7c3u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a7c4u32, hi = 0x00a7c4u32, stride = 1u32, delta = -48i32 },
sepfoldrange { lo = 0x00a7c5u32, hi = 0x00a7c5u32, stride = 1u32, delta = -42307i32 },
sepfoldrange { lo = 0x00a7c6u32, hi = 0x00a7c6u32, stride = 1u32, delta = -35384i32 },
sepfoldrange { lo = 0x00a7c8u32, hi = 0x00a7cau32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a7d1u32, hi = 0x00a7d1u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x00a7d7u32, hi = 0x00a7d9u32, stride = 2u32, delta = -1i32 },
sepfoldrange { lo = 0x00a7f6u32, hi = 0x00a7f6u32, stride = 1u32, delta = -1i32 },
sepfoldrange { lo = 0x00ab53u32, hi = 0x00ab53u32, stride = 1u32, delta = -928i32 },
sepfoldrange { lo = 0x00ab70u32, hi = 0x00abbfu32, stride = 1u32, delta = -38864i32 },
sepfoldrange { lo = 0x00ff41u32, hi = 0x00ff5au32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x010428u32, hi = 0x01044fu32, stride = 1u32, delta = -40i32 },
sepfoldrange { lo = 0x0104d8u32, hi = 0x0104fbu32, stride = 1u32, delta = -40i32 },
sepfoldrange { lo = 0x010597u32, hi = 0x0105a1u32, stride = 1u32, delta = -39i32 },
sepfoldrange { lo = 0x0105a3u32, hi = 0x0105b1u32, stride = 1u32, delta = -39i32 },
sepfoldrange { lo = 0x0105b3u32, hi = 0x0105b9u32, stride = 1u32, delta = -39i32 },
sepfoldrange { lo = 0x0105bbu32, hi = 0x0105bcu32, stride = 1u32, delta = -39i32 },
sepfoldrange { lo = 0x010cc0u32, hi = 0x010cf2u32, stride = 1u32, delta = -64i32 },
sepfoldrange { lo = 0x0118c0u32, hi = 0x0118dfu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x016e60u32, hi = 0x016e7fu32, stride = 1u32, delta = -32i32 },
sepfoldrange { lo = 0x01e922u32, hi = 0x01e943u32, stride = 1u32, delta = -34i32 },
];
// Go 1.26.5 strconv.IsPrint tables for byte-stable %q-style diagnostics.
let sepprint16: [424]u16 = [
0x0020u16, 0x007eu16, 0x00a1u16, 0x0377u16, 0x037au16, 0x037fu16, 0x0384u16, 0x0556u16,
0x0559u16, 0x058au16, 0x058du16, 0x05c7u16, 0x05d0u16, 0x05eau16, 0x05efu16, 0x05f4u16,
0x0606u16, 0x070du16, 0x0710u16, 0x074au16, 0x074du16, 0x07b1u16, 0x07c0u16, 0x07fau16,
0x07fdu16, 0x082du16, 0x0830u16, 0x085bu16, 0x085eu16, 0x086au16, 0x0870u16, 0x088eu16,
0x0898u16, 0x098cu16, 0x098fu16, 0x0990u16, 0x0993u16, 0x09b2u16, 0x09b6u16, 0x09b9u16,
0x09bcu16, 0x09c4u16, 0x09c7u16, 0x09c8u16, 0x09cbu16, 0x09ceu16, 0x09d7u16, 0x09d7u16,
0x09dcu16, 0x09e3u16, 0x09e6u16, 0x09feu16, 0x0a01u16, 0x0a0au16, 0x0a0fu16, 0x0a10u16,
0x0a13u16, 0x0a39u16, 0x0a3cu16, 0x0a42u16, 0x0a47u16, 0x0a48u16, 0x0a4bu16, 0x0a4du16,
0x0a51u16, 0x0a51u16, 0x0a59u16, 0x0a5eu16, 0x0a66u16, 0x0a76u16, 0x0a81u16, 0x0ab9u16,
0x0abcu16, 0x0acdu16, 0x0ad0u16, 0x0ad0u16, 0x0ae0u16, 0x0ae3u16, 0x0ae6u16, 0x0af1u16,
0x0af9u16, 0x0b0cu16, 0x0b0fu16, 0x0b10u16, 0x0b13u16, 0x0b39u16, 0x0b3cu16, 0x0b44u16,
0x0b47u16, 0x0b48u16, 0x0b4bu16, 0x0b4du16, 0x0b55u16, 0x0b57u16, 0x0b5cu16, 0x0b63u16,
0x0b66u16, 0x0b77u16, 0x0b82u16, 0x0b8au16, 0x0b8eu16, 0x0b95u16, 0x0b99u16, 0x0b9fu16,
0x0ba3u16, 0x0ba4u16, 0x0ba8u16, 0x0baau16, 0x0baeu16, 0x0bb9u16, 0x0bbeu16, 0x0bc2u16,
0x0bc6u16, 0x0bcdu16, 0x0bd0u16, 0x0bd0u16, 0x0bd7u16, 0x0bd7u16, 0x0be6u16, 0x0bfau16,
0x0c00u16, 0x0c39u16, 0x0c3cu16, 0x0c4du16, 0x0c55u16, 0x0c5au16, 0x0c5du16, 0x0c5du16,
0x0c60u16, 0x0c63u16, 0x0c66u16, 0x0c6fu16, 0x0c77u16, 0x0cb9u16, 0x0cbcu16, 0x0ccdu16,
0x0cd5u16, 0x0cd6u16, 0x0cddu16, 0x0ce3u16, 0x0ce6u16, 0x0cf3u16, 0x0d00u16, 0x0d4fu16,
0x0d54u16, 0x0d63u16, 0x0d66u16, 0x0d96u16, 0x0d9au16, 0x0dbdu16, 0x0dc0u16, 0x0dc6u16,
0x0dcau16, 0x0dcau16, 0x0dcfu16, 0x0ddfu16, 0x0de6u16, 0x0defu16, 0x0df2u16, 0x0df4u16,
0x0e01u16, 0x0e3au16, 0x0e3fu16, 0x0e5bu16, 0x0e81u16, 0x0ebdu16, 0x0ec0u16, 0x0ed9u16,
0x0edcu16, 0x0edfu16, 0x0f00u16, 0x0f6cu16, 0x0f71u16, 0x0fdau16, 0x1000u16, 0x10c7u16,
0x10cdu16, 0x10cdu16, 0x10d0u16, 0x124du16, 0x1250u16, 0x125du16, 0x1260u16, 0x128du16,
0x1290u16, 0x12b5u16, 0x12b8u16, 0x12c5u16, 0x12c8u16, 0x1315u16, 0x1318u16, 0x135au16,
0x135du16, 0x137cu16, 0x1380u16, 0x1399u16, 0x13a0u16, 0x13f5u16, 0x13f8u16, 0x13fdu16,
0x1400u16, 0x169cu16, 0x16a0u16, 0x16f8u16, 0x1700u16, 0x1715u16, 0x171fu16, 0x1736u16,
0x1740u16, 0x1753u16, 0x1760u16, 0x1773u16, 0x1780u16, 0x17ddu16, 0x17e0u16, 0x17e9u16,
0x17f0u16, 0x17f9u16, 0x1800u16, 0x1819u16, 0x1820u16, 0x1878u16, 0x1880u16, 0x18aau16,
0x18b0u16, 0x18f5u16, 0x1900u16, 0x192bu16, 0x1930u16, 0x193bu16, 0x1940u16, 0x1940u16,
0x1944u16, 0x196du16, 0x1970u16, 0x1974u16, 0x1980u16, 0x19abu16, 0x19b0u16, 0x19c9u16,
0x19d0u16, 0x19dau16, 0x19deu16, 0x1a1bu16, 0x1a1eu16, 0x1a7cu16, 0x1a7fu16, 0x1a89u16,
0x1a90u16, 0x1a99u16, 0x1aa0u16, 0x1aadu16, 0x1ab0u16, 0x1aceu16, 0x1b00u16, 0x1b4cu16,
0x1b50u16, 0x1bf3u16, 0x1bfcu16, 0x1c37u16, 0x1c3bu16, 0x1c49u16, 0x1c4du16, 0x1c88u16,
0x1c90u16, 0x1cbau16, 0x1cbdu16, 0x1cc7u16, 0x1cd0u16, 0x1cfau16, 0x1d00u16, 0x1f15u16,
0x1f18u16, 0x1f1du16, 0x1f20u16, 0x1f45u16, 0x1f48u16, 0x1f4du16, 0x1f50u16, 0x1f7du16,
0x1f80u16, 0x1fd3u16, 0x1fd6u16, 0x1fefu16, 0x1ff2u16, 0x1ffeu16, 0x2010u16, 0x2027u16,
0x2030u16, 0x205eu16, 0x2070u16, 0x2071u16, 0x2074u16, 0x209cu16, 0x20a0u16, 0x20c0u16,
0x20d0u16, 0x20f0u16, 0x2100u16, 0x218bu16, 0x2190u16, 0x2426u16, 0x2440u16, 0x244au16,
0x2460u16, 0x2b73u16, 0x2b76u16, 0x2cf3u16, 0x2cf9u16, 0x2d27u16, 0x2d2du16, 0x2d2du16,
0x2d30u16, 0x2d67u16, 0x2d6fu16, 0x2d70u16, 0x2d7fu16, 0x2d96u16, 0x2da0u16, 0x2e5du16,
0x2e80u16, 0x2ef3u16, 0x2f00u16, 0x2fd5u16, 0x2ff0u16, 0x2ffbu16, 0x3001u16, 0x3096u16,
0x3099u16, 0x30ffu16, 0x3105u16, 0x31e3u16, 0x31f0u16, 0xa48cu16, 0xa490u16, 0xa4c6u16,
0xa4d0u16, 0xa62bu16, 0xa640u16, 0xa6f7u16, 0xa700u16, 0xa7cau16, 0xa7d0u16, 0xa7d9u16,
0xa7f2u16, 0xa82cu16, 0xa830u16, 0xa839u16, 0xa840u16, 0xa877u16, 0xa880u16, 0xa8c5u16,
0xa8ceu16, 0xa8d9u16, 0xa8e0u16, 0xa953u16, 0xa95fu16, 0xa97cu16, 0xa980u16, 0xa9d9u16,
0xa9deu16, 0xaa36u16, 0xaa40u16, 0xaa4du16, 0xaa50u16, 0xaa59u16, 0xaa5cu16, 0xaac2u16,
0xaadbu16, 0xaaf6u16, 0xab01u16, 0xab06u16, 0xab09u16, 0xab0eu16, 0xab11u16, 0xab16u16,
0xab20u16, 0xab6bu16, 0xab70u16, 0xabedu16, 0xabf0u16, 0xabf9u16, 0xac00u16, 0xd7a3u16,
0xd7b0u16, 0xd7c6u16, 0xd7cbu16, 0xd7fbu16, 0xf900u16, 0xfa6du16, 0xfa70u16, 0xfad9u16,
0xfb00u16, 0xfb06u16, 0xfb13u16, 0xfb17u16, 0xfb1du16, 0xfbc2u16, 0xfbd3u16, 0xfd8fu16,
0xfd92u16, 0xfdc7u16, 0xfdcfu16, 0xfdcfu16, 0xfdf0u16, 0xfe19u16, 0xfe20u16, 0xfe6bu16,
0xfe70u16, 0xfefcu16, 0xff01u16, 0xffbeu16, 0xffc2u16, 0xffc7u16, 0xffcau16, 0xffcfu16,
0xffd2u16, 0xffd7u16, 0xffdau16, 0xffdcu16, 0xffe0u16, 0xffeeu16, 0xfffcu16, 0xfffdu16,
];
let sepnotprint16: [133]u16 = [
0x00adu16, 0x038bu16, 0x038du16, 0x03a2u16, 0x0530u16, 0x0590u16, 0x061cu16, 0x06ddu16,
0x083fu16, 0x085fu16, 0x08e2u16, 0x0984u16, 0x09a9u16, 0x09b1u16, 0x09deu16, 0x0a04u16,
0x0a29u16, 0x0a31u16, 0x0a34u16, 0x0a37u16, 0x0a3du16, 0x0a5du16, 0x0a84u16, 0x0a8eu16,
0x0a92u16, 0x0aa9u16, 0x0ab1u16, 0x0ab4u16, 0x0ac6u16, 0x0acau16, 0x0b00u16, 0x0b04u16,
0x0b29u16, 0x0b31u16, 0x0b34u16, 0x0b5eu16, 0x0b84u16, 0x0b91u16, 0x0b9bu16, 0x0b9du16,
0x0bc9u16, 0x0c0du16, 0x0c11u16, 0x0c29u16, 0x0c45u16, 0x0c49u16, 0x0c57u16, 0x0c8du16,
0x0c91u16, 0x0ca9u16, 0x0cb4u16, 0x0cc5u16, 0x0cc9u16, 0x0cdfu16, 0x0cf0u16, 0x0d0du16,
0x0d11u16, 0x0d45u16, 0x0d49u16, 0x0d80u16, 0x0d84u16, 0x0db2u16, 0x0dbcu16, 0x0dd5u16,
0x0dd7u16, 0x0e83u16, 0x0e85u16, 0x0e8bu16, 0x0ea4u16, 0x0ea6u16, 0x0ec5u16, 0x0ec7u16,
0x0ecfu16, 0x0f48u16, 0x0f98u16, 0x0fbdu16, 0x0fcdu16, 0x10c6u16, 0x1249u16, 0x1257u16,
0x1259u16, 0x1289u16, 0x12b1u16, 0x12bfu16, 0x12c1u16, 0x12d7u16, 0x1311u16, 0x1680u16,
0x176du16, 0x1771u16, 0x180eu16, 0x191fu16, 0x1a5fu16, 0x1b7fu16, 0x1f58u16, 0x1f5au16,
0x1f5cu16, 0x1f5eu16, 0x1fb5u16, 0x1fc5u16, 0x1fdcu16, 0x1ff5u16, 0x208fu16, 0x2b96u16,
0x2d26u16, 0x2da7u16, 0x2dafu16, 0x2db7u16, 0x2dbfu16, 0x2dc7u16, 0x2dcfu16, 0x2dd7u16,
0x2ddfu16, 0x2e9au16, 0x3040u16, 0x3130u16, 0x318fu16, 0x321fu16, 0xa7d2u16, 0xa7d4u16,
0xa9ceu16, 0xa9ffu16, 0xab27u16, 0xab2fu16, 0xfb37u16, 0xfb3du16, 0xfb3fu16, 0xfb42u16,
0xfb45u16, 0xfe53u16, 0xfe67u16, 0xfe75u16, 0xffe7u16,
];
let sepprint32: [508]u32 = [
0x010000u32, 0x01004du32, 0x010050u32, 0x01005du32, 0x010080u32, 0x0100fau32, 0x010100u32, 0x010102u32,
0x010107u32, 0x010133u32, 0x010137u32, 0x01019cu32, 0x0101a0u32, 0x0101a0u32, 0x0101d0u32, 0x0101fdu32,
0x010280u32, 0x01029cu32, 0x0102a0u32, 0x0102d0u32, 0x0102e0u32, 0x0102fbu32, 0x010300u32, 0x010323u32,
0x01032du32, 0x01034au32, 0x010350u32, 0x01037au32, 0x010380u32, 0x0103c3u32, 0x0103c8u32, 0x0103d5u32,
0x010400u32, 0x01049du32, 0x0104a0u32, 0x0104a9u32, 0x0104b0u32, 0x0104d3u32, 0x0104d8u32, 0x0104fbu32,
0x010500u32, 0x010527u32, 0x010530u32, 0x010563u32, 0x01056fu32, 0x0105bcu32, 0x010600u32, 0x010736u32,
0x010740u32, 0x010755u32, 0x010760u32, 0x010767u32, 0x010780u32, 0x0107bau32, 0x010800u32, 0x010805u32,
0x010808u32, 0x010838u32, 0x01083cu32, 0x01083cu32, 0x01083fu32, 0x01089eu32, 0x0108a7u32, 0x0108afu32,
0x0108e0u32, 0x0108f5u32, 0x0108fbu32, 0x01091bu32, 0x01091fu32, 0x010939u32, 0x01093fu32, 0x01093fu32,
0x010980u32, 0x0109b7u32, 0x0109bcu32, 0x0109cfu32, 0x0109d2u32, 0x010a06u32, 0x010a0cu32, 0x010a35u32,
0x010a38u32, 0x010a3au32, 0x010a3fu32, 0x010a48u32, 0x010a50u32, 0x010a58u32, 0x010a60u32, 0x010a9fu32,
0x010ac0u32, 0x010ae6u32, 0x010aebu32, 0x010af6u32, 0x010b00u32, 0x010b35u32, 0x010b39u32, 0x010b55u32,
0x010b58u32, 0x010b72u32, 0x010b78u32, 0x010b91u32, 0x010b99u32, 0x010b9cu32, 0x010ba9u32, 0x010bafu32,
0x010c00u32, 0x010c48u32, 0x010c80u32, 0x010cb2u32, 0x010cc0u32, 0x010cf2u32, 0x010cfau32, 0x010d27u32,
0x010d30u32, 0x010d39u32, 0x010e60u32, 0x010eadu32, 0x010eb0u32, 0x010eb1u32, 0x010efdu32, 0x010f27u32,
0x010f30u32, 0x010f59u32, 0x010f70u32, 0x010f89u32, 0x010fb0u32, 0x010fcbu32, 0x010fe0u32, 0x010ff6u32,
0x011000u32, 0x01104du32, 0x011052u32, 0x011075u32, 0x01107fu32, 0x0110c2u32, 0x0110d0u32, 0x0110e8u32,
0x0110f0u32, 0x0110f9u32, 0x011100u32, 0x011147u32, 0x011150u32, 0x011176u32, 0x011180u32, 0x0111f4u32,
0x011200u32, 0x011241u32, 0x011280u32, 0x0112a9u32, 0x0112b0u32, 0x0112eau32, 0x0112f0u32, 0x0112f9u32,
0x011300u32, 0x01130cu32, 0x01130fu32, 0x011310u32, 0x011313u32, 0x011344u32, 0x011347u32, 0x011348u32,
0x01134bu32, 0x01134du32, 0x011350u32, 0x011350u32, 0x011357u32, 0x011357u32, 0x01135du32, 0x011363u32,
0x011366u32, 0x01136cu32, 0x011370u32, 0x011374u32, 0x011400u32, 0x011461u32, 0x011480u32, 0x0114c7u32,
0x0114d0u32, 0x0114d9u32, 0x011580u32, 0x0115b5u32, 0x0115b8u32, 0x0115ddu32, 0x011600u32, 0x011644u32,
0x011650u32, 0x011659u32, 0x011660u32, 0x01166cu32, 0x011680u32, 0x0116b9u32, 0x0116c0u32, 0x0116c9u32,
0x011700u32, 0x01171au32, 0x01171du32, 0x01172bu32, 0x011730u32, 0x011746u32, 0x011800u32, 0x01183bu32,
0x0118a0u32, 0x0118f2u32, 0x0118ffu32, 0x011906u32, 0x011909u32, 0x011909u32, 0x01190cu32, 0x011938u32,
0x01193bu32, 0x011946u32, 0x011950u32, 0x011959u32, 0x0119a0u32, 0x0119a7u32, 0x0119aau32, 0x0119d7u32,
0x0119dau32, 0x0119e4u32, 0x011a00u32, 0x011a47u32, 0x011a50u32, 0x011aa2u32, 0x011ab0u32, 0x011af8u32,
0x011b00u32, 0x011b09u32, 0x011c00u32, 0x011c45u32, 0x011c50u32, 0x011c6cu32, 0x011c70u32, 0x011c8fu32,
0x011c92u32, 0x011cb6u32, 0x011d00u32, 0x011d36u32, 0x011d3au32, 0x011d47u32, 0x011d50u32, 0x011d59u32,
0x011d60u32, 0x011d98u32, 0x011da0u32, 0x011da9u32, 0x011ee0u32, 0x011ef8u32, 0x011f00u32, 0x011f3au32,
0x011f3eu32, 0x011f59u32, 0x011fb0u32, 0x011fb0u32, 0x011fc0u32, 0x011ff1u32, 0x011fffu32, 0x012399u32,
0x012400u32, 0x012474u32, 0x012480u32, 0x012543u32, 0x012f90u32, 0x012ff2u32, 0x013000u32, 0x01342fu32,
0x013440u32, 0x013455u32, 0x014400u32, 0x014646u32, 0x016800u32, 0x016a38u32, 0x016a40u32, 0x016a69u32,
0x016a6eu32, 0x016ac9u32, 0x016ad0u32, 0x016aedu32, 0x016af0u32, 0x016af5u32, 0x016b00u32, 0x016b45u32,
0x016b50u32, 0x016b77u32, 0x016b7du32, 0x016b8fu32, 0x016e40u32, 0x016e9au32, 0x016f00u32, 0x016f4au32,
0x016f4fu32, 0x016f87u32, 0x016f8fu32, 0x016f9fu32, 0x016fe0u32, 0x016fe4u32, 0x016ff0u32, 0x016ff1u32,
0x017000u32, 0x0187f7u32, 0x018800u32, 0x018cd5u32, 0x018d00u32, 0x018d08u32, 0x01aff0u32, 0x01b122u32,
0x01b132u32, 0x01b132u32, 0x01b150u32, 0x01b152u32, 0x01b155u32, 0x01b155u32, 0x01b164u32, 0x01b167u32,
0x01b170u32, 0x01b2fbu32, 0x01bc00u32, 0x01bc6au32, 0x01bc70u32, 0x01bc7cu32, 0x01bc80u32, 0x01bc88u32,
0x01bc90u32, 0x01bc99u32, 0x01bc9cu32, 0x01bc9fu32, 0x01cf00u32, 0x01cf2du32, 0x01cf30u32, 0x01cf46u32,
0x01cf50u32, 0x01cfc3u32, 0x01d000u32, 0x01d0f5u32, 0x01d100u32, 0x01d126u32, 0x01d129u32, 0x01d172u32,
0x01d17bu32, 0x01d1eau32, 0x01d200u32, 0x01d245u32, 0x01d2c0u32, 0x01d2d3u32, 0x01d2e0u32, 0x01d2f3u32,
0x01d300u32, 0x01d356u32, 0x01d360u32, 0x01d378u32, 0x01d400u32, 0x01d49fu32, 0x01d4a2u32, 0x01d4a2u32,
0x01d4a5u32, 0x01d4a6u32, 0x01d4a9u32, 0x01d50au32, 0x01d50du32, 0x01d546u32, 0x01d54au32, 0x01d6a5u32,
0x01d6a8u32, 0x01d7cbu32, 0x01d7ceu32, 0x01da8bu32, 0x01da9bu32, 0x01daafu32, 0x01df00u32, 0x01df1eu32,
0x01df25u32, 0x01df2au32, 0x01e000u32, 0x01e018u32, 0x01e01bu32, 0x01e02au32, 0x01e030u32, 0x01e06du32,
0x01e08fu32, 0x01e08fu32, 0x01e100u32, 0x01e12cu32, 0x01e130u32, 0x01e13du32, 0x01e140u32, 0x01e149u32,
0x01e14eu32, 0x01e14fu32, 0x01e290u32, 0x01e2aeu32, 0x01e2c0u32, 0x01e2f9u32, 0x01e2ffu32, 0x01e2ffu32,
0x01e4d0u32, 0x01e4f9u32, 0x01e7e0u32, 0x01e8c4u32, 0x01e8c7u32, 0x01e8d6u32, 0x01e900u32, 0x01e94bu32,
0x01e950u32, 0x01e959u32, 0x01e95eu32, 0x01e95fu32, 0x01ec71u32, 0x01ecb4u32, 0x01ed01u32, 0x01ed3du32,
0x01ee00u32, 0x01ee24u32, 0x01ee27u32, 0x01ee3bu32, 0x01ee42u32, 0x01ee42u32, 0x01ee47u32, 0x01ee54u32,
0x01ee57u32, 0x01ee64u32, 0x01ee67u32, 0x01ee9bu32, 0x01eea1u32, 0x01eebbu32, 0x01eef0u32, 0x01eef1u32,
0x01f000u32, 0x01f02bu32, 0x01f030u32, 0x01f093u32, 0x01f0a0u32, 0x01f0aeu32, 0x01f0b1u32, 0x01f0f5u32,
0x01f100u32, 0x01f1adu32, 0x01f1e6u32, 0x01f202u32, 0x01f210u32, 0x01f23bu32, 0x01f240u32, 0x01f248u32,
0x01f250u32, 0x01f251u32, 0x01f260u32, 0x01f265u32, 0x01f300u32, 0x01f6d7u32, 0x01f6dcu32, 0x01f6ecu32,
0x01f6f0u32, 0x01f6fcu32, 0x01f700u32, 0x01f776u32, 0x01f77bu32, 0x01f7d9u32, 0x01f7e0u32, 0x01f7ebu32,
0x01f7f0u32, 0x01f7f0u32, 0x01f800u32, 0x01f80bu32, 0x01f810u32, 0x01f847u32, 0x01f850u32, 0x01f859u32,
0x01f860u32, 0x01f887u32, 0x01f890u32, 0x01f8adu32, 0x01f8b0u32, 0x01f8b1u32, 0x01f900u32, 0x01fa53u32,
0x01fa60u32, 0x01fa6du32, 0x01fa70u32, 0x01fa7cu32, 0x01fa80u32, 0x01fa88u32, 0x01fa90u32, 0x01fac5u32,
0x01faceu32, 0x01fadbu32, 0x01fae0u32, 0x01fae8u32, 0x01faf0u32, 0x01faf8u32, 0x01fb00u32, 0x01fbcau32,
0x01fbf0u32, 0x01fbf9u32, 0x020000u32, 0x02a6dfu32, 0x02a700u32, 0x02b739u32, 0x02b740u32, 0x02b81du32,
0x02b820u32, 0x02cea1u32, 0x02ceb0u32, 0x02ebe0u32, 0x02f800u32, 0x02fa1du32, 0x030000u32, 0x03134au32,
0x031350u32, 0x0323afu32, 0x0e0100u32, 0x0e01efu32,
];
let sepnotprint32: [112]u16 = [
0x000cu16, 0x0027u16, 0x003bu16, 0x003eu16, 0x018fu16, 0x039eu16, 0x057bu16,
0x058bu16, 0x0593u16, 0x0596u16, 0x05a2u16, 0x05b2u16, 0x05bau16, 0x0786u16, 0x07b1u16,
0x0809u16, 0x0836u16, 0x0856u16, 0x08f3u16, 0x0a04u16, 0x0a14u16, 0x0a18u16, 0x0e7fu16,
0x0eaau16, 0x10bdu16, 0x1135u16, 0x11e0u16, 0x1212u16, 0x1287u16, 0x1289u16, 0x128eu16,
0x129eu16, 0x1304u16, 0x1329u16, 0x1331u16, 0x1334u16, 0x133au16, 0x145cu16, 0x1914u16,
0x1917u16, 0x1936u16, 0x1c09u16, 0x1c37u16, 0x1ca8u16, 0x1d07u16, 0x1d0au16, 0x1d3bu16,
0x1d3eu16, 0x1d66u16, 0x1d69u16, 0x1d8fu16, 0x1d92u16, 0x1f11u16, 0x246fu16, 0x6a5fu16,
0x6abfu16, 0x6b5au16, 0x6b62u16, 0xaff4u16, 0xaffcu16, 0xafffu16, 0xd455u16, 0xd49du16,
0xd4adu16, 0xd4bau16, 0xd4bcu16, 0xd4c4u16, 0xd506u16, 0xd515u16, 0xd51du16, 0xd53au16,
0xd53fu16, 0xd545u16, 0xd551u16, 0xdaa0u16, 0xe007u16, 0xe022u16, 0xe025u16, 0xe7e7u16,
0xe7ecu16, 0xe7efu16, 0xe7ffu16, 0xee04u16, 0xee20u16, 0xee23u16, 0xee28u16, 0xee33u16,
0xee38u16, 0xee3au16, 0xee48u16, 0xee4au16, 0xee4cu16, 0xee50u16, 0xee53u16, 0xee58u16,
0xee5au16, 0xee5cu16, 0xee5eu16, 0xee60u16, 0xee63u16, 0xee6bu16, 0xee73u16, 0xee78u16,
0xee7du16, 0xee7fu16, 0xee8au16, 0xeea4u16, 0xeeaau16, 0xf0c0u16, 0xf0d0u16, 0xfabeu16,
0xfb93u16,
];
type sepfoldentry = struct {
scope: *u8, // canonical directory; nil for package identities
key: *u8, // request-only Go simple-fold key
exact: *u8, // exact canonical identity or selected basename
};
type seppkg = struct {
path: *u8, // compiler/import identity derived from importbase
importbase: *u8, // canonical ordinary directory import identity
entry: *u8, // resolved package dir (or file, file root), NUL-term
canon: *u8, // canonical location; never package identity
artifact: *u8, // stable non-importable variant artifact key
storage: *u8, // internal storage basename; never package identity
initsymbol: *u8, // canonical package/variant-owned hidden task
storagehashed: bool,
name: *u8, // validated declared name; directory packages only
testpackage: *u8,
fortest: *u8,
sources: **u8, // owned, byte-sorted selected paths; dirs only
nsources: i32,
isdir: i32,
variant: i32,
role: i32,
root: bool, // requested usage; never package-action identity
linkentry: bool,
generatedmain: bool,
generatedtargets: []i32,
ngeneratedtargets: i32,
failed: bool,
action: bool, // reached by this request's semantic action list
testsupport: bool,
loaded: bool,
exportchanged: bool,
sourcestaged: bool,
initstaged: bool,
archivestaged: bool,
emitcontext: i32,
contextstate: []u8, // zero-extended lazily for reached contexts
bindings: []sepbind,
deps: []i32, // stable direct-dep indices into sepgraph.pkg
ndeps: i32,
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
};
type sepcontext = struct {
root: *u8,
searchpath: *u8,
route: *u8,
sourceroot: *u8,
};
type sepgraph = struct {
pkg: []seppkg, // len is allocated capacity; n is action count
n: i32,
context: []sepcontext, // len is allocated capacity
ncontext: i32,
supportcontext: i32,
identityfailed: bool,
packagefolds: []sepfoldentry,
npackagefolds: i32,
filefolds: []sepfoldentry,
nfilefolds: i32,
};
type sepproduct = struct {
dir: *u8,
out: *u8,
identity: *u8,
testpackage: *u8,
productionpackage: *u8,
internalpackage: *u8,
externalpackage: *u8,
status: *u8,
publish: *u8,
artifact: *u8,
variant: i32,
directoryproduct: bool,
notests: bool,
buildaction: bool, // loaded product retained in the action list
publicout: bool,
context: i32,
root: i32,
variantroot: i32,
productionroot: i32,
ptest: i32,
pxtest: i32,
support: i32,
stageout: *u8,
stagepublish: *u8,
stageiface: *u8,
stagestatus: *u8,
};
fn sepgrowcap(current: i32, need: i32) i32 = {
if (need < 0) {
sepfailsize();
return -1;
};
if (need <= current) { return current; };
let cap: i32 = current;
if (cap == 0) { cap = SEP_INITIAL_CAP; };
for (cap < need) {
if (cap > SEP_COUNT_MAX / 2) {
cap = SEP_COUNT_MAX;
break;
};
cap *= 2;
};
if (cap < need) {
sepfailsize();
return -1;
};
return cap;
};
fn sepallocpackages(cap: i32) ([]seppkg | nomem) = {
let value: []seppkg = alloc([], cap: u64)?;
return value;
};
fn sepalloccontexts(cap: i32) ([]sepcontext | nomem) = {
let value: []sepcontext = alloc([], cap: u64)?;
return value;
};
fn sepallocints(cap: i32) ([]i32 | nomem) = {
let value: []i32 = alloc([], cap: u64)?;
return value;
};
fn sepallocu64s(cap: i32) ([]u64 | nomem) = {
let value: []u64 = alloc([], cap: u64)?;
return value;
};
fn sepallocnodeptrs(cap: i32) ([]*syntax.node | nomem) = {
let value: []*syntax.node = alloc([], cap: u64)?;
return value;
};
fn sepallocbinds(cap: i32) ([]sepbind | nomem) = {
let value: []sepbind = alloc([], cap: u64)?;
return value;
};
fn sepallocchildren(cap: i32) ([]sepchild | nomem) = {
let value: []sepchild = alloc([], cap: u64)?;
return value;
};
fn sepallocfoldentries(cap: i32) ([]sepfoldentry | nomem) = {
let value: []sepfoldentry = alloc([], cap: u64)?;
return value;
};
fn sepdupstr(s: str) (str | nomem) = {
let out: str;
out.ptr = nil;
out.len = 0;
out.cap = 0;
if (s.len == 0) { return out; };
let allocation: ([]u8 | nomem) = sepallocbytes(s.len);
let bytes: []u8;
match (allocation) {
case let value: []u8 => bytes = value;
case let e: nomem => return e;
};
bytes.len = s.len;
let i: i32 = 0;
for (i < s.len) { bytes[i] = s[i]; i += 1; };
out.ptr = bytes.ptr;
out.len = bytes.len;
out.cap = bytes.cap;
return out;
};
fn sepallocbytes(cap: i32) ([]u8 | nomem) = {
let value: []u8 = alloc([], cap: u64)?;
return value;
};
fn sepallocproducts(cap: i32) ([]sepproduct | nomem) = {
let value: []sepproduct = alloc([], cap: u64)?;
return value;
};
fn sepallocstrs(cap: i32) ([]str | nomem) = {
let value: []str = alloc([], cap: u64)?;
return value;
};
fn sepallocptrs(cap: i32) ([]*u8 | nomem) = {
let value: []*u8 = alloc([], cap: u64)?;
return value;
};
type septestenv = struct {
values: []str,
path: str,
};
// Pinned cmd/go gives each test binary PATH=$GOROOT/bin:$PATH. The selected
// WW driver's sibling directory is the local toolchain-bin analogue. Remove
// normal inherited PATH duplicates because lib/os/exec deliberately preserves
// the concrete environment array while Go's os/exec keeps the appended value.
fn sepmaketestenv(toolbin: str, out: *septestenv) bool = {
let inherited: []str = os.getenvs();
if (inherited.len == SEP_COUNT_MAX) {
cerr("ww: cannot prepare test environment\n");
return false;
};
let oldpath: str = "";
match (os.getenv("PATH")) {
case let value: str => oldpath = value;
case void => void;
};
let total: i64 = 5i64 + (toolbin.len: i64);
if (oldpath.len != 0) { total += 1i64 + (oldpath.len: i64); };
if (total > SEP_COUNT_MAX: i64) {
cerr("ww: cannot prepare test environment\n");
return false;
};
let pathallocation: ([]u8 | nomem) = sepallocbytes(total: i32);
let pathbytes: []u8;
match (pathallocation) {
case let value: []u8 => pathbytes = value;
case nomem => {
cerr("ww: cannot prepare test environment\n");
return false;
};
};
let prefix: str = "PATH=";
let i: i32 = 0;
for (i < prefix.len) { append(pathbytes, prefix[i]); i += 1; };
i = 0;
for (i < toolbin.len) { append(pathbytes, toolbin[i]); i += 1; };
if (oldpath.len != 0) {
append(pathbytes, ':');
i = 0;
for (i < oldpath.len) { append(pathbytes, oldpath[i]); i += 1; };
};
let pathenv: str = strings.frombytes(pathbytes);
let envallocation: ([]str | nomem) = sepallocstrs(inherited.len + 1);
let env: []str;
match (envallocation) {
case let value: []str => env = value;
case nomem => {
os.free(pathenv.ptr: *void, pathenv.len: u64);
cerr("ww: cannot prepare test environment\n");
return false;
};
};
i = 0;
let inserted: bool = false;
for (i < inherited.len) {
if (strings.hasprefix(inherited[i], "PATH=")) {
if (!inserted) { append(env, pathenv); inserted = true; };
} else {
append(env, inherited[i]);
};
i += 1;
};
if (!inserted) { append(env, pathenv); };
out.values = env;
out.path = pathenv;
return true;
};
fn sepfreetestenv(env: *septestenv) void = {
if (env.path.ptr != nil && env.path.len != 0) {
os.free(env.path.ptr: *void, env.path.len: u64);
};
if (env.values.ptr != nil && env.values.cap != 0) {
os.free(env.values.ptr: *void,
(env.values.cap: u64) * (size(str): u64));
};
};
fn sepallocgraph(pkg: []seppkg, context: []sepcontext) (*sepgraph | nomem) = {
let emptyfolds: []sepfoldentry;
let value: *sepgraph = alloc(sepgraph{
pkg = pkg,
n = 0,
context = context,
ncontext = 0,
supportcontext = -1,
identityfailed = false,
packagefolds = emptyfolds,
npackagefolds = 0,
filefolds = emptyfolds,
nfilefolds = 0,
})?;
return value;
};
fn sepmakeints(count: i32, out: *[]i32) bool = {
if (count < 0) {
sepfailsize();
return false;
};
let allocation: ([]i32 | nomem) = sepallocints(count);
match (allocation) {
case let value: []i32 => {
value.len = count;
*out = value;
return true;
};
case nomem => { sepfailnomem(); return false; };
};
return false;
};
fn sepmakeptrs(count: i32, out: *[]*u8) bool = {
if (count < 0) {
sepfailsize();
return false;
};
let allocation: ([]*u8 | nomem) = sepallocptrs(count);
match (allocation) {
case let value: []*u8 => {
value.len = count;
*out = value;
return true;
};
case nomem => { sepfailnomem(); return false; };
};
return false;
};
fn sepreservepackages(g: *sepgraph, need: i32) bool = {
if (need <= g.pkg.len) { return true; };
let cap: i32 = sepgrowcap(g.pkg.len, need);
if (cap < 0) { return false; };
let allocation: ([]seppkg | nomem) = sepallocpackages(cap);
let next: []seppkg;
match (allocation) {
case let v: []seppkg => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < g.n) { next[i] = g.pkg[i]; i += 1; };
if (g.pkg.ptr != nil) {
os.free(g.pkg.ptr: *void,
(g.pkg.cap: u64) * (size(seppkg): u64));
};
g.pkg = next;
return true;
};
fn sepreservefoldentries(entries: *[]sepfoldentry, used: i32,
need: i32) bool = {
if (need <= entries.len) { return true; };
let cap: i32 = sepgrowcap(entries.len, need);
if (cap < 0) { return false; };
let allocation: ([]sepfoldentry | nomem) = sepallocfoldentries(cap);
let next: []sepfoldentry;
match (allocation) {
case let value: []sepfoldentry => next = value;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < used) { next[i] = (*entries)[i]; i += 1; };
if (entries.ptr != nil) {
os.free(entries.ptr: *void,
(entries.cap: u64) * (size(sepfoldentry): u64));
};
*entries = next;
return true;
};
fn sepreservecontexts(g: *sepgraph, need: i32) bool = {
if (need <= g.context.len) { return true; };
let cap: i32 = sepgrowcap(g.context.len, need);
if (cap < 0) { return false; };
let allocation: ([]sepcontext | nomem) = sepalloccontexts(cap);
let next: []sepcontext;
match (allocation) {
case let v: []sepcontext => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < g.ncontext) { next[i] = g.context[i]; i += 1; };
if (g.context.ptr != nil) {
os.free(g.context.ptr: *void,
(g.context.cap: u64) * (size(sepcontext): u64));
};
g.context = next;
return true;
};
fn sepreservedeps(p: *seppkg, need: i32) bool = {
if (need <= p.deps.len) { return true; };
let cap: i32 = sepgrowcap(p.deps.len, need);
if (cap < 0) { return false; };
let allocation: ([]i32 | nomem) = sepallocints(cap);
let next: []i32;
match (allocation) {
case let v: []i32 => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < p.ndeps) { next[i] = p.deps[i]; i += 1; };
if (p.deps.ptr != nil) {
os.free(p.deps.ptr: *void,
(p.deps.cap: u64) * (size(i32): u64));
};
p.deps = next;
return true;
};
fn sepcontextstate(p: *seppkg, context: i32) u8 = {
if (context < 0 || context >= p.contextstate.len) { return 0u8; };
return p.contextstate[context];
};
fn sepsetcontextstate(p: *seppkg, context: i32, state: u8) bool = {
if (context < 0 || context == SEP_COUNT_MAX) {
sepfailsize();
return false;
};
let need: i32 = context + 1;
if (need > p.contextstate.len) {
let cap: i32 = sepgrowcap(p.contextstate.len, need);
if (cap < 0) { return false; };
let allocation: ([]u8 | nomem) = sepallocbytes(cap);
let next: []u8;
match (allocation) {
case let v: []u8 => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < p.contextstate.len) {
next[i] = p.contextstate[i];
i += 1;
};
for (i < cap) { next[i] = 0u8; i += 1; };
if (p.contextstate.ptr != nil) {
os.free(p.contextstate.ptr: *void,
(p.contextstate.cap: u64) * (size(u8): u64));
};
p.contextstate = next;
};
p.contextstate[context] = state;
return true;
};
fn sepadddep(g: *sepgraph, pi: i32, dep: i32) bool = {
let i: i32 = 0;
for (i < g.pkg[pi].ndeps) {
if (g.pkg[pi].deps[i] == dep) { return true; };
i += 1;
};
if (g.pkg[pi].ndeps == SEP_COUNT_MAX) {
sepfailsize();
return false;
};
if (!sepreservedeps(&g.pkg[pi], g.pkg[pi].ndeps + 1)) {
return false;
};
g.pkg[pi].deps[g.pkg[pi].ndeps] = dep;
g.pkg[pi].ndeps += 1;
return true;
};
fn sepreserveproducts(products: *[]sepproduct, need: i32) bool = {
if (need <= products.cap) { return true; };
let cap: i32 = sepgrowcap(products.cap, need);
if (cap < 0) { return false; };
let allocation: ([]sepproduct | nomem) = sepallocproducts(cap);
let next: []sepproduct;
match (allocation) {
case let v: []sepproduct => next = v;
case nomem => { sepfailnomem(); return false; };
};
let n: i32 = products.len;
next.len = cap;
let i: i32 = 0;
for (i < n) { next[i] = (*products)[i]; i += 1; };
next.len = n;
if (products.ptr != nil) {
os.free(products.ptr: *void,
(products.cap: u64) * (size(sepproduct): u64));
};
*products = next;
return true;
};
fn sepreservesources(names: *[]*u8, nlens: *[]u64, kinds: *[]i32,
used: i32, need: i32) bool = {
if (need <= names.len) { return true; };
let cap: i32 = sepgrowcap(names.len, need);
if (cap < 0) { return false; };
let namesallocation: ([]*u8 | nomem) = sepallocptrs(cap);
let nextnames: []*u8;
match (namesallocation) {
case let value: []*u8 => nextnames = value;
case nomem => { sepfailnomem(); return false; };
};
let lensallocation: ([]u64 | nomem) = sepallocu64s(cap);
let nextlens: []u64;
match (lensallocation) {
case let value: []u64 => nextlens = value;
case nomem => {
os.free(nextnames.ptr: *void,
(nextnames.cap: u64) * (size(*u8): u64));
sepfailnomem(); return false;
};
};
let kindsallocation: ([]i32 | nomem) = sepallocints(cap);
let nextkinds: []i32;
match (kindsallocation) {
case let value: []i32 => nextkinds = value;
case nomem => {
os.free(nextnames.ptr: *void,
(nextnames.cap: u64) * (size(*u8): u64));
os.free(nextlens.ptr: *void,
(nextlens.cap: u64) * (size(u64): u64));
sepfailnomem(); return false;
};
};
nextnames.len = cap;
nextlens.len = cap;
nextkinds.len = cap;
let i: i32 = 0;
for (i < used) {
nextnames[i] = (*names)[i];
nextlens[i] = (*nlens)[i];
nextkinds[i] = (*kinds)[i];
i += 1;
};
if (names.ptr != nil) {
os.free(names.ptr: *void,
(names.cap: u64) * (size(*u8): u64));
};
if (nlens.ptr != nil) {
os.free(nlens.ptr: *void,
(nlens.cap: u64) * (size(u64): u64));
};
if (kinds.ptr != nil) {
os.free(kinds.ptr: *void,
(kinds.cap: u64) * (size(i32): u64));
};
*names = nextnames;
*nlens = nextlens;
*kinds = nextkinds;
return true;
};
fn sepreservebinds(bindings: *[]sepbind, need: i32) bool = {
if (need <= bindings.cap) { return true; };
let cap: i32 = sepgrowcap(bindings.cap, need);
if (cap < 0) { return false; };
let allocation: ([]sepbind | nomem) = sepallocbinds(cap);
let next: []sepbind;
match (allocation) {
case let value: []sepbind => next = value;
case nomem => { sepfailnomem(); return false; };
};
let n: i32 = bindings.len;
next.len = cap;
let i: i32 = 0;
for (i < n) { next[i] = (*bindings)[i]; i += 1; };
next.len = n;
if (bindings.ptr != nil) {
os.free(bindings.ptr: *void,
(bindings.cap: u64) * (size(sepbind): u64));
};
*bindings = next;
return true;
};
fn sepreservechildren(children: *[]sepchild, need: i32) bool = {
if (need <= children.cap) { return true; };
let cap: i32 = sepgrowcap(children.cap, need);
if (cap < 0) { return false; };
let allocation: ([]sepchild | nomem) = sepallocchildren(cap);
let next: []sepchild;
match (allocation) {
case let value: []sepchild => next = value;
case nomem => { sepfailnomem(); return false; };
};
let n: i32 = children.len;
next.len = cap;
let i: i32 = 0;
for (i < n) { next[i] = (*children)[i]; i += 1; };
next.len = n;
if (children.ptr != nil) {
os.free(children.ptr: *void,
(children.cap: u64) * (size(sepchild): u64));
};
*children = next;
return true;
};
fn sepaddbytes(total: *u64, add: u64) bool = {
if (*total > SEP_COUNT_MAX: u64
|| add > (SEP_COUNT_MAX: u64) - *total) {
sepfailsize();
return false;
};
*total += add;
return true;
};
fn sepmuladdbytes(total: *u64, count: u64, factor: u64) bool = {
if (factor != 0u64 && count > (SEP_COUNT_MAX: u64) / factor) {
sepfailsize();
return false;
};
return sepaddbytes(total, count * factor);
};
fn sepmakebytes(count: u64, out: *[]u8) bool = {
let total: u64 = 0u64;
if (!sepaddbytes(&total, count)) { return false; };
let allocation: ([]u8 | nomem) = sepallocbytes(total: i32);
match (allocation) {
case let value: []u8 => {
value.len = total: i32;
*out = value;
return true;
};
case nomem => { sepfailnomem(); return false; };
};
return false;
};
// Package-semantic strings use the same checked signed-count storage rule as
// graph vectors. These helpers never publish a partial owner and mark their
// failure command-fatal so a sibling product cannot proceed to a tool.
fn sepdupcstr(src: *u8, n: u64) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, n) || !sepaddbytes(&need, 1u64)) {
return nil;
};
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let i: u64 = 0u64;
for (i < n) { out[i] = src[i]; i += 1u64; };
out[n] = 0u8;
return out.ptr;
};
// Decode filesystem-name bytes exactly like Go's UTF-8 range operation.
// Each malformed byte is one U+FFFD rune, while diagnostics retain the
// original byte spelling and quote malformed bytes as \xNN.
fn seputf8width(value: *u8, len: u64, index: u64) i32 = {
let c: u8 = value[index];
if (c < 128u8) { return 1; };
if (c >= 194u8 && c <= 223u8 && index + 1u64 < len
&& value[index + 1u64] >= 128u8
&& value[index + 1u64] <= 191u8) {
return 2;
};
if (index + 2u64 < len && value[index + 2u64] >= 128u8
&& value[index + 2u64] <= 191u8) {
let c1: u8 = value[index + 1u64];
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 + 3u64 < len && value[index + 2u64] >= 128u8
&& value[index + 2u64] <= 191u8
&& value[index + 3u64] >= 128u8
&& value[index + 3u64] <= 191u8) {
let c1: u8 = value[index + 1u64];
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;
};
fn seputf8rune(value: *u8, index: u64, width: i32) u32 = {
let c0: u32 = value[index]: u32;
if (width == 1) {
if (c0 >= 128u32) { return 0xfffdu32; };
return c0;
};
let c1: u32 = value[index + 1u64]: u32;
if (width == 2) {
return ((c0 & 31u32) << 6u32) | (c1 & 63u32);
};
let c2: u32 = value[index + 2u64]: u32;
if (width == 3) {
return ((c0 & 15u32) << 12u32)
| ((c1 & 63u32) << 6u32) | (c2 & 63u32);
};
let c3: u32 = value[index + 3u64]: u32;
return ((c0 & 7u32) << 18u32) | ((c1 & 63u32) << 12u32)
| ((c2 & 63u32) << 6u32) | (c3 & 63u32);
};
fn sepfoldrune(r: u32) u32 = {
let low: i32 = 0;
let high: i32 = sepfoldranges.len;
for (low < high) {
let middle: i32 = low + (high - low) / 2;
if (sepfoldranges[middle].lo <= r) { low = middle + 1; }
else { high = middle; };
};
if (low > 0) {
let range: *sepfoldrange = &sepfoldranges[low - 1];
if (r <= range.hi && (r - range.lo) % range.stride == 0u32) {
if (range.delta < 0) { return r - ((-range.delta): u32); };
return r + (range.delta: u32);
};
};
return r;
};
fn seputf8encodedwidth(r: u32) i32 = {
if (r <= 0x7fu32) { return 1; };
if (r <= 0x7ffu32) { return 2; };
if (r <= 0xffffu32) { return 3; };
return 4;
};
fn seputf8encode(out: *u8, offset: u64, r: u32) u64 = {
if (r <= 0x7fu32) {
out[offset] = r: u8;
offset += 1u64;
} else { if (r <= 0x7ffu32) {
out[offset] = (0xc0u32 | (r >> 6u32)): u8;
out[offset + 1u64] = (0x80u32 | (r & 0x3fu32)): u8;
offset += 2u64;
} else { if (r <= 0xffffu32) {
out[offset] = (0xe0u32 | (r >> 12u32)): u8;
out[offset + 1u64] = (0x80u32 | ((r >> 6u32) & 0x3fu32)): u8;
out[offset + 2u64] = (0x80u32 | (r & 0x3fu32)): u8;
offset += 3u64;
} else {
out[offset] = (0xf0u32 | (r >> 18u32)): u8;
out[offset + 1u64] = (0x80u32 | ((r >> 12u32) & 0x3fu32)): u8;
out[offset + 2u64] = (0x80u32 | ((r >> 6u32) & 0x3fu32)): u8;
out[offset + 3u64] = (0x80u32 | (r & 0x3fu32)): u8;
offset += 4u64;
}; }; };
return offset;
};
fn septofold(value: *u8) *u8 = {
let len: u64 = cstrlen(value);
let total: u64 = 0u64;
let i: u64 = 0u64;
for (i < len) {
let width: i32 = seputf8width(value, len, i);
let r: u32 = sepfoldrune(seputf8rune(value, i, width));
if (!sepaddbytes(&total, seputf8encodedwidth(r): u64)) {
return nil;
};
i += width: u64;
};
if (!sepaddbytes(&total, 1u64)) { return nil; };
let bytes: []u8;
if (!sepmakebytes(total, &bytes)) { return nil; };
let offset: u64 = 0u64;
i = 0u64;
for (i < len) {
let width: i32 = seputf8width(value, len, i);
let r: u32 = sepfoldrune(seputf8rune(value, i, width));
offset = seputf8encode(bytes.ptr, offset, r);
i += width: u64;
};
bytes[offset] = 0u8;
return bytes.ptr;
};
fn sepbsearch16(values: []u16, target: u16) i32 = {
let low: i32 = 0;
let high: i32 = values.len;
for (low < high) {
let middle: i32 = low + (high - low) / 2;
if (values[middle] < target) { low = middle + 1; }
else { high = middle; };
};
return low;
};
fn sepbsearch32(values: []u32, target: u32) i32 = {
let low: i32 = 0;
let high: i32 = values.len;
for (low < high) {
let middle: i32 = low + (high - low) / 2;
if (values[middle] < target) { low = middle + 1; }
else { high = middle; };
};
return low;
};
fn sepisprint(r: u32) bool = {
if (r <= 0xffu32) {
if (r >= 0x20u32 && r <= 0x7eu32) { return true; };
if (r >= 0xa1u32 && r <= 0xffu32) { return r != 0xadu32; };
return false;
};
if (r < 0x10000u32) {
let rr: u16 = r: u16;
let i: i32 = sepbsearch16(sepprint16, rr);
if (i >= sepprint16.len) { return false; };
let start: i32 = i;
if (start % 2 != 0) { start -= 1; };
if (start + 1 >= sepprint16.len || rr < sepprint16[start]
|| sepprint16[start + 1] < rr) { return false; };
let excluded: i32 = sepbsearch16(sepnotprint16, rr);
return excluded >= sepnotprint16.len
|| sepnotprint16[excluded] != rr;
};
let i: i32 = sepbsearch32(sepprint32, r);
if (i >= sepprint32.len) { return false; };
let start: i32 = i;
if (start % 2 != 0) { start -= 1; };
if (start + 1 >= sepprint32.len || r < sepprint32[start]
|| sepprint32[start + 1] < r) { return false; };
if (r >= 0x20000u32) { return true; };
let rr: u16 = (r - 0x10000u32): u16;
let excluded: i32 = sepbsearch16(sepnotprint32, rr);
return excluded >= sepnotprint32.len || sepnotprint32[excluded] != rr;
};
fn sepputhexbyte(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(2, &encoded[0], 2u64);
};
fn sepputhexrune(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 digit: i32 = ((value >> shift) & 15u32): i32;
encoded[digits - 1 - i] = alphabet[digit];
i -= 1;
};
os.write(2, &encoded[0], digits: u64);
};
fn sepputquoted(value: *u8) void = {
cerr("\"");
let len: u64 = cstrlen(value);
let i: u64 = 0u64;
for (i < len) {
let c: u8 = value[i];
let width: i32 = seputf8width(value, len, i);
if (c >= 128u8 && width == 1) {
cerr("\\x"); sepputhexbyte(c); i += 1u64; continue;
};
if (width > 1) {
let r: u32 = seputf8rune(value, i, width);
if (sepisprint(r)) {
os.write(2, value + i, width: u64);
} else { if (r < 0x10000u32) {
cerr("\\u"); sepputhexrune(r, 4);
} else {
cerr("\\U"); sepputhexrune(r, 8);
}; };
i += width: u64;
continue;
};
if (c == '"') { cerr("\\\""); }
else { if (c == '\\') { cerr("\\\\"); }
else { if (c == 7u8) { cerr("\\a"); }
else { if (c == 8u8) { cerr("\\b"); }
else { if (c == 12u8) { cerr("\\f"); }
else { if (c == '\n') { cerr("\\n"); }
else { if (c == '\r') { cerr("\\r"); }
else { if (c == '\t') { cerr("\\t"); }
else { if (c == 11u8) { cerr("\\v"); }
else { if (c < 32u8 || c == 127u8) {
cerr("\\x"); sepputhexbyte(c);
} else { os.write(2, value + i, 1u64); };
};};};};};};};};};
i += 1u64;
};
cerr("\"");
};
fn sepdiagfoldcollision(kind: str, a: *u8, b: *u8) void = {
let first: *u8 = a;
let second: *u8 = b;
if (strings.compare(pathstr(first), pathstr(second)) > 0) {
first = b; second = a;
};
cerr("ww: case-insensitive "); cerr(kind); cerr(" collision: ");
sepputquoted(first); cerr(" and "); sepputquoted(second); cerr("\n");
};
fn sepregisterpackagefold(g: *sepgraph, exact: *u8) bool = {
let key: *u8 = septofold(exact);
if (key == nil) { return false; };
let low: i32 = 0;
let high: i32 = g.npackagefolds;
for (low < high) {
let middle: i32 = low + (high - low) / 2;
let cmp: i32 = strings.compare(pathstr(g.packagefolds[middle].key),
pathstr(key)): i32;
if (cmp < 0) { low = middle + 1; } else { high = middle; };
};
if (low < g.npackagefolds
&& cstreq(g.packagefolds[low].key, key)) {
os.free(key: *void, cstrlen(key) + 1u64);
if (cstreq(g.packagefolds[low].exact, exact)) { return true; };
g.identityfailed = true;
sepdiagfoldcollision("import", g.packagefolds[low].exact, exact);
return false;
};
let spelling: *u8 = sepdupcstr(exact, cstrlen(exact));
if (spelling == nil) {
os.free(key: *void, cstrlen(key) + 1u64);
return false;
};
if (g.npackagefolds == SEP_COUNT_MAX
|| !sepreservefoldentries(&g.packagefolds, g.npackagefolds,
g.npackagefolds + 1)) {
if (g.npackagefolds == SEP_COUNT_MAX) { sepfailsize(); };
os.free(key: *void, cstrlen(key) + 1u64);
os.free(spelling: *void, cstrlen(spelling) + 1u64);
return false;
};
let i: i32 = g.npackagefolds;
for (i > low) {
g.packagefolds[i] = g.packagefolds[i - 1];
i -= 1;
};
g.packagefolds[low].scope = nil;
g.packagefolds[low].key = key;
g.packagefolds[low].exact = spelling;
g.npackagefolds += 1;
return true;
};
fn sepfilefoldcmp(entry: *sepfoldentry, scope: *u8, key: *u8) i32 = {
let cmp: i32 = strings.compare(pathstr(entry.scope), pathstr(scope)): i32;
if (cmp != 0) { return cmp; };
return strings.compare(pathstr(entry.key), pathstr(key)): i32;
};
fn sepregisterfilefold(g: *sepgraph, scope: *u8, source: *u8) bool = {
let total: u64 = cstrlen(source);
let base: u64 = total;
for (base > 0u64 && source[base - 1u64] != '/': u8) { base -= 1u64; };
let exact: *u8 = source + base;
let key: *u8 = septofold(exact);
if (key == nil) { return false; };
let low: i32 = 0;
let high: i32 = g.nfilefolds;
for (low < high) {
let middle: i32 = low + (high - low) / 2;
let cmp: i32 = sepfilefoldcmp(&g.filefolds[middle], scope, key);
if (cmp < 0) { low = middle + 1; } else { high = middle; };
};
if (low < g.nfilefolds
&& sepfilefoldcmp(&g.filefolds[low], scope, key) == 0) {
os.free(key: *void, cstrlen(key) + 1u64);
if (cstreq(g.filefolds[low].exact, exact)) { return true; };
g.identityfailed = true;
sepdiagfoldcollision("file name", g.filefolds[low].exact, exact);
return false;
};
let ownedscope: *u8 = sepdupcstr(scope, cstrlen(scope));
let spelling: *u8 = sepdupcstr(exact, cstrlen(exact));
if (ownedscope == nil || spelling == nil) {
os.free(key: *void, cstrlen(key) + 1u64);
if (ownedscope != nil) {
os.free(ownedscope: *void, cstrlen(ownedscope) + 1u64);
};
if (spelling != nil) {
os.free(spelling: *void, cstrlen(spelling) + 1u64);
};
return false;
};
if (g.nfilefolds == SEP_COUNT_MAX
|| !sepreservefoldentries(&g.filefolds, g.nfilefolds,
g.nfilefolds + 1)) {
if (g.nfilefolds == SEP_COUNT_MAX) { sepfailsize(); };
os.free(key: *void, cstrlen(key) + 1u64);
os.free(ownedscope: *void, cstrlen(ownedscope) + 1u64);
os.free(spelling: *void, cstrlen(spelling) + 1u64);
return false;
};
let i: i32 = g.nfilefolds;
for (i > low) {
g.filefolds[i] = g.filefolds[i - 1];
i -= 1;
};
g.filefolds[low].scope = ownedscope;
g.filefolds[low].key = key;
g.filefolds[low].exact = spelling;
g.nfilefolds += 1;
return true;
};
fn sepappendlit(stem: *u8, suffix: str) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, cstrlen(stem))
|| !sepaddbytes(&need, suffix.len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = cstrinto(out.ptr, 0u64, stem);
off = strinto(out.ptr, off, suffix);
cstrseal(out.ptr, off);
return out.ptr;
};
fn sepjoinpathlit(dir: *u8, name: str) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, cstrlen(dir))
|| !sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, name.len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = cstrinto(out.ptr, 0u64, dir);
off = byteinto(out.ptr, off, '/': u8);
off = strinto(out.ptr, off, name);
cstrseal(out.ptr, off);
return out.ptr;
};
fn sepjoinpath(dir: *u8, name: *u8) *u8 = {
return sepjoinpathlit(dir, pathstr(name));
};
fn sepphysicaljoin(dir: *u8, name: str) *u8 = {
if (cstrlen(dir) == 1u64 && dir[0u64] == '/': u8) {
return sepappendlit(dir, name);
};
return sepjoinpathlit(dir, name);
};
fn seppendingpath(target: str, rest: *u8, requiredir: bool) *u8 = {
let restlen: u64 = cstrlen(rest);
let need: u64 = 0u64;
if (!sepaddbytes(&need, target.len: u64)
|| (target.len != 0 && (restlen != 0 || requiredir)
&& !sepaddbytes(&need, 1u64))
|| !sepaddbytes(&need, restlen)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = strinto(out.ptr, 0u64, target);
if (target.len != 0 && (restlen != 0 || requiredir)) {
off = byteinto(out.ptr, off, '/': u8);
};
off = cstrinto(out.ptr, off, rest);
cstrseal(out.ptr, off);
return out.ptr;
};
fn sepphysicalparent(path: *u8) *u8 = {
let n: u64 = cstrlen(path);
for (n > 1u64 && path[n - 1u64] == '/': u8) { n -= 1u64; };
for (n > 1u64 && path[n - 1u64] != '/': u8) { n -= 1u64; };
if (n > 1u64) { n -= 1u64; };
return sepdupcstr(path, n);
};
fn sepcanonicalfile(path: *u8) *u8 = {
if (path[0u64] == 0u8) { return nil; };
let pending: *u8 = sepdupcstr(path, cstrlen(path));
if (pending == nil) { return nil; };
let resolved: *u8 = nil;
if (path[0u64] == '/': u8) {
resolved = sepdupcstr("/".ptr, 1u64);
} else {
let cwd: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &cwd)) { return nil; };
let n: i64 = os.getcwd(cwd.ptr, cwd.len: u64);
if (n <= 1i64 || n > cwd.len: i64) { return nil; };
resolved = sepdupcstr(cwd.ptr, (n - 1i64): u64);
};
if (resolved == nil) { return nil; };
let links: i32 = 0;
for (true) {
let total: u64 = cstrlen(pending);
let start: u64 = 0u64;
for (start < total && pending[start] == '/': u8) { start += 1u64; };
if (start == total) { return resolved; };
let end: u64 = start;
for (end < total && pending[end] != '/': u8) { end += 1u64; };
let followed: bool = end < total;
let rest: u64 = end;
for (rest < total && pending[rest] == '/': u8) { rest += 1u64; };
let requiredir: bool = followed && rest == total;
let name: str;
name.ptr = pending + start;
name.len = (end - start): i32;
if (name.len == 1 && name[0] == '.': u8) {
pending = pending + rest;
continue;
};
if (name.len == 2 && name[0] == '.': u8 && name[1] == '.': u8) {
resolved = sepphysicalparent(resolved);
if (resolved == nil) { return nil; };
pending = pending + rest;
continue;
};
let candidate: *u8 = sepphysicaljoin(resolved, name);
if (candidate == nil) { return nil; };
let fi: os.filestat;
match (os.lstat(&fi, pathstr(candidate))) {
case void => void;
case let e: os.oserror => return nil;
};
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ == os.mode.LINK: u32) {
if (links == 40) { return nil; };
links += 1;
let target: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &target)) { return nil; };
let n: i64 = os.readlink(pathstr(candidate), target.ptr,
os.PATH_MAX: u64);
if (n < 0i64 || n >= os.PATH_MAX: i64) { return nil; };
let ni: i32 = n: i32;
target[ni] = 0u8;
let targetname: str;
targetname.ptr = target.ptr;
targetname.len = ni;
if (ni > 0 && target[0] == '/': u8) {
resolved = sepdupcstr("/".ptr, 1u64);
if (resolved == nil) { return nil; };
};
pending = seppendingpath(targetname, pending + rest, requiredir);
if (pending == nil) { return nil; };
continue;
};
if (followed && typ != os.mode.DIR: u32) { return nil; };
resolved = candidate;
pending = pending + rest;
};
};
fn sepdirectoryvariant(variant: i32) bool = {
return variant == SEP_VARIANT_PRODUCTION
|| variant == SEP_VARIANT_SAME_TEST
|| variant == SEP_VARIANT_EXTERNAL;
};
fn sepvariantpath(variant: i32, base: *u8) *u8 = {
if (variant == SEP_VARIANT_EXTERNAL) { return sepappendlit(base, "_test"); };
return sepdupcstr(base, cstrlen(base));
};
fn sepdiagpathlocations(path: *u8, a: *u8, b: *u8) void = {
let first: *u8 = a;
let second: *u8 = b;
if (strings.compare(pathstr(first), pathstr(second)) > 0) {
first = b; second = a;
};
cerr("ww: package "); cerr(pathstr(path));
cerr(" resolves to directories "); cerr(pathstr(first));
cerr(" and "); cerr(pathstr(second)); cerr("\n");
};
fn sepdiagdiridentities(entry: *u8, a: *u8, b: *u8) void = {
let first: *u8 = a;
let second: *u8 = b;
if (strings.compare(pathstr(first), pathstr(second)) > 0) {
first = b; second = a;
};
cerr("ww: package directory "); cerr(pathstr(entry));
cerr(" has import identities "); cerr(pathstr(first));
cerr(" and "); cerr(pathstr(second)); cerr("\n");
};
fn sepimportbasevalid(base: *u8) bool = {
let total: u64 = cstrlen(base);
let pos: u64 = 0u64;
for (pos < total) {
let end: u64 = pos;
for (end < total && base[end] != '.': u8) { end += 1u64; };
if (!sepimportcomponent(base + pos, end - pos)) { return false; };
if (end == total) { return true; };
pos = end + 1u64;
};
return false;
};
// Go-style command packages keep their canonical import identity while
// declaring main; an external command-test variant declares main_test. These
// declarations classify package kind but never enter action identity.
fn sepcommanddeclaredname(p: *seppkg) bool = {
if (p.name == nil) { return false; };
if (p.variant == SEP_VARIANT_EXTERNAL) {
return cstreqlit(p.name, "main_test");
};
return cstreqlit(p.name, "main");
};
// Directory-package action kind comes only from the loaded declaration. An
// explicit package-less file is the retained raw-unit compatibility path and
// remains a command unit.
fn seprootiscommand(p: *seppkg) bool = {
if (p.name == nil) { return p.isdir == 0; };
return cstreqlit(p.name, "main");
};
fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = {
let to: *seppkg = &g.pkg[dep];
if (to.name == nil || !cstreqlit(to.name, "main")
|| to.role != SEP_ROLE_NORMAL) { return false; };
// An external test's exact colocated production edge is variant wiring,
// not a general source-importable command-package alias.
return !(sepexternalproductionedge(g, importer, dep)
&& sepexternalnamematchesproduction(g, importer, dep));
};
fn sepinternalparentcount(path: *u8, parents: *u64) bool = {
let total: u64 = cstrlen(path);
let p: u64 = 0u64;
let components: u64 = 0u64;
let final: u64 = 0u64;
let found: bool = false;
for (p < total) {
for (p < total && path[p] == '.': u8) { p += 1u64; };
if (p == total) { break; };
let end: u64 = p;
for (end < total && path[end] != '.': u8) { end += 1u64; };
if (end - p == "internal".len: u64
&& bytecmp(path + p, end - p, "internal".ptr,
"internal".len: u64) == 0) {
final = components;
found = true;
};
components += 1u64;
p = end;
};
if (!found) { return false; };
*parents = components - final;
return true;
};
fn seprawimporterdir(p: *seppkg) *u8 = {
let total: u64 = cstrlen(p.canon);
let slash: u64 = total;
let i: u64 = 0u64;
for (i < total) {
if (p.canon[i] == '/': u8) { slash = i; };
i += 1u64;
};
if (slash == total) { return nil; };
if (slash == 0u64) { slash = 1u64; };
return sepdupcstr(p.canon, slash);
};
fn sepcanonicalinternalowner(path: *u8, parents: u64) *u8 = {
let boundary: u64 = cstrlen(path);
for (boundary > 1u64 && path[boundary - 1u64] == '/': u8) {
boundary -= 1u64;
};
let pi: u64 = 0u64;
for (pi < parents) {
for (boundary > 0u64 && path[boundary - 1u64] != '/': u8) {
boundary -= 1u64;
};
for (boundary > 1u64 && path[boundary - 1u64] == '/': u8) {
boundary -= 1u64;
};
pi += 1u64;
};
let lexical: *u8 = nil;
if (boundary == 0u64) {
lexical = sepdupcstr(".".ptr, 1u64);
} else {
lexical = sepdupcstr(path, boundary);
};
if (lexical == nil) { return nil; };
return canonicaldir(pathstr(lexical));
};
fn sepimporterwithinowner(from: *seppkg, targetentry: *u8,
parents: u64) i32 = {
let importer: *u8 = from.canon;
if (from.isdir == 0) {
importer = seprawimporterdir(from);
if (importer == nil) {
if (!sepfatalallocation) {
cerr("ww: cannot canonicalize package ");
cerr(pathstr(from.entry)); cerr("\n");
};
return -1;
};
};
let owner: *u8 = sepcanonicalinternalowner(targetentry, parents);
if (owner == nil) {
if (!sepfatalallocation) {
cerr("ww: cannot canonicalize package ");
cerr(pathstr(targetentry)); cerr("\n");
};
return -1;
};
let boundary: u64 = cstrlen(owner);
let n: u64 = cstrlen(importer);
if (n == boundary
&& bytecmp(importer, boundary, owner, boundary) == 0) {
return 1;
};
if (boundary == 1u64 && owner[0u64] == '/': u8
&& importer[0u64] == '/': u8) { return 1; };
if (n > boundary
&& bytecmp(importer, boundary, owner, boundary) == 0
&& importer[boundary] == '/': u8) { return 1; };
return 0;
};
fn sepinternalimportallowed(from: *seppkg, targetpath: *u8,
targetentry: *u8) i32 = {
let parents: u64 = 0u64;
if (!sepinternalparentcount(targetpath, &parents)) { return 1; };
return sepimporterwithinowner(from, targetentry, parents);
};
// Find the final exact non-terminal dotted component named vendor. The
// effective suffix remains a view into path; parents removes vendor plus that
// suffix from the current edge's lexical target route to obtain its owner.
fn sepvendorsuffix(path: *u8, suffix: *str, parents: *u64) bool = {
let total: u64 = cstrlen(path);
let p: u64 = 0u64;
let components: u64 = 0u64;
let finalcomponent: u64 = 0u64;
let found: bool = false;
for (p < total) {
let end: u64 = p;
for (end < total && path[end] != '.': u8) { end += 1u64; };
if (end < total && end + 1u64 < total
&& end - p == "vendor".len: u64
&& bytecmp(path + p, end - p, "vendor".ptr,
"vendor".len: u64) == 0) {
suffix.ptr = path + end + 1u64;
suffix.len = (total - end - 1u64): i32;
finalcomponent = components;
found = true;
};
components += 1u64;
p = end + 1u64;
};
if (!found) { return false; };
*parents = components - finalcomponent;
return true;
};
// Filesystem twin used only for a directly selected literal external-test
// root, whose ordinary import identity is finalized after source scanning.
fn sepvendorroutesuffix(path: *u8) *u8 = {
let total: u64 = cstrlen(path);
let p: u64 = 0u64;
let final: *u8 = nil;
for (p < total) {
for (p < total && path[p] == '/': u8) { p += 1u64; };
if (p >= total) { break; };
let end: u64 = p;
for (end < total && path[end] != '/': u8) { end += 1u64; };
if (end < total && end + 1u64 < total
&& end - p == "vendor".len: u64
&& bytecmp(path + p, end - p, "vendor".ptr,
"vendor".len: u64) == 0) {
final = path + end + 1u64;
};
p = end + 1u64;
};
return final;
};
fn sepvendorimportallowed(from: *seppkg, targetpath: *u8,
targetentry: *u8) i32 = {
let suffix: str = "";
let parents: u64 = 0u64;
if (!sepvendorsuffix(targetpath, &suffix, &parents)) { return 1; };
return sepimporterwithinowner(from, targetentry, parents);
};
fn seppathisvendored(path: *u8) bool = {
if (path == nil) { return false; };
let suffix: str = "";
let parents: u64 = 0u64;
return sepvendorsuffix(path, &suffix, &parents);
};
fn sepcommandcompilermarker(g: *sepgraph, pi: i32) bool = {
return sepcommanddeclaredname(&g.pkg[pi]) && !g.pkg[pi].linkentry;
};
// Bind a provisional directory action to its canonical ordinary import
// identity. The compiler path is derived from that base and the semantic
// variant; root/product/artifact state never participates.
fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = {
if (base == nil || base[0u64] == 0u8) { return -1; };
if (!reservedimportpath(base) && !sepimportbasevalid(base)) {
cerr("ww: invalid package path "); cerr(pathstr(base)); cerr("\n");
return -1;
};
let p: *seppkg = &g.pkg[pi];
if (p.importbase != nil) {
if (cstreq(p.importbase, base)) { return 0; };
if (p.role != SEP_ROLE_TEST_SUPPORT
&& !sepregisterpackagefold(g, base)) { return -1; };
g.identityfailed = true;
sepdiagdiridentities(p.entry, p.importbase, base);
return -1;
};
if (p.role != SEP_ROLE_TEST_SUPPORT
&& !sepregisterpackagefold(g, base)) { return -1; };
let candidate: *u8 = sepvariantpath(p.variant, base);
if (candidate == nil) { return -1; };
let i: i32 = 0;
for (i < g.n) {
if (i != pi && g.pkg[i].isdir != 0 && !g.pkg[i].generatedmain) {
let samelocation: bool = cstreq(g.pkg[i].canon, p.canon);
let supportalias: bool = p.role == SEP_ROLE_TEST_SUPPORT
|| g.pkg[i].role == SEP_ROLE_TEST_SUPPORT;
if (!supportalias && !samelocation
&& g.pkg[i].importbase != nil
&& cstreq(g.pkg[i].importbase, base)) {
g.identityfailed = true;
sepdiagpathlocations(base, g.pkg[i].entry, p.entry);
return -1;
};
if (samelocation && !supportalias
&& g.pkg[i].importbase != nil
&& !cstreq(g.pkg[i].importbase, base)) {
if (!seppathisvendored(g.pkg[i].importbase)
&& !seppathisvendored(base)) {
g.identityfailed = true;
sepdiagdiridentities(p.entry, g.pkg[i].importbase, base);
return -1;
};
};
if (g.pkg[i].path != nil && g.pkg[i].path[0u64] != 0u8
&& cstreq(g.pkg[i].path, candidate)) {
if (!samelocation) {
g.identityfailed = true;
sepdiagpathlocations(candidate, g.pkg[i].entry, p.entry);
return -1;
};
if (supportalias || g.pkg[i].variant == p.variant) {
g.identityfailed = true;
cerr("ww: package action identity collision for ");
cerr(pathstr(candidate)); cerr(" in ");
cerr(pathstr(p.entry)); cerr("\n");
return -1;
};
};
};
i += 1;
};
p.importbase = sepdupcstr(base, cstrlen(base));
if (p.importbase == nil) { return -1; };
p.path = candidate;
return 0;
};
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
isdir: i32, variant: i32, testpackage: *u8, role: i32,
artifact: *u8, root: bool) i32 = {
let canon: *u8 = nil;
if (isdir != 0) {
canon = canonicaldir(pathstr(entry));
} else {
canon = sepcanonicalfile(entry);
};
if (canon == nil) {
if (sepfatalallocation) { return -1; };
cerr("ww: cannot canonicalize package ");
cerr(pathstr(entry)); cerr("\n");
return -1;
};
if (isdir != 0 && path[0u64] != 0u8
&& role != SEP_ROLE_TEST_SUPPORT
&& (reservedimportpath(path) || sepimportbasevalid(path))
&& !sepregisterpackagefold(g, path)) {
os.free(canon: *void, cstrlen(canon) + 1u64);
return -1;
};
let incoming: *u8 = nil;
if (isdir != 0 && path[0u64] != 0u8) {
incoming = sepvariantpath(variant, path);
if (incoming == nil) { return -1; };
};
let i: i32 = 0;
for (i < g.n) {
let samelocation: bool = false;
if (isdir != 0 && g.pkg[i].isdir != 0 && canon != nil
&& g.pkg[i].canon != nil) {
samelocation = cstreq(g.pkg[i].canon, canon);
} else {
samelocation = os.samefile(pathstr(g.pkg[i].entry),
pathstr(entry));
};
if (isdir != 0 && g.pkg[i].isdir != 0
&& !g.pkg[i].generatedmain) {
let supportalias: bool = role == SEP_ROLE_TEST_SUPPORT
|| g.pkg[i].role == SEP_ROLE_TEST_SUPPORT;
if (!supportalias && !samelocation && path[0u64] != 0u8
&& g.pkg[i].importbase != nil
&& cstreq(path, g.pkg[i].importbase)) {
g.identityfailed = true;
sepdiagpathlocations(path, g.pkg[i].entry, entry);
return -1;
};
if (incoming != nil && g.pkg[i].path != nil
&& g.pkg[i].path[0u64] != 0u8
&& cstreq(incoming, g.pkg[i].path) && !samelocation) {
g.identityfailed = true;
sepdiagpathlocations(incoming, g.pkg[i].entry, entry);
return -1;
};
if (samelocation && g.pkg[i].variant == variant
&& g.pkg[i].role == role) {
if (path[0u64] != 0u8 && seppathisvendored(path)
&& g.pkg[i].root && g.pkg[i].importbase == nil) {
i += 1; continue;
};
if (root && path[0u64] == 0u8
&& g.pkg[i].importbase != nil
&& seppathisvendored(g.pkg[i].importbase)) {
i += 1; continue;
};
if (path[0u64] != 0u8 && g.pkg[i].importbase != nil
&& !cstreq(path, g.pkg[i].importbase)
&& (seppathisvendored(path)
|| seppathisvendored(g.pkg[i].importbase))) {
i += 1; continue;
};
let sametest: bool = testpackage == nil
&& g.pkg[i].testpackage == nil;
if (testpackage != nil && g.pkg[i].testpackage != nil) {
sametest = cstreq(testpackage, g.pkg[i].testpackage);
};
if (variant != SEP_VARIANT_PRODUCTION && !sametest) {
cerr("ww: incompatible package-test roots ");
cerr(pathstr(entry)); cerr("\n");
return -1;
};
if (path[0u64] != 0u8
&& sepbindimportbase(g, i, path) < 0) { return -1; };
g.pkg[i].root = g.pkg[i].root || root;
return i;
};
if (samelocation) {
if (supportalias) { i += 1; continue; };
if (g.pkg[i].importbase != nil && path[0u64] != 0u8
&& !cstreq(g.pkg[i].importbase, path)) {
if (seppathisvendored(g.pkg[i].importbase)
|| seppathisvendored(path)) {
i += 1; continue;
};
g.identityfailed = true;
sepdiagdiridentities(entry, g.pkg[i].importbase, path);
return -1;
};
if (sepdirectoryvariant(g.pkg[i].variant)
&& sepdirectoryvariant(variant)) {
i += 1; continue;
};
cerr("ww: package directory "); cerr(pathstr(entry));
cerr(" has incompatible variants\n");
return -1;
};
i += 1; continue;
};
if (samelocation && cstreq(g.pkg[i].path, path)
&& g.pkg[i].variant == variant && g.pkg[i].role == role) {
g.pkg[i].root = g.pkg[i].root || root;
return i;
};
i += 1;
};
if (g.n == SEP_COUNT_MAX) { sepfailsize(); return -1; };
if (!sepreservepackages(g, g.n + 1)) {
return -1;
};
let plen: u64 = cstrlen(path);
let elen: u64 = cstrlen(entry);
g.pkg[g.n].path = sepdupcstr(path, plen);
g.pkg[g.n].importbase = nil;
g.pkg[g.n].entry = sepdupcstr(entry, elen);
if (g.pkg[g.n].path == nil || g.pkg[g.n].entry == nil) { return -1; };
g.pkg[g.n].canon = canon;
g.pkg[g.n].artifact = nil;
g.pkg[g.n].storage = nil;
g.pkg[g.n].initsymbol = nil;
g.pkg[g.n].storagehashed = false;
g.pkg[g.n].name = nil;
g.pkg[g.n].testpackage = nil;
g.pkg[g.n].fortest = nil;
if (testpackage != nil) {
g.pkg[g.n].testpackage = sepdupcstr(testpackage,
cstrlen(testpackage));
if (g.pkg[g.n].testpackage == nil) { return -1; };
};
g.pkg[g.n].sources = nil;
g.pkg[g.n].nsources = 0;
g.pkg[g.n].isdir = isdir;
g.pkg[g.n].variant = variant;
g.pkg[g.n].role = role;
g.pkg[g.n].root = root;
g.pkg[g.n].linkentry = false;
g.pkg[g.n].generatedmain = false;
let emptytargets: []i32;
g.pkg[g.n].generatedtargets = emptytargets;
g.pkg[g.n].ngeneratedtargets = 0;
g.pkg[g.n].failed = false;
g.pkg[g.n].action = false;
g.pkg[g.n].testsupport = false;
g.pkg[g.n].loaded = false;
g.pkg[g.n].exportchanged = false;
g.pkg[g.n].sourcestaged = false;
g.pkg[g.n].initstaged = false;
g.pkg[g.n].archivestaged = false;
g.pkg[g.n].emitcontext = -1;
let emptystate: []u8;
g.pkg[g.n].contextstate = emptystate;
let emptybindings: []sepbind;
g.pkg[g.n].bindings = emptybindings;
let emptydeps: []i32;
g.pkg[g.n].deps = emptydeps;
g.pkg[g.n].ndeps = 0;
g.pkg[g.n].color = 0;
if (isdir != 0) {
let inherited: *u8 = path;
if (inherited[0u64] == 0u8) {
i = 0;
for (i < g.n) {
if (g.pkg[i].isdir != 0 && !g.pkg[i].generatedmain
&& g.pkg[i].role != SEP_ROLE_TEST_SUPPORT
&& role != SEP_ROLE_TEST_SUPPORT
&& cstreq(g.pkg[i].canon, canon)
&& g.pkg[i].importbase != nil) {
if (root && seppathisvendored(g.pkg[i].importbase)) {
i += 1; continue;
};
inherited = g.pkg[i].importbase;
break;
};
i += 1;
};
};
if (inherited[0u64] != 0u8
&& sepbindimportbase(g, g.n, inherited) < 0) { return -1; };
} else {
g.pkg[g.n].artifact = artifact;
};
let r: i32 = g.n;
g.n += 1;
return r;
};
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
return sepfindoraddvariant(g, path, entry, isdir,
SEP_VARIANT_PRODUCTION, nil, SEP_ROLE_NORMAL, nil, false);
};
fn sepfindoraddrole(g: *sepgraph, path: *u8, entry: *u8, isdir: i32,
role: i32, artifact: *u8) i32 = {
return sepfindoraddvariant(g, path, entry, isdir,
SEP_VARIANT_PRODUCTION, nil, role, artifact, false);
};
fn seppkgfreeowned(p: *seppkg) void = {
let j: i32 = 0;
for (j < p.nsources) {
os.free(p.sources[j]: *void, os.PATH_MAX: u64);
j += 1;
};
if (p.sources != nil) {
os.free(p.sources: *void,
(p.nsources: u64) * (size(*u8): u64));
};
if (p.name != nil) {
os.free(p.name: *void, cstrlen(p.name) + 1u64);
};
if (p.initsymbol != nil) {
os.free(p.initsymbol: *void,
cstrlen(p.initsymbol) + 1u64);
};
if (p.contextstate.ptr != nil) {
os.free(p.contextstate.ptr: *void,
(p.contextstate.cap: u64) * (size(u8): u64));
};
if (p.deps.ptr != nil) {
os.free(p.deps.ptr: *void,
(p.deps.cap: u64) * (size(i32): u64));
};
if (p.generatedtargets.ptr != nil) {
os.free(p.generatedtargets.ptr: *void,
(p.generatedtargets.cap: u64) * (size(i32): u64));
};
if (p.fortest != nil) {
os.free(p.fortest: *void, cstrlen(p.fortest) + 1u64);
};
let bi: i32 = 0;
for (bi < p.bindings.len) {
if (p.bindings[bi].name.ptr != nil) {
os.free(p.bindings[bi].name.ptr: *void,
p.bindings[bi].name.cap: u64);
};
if (p.bindings[bi].source.ptr != nil) {
os.free(p.bindings[bi].source.ptr: *void,
p.bindings[bi].source.cap: u64);
};
bi += 1;
};
if (p.bindings.ptr != nil) {
os.free(p.bindings.ptr: *void,
(p.bindings.cap: u64) * (size(sepbind): u64));
};
};
// Release package-owned action state through one boundary shared by normal
// graph teardown and an unpublished copy-on-write clone failure.
fn sepgraphfree(g: *sepgraph) void = {
if (g == nil) { return; };
let i: i32 = 0;
for (i < g.n) {
seppkgfreeowned(&g.pkg[i]);
i += 1;
};
if (g.pkg.ptr != nil) {
os.free(g.pkg.ptr: *void,
(g.pkg.cap: u64) * (size(seppkg): u64));
};
i = 0;
for (i < g.npackagefolds) {
if (g.packagefolds[i].scope != nil) {
os.free(g.packagefolds[i].scope: *void,
cstrlen(g.packagefolds[i].scope) + 1u64);
};
os.free(g.packagefolds[i].key: *void,
cstrlen(g.packagefolds[i].key) + 1u64);
os.free(g.packagefolds[i].exact: *void,
cstrlen(g.packagefolds[i].exact) + 1u64);
i += 1;
};
if (g.packagefolds.ptr != nil) {
os.free(g.packagefolds.ptr: *void,
(g.packagefolds.cap: u64) * (size(sepfoldentry): u64));
};
i = 0;
for (i < g.nfilefolds) {
os.free(g.filefolds[i].scope: *void,
cstrlen(g.filefolds[i].scope) + 1u64);
os.free(g.filefolds[i].key: *void,
cstrlen(g.filefolds[i].key) + 1u64);
os.free(g.filefolds[i].exact: *void,
cstrlen(g.filefolds[i].exact) + 1u64);
i += 1;
};
if (g.filefolds.ptr != nil) {
os.free(g.filefolds.ptr: *void,
(g.filefolds.cap: u64) * (size(sepfoldentry): u64));
};
i = 0;
for (i < g.ncontext) {
if (g.context[i].root != nil) {
os.free(g.context[i].root: *void,
cstrlen(g.context[i].root) + 1u64);
};
if (g.context[i].searchpath != nil) {
os.free(g.context[i].searchpath: *void,
cstrlen(g.context[i].searchpath) + 1u64);
};
if (g.context[i].route != nil) {
os.free(g.context[i].route: *void,
cstrlen(g.context[i].route) + 1u64);
};
if (g.context[i].sourceroot != nil) {
os.free(g.context[i].sourceroot: *void,
cstrlen(g.context[i].sourceroot) + 1u64);
};
i += 1;
};
if (g.context.ptr != nil) {
os.free(g.context.ptr: *void,
(g.context.cap: u64) * (size(sepcontext): u64));
};
os.free(g: *void, size(sepgraph): u64);
};
type seplocated = struct {
entry: *u8,
root: *u8,
};
fn septrimmedpath(path: *u8) *u8 = {
let n: u64 = cstrlen(path);
for (n > 1u64 && path[n - 1u64] == '/': u8) { n -= 1u64; };
if (n == 0u64) { return sepdupcstr(".".ptr, 1u64); };
return sepdupcstr(path, n);
};
fn seplexicalparent(path: *u8) *u8 = {
let n: u64 = cstrlen(path);
for (n > 1u64 && path[n - 1u64] == '/': u8) { n -= 1u64; };
let slash: u64 = n;
for (slash > 0u64 && path[slash - 1u64] != '/': u8) {
slash -= 1u64;
};
if (slash == 0u64) { return sepdupcstr(".".ptr, 1u64); };
let parent: u64 = slash - 1u64;
for (parent > 1u64 && path[parent - 1u64] == '/': u8) {
parent -= 1u64;
};
if (parent == 0u64) { parent = 1u64; };
return sepdupcstr(path, parent);
};
fn sepjoinroute(root: *u8, rel: *u8) *u8 = {
let n: u64 = cstrlen(root);
if (n > 0u64 && root[n - 1u64] == '/': u8) {
return sepappendlit(root, pathstr(rel));
};
return sepjoinpath(root, rel);
};
fn seplexicalrelative(root: *u8, route: *u8, rel: *str) bool = {
let rn: u64 = cstrlen(root);
let pn: u64 = cstrlen(route);
for (rn > 1u64 && root[rn - 1u64] == '/': u8) { rn -= 1u64; };
for (pn > 1u64 && route[pn - 1u64] == '/': u8) { pn -= 1u64; };
if (rn == 1u64 && root[0u64] == '/': u8) {
if (pn > 1u64 && route[0u64] == '/': u8) {
rel.ptr = route + 1u64;
rel.len = (pn - 1u64): i32;
return true;
};
return false;
};
if (rn == 1u64 && root[0u64] == '.': u8 && pn > 2u64
&& route[0u64] == '.': u8 && route[1u64] == '/': u8) {
rel.ptr = route + 2u64;
rel.len = (pn - 2u64): i32;
return true;
};
if (pn > rn && bytecmp(route, rn, root, rn) == 0
&& route[rn] == '/': u8) {
rel.ptr = route + rn + 1u64;
rel.len = (pn - rn - 1u64): i32;
return true;
};
return false;
};
fn sepsamecanonicaldir(a: *u8, b: *u8) i32 = {
let ac: *u8 = canonicaldir(pathstr(a));
let bc: *u8 = canonicaldir(pathstr(b));
if (ac == nil || bc == nil) {
if (sepfatalallocation) { return -1; };
return 0;
};
if (cstreq(ac, bc)) { return 1; };
return 0;
};
fn sepcheckedimportpathform(name: *u8) *u8 = {
let n: u64 = cstrlen(name);
let out: []u8;
if (!sepmakebytes(n + 1u64, &out)) { return nil; };
let i: u64 = 0u64;
for (i < n) {
if (name[i] == '.': u8) { out[i] = '/': u8; }
else { out[i] = name[i]; };
i += 1u64;
};
out[n] = 0u8;
return out.ptr;
};
fn seplocateimportroot(dirs: *u8, pathform: *u8) seplocated = {
let result: seplocated;
result.entry = nil;
result.root = nil;
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
let q: u64 = p;
for (q < total && dirs[q] != ':': u8) { q += 1u64; };
let n: u64 = q - p;
if (n > 0u64) {
let root: *u8 = sepdupcstr(dirs + p, n);
if (root == nil) { return result; };
let entry: *u8 = sepjoinroute(root, pathform);
if (entry == nil) { return result; };
let fi: os.filestat;
match (os.stat(&fi, pathstr(entry))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ == os.mode.DIR: u32) {
result.entry = entry;
result.root = root;
return result;
};
};
case let e: os.oserror => void;
};
};
p = q + 1u64;
};
return result;
};
fn sepinitialrouteroot(entry: *u8, identity: *u8, searchpath: *u8,
routeout: **u8, rootout: **u8) i32 = {
let entrytrim: *u8 = septrimmedpath(entry);
if (entrytrim == nil) { return -1; };
if (identity != nil && identity[0u64] != 0u8) {
let root: *u8 = sepdupcstr(entrytrim, cstrlen(entrytrim));
if (root == nil) { return -1; };
let components: u64 = 1u64;
let ii: u64 = 0u64;
for (ii < cstrlen(identity)) {
if (identity[ii] == '.': u8) { components += 1u64; };
ii += 1u64;
};
let ci: u64 = 0u64;
for (ci < components) {
root = seplexicalparent(root);
if (root == nil) { return -1; };
ci += 1u64;
};
let pathform: *u8 = sepcheckedimportpathform(identity);
if (pathform == nil) { return -1; };
let route: *u8 = sepjoinroute(root, pathform);
if (route == nil) { return -1; };
let same: i32 = sepsamecanonicaldir(route, entrytrim);
if (same <= 0) {
if (same == 0) {
cerr("ww: package "); cerr(pathstr(identity));
cerr(" does not match resolved directory ");
cerr(pathstr(entry)); cerr("\n");
};
return -1;
};
*routeout = route;
*rootout = root;
return 0;
};
let entrycanon: *u8 = canonicaldir(pathstr(entrytrim));
if (entrycanon == nil && sepfatalallocation) { return -1; };
let total: u64 = cstrlen(searchpath);
let p: u64 = 0u64;
for (p < total) {
let q: u64 = p;
for (q < total && searchpath[q] != ':': u8) { q += 1u64; };
let n: u64 = q - p;
if (n > 0u64) {
let candidate: *u8 = sepdupcstr(searchpath + p, n);
if (candidate == nil) { return -1; };
let rel: str = "";
let relative: bool = seplexicalrelative(candidate,
entrytrim, &rel);
if (!relative && entrycanon != nil) {
let canonroot: *u8 = canonicaldir(pathstr(candidate));
if (canonroot == nil && sepfatalallocation) { return -1; };
if (canonroot != nil) {
let rn: u64 = cstrlen(canonroot);
let en: u64 = cstrlen(entrycanon);
if (rn == 1u64 && canonroot[0u64] == '/': u8
&& en > 1u64 && entrycanon[0u64] == '/': u8) {
rel.ptr = entrycanon + 1u64;
rel.len = (en - 1u64): i32;
relative = true;
} else { if (en > rn
&& bytecmp(entrycanon, rn, canonroot, rn) == 0
&& entrycanon[rn] == '/': u8) {
rel.ptr = entrycanon + rn + 1u64;
rel.len = (en - rn - 1u64): i32;
relative = true;
}; };
};
};
if (relative && rel.len > 0) {
let ident: []u8;
if (!sepmakebytes((rel.len + 1): u64, &ident)) { return -1; };
let valid: i32 = sepimportpathfromrelative(rel.ptr,
ident.ptr, (rel.len + 1): u64);
if (valid > 0 && !reservedimportpath(ident.ptr)) {
let selected: seplocated = seplocateimportroot(
searchpath, rel.ptr);
if (selected.entry == nil && sepfatalallocation) {
return -1;
};
if (selected.entry != nil) {
let same: i32 = sepsamecanonicaldir(
selected.entry, entrytrim);
if (same < 0) { return -1; };
if (same > 0) {
let route: *u8 = sepjoinroute(candidate, rel.ptr);
if (route == nil) { return -1; };
*routeout = route;
*rootout = candidate;
return 0;
};
};
};
};
};
p = q + 1u64;
};
*routeout = entrytrim;
*rootout = sepdupcstr(entrytrim, cstrlen(entrytrim));
if (*rootout == nil) { return -1; };
return 0;
};
fn sepcontextadd(g: *sepgraph, root: *u8, searchpath: *u8,
route: *u8, sourceroot: *u8) i32 = {
let i: i32 = 0;
for (i < g.ncontext) {
if (cstreq(g.context[i].root, root)
&& cstreq(g.context[i].searchpath, searchpath)
&& cstreq(g.context[i].route, route)
&& cstreq(g.context[i].sourceroot, sourceroot)) { return i; };
i += 1;
};
if (g.ncontext == SEP_COUNT_MAX) { sepfailsize(); return -1; };
if (!sepreservecontexts(g, g.ncontext + 1)) { return -1; };
g.context[g.ncontext].root = sepdupcstr(root, cstrlen(root));
g.context[g.ncontext].searchpath = sepdupcstr(searchpath,
cstrlen(searchpath));
g.context[g.ncontext].route = septrimmedpath(route);
g.context[g.ncontext].sourceroot = septrimmedpath(sourceroot);
if (g.context[g.ncontext].root == nil
|| g.context[g.ncontext].searchpath == nil
|| g.context[g.ncontext].route == nil
|| g.context[g.ncontext].sourceroot == nil) { return -1; };
let result: i32 = g.ncontext;
g.ncontext += 1;
return result;
};
fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8,
toolsrcdir: *u8, identity: *u8) i32 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, cstrlen(root))
|| !sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, cstrlen(toolsrcdir))
|| !sepaddbytes(&need, 1u64)) { return -1; };
if (incs != nil && incs[0u64] != 0u8) {
if (!sepaddbytes(&need, cstrlen(incs))
|| !sepaddbytes(&need, 1u64)) { return -1; };
};
let search: []u8;
if (!sepmakebytes(need, &search)) { return -1; };
let off: u64 = cstrinto(search.ptr, 0u64, root);
off = byteinto(search.ptr, off, 58u8);
if (incs != nil && incs[0u64] != 0u8) {
off = cstrinto(search.ptr, off, incs);
off = byteinto(search.ptr, off, 58u8);
};
off = cstrinto(search.ptr, off, toolsrcdir);
cstrseal(search.ptr, off);
let route: *u8 = nil;
let sourceroot: *u8 = nil;
if (sepinitialrouteroot(root, identity, search.ptr,
&route, &sourceroot) < 0) { return -1; };
return sepcontextadd(g, root, search.ptr, route, sourceroot);
};
fn sepchildcontextfor(g: *sepgraph, parent: i32, route: *u8,
sourceroot: *u8) i32 = {
if (parent < 0 || parent >= g.ncontext) { return -1; };
return sepcontextadd(g, g.context[parent].root,
g.context[parent].searchpath, route, sourceroot);
};
def SEP_NAME_MAX: u64 = 255u64;
fn sepstoragedigest(p: *seppkg) *u8 = {
let state: sha256.state = sha256.sha256();
let h: *hash.hash = (&state): *hash.hash;
hash.write(h, strings.toutf8("ww-package-storage-v3:"));
let tag: [4]u8;
tag[0] = ('0': i32 + p.variant): u8;
tag[1] = ':': u8;
tag[2] = ('0': i32 + p.role): u8;
tag[3] = ':': u8;
hash.write(h, tag[0:4]);
hash.write(h, strings.toutf8(pathstr(p.path)));
let zero: [1]u8;
zero[0] = 0u8;
hash.write(h, zero[0:1]);
hash.write(h, strings.toutf8(pathstr(p.canon)));
hash.write(h, zero[0:1]);
if (p.fortest != nil) {
hash.write(h, strings.toutf8(pathstr(p.fortest)));
};
let digest: [32]u8;
hash.sum(h, digest[0:32]);
let need: u64 = "__wwpkg.v".len: u64 + 1u64 + ".r".len: u64
+ 1u64 + ".h".len: u64 + 64u64 + 1u64;
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = strinto(out.ptr, 0u64, "__wwpkg.v");
off = byteinto(out.ptr, off, tag[0]);
off = strinto(out.ptr, off, ".r");
off = byteinto(out.ptr, off, tag[2]);
off = strinto(out.ptr, off, ".h");
let hex: str = "0123456789abcdef";
let i: i32 = 0;
for (i < 32) {
let high: i32 = (digest[i] / 16u8): i32;
let low: i32 = (digest[i] % 16u8): i32;
off = byteinto(out.ptr, off, hex[high]);
off = byteinto(out.ptr, off, hex[low]);
i += 1;
};
cstrseal(out.ptr, off);
return out.ptr;
};
fn seplegacyartifact(p: *seppkg) *u8 = {
if (p.artifact != nil && p.artifact[0u64] != 0u8) { return p.artifact; };
if (p.path != nil && p.path[0u64] != 0u8) { return p.path; };
return "__root\0".ptr;
};
fn sepvalidatestoragepath(p: *seppkg, scratch: *u8) i32 = {
let tail: str = ".init.unit.ww.wwtxn.9223372036854775807.old";
let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(p.storage)
+ tail.len: u64 + 1u64;
if (cstrlen(p.storage) + tail.len: u64 > SEP_NAME_MAX
|| need > os.PATH_MAX: u64) {
cerr("ww: package artifact path is too long\n");
return -1;
};
return 0;
};
fn sepassignstorage(p: *seppkg, scratch: *u8) i32 = {
let tail: str = ".init.unit.ww.wwtxn.9223372036854775807.old";
let base: *u8 = seplegacyartifact(p);
let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(base)
+ tail.len: u64 + 1u64;
if (cstrlen(base) + tail.len: u64 <= SEP_NAME_MAX
&& need <= os.PATH_MAX: u64) {
p.storage = sepdupcstr(base, cstrlen(base));
p.storagehashed = false;
} else {
p.storage = sepstoragedigest(p);
p.storagehashed = true;
};
if (p.storage == nil) { return -1; };
return sepvalidatestoragepath(p, scratch);
};
fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, cstrlen(scratch))
|| !sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, cstrlen(g.pkg[pi].storage))
|| !sepaddbytes(&need, suffix.len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let buf: []u8;
if (!sepmakebytes(need, &buf)) { return nil; };
let off: u64 = cstrinto(buf.ptr, 0u64, scratch);
off = byteinto(buf.ptr, off, 47u8); // '/'
off = cstrinto(buf.ptr, off, g.pkg[pi].storage);
off = strinto(buf.ptr, off, suffix);
cstrseal(buf.ptr, off);
return buf.ptr;
};
fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
let i: i32 = 0;
for (i < g.n) {
if (g.pkg[i].failed || !g.pkg[i].loaded || !g.pkg[i].action) {
i += 1; continue;
};
if (g.pkg[i].storage == nil
&& sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; };
i += 1;
};
let changed: bool = true;
for (changed) {
changed = false;
i = 0;
for (i < g.n && !changed) {
if (g.pkg[i].failed || !g.pkg[i].loaded || !g.pkg[i].action) {
i += 1; continue;
};
let j: i32 = i + 1;
for (j < g.n) {
if (g.pkg[j].failed || !g.pkg[j].loaded || !g.pkg[j].action
|| !cstreq(g.pkg[i].storage, g.pkg[j].storage)) {
j += 1; continue;
};
if (!g.pkg[i].storagehashed || !g.pkg[j].storagehashed) {
if (!g.pkg[i].storagehashed) {
g.pkg[i].storage = sepstoragedigest(&g.pkg[i]);
g.pkg[i].storagehashed = true;
if (sepvalidatestoragepath(&g.pkg[i], scratch) < 0) {
return -1;
};
};
if (!g.pkg[j].storagehashed) {
g.pkg[j].storage = sepstoragedigest(&g.pkg[j]);
g.pkg[j].storagehashed = true;
if (sepvalidatestoragepath(&g.pkg[j], scratch) < 0) {
return -1;
};
};
changed = true;
j = g.n;
} else {
cerr("ww: package storage collision for ");
cerr(pathstr(g.pkg[i].path)); cerr(" in ");
cerr(pathstr(g.pkg[i].canon)); cerr(" and ");
cerr(pathstr(g.pkg[j].path)); cerr(" in ");
cerr(pathstr(g.pkg[j].canon)); cerr(" at ");
cerr(pathstr(g.pkg[i].storage)); cerr("\n");
return -1;
};
};
i += 1;
};
};
return 0;
};
fn sepexternalname(pkg: *seppkg, path: *u8, n: u64) bool = {
if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) {
return false;
};
let tn: u64 = cstrlen(pkg.testpackage);
if (tn != n + 5u64) { return false; };
if (bytecmp(pkg.testpackage, n, path, n) != 0) {
return false;
};
return pkg.testpackage[n] == '_'
&& pkg.testpackage[n + 1u64] == 't'
&& pkg.testpackage[n + 2u64] == 'e'
&& pkg.testpackage[n + 3u64] == 's'
&& pkg.testpackage[n + 4u64] == 't';
};
fn sepexternalproductionedge(g: *sepgraph, importer: i32, dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
return from.variant == SEP_VARIANT_EXTERNAL
&& (to.variant == SEP_VARIANT_PRODUCTION
|| to.variant == SEP_VARIANT_SAME_TEST)
&& to.role == SEP_ROLE_NORMAL
&& cstreq(from.canon, to.canon);
};
fn sepexternalnamematchesproduction(g: *sepgraph, importer: i32,
dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
if (from.name == nil || to.name == nil) { return false; };
return sepexternalname(from, to.name, cstrlen(to.name));
};
fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str,
dep: i32, source: str, line: i32, col: i32) bool = {
if (bindings.len == SEP_COUNT_MAX) { sepfailsize(); return false; };
if (!sepreservebinds(bindings, bindings.len + 1)) {
return false;
};
let copiedallocation: (str | nomem) = sepdupstr(name);
let copied: str;
match (copiedallocation) {
case let value: str => copied = value;
case nomem => { sepfailnomem(); return false; };
};
let sourceallocation: (str | nomem) = sepdupstr(source);
let sourcecopy: str;
match (sourceallocation) {
case let value: str => sourcecopy = value;
case nomem => {
if (copied.ptr != nil) {
os.free(copied.ptr: *void, copied.cap: u64);
};
sepfailnomem();
return false;
};
};
append(*bindings, sepbind {
kind = kind,
name = copied,
source = sourcecopy,
line = line,
col = col,
dep = dep,
});
return true;
};
fn sepbindcmp(a: sepbind, b: sepbind) i32 = {
let r: i32 = strings.compare(a.name, b.name): i32;
if (r != 0) { return r; };
if (a.kind < b.kind) { return -1; };
if (a.kind > b.kind) { return 1; };
if (a.dep < b.dep) { return -1; };
if (a.dep > b.dep) { return 1; };
r = strings.compare(a.source, b.source): i32;
if (r != 0) { return r; };
if (a.line < b.line) { return -1; };
if (a.line > b.line) { return 1; };
if (a.col < b.col) { return -1; };
if (a.col > b.col) { return 1; };
return 0;
};
fn sepbindsort(bindings: *[]sepbind) void = {
let i: i32 = 1;
for (i < bindings.len) {
let value: sepbind = (*bindings)[i];
let j: i32 = i;
for (j > 0 && sepbindcmp((*bindings)[j - 1], value) > 0) {
(*bindings)[j] = (*bindings)[j - 1];
j -= 1;
};
(*bindings)[j] = value;
i += 1;
};
};
fn sepbindsemanticsame(a: sepbind, b: sepbind) bool = {
return a.kind == b.kind && a.dep == b.dep
&& syntax.streq(a.name, b.name);
};
fn sepbindsame(a: []sepbind, b: []sepbind) bool = {
let ai: i32 = 0;
let bi: i32 = 0;
for (ai < len(a) && bi < len(b)) {
if (!sepbindsemanticsame(a[ai], b[bi])) { return false; };
let av: sepbind = a[ai];
let bv: sepbind = b[bi];
ai += 1;
for (ai < len(a) && sepbindsemanticsame(av, a[ai])) { ai += 1; };
bi += 1;
for (bi < len(b) && sepbindsemanticsame(bv, b[bi])) { bi += 1; };
};
return ai == len(a) && bi == len(b);
};
fn sepvalidatebindings(g: *sepgraph, bindings: []sepbind) bool = {
let name: str;
let dep: i32 = -1;
let i: i32 = 0;
for (i < bindings.len) {
let b: sepbind = bindings[i];
if (b.kind == 'D': u8) {
if (name.len > 0 && syntax.streq(name, b.name) && dep != b.dep) {
cerrpos(b.source, b.line, b.col);
cerr(": error: package path "); cerr(b.name);
cerr(" resolves to both "); cerr(pathstr(g.pkg[dep].path));
cerr(" and "); cerr(pathstr(g.pkg[b.dep].path)); cerr("\n");
return false;
};
name = b.name;
dep = b.dep;
};
i += 1;
};
return true;
};
fn sepbindneedsmap(g: *sepgraph, b: sepbind) bool = {
return b.kind == 'D': u8 && b.dep >= 0 && b.dep < g.n
&& !syntax.streq(b.name, pathstr(g.pkg[b.dep].path));
};
fn sepbindfirstmap(g: *sepgraph, bindings: []sepbind, i: i32) bool = {
if (!sepbindneedsmap(g, bindings[i])) { return false; };
let j: i32 = i - 1;
for (j >= 0 && syntax.streq(bindings[j].name, bindings[i].name)) {
if (sepbindneedsmap(g, bindings[j])) { return false; };
j -= 1;
};
return true;
};
fn sepchildrenadd(children: *[]sepchild, pkg: i32, context: i32) bool = {
let i: i32 = 0;
for (i < children.len) {
if ((*children)[i].pkg == pkg
&& (*children)[i].context == context) { return true; };
i += 1;
};
if (children.len == SEP_COUNT_MAX) { sepfailsize(); return false; };
if (!sepreservechildren(children, children.len + 1)) { return false; };
append(*children, sepchild { pkg = pkg, context = context });
return true;
};
type sepresolved = struct {
identity: *u8,
entry: *u8,
sourceroot: *u8,
vendored: bool,
};
// Return 1 only after observing a non-directory source suffix, 0 for a
// non-candidate (including lookup/read failure), and -1 for loader allocation
// failure that must abort graph discovery before persistent state or tools.
fn sepvendorsourcecandidate(candidate: *u8) i32 = {
let fi: os.filestat;
match (os.stat(&fi, pathstr(candidate))) {
case let e: os.oserror => return 0;
case void => void;
};
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ != os.mode.DIR: u32) { return 0; };
let fd: i32 = os.open(pathstr(candidate), os.flag.RDONLY, 0i32);
if (fd < 0) { return 0; };
let buf: []u8;
if (!sepmakebytes(8192u64, &buf)) { os.close(fd); return -1; };
let r: i64 = os.getdents64(fd, buf.ptr, 8192u64);
for (r > 0i64) {
let off: u64 = 0u64;
let ru: u64 = r: u64;
for (off < ru) {
let reclen: u64 = (buf[off + 16u64]: u64)
+ (buf[off + 17u64]: u64) * 256u64;
let name: *u8 = buf.ptr + off + 19u64;
let n: u64 = cstrlen(name);
if (n >= 3u64 && name[n - 3u64] == '.': u8
&& name[n - 2u64] == 'w': u8
&& name[n - 1u64] == 'w': u8) {
let entry: *u8 = sepjoinroute(candidate, name);
if (entry == nil) { os.close(fd); return -1; };
let ent: os.filestat;
let directory: bool = false;
match (os.lstat(&ent, pathstr(entry))) {
case void => directory = ((ent.mode: u32) & 61440u32)
== os.mode.DIR: u32;
case let e: os.oserror => void;
};
if (!directory) { os.close(fd); return 1; };
};
off += reclen;
};
r = os.getdents64(fd, buf.ptr, 8192u64);
};
os.close(fd);
return 0;
};
fn sepresolvesourceimport(g: *sepgraph, context: i32, name: *u8,
pathform: *u8, out: *sepresolved) i32 = {
out.identity = nil;
out.entry = nil;
out.sourceroot = nil;
out.vendored = false;
if (context < 0 || context >= g.ncontext) { return -1; };
let route: *u8 = g.context[context].route;
let sourceroot: *u8 = g.context[context].sourceroot;
let ignored: str = "";
if (!cstreq(route, sourceroot)
&& !seplexicalrelative(sourceroot, route, &ignored)) {
cerr("ww: package route "); cerr(pathstr(route));
cerr(" is outside source root "); cerr(pathstr(sourceroot));
cerr("\n");
return -1;
};
let directsuffix: str = "";
let directparents: u64 = 0u64;
let directexpanded: bool = sepvendorsuffix(name, &directsuffix,
&directparents);
let ancestor: *u8 = nil;
if (!directexpanded) { ancestor = septrimmedpath(route); };
if (!directexpanded && ancestor == nil) { return -1; };
for (!directexpanded) {
let vendordir: *u8 = sepjoinroute(ancestor, "vendor\0".ptr);
if (vendordir == nil) { return -1; };
let candidate: *u8 = sepjoinroute(vendordir, pathform);
if (candidate == nil) { return -1; };
let sourcecandidate: i32 = sepvendorsourcecandidate(candidate);
if (sourcecandidate < 0) { return -1; };
if (sourcecandidate > 0) {
let rel: str = "";
if (!seplexicalrelative(sourceroot, candidate, &rel)) {
return -1;
};
let identity: []u8;
if (!sepmakebytes((rel.len + 1): u64, &identity)) { return -1; };
let valid: i32 = sepimportpathfromrelative(rel.ptr,
identity.ptr, (rel.len + 1): u64);
if (valid <= 0 || reservedimportpath(identity.ptr)) {
if (valid == 0) {
cerr("ww: invalid vendored package path ");
cerr(rel); cerr("\n");
};
return -1;
};
out.identity = identity.ptr;
out.entry = candidate;
out.sourceroot = sepdupcstr(sourceroot, cstrlen(sourceroot));
if (out.sourceroot == nil) { return -1; };
out.vendored = true;
return 1;
};
if (cstreq(ancestor, sourceroot)) { break; };
let parent: *u8 = seplexicalparent(ancestor);
if (parent == nil || cstreq(parent, ancestor)) { return -1; };
ancestor = parent;
};
let located: seplocated = seplocateimportroot(
g.context[context].searchpath, pathform);
if (located.entry == nil && sepfatalallocation) { return -1; };
if (located.entry == nil) { return 0; };
out.identity = sepdupcstr(name, cstrlen(name));
out.entry = located.entry;
out.sourceroot = located.root;
if (out.identity == nil) { return -1; };
return 1;
};
fn sepusecmp(a: *syntax.node, b: *syntax.node) i32 = {
let apath: str = a.usesource;
if (apath.len == 0) { apath = a.usepath; };
let bpath: str = b.usesource;
if (bpath.len == 0) { bpath = b.usepath; };
let r: i32 = strings.compare(apath, bpath): i32;
if (r != 0) { return r; };
r = strings.compare(a.file, b.file): i32;
if (r != 0) { return r; };
if (a.line < b.line) { return -1; };
if (a.line > b.line) { return 1; };
if (a.col < b.col) { return -1; };
if (a.col > b.col) { return 1; };
return 0;
};
// Scan one already-selected source file for its leading package clause
// (when it is an owned directory source) and top-level imports. A DIRECTORY
// import is a package boundary: add as a direct dep of pi. A FILE import is an
// intra-package split: fold its imports into pi. Mirrors cstage
// sep_scan_file (collects PATHS, not bytes).
fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, context: i32,
fv: *expctx, bindings: *[]sepbind, children: *[]sepchild,
ownedsource: i32) i32 = {
let fview: str;
fview.ptr = file;
fview.len = cstrlen(file): i32;
let fdupres: (str | nomem) = sepdupstr(fview);
let fdup: str;
match (fdupres) {
case let value: str => fdup = value;
case nomem => { sepfailnomem(); return -1; };
};
if (visitseen(fv, fdup)) { return 0; };
if (!visitadd(fv, fdup)) { return -1; };
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(file);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return -1;
};
let l: syntax.lex;
syntax.lexinit(&l, fdup, bufp, blen);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let imports: *syntax.node = syntax.parseimports(&ps);
if (l.errs > 0 || ps.errs > 0) { return -1; };
if (imports.nmod.len == 0 && ownedsource != 0) {
cerrpos(fdup, 1, 1);
cerr(": error: invalid or missing package clause\n");
return -1;
};
if (imports.nmod.len != 0
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
let declared: *u8 = imports.nmod.ptr;
let declaredn: u64 = imports.nmod.len: u64;
if (g.pkg[pi].name == nil) {
g.pkg[pi].name = sepdupcstr(declared, declaredn);
if (g.pkg[pi].name == nil) { return -1; };
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
declared, declaredn) != 0) {
cerrpos(imports.file, imports.line, imports.col);
cerr(": error: conflicting package names ");
cerr(pathstr(g.pkg[pi].name));
cerr(" and ");
os.write(2, declared, declaredn);
cerr(" in "); cerr(pathstr(g.pkg[pi].entry)); cerr("\n");
return -1;
}; };
};
if (ownedsource != 0 && g.pkg[pi].isdir != 0) {
let pm: *syntax.node = imports.body;
for (pm != nil) {
if (!syntax.streq(pm.nmod, pathstr(g.pkg[pi].name))) {
cerrpos(pm.file, pm.line, pm.col);
cerr(": error: conflicting package names ");
cerr(pathstr(g.pkg[pi].name)); cerr(" and ");
cerr(pm.nmod); cerr(" in ");
cerr(pathstr(g.pkg[pi].entry)); cerr("\n");
return -1;
};
pm = pm.next;
};
};
let nuse: i32 = 0;
let u: *syntax.node = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) {
if (nuse == SEP_COUNT_MAX) {
sepfailsize();
return -1;
};
nuse += 1;
};
u = u.next;
};
let uses: []*syntax.node = [];
if (nuse > 0) {
let allocation: ([]*syntax.node | nomem) = sepallocnodeptrs(nuse);
match (allocation) {
case let value: []*syntax.node => uses = value;
case nomem => { sepfailnomem(); return -1; };
};
uses.len = nuse;
};
let ui: i32 = 0;
u = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { uses[ui] = u; ui += 1; };
u = u.next;
};
let si: i32 = 1;
for (si < nuse) {
let sj: i32 = si;
for (sj > 0) {
if (sepusecmp(uses[sj - 1], uses[sj]) <= 0) { sj = 0; }
else {
let t: *syntax.node = uses[sj];
uses[sj] = uses[sj - 1];
uses[sj - 1] = t;
sj -= 1;
};
};
si += 1;
};
ui = 0;
for (ui < nuse) {
u = uses[ui];
let sourcepath: str = u.usesource;
if (sourcepath.len == 0) { sourcepath = u.usepath; };
let idp: *u8 = sourcepath.ptr;
let idn: u64 = sourcepath.len: u64;
if (reservedimport(sourcepath)) {
cerrpos(u.file, u.line, u.col);
cerr(": error: package path ");
cerr(sourcepath); cerr(" is reserved\n");
return -1;
};
let nm: []u8;
if (!sepmakebytes(idn + 1u64, &nm)) { return -1; };
let k: u64 = 0u64;
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
nm[idn] = 0u8;
let pathform: *u8 = sepcheckedimportpathform(nm.ptr);
if (pathform == nil) { return -1; };
let resolved: sepresolved;
resolved.identity = nil;
resolved.entry = nil;
resolved.sourceroot = nil;
resolved.vendored = false;
let located: i32 = sepresolvesourceimport(g, context, nm.ptr,
pathform, &resolved);
// A directly selected literal external root gets a colocated
// production fallback only after ordinary source resolution misses.
if (located == 0 && g.pkg[pi].variant == SEP_VARIANT_EXTERNAL) {
let routesuffix: *u8 = sepvendorroutesuffix(
g.context[context].route);
let ordinaryleaf: bool = true;
let oi: u64 = 0u64;
for (oi < idn) {
if (idp[oi] == '.': u8) { ordinaryleaf = false; };
oi += 1u64;
};
let literalself: bool = routesuffix != nil
&& cstreq(pathform, routesuffix);
if (routesuffix == nil) {
literalself = ordinaryleaf
&& sepexternalname(&g.pkg[pi], idp, idn);
};
if (literalself) {
if (g.pkg[pi].importbase != nil) {
resolved.identity = sepdupcstr(g.pkg[pi].importbase,
cstrlen(g.pkg[pi].importbase));
} else {
resolved.identity = seplocalimportbase(&g.pkg[pi]);
};
resolved.entry = sepdupcstr(g.context[context].route,
cstrlen(g.context[context].route));
resolved.sourceroot = sepdupcstr(
g.context[context].sourceroot,
cstrlen(g.context[context].sourceroot));
if (resolved.identity == nil || resolved.entry == nil
|| resolved.sourceroot == nil) { return -1; };
located = 1;
};
};
if (located < 0) { return -1; };
if (located > 0) {
let externalproduction: bool = false;
let self: bool = os.samefile(pathstr(resolved.entry),
pathstr(g.pkg[pi].entry));
if (self && g.pkg[pi].variant == SEP_VARIANT_EXTERNAL) {
externalproduction = true;
};
if (self && !externalproduction) {
cerrpos(u.file, u.line, u.col);
cerr(": error: self-import: package '");
if (g.pkg[pi].path[0u64] != 0u8) {
cerr(pathstr(g.pkg[pi].path));
} else { cerr(pathstr(g.pkg[pi].canon)); };
cerr("' cannot import itself\n");
return -1;
};
// Normal resolution and legality precede test-graph substitution.
let di: i32 = -1;
if (externalproduction) {
let candidate: i32 = 0;
for (candidate < g.n) {
let q: *seppkg = &g.pkg[candidate];
let identitymatch: bool = false;
if (q.importbase != nil) {
identitymatch = cstreq(q.importbase,
resolved.identity);
} else {
identitymatch = g.pkg[pi].importbase == nil;
};
if (q.variant == SEP_VARIANT_SAME_TEST
&& q.role == SEP_ROLE_NORMAL
&& os.samefile(pathstr(q.entry),
pathstr(resolved.entry))
&& identitymatch) {
di = candidate;
break;
};
candidate += 1;
};
};
if (di < 0) {
di = sepfindoradd(g, resolved.identity, resolved.entry, 1);
};
if (di < 0) { return -1; };
let allowed: i32 = sepinternalimportallowed(&g.pkg[pi],
resolved.identity, resolved.entry);
if (allowed < 0) { return -1; };
if (allowed == 0) {
cerrpos(u.file, u.line, u.col);
cerr(": error: use of internal package ");
cerr(pathstr(resolved.identity)); cerr(" not allowed\n");
return SEP_LOAD_INTERNAL;
};
allowed = sepvendorimportallowed(&g.pkg[pi],
resolved.identity, resolved.entry);
if (allowed < 0) { return -1; };
if (allowed == 0) {
cerrpos(u.file, u.line, u.col);
cerr(": error: use of vendored package not allowed\n");
return SEP_LOAD_VENDOR;
};
let suffix: str = "";
let parents: u64 = 0u64;
if (sepvendorsuffix(resolved.identity, &suffix, &parents)
&& bytecmp(nm.ptr, idn, suffix.ptr,
suffix.len: u64) != 0) {
cerrpos(u.file, u.line, u.col);
cerr(": error: "); cerr(pathstr(resolved.identity));
cerr(" must be imported as "); cerr(suffix); cerr("\n");
return SEP_LOAD_VENDOR;
};
let childcontext: i32 = sepchildcontextfor(g, context,
resolved.entry, resolved.sourceroot);
if (childcontext < 0
|| !sepbindadd(bindings, 'D': u8, sourcepath, di,
u.file, u.line, u.col)
|| !sepadddep(g, pi, di)
|| !sepchildrenadd(children, di, childcontext)) {
return -1;
};
} else {
let inlinepackage: bool = false;
if (g.pkg[pi].isdir == 0) {
let pm: *syntax.node = imports.body;
for (pm != nil) {
if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64,
idp, idn) == 0) { inlinepackage = true; };
pm = pm.next;
};
};
if (!inlinepackage) {
cerrpos(u.file, u.line, u.col);
cerr(": error: cannot find package ");
os.write(2, idp, idn);
cerr("\n");
return -1;
} else {
if (!sepbindadd(bindings, 'I': u8, sourcepath, -1,
u.file, u.line, u.col)) {
return -1;
};
};
};
ui += 1;
};
return 0;
};
fn sepdepcmp(g: *sepgraph, a: i32, b: i32) i32 = {
let r: i32 = strings.compare(pathstr(g.pkg[a].path),
pathstr(g.pkg[b].path)): i32;
if (r != 0) { return r; };
if (g.pkg[a].variant < g.pkg[b].variant) { return -1; };
if (g.pkg[a].variant > g.pkg[b].variant) { return 1; };
if (g.pkg[a].role < g.pkg[b].role) { return -1; };
if (g.pkg[a].role > g.pkg[b].role) { return 1; };
if (g.pkg[a].canon != nil && g.pkg[b].canon != nil) {
r = strings.compare(pathstr(g.pkg[a].canon),
pathstr(g.pkg[b].canon)): i32;
if (r != 0) { return r; };
} else {
r = strings.compare(pathstr(g.pkg[a].entry),
pathstr(g.pkg[b].entry)): i32;
if (r != 0) { return r; };
};
if (g.pkg[a].fortest == nil || g.pkg[b].fortest == nil) {
if (g.pkg[a].fortest == nil && g.pkg[b].fortest == nil) { return 0; };
if (g.pkg[a].fortest == nil) { return -1; };
return 1;
};
return strings.compare(pathstr(g.pkg[a].fortest),
pathstr(g.pkg[b].fortest)): i32;
};
fn seppackageinitsymbol(p: *seppkg) *u8 = {
let empty: bool = p.path[0u64] == 0u8;
let need: u64 = 0u64;
if (empty) {
if (!sepaddbytes(&need, "__ww..pkg.e.v".len: u64)) { return nil; };
} else {
if (!sepaddbytes(&need, "__ww..pkg.p.".len: u64)
|| !sepaddbytes(&need, cstrlen(p.path))
|| !sepaddbytes(&need, ".v".len: u64)) { return nil; };
};
if (!sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, ".r".len: u64)
|| !sepaddbytes(&need, 1u64)
|| !sepaddbytes(&need, ".init".len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let buf: []u8;
if (!sepmakebytes(need, &buf)) { return nil; };
let off: u64 = 0u64;
if (empty) {
off = strinto(buf.ptr, off, "__ww..pkg.e.v");
} else {
off = strinto(buf.ptr, off, "__ww..pkg.p.");
off = cstrinto(buf.ptr, off, p.path);
off = strinto(buf.ptr, off, ".v");
};
off = byteinto(buf.ptr, off, (('0': i32) + p.variant): u8);
off = strinto(buf.ptr, off, ".r");
off = byteinto(buf.ptr, off, (('0': i32) + p.role): u8);
off = strinto(buf.ptr, off, ".init");
cstrseal(buf.ptr, off);
return buf.ptr;
};
fn generatedmainpath(pkgpath: *u8) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, "__wwtestmain.".len: u64)
|| !sepaddbytes(&need, cstrlen(pkgpath))
|| !sepaddbytes(&need, ".main".len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let buf: []u8;
if (!sepmakebytes(need, &buf)) { return nil; };
let off: u64 = strinto(buf.ptr, 0u64, "__wwtestmain.");
off = cstrinto(buf.ptr, off, pkgpath);
off = strinto(buf.ptr, off, ".main");
cstrseal(buf.ptr, off);
return buf.ptr;
};
// One canonical directory owns one generated main and an explicit target set.
fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
support: i32) i32 = {
let targets: [2]i32;
let ntargets: i32 = 0;
if (product.ptest >= 0) {
targets[ntargets] = product.ptest;
ntargets += 1;
};
if (product.pxtest >= 0) {
targets[ntargets] = product.pxtest;
ntargets += 1;
};
if (ntargets == 0) { return -1; };
if (ntargets == 2 && sepdepcmp(g, targets[0], targets[1]) > 0) {
let swap: i32 = targets[0];
targets[0] = targets[1];
targets[1] = swap;
};
let owner: i32 = targets[0];
if (g.pkg[owner].importbase == nil) { return -1; };
let mainpath: *u8 = generatedmainpath(g.pkg[owner].importbase);
if (mainpath == nil) { return -1; };
let pathi: i32 = 0;
for (pathi < g.n) {
if (cstreq(g.pkg[pathi].path, mainpath)) {
if (g.pkg[pathi].generatedmain) {
let supportistarget: bool = false;
let tk: i32 = 0;
for (tk < ntargets) {
if (targets[tk] == support) { supportistarget = true; };
tk += 1;
};
let wantssupport: bool = support >= 0 && !supportistarget;
let wanteddeps: i32 = ntargets;
if (wantssupport) { wanteddeps += 1; };
let sametargets: bool =
g.pkg[pathi].ngeneratedtargets == ntargets;
tk = 0;
for (tk < ntargets && sametargets) {
if (g.pkg[pathi].generatedtargets[tk] != targets[tk]) {
sametargets = false;
};
tk += 1;
};
let hassupport: bool = false;
let dk: i32 = 0;
for (dk < g.pkg[pathi].ndeps) {
if (wantssupport && g.pkg[pathi].deps[dk] == support) {
hassupport = true;
};
dk += 1;
};
if (sametargets && hassupport == wantssupport
&& g.pkg[pathi].ndeps == wanteddeps) {
return pathi;
};
};
cerr("ww: generated test-main package identity collides with source import ");
cerr(pathstr(mainpath)); cerr("\n");
return -1;
};
pathi += 1;
};
if (g.n == SEP_COUNT_MAX) { sepfailsize(); return -1; };
if (!sepreservepackages(g, g.n + 1)) {
return -1;
};
let p: *seppkg = &g.pkg[g.n];
p.path = mainpath;
p.importbase = nil;
p.entry = g.pkg[owner].entry;
p.canon = sepappendlit(g.pkg[owner].canon, "#directory-test-main");
if (p.canon == nil) { return -1; };
p.artifact = sepappendlit(g.pkg[owner].importbase, "-test-main");
if (p.artifact == nil) { return -1; };
p.storage = nil;
p.initsymbol = nil;
p.storagehashed = false;
p.name = sepdupcstr("main\0".ptr, 4u64);
if (p.name == nil) { return -1; };
p.testpackage = nil;
p.fortest = nil;
p.sources = nil;
p.nsources = 0;
p.isdir = 0;
p.variant = SEP_VARIANT_TEST_MAIN;
p.role = SEP_ROLE_GENERATED_MAIN;
p.root = true;
p.linkentry = true;
p.generatedmain = true;
let generatedtargets: []i32;
if (!sepmakeints(ntargets, &generatedtargets)) { return -1; };
p.generatedtargets = generatedtargets;
p.ngeneratedtargets = ntargets;
let ti: i32 = 0;
for (ti < ntargets) {
p.generatedtargets[ti] = targets[ti];
ti += 1;
};
p.failed = false;
p.action = false;
p.testsupport = false;
p.loaded = true;
p.exportchanged = false;
p.sourcestaged = false;
p.initstaged = false;
p.archivestaged = false;
p.emitcontext = product.context;
let emptystate: []u8;
p.contextstate = emptystate;
let emptybindings: []sepbind;
p.bindings = emptybindings;
let emptydeps: []i32;
p.deps = emptydeps;
p.ndeps = 0;
if (!sepsetcontextstate(p, product.context, 2u8)) { return -1; };
ti = 0;
for (ti < ntargets) {
if (!sepadddep(g, g.n, targets[ti])) { return -1; };
ti += 1;
};
let supportistarget: bool = false;
ti = 0;
for (ti < ntargets) {
if (targets[ti] == support) { supportistarget = true; };
ti += 1;
};
if (support >= 0 && !supportistarget
&& !sepadddep(g, g.n, support)) { return -1; };
let i: i32 = 1;
for (i < p.ndeps) {
let v: i32 = p.deps[i];
let j: i32 = i;
for (j > 0 && sepdepcmp(g, p.deps[j - 1], v) > 0) {
p.deps[j] = p.deps[j - 1];
j -= 1;
};
p.deps[j] = v;
i += 1;
};
p.color = 0;
let r: i32 = g.n;
g.n += 1;
return r;
};
fn seprewriteactiondeps(g: *sepgraph, pi: i32,
replacement: []i32) bool = {
let p: *seppkg = &g.pkg[pi];
let deps: []i32;
if (!sepmakeints(p.ndeps, &deps)) { return false; };
let ndeps: i32 = 0;
let i: i32 = 0;
for (i < p.ndeps) {
let dep: i32 = p.deps[i];
if (dep >= 0 && dep < replacement.len) { dep = replacement[dep]; };
let found: bool = false;
let j: i32 = 0;
for (j < ndeps) {
if (deps[j] == dep) { found = true; break; };
j += 1;
};
if (!found) { deps[ndeps] = dep; ndeps += 1; };
i += 1;
};
i = 1;
for (i < ndeps) {
let dep: i32 = deps[i];
let j: i32 = i;
for (j > 0 && sepdepcmp(g, deps[j - 1], dep) > 0) {
deps[j] = deps[j - 1];
j -= 1;
};
deps[j] = dep;
i += 1;
};
i = 0;
for (i < p.bindings.len) {
let dep: i32 = p.bindings[i].dep;
if (dep >= 0 && dep < replacement.len) {
p.bindings[i].dep = replacement[dep];
};
i += 1;
};
sepbindsort(&p.bindings);
if (p.deps.ptr != nil) {
os.free(p.deps.ptr: *void,
(p.deps.cap: u64) * (size(i32): u64));
};
p.deps = deps;
p.ndeps = ndeps;
return true;
};
fn sepclonefortest(g: *sepgraph, original: i32, owner: *u8,
replacement: []i32) i32 = {
if (g.n == SEP_COUNT_MAX || !sepreservepackages(g, g.n + 1)) {
return -1;
};
let src: *seppkg = &g.pkg[original];
let p: seppkg;
p.path = src.path;
p.importbase = src.importbase;
p.entry = src.entry;
p.canon = src.canon;
p.artifact = nil;
p.storage = nil;
p.initsymbol = nil;
p.storagehashed = false;
p.name = nil;
p.testpackage = src.testpackage;
p.fortest = nil;
p.sources = nil;
p.nsources = 0;
p.isdir = src.isdir;
p.variant = SEP_VARIANT_TEST_COPY;
p.role = src.role;
p.root = false;
p.linkentry = false;
p.generatedmain = false;
let emptytargets: []i32;
p.generatedtargets = emptytargets;
p.ngeneratedtargets = 0;
p.failed = src.failed;
p.action = false;
p.testsupport = src.testsupport;
p.loaded = src.loaded;
p.exportchanged = false;
p.sourcestaged = false;
p.initstaged = false;
p.archivestaged = false;
p.emitcontext = src.emitcontext;
let emptycontext: []u8;
p.contextstate = emptycontext;
let emptybindings: []sepbind;
p.bindings = emptybindings;
let emptydeps: []i32;
p.deps = emptydeps;
p.ndeps = 0;
p.color = 0;
if (src.name != nil) {
p.name = sepdupcstr(src.name, cstrlen(src.name));
if (p.name == nil) {
seppkgfreeowned(&p);
return -1;
};
};
p.fortest = sepdupcstr(owner, cstrlen(owner));
if (p.fortest == nil) {
seppkgfreeowned(&p);
return -1;
};
// A test copy is product-scoped even when its replaced source node is not
// in the final action closure. Preserve the complete-action locator without
// relying on a storage collision with that inactive source node.
p.storage = sepstoragedigest(&p);
p.storagehashed = true;
if (p.storage == nil) {
seppkgfreeowned(&p);
return -1;
};
if (src.nsources > 0) {
let sources: []*u8;
if (!sepmakeptrs(src.nsources, &sources)) {
seppkgfreeowned(&p);
return -1;
};
let si: i32 = 0;
for (si < src.nsources) {
let bytes: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &bytes)) {
let sj: i32 = 0;
for (sj < si) {
os.free(sources[sj]: *void, os.PATH_MAX: u64);
sj += 1;
};
os.free(sources.ptr: *void,
(sources.cap: u64) * (size(*u8): u64));
seppkgfreeowned(&p);
return -1;
};
let n: u64 = cstrlen(src.sources[si]);
let sj: u64 = 0u64;
for (sj < n) {
bytes[sj] = src.sources[si][sj];
sj += 1u64;
};
bytes[n] = 0u8;
sources[si] = bytes.ptr;
si += 1;
};
p.sources = sources.ptr;
p.nsources = src.nsources;
};
if (src.contextstate.len > 0) {
let contextstate: []u8;
if (!sepmakebytes(src.contextstate.len: u64, &contextstate)) {
seppkgfreeowned(&p);
return -1;
};
let ci: i32 = 0;
for (ci < src.contextstate.len) {
contextstate[ci] = src.contextstate[ci];
ci += 1;
};
p.contextstate = contextstate;
};
let bi: i32 = 0;
for (bi < src.bindings.len) {
let b: sepbind = src.bindings[bi];
if (!sepbindadd(&p.bindings, b.kind, b.name, b.dep,
b.source, b.line, b.col)) {
seppkgfreeowned(&p);
return -1;
};
bi += 1;
};
if (src.ndeps > 0) {
let deps: []i32;
if (!sepmakeints(src.ndeps, &deps)) {
seppkgfreeowned(&p);
return -1;
};
let di: i32 = 0;
for (di < src.ndeps) {
deps[di] = src.deps[di];
di += 1;
};
p.deps = deps;
p.ndeps = src.ndeps;
};
let ni: i32 = g.n;
g.pkg[ni] = p;
g.n += 1;
if (!seprewriteactiondeps(g, ni, replacement)) {
seppkgfreeowned(&g.pkg[ni]);
g.n -= 1;
return -1;
};
return ni;
};
fn seprecompilefortest(g: *sepgraph, product: *sepproduct) i32 = {
if (product.productionroot < 0 || product.ptest < 0
|| product.productionroot == product.ptest) { return 0; };
let nbase: i32 = g.n;
let order: []i32;
let stack: []i32;
let replacement: []i32;
if (!sepmakeints(nbase, &order) || !sepmakeints(nbase, &stack)
|| !sepmakeints(nbase, &replacement)) { return -1; };
let i: i32 = 0;
for (i < nbase) {
g.pkg[i].color = 0;
replacement[i] = i;
i += 1;
};
replacement[product.productionroot] = product.ptest;
let norder: i32 = 0;
if (septopovisit(g, product.root, order, &norder, stack, 0) < 0) {
return -1;
};
let ownerprefix: *u8 = sepappendlit(g.pkg[product.ptest].importbase, "#");
if (ownerprefix == nil) { return -1; };
let owner: *u8 = sepappendlit(ownerprefix, pathstr(g.pkg[product.ptest].canon));
if (owner == nil) { return -1; };
let oi: i32 = 0;
for (oi < norder) {
let pi: i32 = order[oi];
if (pi == product.productionroot) { oi += 1; continue; };
let changed: bool = false;
let k: i32 = 0;
for (k < g.pkg[pi].ndeps && !changed) {
let dep: i32 = g.pkg[pi].deps[k];
if (dep >= 0 && dep < nbase && replacement[dep] != dep) {
changed = true;
};
k += 1;
};
if (changed) {
if (pi == product.ptest || pi == product.pxtest
|| pi == product.root) {
if (!seprewriteactiondeps(g, pi, replacement)) { return -1; };
} else {
let copy: i32 = sepclonefortest(g, pi, owner, replacement);
if (copy < 0) { return -1; };
replacement[pi] = copy;
};
};
oi += 1;
};
return 0;
};
// Load one action's owned sources and direct bindings. Dependency descent is
// iterative below so a valid deep graph is not limited by the native stack.
fn seppreparepkgcontext(g: *sepgraph, pi: i32, context: i32,
children: *[]sepchild) i32 = {
if (!sepsetcontextstate(&g.pkg[pi], context, 1u8)) { return -1; };
let searchpath: *u8 = g.context[context].searchpath;
let fv: expctx;
fv.out = -1;
fv.dirs = searchpath;
fv.visit = nil;
let bindings: []sepbind;
let rc: i32 = 0;
if (!g.pkg[pi].loaded) {
g.pkg[pi].loaded = true;
if (g.pkg[pi].isdir != 0) {
let sources: **u8;
let nsources: i32;
sources, nsources = enumeratedir(g.pkg[pi].entry,
g.pkg[pi].variant, g.pkg[pi].testpackage);
g.pkg[pi].sources = sources;
g.pkg[pi].nsources = nsources;
if (g.pkg[pi].nsources == -2) {
// diagnosed in enumeratedir
rc = -1;
} else { if (g.pkg[pi].nsources < 0) {
cerr("ww: cannot read directory ");
cerr(pathstr(g.pkg[pi].entry));
cerr("\n");
rc = -1;
} else { if (g.pkg[pi].nsources == 0) {
cerr("ww: ");
cerr(pathstr(g.pkg[pi].entry));
cerr(": directory contains no WW package sources\n");
rc = -1;
}; }; };
let i: i32 = 0;
for (i < g.pkg[pi].nsources && rc == 0) {
if (!sepregisterfilefold(g, g.pkg[pi].canon,
g.pkg[pi].sources[i])) { rc = -1; };
i += 1;
};
};
};
if (g.pkg[pi].isdir != 0) {
let i: i32 = 0;
for (i < g.pkg[pi].nsources) {
if (rc == 0) {
rc = sepscanfile(g, pi, g.pkg[pi].sources[i],
context, &fv, &bindings, children, 1);
};
i += 1;
};
} else { if (rc == 0) {
rc = sepscanfile(g, pi, g.pkg[pi].entry, context,
&fv, &bindings, children, 0);
}; };
sepbindsort(&bindings);
if (rc == 0 && !sepvalidatebindings(g, bindings)) { rc = -1; };
if (rc == 0 && g.pkg[pi].emitcontext < 0) {
g.pkg[pi].bindings = bindings;
g.pkg[pi].emitcontext = context;
} else { if (rc == 0 && !sepbindsame(g.pkg[pi].bindings, bindings)) {
let first: *u8 = g.context[g.pkg[pi].emitcontext].root;
let second: *u8 = g.context[context].root;
if (strings.compare(pathstr(first), pathstr(second)) > 0) {
let swap: *u8 = first; first = second; second = swap;
};
cerr("ww: package ");
if (g.pkg[pi].path[0u64] != 0u8) {
cerr(pathstr(g.pkg[pi].path));
} else { cerr(pathstr(g.pkg[pi].canon)); };
cerr(" resolves imports differently in ");
cerr(pathstr(first));
cerr(" and "); cerr(pathstr(second)); cerr("\n");
rc = -1;
}; };
if (rc < 0) {
g.pkg[pi].contextstate[context] = 2u8;
g.pkg[pi].failed = true;
return rc;
};
let si: i32 = 1;
for (si < g.pkg[pi].ndeps) {
let v: i32 = g.pkg[pi].deps[si];
let sj: i32 = si;
for (sj > 0 && sepdepcmp(g,
g.pkg[pi].deps[sj - 1], v) > 0) {
g.pkg[pi].deps[sj] = g.pkg[pi].deps[sj - 1];
sj -= 1;
};
g.pkg[pi].deps[sj] = v;
si += 1;
};
g.pkg[pi].contextstate[context] = 2u8;
return 0;
};
type seploadframe = struct {
pkg: i32,
context: i32,
nextchild: i32,
pendingdep: i32,
children: []sepchild,
};
fn sepallocloadframes(cap: i32) ([]seploadframe | nomem) = {
let value: []seploadframe = alloc([], cap: u64)?;
return value;
};
fn sepreserveloadframes(frames: *[]seploadframe, used: i32,
need: i32) bool = {
if (need <= frames.len) { return true; };
let cap: i32 = sepgrowcap(frames.len, need);
if (cap < 0) { return false; };
let allocation: ([]seploadframe | nomem) = sepallocloadframes(cap);
let next: []seploadframe;
match (allocation) {
case let v: []seploadframe => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < used) { next[i] = (*frames)[i]; i += 1; };
if (frames.ptr != nil) {
os.free(frames.ptr: *void,
(frames.cap: u64) * (size(seploadframe): u64));
};
*frames = next;
return true;
};
fn sepfinishloadframes(frames: []seploadframe, result: i32) i32 = {
if (frames.ptr != nil) {
os.free(frames.ptr: *void,
(frames.cap: u64) * (size(seploadframe): u64));
};
return result;
};
fn sepclearchildren(children: *[]sepchild) void = {
if (children.ptr != nil) {
os.free(children.ptr: *void,
(children.cap: u64) * (size(sepchild): u64));
};
let empty: []sepchild;
*children = empty;
};
// Load pi once: a directory node takes ownership of its sorted production
// paths, then every selected-root context verifies the same canonical import
// bindings before the package is compiled once.
fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
if (pi < 0 || pi >= g.n || context < 0 || context >= g.ncontext) {
return -1;
};
if (g.pkg[pi].testsupport
&& g.supportcontext >= 0) { context = g.supportcontext; };
let frames: []seploadframe;
let nframe: i32 = 0;
if (!sepreserveloadframes(&frames, nframe, 1)) { return -2; };
frames[0].pkg = pi;
frames[0].context = context;
frames[0].nextchild = -1;
frames[0].pendingdep = -1;
let rootchildren: []sepchild;
frames[0].children = rootchildren;
nframe = 1;
for (nframe > 0) {
let f: *seploadframe = &frames[nframe - 1];
if (f.nextchild < 0) {
let state: u8 = sepcontextstate(&g.pkg[f.pkg], f.context);
if (state == 2u8) {
if (g.pkg[f.pkg].failed) {
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -1);
};
sepclearchildren(&f.children);
nframe -= 1;
continue;
};
if (state == 1u8) {
sepclearchildren(&f.children);
nframe -= 1;
continue;
};
let prepared: i32 = seppreparepkgcontext(g, f.pkg,
f.context, &f.children);
if (prepared < 0) {
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
if (sepfatalallocation) {
return sepfinishloadframes(frames, -2);
};
return sepfinishloadframes(frames, prepared);
};
f.nextchild = 0;
};
if (f.pendingdep >= 0) {
let dep: i32 = f.pendingdep;
f.pendingdep = -1;
if (dep != f.pkg && sepexternalproductionedge(g, f.pkg, dep)
&& !sepexternalnamematchesproduction(g, f.pkg, dep)) {
cerr("ww: external test package ");
cerr(pathstr(g.pkg[f.pkg].name));
cerr(" does not match production package ");
cerr(pathstr(g.pkg[dep].name)); cerr("\n");
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -1);
};
if (dep != f.pkg && sepforbiddencommandimport(g, f.pkg, dep)) {
cerr("ww: package ");
if (g.pkg[dep].path[0u64] != 0u8) {
cerr(pathstr(g.pkg[dep].path));
} else { cerr(pathstr(g.pkg[dep].canon)); };
cerr(" is a program, not an importable package\n");
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -1);
};
};
if (f.nextchild >= f.children.len) {
sepclearchildren(&f.children);
nframe -= 1;
continue;
};
let child: sepchild = f.children[f.nextchild];
f.nextchild += 1;
let dep: i32 = child.pkg;
f.pendingdep = dep;
let childcontext: i32 = child.context;
if (g.pkg[dep].testsupport && g.supportcontext >= 0) {
childcontext = g.supportcontext;
};
if (nframe == SEP_COUNT_MAX) {
sepfailsize();
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -2);
};
if (!sepreserveloadframes(&frames, nframe, nframe + 1)) {
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
sepclearchildren(&frames[fi].children);
fi += 1;
};
return sepfinishloadframes(frames, -2);
};
frames[nframe].pkg = dep;
frames[nframe].context = childcontext;
frames[nframe].nextchild = -1;
frames[nframe].pendingdep = -1;
let childchildren: []sepchild;
frames[nframe].children = childchildren;
nframe += 1;
};
return sepfinishloadframes(frames, 0);
};
fn sepimportcomponent(s: *u8, n: u64) bool = {
if (n == 0u64) { return false; };
let first: u8 = s[0u64];
if (!((first >= 'a': u8 && first <= 'z': u8)
|| (first >= 'A': u8 && first <= 'Z': u8)
|| first == '_': u8)) { return false; };
let i: u64 = 1u64;
for (i < n) {
let c: u8 = s[i];
if (!((c >= 'a': u8 && c <= 'z': u8)
|| (c >= 'A': u8 && c <= 'Z': u8)
|| (c >= '0': u8 && c <= '9': u8)
|| c == '_': u8)) { return false; };
i += 1u64;
};
return syntax.kwlookup(s, n: i32) == syntax.tkind.TK_NONE;
};
fn sepimportpathfromrelative(rel: *u8, out: *u8, outsz: u64) i32 = {
let off: u64 = 0u64;
let p: u64 = 0u64;
let total: u64 = cstrlen(rel);
for (p < total) {
let q: u64 = p;
for (q < total && rel[q] != '/': u8) { q += 1u64; };
let n: u64 = q - p;
if (!sepimportcomponent(rel + p, n)) { return 0; };
let more: bool = q < total;
let separator: u64 = 0u64;
if (more) { separator = 1u64; };
if (off + n + separator + 1u64 > outsz) {
return -1;
};
bytecpy(out + off, rel + p, n);
off += n;
if (more) { out[off] = '.': u8; off += 1u64; };
p = q + 1u64;
};
out[off] = 0u8;
if (off == 0u64) { return 0; };
return 1;
};
// Bind a literal directory's complete lexical identity before a source edge
// can reach the same physical directory under another vendored route.
fn sepcontextimportbase(g: *sepgraph, context: i32, out: **u8) i32 = {
*out = nil;
if (context < 0 || context >= g.ncontext) { return -1; };
let c: *sepcontext = &g.context[context];
if (cstreq(c.route, c.sourceroot)) { return 0; };
let rel: str = "";
if (!seplexicalrelative(c.sourceroot, c.route, &rel)
|| rel.len == 0) {
cerr("ww: package route "); cerr(pathstr(c.route));
cerr(" is outside source root "); cerr(pathstr(c.sourceroot));
cerr("\n");
return -1;
};
let base: []u8;
if (!sepmakebytes((rel.len + 1): u64, &base)) { return -1; };
let converted: i32 = sepimportpathfromrelative(rel.ptr, base.ptr,
(rel.len + 1): u64);
if (converted <= 0 || reservedimportpath(base.ptr)) {
if (converted == 0) {
cerr("ww: invalid package path "); cerr(rel); cerr("\n");
};
return -1;
};
*out = base.ptr;
return 1;
};
// A reverse candidate is authoritative only when the ordinary ordered lookup
// selects this exact canonical directory. Later or nested roots therefore
// cannot manufacture an alias hidden by an earlier source root.
fn sepreverseimportbase(g: *sepgraph, p: *seppkg, context: i32,
out: *u8, outsz: u64) i32 = {
let searchpath: *u8 = g.context[context].searchpath;
let total: u64 = cstrlen(searchpath);
let pos: u64 = 0u64;
for (pos < total) {
let end: u64 = pos;
for (end < total && searchpath[end] != ':': u8) { end += 1u64; };
if (end > pos) {
let root: str;
root.ptr = searchpath + pos;
root.len = (end - pos): i32;
let canonroot: *u8 = canonicaldir(root);
if (canonroot == nil && sepfatalallocation) { return -1; };
if (canonroot != nil) {
let rn: u64 = cstrlen(canonroot);
let dn: u64 = cstrlen(p.canon);
let rel: *u8 = nil;
if (rn == 1u64 && canonroot[0u64] == '/': u8
&& dn > 1u64 && p.canon[0u64] == '/': u8) {
rel = p.canon + 1u64;
} else { if (dn > rn + 1u64
&& bytecmp(p.canon, rn, canonroot, rn) == 0
&& p.canon[rn] == '/': u8) {
rel = p.canon + rn + 1u64;
}; };
if (rel != nil) {
let converted: i32 = sepimportpathfromrelative(rel,
out, outsz);
if (converted < 0) { return -1; };
if (converted > 0 && reservedimportpath(out)) {
converted = 0;
};
if (converted > 0) {
let selected: *u8 = locateimport(searchpath, rel,
cstrlen(rel));
if (selected != nil) {
let selectedcanon: *u8 = canonicaldir(pathstr(selected));
if (selectedcanon == nil && sepfatalallocation) {
return -1;
};
if (selectedcanon != nil
&& cstreq(selectedcanon, p.canon)) {
return 1;
};
};
};
};
};
};
pos = end + 1u64;
};
return 0;
};
// The reserved local namespace is reversible, so filesystem identity never
// depends on a hash, request order, output name, declared package name, or
// another selected package.
fn seplocalimportbase(p: *seppkg) *u8 = {
let need: u64 = 0u64;
if (!sepaddbytes(&need, SEP_LOCAL_IMPORT_PREFIX.len: u64)
|| !sepaddbytes(&need, 2u64)
|| !sepmuladdbytes(&need, cstrlen(p.canon), 4u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = strinto(out.ptr, 0u64, SEP_LOCAL_IMPORT_PREFIX);
off = byteinto(out.ptr, off, '.': u8);
off = byteinto(out.ptr, off, 'p': u8);
let hex: str = "0123456789abcdef";
let i: u64 = 0u64;
for (p.canon[i] != 0u8) {
let c: u8 = p.canon[i];
if ((c >= 'a': u8 && c <= 'z': u8)
|| (c >= 'A': u8 && c <= 'Z': u8)
|| (c >= '0': u8 && c <= '9': u8)) {
off = byteinto(out.ptr, off, c);
} else { if (c == '_': u8 || c == '/': u8) {
off = byteinto(out.ptr, off, '_': u8);
let escaped: u8 = 's': u8;
if (c == '_': u8) { escaped = 'u': u8; };
off = byteinto(out.ptr, off, escaped);
} else {
off = byteinto(out.ptr, off, '_': u8);
off = byteinto(out.ptr, off, 'x': u8);
let high: i32 = (c / 16u8): i32;
let low: i32 = (c % 16u8): i32;
off = byteinto(out.ptr, off, hex[high]);
off = byteinto(out.ptr, off, hex[low]);
}; };
i += 1u64;
};
cstrseal(out.ptr, off);
return out.ptr;
};
// Finalization verifies every reached context before generated-main creation.
// Source bindings and explicit lookup identities remain authoritative; a
// literal root either round-trips through an active root or receives the
// reserved reversible local identity.
fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = {
let pi: i32 = 0;
for (pi < g.n) {
let p: *seppkg = &g.pkg[pi];
if (p.isdir != 0 && !p.generatedmain && !p.failed && p.loaded
&& p.role != SEP_ROLE_TEST_SUPPORT && p.importbase == nil) {
let ci: i32 = 0;
for (ci < g.ncontext) {
if (sepcontextstate(p, ci) == 2u8) {
let candidatesz: u64 = cstrlen(p.canon) + 1u64;
let candidate: []u8;
if (!sepmakebytes(candidatesz, &candidate)) {
return -1;
};
let found: i32 = sepreverseimportbase(g, p, ci,
candidate.ptr, candidatesz);
if (found < 0) { return -1; };
if (found > 0 && sepbindimportbase(g, pi,
candidate.ptr) < 0) { return -1; };
};
ci += 1;
};
};
pi += 1;
};
pi = 0;
for (pi < g.n) {
let p: *seppkg = &g.pkg[pi];
if (p.isdir != 0 && !p.generatedmain && !p.failed && p.loaded
&& p.importbase == nil) {
let base: *u8 = nil;
let i: i32 = 0;
for (i < g.n) {
if (i != pi && g.pkg[i].isdir != 0
&& !g.pkg[i].generatedmain
&& g.pkg[i].role != SEP_ROLE_TEST_SUPPORT
&& p.role != SEP_ROLE_TEST_SUPPORT
&& cstreq(g.pkg[i].canon, p.canon)
&& g.pkg[i].importbase != nil) {
if (p.root && seppathisvendored(g.pkg[i].importbase)) {
i += 1; continue;
};
base = g.pkg[i].importbase;
break;
};
i += 1;
};
if (base == nil) {
base = seplocalimportbase(p);
if (base == nil) { return -1; };
};
if (sepbindimportbase(g, pi, base) < 0) { return -1; };
};
pi += 1;
};
pi = 0;
for (pi < g.n) {
let p: *seppkg = &g.pkg[pi];
if (p.isdir != 0 && !p.generatedmain && !p.failed && p.loaded) {
p.artifact = nil;
if (p.variant == SEP_VARIANT_SAME_TEST) {
p.artifact = sepappendlit(p.path, "-internal-test");
} else { if (p.variant == SEP_VARIANT_EXTERNAL) {
p.artifact = sepappendlit(p.path, "-external-test");
}; };
if ((p.variant == SEP_VARIANT_SAME_TEST
|| p.variant == SEP_VARIANT_EXTERNAL)
&& p.artifact == nil) { return -1; };
};
pi += 1;
};
return 0;
};
fn sepcyclenode(p: *u8) void = {
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
};
type septopoframe = struct {
pkg: i32,
nextdep: i32,
};
fn sepalloctopoframes(cap: i32) ([]septopoframe | nomem) = {
let value: []septopoframe = alloc([], cap: u64)?;
return value;
};
fn sepreservetopoframes(frames: *[]septopoframe, used: i32,
need: i32) bool = {
if (need <= frames.len) { return true; };
let cap: i32 = sepgrowcap(frames.len, need);
if (cap < 0) { return false; };
let allocation: ([]septopoframe | nomem) = sepalloctopoframes(cap);
let next: []septopoframe;
match (allocation) {
case let v: []septopoframe => next = v;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < used) { next[i] = (*frames)[i]; i += 1; };
if (frames.ptr != nil) {
os.free(frames.ptr: *void,
(frames.cap: u64) * (size(septopoframe): u64));
};
*frames = next;
return true;
};
fn sepfinishtopoframes(frames: []septopoframe, result: i32) i32 = {
if (frames.ptr != nil) {
os.free(frames.ptr: *void,
(frames.cap: u64) * (size(septopoframe): u64));
};
return result;
};
// Iterative DFS post-order over the dep DAG → reverse-topo (deps before
// importer). Tri-color and stack[0..nframe) retain the exact live path and
// deterministic cycle diagnostic without consuming one native frame/action.
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
stack: []i32, depth: i32) i32 = {
if (g.pkg[pi].color == 2) { return 0; };
if (g.pkg[pi].color == 1) {
cerr("ww: dependency cycle: ");
sepcyclenode(g.pkg[pi].path);
cerr("\n");
return -1;
};
let frames: []septopoframe;
let nframe: i32 = 0;
if (!sepreservetopoframes(&frames, nframe, 1)) { return -2; };
g.pkg[pi].color = 1;
stack[0] = pi;
frames[0].pkg = pi;
frames[0].nextdep = 0;
nframe = 1;
for (nframe > 0) {
let f: *septopoframe = &frames[nframe - 1];
if (f.nextdep < g.pkg[f.pkg].ndeps) {
let dep: i32 = g.pkg[f.pkg].deps[f.nextdep];
f.nextdep += 1;
if (g.pkg[dep].color == 2) { continue; };
if (g.pkg[dep].color == 1) {
let j: i32 = 0;
for (j < nframe && stack[j] != dep) { j += 1; };
cerr("ww: dependency cycle: ");
let s: i32 = j;
for (s < nframe) {
sepcyclenode(g.pkg[stack[s]].path);
cerr(" -> ");
s += 1;
};
sepcyclenode(g.pkg[dep].path);
cerr("\n");
return sepfinishtopoframes(frames, -1);
};
if (nframe == SEP_COUNT_MAX) {
sepfailsize();
return sepfinishtopoframes(frames, -2);
};
if (!sepreservetopoframes(&frames, nframe, nframe + 1)) {
return sepfinishtopoframes(frames, -2);
};
g.pkg[dep].color = 1;
stack[nframe] = dep;
frames[nframe].pkg = dep;
frames[nframe].nextdep = 0;
nframe += 1;
continue;
};
g.pkg[f.pkg].color = 2;
order[*no] = f.pkg;
*no += 1;
nframe -= 1;
};
return sepfinishtopoframes(frames, 0);
};
fn sepinitcmp(g: *sepgraph, a: i32, b: i32) i32 = {
let r: i32 = strings.compare(pathstr(g.pkg[a].path),
pathstr(g.pkg[b].path)): i32;
if (r != 0) { return r; };
if (g.pkg[a].variant < g.pkg[b].variant) { return -1; };
if (g.pkg[a].variant > g.pkg[b].variant) { return 1; };
if (g.pkg[a].role < g.pkg[b].role) { return -1; };
if (g.pkg[a].role > g.pkg[b].role) { return 1; };
if (g.pkg[a].canon != nil && g.pkg[b].canon != nil) {
r = strings.compare(pathstr(g.pkg[a].canon),
pathstr(g.pkg[b].canon)): i32;
if (r != 0) { return r; };
};
if (g.pkg[a].fortest == nil || g.pkg[b].fortest == nil) {
if (g.pkg[a].fortest == nil && g.pkg[b].fortest == nil) { return 0; };
if (g.pkg[a].fortest == nil) { return -1; };
return 1;
};
return strings.compare(pathstr(g.pkg[a].fortest),
pathstr(g.pkg[b].fortest)): i32;
};
// Go's linker uses a lexical ready queue over the reachable init-task DAG.
// Dependencies become ready first; canonical package identity breaks ties.
fn sepinitorder(g: *sepgraph, root: i32, out: *[]i32, nout: *i32) i32 = {
let active: []u8;
let done: []u8;
let todo: []i32;
let order: []i32;
if (!sepmakebytes(g.n: u64, &active)
|| !sepmakebytes(g.n: u64, &done)
|| !sepmakeints(g.n, &todo)
|| !sepmakeints(g.n, &order)) {
return -1;
};
let zi: i32 = 0;
for (zi < g.n) {
active[zi] = 0u8;
done[zi] = 0u8;
zi += 1;
};
let ntodo: i32 = 0;
active[root] = 1u8;
todo[ntodo] = root;
ntodo += 1;
for (ntodo > 0) {
ntodo -= 1;
let pi: i32 = todo[ntodo];
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
let dep: i32 = g.pkg[pi].deps[k];
if (active[dep] == 0u8) {
active[dep] = 1u8;
todo[ntodo] = dep;
ntodo += 1;
};
k += 1;
};
};
let nactive: i32 = 0;
let pi: i32 = 0;
for (pi < g.n) {
if (active[pi] != 0u8) { nactive += 1; };
pi += 1;
};
let no: i32 = 0;
for (no < nactive) {
let best: i32 = -1;
pi = 0;
for (pi < g.n) {
if (active[pi] != 0u8 && done[pi] == 0u8) {
let blocked: bool = false;
let k: i32 = 0;
for (k < g.pkg[pi].ndeps && !blocked) {
let dep: i32 = g.pkg[pi].deps[k];
if (dep != pi && active[dep] != 0u8
&& done[dep] == 0u8) { blocked = true; };
k += 1;
};
if (!blocked && (best < 0 || sepinitcmp(g, pi, best) < 0)) {
best = pi;
};
};
pi += 1;
};
if (best < 0) {
cerr("ww: dependency cycle in initialization closure\n");
return -1;
};
done[best] = 1u8;
order[no] = best;
no += 1;
};
*out = order;
*nout = no;
return 0;
};
fn sepcomposeinitdispatch(g: *sepgraph, product: *sepproduct,
unitpath: *u8, asmpath: *u8) i32 = {
let order: []i32;
let norder: i32 = 0;
if (sepinitorder(g, product.root, &order, &norder) < 0) { return -1; };
let unit: i32 = os.open(pathstr(unitpath),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32);
if (unit < 0) {
cerrpath("ww: cannot open ", unitpath, "\n");
return -1;
};
let bad: bool = !sepwriteall(unit, "//ww:init-root ".ptr,
"//ww:init-root ".len: u64)
|| !sepwriteall(unit, g.pkg[product.root].initsymbol,
cstrlen(g.pkg[product.root].initsymbol))
|| !sepwriteall(unit, "\n".ptr, 1u64);
let i: i32 = 0;
for (i < norder && !bad) {
bad = !sepwriteall(unit, "//ww:init-call ".ptr,
"//ww:init-call ".len: u64)
|| !sepwriteall(unit, g.pkg[order[i]].initsymbol,
cstrlen(g.pkg[order[i]].initsymbol))
|| !sepwriteall(unit, "\n".ptr, 1u64);
i += 1;
};
if (os.close(unit) != 0) { bad = true; };
if (bad) {
cerr("ww: cannot write initialization unit\n");
return -1;
};
let assembly: i32 = os.open(pathstr(asmpath),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32);
if (assembly < 0) {
cerrpath("ww: cannot open ", asmpath, "\n");
os.remove(pathstr(unitpath));
return -1;
};
let prologue: str = "TEXT __ww..dispatch,$0\n\tPUSHQ\tBP\n\tMOVQ\tSP, BP\n\tSUBQ\t$0, SP\n";
bad = !sepwriteall(assembly, prologue.ptr, prologue.len: u64);
i = 0;
for (i < norder && !bad) {
bad = !sepwriteall(assembly, "\tCALL\t".ptr, "\tCALL\t".len: u64)
|| !sepwriteall(assembly, g.pkg[order[i]].initsymbol,
cstrlen(g.pkg[order[i]].initsymbol))
|| !sepwriteall(assembly, "(SB)\n".ptr, "(SB)\n".len: u64);
i += 1;
};
let epilogue: str = "\tMOVQ\t$0, AX\n\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n";
if (!bad) { bad = !sepwriteall(assembly, epilogue.ptr, epilogue.len: u64); };
if (os.close(assembly) != 0) { bad = true; };
if (bad) {
cerr("ww: cannot write initialization assembly\n");
os.remove(pathstr(unitpath));
os.remove(pathstr(asmpath));
return -1;
};
return 0;
};
fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32,
includeroot: bool) i32 = {
let i: i32 = 0;
for (i < n) {
let a: i32 = order[i];
if ((includeroot || !g.pkg[a].root)
&& g.pkg[a].path[0u64] != 0u8) {
let j: i32 = i + 1;
for (j < n) {
let b: i32 = order[j];
if ((includeroot || !g.pkg[b].root)
&& cstreq(g.pkg[a].path, g.pkg[b].path)) {
cerr("ww: product closure contains multiple packages named ");
cerr(pathstr(g.pkg[a].path)); cerr("\n");
return -1;
};
j += 1;
};
};
i += 1;
};
return 0;
};
// No imported source or export enters this body: the package owns exactly its
// sorted source set.
fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
match (os.writeall(fd, buf, n)) {
case let wrote: i64 => return wrote == n: i64;
case let e: os.oserror => return false;
};
};
fn sepemitbody(fd: i32, path: *u8, modpath: *u8) i32 = {
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(path);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return -1;
};
// #57: tag the primary body by its full dotted import path so the
// definer mangles == the importer reference; a root build (path "")
// stays a bare reset (keeps bare main).
if (modpath != nil && modpath[0u64] != 0u8) {
let dm: str = "//ww:module-reset ";
if (!sepwriteall(fd, dm.ptr, dm.len: u64)
|| !sepwriteall(fd, modpath, cstrlen(modpath))
|| !sepwriteall(fd, "\n".ptr, 1u64)) {
cerr("ww: cannot write package unit\n");
return -1;
};
} else {
let d: str = "//ww:module-reset\n";
if (!sepwriteall(fd, d.ptr, d.len: u64)) {
cerr("ww: cannot write package unit\n");
return -1;
};
};
if (!sepwriteall(fd, bufp, blen)
|| !sepwriteall(fd, "\n".ptr, 1u64)) {
cerr("ww: cannot write package unit\n");
return -1;
};
return 0;
};
// Filesystem paths are opaque bytes. Hex encoding keeps their persistent
// identity inside one ignored comment even when a legal name contains '\n'.
fn sepwritehex(fd: i32, value: *u8) bool = {
let digits: str = "0123456789abcdef";
let pair: [2]u8;
let i: u64 = 0u64;
for (value[i] != 0u8) {
let high: i32 = (value[i] / 16u8): i32;
let low: i32 = (value[i] % 16u8): i32;
pair[0] = digits[high];
pair[1] = digits[low];
if (!sepwriteall(fd, pair.ptr, 2u64)) { return false; };
i += 1u64;
};
return true;
};
fn sepwritefilehex(fd: i32, path: *u8) bool = {
let data: *u8;
let n: u64;
data, n = slurp(path);
if (data == nil) { return false; };
let digits: str = "0123456789abcdef";
let pair: [2]u8;
let i: u64 = 0u64;
for (i < n) {
let high: i32 = (data[i] / 16u8): i32;
let low: i32 = (data[i] % 16u8): i32;
pair[0] = digits[high];
pair[1] = digits[low];
if (!sepwriteall(fd, pair.ptr, 2u64)) { return false; };
i += 1u64;
};
return true;
};
// Compose pi's sep-unit from only pi's byte-sorted sources. Direct exports are
// separate compiler inputs; the linker retains the reachable archive closure.
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, unitf: *u8) i32 = {
if (g.pkg[pi].emitcontext < 0
|| g.pkg[pi].emitcontext >= g.ncontext) { return -1; };
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (u < 0) {
cerr("ww: cannot open unit\n");
return -1;
};
let bodyrc: i32 = 0;
if (g.pkg[pi].generatedmain) {
let head: str = "//ww:module-reset ";
let pkg: str = "\npackage main;\n";
if (!sepwriteall(u, head.ptr, head.len: u64)
|| !sepwriteall(u, g.pkg[pi].path, cstrlen(g.pkg[pi].path))
|| !sepwriteall(u, pkg.ptr, pkg.len: u64)) {
bodyrc = -1;
};
let i: i32 = 0;
for (i < g.pkg[pi].ndeps && bodyrc == 0) {
let pre: str = "import ";
let end: str = ";\n";
let dep: *u8 = g.pkg[g.pkg[pi].deps[i]].path;
if (!sepwriteall(u, pre.ptr, pre.len: u64)
|| !sepwriteall(u, dep, cstrlen(dep))
|| !sepwriteall(u, end.ptr, end.len: u64)) {
bodyrc = -1;
};
i += 1;
};
} else { if (g.pkg[pi].isdir != 0) {
let i: i32 = 0;
for (i < g.pkg[pi].nsources && bodyrc == 0) {
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], g.pkg[pi].path);
i += 1;
};
} else {
bodyrc = sepemitbody(u, g.pkg[pi].entry, g.pkg[pi].path);
}; };
let ownsuffix: str = "";
let ownparents: u64 = 0u64;
if (bodyrc == 0 && sepvendorsuffix(g.pkg[pi].path,
&ownsuffix, &ownparents)) {
let pre: str = "//ww:vendor-dir ";
if (!sepwriteall(u, pre.ptr, pre.len: u64)
|| !sepwritehex(u, g.pkg[pi].canon)
|| !sepwriteall(u, "\n".ptr, 1u64)) { bodyrc = -1; };
};
let bi: i32 = 0;
for (bi < g.pkg[pi].bindings.len && bodyrc == 0) {
let b: sepbind = g.pkg[pi].bindings[bi];
if (sepbindfirstmap(g, g.pkg[pi].bindings, bi)) {
let pre: str = "//ww:import-map ";
let space: str = " ";
let newline: str = "\n";
if (!sepwriteall(u, pre.ptr, pre.len: u64)
|| !sepwriteall(u, b.name.ptr, b.name.len: u64)
|| !sepwriteall(u, space.ptr, 1u64)
|| !sepwriteall(u, g.pkg[b.dep].path,
cstrlen(g.pkg[b.dep].path))
|| !sepwriteall(u, space.ptr, 1u64)
|| !sepwritehex(u, g.pkg[b.dep].canon)
|| !sepwriteall(u, newline.ptr, 1u64)) { bodyrc = -1; };
};
bi += 1;
};
// Pin exact sorted direct semantic exports into the source-action voucher.
// A rejected importer may retain its old committed voucher without ever
// accepting it after a dependency export change.
let di: i32 = 0;
for (di < g.pkg[pi].ndeps && bodyrc == 0) {
let dep: i32 = g.pkg[pi].deps[di];
let ifacesuffix: str = ".wwi";
if (g.pkg[dep].sourcestaged) { ifacesuffix = ".wwi.new"; };
let interface: *u8 = sepfname(g, dep, scratch, ifacesuffix);
let pre: str = "//ww:direct-export ";
if (interface == nil
|| !sepwriteall(u, pre.ptr, pre.len: u64)
|| !sepwriteall(u, g.pkg[dep].path,
cstrlen(g.pkg[dep].path))
|| !sepwriteall(u, " ".ptr, 1u64)
|| !sepwritefilehex(u, interface)
|| !sepwriteall(u, "\n".ptr, 1u64)) { bodyrc = -1; };
di += 1;
};
if (os.close(u) != 0) {
cerr("ww: cannot close package unit\n");
return -1;
};
return bodyrc;
};
// Stream one fixed-name SysV ar member. The caller owns the global magic and
// exact member order. A fixed transfer buffer avoids archive-size-dependent
// allocation in both driver stages.
fn archivemember(out: i32, objpath: *u8, member: str) bool = {
if (member.len > 16) { return false; };
let input: i32 = os.open(pathstr(objpath), os.flag.RDONLY, 0i32);
if (input < 0) {
cerrpath("ww: cannot read ", objpath, "\n");
return false;
};
let sr: (i64 | os.oserror) = os.filesize(input);
let objn: i64 = -1i64;
match (sr) {
case let n: i64 => objn = n;
case let e: os.oserror => { os.close(input); return false; };
};
if (objn < 0i64) { os.close(input); return false; };
let objnu: u64 = objn: u64;
let header: [60]u8;
let j: u64 = 0u64;
for (j < 60u64) { header[j] = 32u8; j += 1u64; };
strinto(&header[0], 0u64, member);
header[16] = 48u8;
header[28] = 48u8;
header[34] = 48u8;
strinto(&header[0], 40u64, "100644");
let ndig: u64 = 1u64;
if (objnu != 0u64) {
ndig = 0u64;
let t: u64 = objnu;
for (t > 0u64) { ndig += 1u64; t = t / 10u64; };
};
if (ndig > 10u64) { os.close(input); return false; };
if (objnu == 0u64) {
header[48] = 48u8;
} else {
let d: u64 = ndig;
let t: u64 = objnu;
for (t > 0u64) {
d -= 1u64;
header[48u64 + d] = ((t % 10u64): u8) + 48u8;
t = t / 10u64;
};
};
header[58] = 96u8;
header[59] = 10u8;
let good: bool = sepwriteall(out, &header[0], 60u64);
let buf: [8192]u8;
let remaining: u64 = objnu;
for (good && remaining > 0u64) {
let want: u64 = remaining;
if (want > 8192u64) { want = 8192u64; };
let got: i64 = os.read(input, &buf[0], want);
if (got <= 0i64 || (got: u64) > want
|| !sepwriteall(out, &buf[0], got: u64)) {
good = false;
} else { remaining -= got: u64; };
};
if (os.close(input) != 0) { good = false; };
if (good && (objnu & 1u64) != 0u64) {
let pad: [1]u8 = [10u8];
good = sepwriteall(out, &pad[0], 1u64);
};
return good;
};
// Deterministic package archive: `pkg.o/` and, for an executable or
// generated-test root only, the root-owned `init.o/` dispatcher member.
fn archiveo(objpath: *u8, initpath: *u8, apath: *u8) i32 = {
let fd: i32 = os.open(pathstr(apath),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32);
if (fd < 0) {
cerrpath("ww: cannot open ", apath, "\n");
return -1;
};
let magic: str = "!<arch>\n";
let good: bool = sepwriteall(fd, magic.ptr, magic.len: u64);
if (good) { good = archivemember(fd, objpath, "pkg.o/"); };
if (good && initpath != nil) {
good = archivemember(fd, initpath, "init.o/");
};
let bad: bool = !good;
if (os.close(fd) != 0) { bad = true; };
if (bad) {
cerrpath("ww: cannot write archive ", apath, "\n");
return -1;
};
return 0;
};
fn runassembler(tool: *u8, output: *u8, input: *u8) i32 = {
let argv: []str = ["w6a", "-o", pathstr(output), pathstr(input)];
let env: []str = os.getenvs();
let result: exec.result;
exec.runstdio(pathstr(tool), argv, env, &result);
if (result.termination == exec.termination.EXIT && result.code == 0) {
return 0;
};
if (result.termination == exec.termination.ERROR && result.code == 127) {
cerr("ww: execve failed\n");
};
return -1;
};
// buildonesep — discover deps, reverse-topo,
// the dependency-first producer loop (one `w6c -c -I` per package,
// each package `.o` wrapped in its own deterministic per-package `.a`), then a
// reverse-topo `w6l` of the root `.a` + reachable `.a` set + libwwrt.a.
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
// build_one_sep.
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree
// that replaces the fresh `.sepwork` scratch. Staleness is pure content
// identity, never mtime: a package is reused only when its freshly composed
// unit byte-equals the committed unit, no direct dependency emitted a changed
// export, AND the driver/tool copies recorded in the dir byte-equal the live
// executables — every decision is
// reproducible by hand with cmp(1) against plain files. Artifacts, units, tool
// records, stamp, products, and statuses stage together and publish through one
// rollback-capable request transaction, so a killed or rejected build cannot
// expose a mixed generation. The caller serializes invocations per workdir and
// `make clean` reclaims the state. Cstage twin: cmd/ww/main.c
// file_equal/workdir_stamp_text/transaction group.
// `.s`/`.wwi` may be legitimately empty (an FFI-only package like rt
// emits no text), so committed presence is their freshness test; the
// rename-commit protocol owns integrity. `.o`/`.a` are never empty
// (ELF/ar headers), so a zero size there is always a torn write.
fn fileisreg(path: *u8) bool = {
let fi: os.filestat;
let ok: bool = false;
match (os.lstat(&fi, pathstr(path))) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.REG: u32) { ok = true; };
};
case let e: os.oserror => void;
};
return ok;
};
fn filesizenonzero(path: *u8) bool = {
let fi: os.filestat;
let ok: bool = false;
match (os.lstat(&fi, pathstr(path))) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.REG: u32) {
if (fi.sz > 0u64) { ok = true; };
};
};
case let e: os.oserror => void;
};
return ok;
};
// 1 means occupied by any terminal directory entry (including a dangling
// symlink), 0 means ENOENT, and -1 is another lookup failure. Staging and
// rollback paths fail closed on every nonzero result.
fn pathexistsnofollow(path: *u8) i32 = {
let fi: os.filestat;
match (os.lstat(&fi, pathstr(path))) {
case void => return 1;
case let e: os.oserror => {
if ((e: i64) == -2i64) { return 0; };
return -1;
};
};
return -1;
};
fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = {
let unit: *u8 = sepfname(g, pi, scratch, ".unit.ww");
if (unit == nil) { return -1; };
let fi: os.filestat;
match (os.lstat(&fi, pathstr(unit))) {
case let e: os.oserror => {
if ((e: i64) == -2i64) { return 0; };
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
case void => void;
};
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ != os.mode.REG: u32) {
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
let buf: *u8;
let n: u64;
buf, n = slurp(unit);
let prefix: str = "//ww:module-reset";
let pathn: u64 = cstrlen(g.pkg[pi].path);
let want: u64 = prefix.len: u64 + 1u64;
if (pathn > 0u64) { want += 1u64 + pathn; };
let matches: bool = buf != nil && n >= want
&& bytecmp(buf, prefix.len: u64, prefix.ptr,
prefix.len: u64) == 0;
if (matches) {
let off: u64 = prefix.len: u64;
if (pathn == 0u64) {
matches = buf[off] == ('\n': u8);
} else {
matches = buf[off] == (' ': u8)
&& bytecmp(buf + off + 1u64, pathn,
g.pkg[pi].path, pathn) == 0
&& buf[off + 1u64 + pathn] == ('\n': u8);
};
};
if (matches) { return 1; };
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (pathn == 0u64) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = {
let i: i32 = 0;
for (i < g.n) {
if (!g.pkg[i].failed && g.pkg[i].loaded && g.pkg[i].action
&& sepvalidateunitowner(g, i, scratch) < 0) { return -1; };
i += 1;
};
return 0;
};
// Byte equality of two files; absence or IO error is inequality.
fn fileequal(a: *u8, b: *u8) bool = {
let fa: i32 = os.open(pathstr(a), os.flag.RDONLY, 0i32);
if (fa < 0) { return false; };
let fb: i32 = os.open(pathstr(b), os.flag.RDONLY, 0i32);
if (fb < 0) { os.close(fa); return false; };
let bufa: [8192]u8;
let bufb: [8192]u8;
let eq: bool = true;
let done: bool = false;
for (!done) {
let na: i64 = os.read(fa, &bufa[0], 8192u64);
let nb: i64 = os.read(fb, &bufb[0], 8192u64);
if (na < 0 || na != nb) { eq = false; done = true; }
else { if (na == 0) { done = true; }
else {
let k: u64 = 0u64;
for (k < (na: u64)) {
if (bufa[k] != bufb[k]) {
eq = false; done = true; k = (na: u64);
};
k += 1u64;
};
}; };
};
os.close(fa);
os.close(fb);
return eq;
};
fn copyfilestage(src: *u8, dst: *u8) i32 = {
let in: i32 = os.open(pathstr(src), os.flag.RDONLY, 0i32);
if (in < 0) { return -1; };
// BuildInstallFunc gives non-link outputs base mode 0o666; the fresh
// publication stage applies the invoking process's umask at creation.
let out: i32 = os.open(pathstr(dst),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 438i32);
if (out < 0) { os.close(in); return -1; };
let buf: [65536]u8;
let bad: bool = false;
for (!bad) {
let n: i64 = os.read(in, &buf[0], 65536u64);
if (n < 0) { bad = true; break; };
if (n == 0) { break; };
match (os.writeall(out, &buf[0], n: u64)) {
case let wrote: i64 => { if (wrote != n) { bad = true; }; };
case let e: os.oserror => bad = true;
};
};
if (os.close(in) != 0) { bad = true; };
if (os.close(out) != 0) { bad = true; };
if (bad) { os.remove(pathstr(dst)); return -1; };
return 0;
};
// BuildInstallFunc's linked-executable mode is 0777 filtered by umask. The
// retained stage owns a distinct inode but exactly the temporary runnable's
// bytes; the request transaction publishes them together below.
fn copyexecutablestage(src: *u8, dst: *u8) i32 = {
let in: i32 = os.open(pathstr(src), os.flag.RDONLY, 0i32);
if (in < 0) { return -1; };
let out: i32 = os.open(pathstr(dst),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 511i32);
if (out < 0) { os.close(in); return -1; };
let buf: [65536]u8;
let bad: bool = false;
for (!bad) {
let n: i64 = os.read(in, &buf[0], 65536u64);
if (n < 0) { bad = true; break; };
if (n == 0) { break; };
match (os.writeall(out, &buf[0], n: u64)) {
case let wrote: i64 => { if (wrote != n) { bad = true; }; };
case let e: os.oserror => bad = true;
};
};
if (os.close(in) != 0) { bad = true; };
if (os.close(out) != 0) { bad = true; };
if (bad) { os.remove(pathstr(dst)); return -1; };
return 0;
};
fn sepproductstagepath(dst: *u8) *u8 = {
let path: *u8 = sepappendlit(dst, ".new");
if (path != nil && cstrlen(path) + 1u64 > os.PATH_MAX: u64) {
cerr("ww: product staging path is too long\n");
return nil;
};
return path;
};
fn sepprepareproductstage(current: *u8, dst: *u8) *u8 = {
let stage: *u8 = current;
if (stage == nil) { stage = sepproductstagepath(dst); };
if (stage == nil) { return nil; };
if (pathexistsnofollow(stage) != 0) {
cerrpath("ww: product staging path already exists: ",
stage, "\n");
return nil;
};
return stage;
};
fn sepproductpathsoverlap(a: *u8, b: *u8) bool = {
if (a == nil || b == nil) { return false; };
if (cstreq(a, b)) { return true; };
let an: u64 = cstrlen(a);
let bn: u64 = cstrlen(b);
return (an == bn + 4u64 && bytecmp(a, bn, ".new".ptr, 4u64) == 0)
|| (bn == an + 4u64 && bytecmp(b, an, ".new".ptr, 4u64) == 0);
};
fn sepvalidateproductpathpair(a: *sepproduct, b: *sepproduct) i32 = {
let ap: []*u8 = [nil, nil, nil, a.stagestatus, a.stageout,
a.stagepublish, a.stageiface];
let bp: []*u8 = [nil, nil, nil, b.stagestatus, b.stageout,
b.stagepublish, b.stageiface];
if (a.stagestatus != nil) { ap[0] = a.status; };
if (a.stageout != nil) { ap[1] = a.out; };
if (a.stagepublish != nil) { ap[2] = a.publish; };
if (b.stagestatus != nil) { bp[0] = b.status; };
if (b.stageout != nil) { bp[1] = b.out; };
if (b.stagepublish != nil) { bp[2] = b.publish; };
let i: i32 = 0;
for (i < ap.len) {
if (ap[i] != nil) {
let j: i32 = 0;
for (j < bp.len) {
if (sepproductpathsoverlap(ap[i], bp[j])) {
cerrpath("ww: product path collision: ", bp[j], "\n");
return -1;
};
j += 1;
};
};
i += 1;
};
return 0;
};
fn sepwritetextstage(path: *u8, body: str) i32 = {
let fd: i32 = os.open(pathstr(path),
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32);
if (fd < 0) { return -1; };
let bad: bool = false;
match (os.writeall(fd, body.ptr, body.len: u64)) {
case let n: i64 => { if (n != body.len: i64) { bad = true; }; };
case let e: os.oserror => { bad = true; };
};
if (os.close(fd) != 0) { bad = true; };
if (bad) { os.remove(pathstr(path)); return -1; };
return 0;
};
fn sepstageproductstatus(product: *sepproduct) i32 = {
if (product.status == nil) { return 0; };
product.stagestatus = sepprepareproductstage(product.stagestatus,
product.status);
if (product.stagestatus == nil) { return -1; };
return sepwritetextstage(product.stagestatus, "ok\n");
};
// Fail closed on every coordinator-owned staging name before scratch or tool
// acquisition. lstat keeps dangling symlinks occupied rather than following
// them through a later truncate.
fn sepvalidaterequeststaging(g: *sepgraph, scratch: *u8, warm: bool,
products: *sepproduct, nproducts: i32, rootpackage: bool,
publishpackage: i32, emitasm: i32, istest: i32) i32 = {
if (warm) {
let suffix: []str = [".unit.new", ".wwi.new", ".s.new", ".o.new",
".a.new", ".init.unit.new", ".init.s.new", ".init.o.new"];
let pi: i32 = 0;
for (pi < g.n) {
if (!g.pkg[pi].failed && g.pkg[pi].loaded && g.pkg[pi].action) {
let si: i32 = 0;
for (si < suffix.len) {
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
if (path == nil) { return -1; };
if (pathexistsnofollow(path) != 0) {
cerrpath("ww: package staging path already exists: ",
path, "\n");
return -1;
};
si += 1;
};
};
pi += 1;
};
let toolsuffix: []str = [".wwtool.ww.new", ".wwtool.w6c.new",
".wwtool.w6a.new", ".wwtool.stamp.new"];
let ti: i32 = 0;
for (ti < toolsuffix.len) {
let path: *u8 = sepjoinpathlit(scratch, toolsuffix[ti]);
if (path == nil) { return -1; };
if (pathexistsnofollow(path) != 0) {
cerrpath("ww: tool staging path already exists: ", path,
"\n");
return -1;
};
ti += 1;
};
};
let i: i32 = 0;
for (i < nproducts) {
if (products[i].status != nil) {
products[i].stagestatus = sepprepareproductstage(
products[i].stagestatus, products[i].status);
if (products[i].stagestatus == nil) { return -1; };
};
if (products[i].publish != nil && !products[i].notests) {
products[i].stagepublish = sepprepareproductstage(
products[i].stagepublish, products[i].publish);
if (products[i].stagepublish == nil) { return -1; };
};
if (emitasm == 0) {
let ownsoutput: bool = false;
if (rootpackage) { ownsoutput = publishpackage != 0; }
else { if (istest != 0) { ownsoutput = !products[i].notests; }
else { ownsoutput = seprootiscommand(
&g.pkg[products[i].root]); }; };
if (ownsoutput) {
products[i].stageout = sepprepareproductstage(
products[i].stageout, products[i].out);
if (products[i].stageout == nil) { return -1; };
if (rootpackage) {
let iface: *u8 = sepappendlit(products[i].out, ".wwi");
if (iface == nil) { return -1; };
products[i].stageiface = sepprepareproductstage(
products[i].stageiface, iface);
if (products[i].stageiface == nil) { return -1; };
};
};
};
i += 1;
};
i = 0;
for (i < nproducts) {
let j: i32 = 0;
for (j < i) {
if (sepvalidateproductpathpair(&products[j],
&products[i]) < 0) { return -1; };
j += 1;
};
i += 1;
};
return 0;
};
type septxnentry = struct {
stage: *u8,
dst: *u8,
backup: *u8,
hadold: bool,
installed: bool,
publicoutput: bool,
};
fn sepalloctxnentries(cap: i32) ([]septxnentry | nomem) = {
let value: []septxnentry = alloc([], cap: u64)?;
return value;
};
fn sepreservetxnentries(entries: *[]septxnentry, used: i32,
need: i32) bool = {
if (need <= entries.len) { return true; };
let cap: i32 = sepgrowcap(entries.len, need);
if (cap < 0) { return false; };
let allocation: ([]septxnentry | nomem) = sepalloctxnentries(cap);
let next: []septxnentry;
match (allocation) {
case let value: []septxnentry => next = value;
case nomem => { sepfailnomem(); return false; };
};
next.len = cap;
let i: i32 = 0;
for (i < used) { next[i] = (*entries)[i]; i += 1; };
if (entries.ptr != nil) {
os.free(entries.ptr: *void,
(entries.cap: u64) * (size(septxnentry): u64));
};
*entries = next;
return true;
};
fn septxnbackup(dst: *u8) *u8 = {
let dn: u64 = cstrlen(dst);
let pid: i32 = os.getpid();
let v: i32 = pid;
if (v < 0) { v = -v; };
let digits: i32 = 1;
let q: i32 = v;
for (q >= 10) { digits += 1; q = q / 10; };
let suffix: str = ".wwtxn.";
let tail: str = ".old";
let need: u64 = dn;
if (!sepaddbytes(&need, suffix.len: u64)
|| !sepaddbytes(&need, digits: u64)
|| !sepaddbytes(&need, tail.len: u64)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let allocation: ([]u8 | nomem) = sepallocbytes(need: i32);
let buf: []u8;
match (allocation) {
case let value: []u8 => buf = value;
case nomem => { sepfailnomem(); return nil; };
};
let off: u64 = 0u64;
let i: u64 = 0u64;
for (i < dn) { buf[off] = dst[i]; off += 1u64; i += 1u64; };
i = 0u64;
for (i < suffix.len: u64) {
buf[off] = suffix.ptr[i]; off += 1u64; i += 1u64;
};
let rev: [16]u8;
let n: i32 = 0;
if (v == 0) { rev[0] = '0'; n = 1; }
else { for (v > 0) {
rev[n] = ((v % 10) + 48): u8; n += 1; v = v / 10;
}; };
let ri: i32 = n - 1;
for (ri >= 0) { buf[off] = rev[ri]; off += 1u64; ri -= 1; };
i = 0u64;
for (i < tail.len: u64) {
buf[off] = tail.ptr[i]; off += 1u64; i += 1u64;
};
buf[off] = 0u8;
return buf.ptr;
};
fn septxnaddmode(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8, publicoutput: bool) bool = {
if (cstreq(stage, dst)) {
cerrpath("ww: transaction path collision: ", dst, "\n");
return false;
};
let i: i32 = 0;
for (i < *n) {
if (cstreq((*entries)[i].dst, dst)
|| cstreq((*entries)[i].stage, stage)
|| cstreq((*entries)[i].dst, stage)
|| cstreq((*entries)[i].stage, dst)) {
cerrpath("ww: transaction path collision: ", dst, "\n");
return false;
};
i += 1;
};
if (*n == SEP_COUNT_MAX
|| !sepreservetxnentries(entries, *n, *n + 1)) { return false; };
let stagecopy: *u8 = sepdupcstr(stage, cstrlen(stage));
let dstcopy: *u8 = sepdupcstr(dst, cstrlen(dst));
let backup: *u8 = septxnbackup(dst);
if (stagecopy == nil || dstcopy == nil || backup == nil) {
if (stagecopy != nil) {
os.free(stagecopy: *void, cstrlen(stagecopy) + 1u64);
};
if (dstcopy != nil) {
os.free(dstcopy: *void, cstrlen(dstcopy) + 1u64);
};
if (backup != nil) {
os.free(backup: *void, cstrlen(backup) + 1u64);
};
return false;
};
if (cstrlen(backup) + 1u64 > os.PATH_MAX: u64) {
cerr("ww: transaction path is too long\n");
os.free(stagecopy: *void, cstrlen(stagecopy) + 1u64);
os.free(dstcopy: *void, cstrlen(dstcopy) + 1u64);
os.free(backup: *void, cstrlen(backup) + 1u64);
return false;
};
(*entries)[*n].stage = stagecopy;
(*entries)[*n].dst = dstcopy;
(*entries)[*n].backup = backup;
(*entries)[*n].hadold = false;
(*entries)[*n].installed = false;
(*entries)[*n].publicoutput = publicoutput;
*n += 1;
return true;
};
fn septxnadd(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8) bool = {
return septxnaddmode(entries, n, stage, dst, false);
};
fn septxnaddpublic(entries: *[]septxnentry, n: *i32,
stage: *u8, dst: *u8) bool = {
return septxnaddmode(entries, n, stage, dst, true);
};
fn septxnaddpkgsuffix(entries: *[]septxnentry, n: *i32,
g: *sepgraph, pi: i32, scratch: *u8,
stagesuffix: str, dstsuffix: str) bool = {
let stage: *u8 = sepfname(g, pi, scratch, stagesuffix);
let dst: *u8 = sepfname(g, pi, scratch, dstsuffix);
if (stage == nil || dst == nil) { return false; };
return septxnadd(entries, n, stage, dst);
};
fn septxndiscard(entries: []septxnentry, n: i32) void = {
let i: i32 = 0;
for (i < n) { os.remove(pathstr(entries[i].stage)); i += 1; };
};
fn sepoutputprefix(buf: *u8, n: i64, magic: str) bool = {
if (n < 0i64 || n < magic.len: i64) { return false; };
let i: i32 = 0;
for (i < magic.len) {
if (buf[i] != magic.ptr[i]) { return false; };
i += 1;
};
return true;
};
fn sepoutput2(buf: *u8, n: i64, a: u8, b: u8) bool = {
return n >= 2i64 && buf[0u64] == a && buf[1u64] == b;
};
fn sepoutput4(buf: *u8, n: i64, a: u8, b: u8, c: u8, d: u8) bool = {
return n >= 4i64 && buf[0u64] == a && buf[1u64] == b
&& buf[2u64] == c && buf[3u64] == d;
};
fn sepoutput6(buf: *u8, n: i64, a: u8, b: u8, c: u8, d: u8,
e: u8, f: u8) bool = {
return n >= 6i64 && buf[0u64] == a && buf[1u64] == b
&& buf[2u64] == c && buf[3u64] == d
&& buf[4u64] == e && buf[5u64] == f;
};
// Go 1.26.5 work.objectMagic, plus WW's compiler-owned interface prefix.
fn sepisobjectoutput(path: *u8) bool = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return false; };
let buf: [64]u8;
let got: u64 = 0u64;
let bad: bool = false;
for (got < 64u64) {
let n: i64 = os.read(fd, &buf[0] + got, 64u64 - got);
if (n > 0) { got += n: u64; }
else if (n == 0) { break; }
else if (n != -4i64) { bad = true; break; };
};
os.close(fd);
if (bad) { return false; };
let n: i64 = got: i64;
if (sepoutputprefix(&buf[0], n, "!<arch>\n")
|| sepoutputprefix(&buf[0], n, "<bigaf>\n")
|| sepoutput4(&buf[0], n, 127u8, 69u8, 76u8, 70u8)
|| sepoutput4(&buf[0], n, 254u8, 237u8, 250u8, 206u8)
|| sepoutput4(&buf[0], n, 254u8, 237u8, 250u8, 207u8)
|| sepoutput4(&buf[0], n, 206u8, 250u8, 237u8, 254u8)
|| sepoutput4(&buf[0], n, 207u8, 250u8, 237u8, 254u8)
|| sepoutput6(&buf[0], n, 77u8, 90u8, 144u8, 0u8, 3u8, 0u8)
|| sepoutput6(&buf[0], n, 77u8, 90u8, 120u8, 0u8, 1u8, 0u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 1u8, 235u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 138u8, 151u8)
|| sepoutput4(&buf[0], n, 0u8, 0u8, 6u8, 71u8)
|| sepoutput4(&buf[0], n, 0u8, 97u8, 115u8, 109u8)
|| sepoutput2(&buf[0], n, 1u8, 223u8)
|| sepoutput2(&buf[0], n, 1u8, 247u8)
|| sepoutputprefix(&buf[0], n, "//ww:module ")) {
return true;
};
return false;
};
fn sepcheckdstoverwrite(dst: *u8) bool = {
let fi: os.filestat;
match (os.stat(&fi, pathstr(dst))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ == os.mode.DIR: u32) {
cerr("ww: build output "); sepputquoted(dst);
cerr(" already exists and is a directory\n");
return false;
};
if (typ == os.mode.REG: u32 && fi.sz != 0u64
&& !sepisobjectoutput(dst)) {
cerr("ww: build output "); sepputquoted(dst);
cerr(" already exists and is not an object file\n");
return false;
};
};
case let e: os.oserror => void;
};
return true;
};
fn septxncommit(entries: []septxnentry, n: i32) bool = {
let i: i32 = 0;
let valid: bool = true;
for (i < n) {
if (!fileisreg(entries[i].stage)) {
cerrpath("ww: transaction stage is not a regular file: ",
entries[i].stage, "\n");
valid = false;
break;
};
i += 1;
};
if (valid) {
i = 0;
for (i < n) {
if (entries[i].publicoutput
&& !sepcheckdstoverwrite(entries[i].dst)) {
valid = false;
break;
};
i += 1;
};
};
if (valid) {
i = 0;
for (i < n) {
if (pathexistsnofollow(entries[i].backup) != 0) {
cerrpath("ww: transaction backup already exists: ",
entries[i].backup, "\n");
break;
};
i += 1;
};
};
if (i == n) {
i = 0;
for (i < n) {
let rr: i32 = os.rename(pathstr(entries[i].dst),
pathstr(entries[i].backup));
if (rr == 0) { entries[i].hadold = true; }
else { if (rr != -2) {
cerrpath("ww: cannot preserve transaction destination ",
entries[i].dst, "\n");
break;
}; };
i += 1;
};
};
if (i == n) {
i = 0;
for (i < n) {
if (os.rename(pathstr(entries[i].stage),
pathstr(entries[i].dst)) != 0) {
cerrpath("ww: cannot install transaction destination ",
entries[i].dst, "\n");
break;
};
entries[i].installed = true;
i += 1;
};
if (i == n) {
i = 0;
for (i < n) {
if (entries[i].hadold
&& os.remove(pathstr(entries[i].backup)) != 0) {
cerrpath("ww: cannot remove transaction backup ",
entries[i].backup, "\n");
};
i += 1;
};
return true;
};
};
i = n - 1;
for (i >= 0) {
if (entries[i].installed) {
let rr: i32 = os.remove(pathstr(entries[i].dst));
if (rr != 0 && rr != -2) {
cerrpath("ww: cannot roll back ", entries[i].dst, "\n");
};
};
if (entries[i].hadold) {
if (os.rename(pathstr(entries[i].backup),
pathstr(entries[i].dst)) != 0) {
cerrpath("ww: cannot restore ", entries[i].dst, "\n");
};
};
os.remove(pathstr(entries[i].stage));
i -= 1;
};
return false;
};
// Package publication writes OUT, OUT.new, OUT.wwi, and OUT.wwi.new. Check
// the longest spelling before any producer tool can run.
fn validatepackageoutputpath(out: *u8) i32 = {
let suffix: u64 = ".wwi.wwtxn.9223372036854775807.old".len: u64
+ 1u64;
let limit: u64 = os.PATH_MAX: u64;
if (suffix > limit || cstrlen(out) > limit - suffix) {
cerr("ww: package output path is too long\n");
return -1;
};
return 0;
};
fn validatecommandoutputpath(out: *u8) i32 = {
let suffix: u64 = ".wwtxn.9223372036854775807.old".len: u64 + 1u64;
if (out == nil || suffix > os.PATH_MAX: u64
|| cstrlen(out) > os.PATH_MAX: u64 - suffix) {
cerr("ww: command output path is too long\n");
return -1;
};
return 0;
};
// The stamp pins the non-content build inputs a unit compare cannot see:
// the -T/-S/root-action shape of the producer pass and the artifact protocol
// revision (bump "fmt" when the unit/archive/commit format changes).
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
if (emitasm != 0) {
return "ww workdir fmt 19 mode test asm 1\n";
};
return "ww workdir fmt 19 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 18 mode build asm 1\n";
};
return "ww workdir fmt 18 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return false; };
let buf: []u8;
if (!sepmakebytes(128u64, &buf)) { os.close(fd); return false; };
let n: i64 = os.read(fd, buf.ptr, 127u64);
os.close(fd);
if (n < 0) { return false; };
if ((n: i32) != want.len) { return false; };
let k: u64 = 0u64;
let eq: bool = true;
for (k < (n: u64)) {
if (buf[k] != want.ptr[k]) { eq = false; k = (n: u64); };
k += 1u64;
};
return eq;
};
fn sepdiscardactionstaging(warm: bool, unit: *u8, wwi: *u8,
assembly: *u8, object: *u8, archive: *u8) i32 = {
let ignoredwarm: bool = warm;
let paths: []*u8 = [unit, wwi, assembly, object, archive];
let i: i32 = 0;
for (i < paths.len) {
let rr: i32 = os.remove(pathstr(paths[i]));
if (rr != 0 && rr != -2) {
cerr("ww: cannot remove staged package artifacts\n");
return -1;
};
i += 1;
};
return 0;
};
fn sepdiscardinitstaging(warm: bool, unit: *u8, assembly: *u8,
object: *u8) i32 = {
let ignoredwarm: bool = warm;
let paths: []*u8 = [unit, assembly, object];
let i: i32 = 0;
for (i < paths.len) {
if (paths[i] != nil && paths[i][0u64] != 0u8) {
let rr: i32 = os.remove(pathstr(paths[i]));
if (rr != 0 && rr != -2) {
cerr("ww: cannot remove staged initialization artifacts\n");
return -1;
};
};
i += 1;
};
return 0;
};
fn sepdiscardrequeststaging(g: *sepgraph, scratch: *u8, warm: bool,
products: *sepproduct, nproducts: i32) i32 = {
let warmsuffix: []str = [".unit.new", ".wwi.new", ".s.new", ".o.new",
".a.new", ".init.unit.new", ".init.s.new", ".init.o.new"];
let coldsuffix: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
".init.unit.ww", ".init.s", ".init.o"];
let suffix: []str = coldsuffix;
if (warm) { suffix = warmsuffix; };
let rc: i32 = 0;
let pi: i32 = 0;
for (pi < g.n) {
if (!g.pkg[pi].action) { pi += 1; continue; };
let si: i32 = 0;
for (si < suffix.len) {
let path: *u8 = sepfname(g, pi, scratch, suffix[si]);
if (path == nil) { rc = -1; }
else {
let rr: i32 = os.remove(pathstr(path));
if (rr != 0 && rr != -2) { rc = -1; };
};
si += 1;
};
pi += 1;
};
let producti: i32 = 0;
for (producti < nproducts) {
let paths: []*u8 = [products[producti].stageout,
products[producti].stagepublish, products[producti].stageiface,
products[producti].stagestatus];
let si: i32 = 0;
for (si < paths.len) {
if (paths[si] != nil) {
let rr: i32 = os.remove(pathstr(paths[si]));
if (rr != 0 && rr != -2) { rc = -1; };
};
si += 1;
};
producti += 1;
};
if (warm) {
let toolsuffix: []str = [".wwtool.ww.new", ".wwtool.w6c.new",
".wwtool.w6a.new", ".wwtool.stamp.new"];
let ti: i32 = 0;
for (ti < toolsuffix.len) {
let path: *u8 = sepjoinpathlit(scratch, toolsuffix[ti]);
if (path == nil) { rc = -1; }
else {
let rr: i32 = os.remove(pathstr(path));
if (rr != 0 && rr != -2) { rc = -1; };
};
ti += 1;
};
};
if (rc != 0) { cerr("ww: cannot discard rejected request staging\n"); };
return rc;
};
type sepcreateddirs = struct {
path: [4096]u8,
offset: [2048]u16,
n: i32,
};
fn seprollbackdirs(created: *sepcreateddirs) void = {
for (created.n > 0) {
created.n -= 1;
let at: u64 = created.offset[created.n]: u64;
let saved: u8 = created.path[at];
created.path[at] = 0u8;
let ignored: i32 = os.rmdir(pathstr(&created.path[0]));
created.path[at] = saved;
};
};
// Keep the prefixes created after semantic preflight separately from
// pre-existing caller state so a later setup failure can reclaim only them.
fn sepmkdirsrecord(path: *u8, mode: i32, created: *sepcreateddirs) i32 = {
created.n = 0;
let n: u64 = cstrlen(path);
if (n == 0u64 || n >= os.PATH_MAX: u64) { return -1; };
let buf: [4096]u8;
bytecpy(&buf[0], path, n + 1u64);
for (n > 1u64 && buf[n - 1u64] == 47u8) {
n -= 1u64;
buf[n] = 0u8;
};
bytecpy(&created.path[0], &buf[0], n + 1u64);
let i: u64 = 0u64;
if (buf[0] == 47u8) { i = 1u64; };
for (i <= n) {
if (buf[i] != 47u8 && buf[i] != 0u8) {
i += 1u64;
continue;
};
let saved: u8 = buf[i];
buf[i] = 0u8;
if (buf[0] != 0u8) {
let fi: os.filestat;
let missing: bool = false;
match (os.stat(&fi, pathstr(&buf[0]))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ != os.mode.DIR: u32) {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
};
case let e: os.oserror => {
if ((e: i64) == -2i64) { missing = true; }
else {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
};
};
if (missing) {
if (created.n >= 2048
|| os.mkdir(pathstr(&buf[0]), mode) != 0) {
buf[i] = saved;
seprollbackdirs(created);
return -1;
};
created.offset[created.n] = i: u16;
created.n += 1;
};
};
buf[i] = saved;
if (saved == 0u8) { break; };
i += 1u64;
};
return 0;
};
// cerrpath — the "ww: <head><path>\n" diagnostic shape shared by the
// workdir error sites; byte-identical wording to the cstage twin's
// fprintf(..., "%s", path) forms.
fn cerrpath(head: str, path: *u8, tail: str) void = {
cerr(head);
os.write(2, path, cstrlen(path));
cerr(tail);
};
fn septxnrelease(entries: *[]septxnentry, n: i32) void = {
let i: i32 = 0;
for (i < n) {
if ((*entries)[i].stage != nil) {
os.free((*entries)[i].stage: *void,
cstrlen((*entries)[i].stage) + 1u64);
};
if ((*entries)[i].dst != nil) {
os.free((*entries)[i].dst: *void,
cstrlen((*entries)[i].dst) + 1u64);
};
if ((*entries)[i].backup != nil) {
os.free((*entries)[i].backup: *void,
cstrlen((*entries)[i].backup) + 1u64);
};
i += 1;
};
if (entries.ptr != nil) {
os.free(entries.ptr: *void,
(entries.cap: u64) * (size(septxnentry): u64));
};
entries.ptr = nil;
entries.len = 0;
entries.cap = 0;
};
// Running retained tests enter this Go-like install action only after their
// request-private executable has returned successfully.
fn sepinstalltestoutput(stage: *u8, dst: *u8) i32 = {
let installstage: *u8 = sepappendlit(stage, ".install");
if (installstage == nil) { return 1; };
if (cstrlen(installstage) + 1u64 > os.PATH_MAX: u64
|| pathexistsnofollow(installstage) != 0
|| copyexecutablestage(stage, installstage) != 0) {
cerr("ww: cannot stage retained test output\n");
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
let parent: *u8 = seplexicalparent(dst);
if (parent == nil) {
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
let created: sepcreateddirs;
created.n = 0;
if (sepmkdirsrecord(parent, 511, &created) != 0) {
cerrpath("ww: cannot create test output directory ", parent, "\n");
os.free(parent: *void, cstrlen(parent) + 1u64);
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
os.free(parent: *void, cstrlen(parent) + 1u64);
let entries: []septxnentry;
let n: i32 = 0;
if (!septxnaddpublic(&entries, &n, installstage, dst)
|| !septxncommit(entries, n)) {
septxndiscard(entries, n);
septxnrelease(&entries, n);
seprollbackdirs(&created);
os.remove(pathstr(installstage));
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 1;
};
septxnrelease(&entries, n);
os.free(installstage: *void, cstrlen(installstage) + 1u64);
return 0;
};
fn sepfinishfail(entries: *[]septxnentry, n: i32) i32 = {
septxndiscard(*entries, n);
septxnrelease(entries, n);
return 1;
};
fn sepfreeproductstaging(products: *sepproduct, nproducts: i32) void = {
let i: i32 = 0;
for (i < nproducts) {
let paths: []*u8 = [products[i].stageout, products[i].stagepublish,
products[i].stageiface, products[i].stagestatus];
let k: i32 = 0;
for (k < paths.len) {
if (paths[k] != nil) {
os.free(paths[k]: *void, cstrlen(paths[k]) + 1u64);
};
k += 1;
};
products[i].stageout = nil;
products[i].stagepublish = nil;
products[i].stageiface = nil;
products[i].stagestatus = nil;
i += 1;
};
};
fn seprejectrequest(g: *sepgraph, scratch: *u8, warm: bool,
products: *sepproduct, nproducts: i32,
createdwork: *sepcreateddirs, createdoutput: *sepcreateddirs,
scratchout: **u8) i32 = {
sepdiscardrequeststaging(g, scratch, warm, products, nproducts);
sepfreeproductstaging(products, nproducts);
if (!warm) {
let rr: i32 = os.rmdir(pathstr(scratch));
if (rr != 0 && rr != -2) {
cerrpath("ww: cannot remove rejected scratch ", scratch, "\n");
} else { if (scratchout != nil) { *scratchout = nil; }; };
} else {
seprollbackdirs(createdwork);
};
seprollbackdirs(createdoutput);
return 1;
};
// Finish all products before opening one request-wide rollback group. Package
// actions remain in `.new`; caller-visible outputs and statuses are adjacent
// `.new` files so final rename never crosses filesystems.
fn sepfinishrequest(selfdir: *u8, l6: *u8, c6: *u8, a6: *u8,
g: *sepgraph, scratch: *u8, warm: bool, rootpackage: bool,
publishpackage: i32, istest: i32, emitasm: i32,
products: *sepproduct, nproducts: i32,
order: []i32, norder: i32, rtpaths: []*u8, nrt: i32, lf: *lflags,
toolw: *u8, toolc: *u8, toola: *u8, stampf: *u8,
stampwant: str, stampok: bool) i32 = {
let entries: []septxnentry;
entries.ptr = nil;
entries.len = 0;
entries.cap = 0;
let ntxn: i32 = 0;
let producti: i32 = 0;
if (emitasm != 0) {
for (producti < nproducts) {
if (sepstageproductstatus(&products[producti]) != 0) {
cerr("ww: cannot stage package-build product\n");
return sepfinishfail(&entries, ntxn);
};
producti += 1;
};
} else { if (rootpackage) {
let root: i32 = products[0].root;
if (publishpackage != 0) {
let asuffix: str = ".a";
let isuffix: str = ".wwi";
if (warm && g.pkg[root].archivestaged) { asuffix = ".a.new"; };
if (warm && g.pkg[root].sourcestaged) { isuffix = ".wwi.new"; };
let archive: *u8 = sepfname(g, root, scratch, asuffix);
let iface: *u8 = sepfname(g, root, scratch, isuffix);
let outiface: *u8 = sepappendlit(products[0].out, ".wwi");
if (archive == nil || iface == nil || outiface == nil) {
cerrpath("ww: cannot stage package artifact ",
products[0].out, "\n");
return sepfinishfail(&entries, ntxn);
};
products[0].stageout = sepprepareproductstage(
products[0].stageout, products[0].out);
products[0].stageiface = sepprepareproductstage(
products[0].stageiface, outiface);
if (products[0].stageout == nil
|| products[0].stageiface == nil
|| copyfilestage(archive, products[0].stageout) != 0
|| copyfilestage(iface, products[0].stageiface) != 0) {
cerrpath("ww: cannot stage package artifact ",
products[0].out, "\n");
return sepfinishfail(&entries, ntxn);
};
};
if (sepstageproductstatus(&products[0]) != 0) {
cerr("ww: cannot stage package-build product\n");
return sepfinishfail(&entries, ntxn);
};
} else {
let nldirs: i32 = 0;
let nllibs: i32 = 0;
let ldirs: **u8 = nil;
let llibs: **u8 = nil;
if (lf != nil) {
nldirs = lf.nlibdirs;
nllibs = lf.nlibs;
ldirs = lf.libdirs;
llibs = lf.libs;
};
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (istest != 0 && products[producti].notests) {
if (sepstageproductstatus(&products[producti]) != 0) {
cerr("ww: cannot stage package-test product\n");
return sepfinishfail(&entries, ntxn);
};
producti += 1;
continue;
};
if (istest == 0 && !seprootiscommand(&g.pkg[root])) {
if (sepstageproductstatus(&products[producti]) != 0) {
cerr("ww: cannot stage package-build product\n");
return sepfinishfail(&entries, ntxn);
};
producti += 1;
continue;
};
products[producti].stageout = sepprepareproductstage(
products[producti].stageout, products[producti].out);
if (products[producti].stageout == nil) {
return sepfinishfail(&entries, ntxn);
};
let ci: i32 = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let linkorder: []i32;
let linkstack: []i32;
if (!sepmakeints(g.n, &linkorder)
|| !sepmakeints(g.n, &linkstack)) {
return sepfinishfail(&entries, ntxn);
};
let nlink: i32 = 0;
if (septopovisit(g, root, linkorder, &nlink,
linkstack, 0) < 0) {
return sepfinishfail(&entries, ntxn);
};
let total: i32 = 4;
if (nlink > SEP_COUNT_MAX - total) {
sepfailsize(); return sepfinishfail(&entries, ntxn);
};
total += nlink;
if (nrt > SEP_COUNT_MAX - total) {
sepfailsize(); return sepfinishfail(&entries, ntxn);
};
total += nrt;
if (nldirs > (SEP_COUNT_MAX - total) / 2) {
sepfailsize(); return sepfinishfail(&entries, ntxn);
};
total += nldirs * 2;
if (nllibs > (SEP_COUNT_MAX - total) / 2) {
sepfailsize(); return sepfinishfail(&entries, ntxn);
};
total += nllibs * 2;
let largvallocation: ([]*u8 | nomem) = sepallocptrs(total);
let largv: []*u8;
match (largvallocation) {
case let value: []*u8 => largv = value;
case nomem => {
sepfailnomem(); return sepfinishfail(&entries, ntxn);
};
};
largv.len = total;
largv[0] = "w6l\0".ptr;
largv[1] = "-o\0".ptr;
largv[2] = products[producti].stageout;
let pos: i32 = 3;
let li: i32 = nlink - 1;
for (li >= 0) {
let pi: i32 = linkorder[li];
let suffix: str = ".a";
if (warm && g.pkg[pi].archivestaged) { suffix = ".a.new"; };
largv[pos] = sepfname(g, pi, scratch, suffix);
if (largv[pos] == nil) {
return sepfinishfail(&entries, ntxn);
};
pos += 1;
li -= 1;
};
let ri: i32 = 0;
for (ri < nrt) { largv[pos] = rtpaths[ri]; pos += 1; ri += 1; };
let k: i32 = 0;
for (k < nldirs) {
largv[pos] = "-L\0".ptr; pos += 1;
largv[pos] = ldirs[k]; pos += 1; k += 1;
};
k = 0;
for (k < nllibs) {
largv[pos] = "-l\0".ptr; pos += 1;
largv[pos] = llibs[k]; pos += 1; k += 1;
};
largv[pos] = nil;
let linkallocation: ([]str | nomem) = sepallocstrs(pos);
let linkargs: []str;
match (linkallocation) {
case let value: []str => linkargs = value;
case nomem => {
sepfailnomem(); return sepfinishfail(&entries, ntxn);
};
};
let ai: i32 = 0;
for (ai < pos) { append(linkargs, pathstr(largv[ai])); ai += 1; };
let linkenv: []str = os.getenvs();
let linkresult: exec.result;
exec.runstdio(pathstr(l6), linkargs, linkenv, &linkresult);
if (linkresult.termination != exec.termination.EXIT
|| linkresult.code != 0) {
if (linkresult.termination == exec.termination.ERROR
&& linkresult.code == 127) { cerr("ww: execve failed\n"); };
cerr("ww: w6l failed\n");
g.pkg[root].failed = true;
return sepfinishfail(&entries, ntxn);
};
if (products[producti].publish != nil
&& (products[producti].stagepublish == nil
|| copyexecutablestage(products[producti].stageout,
products[producti].stagepublish) != 0)) {
cerrpath("ww: cannot stage test binary ",
products[producti].publish, "\n");
return sepfinishfail(&entries, ntxn);
};
if (sepstageproductstatus(&products[producti]) != 0) {
cerr("ww: cannot stage package-test product\n");
return sepfinishfail(&entries, ntxn);
};
producti += 1;
};
}; };
if (warm) {
let oi: i32 = 0;
for (oi < norder) {
let pi: i32 = order[oi];
if (g.pkg[pi].sourcestaged) {
if (!septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".wwi.new", ".wwi")
|| !septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".s.new", ".s")
|| (emitasm == 0
&& !septxnaddpkgsuffix(&entries, &ntxn, g, pi,
scratch, ".o.new", ".o"))) {
return sepfinishfail(&entries, ntxn);
};
};
if (g.pkg[pi].initstaged) {
if (!septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".init.s.new", ".init.s")
|| (emitasm == 0
&& !septxnaddpkgsuffix(&entries, &ntxn, g, pi,
scratch, ".init.o.new", ".init.o"))) {
return sepfinishfail(&entries, ntxn);
};
};
if (g.pkg[pi].archivestaged
&& !septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".a.new", ".a")) {
return sepfinishfail(&entries, ntxn);
};
if (g.pkg[pi].sourcestaged
&& !septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".unit.new", ".unit.ww")) {
return sepfinishfail(&entries, ntxn);
};
if (g.pkg[pi].initstaged
&& !septxnaddpkgsuffix(&entries, &ntxn, g, pi, scratch,
".init.unit.new", ".init.unit.ww")) {
return sepfinishfail(&entries, ntxn);
};
oi += 1;
};
let toolsrc: []*u8 = [selfpath, c6, a6];
let tooldst: []*u8 = [toolw, toolc, toola];
let ntools: i32 = 3;
if (emitasm != 0) { ntools = 2; };
let ti: i32 = 0;
for (ti < ntools) {
if (!fileequal(tooldst[ti], toolsrc[ti])) {
let stage: *u8 = sepappendlit(tooldst[ti], ".new");
if (stage == nil || copyfilestage(toolsrc[ti], stage) != 0
|| !septxnadd(&entries, &ntxn, stage, tooldst[ti])) {
cerr("ww: cannot stage workdir tool identity\n");
return sepfinishfail(&entries, ntxn);
};
};
ti += 1;
};
if (!stampok) {
let stage: *u8 = sepappendlit(stampf, ".new");
if (stage == nil || sepwritetextstage(stage, stampwant) != 0
|| !septxnadd(&entries, &ntxn, stage, stampf)) {
cerr("ww: cannot stage workdir stamp\n");
return sepfinishfail(&entries, ntxn);
};
};
};
producti = 0;
for (producti < nproducts) {
if (products[producti].stageout != nil
&& ((products[producti].publicout
&& !septxnaddpublic(&entries, &ntxn,
products[producti].stageout, products[producti].out))
|| (!products[producti].publicout
&& !septxnadd(&entries, &ntxn,
products[producti].stageout, products[producti].out)))) {
return sepfinishfail(&entries, ntxn);
};
if (products[producti].stagepublish != nil
&& !septxnaddpublic(&entries, &ntxn,
products[producti].stagepublish,
products[producti].publish)) {
return sepfinishfail(&entries, ntxn);
};
if (products[producti].stageiface != nil) {
let outiface: *u8 = sepappendlit(products[producti].out, ".wwi");
if (outiface == nil
|| !septxnaddpublic(&entries, &ntxn,
products[producti].stageiface, outiface)) {
return sepfinishfail(&entries, ntxn);
};
};
producti += 1;
};
producti = 0;
for (producti < nproducts) {
if (products[producti].stagestatus != nil
&& !septxnadd(&entries, &ntxn, products[producti].stagestatus,
products[producti].status)) {
return sepfinishfail(&entries, ntxn);
};
producti += 1;
};
if (!septxncommit(entries, ntxn)) {
return sepfinishfail(&entries, ntxn);
};
septxnrelease(&entries, ntxn);
return 0;
};
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
out: *u8, objstem: *u8, incs: *u8, lf: *lflags,
publishpackage: i32, requirecommand: i32, istest: i32,
products: *sepproduct, nproducts: i32, emitasm: i32,
workdir: *u8, createworkdir: bool, createoutputdir: *u8,
defaultoutputdir: *u8, outputpatherror: bool,
outputcollisionbase: *u8, outputcollisiondir: *u8,
scratchout: **u8,
graphout: **sepgraph) i32 = {
sepfatalallocation = false;
if (nproducts < 1) { return 1; };
let c6: *u8 = toolpath(selfdir, "WW_W6C", "w6c_ww");
let a6: *u8 = toolpath(selfdir, "WW_W6A", "w6a_ww");
let l6: *u8 = toolpath(selfdir, "WW_W6L", "w6l_ww");
if (c6 == nil || a6 == nil || l6 == nil) { return 1; };
let libdir: *u8 = envpath("WW_LIB");
if (sepfatalallocation) { return 1; };
if (libdir == nil) { libdir = sepjoinpathlit(selfdir, "../lib"); };
if (libdir == nil) { return 1; };
let toolsrcdir: *u8 = envpath("WW_SRCLIB");
if (sepfatalallocation) { return 1; };
if (toolsrcdir == nil) {
let candidate: *u8 = sepjoinpathlit(selfdir, "../../lib");
if (candidate == nil) { return 1; };
if (os.access(pathstr(candidate), 0i32) == 0) {
toolsrcdir = candidate;
} else { if (os.access("lib", 0i32) == 0) {
toolsrcdir = "lib\0".ptr;
} else {
toolsrcdir = libdir;
}; };
};
let srcd: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &srcd)) { return 1; };
if (entryisdir != 0) {
let slen: u64 = cstrlen(src);
let k: u64 = 0u64;
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
for (slen > 1u64) {
if (srcd[slen - 1u64] != 47u8) { break; };
slen -= 1u64;
};
srcd[slen] = 0u8;
} else {
let slen: u64 = cstrlen(src);
let last: u64 = slen;
let found: bool = false;
let i: u64 = slen;
for (i > 0u64) {
i -= 1u64;
if (src[i] == 47u8) { last = i; found = true; i = 0u64; };
};
if (found) {
let k: u64 = 0u64;
for (k < last) { srcd[k] = src[k]; k += 1u64; };
srcd[last] = 0u8;
} else {
srcd[0] = 46u8; // '.'
srcd[1] = 0u8;
};
};
let stem: *u8 = nil;
if (entryisdir != 0) {
let dlen: u64 = cstrlen(srcd.ptr);
let bo: u64 = basenameoff(srcd.ptr, dlen);
stem = sepjoinpath(srcd.ptr, srcd.ptr + bo);
} else {
let stembuf: []u8;
let stemneed: u64 = 0u64;
if (!sepaddbytes(&stemneed, cstrlen(src))
|| !sepaddbytes(&stemneed, 1u64)
|| !sepmakebytes(stemneed, &stembuf)) { return 1; };
makestem(stembuf.ptr, src);
stem = stembuf.ptr;
};
if (stem == nil) { return 1; };
let effstem: *u8 = stem;
if (objstem != nil) { effstem = objstem; };
let warm: bool = false;
let workdirexists: bool = false;
if (workdir != nil) {
if (workdir[0u64] != 0u8) { warm = true; };
};
let scratch: *u8 = nil;
if (warm) {
let wfi: os.filestat;
let wok: bool = false;
let missing: bool = false;
match (os.stat(&wfi, pathstr(workdir))) {
case void => {
let wt: u32 = (wfi.mode: u32) & 61440u32; // S_IFMT
if (wt == os.mode.DIR: u32) {
wok = true; workdirexists = true;
};
};
case let e: os.oserror => {
if ((e: i64) == -2i64) { missing = true; };
};
};
if (!wok && !(createworkdir && missing)) {
cerrpath("ww: workdir ", workdir,
" is not a directory\n");
return 1;
};
if (cstrlen(workdir) + 1u64 > os.PATH_MAX: u64) {
cerr("ww: workdir path is too long\n");
return 1;
};
// The workdir is caller-owned and persistent: no acquisition,
// no refusal, and scratchout stays nil so the wrapper never
// cleans it.
scratch = workdir;
} else {
let suffix: u64 = ".sepwork".len: u64 + 1u64;
if (suffix > os.PATH_MAX: u64
|| cstrlen(effstem) > os.PATH_MAX: u64 - suffix) {
cerr("ww: scratch path is too long\n");
return 1;
};
scratch = sepappendlit(effstem, ".sepwork");
if (scratch == nil) { return 1; };
};
let staleall: bool = false;
let stampok: bool = false;
let toolw: *u8 = nil;
let toolc: *u8 = nil;
let toola: *u8 = nil;
let stampf: *u8 = nil;
let stampwant: str = "";
if (warm) {
if (!fileisreg(selfpath)) {
cerrpath("ww: cannot read driver identity ", selfpath, "\n");
return 1;
};
let toolsuffix: u64 = "/.wwtool.stamp".len: u64 + 1u64;
if (toolsuffix > os.PATH_MAX: u64
|| cstrlen(scratch) > os.PATH_MAX: u64 - toolsuffix) {
cerr("ww: workdir path is too long\n");
return 1;
};
toolw = sepjoinpathlit(scratch, ".wwtool.ww");
toolc = sepjoinpathlit(scratch, ".wwtool.w6c");
toola = sepjoinpathlit(scratch, ".wwtool.w6a");
stampf = sepjoinpathlit(scratch, ".wwtool.stamp");
if (toolw == nil || toolc == nil || toola == nil || stampf == nil) {
return 1;
};
stampwant = workdirstamptext(istest, emitasm);
stampok = stampmatches(stampf, stampwant);
staleall = !stampok;
if (!staleall) {
if (!fileequal(toolw, selfpath)) { staleall = true; };
};
if (!staleall) {
if (!fileequal(toolc, c6)) { staleall = true; };
};
if (!staleall) {
if (emitasm == 0) {
if (!fileequal(toola, a6)) { staleall = true; };
};
};
};
if (sepfatalallocation) { return 1; };
let rtallocation: ([]*u8 | nomem) = sepallocptrs(2);
let rtpaths: []*u8;
match (rtallocation) {
case let value: []*u8 => rtpaths = value;
case nomem => { sepfailnomem(); return 1; };
};
rtpaths.len = 2;
let nrt: i32 = 1;
let havearchive: bool = false;
if (cstrlen(libdir) + 1u64 + "libwwrt.a".len: u64 + 1u64
<= os.PATH_MAX: u64) {
rtpaths[0] = sepjoinpathlit(libdir, "libwwrt.a");
if (rtpaths[0] == nil) { return 1; };
if (os.access(pathstr(rtpaths[0]), 0i32) == 0) {
havearchive = true;
};
};
if (!havearchive) {
nrt = 2;
rtpaths[0] = sepjoinpathlit(selfdir, "../obj/rt/start.o");
rtpaths[1] = sepjoinpathlit(selfdir, "../obj/rt/syscall.o");
if (rtpaths[0] == nil || rtpaths[1] == nil) { return 1; };
};
let pkgslot: []seppkg;
let contextslot: []sepcontext;
let graphallocation: (*sepgraph | nomem) = sepallocgraph(pkgslot,
contextslot);
let g: *sepgraph;
match (graphallocation) {
case let value: *sepgraph => g = value;
case nomem => { sepfailnomem(); return 1; };
};
if (graphout != nil) { *graphout = g; };
let producti: i32 = 0;
for (producti < nproducts) {
products[producti].support = -1;
products[producti].productionroot = -1;
products[producti].ptest = -1;
products[producti].pxtest = -1;
products[producti].stageout = nil;
products[producti].stagepublish = nil;
products[producti].stageiface = nil;
products[producti].stagestatus = nil;
producti += 1;
};
producti = 0;
for (producti < nproducts) {
let entry: *u8 = src;
if (products[producti].dir != nil) {
entry = products[producti].dir;
};
let contextroot: *u8 = entry;
if (entryisdir == 0) { contextroot = srcd.ptr; };
let requestedpath: *u8 = "\0".ptr;
if (products[producti].identity != nil) {
requestedpath = products[producti].identity;
};
products[producti].context = sepcontextfor(g, contextroot,
incs, toolsrcdir, requestedpath);
if (products[producti].context < 0) { return 1; };
let inferredpath: *u8 = nil;
let rootpath: *u8 = requestedpath;
if (entryisdir != 0 && rootpath[0u64] == 0u8) {
let inferred: i32 = sepcontextimportbase(g,
products[producti].context, &inferredpath);
if (inferred < 0) { return 1; };
if (inferred > 0) { rootpath = inferredpath; };
};
if (products[producti].directoryproduct) {
if (products[producti].productionpackage != nil) {
products[producti].productionroot = sepfindoraddvariant(g,
rootpath, entry, 1, SEP_VARIANT_PRODUCTION, nil,
SEP_ROLE_NORMAL, nil, true);
if (products[producti].productionroot < 0) { return 1; };
};
if (products[producti].internalpackage != nil) {
products[producti].ptest = sepfindoraddvariant(g, rootpath,
entry, 1, SEP_VARIANT_SAME_TEST,
products[producti].internalpackage,
SEP_ROLE_NORMAL, nil, true);
if (products[producti].ptest < 0) { return 1; };
} else { if (istest != 0 && !products[producti].notests) {
products[producti].ptest = products[producti].productionroot;
}; };
if (products[producti].externalpackage != nil) {
products[producti].pxtest = sepfindoraddvariant(g, rootpath,
entry, 1, SEP_VARIANT_EXTERNAL,
products[producti].externalpackage,
SEP_ROLE_NORMAL, nil, true);
if (products[producti].pxtest < 0) { return 1; };
};
if (istest == 0 || products[producti].notests) {
products[producti].root = products[producti].productionroot;
} else { if (products[producti].ptest >= 0) {
products[producti].root = products[producti].ptest;
} else {
products[producti].root = products[producti].pxtest;
}; };
products[producti].variantroot = products[producti].ptest;
} else {
let selector: *u8 = products[producti].testpackage;
if (products[producti].variant == SEP_VARIANT_PRODUCTION) {
selector = nil;
};
products[producti].root = sepfindoraddvariant(g, rootpath, entry,
entryisdir, products[producti].variant, selector,
SEP_ROLE_NORMAL, products[producti].artifact, true);
products[producti].variantroot = products[producti].root;
};
if (products[producti].root < 0) { return 1; };
producti += 1;
};
producti = 0;
for (producti < nproducts) {
if (products[producti].directoryproduct) {
let a: i32 = products[producti].productionroot;
if (a < 0) { a = products[producti].ptest; };
if (a < 0) { a = products[producti].pxtest; };
let previous: i32 = 0;
for (previous < producti) {
if (products[previous].directoryproduct) {
let b: i32 = products[previous].productionroot;
if (b < 0) { b = products[previous].ptest; };
if (b < 0) { b = products[previous].pxtest; };
let duplicate: bool = a >= 0 && b >= 0 && a == b;
if (!duplicate && a >= 0 && b >= 0) {
let aid: *u8 = g.pkg[a].importbase;
let bid: *u8 = g.pkg[b].importbase;
if (aid != nil && bid != nil) {
duplicate = cstreq(aid, bid);
} else { if (aid == nil && bid == nil) {
duplicate = cstreq(g.pkg[a].canon,
g.pkg[b].canon);
}; };
};
if (duplicate) {
cerr("ww test: duplicate --ww-package-test product for canonical directory\n");
return 1;
};
};
previous += 1;
};
};
producti += 1;
};
let testsupportmodule: str = "test";
let haverunnabletests: bool = false;
producti = 0;
for (producti < nproducts) {
if (istest != 0 && !products[producti].notests) {
haverunnabletests = true;
};
producti += 1;
};
// -T generates a dispatcher whose support qualifier is selected by the
// command. Represent that requirement as a direct generated-main edge. It
// normally coalesces with an explicit toolchain `import test`;
// when user source occupies that identity, the reserved graph alias keeps
// it distinct. The linker receives the same support archive closure.
if (haverunnabletests) {
let td: i32 = 1;
let tp: *u8 = locateimport(toolsrcdir, "test".ptr,
"test".len: u64);
if (tp != nil) {
let supportsearch: *u8 = sepappendlit(toolsrcdir, ":");
if (supportsearch == nil) { return 1; };
supportsearch = sepappendlit(supportsearch,
pathstr(toolsrcdir));
if (supportsearch == nil) { return 1; };
g.supportcontext = sepcontextadd(g, toolsrcdir,
supportsearch, tp, toolsrcdir);
if (g.supportcontext < 0) { return 1; };
let collision: bool = false;
producti = 0;
for (producti < nproducts) {
if (products[producti].notests) {
producti += 1; continue;
};
let root: i32 = products[producti].ptest;
if (root < 0) { root = products[producti].pxtest; };
let rootissupport: bool = entryisdir != 0
&& os.samefile(pathstr(tp),
pathstr(g.pkg[root].entry));
let name: *u8 = products[producti].testpackage;
if (!rootissupport && name != nil
&& (cstreqlit(name, "test")
|| cstreqlit(name, "test_test"))) {
collision = true;
};
producti += 1;
};
producti = 0;
for (producti < nproducts && !collision) {
if (products[producti].notests) {
producti += 1;
continue;
};
let up: *u8 = locateimport(
g.context[products[producti].context].searchpath,
"test".ptr,
"test".len: u64);
if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) {
collision = true;
};
producti += 1;
};
if (collision) { testsupportmodule = SEP_TEST_SUPPORT_MODULE; };
producti = 0;
for (producti < nproducts) {
if (products[producti].notests) {
producti += 1; continue;
};
let root: i32 = products[producti].ptest;
if (root < 0) { root = products[producti].pxtest; };
let rootissupport: bool = entryisdir != 0
&& os.samefile(pathstr(tp),
pathstr(g.pkg[root].entry));
// A same-test build of the runtime package already owns run
// and its source imports. An external test still needs the
// colocated production node, also its support dependency.
if (rootissupport
&& syntax.streq(testsupportmodule, "test")) {
products[producti].support = root;
producti += 1;
continue;
};
let ti: i32 = -1;
if (syntax.streq(testsupportmodule,
SEP_TEST_SUPPORT_MODULE)) {
ti = sepfindoraddrole(g, testsupportmodule.ptr, tp,
td, SEP_ROLE_TEST_SUPPORT, nil);
} else {
ti = sepfindoradd(g, testsupportmodule.ptr, tp, td);
};
if (ti < 0) { return 1; };
g.pkg[ti].testsupport = true;
products[producti].support = ti;
producti += 1;
};
};
};
producti = 0;
for (producti < nproducts) {
let roots: [3]i32;
roots[0] = products[producti].productionroot;
roots[1] = products[producti].ptest;
roots[2] = products[producti].pxtest;
let selectors: [3]*u8;
selectors[0] = products[producti].productionpackage;
selectors[1] = products[producti].internalpackage;
selectors[2] = products[producti].externalpackage;
let nroots: i32 = 3;
if (!products[producti].directoryproduct) {
nroots = 1;
roots[0] = products[producti].variantroot;
selectors[0] = products[producti].testpackage;
if (products[producti].variant == SEP_VARIANT_PRODUCTION) {
selectors[0] = nil;
};
};
// Raw single-file tests retain the explicit fixture exception: test-main
// synthesis stays in that action and support remains a direct export.
if (istest != 0 && entryisdir == 0) {
let support: i32 = products[producti].support;
if (support >= 0 && support != roots[0]
&& !sepadddep(g, roots[0], support)) { return 1; };
g.pkg[roots[0]].linkentry = true;
};
let ri: i32 = 0;
for (ri < nroots) {
let root: i32 = roots[ri];
if (root < 0) { ri += 1; continue; };
if (ri > 0 && products[producti].productionroot >= 0
&& g.pkg[products[producti].productionroot].failed) {
g.pkg[root].failed = true;
ri += 1;
continue;
};
let duplicate: bool = false;
let rj: i32 = 0;
for (rj < ri) {
if (roots[rj] == root) { duplicate = true; };
rj += 1;
};
if (duplicate) { ri += 1; continue; };
let loadresult: i32 = seploadpkg(g, root,
products[producti].context);
if (loadresult == -2) { return 1; };
if (loadresult == SEP_LOAD_INTERNAL
|| loadresult == SEP_LOAD_VENDOR) { return 1; };
if (loadresult < 0) {
g.pkg[root].failed = true;
ri += 1; continue;
};
if (selectors[ri] != nil
&& !cstreq(g.pkg[root].name, selectors[ri])) {
cerr("ww: package-test selector does not match loaded package\n");
g.pkg[root].failed = true;
};
ri += 1;
};
producti += 1;
};
if (g.identityfailed) { return 1; };
if (haverunnabletests) {
producti = 0;
for (producti < nproducts) {
if (products[producti].notests) {
producti += 1; continue;
};
let variant: i32 = products[producti].ptest;
if (variant < 0) { variant = products[producti].pxtest; };
let support: i32 = products[producti].support;
if (support >= 0 && support != variant) {
let loadresult: i32 = seploadpkg(g, support,
products[producti].context);
if (loadresult == -2) { return 1; };
if (loadresult == SEP_LOAD_INTERNAL
|| loadresult == SEP_LOAD_VENDOR) { return 1; };
if (loadresult < 0) {
g.pkg[variant].failed = true;
};
};
producti += 1;
};
};
if (g.identityfailed) { return 1; };
if (sepfinalizedirectoryidentities(g) < 0) { return 1; };
if (istest == 0) {
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (!g.pkg[root].failed) {
g.pkg[root].linkentry =
seprootiscommand(&g.pkg[root]);
};
producti += 1;
};
};
if (istest != 0 && entryisdir != 0) {
producti = 0;
for (producti < nproducts) {
if (products[producti].notests) {
producti += 1; continue;
};
let variant: i32 = products[producti].ptest;
if (variant < 0) { variant = products[producti].pxtest; };
let support: i32 = products[producti].support;
let failed: bool = g.pkg[variant].failed;
if (products[producti].productionroot >= 0
&& g.pkg[products[producti].productionroot].failed) {
failed = true;
};
if (products[producti].ptest >= 0
&& g.pkg[products[producti].ptest].failed) { failed = true; };
if (products[producti].pxtest >= 0
&& g.pkg[products[producti].pxtest].failed) { failed = true; };
if (failed
|| (support >= 0 && g.pkg[support].failed)) {
products[producti].root = variant;
g.pkg[variant].failed = true;
producti += 1;
continue;
};
let mainpkg: i32 = sepaddgeneratedmain(g, &products[producti],
producti, support);
if (mainpkg < 0) { return 1; };
products[producti].root = mainpkg;
producti += 1;
};
producti = 0;
for (producti < nproducts) {
if (!products[producti].notests
&& !g.pkg[products[producti].root].failed
&& seprecompilefortest(g, &products[producti]) < 0) {
return 1;
};
producti += 1;
};
};
let initpi: i32 = 0;
for (initpi < g.n) {
g.pkg[initpi].initsymbol = seppackageinitsymbol(&g.pkg[initpi]);
if (g.pkg[initpi].initsymbol == nil) { return 1; };
initpi += 1;
};
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (!g.pkg[root].failed && seprootiscommand(&g.pkg[root])
&& validatecommandoutputpath(products[producti].out) < 0) {
return 1;
};
if (products[producti].publish != nil
&& validatecommandoutputpath(products[producti].publish) < 0) {
return 1;
};
if (products[producti].status != nil
&& validatecommandoutputpath(products[producti].status) < 0) {
return 1;
};
producti += 1;
};
let rootpackage: bool = istest == 0 && nproducts == 1
&& !g.pkg[products[0].root].failed
&& !seprootiscommand(&g.pkg[products[0].root]);
let ci: i32 = 0;
let order: []i32;
let stack: []i32;
if (!sepmakeints(g.n, &order) || !sepmakeints(g.n, &stack)) {
return 1;
};
let norder: i32 = 0;
// Diagnose cycles per product before constructing the shared union. A
// variant-local cycle does not erase another root's attribution, although
// any failure still rejects the request-wide publication transaction.
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (!g.pkg[root].failed) {
ci = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let ignored: i32 = 0;
let topores: i32 = septopovisit(g, root, order,
&ignored, stack, 0);
if (topores == -2) { return 1; };
if (topores < 0
|| sepvalidatemoduleclosure(g, order, ignored, true) < 0) {
g.pkg[root].failed = true;
};
};
producti += 1;
};
// Go rejects cycles introduced by internal-test substitution before any
// compiler/link action (I -> X -> P becomes I -> X -> I).
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (!g.pkg[root].failed) {
let initcheck: []i32;
let ninitcheck: i32 = 0;
if (sepinitorder(g, root, &initcheck, &ninitcheck) < 0) {
g.pkg[root].failed = true;
};
if (initcheck.ptr != nil) {
os.free(initcheck.ptr: *void,
(initcheck.cap: u64) * (size(i32): u64));
};
};
producti += 1;
};
if (sepfatalallocation) { return 1; };
if (istest == 0 && requirecommand != 0) {
let root: i32 = products[0].root;
if (!g.pkg[root].failed && !seprootiscommand(&g.pkg[root])) {
let identity: *u8 = g.pkg[root].canon;
if (g.pkg[root].path[0u64] != 0u8) {
identity = g.pkg[root].path;
};
cerrpath("ww: package ", identity,
" is not a main package\n");
return 1;
};
};
if (istest == 0 && createoutputdir != nil) {
let actions: i32 = 0;
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (g.pkg[root].failed) { return 1; };
products[producti].buildaction =
seprootiscommand(&g.pkg[root]);
if (products[producti].buildaction) { actions += 1; };
producti += 1;
};
if (actions == 0) {
cerr("ww: no main packages to build\n");
return 1;
};
};
if (!g.pkg[products[0].root].failed
&& rootpackage && publishpackage != 0 && emitasm == 0
&& validatepackageoutputpath(out) < 0) { return 1; };
ci = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].root;
if (products[producti].buildaction && !g.pkg[root].failed) {
if (septopovisit(g, root, order,
&norder, stack, 0) < 0) { return 1; };
};
producti += 1;
};
let actionpi: i32 = 0;
for (actionpi < g.n) {
g.pkg[actionpi].action = false;
actionpi += 1;
};
let actionoi: i32 = 0;
for (actionoi < norder) {
g.pkg[order[actionoi]].action = true;
actionoi += 1;
};
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
if (warm && workdirexists
&& sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
// Propagate already-known package-load failures before scratch or status
// acquisition. Good sibling roots may remain viable for deterministic
// staging/diagnosis, but any failure rejects publication; an entirely failed
// cold request leaves no persistent or caller-visible tree.
let preoi: i32 = 0;
for (preoi < norder) {
let pi: i32 = order[preoi];
let dk: i32 = 0;
for (dk < g.pkg[pi].ndeps) {
if (g.pkg[g.pkg[pi].deps[dk]].failed) {
g.pkg[pi].failed = true;
};
dk += 1;
};
preoi += 1;
};
let viableproduct: bool = false;
producti = 0;
for (producti < nproducts) {
if (products[producti].buildaction
&& !g.pkg[products[producti].root].failed) {
viableproduct = true;
};
producti += 1;
};
if (!viableproduct) { return 1; };
if (istest == 0 && emitasm == 0 && outputpatherror) {
cerr("ww: command output path is too long\n");
return 1;
};
if (istest == 0 && emitasm == 0 && outputcollisionbase != nil) {
cerr("ww: multiple commands produce output basename ");
sepputquoted(outputcollisionbase);
cerr(" in directory ");
sepputquoted(outputcollisiondir);
cerr("\n");
return 1;
};
if (istest == 0 && emitasm == 0 && defaultoutputdir != nil && nproducts == 1
&& seprootiscommand(&g.pkg[products[0].root])) {
cerrpath("ww: build output \"", defaultoutputdir,
"\" already exists and is a directory\n");
return 1;
};
if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts,
rootpackage, publishpackage, emitasm, istest) < 0) {
sepfreeproductstaging(products, nproducts);
return 1;
};
// Source-derived resolution and contextual legality are complete before
// coordinator completion markers or persistent vouchers are changed.
let createdoutput: sepcreateddirs;
let createdwork: sepcreateddirs;
createdoutput.n = 0;
createdwork.n = 0;
if (emitasm == 0 && createoutputdir != nil
&& sepmkdirsrecord(createoutputdir, 511, &createdoutput) != 0) {
if (istest != 0) {
cerrpath("ww: cannot create test output directory ",
createoutputdir, "\n");
} else {
cerrpath("ww: cannot create build output directory ",
createoutputdir, "\n");
};
return 1;
};
if (warm && !workdirexists) {
if (!createworkdir
|| sepmkdirsrecord(scratch, 448, &createdwork) != 0) {
cerrpath("ww: workdir ", scratch, " is not a directory\n");
seprollbackdirs(&createdoutput);
return 1;
};
workdirexists = true;
};
if (!warm) {
if (os.mkdir(pathstr(scratch), 493i32) != 0) {
cerrpath("ww: cannot create scratch ", scratch, "\n");
seprollbackdirs(&createdwork);
seprollbackdirs(&createdoutput);
return 1;
};
// The wrapper owns only the directory this invocation acquired.
if (scratchout != nil) { *scratchout = scratch; };
};
let anyfailed: bool = false;
producti = 0;
for (producti < nproducts) {
if (g.pkg[products[producti].root].failed) { anyfailed = true; };
producti += 1;
};
let oi: i32 = 0;
for (oi < norder) {
let pi: i32 = order[oi];
let dk: i32 = 0;
for (dk < g.pkg[pi].ndeps) {
if (g.pkg[g.pkg[pi].deps[dk]].failed) {
g.pkg[pi].failed = true;
};
dk += 1;
};
if (g.pkg[pi].failed) {
anyfailed = true;
oi += 1;
continue;
};
let unitf: *u8 = sepfname(g, pi, scratch, ".unit.ww");
let wwi: *u8 = sepfname(g, pi, scratch, ".wwi");
let asmf: *u8 = sepfname(g, pi, scratch, ".s");
let objf: *u8 = sepfname(g, pi, scratch, ".o");
let apath: *u8 = sepfname(g, pi, scratch, ".a");
let unitnew: *u8 = sepfname(g, pi, scratch, ".unit.new");
let wwinew: *u8 = sepfname(g, pi, scratch, ".wwi.new");
let asmnew: *u8 = sepfname(g, pi, scratch, ".s.new");
let objnew: *u8 = sepfname(g, pi, scratch, ".o.new");
let anew: *u8 = sepfname(g, pi, scratch, ".a.new");
if (unitf == nil || wwi == nil || asmf == nil || objf == nil
|| apath == nil || unitnew == nil || wwinew == nil
|| asmnew == nil || objnew == nil || anew == nil) {
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
let initunitf: *u8 = nil;
let initasmf: *u8 = nil;
let initobj: *u8 = nil;
let initunitnew: *u8 = nil;
let initasmnew: *u8 = nil;
let initobjnew: *u8 = nil;
let productindex: i32 = -1;
if (g.pkg[pi].linkentry) {
let owneri: i32 = 0;
for (owneri < nproducts && productindex < 0) {
if (products[owneri].root == pi) { productindex = owneri; };
owneri += 1;
};
if (productindex < 0) {
cerr("ww: executable action has no owning product\n");
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
initunitf = sepfname(g, pi, scratch, ".init.unit.ww");
initasmf = sepfname(g, pi, scratch, ".init.s");
initobj = sepfname(g, pi, scratch, ".init.o");
initunitnew = sepfname(g, pi, scratch, ".init.unit.new");
initasmnew = sepfname(g, pi, scratch, ".init.s.new");
initobjnew = sepfname(g, pi, scratch, ".init.o.new");
if (initunitf == nil || initasmf == nil || initobj == nil
|| initunitnew == nil || initasmnew == nil
|| initobjnew == nil) {
return seprejectrequest(g, scratch, warm, products,
nproducts, &createdwork, &createdoutput, scratchout);
};
};
// Classic scratch has no committed generation. Alias cleanup paths to
// the in-place outputs so rejected producers leave no partial action.
if (!warm) {
unitnew = unitf; wwinew = wwi; asmnew = asmf;
objnew = objf; anew = apath;
if (productindex >= 0) {
initunitnew = initunitf;
initasmnew = initasmf;
initobjnew = initobj;
};
};
// Warm mode compiles from staged `.new` paths and commits by
// rename; classic mode keeps its exact in-place paths.
let cu: *u8 = unitf;
let cw: *u8 = wwi;
let cs: *u8 = asmf;
let co: *u8 = objf;
let ca: *u8 = apath;
let ciu: *u8 = initunitf;
let cis: *u8 = initasmf;
let cio: *u8 = initobj;
if (warm) {
cu = unitnew; cw = wwinew; cs = asmnew;
co = objnew; ca = anew;
ciu = initunitnew; cis = initasmnew; cio = initobjnew;
};
if (sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew) < 0
|| sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew) < 0) {
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
if (sepcomposeunit(g, pi, scratch, cu) < 0) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
if (productindex >= 0
&& sepcomposeinitdispatch(g, &products[productindex],
ciu, cis) < 0) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
let depschanged: bool = false;
let changedk: i32 = 0;
for (changedk < g.pkg[pi].ndeps) {
if (g.pkg[g.pkg[pi].deps[changedk]].exportchanged) {
depschanged = true;
};
changedk += 1;
};
let sourcereusable: bool = false;
if (warm) {
if (!staleall && !depschanged) {
sourcereusable = fileequal(unitnew, unitf);
if (sourcereusable) { sourcereusable = fileisreg(asmf); };
if (sourcereusable) { sourcereusable = fileisreg(wwi); };
if (sourcereusable) {
if (emitasm == 0) {
sourcereusable = filesizenonzero(objf);
};
};
if (sourcereusable && emitasm == 0
&& productindex < 0) {
sourcereusable = filesizenonzero(apath);
};
};
};
let initreusable: bool = productindex < 0;
if (productindex >= 0 && warm && !staleall) {
initreusable = fileisreg(initunitf)
&& fileequal(initunitnew, initunitf);
if (initreusable) { initreusable = fileisreg(initasmf); };
if (initreusable && emitasm == 0) {
initreusable = filesizenonzero(initobj);
};
};
if (sepfatalallocation) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
let archivereusable: bool = emitasm != 0 || filesizenonzero(apath);
if (sourcereusable && initreusable && archivereusable) {
if (sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew) < 0
|| sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew) < 0) {
g.pkg[pi].failed = true;
anyfailed = true;
};
oi += 1;
continue;
};
// Closure-only changes rebuild the root-owned dispatcher/archive without
// invoking the compiler for an otherwise reusable root source action.
if (sourcereusable && productindex >= 0) {
let ignoredunit: i32 = os.remove(pathstr(unitnew));
if (emitasm == 0
&& (runassembler(a6, cio, cis) != 0
|| archiveo(objf, cio, ca) != 0)) {
if (g.pkg[pi].path[0u64] != 0u8) {
cerrpath("ww: initialization archive failed for ",
g.pkg[pi].path, "\n");
} else {
cerr("ww: initialization archive failed for (root)\n");
};
g.pkg[pi].failed = true;
anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
sepdiscardinitstaging(warm, initunitnew,
initasmnew, initobjnew);
oi += 1;
continue;
};
if (warm) {
g.pkg[pi].initstaged = true;
if (emitasm == 0) { g.pkg[pi].archivestaged = true; };
};
oi += 1;
continue;
};
// Staged producers cannot disturb the previous generation. Its source
// and dispatcher vouchers remain committed until commit actually opens;
// direct-export bytes in the source voucher prevent stale later reuse.
if (productindex >= 0 && initreusable && warm) {
if (sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew) < 0) {
cerr("ww: cannot remove staged initialization assembly\n");
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
sepdiscardinitstaging(warm, initunitnew,
initasmnew, initobjnew);
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
};
{
let rawtest: bool = (istest != 0) && g.pkg[pi].root
&& g.pkg[pi].isdir == 0;
let gent: bool = g.pkg[pi].generatedmain || rawtest;
let testpkg: bool = g.pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g.pkg[pi].variant == SEP_VARIANT_EXTERNAL;
let commandpkg: bool = sepcommandcompilermarker(g, pi);
let entry: bool = g.pkg[pi].linkentry;
let supportpkg: bool = g.pkg[pi].testsupport;
let nmaps: i32 = 0;
let mapk: i32 = 0;
for (mapk < g.pkg[pi].bindings.len) {
if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) {
if (nmaps == SEP_COUNT_MAX) {
sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
sepdiscardinitstaging(warm, initunitnew,
initasmnew, initobjnew);
return seprejectrequest(g, scratch, warm, products,
nproducts, &createdwork, &createdoutput,
scratchout);
};
nmaps += 1;
};
mapk += 1;
};
let alen: i32 = 18;
if (g.pkg[pi].ngeneratedtargets
> (SEP_COUNT_MAX - alen) / 2) {
sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
sepdiscardinitstaging(warm, initunitnew,
initasmnew, initobjnew);
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
alen += g.pkg[pi].ngeneratedtargets * 2;
if (g.pkg[pi].ndeps > (SEP_COUNT_MAX - alen) / 3) {
sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
alen += g.pkg[pi].ndeps * 3;
if (nmaps > (SEP_COUNT_MAX - alen) / 3) {
sepfailsize();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
alen += nmaps * 3;
let allocation: ([]str | nomem) = sepallocstrs(alen);
let argv: []str;
match (allocation) {
case let value: []str => argv = value;
case nomem => {
sepfailnomem();
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
return seprejectrequest(g, scratch, warm, products,
nproducts, &createdwork, &createdoutput,
scratchout);
};
};
append(argv, "w6c");
if (gent) {
append(argv, "-T");
append(argv, "--entry");
append(argv, "--test-support-module");
append(argv, testsupportmodule);
if (g.pkg[pi].generatedmain) {
let targetk: i32 = 0;
for (targetk < g.pkg[pi].ngeneratedtargets) {
append(argv, "--test-target-package");
append(argv, pathstr(g.pkg[
g.pkg[pi].generatedtargets[targetk]].path));
targetk += 1;
};
};
} else {
if (testpkg) { append(argv, "--test-package"); };
if (commandpkg) { append(argv, "--command-package"); };
if (entry) { append(argv, "--entry"); };
if (supportpkg) {
append(argv, "--test-support-module");
append(argv, testsupportmodule);
};
};
append(argv, "--package-init-symbol");
append(argv, pathstr(g.pkg[pi].initsymbol));
if (productindex >= 0) {
append(argv, "--init-dispatch-symbol");
append(argv, "__ww..dispatch");
};
append(argv, "-c");
let importk: i32 = 0;
for (importk < g.pkg[pi].ndeps) {
let dj: i32 = g.pkg[pi].deps[importk];
append(argv, "--import");
append(argv, pathstr(g.pkg[dj].path));
let depsuffix: str = ".wwi";
if (g.pkg[dj].sourcestaged) { depsuffix = ".wwi.new"; };
let depinterface: *u8 = sepfname(g, dj, scratch, depsuffix);
if (depinterface == nil) {
sepdiscardactionstaging(warm, unitnew, wwinew,
asmnew, objnew, anew);
sepdiscardinitstaging(warm, initunitnew,
initasmnew, initobjnew);
return seprejectrequest(g, scratch, warm, products,
nproducts, &createdwork, &createdoutput,
scratchout);
};
append(argv, pathstr(depinterface));
importk += 1;
};
mapk = 0;
for (mapk < g.pkg[pi].bindings.len) {
let b: sepbind = g.pkg[pi].bindings[mapk];
if (sepbindfirstmap(g, g.pkg[pi].bindings, mapk)) {
append(argv, "--import-map");
append(argv, b.name);
append(argv, pathstr(g.pkg[b.dep].path));
};
mapk += 1;
};
append(argv, "-I");
append(argv, pathstr(cw));
append(argv, "-o");
append(argv, pathstr(cs));
append(argv, pathstr(cu));
let env: []str = os.getenvs();
let result: exec.result;
exec.runstdio(pathstr(c6), argv, env, &result);
if (result.termination != exec.termination.EXIT
|| result.code != 0) {
if (result.termination == exec.termination.ERROR
&& result.code == 127) {
cerr("ww: execve failed\n");
};
if (g.pkg[pi].path[0u64] != 0u8) {
cerrpath("ww: w6c failed for ",
g.pkg[pi].path, "\n");
} else {
cerr("ww: w6c failed for (root)\n");
};
g.pkg[pi].failed = true;
anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
oi += 1;
continue;
};
};
if (!warm || !fileequal(wwinew, wwi)) {
g.pkg[pi].exportchanged = true;
};
if (sepfatalallocation) {
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
if (emitasm == 0 && runassembler(a6, co, cs) != 0) {
if (g.pkg[pi].path[0u64] != 0u8) {
cerrpath("ww: w6a failed for ",
g.pkg[pi].path, "\n");
} else {
cerr("ww: w6a failed for (root)\n");
};
g.pkg[pi].failed = true;
anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
oi += 1;
continue;
};
if (emitasm == 0 && productindex >= 0 && !initreusable
&& runassembler(a6, cio, cis) != 0) {
if (g.pkg[pi].path[0u64] != 0u8) {
cerrpath("ww: w6a failed for initialization of ",
g.pkg[pi].path, "\n");
} else {
cerr("ww: w6a failed for initialization of (root)\n");
};
g.pkg[pi].failed = true;
anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
oi += 1;
continue;
};
// Root archives add the dispatcher as the fixed second member.
if (emitasm == 0) {
let archiveinit: *u8 = nil;
if (productindex >= 0) {
archiveinit = cio;
if (initreusable) { archiveinit = initobj; };
};
if (archiveo(co, archiveinit, ca) != 0) {
if (g.pkg[pi].path[0u64] != 0u8) {
cerrpath("ww: archive failed for ", g.pkg[pi].path,
"\n");
} else {
cerr("ww: archive failed for (root)\n");
};
g.pkg[pi].failed = true;
anyfailed = true;
sepdiscardactionstaging(warm, unitnew, wwinew, asmnew,
objnew, anew);
sepdiscardinitstaging(warm, initunitnew, initasmnew,
initobjnew);
oi += 1;
continue;
};
};
if (warm) {
g.pkg[pi].sourcestaged = true;
if (emitasm == 0) { g.pkg[pi].archivestaged = true; };
if (productindex >= 0 && !initreusable) {
g.pkg[pi].initstaged = true;
};
};
oi += 1;
};
if (anyfailed) {
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
let finishresult: i32 = sepfinishrequest(selfdir, l6, c6, a6,
g, scratch, warm, rootpackage, publishpackage, istest, emitasm,
products, nproducts, order, norder, rtpaths, nrt, lf,
toolw, toolc, toola, stampf, stampwant, stampok);
if (finishresult != 0) {
return seprejectrequest(g, scratch, warm, products, nproducts,
&createdwork, &createdoutput, scratchout);
};
sepfreeproductstaging(products, nproducts);
return 0;
};
// buildonesep — `ww build` and an explicit `ww test -o` retain caller-visible
// `.sepwork` artifacts; their caller owns that exact tree. `ww run` and a
// no-output single-file test remove internal scratch on success and failure.
// The path is non-nil only after this invocation successfully created the
// exact tree. Twin of the cstage build_one_sep wrapper.
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
requirecommand: i32, istest: i32,
rootvariant: i32, testpackage: *u8, emitasm: i32,
publicoutput: bool, keepscratch: i32, workdir: *u8,
createoutputdir: *u8,
defaultoutputdir: *u8, outputpatherror: bool) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
let product: sepproduct;
product.dir = src;
product.out = out;
product.identity = rootidentity;
product.testpackage = testpackage;
product.productionpackage = nil;
product.internalpackage = nil;
product.externalpackage = nil;
product.status = nil;
product.publish = nil;
product.artifact = nil;
if (entryisdir == 0) {
product.artifact = "__root\0".ptr;
};
product.variant = rootvariant;
product.directoryproduct = false;
product.notests = false;
product.buildaction = true;
product.publicout = publicoutput;
product.root = -1;
product.variantroot = -1;
product.support = -1;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
incs, lf, publishpackage, requirecommand, istest, &product, 1,
emitasm, workdir, false, createoutputdir, defaultoutputdir,
outputpatherror, nil, nil,
&scratch, &g);
sepgraphfree(g);
if (keepscratch == 0 && scratch != nil) {
if (cstrendswithlit(scratch, ".sepwork")) {
let argv: []str = alloc([], 4u64)!;
append(argv, "rm");
append(argv, "-rf");
append(argv, "--");
append(argv, pathstr(scratch));
let env: []str = os.getenvs();
let result: exec.result;
exec.runstdio("/bin/rm", argv, env, &result);
if (result.termination != exec.termination.EXIT
|| result.code != 0) {
if (result.termination == exec.termination.ERROR
&& result.code == 127) {
cerr("ww: execve failed\n");
};
cerr("ww: cannot remove scratch\n");
if (r == 0) { r = 1; };
};
};
};
return r;
};
// Build every selected directory/variant root inside one command-owned package
// universe. The first output owns the shared cold sepwork tree.
fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
incs: *u8, workdir: *u8, products: *sepproduct, nproducts: i32,
istest: i32, publishpackage: i32, lf: *lflags, emitasm: i32,
createworkdir: bool, createoutputdir: *u8, defaultoutputdir: *u8,
outputpatherror: bool, outputcollisionbase: *u8,
outputcollisiondir: *u8) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
let i: i32 = 0;
for (i < nproducts) {
products[i].identity = rootidentity;
i += 1;
};
let r: i32 = buildonesepimpl(selfdir, src, 1,
products[0].out, products[0].out, incs, lf,
publishpackage, 0, istest,
products, nproducts, emitasm, workdir, createworkdir,
createoutputdir, defaultoutputdir, outputpatherror,
outputcollisionbase, outputcollisiondir, &scratch, &g);
sepgraphfree(g);
return r;
};
fn cstrendswithlit(p: *u8, lit: str) bool = {
return strings.hassuffix(pathstr(p), lit);
};
fn basenameoff(p: *u8, plen: u64) u64 = {
let start: u64 = 0u64;
let i: u64 = 0u64;
for (i < plen) {
if (p[i] == '/') { start = i + 1u64; };
i += 1u64;
};
return start;
};
fn arenadupcstr(src: *u8, plen: u64) *u8 = {
let buf: []u8 = alloc([], plen + 1u64)!;
let i: u64 = 0u64;
for (i < plen) { buf[i] = src[i]; i += 1u64; };
buf[plen] = 0u8;
return buf.ptr;
};
// resolvemodule search-path order: "." : <incs> : selected source library.
fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
let libdir: *u8 = envpath("WW_SRCLIB");
if (libdir == nil) { libdir = envpath("WW_LIB"); };
if (libdir == nil) {
let candidate: *u8 = joinpathlit(selfdir, "../../lib");
if (os.access(pathstr(candidate), 0i32) == 0) {
libdir = candidate;
} else { if (os.access("lib", 0i32) == 0) {
libdir = "lib\0".ptr;
} else {
libdir = joinpathlit(selfdir, "../lib");
}; };
};
let need: u64 = 1u64 + 1u64 + cstrlen(libdir) + 1u64;
if (incs != nil && incs[0u64] != 0u8) {
need += cstrlen(incs) + 1u64;
};
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = 0u64;
buf[off] = 46u8; off += 1u64; // '.'
if (incs != nil) {
if (incs[0u64] != 0u8) {
buf[off] = 58u8; off += 1u64; // ':'
off = cstrinto(buf.ptr, off, incs);
};
};
buf[off] = 58u8; off += 1u64;
off = cstrinto(buf.ptr, off, libdir);
cstrseal(buf.ptr, off);
return buf.ptr;
};
// Mirrors cmd/ww/main.c:resolvemodule. Sets *isdir when the entry is a
// module directory (caller will dir-enumerate).
fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
let nlen: u64 = cstrlen(name);
if (cstrendswithlit(name, ".ww")) {
if (os.access(pathstr(name), 0i32) == 0) {
*isdir = 0;
return arenadupcstr(name, nlen);
};
};
let fi: os.filestat;
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
let found: bool = false;
let foundisdir: i32 = 0;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) { foundisdir = 1; };
found = true;
};
case let e: os.oserror => void;
};
if (found) {
*isdir = foundisdir;
return arenadupcstr(name, nlen);
};
if (reservedimportpath(name)) { return nil; };
let search: *u8 = buildsearchpath(selfdir, incs);
return locatemodule(search, name, nlen, isdir);
};
fn writeusage(fd: i32) void = {
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-I DIR] [-o FILE|DIR] [path ...] build local package graphs\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path ...] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then the source library for foo.ww or foo/\n lib/foo directory: build its package sources\n -o FILE publishes a non-main archive FILE + FILE.wwi\n -o DIR publishes each selected command beneath DIR\n lib/... every eligible package under lib, recursively\n . current directory package (default when no path is given)\n";
os.write(fd, s.ptr, s.len: u64);
};
fn doversion() i32 = {
os.write(1, "ww 0.0\n".ptr, 7u64);
return 0;
};
fn defaultoutpath(src: *u8) *u8 = {
let n: u64 = cstrlen(src);
let start: u64 = 0u64;
let i: u64 = 0u64;
for (i < n) {
if (src[i] == 47u8) { start = i + 1u64; }; // '/'
i += 1u64;
};
let out: []u8 = alloc([], (os.PATH_MAX: u64))!;
out.len = os.PATH_MAX;
let off: u64 = 0u64;
let j: u64 = start;
for (j < n) {
out[off] = src[j];
off += 1u64;
j += 1u64;
};
if (off >= 3u64) {
if (out[off - 3u64] == 46u8) {
if (out[off - 2u64] == 119u8) {
if (out[off - 1u64] == 119u8) {
off -= 3u64;
};
};
};
};
cstrseal(out.ptr, off);
return out.ptr;
};
// Go's build -o directory branch follows an existing destination through
// stat, and a trailing platform separator declares a directory which the
// request may need to create. WW's platform separator is '/'.
fn buildoutputdir(path: *u8) bool = {
if (cstrendswithlit(path, "/")) { return true; };
let fi: os.filestat;
match (os.stat(&fi, pathstr(path))) {
case void => {
let typ: u32 = (fi.mode: u32) & 61440u32;
return typ == os.mode.DIR: u32;
};
case let e: os.oserror => return false;
};
};
fn buildoutputpath(dir: *u8, src: *u8) *u8 = {
let base: *u8 = defaultoutpath(src);
let need: u64 = 0u64;
if (!sepaddbytes(&need, cstrlen(dir))
|| (!cstrendswithlit(dir, "/") && !sepaddbytes(&need, 1u64))
|| !sepaddbytes(&need, cstrlen(base))
|| !sepaddbytes(&need, 1u64)
|| need > os.PATH_MAX: u64) {
return nil;
};
if (cstrendswithlit(dir, "/")) {
return sepappendlit(dir, pathstr(base));
};
return sepjoinpath(dir, base);
};
fn defaultimportoutpath(identity: *u8) *u8 = {
let n: u64 = cstrlen(identity);
let start: u64 = 0u64;
let i: u64 = 0u64;
for (i < n) {
if (identity[i] == 46u8) { start = i + 1u64; };
i += 1u64;
};
let out: []u8 = alloc([], (os.PATH_MAX: u64))!;
out.len = os.PATH_MAX;
let off: u64 = 0u64;
i = start;
for (i < n) { out[off] = identity[i]; off += 1u64; i += 1u64; };
cstrseal(out.ptr, off);
return out.ptr;
};
fn buildcurrentdirspelling(path: *u8) bool = {
if (path[0u64] == 0u8 || path[0u64] == '/': u8) { return false; };
let i: u64 = 0u64;
let found: bool = false;
for (true) {
for (path[i] == '/': u8) { i += 1u64; };
if (path[i] == 0u8) { return found; };
if (path[i] != '.': u8) { return false; };
i += 1u64;
if (path[i] != 0u8 && path[i] != '/': u8) { return false; };
found = true;
};
return false;
};
fn buildlocaldirleaf(resolved: *u8) *u8 = {
let selected: *u8 = resolved;
if (buildcurrentdirspelling(resolved)) {
let canonical: *u8 = canonicaldir(pathstr(resolved));
if (canonical != nil) { selected = canonical; };
};
let rlen: u64 = cstrlen(selected);
for (rlen > 1u64) {
if (selected[rlen - 1u64] != '/': u8) { break; };
rlen -= 1u64;
};
let bo: u64 = basenameoff(selected, rlen);
let outbuf: []u8 = alloc([], (os.PATH_MAX: u64))!;
outbuf.len = os.PATH_MAX;
let out: *u8 = outbuf.ptr;
let i: u64 = bo;
let off: u64 = 0u64;
for (i < rlen) { out[off] = selected[i]; off += 1u64; i += 1u64; };
cstrseal(out, off);
return out;
};
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let src: *u8 = nil;
let srcindex: i32 = -1;
let multipleroots: bool = false;
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
let workdir: *u8 = nil; // -w persistent package-artifact workdir
let emitasm: i32 = 0;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
if (!sepaddbytes(&inccap, cstrlen(argv[capi]))
|| !sepaddbytes(&inccap, 1u64)) { return 1; };
capi += 1;
};
let incs: []u8;
if (!sepmakebytes(inccap, &incs)) { return 1; };
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
let maxlflags: i32 = 32;
let libdirs: [32]*u8;
let nlibdirs: i32 = 0;
let libs: [32]*u8;
let nlibs: i32 = 0;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "--")) {
multipleroots = true;
break;
} else { if (cstreqlit(p, "-S")) {
emitasm = 1;
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -I needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (incoff > 0u64) {
incs[incoff] = 58u8; // ':'
incoff += 1u64;
};
incoff = cstrinto(incs.ptr, incoff, dir);
cstrseal(incs.ptr, incoff);
} else { if (p[1u64] == 76u8) { // '-L'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -L needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (nlibdirs >= maxlflags) {
cerr("ww build: too many -L\n");
return 2;
};
libdirs[nlibdirs] = dir;
nlibdirs += 1;
} else { if (p[1u64] == 108u8) { // '-l'
let nm: *u8 = nil;
if (p[2u64] != 0u8) {
nm = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -l needs an argument\n");
return 2;
};
i += 1;
nm = argv[i];
};
if (nlibs >= maxlflags) {
cerr("ww build: too many -l\n");
return 2;
};
libs[nlibs] = nm;
nlibs += 1;
} else { if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outflag = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -o needs an argument\n");
return 2;
};
i += 1;
outflag = argv[i];
};
if (!clipathfits("build", "-o", outflag)) { return 2; };
} else { if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
workdir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww build: -w needs an argument\n");
return 2;
};
i += 1;
workdir = argv[i];
};
if (!clipathfits("build", "-w", workdir)) { return 2; };
} else {
cerr("ww build: unknown flag\n");
return 2;
}; }; }; }; }; }; };
} else {
if (src == nil) {
src = p;
srcindex = i;
i += 1;
if (i < argc) { multipleroots = true; };
break;
} else { multipleroots = true; break; };
};
i += 1;
};
if (src == nil) {
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
if (multipleroots || strings.contains(pathstr(src), "...")) {
return execpackagetests(selfdir, argv, argc, start, srcindex,
nil, nil, false, true);
};
let requestedliteral: bool = false;
let requestedstat: os.filestat;
match (os.stat(&requestedstat, pathstr(src))) {
case void => requestedliteral = true;
case let e: os.oserror => void;
};
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
if (resolved == nil) {
cerrpath("ww build: cannot find module ", src, "\n");
return 1;
};
let out: *u8 = nil;
let objstem: *u8 = nil;
let discardoutput: bool = outflag != nil
&& cstreqlit(outflag, "/dev/null");
let outputdir: bool = outflag != nil && outflag[0u64] != 0u8
&& !discardoutput && buildoutputdir(outflag);
let rootidentity: *u8 = nil;
if (!requestedliteral && isdir != 0) { rootidentity = src; };
if (outputdir && isdir != 0) {
let coordinatortarget: *u8 = resolved;
if (srcindex < 0) { coordinatortarget = nil; };
return execpackagetests(selfdir, argv, argc, start, srcindex,
coordinatortarget, rootidentity, srcindex < 0, true);
};
let createoutputdir: *u8 = nil;
let outputpatherror: bool = false;
if (outflag != nil && outflag[0u64] != 0u8 && !discardoutput) {
// -o sets both the binary path and the intermediate stem so
// artifacts land beside the requested output (T3).
if (outputdir) {
out = buildoutputpath(outflag, resolved);
if (out == nil) {
outputpatherror = true;
out = defaultoutpath(resolved);
};
createoutputdir = outflag;
} else {
out = outflag;
};
objstem = out;
} else { if (isdir != 0 && rootidentity != nil) {
out = defaultimportoutpath(rootidentity);
} else { if (isdir != 0) {
out = buildlocaldirleaf(resolved);
} else {
out = defaultoutpath(resolved);
}; }; };
if ((outflag == nil || outflag[0u64] == 0u8) && isdir != 0
&& buildcurrentdirspelling(resolved)) {
objstem = out;
};
let defaultoutputdir: *u8 = nil;
if ((outflag == nil || outflag[0u64] == 0u8) && buildoutputdir(out)) {
defaultoutputdir = out;
};
let lf: lflags;
lf.libdirs = &libdirs[0];
lf.nlibdirs = nlibdirs;
lf.libs = &libs[0];
lf.nlibs = nlibs;
if (discardoutput) {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makedrivertmp(tmp.ptr, "ww_build_");
if (os.mkdir(pathstr(tmp.ptr), 448i32) != 0) {
cerr("ww: cannot create temporary directory\n");
return 1;
};
let outp: *u8 = joinpathlit(tmp.ptr, "main");
// Go's null output removes installation, while command linking and
// library compilation still need request-private product paths.
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, false, 0i32, workdir, nil, nil, false);
let cleanbad: bool = false;
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
cleanbad = true;
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
cleanbad = true;
};
if (cleanbad && rc == 0) { rc = 1; };
return rc;
};
let publishpackage: i32 = 0;
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
return buildonesep(selfdir, resolved, isdir, rootidentity,
out, objstem, incs.ptr, &lf,
publishpackage, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, true, 1i32, workdir, createoutputdir, defaultoutputdir,
outputpatherror);
};
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
// folded in decimal manually since this driver does not import strconv.
fn makedrivertmp(buf: *u8, prefix: str) void = {
let off: u64 = 0u64;
off = strinto(buf, off, "/tmp/");
let pk: i32 = 0;
for (pk < prefix.len) {
buf[off] = prefix[pk];
off += 1u64;
pk += 1;
};
let pid: i32 = os.getpid();
let dig: [16]u8;
let n: i32 = 0;
if (pid <= 0) {
dig[n] = 48u8; // '0'
n += 1;
} else {
let v: i32 = pid;
for (v > 0) {
dig[n] = ((v % 10) + 48): u8;
n += 1;
v = v / 10;
};
};
let k: i32 = n - 1;
for (k >= 0) {
buf[off] = dig[k];
off += 1u64;
k -= 1;
};
cstrseal(buf, off);
};
fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let src: *u8 = nil;
let passstart: i32 = -1; // first argv idx to pass through to program
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
if (!sepaddbytes(&inccap, cstrlen(argv[capi]))
|| !sepaddbytes(&inccap, 1u64)) { return 1; };
capi += 1;
};
let incs: []u8;
if (!sepmakebytes(inccap, &incs)) { return 1; };
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
let maxlflags: i32 = 32;
let libdirs: []*u8;
if (!sepmakeptrs(maxlflags, &libdirs)) { return 1; };
let nlibdirs: i32 = 0;
let libs: []*u8;
if (!sepmakeptrs(maxlflags, &libs)) { return 1; };
let nlibs: i32 = 0;
let i: i32 = start;
for (i < argc) {
if (passstart >= 0) { i = argc; } // stop, leave rest for exec
else {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) {
if (p[1u64] == 73u8) {
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww run: -I needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (incoff > 0u64) {
incs[incoff] = 58u8;
incoff += 1u64;
};
incoff = cstrinto(incs.ptr, incoff, dir);
cstrseal(incs.ptr, incoff);
} else { if (p[1u64] == 76u8) {
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww run: -L needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (nlibdirs >= maxlflags) {
cerr("ww run: too many -L\n");
return 2;
};
libdirs[nlibdirs] = dir;
nlibdirs += 1;
} else { if (p[1u64] == 108u8) {
let nm: *u8 = nil;
if (p[2u64] != 0u8) {
nm = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww run: -l needs an argument\n");
return 2;
};
i += 1;
nm = argv[i];
};
if (nlibs >= maxlflags) {
cerr("ww run: too many -l\n");
return 2;
};
libs[nlibs] = nm;
nlibs += 1;
} else { if (p[1u64] == 111u8) { // '-o'
// run always execs the temp binary; -o is accepted+
// ignored, mirroring the C driver's shared flag parser.
if (p[2u64] == 0u8) {
if (i + 1 >= argc) {
cerr("ww run: -o needs an argument\n");
return 2;
};
i += 1;
};
} else {
cerr("ww run: unknown flag\n");
return 2;
}; }; }; };
i += 1;
} else {
if (src == nil) {
src = p;
i += 1;
} else {
passstart = i;
};
};
};
};
if (src == nil) {
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
let requestedliteral: bool = false;
let requestedstat: os.filestat;
match (os.stat(&requestedstat, pathstr(src))) {
case void => requestedliteral = true;
case let e: os.oserror => void;
};
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
if (resolved == nil) {
cerrpath("ww run: cannot find module ", src, "\n");
return 1;
};
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makedrivertmp(tmp.ptr, "ww_run_");
if (os.mkdir(pathstr(tmp.ptr), 448i32) != 0) {
cerr("ww: cannot create temporary directory\n");
return 1;
};
let outp: *u8 = joinpathlit(tmp.ptr, "main");
let lf: lflags;
lf.libdirs = libdirs.ptr;
lf.nlibdirs = nlibdirs;
lf.libs = libs.ptr;
lf.nlibs = nlibs;
// The freshly acquired directory owns both main and main.sepwork.
let rootidentity: *u8 = nil;
if (!requestedliteral && isdir != 0) { rootidentity = src; };
if (buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 1i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
0i32, false, 0i32, nil, nil, nil, false) != 0) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
};
return 1;
};
// The standard process facility inherits stdio and waits only for
// this user program.
let nextra: i32 = 0;
if (passstart >= 0) { nextra = argc - passstart; };
let execargv: []str = alloc([], (nextra + 1): u64)!;
append(execargv, pathstr(outp));
let k: i32 = 0;
for (k < nextra) {
append(execargv, pathstr(argv[passstart + k]));
k += 1;
};
let env: []str = os.getenvs();
let result: exec.result;
exec.runstdio(pathstr(outp), execargv, env, &result);
let rc: i32 = 1;
if (result.termination == exec.termination.EXIT) {
rc = result.code;
} else { if (result.termination == exec.termination.ERROR) {
if (result.code == 127) {
cerr("ww: execve failed\n");
rc = 127;
} else {
cerr("ww: process launch/wait failed\n");
rc = -1;
};
}; };
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
if (rc == 0) { rc = 1; };
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
if (rc == 0) { rc = 1; };
};
return rc;
};
// Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap
// compatibility route; directory/default requests delegate to wwtest.
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
emitasm: i32, outstem: *u8, workdir: *u8, pattern: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
// A retained -o redirects the binary and sepwork intermediates to its stem;
// exact /dev/null needs the no-install path used by Go's test builder.
let outp: *u8 = nil;
let objstem: *u8 = nil;
// A retained output or -w has caller-owned storage; every other binary is
// request-private and must be removed here.
let owntmp: bool = false;
let retainout: bool = outstem != nil
&& !cstreqlit(outstem, "/dev/null");
let deferredinstall: bool = retainout && compileonly == 0
&& emitasm == 0;
if (retainout && !deferredinstall) {
outp = outstem;
objstem = outstem;
} else { if (workdir != nil && !deferredinstall) {
// The workdir owns the persistent test binary the same way it
// owns the package artifacts.
outp = joinpathlit(workdir, "main");
objstem = outp;
} else {
owntmp = true;
makedrivertmp(tmp.ptr, "ww_test_");
if (os.mkdir(pathstr(tmp.ptr), 448i32) != 0) {
cerr("ww: cannot create temporary directory\n");
return 1;
};
outp = joinpathlit(tmp.ptr, "main");
objstem = outp;
if (retainout) { objstem = outstem; };
}; };
// E3-C1: separate compilation is the sole build path (task #87).
let lf: lflags;
lf.libdirs = nil;
lf.nlibdirs = 0;
lf.libs = nil;
lf.nlibs = 0;
let keep: i32 = 0;
if (retainout) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, retainout && !deferredinstall, keep, workdir,
nil, nil, false);
if (bres != 0) {
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
};
};
if (compileonly == 0 && emitasm == 0) {
os.write(1, "FAIL\n".ptr, 5u64);
};
return 1;
};
if (compileonly != 0 || emitasm != 0) {
if (owntmp) {
let cleanbad: bool = false;
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
cleanbad = true;
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
cleanbad = true;
};
if (cleanbad) { return 1; };
};
return 0;
};
// #17 fnmatch filter: forward `pattern` as argv[1] so lib/test run()
// reads it via os.args. cstage twin: run_test_bin.
let execargv: []str = alloc([], 2u64)!;
append(execargv, pathstr(outp));
if (pattern != nil) {
append(execargv, pathstr(pattern));
};
let result: exec.result;
let rc: i32 = 1;
let env: septestenv;
let toolbin: *u8 = canonicaldir(pathstr(selfdir));
if (toolbin == nil) {
cerr("ww: cannot prepare test environment\n");
} else { if (sepmaketestenv(pathstr(toolbin), &env)) {
exec.runstdio(pathstr(outp), execargv, env.values, &result);
sepfreetestenv(&env);
if (result.termination == exec.termination.EXIT) {
rc = result.code;
} else { if (result.termination == exec.termination.ERROR) {
if (result.code == 127) {
cerr("ww: execve failed\n");
rc = 127;
} else {
cerr("ww: process launch/wait failed\n");
rc = -1;
};
}; };
}; };
if (toolbin != nil) {
os.free(toolbin: *void, cstrlen(toolbin) + 1u64);
};
let testfailed: bool = rc != 0;
if (rc == 0 && deferredinstall
&& sepinstalltestoutput(outp, outstem) != 0) { rc = 1; };
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
if (rc == 0) { rc = 1; };
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
if (rc == 0) { rc = 1; };
};
};
if (testfailed) { os.write(1, "FAIL\n".ptr, 5u64); };
return rc;
};
fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (argc - start == 3
&& cstreqlit(argv[start], "--ww-install-test-output")) {
return sepinstalltestoutput(argv[start + 1], argv[start + 2]);
};
// -I parsing mirrors dobuild/dorun so a single-file test can resolve
// transitive imports (e.g. 905_nkname asttest → tok); coupled to the
// -T flip (task #5/#10).
let target: *u8 = nil;
let targetindex: i32 = -1;
// #17: optional 2nd positional = fnmatch name-filter pattern, forwarded
// to the test binary as argv[1] (single-file/module only; dir-mode
// rejects). cstage twin: do_test `pattern`.
let patarg: *u8 = nil;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
if (!sepaddbytes(&inccap, cstrlen(argv[capi]))
|| !sepaddbytes(&inccap, 1u64)) { return 1; };
capi += 1;
};
let incs: []u8;
if (!sepmakebytes(inccap, &incs)) { return 1; };
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
// -c (compile-only) + -o <stem>: build the test binary + its lib/test-
// inclusive sep unit WITHOUT running it, for the byte-id gates. cstage
// twin: cmd/ww/main.c do_test (error wording identical).
let compileonly: i32 = 0;
let emitasm: i32 = 0;
let outstem: *u8 = nil;
let workdir: *u8 = nil;
let requestidentity: *u8 = nil;
let products: []sepproduct;
let packageopts: bool = false;
let packagebuild: bool = false;
let packagepublish: bool = false;
let packagecreateworkdir: bool = false;
let packagecreateoutputdir: *u8 = nil;
let packageoutputpatherror: bool = false;
let packagedefaultoutputdir: *u8 = nil;
let packageoutputcollisionbase: *u8 = nil;
let packageoutputcollisiondir: *u8 = nil;
let maxpackagelflags: i32 = 32;
let packagelibdirs: [32]*u8;
let packagelibs: [32]*u8;
let packagelinks: lflags;
packagelinks.libdirs = &packagelibdirs[0];
packagelinks.nlibdirs = 0;
packagelinks.libs = &packagelibs[0];
packagelinks.nlibs = 0;
let afterdash: bool = false;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
if (afterdash) { i += 1; continue; };
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "--")) {
packageopts = true; afterdash = true; i += 1; continue;
};
if (cstreqlit(p, "--ww-root-identity")) {
if (i + 1 >= argc || requestidentity != nil
|| argv[i + 1][0u64] == 0u8
|| reservedimportpath(argv[i + 1])) {
cerr("ww test: invalid --ww-root-identity\n");
return 2;
};
requestidentity = argv[i + 1];
i += 2;
continue;
};
if (cstreqlit(p, "--ww-package-build")) {
if (packagebuild) {
cerr("ww test: invalid --ww-package-build\n");
return 2;
};
packagebuild = true;
compileonly = 1;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-package-publish")) {
if (packagepublish) {
cerr("ww test: invalid --ww-package-publish\n");
return 2;
};
packagepublish = true;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-create-workdir")) {
if (packagecreateworkdir) {
cerr("ww test: invalid --ww-create-workdir\n");
return 2;
};
packagecreateworkdir = true;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-create-output-dir")) {
if (i + 1 >= argc || packagecreateoutputdir != nil
|| argv[i + 1][0u64] == 0u8) {
cerr("ww test: invalid --ww-create-output-dir\n");
return 2;
};
packagecreateoutputdir = argv[i + 1];
i += 2;
continue;
};
if (cstreqlit(p, "--ww-command-output-path-error")) {
if (packageoutputpatherror) {
cerr("ww test: invalid --ww-command-output-path-error\n");
return 2;
};
packageoutputpatherror = true;
i += 1;
continue;
};
if (cstreqlit(p, "--ww-default-output-dir")) {
if (i + 1 >= argc || packagedefaultoutputdir != nil
|| argv[i + 1][0u64] == 0u8) {
cerr("ww test: invalid --ww-default-output-dir\n");
return 2;
};
packagedefaultoutputdir = argv[i + 1];
i += 2;
continue;
};
if (cstreqlit(p, "--ww-command-output-collision")) {
if (i + 2 >= argc || packageoutputcollisionbase != nil
|| argv[i + 1][0u64] == 0u8
|| argv[i + 2][0u64] == 0u8) {
cerr("ww test: invalid --ww-command-output-collision\n");
return 2;
};
packageoutputcollisionbase = argv[i + 1];
packageoutputcollisiondir = argv[i + 2];
i += 3;
continue;
};
if (cstreqlit(p, "--ww-package-test")) {
if (i + 9 >= argc) {
cerr("ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, publication, and status\n");
return 2;
};
let kind: *u8 = argv[i + 1];
let name: *u8 = argv[i + 2];
let production: *u8 = argv[i + 3];
let internal: *u8 = argv[i + 4];
let external: *u8 = argv[i + 5];
let dir: *u8 = argv[i + 6];
let output: *u8 = argv[i + 7];
let publish: *u8 = argv[i + 8];
let status: *u8 = argv[i + 9];
let pn: u64 = cstrlen(name);
let publicbuildproduct: bool =
cstreqlit(kind, "build-public");
let buildproduct: bool = cstreqlit(kind, "build")
|| publicbuildproduct;
let testproduct: bool = cstreqlit(kind, "test");
let hasproduction: bool = !cstreqlit(production, "-");
let hasinternal: bool = !cstreqlit(internal, "-");
let hasexternal: bool = !cstreqlit(external, "-");
if ((!buildproduct && !testproduct)
|| pn == 0u64
|| dir[0u64] == 0u8 || output[0u64] == 0u8
|| publish[0u64] == 0u8
|| status[0u64] == 0u8
|| (hasproduction && !cstreq(production, name))
|| (hasinternal && !cstreq(internal, name))
|| (hasexternal && (cstrlen(external) != pn + 5u64
|| !strings.hasprefix(pathstr(external), pathstr(name))
|| !cstrendswithlit(external, "_test")))
|| (buildproduct && (!hasproduction
|| hasinternal || hasexternal
|| !cstreqlit(publish, "-")))
|| (testproduct && !hasproduction
&& !hasinternal && !hasexternal)
|| (testproduct && !hasinternal && !hasexternal
&& !cstreqlit(publish, "-"))) {
cerr("ww test: invalid --ww-package-test product\n");
return 2;
};
let product: sepproduct;
product.dir = dir;
product.out = output;
product.identity = nil;
product.testpackage = name;
product.productionpackage = nil;
if (hasproduction) { product.productionpackage = production; };
product.internalpackage = nil;
if (hasinternal) { product.internalpackage = internal; };
product.externalpackage = nil;
if (hasexternal) { product.externalpackage = external; };
product.status = status;
product.publish = nil;
if (!cstreqlit(publish, "-")) { product.publish = publish; };
product.artifact = nil;
product.variant = SEP_VARIANT_TEST_MAIN;
if (buildproduct) { product.variant = SEP_VARIANT_PRODUCTION; };
product.directoryproduct = true;
product.notests = testproduct && !hasinternal && !hasexternal;
product.buildaction = true;
product.publicout = publicbuildproduct;
product.root = -1;
product.variantroot = -1;
product.productionroot = -1;
product.ptest = -1;
product.pxtest = -1;
product.support = -1;
if (products.len == SEP_COUNT_MAX) {
sepfailsize();
return 1;
};
if (!sepreserveproducts(&products,
products.len + 1)) { return 1; };
append(products, product);
i += 10;
continue;
};
if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -I needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (incoff > 0u64) {
incs[incoff] = 58u8; // ':'
incoff += 1u64;
};
incoff = cstrinto(incs.ptr, incoff, dir);
cstrseal(incs.ptr, incoff);
i += 1; continue;
};
if (cstreqlit(p, "-c")) { compileonly = 1; i += 1; continue; };
if (cstreqlit(p, "-S")) { emitasm = 1; i += 1; continue; };
if (!packagebuild && cstreqlit(p, "-list")) {
packageopts = true; i += 1; continue;
};
if (packagebuild && p[1u64] == 76u8) { // '-L'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
dir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -L needs an argument\n");
return 2;
};
i += 1;
dir = argv[i];
};
if (packagelinks.nlibdirs >= maxpackagelflags) {
cerr("ww test: too many -L\n");
return 2;
};
packagelibdirs[packagelinks.nlibdirs] = dir;
packagelinks.nlibdirs += 1;
i += 1; continue;
};
if (packagebuild && p[1u64] == 108u8) { // '-l'
let name: *u8 = nil;
if (p[2u64] != 0u8) {
name = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -l needs an argument\n");
return 2;
};
i += 1;
name = argv[i];
};
if (packagelinks.nlibs >= maxpackagelflags) {
cerr("ww test: too many -l\n");
return 2;
};
packagelibs[packagelinks.nlibs] = name;
packagelinks.nlibs += 1;
i += 1; continue;
};
if (cstreqlit(p, "-j") || cstreqlit(p, "-run")
|| cstreqlit(p, "-filter")) {
if (i + 1 >= argc) {
cerr("ww test: "); os.write(2, p, cstrlen(p));
cerr(" needs an argument\n"); return 2;
};
packageopts = true; i += 2; continue;
};
let ps: str = pathstr(p);
if (strings.hasprefix(ps, "-timeout-ms=")
&& ps.len > 12) {
packageopts = true; i += 1; continue;
};
if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outstem = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -o needs an argument\n");
return 2;
};
i += 1;
outstem = argv[i];
};
if (!clipathfits("test", "-o", outstem)) { return 2; };
i += 1; continue;
};
if (p[1u64] == 119u8) { // '-w'
if (p[2u64] != 0u8) {
workdir = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -w needs an argument\n");
return 2;
};
i += 1;
workdir = argv[i];
};
if (!clipathfits("test", "-w", workdir)) { return 2; };
i += 1; continue;
};
cerr("ww test: unknown flag\n"); return 2;
} else {
if (target == nil) { target = p; targetindex = i; }
else { if (patarg == nil) { patarg = p; }; };
};
i += 1;
};
if (target == nil) {
let dot: [2]u8 = ['.': u8, 0u8];
target = &dot[0];
};
if (emitasm != 0 && outstem == nil && products.len == 0) {
cerr("ww test: -S needs -o\n");
return 2;
};
let producti: i32 = 1;
for (producti < products.len) {
let product: sepproduct = products[producti];
let j: i32 = producti;
for (j > 0 && strings.compare(pathstr(products[j - 1].dir),
pathstr(product.dir)) > 0) {
products[j] = products[j - 1];
j -= 1;
};
products[j] = product;
producti += 1;
};
producti = 0;
for (producti < products.len) {
if (producti > 0
&& cstreq(products[producti - 1].dir, products[producti].dir)) {
cerr("ww test: duplicate --ww-package-test product for directory\n");
return 2;
};
// Artifact identity is finalized from canonical package identity.
products[producti].artifact = nil;
producti += 1;
};
if (products.len != 0 && packageopts) {
cerr("ww test: package-test variant rejects package options\n");
return 2;
};
if (packagebuild && products.len == 0) {
cerr("ww test: --ww-package-build needs products\n");
return 2;
};
if (packagepublish && (!packagebuild || products.len != 1)) {
cerr("ww test: invalid --ww-package-publish\n");
return 2;
};
if (packagecreateoutputdir != nil && products.len == 0) {
cerr("ww test: invalid private directory creation\n");
return 2;
};
if ((packageoutputpatherror || packagedefaultoutputdir != nil
|| packageoutputcollisionbase != nil)
&& (!packagebuild || products.len == 0)) {
cerr("ww test: invalid private output preflight\n");
return 2;
};
if (packagecreateworkdir && products.len == 0) {
cerr("ww test: invalid private directory creation\n");
return 2;
};
if (packagecreateworkdir && workdir == nil) {
cerr("ww test: --ww-create-workdir needs -w\n");
return 2;
};
if (!packagebuild && (packagelinks.nlibdirs != 0
|| packagelinks.nlibs != 0)) {
cerr("ww test: unknown flag\n");
return 2;
};
if (packagebuild) {
producti = 0;
for (producti < products.len) {
if (products[producti].variant != SEP_VARIANT_PRODUCTION) {
cerr("ww test: package-build products must be production\n");
return 2;
};
producti += 1;
};
} else {
producti = 0;
for (producti < products.len) {
if (products[producti].directoryproduct
&& products[producti].variant == SEP_VARIANT_PRODUCTION) {
cerr("ww test: package-test products must use test kind\n");
return 2;
};
producti += 1;
};
};
if (patarg != nil) {
let first: os.filestat;
let firstregular: bool = false;
let firstfound: bool = false;
match (os.stat(&first, pathstr(target))) {
case void => {
firstfound = true;
firstregular = (((first.mode: u32) & 61440u32)
== (os.mode.REG: u32));
};
case let e: os.oserror => void;
};
if (!firstregular) {
let firstresolved: *u8 = nil;
let firstisdir: i32 = 0;
if (!firstfound) {
firstresolved = resolvemodule(selfdir, target, incs.ptr,
&firstisdir);
};
if (firstfound || firstresolved == nil || firstisdir != 0) {
let replacement: *u8 = nil;
let identity: *u8 = requestidentity;
if (firstisdir != 0) {
replacement = firstresolved;
identity = target;
};
return execpackagetests(selfdir, argv, argc, start,
targetindex, replacement, identity, false, false);
};
// A logical import resolving to one source retains patarg as its
// historical test-name filter and follows the single-file path.
};
};
if (products.len != 0 && outstem != nil) {
cerr("ww test: package-test products reject -o\n");
return 2;
};
// A local spelling containing "..." enters request selection before
// literal-path or logical-import resolution.
let istree: bool = strings.contains(pathstr(target), "...");
if (istree) {
if (products.len != 0) {
cerr("ww test: package-test variant needs one directory\n");
return 2;
};
if (emitasm != 0) {
cerr("ww test: -S needs a single test file\n");
return 2;
};
// The coordinator independently wires -o retention and -c run
// suppression after loading the complete package set.
// -w forwards one caller-owned semantic-action store shared by
// the complete selected package universe.
return execpackagetests(selfdir, argv, argc, start,
targetindex, nil, nil, false, false);
};
let resolved: *u8 = target;
let isdir: i32 = 0;
let found: bool = false;
let fi: os.filestat;
match (os.stat(&fi, pathstr(target))) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32;
if (t == os.mode.DIR: u32) { isdir = 1; found = true; }
else { if (t == os.mode.REG: u32) { found = true; }; };
};
case let e: os.oserror => void;
};
let requestedliteral: bool = found;
if (!found) {
resolved = resolvemodule(selfdir, target, incs.ptr, &isdir);
if (resolved == nil) {
cerr("ww test: cannot find ");
os.write(2, target, cstrlen(target)); cerr("\n");
if (targetindex >= 0 && compileonly == 0 && emitasm == 0) {
os.write(1, "FAIL\n".ptr, 5u64);
};
return 1;
};
};
let rootidentity: *u8 = requestidentity;
if (!requestedliteral && rootidentity == nil && isdir != 0) {
rootidentity = target;
};
if (isdir == 0) {
if (products.len != 0) {
cerr("ww test: package-test variant needs one directory\n");
return 2;
};
if (packageopts) {
cerr("ww test: package options need a directory\n"); return 2;
};
return runsingletest(selfdir, resolved, incs.ptr, compileonly,
emitasm, outstem, workdir, patarg);
};
if (emitasm != 0 && products.len == 0) {
cerr("ww test: -S needs a single test file\n");
return 2;
};
if (products.len != 0) {
if (compileonly == 0) {
cerr("ww test: package-test products need -c\n");
return 2;
};
let packagemode: i32 = 1;
if (packagebuild) { packagemode = 0; };
let publishmode: i32 = 0;
if (packagepublish) { publishmode = 1; };
return buildpackagetests(selfdir, resolved, rootidentity,
incs.ptr, workdir,
products.ptr, products.len, packagemode, publishmode,
&packagelinks, emitasm, packagecreateworkdir,
packagecreateoutputdir, packagedefaultoutputdir,
packageoutputpatherror, packageoutputcollisionbase,
packageoutputcollisiondir);
};
let replacement: *u8 = nil;
if (resolved != target) { replacement = resolved; };
return execpackagetests(selfdir, argv, argc, start, targetindex,
replacement, rootidentity, targetindex < 0, false);
};
export fn main(argc: i32, argv: **u8) i32 = {
if (argc < 1) {
writeusage(2);
return 2;
};
selfpath = argv[0];
let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!;
selfdir.len = os.PATH_MAX;
selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]);
if (argc < 2) {
writeusage(2);
return 2;
};
let cmd: *u8 = argv[1];
if (cstreqlit(cmd, "-V")) { return doversion(); };
if (cstreqlit(cmd, "version")) { return doversion(); };
if (cstreqlit(cmd, "-h")) {
writeusage(1);
return 0;
};
if (cstreqlit(cmd, "--help")) {
writeusage(1);
return 0;
};
if (cstreqlit(cmd, "build")) {
return dobuild(selfdir.ptr, argv, argc, 2);
};
if (cstreqlit(cmd, "run")) {
return dorun(selfdir.ptr, argv, argc, 2);
};
if (cstreqlit(cmd, "test")) {
return dotest(selfdir.ptr, argv, argc, 2);
};
cerr("ww: unknown subcommand: ");
os.write(2, cmd, cstrlen(cmd));
cerr("\n");
writeusage(2);
return 2;
};