cmd: build directory package test variants
This commit is contained in:
@@ -353,21 +353,20 @@ fn locateimport(dirs: *u8, name: *u8, namelen: u64,
|
||||
return nil;
|
||||
};
|
||||
|
||||
// Go's contract: only *_test.ww is a test source. A line-leading @test
|
||||
// declaration anywhere else would be silently dropped by a non-T build
|
||||
// (#6, the Hare model), so directory enumeration rejects it loudly.
|
||||
// This is the wwstage twin of cmd/ww/main.c:file_has_line_test.
|
||||
fn dirfileattest(dirpath: *u8, name: *u8) bool = {
|
||||
// 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 false; };
|
||||
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 false; };
|
||||
case let e: os.oserror => { os.close(fd); return -1; };
|
||||
};
|
||||
if (n <= 0i64) { os.close(fd); return false; };
|
||||
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);
|
||||
@@ -375,29 +374,55 @@ fn dirfileattest(dirpath: *u8, name: *u8) bool = {
|
||||
let got: i64 = -1i64;
|
||||
match (rr) {
|
||||
case let v: i64 => got = v;
|
||||
case let e: os.oserror => return false;
|
||||
case let e: os.oserror => return -1;
|
||||
};
|
||||
if (got != n) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < b.len) {
|
||||
for (i < b.len && (b[i] == ' ' || b[i] == '\t'
|
||||
|| b[i] == '\r')) { i += 1; };
|
||||
if (i + 5 < b.len && b[i] == '@' && b[i + 1] == 't'
|
||||
&& b[i + 2] == 'e' && b[i + 3] == 's'
|
||||
&& b[i + 4] == 't'
|
||||
&& (b[i + 5] == ' ' || b[i + 5] == '\t')) {
|
||||
return true;
|
||||
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;
|
||||
};
|
||||
};
|
||||
for (i < b.len && b[i] != '\n') { i += 1; };
|
||||
if (i < b.len) { i += 1; };
|
||||
d = d.next;
|
||||
};
|
||||
return false;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Classify a directory entry for production enumeration: 1 keep,
|
||||
// 0 skip (non-source or *_test.ww), -1 @test outside *_test.ww
|
||||
// (caller diagnoses and fails).
|
||||
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
||||
def SEP_VARIANT_PRODUCTION: i32 = 0;
|
||||
def SEP_VARIANT_SAME_TEST: i32 = 1;
|
||||
def SEP_VARIANT_EXTERNAL: i32 = 2;
|
||||
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
|
||||
|
||||
// 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; };
|
||||
@@ -405,7 +430,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
||||
s.ptr = name;
|
||||
s.len = nlen: i32;
|
||||
if (!strings.hassuffix(s, ".ww")) { return 0; };
|
||||
if (strings.hassuffix(s, "_test.ww")) { 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 = joinpath(dirpath, name);
|
||||
let fi: os.filestat;
|
||||
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
|
||||
@@ -422,10 +449,45 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
||||
cerr(": package source is not a regular file\n");
|
||||
return -2;
|
||||
};
|
||||
if (dirfileattest(dirpath, name)) { return -1; };
|
||||
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;
|
||||
};
|
||||
if (imports.nmod.len >= 256) {
|
||||
cerrpos(imports.file, imports.line, imports.col);
|
||||
cerr(": error: package name is too long\n");
|
||||
return nil;
|
||||
};
|
||||
return arenadupcstr(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).
|
||||
@@ -445,12 +507,10 @@ fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// enumeratedir — list production *.ww paths of `dirpath` (less *_test.ww
|
||||
// test sources), byte-sort. A line-leading @test in any other source is
|
||||
// diagnosed here and returns -2. Returns one exact pointer array of
|
||||
// NUL-terminated full paths. This is the sole directory-membership
|
||||
// discovery path; the owning seppkg retains the list.
|
||||
fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
// 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,
|
||||
@@ -460,6 +520,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
let cap: i32 = 8;
|
||||
let names: []*u8 = alloc([], cap: u64)!;
|
||||
let nlens: []u64 = alloc([], cap: u64)!;
|
||||
let kinds: []i32 = alloc([], cap: u64)!;
|
||||
let n: i32 = 0;
|
||||
let buf: []u8 = alloc([], 8192u64)!;
|
||||
buf.len = 8192;
|
||||
@@ -473,7 +534,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
let reclen: u64 = blo + (bhi * 256u64);
|
||||
let nm: *u8 = buf.ptr + off + 19u64;
|
||||
let nl: u64 = cstrlen(nm);
|
||||
let cls: i32 = dirfileclass(dirpath, nm, nl);
|
||||
let cls: i32 = dirfileclass(dirpath, nm, nl, variant);
|
||||
if (cls == -1) {
|
||||
cerr("ww: ");
|
||||
cerr(pathstr(joinpath(dirpath, nm)));
|
||||
@@ -486,23 +547,38 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
return nil: **u8, -2;
|
||||
};
|
||||
if (cls > 0) {
|
||||
let full: *u8 = joinpath(dirpath, nm);
|
||||
if (cls == 2) {
|
||||
let pn: *u8 = dirpackagename(full);
|
||||
if (pn == nil) {
|
||||
os.close(fd);
|
||||
return nil: **u8, -2;
|
||||
};
|
||||
if (testpackage == nil || !cstreq(pn, testpackage)) {
|
||||
off += reclen;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
if (n >= cap) {
|
||||
let ncap: i32 = cap * 2;
|
||||
let nn: []*u8 = alloc([], ncap: u64)!;
|
||||
let nl2: []u64 = alloc([], ncap: u64)!;
|
||||
let nk: []i32 = alloc([], ncap: u64)!;
|
||||
let k: i32 = 0;
|
||||
for (k < n) {
|
||||
nn[k] = names[k];
|
||||
nl2[k] = nlens[k];
|
||||
nk[k] = kinds[k];
|
||||
k += 1;
|
||||
};
|
||||
names = nn;
|
||||
nlens = nl2;
|
||||
kinds = nk;
|
||||
cap = ncap;
|
||||
};
|
||||
let full: *u8 = joinpath(dirpath, nm);
|
||||
names[n] = full;
|
||||
nlens[n] = cstrlen(full);
|
||||
kinds[n] = cls;
|
||||
n += 1;
|
||||
};
|
||||
off += reclen;
|
||||
@@ -524,8 +600,12 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
for (i < n) {
|
||||
let j: i32 = i;
|
||||
for (j > 0) {
|
||||
if (bytecmp(names[j - 1], nlens[j - 1],
|
||||
names[j], nlens[j]) <= 0) { 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];
|
||||
@@ -533,6 +613,9 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
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;
|
||||
};
|
||||
};
|
||||
@@ -541,6 +624,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
if (n == 0) {
|
||||
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
||||
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
||||
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
|
||||
return nil: **u8, 0;
|
||||
};
|
||||
let exact: []*u8 = alloc([], n: u64)!;
|
||||
@@ -551,6 +635,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
||||
// shape correct for the driver's allocations.
|
||||
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
||||
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
||||
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
|
||||
return exact.ptr, n;
|
||||
};
|
||||
|
||||
@@ -637,9 +722,12 @@ type seppkg = struct {
|
||||
path: *u8, // dotted import path, NUL-term; root path[0]==0
|
||||
entry: *u8, // resolved package dir (or file, file root), NUL-term
|
||||
name: *u8, // validated declared name; directory packages only
|
||||
sources: **u8, // owned, byte-sorted production paths; dirs only
|
||||
testpackage: *u8,
|
||||
sources: **u8, // owned, byte-sorted selected paths; dirs only
|
||||
nsources: i32,
|
||||
isdir: i32,
|
||||
variant: i32,
|
||||
testsupport: bool,
|
||||
deps: []i32, // direct-dep indices into sepgraph.pkg
|
||||
ndeps: i32,
|
||||
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
|
||||
@@ -650,15 +738,26 @@ type sepgraph = struct {
|
||||
n: i32,
|
||||
};
|
||||
|
||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
|
||||
isdir: i32, variant: i32, testpackage: *u8) 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) {
|
||||
let testproductionpair: bool = i == 0
|
||||
&& g.pkg[i].variant != SEP_VARIANT_PRODUCTION
|
||||
&& variant == SEP_VARIANT_PRODUCTION && isdir != 0
|
||||
&& g.pkg[i].isdir != 0
|
||||
&& os.samefile(pathstr(g.pkg[i].entry), pathstr(entry));
|
||||
if (cstreq(g.pkg[i].path, path)) {
|
||||
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
||||
if (testproductionpair) {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))
|
||||
|| g.pkg[i].variant != variant) {
|
||||
cerr("ww: package "); cerr(pathstr(path));
|
||||
cerr(" resolves to more than one location\n");
|
||||
return -1;
|
||||
@@ -666,6 +765,10 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
return i;
|
||||
};
|
||||
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
||||
if (testproductionpair) {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
cerr("ww: package directory "); cerr(pathstr(entry));
|
||||
cerr(" has identities ");
|
||||
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
|
||||
@@ -687,9 +790,16 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
g.pkg[g.n].path = arenadupcstr(path, plen);
|
||||
g.pkg[g.n].entry = arenadupcstr(entry, elen);
|
||||
g.pkg[g.n].name = nil;
|
||||
g.pkg[g.n].testpackage = nil;
|
||||
if (testpackage != nil) {
|
||||
g.pkg[g.n].testpackage = arenadupcstr(testpackage,
|
||||
cstrlen(testpackage));
|
||||
};
|
||||
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].testsupport = false;
|
||||
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
|
||||
dslot.len = SEP_MAXPKG;
|
||||
g.pkg[g.n].deps = dslot;
|
||||
@@ -700,6 +810,11 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
return r;
|
||||
};
|
||||
|
||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
return sepfindoraddvariant(g, path, entry, isdir,
|
||||
SEP_VARIANT_PRODUCTION, nil);
|
||||
};
|
||||
|
||||
// Release the package-owned directory-membership lists through one graph
|
||||
// cleanup function. rt_free is a no-op in today's no-free runtime, but this
|
||||
// records the same ownership boundary as the C bootstrap twin.
|
||||
@@ -741,6 +856,36 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
||||
return buf.ptr;
|
||||
};
|
||||
|
||||
fn sepexternalname(pkg: *seppkg, path: *u8, n: u64,
|
||||
leafonly: bool) bool = {
|
||||
if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) {
|
||||
return false;
|
||||
};
|
||||
let begin: u64 = 0u64;
|
||||
if (leafonly) {
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
if (path[i] == '.') { begin = i + 1u64; };
|
||||
i += 1u64;
|
||||
};
|
||||
};
|
||||
let leafn: u64 = n - begin;
|
||||
let tn: u64 = cstrlen(pkg.testpackage);
|
||||
if (tn != leafn + 5u64) { return false; };
|
||||
if (bytecmp(pkg.testpackage, leafn, path + begin, leafn) != 0) {
|
||||
return false;
|
||||
};
|
||||
return pkg.testpackage[leafn] == '_'
|
||||
&& pkg.testpackage[leafn + 1u64] == 't'
|
||||
&& pkg.testpackage[leafn + 2u64] == 'e'
|
||||
&& pkg.testpackage[leafn + 3u64] == 's'
|
||||
&& pkg.testpackage[leafn + 4u64] == 't';
|
||||
};
|
||||
|
||||
fn sepexternalproduction(pkg: *seppkg, path: *u8, n: u64) bool = {
|
||||
return sepexternalname(pkg, path, n, false);
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -776,6 +921,11 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
||||
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
|
||||
let declared: *u8 = imports.nmod.ptr;
|
||||
let declaredn: u64 = imports.nmod.len: u64;
|
||||
if (declaredn >= 256u64) {
|
||||
cerrpos(imports.file, imports.line, imports.col);
|
||||
cerr(": error: package name is too long\n");
|
||||
return -1;
|
||||
};
|
||||
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),
|
||||
@@ -852,10 +1002,24 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
||||
return -1;
|
||||
};
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
let externalproduction: bool = sepexternalproduction(
|
||||
&g.pkg[pi], idp, idn);
|
||||
let ipath: *u8 = nil;
|
||||
if (externalproduction) {
|
||||
ipath = g.pkg[pi].entry;
|
||||
isdir = 1;
|
||||
} else {
|
||||
ipath = locateimport(searchpath, idp, idn, &isdir);
|
||||
};
|
||||
if (ipath != nil) {
|
||||
if (isdir != 0) {
|
||||
if (os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry))) {
|
||||
let self: bool = os.samefile(pathstr(ipath),
|
||||
pathstr(g.pkg[pi].entry));
|
||||
if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) {
|
||||
externalproduction = true;
|
||||
};
|
||||
if (self
|
||||
&& !externalproduction) {
|
||||
cerrpos(u.file, u.line, u.col);
|
||||
cerr(": error: self-import: package '");
|
||||
if (g.pkg[pi].path[0u64] != 0u8) {
|
||||
@@ -931,7 +1095,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
||||
if (g.pkg[pi].isdir != 0) {
|
||||
let sources: **u8;
|
||||
let nsources: i32;
|
||||
sources, nsources = enumeratedir(g.pkg[pi].entry);
|
||||
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) {
|
||||
@@ -956,7 +1121,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8) {
|
||||
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8
|
||||
&& !g.pkg[pi].testsupport) {
|
||||
let plen: u64 = cstrlen(g.pkg[pi].path);
|
||||
let leaf: *u8 = g.pkg[pi].path;
|
||||
let j: u64 = 0u64;
|
||||
@@ -1056,7 +1222,7 @@ fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
|
||||
};
|
||||
|
||||
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
||||
modpath: *u8) i32 = {
|
||||
modpath: *u8, pkg: *seppkg) i32 = {
|
||||
let pview: str;
|
||||
pview.ptr = path;
|
||||
pview.len = cstrlen(path): i32;
|
||||
@@ -1114,12 +1280,16 @@ fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
||||
u = uses[ui];
|
||||
let idp: *u8 = u.usepath.ptr;
|
||||
let idn: u64 = u.usepath.len: u64;
|
||||
if (sepexternalproduction(pkg, idp, idn)) {
|
||||
ui += 1;
|
||||
continue;
|
||||
};
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir == 0) {
|
||||
if (sepemitbody(fd, ipath, visit, searchpath,
|
||||
modpath) < 0) { return -1; };
|
||||
modpath, pkg) < 0) { return -1; };
|
||||
};
|
||||
};
|
||||
ui += 1;
|
||||
@@ -1195,12 +1365,12 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
|
||||
let i: i32 = 0;
|
||||
for (i < g.pkg[pi].nsources && bodyrc == 0) {
|
||||
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
|
||||
g.pkg[pi].path);
|
||||
g.pkg[pi].path, &g.pkg[pi]);
|
||||
i += 1;
|
||||
};
|
||||
} else {
|
||||
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
|
||||
g.pkg[pi].path);
|
||||
g.pkg[pi].path, &g.pkg[pi]);
|
||||
};
|
||||
if (os.close(u) != 0) {
|
||||
cerr("ww: cannot close package unit\n");
|
||||
@@ -1405,14 +1575,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
|
||||
fn workdirstamptext(istest: i32, emitasm: i32) str = {
|
||||
if (istest != 0) {
|
||||
if (emitasm != 0) {
|
||||
return "ww workdir fmt 2 mode test asm 1\n";
|
||||
return "ww workdir fmt 3 mode test asm 1\n";
|
||||
};
|
||||
return "ww workdir fmt 2 mode test asm 0\n";
|
||||
return "ww workdir fmt 3 mode test asm 0\n";
|
||||
};
|
||||
if (emitasm != 0) {
|
||||
return "ww workdir fmt 2 mode build asm 1\n";
|
||||
return "ww workdir fmt 3 mode build asm 1\n";
|
||||
};
|
||||
return "ww workdir fmt 2 mode build asm 0\n";
|
||||
return "ww workdir fmt 3 mode build asm 0\n";
|
||||
};
|
||||
|
||||
fn stampmatches(path: *u8, want: str) bool = {
|
||||
@@ -1460,7 +1630,8 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
|
||||
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,
|
||||
rootvariant: i32, testpackage: *u8, emitasm: i32,
|
||||
workdir: *u8, scratchout: **u8,
|
||||
graphout: **sepgraph) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
@@ -1605,34 +1776,65 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
if (graphout != nil) { *graphout = g; };
|
||||
let rootpath: *u8 = "\0".ptr;
|
||||
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
|
||||
let root: i32 = sepfindoradd(g, rootpath, src, entryisdir);
|
||||
let root: i32 = sepfindoraddvariant(g, rootpath, src, entryisdir,
|
||||
rootvariant, testpackage);
|
||||
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
|
||||
// seploadpkg pulls test + its transitive deps; the producer adds -T to
|
||||
// the root and `test.run` links against test's `.a` — via the
|
||||
// sepscanfile dedup-guarded dep append.
|
||||
let testsupportmodule: str = "test";
|
||||
// -T generates a dispatcher whose support qualifier is selected by the
|
||||
// command. Represent that compiler-generated requirement as a direct root
|
||||
// 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 (istest != 0) {
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
let tp: *u8 = locateimport(dotdotlib.ptr, "test".ptr,
|
||||
"test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
let ti: i32 = sepfindoradd(g, "test\0".ptr, tp, td);
|
||||
if (ti < 0) { return 1; };
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[root].ndeps) {
|
||||
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
||||
m += 1;
|
||||
let rootissupport: bool = entryisdir != 0
|
||||
&& os.samefile(pathstr(tp), pathstr(src));
|
||||
let collision: bool = false;
|
||||
if (!rootissupport && testpackage != nil) {
|
||||
collision = cstreqlit(testpackage, "test")
|
||||
|| cstreqlit(testpackage, "test_test");
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
||||
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
||||
g.pkg[root].ndeps += 1;
|
||||
if (!rootissupport && !collision) {
|
||||
let ud: i32 = 0;
|
||||
let up: *u8 = locateimport(searchpath.ptr, "test".ptr,
|
||||
"test".len: u64, &ud);
|
||||
if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) {
|
||||
collision = true;
|
||||
};
|
||||
};
|
||||
if (collision) { testsupportmodule = SEP_TEST_SUPPORT_MODULE; };
|
||||
// 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 one support dependency.
|
||||
if (!rootissupport || rootvariant == SEP_VARIANT_EXTERNAL) {
|
||||
let ti: i32 = sepfindoradd(g,
|
||||
testsupportmodule.ptr, tp, td);
|
||||
if (ti < 0) { return 1; };
|
||||
g.pkg[ti].testsupport = true;
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[root].ndeps) {
|
||||
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
||||
m += 1;
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
||||
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
||||
g.pkg[root].ndeps += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||
if (rootvariant != SEP_VARIANT_PRODUCTION
|
||||
&& (testpackage == nil || !cstreq(g.pkg[root].name, testpackage))) {
|
||||
cerr("ww: package-test selector does not match loaded package\n");
|
||||
return 1;
|
||||
};
|
||||
let rootpackage: bool = packageonly != 0;
|
||||
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
|
||||
cerr("ww: -p requires a non-main package\n");
|
||||
@@ -1719,11 +1921,21 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
// #79: the root carries -T under `ww test` so w6c
|
||||
// synthesizes the test main; deps never get -T.
|
||||
let roott: bool = (pi == root) && (istest != 0);
|
||||
let supportt: bool = g.pkg[pi].testsupport;
|
||||
let alen: u64 = 8u64;
|
||||
if (!needsexport) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
||||
if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; };
|
||||
if (supportt) { alen += 2u64; };
|
||||
let argv: []str = alloc([], alen)!;
|
||||
append(argv, "w6c");
|
||||
if (roott) { append(argv, "-T"); };
|
||||
if (roott) {
|
||||
append(argv, "-T");
|
||||
append(argv, "--test-support-module");
|
||||
append(argv, testsupportmodule);
|
||||
};
|
||||
if (supportt) {
|
||||
append(argv, "--test-support-module");
|
||||
append(argv, testsupportmodule);
|
||||
};
|
||||
append(argv, "-c");
|
||||
if (needsexport) {
|
||||
append(argv, "-I");
|
||||
@@ -1859,8 +2071,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
};
|
||||
|
||||
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
|
||||
// files after, then libwwrt.a (which still
|
||||
// selectively pulls only the runtime members a live undef needs).
|
||||
// files after, then libwwrt.a (which still selectively pulls only the
|
||||
// runtime members a live undef needs). A same-test root already contains
|
||||
// its production sources; if that production variant is also reached
|
||||
// through the runtime closure, retain its dependencies but omit its
|
||||
// duplicate archive.
|
||||
// argv: 3 fixed (w6l,-o,out) + one root object/archive per package
|
||||
// + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
|
||||
let nldirs: i32 = 0;
|
||||
@@ -1882,10 +2097,19 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
let pos: i32 = 3;
|
||||
let li: i32 = norder - 1;
|
||||
for (li >= 0) {
|
||||
let pi: i32 = order[li];
|
||||
if (g.pkg[root].variant == SEP_VARIANT_SAME_TEST
|
||||
&& pi != root
|
||||
&& g.pkg[pi].variant == SEP_VARIANT_PRODUCTION
|
||||
&& os.samefile(pathstr(g.pkg[pi].entry),
|
||||
pathstr(g.pkg[root].entry))) {
|
||||
li -= 1;
|
||||
continue;
|
||||
};
|
||||
// root: positional `.o` (force-load); deps: `.a` (selective).
|
||||
let suf: str = ".a";
|
||||
if (order[li] == root) { suf = ".o"; };
|
||||
largv[pos] = sepfname(g, order[li], scratch, suf);
|
||||
if (pi == root) { suf = ".o"; };
|
||||
largv[pos] = sepfname(g, pi, scratch, suf);
|
||||
pos += 1;
|
||||
li -= 1;
|
||||
};
|
||||
@@ -1934,12 +2158,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: 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 = {
|
||||
rootvariant: i32, testpackage: *u8, emitasm: i32,
|
||||
keepscratch: i32, workdir: *u8) i32 = {
|
||||
let scratch: *u8 = nil;
|
||||
let g: *sepgraph = nil;
|
||||
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
|
||||
out, objstem,
|
||||
incs, lf, packageonly, istest, emitasm, workdir, &scratch, &g);
|
||||
incs, lf, packageonly, istest, rootvariant, testpackage,
|
||||
emitasm, workdir, &scratch, &g);
|
||||
sepgraphfree(g);
|
||||
if (keepscratch == 0 && scratch != nil) {
|
||||
if (cstrendswithlit(scratch, ".sepwork")) {
|
||||
@@ -2248,7 +2474,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
|
||||
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||
out, objstem, incs.ptr, &lf,
|
||||
packageonly, 0i32, emitasm, 1i32, workdir);
|
||||
packageonly, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
emitasm, 1i32, workdir);
|
||||
};
|
||||
|
||||
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
||||
@@ -2413,7 +2640,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibs = nlibs;
|
||||
// The freshly acquired directory owns both main and main.sepwork.
|
||||
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
|
||||
0i32, 0i32, 0i32, 0i32, nil) != 0) {
|
||||
0i32, 0i32, SEP_VARIANT_PRODUCTION, nil, 0i32, 0i32, nil) != 0) {
|
||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||
cerr("ww: cannot remove temporary output\n");
|
||||
@@ -2503,7 +2730,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
let keep: i32 = 0;
|
||||
if (outstem != nil) { keep = 1; };
|
||||
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||
0i32, 1i32, emitasm, keep, workdir);
|
||||
0i32, 1i32, SEP_VARIANT_PRODUCTION, nil, emitasm, keep, workdir);
|
||||
if (bres != 0) {
|
||||
if (owntmp) {
|
||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||
@@ -2590,6 +2817,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let emitasm: i32 = 0;
|
||||
let outstem: *u8 = nil;
|
||||
let workdir: *u8 = nil;
|
||||
let packagetestkind: *u8 = nil;
|
||||
let packagetestname: *u8 = nil;
|
||||
let packageopts: bool = false;
|
||||
let afterdash: bool = false;
|
||||
let i: i32 = start;
|
||||
@@ -2600,6 +2829,27 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
if (cstreqlit(p, "--")) {
|
||||
packageopts = true; afterdash = true; i += 1; continue;
|
||||
};
|
||||
if (cstreqlit(p, "--ww-package-test")) {
|
||||
if (i + 2 >= argc || packagetestkind != nil) {
|
||||
cerr("ww test: --ww-package-test needs kind and package\n");
|
||||
return 2;
|
||||
};
|
||||
packagetestkind = argv[i + 1];
|
||||
packagetestname = argv[i + 2];
|
||||
let pn: u64 = cstrlen(packagetestname);
|
||||
if ((!cstreqlit(packagetestkind, "same")
|
||||
&& !cstreqlit(packagetestkind, "external"))
|
||||
|| pn == 0u64 || pn >= 256u64
|
||||
|| (cstreqlit(packagetestkind, "external")
|
||||
&& (pn <= 5u64
|
||||
|| !cstrendswithlit(packagetestname,
|
||||
"_test")))) {
|
||||
cerr("ww test: invalid --ww-package-test variant\n");
|
||||
return 2;
|
||||
};
|
||||
i += 3;
|
||||
continue;
|
||||
};
|
||||
if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
@@ -2679,6 +2929,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -S needs -o\n");
|
||||
return 2;
|
||||
};
|
||||
if (packagetestkind != nil && packageopts) {
|
||||
cerr("ww test: package-test variant rejects package options\n");
|
||||
return 2;
|
||||
};
|
||||
|
||||
// Go's ./... form: a trailing "..." element is a package-tree
|
||||
// request for the coordinator, never a literal path — recognized
|
||||
@@ -2692,6 +2946,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
&& target[tlen - 1u64] == '.';
|
||||
};
|
||||
if (istree) {
|
||||
if (packagetestkind != nil) {
|
||||
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;
|
||||
@@ -2733,6 +2991,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
};
|
||||
};
|
||||
if (isdir == 0) {
|
||||
if (packagetestkind != nil) {
|
||||
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;
|
||||
};
|
||||
@@ -2752,6 +3014,22 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: pattern needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
if (packagetestkind != nil) {
|
||||
if (compileonly == 0 || outstem == nil) {
|
||||
cerr("ww test: package-test variant needs -c -o\n");
|
||||
return 2;
|
||||
};
|
||||
let variant: i32 = SEP_VARIANT_EXTERNAL;
|
||||
if (cstreqlit(packagetestkind, "same")) {
|
||||
variant = SEP_VARIANT_SAME_TEST;
|
||||
};
|
||||
let lf: lflags;
|
||||
lf.libdirs = nil; lf.nlibdirs = 0;
|
||||
lf.libs = nil; lf.nlibs = 0;
|
||||
return buildonesep(selfdir, resolved, 1, nil, outstem, outstem,
|
||||
incs.ptr, &lf, 0i32, 1i32, variant, packagetestname,
|
||||
0i32, 1i32, workdir);
|
||||
};
|
||||
let replacement: *u8 = nil;
|
||||
if (resolved != target) { replacement = resolved; };
|
||||
return execpackagetests(selfdir, argv, argc, start, targetindex,
|
||||
|
||||
Reference in New Issue
Block a user