cmd: build local package graphs

This commit is contained in:
2026-08-11 22:26:07 +09:00
parent db96422f74
commit 52f6d6f0d4
6 changed files with 1316 additions and 510 deletions

View File

@@ -17,6 +17,7 @@ import os;
import os.exec;
import rt;
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
@@ -33,6 +34,25 @@ fn cerr(m: str) void = {
os.write(2, m.ptr, m.len: u64);
};
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; };
@@ -386,6 +406,22 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
s.len = nlen: i32;
if (!strings.hassuffix(s, ".ww")) { return 0; };
if (strings.hassuffix(s, "_test.ww")) { return 0; };
let source: *u8 = joinpath(dirpath, name);
let fi: os.filestat;
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
let regular: bool = false;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32;
if (t == os.mode.REG: u32) { regular = true; };
};
case let e: os.oserror => void;
};
if (!regular) {
cerr("ww: "); cerr(pathstr(source));
cerr(": package source is not a regular file\n");
return -2;
};
if (dirfileattest(dirpath, name)) { return -1; };
return 1;
};
@@ -438,13 +474,17 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
let nm: *u8 = buf.ptr + off + 19u64;
let nl: u64 = cstrlen(nm);
let cls: i32 = dirfileclass(dirpath, nm, nl);
if (cls < 0) {
if (cls == -1) {
cerr("ww: ");
cerr(pathstr(joinpath(dirpath, nm)));
cerr(": @test declaration outside *_test.ww\n");
os.close(fd);
return nil: **u8, -2;
};
if (cls == -2) {
os.close(fd);
return nil: **u8, -2;
};
if (cls > 0) {
if (n >= cap) {
let ncap: i32 = cap * 2;
@@ -527,59 +567,17 @@ fn slurp(pathcs: *u8) (*u8, u64) = {
let buf: []u8 = alloc([], nu + 1u64)!;
buf.len = (nu + 1u64): i32;
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu);
os.close(fd);
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) { return nil, 0u64; };
if (got != n || closed != 0) { return nil, 0u64; };
buf[nu] = 0u8;
return buf.ptr, nu;
};
fn isidentbyte(c: u8) bool = {
if (c >= 'a' && c <= 'z') { return true; };
if (c >= 'A' && c <= 'Z') { return true; };
if (c >= '0' && c <= '9') { return true; };
if (c == '_') { return true; };
if (c == '.') { return true; };
return false;
};
// Scan one `import IDENT;` line out of [start, end). Returns the start
// of the ident and its length, or (nil, 0) if no `import` here. The
// caller passes a slice of the source: src points at the line start.
fn scanuse(src: *u8, len: u64) (*u8, u64) = {
let i: u64 = 0u64;
for (i < len) {
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
i += 1u64;
};
// i+7>len guard kept: hasprefix("import") covers the 6 spell-out
// bytes, but the sep read at src[i+6] still needs i+6 < len.
if (i + 7u64 > len) { return nil, 0u64; };
let rest: str;
rest.ptr = src + i;
rest.len = (len - i): i32;
if (!strings.hasprefix(rest, "import")) { return nil, 0u64; };
let sep: u8 = src[i + 6u64];
if (sep != 32u8) { if (sep != 9u8) { return nil, 0u64; }; };
i += 7u64;
for (i < len) {
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
i += 1u64;
};
let idstart: u64 = i;
for (i < len) {
if (!isidentbyte(src[i])) { break; };
i += 1u64;
};
let idlen: u64 = i - idstart;
if (idlen == 0u64) { return nil, 0u64; };
return src + idstart, idlen;
};
fn makestem(stem: *u8, src: *u8) void = {
let n: u64 = cstrlen(src);
let stop: u64 = n;
@@ -632,8 +630,10 @@ type lflags = struct {
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
// closure of a package's deps (lead-ratified): a dep's interface can
// name a transitive dep's type, so the consuming unit needs the whole
// closure for resolution. The unit composition is byte-identical to the
// cstage driver (rule 10) so w6c/w6c_ww emit identical `.s`.
// closure for resolution. Qualifier lookup remains scoped to the direct
// imports owned by each source package, so those type facts do not expose
// a transitive package. The unit composition is byte-identical to the cstage
// driver (rule 10) so w6c/w6c_ww emit identical `.s`.
def SEP_MAXPKG: i32 = 256;
@@ -655,9 +655,30 @@ type sepgraph = struct {
};
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
if (cstrlen(path) >= 256u64) {
cerr("ww: package path is too long (limit 255 bytes)\n");
return -1;
};
let i: i32 = 0;
for (i < g.n) {
if (cstreq(g.pkg[i].path, path)) { return i; };
if (cstreq(g.pkg[i].path, path)) {
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
cerr("ww: package "); cerr(pathstr(path));
cerr(" resolves to more than one location\n");
return -1;
};
return i;
};
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
cerr("ww: one package directory has identities ");
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[i].path)); };
cerr(" and ");
if (path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(path)); };
cerr("\n");
return -1;
};
i += 1;
};
if (g.n >= SEP_MAXPKG) {
@@ -723,140 +744,6 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
return buf.ptr;
};
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
// genuinely-missing import from one satisfied by an INLINE package in the
// same single-file multi-package unit (`package aa; ... package main;
// import aa;`). Scans EVERY line (comment-skip) — not just the first
// package decl. Decision byte-identical to
// cstage so the skip/fatal choice + driver output match (rule 10).
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
let p: u64 = 0u64;
for (p < buflen) {
let q: u64 = p;
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
let s: u64 = p;
for (s < q) {
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
s += 1u64;
};
if (s < q) {
let line: str;
line.ptr = buf + s;
line.len = (q - s): i32;
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
if (s + 8u64 <= q) {
if (strings.hasprefix(line, "package")) {
let sep: u8 = buf[s + 7u64];
let oksep: bool = false;
if (sep == 32u8) { oksep = true; }
else { if (sep == 9u8) { oksep = true; }; };
if (oksep) {
let t: u64 = s + 8u64;
for (t < q) {
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
t += 1u64;
};
let m: u64 = 0u64;
let eq: bool = true;
for (m < leafn) {
if (t + m >= q) { eq = false; break; };
if (buf[t + m] != leafp[m]) { eq = false; break; };
m += 1u64;
};
if (eq) {
let after: u64 = t + leafn;
let term: bool = false;
if (after >= q) { term = true; }
else {
let c: u8 = buf[after];
if (c == 59u8) { term = true; } // ';'
else { if (c == 32u8) { term = true; }
else { if (c == 9u8) { term = true; }; }; };
};
if (term) { return true; };
};
};
};
};
};
p = q + 1u64;
};
return false;
};
fn sepidentstart(c: u8) bool = {
if (c >= 'a' && c <= 'z') { return true; };
if (c >= 'A' && c <= 'Z') { return true; };
return c == '_';
};
fn sepidentcontinue(c: u8) bool = {
if (sepidentstart(c)) { return true; };
return c >= '0' && c <= '9';
};
// Skip the whitespace and comments accepted before and within the leading
// package clause. This is deliberately only the loader's small header
// grammar, not a second compiler lexer.
fn sepskipspace(src: *u8, n: u64, start: u64, ok: *bool) u64 = {
let i: u64 = start;
*ok = true;
for (true) {
for (i < n && (src[i] == ' ' || src[i] == '\t'
|| src[i] == '\r' || src[i] == '\n')) { i += 1u64; };
if (i + 1u64 < n && src[i] == '/' && src[i + 1u64] == '/') {
i += 2u64;
for (i < n && src[i] != '\n') { i += 1u64; };
continue;
};
if (i + 1u64 < n && src[i] == '/' && src[i + 1u64] == '*') {
i += 2u64;
let closed: bool = false;
for (i + 1u64 < n) {
if (src[i] == '*' && src[i + 1u64] == '/') {
i += 2u64;
closed = true;
break;
};
i += 1u64;
};
if (!closed) { *ok = false; return i; };
continue;
};
break;
};
return i;
};
// Parse exactly the leading loader grammar `package ident;`.
fn seppackageclause(src: *u8, n: u64, outp: **u8, outn: *u64) bool = {
let ok: bool = true;
let i: u64 = sepskipspace(src, n, 0u64, &ok);
if (!ok || i + 7u64 >= n) { return false; };
let word: str = "package";
let j: i32 = 0;
for (j < word.len) {
let ju: u64 = j: u64;
if (src[i + ju] != word[j]) { return false; };
j += 1;
};
i += 7u64;
if (i >= n || !(src[i] == ' ' || src[i] == '\t'
|| src[i] == '\r' || src[i] == '\n')) { return false; };
i = sepskipspace(src, n, i, &ok);
if (!ok || i >= n || !sepidentstart(src[i])) { return false; };
let begin: u64 = i;
i += 1u64;
for (i < n && sepidentcontinue(src[i])) { i += 1u64; };
let end: u64 = i;
i = sepskipspace(src, n, i, &ok);
if (!ok || i >= n || src[i] != ';') { return false; };
*outp = src + begin;
*outn = end - begin;
return true;
};
// 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
@@ -877,44 +764,109 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
cerr("ww: cannot read source\n");
return -1;
};
if (ownedsource != 0) {
let declared: *u8 = nil;
let declaredn: u64 = 0u64;
if (!seppackageclause(bufp, blen, &declared, &declaredn)) {
cerr("ww: ");
cerr(pathstr(file));
cerr(": invalid or missing package clause\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 = arenadupcstr(declared, declaredn);
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
declared, declaredn) != 0) {
cerr("ww: ");
cerr(pathstr(g.pkg[pi].entry));
cerr(": conflicting package names ");
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("\n");
cerr(" in "); cerr(pathstr(g.pkg[pi].entry)); cerr("\n");
return -1;
}; };
};
let i: u64 = 0u64;
for (i < blen) {
let j: u64 = i;
for (j < blen) {
if (bufp[j] == 10u8) { break; }; // '\n'
j += 1u64;
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 idp: *u8;
let idn: u64;
idp, idn = scanuse(bufp + i, j - i);
if (idp != nil) {
};
let nuse: i32 = 0;
let u: *syntax.node = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { nuse += 1; };
u = u.next;
};
let uses: []*syntax.node = [];
if (nuse > 0) {
let allocated: []*syntax.node = alloc([], nuse: u64)!;
uses = allocated;
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 (strings.compare(uses[sj - 1].usepath,
uses[sj].usepath) <= 0) { sj = 0; }
else {
let t: *syntax.node = uses[sj];
uses[sj] = uses[sj - 1];
uses[sj - 1] = t;
sj -= 1;
};
};
si += 1;
};
let previous: str = "";
ui = 0;
for (ui < nuse) {
u = uses[ui];
let duplicate: bool = previous.len > 0
&& syntax.streq(previous, u.usepath);
if (!duplicate) {
previous = u.usepath;
let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64;
if (idn >= 256u64) {
cerrpos(u.file, u.line, u.col);
cerr(": error: import path is too long (limit 255 bytes)\n");
return -1;
};
let isdir: i32 = 0;
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
if (ipath != nil) {
if (isdir != 0) {
if (os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry))) {
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].name)); };
cerr("' cannot import itself\n");
return -1;
};
let nm: []u8 = alloc([], idn + 1u64)!;
let k: u64 = 0u64;
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
@@ -938,14 +890,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
};
};
} else {
// #16 ENFORCE-driver (rob A): a locate-miss is legal
// when the package is defined INLINE in the same unit
// (single-file multi-package). leaf = last dotted
// component; inline `package <leaf>` -> skip (the
// checker binds it), else fatal. E3-C1 (#87): the
// legacy amalgamator that owned the genuine-missing
// case is gone, so the sep producer enforces it here
// (INV-2). cstage twin; fatal text identical.
let lstart: u64 = 0u64;
let lk: u64 = 0u64;
for (lk < idn) {
@@ -954,15 +898,23 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
};
let leafp: *u8 = idp + lstart;
let leafn: u64 = idn - lstart;
if (!unithaspackage(bufp, blen, leafp, leafn)) {
cerr("ww: cannot find package ");
let inlinepackage: bool = false;
let pm: *syntax.node = imports.body;
for (pm != nil) {
if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64,
leafp, leafn) == 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;
};
};
};
i = j + 1u64;
ui += 1;
};
return 0;
};
@@ -1028,6 +980,24 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
rc = sepscanfile(g, pi, g.pkg[pi].entry, searchpath, &fv, 0);
};
if (rc < 0) { return rc; };
if (pi == 0 && g.pkg[pi].path[0u64] == 0u8
&& g.pkg[pi].name != nil) {
g.pkg[pi].path = arenadupcstr(g.pkg[pi].name,
cstrlen(g.pkg[pi].name));
};
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 && strings.compare(
pathstr(g.pkg[g.pkg[pi].deps[sj - 1]].path),
pathstr(g.pkg[v].path)) > 0) {
g.pkg[pi].deps[sj] = g.pkg[pi].deps[sj - 1];
sj -= 1;
};
g.pkg[pi].deps[sj] = v;
si += 1;
};
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
if (seploadpkg(g, g.pkg[pi].deps[k], searchpath) < 0) { return -1; };
@@ -1093,55 +1063,106 @@ fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
// //ww:module-reset primary boundary (so -c emits its decls, imported
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
// FILE imports fold in (intra-package split).
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8, modpath: *u8) void = {
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, visit: *expctx, searchpath: *u8,
modpath: *u8) i32 = {
let pview: str;
pview.ptr = path;
pview.len = cstrlen(path): i32;
let pdup: str = strings.dup(pview);
if (visitseen(visit, pdup)) { return; };
if (visitseen(visit, pdup)) { return 0; };
visitadd(visit, pdup);
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(path);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return;
return -1;
};
let i: u64 = 0u64;
for (i < blen) {
let j: u64 = i;
for (j < blen) {
if (bufp[j] == 10u8) { break; }; // '\n'
j += 1u64;
let l: syntax.lex;
syntax.lexinit(&l, pdup, 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; };
let nuse: i32 = 0;
let u: *syntax.node = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { nuse += 1; };
u = u.next;
};
let uses: []*syntax.node = [];
if (nuse > 0) {
let allocated: []*syntax.node = alloc([], nuse: u64)!;
uses = allocated;
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 (strings.compare(uses[sj - 1].usepath,
uses[sj].usepath) <= 0) { sj = 0; }
else {
let t: *syntax.node = uses[sj];
uses[sj] = uses[sj - 1];
uses[sj - 1] = t;
sj -= 1;
};
};
let idp: *u8;
let idn: u64;
idp, idn = scanuse(bufp + i, j - i);
if (idp != nil) {
si += 1;
};
ui = 0;
for (ui < nuse) {
u = uses[ui];
let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64;
let isdir: i32 = 0;
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
if (ipath != nil) {
if (isdir == 0) {
sepemitbody(fd, ipath, visit, searchpath, modpath);
if (sepemitbody(fd, ipath, visit, searchpath,
modpath) < 0) { return -1; };
};
};
};
i = j + 1u64;
ui += 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 ";
os.writeall(fd, dm.ptr, dm.len: u64);
os.writeall(fd, modpath, cstrlen(modpath));
os.writeall(fd, "\n".ptr, 1u64);
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";
os.writeall(fd, d.ptr, d.len: u64);
if (!sepwriteall(fd, d.ptr, d.len: u64)) {
cerr("ww: cannot write package unit\n");
return -1;
};
};
os.writeall(fd, bufp, blen);
os.writeall(fd, "\n".ptr, 1u64);
if (!sepwriteall(fd, bufp, blen)
|| !sepwriteall(fd, "\n".ptr, 1u64)) {
cerr("ww: cannot write package unit\n");
return -1;
};
return 0;
};
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
@@ -1175,11 +1196,16 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
return -1;
};
let dm: str = "//ww:module ";
os.writeall(u, dm.ptr, dm.len: u64);
os.writeall(u, g.pkg[dj].path, cstrlen(g.pkg[dj].path));
os.writeall(u, "\n".ptr, 1u64);
os.writeall(u, wb, wn);
os.writeall(u, "\n".ptr, 1u64);
if (!sepwriteall(u, dm.ptr, dm.len: u64)
|| !sepwriteall(u, g.pkg[dj].path,
cstrlen(g.pkg[dj].path))
|| !sepwriteall(u, "\n".ptr, 1u64)
|| !sepwriteall(u, wb, wn)
|| !sepwriteall(u, "\n".ptr, 1u64)) {
cerr("ww: cannot compose package unit\n");
os.close(u);
return -1;
};
};
};
oi += 1;
@@ -1188,18 +1214,23 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
bv.out = u;
bv.dirs = searchpath;
bv.visit = nil;
let bodyrc: i32 = 0;
if (g.pkg[pi].isdir != 0) {
let i: i32 = 0;
for (i < g.pkg[pi].nsources) {
sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
for (i < g.pkg[pi].nsources && bodyrc == 0) {
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
g.pkg[pi].path);
i += 1;
};
} else {
sepemitbody(u, g.pkg[pi].entry, &bv, searchpath, g.pkg[pi].path);
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
g.pkg[pi].path);
};
os.close(u);
return 0;
if (os.close(u) != 0) {
cerr("ww: cannot close package unit\n");
return -1;
};
return bodyrc;
};
// archiveo — twin of cmd/ww/main.c archive_o. Writes a deterministic
@@ -1268,8 +1299,12 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
cerr("ww: cannot open archive\n");
return -1;
};
os.writeall(fd, out, total);
os.close(fd);
let bad: bool = !sepwriteall(fd, out, total);
if (os.close(fd) != 0) { bad = true; };
if (bad) {
cerr("ww: cannot write archive\n");
return -1;
};
return 0;
};
@@ -1375,12 +1410,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
else { if (n == 0) { done = true; }
else {
match (os.writeall(out, buf.ptr, n: u64)) {
case let w: i64 => void;
case let w: i64 => {
if (w != n) { bad = true; done = true; };
};
case let e: os.oserror => { bad = true; done = true; };
};
}; };
};
os.close(in);
if (os.close(in) != 0) { bad = true; };
if (os.close(out) != 0) { bad = true; };
if (bad) { return -1; };
return os.rename(pathstr(tmpp), pathstr(dst));
@@ -1427,7 +1464,7 @@ fn writestampatomic(path: *u8, want: str) i32 = {
if (fd < 0) { return -1; };
let bad: bool = false;
match (os.writeall(fd, want.ptr, want.len: u64)) {
case let w: i64 => void;
case let w: i64 => { if (w != want.len: i64) { bad = true; }; };
case let e: os.oserror => { bad = true; };
};
if (os.close(fd) != 0) { bad = true; };
@@ -1444,9 +1481,11 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
cerr(tail);
};
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, emitasm: i32,
workdir: *u8, scratchout: **u8, graphout: **sepgraph) i32 = {
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
emitasm: i32, workdir: *u8, scratchout: **u8,
graphout: **sepgraph) i32 = {
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
@@ -1588,7 +1627,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
pkgslot.len = SEP_MAXPKG;
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
if (graphout != nil) { *graphout = g; };
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
let rootpath: *u8 = "\0".ptr;
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
let root: i32 = sepfindoradd(g, rootpath, src, entryisdir);
if (root < 0) { return 1; };
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
// files never `import test;`. Inject it as a direct dep of the root so
@@ -1616,6 +1657,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
};
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
let rootpackage: bool = packageonly != 0;
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
cerr("ww: -p requires a non-main package\n");
return 1;
};
let ci: i32 = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
@@ -1625,6 +1671,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
stack.len = g.n;
let norder: i32 = 0;
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
if (!rootpackage) { g.pkg[root].path = "\0".ptr; };
let oi: i32 = 0;
for (oi < norder) {
@@ -1650,6 +1697,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cu = unitnew; cw = wwinew; cs = asmnew;
co = objnew; ca = anew;
};
let needsexport: bool = (pi != root) || rootpackage;
let needsarchive: bool = (pi != root) || rootpackage;
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, cu) < 0) {
return 1;
};
@@ -1659,7 +1708,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
fresh = fileequal(unitnew, unitf);
if (fresh) { fresh = fileisreg(asmf); };
if (fresh) {
if (pi != root) {
if (needsexport) {
fresh = fileisreg(wwi);
};
};
@@ -1669,7 +1718,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
};
if (fresh) {
if (emitasm == 0 && pi != root) {
if (emitasm == 0 && needsarchive) {
fresh = filesizenonzero(apath);
};
};
@@ -1695,12 +1744,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
// synthesizes the test main; deps never get -T.
let roott: bool = (pi == root) && (istest != 0);
let alen: u64 = 8u64;
if (pi == root) { alen = 6u64; if (roott) { alen = 7u64; }; };
if (!needsexport) { alen = 6u64; if (roott) { alen = 7u64; }; };
let argv: []str = alloc([], alen)!;
append(argv, "w6c");
if (roott) { append(argv, "-T"); };
append(argv, "-c");
if (pi != root) {
if (needsexport) {
append(argv, "-I");
append(argv, pathstr(cw));
};
@@ -1744,7 +1793,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
// the build target), so `main` is defined before any archive is
// processed. The link
// consumes `.o`/`.a`, never `.wwi`.
if (emitasm == 0 && pi != root) {
if (emitasm == 0 && needsarchive) {
if (archiveo(co, ca) != 0) {
cerr("ww: archive failed\n");
return 1;
@@ -1754,7 +1803,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
// them, unit strictly last.
if (warm) {
let bad: bool = false;
if (pi != root) {
if (needsexport) {
if (os.rename(pathstr(wwinew), pathstr(wwi)) != 0) {
bad = true;
};
@@ -1772,7 +1821,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
};
if (!bad) {
if (emitasm == 0 && pi != root) {
if (emitasm == 0 && needsarchive) {
if (os.rename(pathstr(anew), pathstr(apath)) != 0) {
bad = true;
};
@@ -1821,6 +1870,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
};
};
if (emitasm != 0) { return 0; };
if (rootpackage) {
let archive: *u8 = sepfname(g, root, scratch, ".a");
let iface: *u8 = sepfname(g, root, scratch, ".wwi");
let outiface: *u8 = appendlit(out, ".wwi");
if (copyfileatomic(archive, out) != 0
|| copyfileatomic(iface, outiface) != 0) {
cerrpath("ww: cannot write package artifact ", out, "\n");
return 1;
};
return 0;
};
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
// files after, then libwwrt.a (which still
@@ -1895,13 +1955,15 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
// 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, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, istest: i32, emitasm: i32,
keepscratch: i32, workdir: *u8) i32 = {
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
emitasm: i32, keepscratch: i32, workdir: *u8) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
incs, lf, istest, emitasm, workdir, &scratch, &g);
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
out, objstem,
incs, lf, packageonly, istest, emitasm, workdir, &scratch, &g);
sepgraphfree(g);
if (keepscratch == 0 && scratch != nil) {
if (cstrendswithlit(scratch, ".sepwork")) {
@@ -2001,7 +2063,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
};
fn writeusage(fd: i32) void = {
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\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 lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-p] [-S] [-w DIR] [-I DIR] [-o FILE] [path] build a local package graph\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 -p emits a non-main archive FILE + FILE.wwi\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
os.write(fd, s.ptr, s.len: u64);
};
@@ -2045,6 +2107,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
let workdir: *u8 = nil; // -w persistent package-artifact workdir
let emitasm: i32 = 0;
let packageonly: i32 = 0;
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
let incoff: u64 = 0u64;
@@ -2064,6 +2127,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "-S")) {
emitasm = 1;
} else { if (cstreqlit(p, "-p")) {
packageonly = 1;
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -2143,7 +2208,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
} else {
cerr("ww build: unknown flag\n");
return 2;
}; }; }; }; }; };
}; }; }; }; }; }; };
} else {
if (src == nil) { src = p; };
};
@@ -2154,12 +2219,26 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
if (packageonly != 0 && emitasm != 0) {
cerr("ww build: -p and -S cannot be combined\n");
return 2;
};
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) {
cerr("ww build: cannot find module\n");
return 1;
};
if (packageonly != 0 && isdir == 0) {
cerr("ww build: -p needs a package directory\n");
return 2;
};
let out: *u8 = nil;
let objstem: *u8 = nil;
if (outflag != nil) {
@@ -2189,8 +2268,11 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibdirs = nlibdirs;
lf.libs = libs.ptr;
lf.nlibs = nlibs;
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf,
0i32, emitasm, 1i32, workdir);
let rootidentity: *u8 = nil;
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
return buildonesep(selfdir, resolved, isdir, rootidentity,
out, objstem, incs.ptr, &lf,
packageonly, 0i32, emitasm, 1i32, workdir);
};
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
@@ -2354,8 +2436,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.libs = libs.ptr;
lf.nlibs = nlibs;
// The freshly acquired directory owns both main and main.sepwork.
if (buildonesep(selfdir, resolved, isdir, outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, nil) != 0) {
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, 0i32, nil) != 0) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
@@ -2444,8 +2526,8 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
lf.nlibs = 0;
let keep: i32 = 0;
if (outstem != nil) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf,
1i32, emitasm, keep, workdir);
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 1i32, emitasm, keep, workdir);
if (bres != 0) {
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));