9934 lines
312 KiB
Plaintext
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;
|
|
};
|