build: make executable and test roots package actions

This commit is contained in:
2026-08-12 22:00:02 +09:00
parent fc4bde703e
commit 1724ea086f
11 changed files with 722 additions and 364 deletions

View File

@@ -322,9 +322,11 @@ source_has_test_decl(const char *path)
#define SEP_VARIANT_PRODUCTION 0 #define SEP_VARIANT_PRODUCTION 0
#define SEP_VARIANT_SAME_TEST 1 #define SEP_VARIANT_SAME_TEST 1
#define SEP_VARIANT_EXTERNAL 2 #define SEP_VARIANT_EXTERNAL 2
#define SEP_VARIANT_TEST_MAIN 3
#define SEP_ROLE_NORMAL 0 #define SEP_ROLE_NORMAL 0
#define SEP_ROLE_EXTERNAL_PRODUCTION 1 #define SEP_ROLE_EXTERNAL_PRODUCTION 1
#define SEP_ROLE_TEST_SUPPORT 2 #define SEP_ROLE_TEST_SUPPORT 2
#define SEP_ROLE_GENERATED_MAIN 3
#define SEP_TEST_SUPPORT_MODULE "__wwtest" #define SEP_TEST_SUPPORT_MODULE "__wwtest"
#define SEP_MAXPRODUCT 256 #define SEP_MAXPRODUCT 256
#define SEP_MAXCONTEXT (SEP_MAXPRODUCT + 1) #define SEP_MAXCONTEXT (SEP_MAXPRODUCT + 1)
@@ -508,7 +510,7 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
#define SEP_MAXPKG 256 #define SEP_MAXPKG 256
struct seppkg { struct seppkg {
char path[256]; /* dotted import path; "" == root/primary */ char path[256]; /* canonical dotted package identity */
char entry[1024]; /* resolved package dir (or file, for a file root) */ char entry[1024]; /* resolved package dir (or file, for a file root) */
char canon[1024]; /* canonical location; never package identity */ char canon[1024]; /* canonical location; never package identity */
char artifact[64]; /* non-importable product-root artifact key */ char artifact[64]; /* non-importable product-root artifact key */
@@ -519,7 +521,9 @@ struct seppkg {
int is_dir; int is_dir;
int variant; /* SEP_VARIANT_*; dependencies are production */ int variant; /* SEP_VARIANT_*; dependencies are production */
int role; /* normal, external-production, or test support */ int role; /* normal, external-production, or test support */
int root; /* independently compiled/linkable product root */ int root; /* requested package action (possibly a test variant) */
int link_entry; /* package supplies the executable's bare main */
int generated_main; /* compiler-owned generated test-main package */
int failed; /* discovery/compile failure reaches this action */ int failed; /* discovery/compile failure reaches this action */
int test_support; /* compiler-generated -T support package */ int test_support; /* compiler-generated -T support package */
int loaded; /* directory membership/name loaded exactly once */ int loaded; /* directory membership/name loaded exactly once */
@@ -554,6 +558,7 @@ struct sepproduct {
int variant; int variant;
int context; int context;
int root; int root;
int variant_root; /* production-plus-test or external test package */
}; };
#define SEP_MAXLFLAGS 32 #define SEP_MAXLFLAGS 32
@@ -591,8 +596,17 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path,
if (root && g->pkg[i].root) { if (root && g->pkg[i].root) {
if (!same_location) continue; if (!same_location) continue;
if (variant != g->pkg[i].variant) continue; if (variant != g->pkg[i].variant) continue;
if (g->pkg[i].role == role
&& strcmp(g->pkg[i].path, path) == 0
&& strcmp(g->pkg[i].test_package,
test_package ? test_package : "") == 0) {
/* One canonical directory variant is one compile action,
* even when more than one product requests it. */
free(canon);
return i;
}
fprintf(stderr, fprintf(stderr,
"ww: duplicate package-test root %s\n", entry); "ww: incompatible package-test roots %s\n", entry);
free(canon); free(canon);
return -1; return -1;
} }
@@ -693,6 +707,8 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path,
p->variant = variant; p->variant = variant;
p->role = role; p->role = role;
p->root = root; p->root = root;
p->link_entry = 0;
p->generated_main = 0;
p->failed = 0; p->failed = 0;
p->test_support = 0; p->test_support = 0;
p->loaded = 0; p->loaded = 0;
@@ -1135,6 +1151,62 @@ sep_dep_cmp(const struct sepgraph *g, int a, int b)
return strcmp(g->pkg[a].artifact, g->pkg[b].artifact); return strcmp(g->pkg[a].artifact, g->pkg[b].artifact);
} }
/* Add the compiler-owned test main as a real package action. Its semantic
* identity and artifact key are distinct from every directory variant; its
* only dependencies are the selected test variant and dispatcher support. */
static int
sep_add_generated_main(struct sepgraph *g, struct sepproduct *product,
int ordinal, int support)
{
if (g->n >= SEP_MAXPKG) {
fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG);
return -1;
}
int variant = product->variant_root;
if (variant < 0 || variant >= g->n) return -1;
char variantcanon[sizeof g->pkg[0].canon];
char variantentry[sizeof g->pkg[0].entry];
snprintf(variantcanon, sizeof variantcanon, "%s", g->pkg[variant].canon);
snprintf(variantentry, sizeof variantentry, "%s", g->pkg[variant].entry);
struct seppkg *p = &g->pkg[g->n];
memset(p, 0, sizeof *p);
int pn = snprintf(p->path, sizeof p->path,
"__wwtestmain.%03d.main", ordinal);
int an = snprintf(p->artifact, sizeof p->artifact,
"__ww-test-%03d-main", ordinal);
int cn = snprintf(p->canon, sizeof p->canon, "%s#test-main-%03d",
variantcanon, ordinal);
if (pn < 0 || (size_t)pn >= sizeof p->path
|| an < 0 || (size_t)an >= sizeof p->artifact
|| cn < 0 || (size_t)cn >= sizeof p->canon) {
fprintf(stderr, "ww: generated test-main identity is too long\n");
return -1;
}
snprintf(p->entry, sizeof p->entry, "%s", variantentry);
snprintf(p->name, sizeof p->name, "main");
p->variant = SEP_VARIANT_TEST_MAIN;
p->role = SEP_ROLE_GENERATED_MAIN;
p->root = 1;
p->link_entry = 1;
p->generated_main = 1;
p->loaded = 1;
p->emit_context = product->context;
p->context_state[product->context] = 2;
p->deps[p->ndeps++] = variant;
if (support >= 0 && support != variant)
p->deps[p->ndeps++] = support;
for (int i = 1; i < p->ndeps; i++) {
int v = p->deps[i];
int j = i;
while (j > 0 && sep_dep_cmp(g, p->deps[j - 1], v) > 0) {
p->deps[j] = p->deps[j - 1];
j--;
}
p->deps[j] = v;
}
return g->n++;
}
/* Load one canonical package under one selected-root resolution context. /* Load one canonical package under one selected-root resolution context.
* Source membership is owned once, but a shared package's imports are checked * Source membership is owned once, but a shared package's imports are checked
* under every context that reaches it. The first canonical binding set owns * under every context that reaches it. The first canonical binding set owns
@@ -1224,10 +1296,11 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
return -1; return -1;
} }
/* Give a non-main root its declared identity before recursively loading /* Give a directory root its declared package identity before recursively
* dependencies. A back-edge can then reuse the root and reach the normal * loading dependencies. Explicit raw single-file compiler fixtures retain
* cycle detector instead of looking like a location alias. */ * their historical anonymous multi-package boundary. */
if (g->pkg[pi].root && g->pkg[pi].path[0] == '\0' if (g->pkg[pi].root && g->pkg[pi].is_dir
&& g->pkg[pi].path[0] == '\0'
&& g->pkg[pi].name[0] != '\0') { && g->pkg[pi].name[0] != '\0') {
size_t n = strlen(g->pkg[pi].name); size_t n = strlen(g->pkg[pi].name);
memcpy(g->pkg[pi].path, g->pkg[pi].name, n + 1); memcpy(g->pkg[pi].path, g->pkg[pi].name, n + 1);
@@ -1358,7 +1431,15 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *unitf)
return -1; return -1;
} }
int bodyrc = 0; int bodyrc = 0;
if (g->pkg[pi].is_dir) { if (g->pkg[pi].generated_main) {
if (fprintf(u, "//ww:module-reset %s\npackage main;\n",
g->pkg[pi].path) < 0)
bodyrc = -1;
for (int i = 0; i < g->pkg[pi].ndeps && bodyrc == 0; i++)
if (fprintf(u, "import %s;\n",
g->pkg[g->pkg[pi].deps[i]].path) < 0)
bodyrc = -1;
} else if (g->pkg[pi].is_dir) {
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++) for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i], bodyrc = sep_emit_body(u, g->pkg[pi].sources[i],
g->pkg[pi].path); g->pkg[pi].path);
@@ -1536,7 +1617,7 @@ static void
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm) workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
{ {
snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n", snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n",
is_test ? 7 : 6, is_test ? "test" : "build", emit_asm); is_test ? 8 : 7, is_test ? "test" : "build", emit_asm);
} }
/* A stale global builder identity invalidates every committed unit voucher in /* A stale global builder identity invalidates every committed unit voucher in
@@ -1574,8 +1655,8 @@ invalidate_workdir_units(const char *scratch)
* package universe, compile the dependency-first union once, then link each * package universe, compile the dependency-first union once, then link each
* root from its own complete reachable archive closure. The dependency-first * root from its own complete reachable archive closure. The dependency-first
* producer loop (one `w6c -c -I` per package, * producer loop (one `w6c -c -I` per package,
* each DEP `.o` wrapped in its own deterministic `.a`), then a * each package `.o` wrapped in its own deterministic `.a`), then a
* reverse-topo `w6l` of each root `.o` + dep `.a` set + libwwrt.a. Side * reverse-topo `w6l` of each root `.a` + reachable `.a` set + libwwrt.a. Side
* files land in a cold `<stem>.sepwork` dir, or under the persistent * files land in a cold `<stem>.sepwork` dir, or under the persistent
* `-w` workdir with content-identity package reuse. */ * `-w` workdir with content-identity package reuse. */
static int static int
@@ -1696,6 +1777,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (graphout) *graphout = g; if (graphout) *graphout = g;
const char *rootpath = package_only && root_identity const char *rootpath = package_only && root_identity
? root_identity : ""; ? root_identity : "";
int support_for[SEP_MAXPRODUCT];
for (int i = 0; i < nproducts; i++) support_for[i] = -1;
for (int i = 0; i < nproducts; i++) { for (int i = 0; i < nproducts; i++) {
const char *entry = products[i].dir != NULL const char *entry = products[i].dir != NULL
? products[i].dir : src; ? products[i].dir : src;
@@ -1720,11 +1803,15 @@ build_one_sep_impl(const char *src, int entry_is_dir,
entry_is_dir, products[i].variant, products[i].test_package, entry_is_dir, products[i].variant, products[i].test_package,
SEP_ROLE_NORMAL, products[i].artifact, 1); SEP_ROLE_NORMAL, products[i].artifact, 1);
if (products[i].root < 0) return 1; if (products[i].root < 0) return 1;
products[i].variant_root = products[i].root;
if (!is_test && !package_only)
g->pkg[products[i].root].link_entry = 1;
} }
const char *test_support_module = "test"; const char *test_support_module = "test";
/* -T generates a dispatcher whose support qualifier is selected by the /* -T generates a dispatcher whose support qualifier is selected by the
* command. Represent that compiler-generated requirement as a direct root * command. Represent that compiler-generated requirement as a direct edge
* edge. It normally coalesces with an explicit toolchain `import test`; * of the generated-main action. It normally coalesces with an explicit
* toolchain `import test`;
* when user source occupies that identity, the reserved graph alias keeps * when user source occupies that identity, the reserved graph alias keeps
* it distinct. The linker receives the same support archive closure. */ * it distinct. The linker receives the same support archive closure. */
if (is_test) { if (is_test) {
@@ -1770,8 +1857,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
* colocated production node, which is also its support dep. */ * colocated production node, which is also its support dep. */
if (root_is_support if (root_is_support
&& strcmp(test_support_module, "test") == 0 && strcmp(test_support_module, "test") == 0
&& products[i].variant != SEP_VARIANT_EXTERNAL) && products[i].variant != SEP_VARIANT_EXTERNAL) {
support_for[i] = root;
continue; continue;
}
int ti; int ti;
if (strcmp(test_support_module, if (strcmp(test_support_module,
SEP_TEST_SUPPORT_MODULE) == 0) SEP_TEST_SUPPORT_MODULE) == 0)
@@ -1782,19 +1871,27 @@ build_one_sep_impl(const char *src, int entry_is_dir,
tdir); tdir);
if (ti < 0) return 1; if (ti < 0) return 1;
g->pkg[ti].test_support = 1; g->pkg[ti].test_support = 1;
int seen = 0; support_for[i] = ti;
for (int k = 0; k < g->pkg[root].ndeps; k++)
if (g->pkg[root].deps[k] == ti) {
seen = 1; break;
}
if (!seen && g->pkg[root].ndeps < SEP_MAXPKG)
g->pkg[root].deps[g->pkg[root].ndeps++] = ti;
} }
free(tc); free(tc);
} }
} }
for (int i = 0; i < nproducts; i++) { for (int i = 0; i < nproducts; i++) {
int root = products[i].root; int root = products[i].variant_root;
/* Raw single-file test fixtures are the one retained non-directory
* exception: keep compiler-owned test-main synthesis in that action.
* Its support export is still an exact direct input. */
if (is_test && !entry_is_dir) {
int support = support_for[i];
if (support >= 0 && support != root) {
int seen = 0;
for (int k = 0; k < g->pkg[root].ndeps; k++)
if (g->pkg[root].deps[k] == support) seen = 1;
if (!seen)
g->pkg[root].deps[g->pkg[root].ndeps++] = support;
}
g->pkg[root].link_entry = 1;
}
if (sep_load_pkg(g, root, products[i].context) < 0) { if (sep_load_pkg(g, root, products[i].context) < 0) {
g->pkg[root].failed = 1; g->pkg[root].failed = 1;
continue; continue;
@@ -1808,6 +1905,22 @@ build_one_sep_impl(const char *src, int entry_is_dir,
g->pkg[root].failed = 1; g->pkg[root].failed = 1;
} }
} }
if (is_test && entry_is_dir) {
for (int i = 0; i < nproducts; i++) {
int variant = products[i].variant_root;
int support = support_for[i];
if (support >= 0 && support != variant
&& sep_load_pkg(g, support, products[i].context) < 0)
g->pkg[variant].failed = 1;
int mainpkg = sep_add_generated_main(g, &products[i], i,
support);
if (mainpkg < 0) return 1;
if (g->pkg[variant].failed
|| (support >= 0 && g->pkg[support].failed))
g->pkg[mainpkg].failed = 1;
products[i].root = mainpkg;
}
}
if (sep_validate_artifact_paths(g, scratch) < 0) if (sep_validate_artifact_paths(g, scratch) < 0)
return 1; return 1;
int root_package = package_only; int root_package = package_only;
@@ -1830,8 +1943,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0; for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
int ignored = 0; int ignored = 0;
if (sep_topo_visit(g, root, order, &ignored, stack, 0) < 0 if (sep_topo_visit(g, root, order, &ignored, stack, 0) < 0
|| sep_validate_module_closure(g, order, ignored, || sep_validate_module_closure(g, order, ignored, 1) < 0)
root_package) < 0)
g->pkg[root].failed = 1; g->pkg[root].failed = 1;
} }
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0; for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
@@ -1843,11 +1955,6 @@ build_one_sep_impl(const char *src, int entry_is_dir,
} }
} }
free(stack); free(stack);
if (!root_package)
for (int i = 0; i < nproducts; i++)
if (g->pkg[products[i].root].path[0] != '\0')
g->pkg[products[i].root].path[0] = '\0';
int any_failed = 0; int any_failed = 0;
for (int i = 0; i < nproducts; i++) for (int i = 0; i < nproducts; i++)
if (g->pkg[products[i].root].failed) any_failed = 1; if (g->pkg[products[i].root].failed) any_failed = 1;
@@ -1882,8 +1989,6 @@ build_one_sep_impl(const char *src, int entry_is_dir,
const char *cs = warm ? asmnew : asmf; const char *cs = warm ? asmnew : asmf;
const char *co = warm ? objnew : obj; const char *co = warm ? objnew : obj;
const char *ca = warm ? anew : apath; const char *ca = warm ? anew : apath;
int needs_export = !g->pkg[pi].root || root_package;
int needs_archive = !g->pkg[pi].root || root_package;
if (sep_compose_unit(g, pi, cu) < 0) { if (sep_compose_unit(g, pi, cu) < 0) {
g->pkg[pi].failed = 1; g->pkg[pi].failed = 1;
any_failed = 1; any_failed = 1;
@@ -1896,9 +2001,9 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (warm && !stale_all && !deps_changed if (warm && !stale_all && !deps_changed
&& file_equal(unitnew, unitf) && file_equal(unitnew, unitf)
&& file_is_reg(asmf) && file_is_reg(asmf)
&& (!needs_export || file_is_reg(wwi)) && file_is_reg(wwi)
&& (emit_asm || (file_size_nonzero(obj) && (emit_asm || (file_size_nonzero(obj)
&& (!needs_archive || file_size_nonzero(apath))))) { && file_size_nonzero(apath)))) {
if (unlink(unitnew) != 0) { if (unlink(unitnew) != 0) {
fprintf(stderr, "ww: cannot remove %s\n", fprintf(stderr, "ww: cannot remove %s\n",
unitnew); unitnew);
@@ -1907,13 +2012,6 @@ build_one_sep_impl(const char *src, int entry_is_dir,
} }
continue; continue;
} }
/* BUG-1 (#69): -I <wwi> is purely the root's UNUSED
* `.wwi` output path, but it triggers wwi_emit →
* check_exported_type on the root. A terminal binary's
* root legitimately has `export fn` over an unexported
* LOCAL type (the root is never imported), which the
* export-check rejects. Skip -I for the root; its `.wwi`
* is never consumed. */
size_t cargvcap = (size_t)(12 + 3 * g->pkg[pi].ndeps); size_t cargvcap = (size_t)(12 + 3 * g->pkg[pi].ndeps);
char **cargv = calloc(cargvcap, sizeof *cargv); char **cargv = calloc(cargvcap, sizeof *cargv);
char (*importfiles)[SEP_ARTIFACT_MAX] = NULL; char (*importfiles)[SEP_ARTIFACT_MAX] = NULL;
@@ -1930,16 +2028,21 @@ build_one_sep_impl(const char *src, int entry_is_dir,
} }
int cpos = 0; int cpos = 0;
cargv[cpos++] = "w6c"; cargv[cpos++] = "w6c";
if (!needs_export && is_test && g->pkg[pi].root) { if (g->pkg[pi].generated_main
/* #79: the root carries -T under `ww test` || (is_test && g->pkg[pi].root && !g->pkg[pi].is_dir)) {
* so w6c synthesizes the test main. Deps never
* get -T. */
cargv[cpos++] = "-T"; cargv[cpos++] = "-T";
cargv[cpos++] = "--entry";
cargv[cpos++] = "--test-support-module"; cargv[cpos++] = "--test-support-module";
cargv[cpos++] = (char *)test_support_module; cargv[cpos++] = (char *)test_support_module;
} else if (needs_export && g->pkg[pi].test_support) { } else {
cargv[cpos++] = "--test-support-module"; if (is_test && g->pkg[pi].root)
cargv[cpos++] = (char *)test_support_module; cargv[cpos++] = "--test-package";
if (g->pkg[pi].link_entry)
cargv[cpos++] = "--entry";
if (g->pkg[pi].test_support) {
cargv[cpos++] = "--test-support-module";
cargv[cpos++] = (char *)test_support_module;
}
} }
cargv[cpos++] = "-c"; cargv[cpos++] = "-c";
for (int k = 0; k < g->pkg[pi].ndeps; k++) { for (int k = 0; k < g->pkg[pi].ndeps; k++) {
@@ -1950,10 +2053,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
cargv[cpos++] = g->pkg[dj].path; cargv[cpos++] = g->pkg[dj].path;
cargv[cpos++] = importfiles[k]; cargv[cpos++] = importfiles[k];
} }
if (needs_export) { cargv[cpos++] = "-I";
cargv[cpos++] = "-I"; cargv[cpos++] = (char *)cw;
cargv[cpos++] = (char *)cw;
}
cargv[cpos++] = "-o"; cargv[cpos++] = "-o";
cargv[cpos++] = (char *)cs; cargv[cpos++] = (char *)cs;
cargv[cpos++] = (char *)cu; cargv[cpos++] = (char *)cu;
@@ -1968,9 +2069,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
any_failed = 1; any_failed = 1;
continue; continue;
} }
if (needs_export) g->pkg[pi].export_changed = !warm
g->pkg[pi].export_changed = !warm || !file_equal(wwinew, wwi);
|| !file_equal(wwinew, wwi);
if (!emit_asm) { if (!emit_asm) {
char *aargv[] = {"w6a", "-o", (char *)co, char *aargv[] = {"w6a", "-o", (char *)co,
(char *)cs, NULL}; (char *)cs, NULL};
@@ -1982,12 +2082,9 @@ build_one_sep_impl(const char *src, int entry_is_dir,
continue; continue;
} }
} }
/* wrap each DEP package's `.o` in its own deterministic `.a` /* Every package action, including executable and generated-test roots,
* (5a). The ROOT stays a positional `.o` (force-loaded — it's * produces the existing deterministic single-member archive. */
* the build target, always fully linked), so `main` is defined if (!emit_asm) {
* before any archive is processed. The link consumes `.o`/`.a`,
* never `.wwi`. */
if (!emit_asm && needs_archive) {
if (archive_o(co, ca) != 0) { if (archive_o(co, ca) != 0) {
fprintf(stderr, "ww: archive failed for %s\n", fprintf(stderr, "ww: archive failed for %s\n",
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
@@ -1999,11 +2096,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
/* Commit order: artifacts before the unit that vouches for /* Commit order: artifacts before the unit that vouches for
* them, unit strictly last. */ * them, unit strictly last. */
if (warm) { if (warm) {
if ((needs_export && rename(wwinew, wwi) != 0) if (rename(wwinew, wwi) != 0
|| rename(asmnew, asmf) != 0 || rename(asmnew, asmf) != 0
|| (!emit_asm && rename(objnew, obj) != 0) || (!emit_asm && rename(objnew, obj) != 0)
|| (!emit_asm && needs_archive || (!emit_asm && rename(anew, apath) != 0)
&& rename(anew, apath) != 0)
|| rename(unitnew, unitf) != 0) { || rename(unitnew, unitf) != 0) {
fprintf(stderr, "ww: cannot commit %s\n", fprintf(stderr, "ww: cannot commit %s\n",
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
@@ -2066,10 +2162,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
} }
free(order); free(order);
/* Each product gets its own reverse-topological closure: root `.o` first, /* Each product gets its own reverse-topological archive closure: root `.a`
* then every transitively reachable dependency `.a`, then libwwrt.a. A * first, then every transitively reachable package `.a`, then libwwrt.a. An
* same-test root already contains its production sources, so its colocated * internal test variant already contains production sources, so its
* production archive is omitted without dropping that node's dependencies. */ * colocated production archive is omitted without dropping dependencies. */
char rtpaths[2][1024]; char rtpaths[2][1024];
int nrt = 1; int nrt = 1;
snprintf(rtpaths[0], sizeof rtpaths[0], "%s/libwwrt.a", libdir); snprintf(rtpaths[0], sizeof rtpaths[0], "%s/libwwrt.a", libdir);
@@ -2084,6 +2180,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
int nlibs = linkflags ? linkflags->nlibs : 0; int nlibs = linkflags ? linkflags->nlibs : 0;
for (int i = 0; i < nproducts; i++) { for (int i = 0; i < nproducts; i++) {
int root = products[i].root; int root = products[i].root;
int variant_root = products[i].variant_root;
if (g->pkg[root].failed) { any_failed = 1; continue; } if (g->pkg[root].failed) { any_failed = 1; continue; }
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0; for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
int *linkorder = calloc((size_t)g->n, sizeof *linkorder); int *linkorder = calloc((size_t)g->n, sizeof *linkorder);
@@ -2110,14 +2207,15 @@ build_one_sep_impl(const char *src, int entry_is_dir,
largv[pos++] = (char *)products[i].out; largv[pos++] = (char *)products[i].out;
for (int oi = nlink - 1; oi >= 0; oi--) { for (int oi = nlink - 1; oi >= 0; oi--) {
int pi = linkorder[oi]; int pi = linkorder[oi];
if (g->pkg[root].variant == SEP_VARIANT_SAME_TEST if (variant_root >= 0
&& pi != root && g->pkg[variant_root].variant == SEP_VARIANT_SAME_TEST
&& pi != variant_root
&& g->pkg[pi].variant == SEP_VARIANT_PRODUCTION && g->pkg[pi].variant == SEP_VARIANT_PRODUCTION
&& g->pkg[pi].role != SEP_ROLE_TEST_SUPPORT && g->pkg[pi].role != SEP_ROLE_TEST_SUPPORT
&& strcmp(g->pkg[pi].canon, g->pkg[root].canon) == 0) && strcmp(g->pkg[pi].canon,
g->pkg[variant_root].canon) == 0)
continue; continue;
sep_fname(g, pi, scratch, sep_fname(g, pi, scratch, ".a", linkpaths[npath],
pi == root ? ".o" : ".a", linkpaths[npath],
sizeof linkpaths[npath]); sizeof linkpaths[npath]);
largv[pos++] = linkpaths[npath++]; largv[pos++] = linkpaths[npath++];
} }
@@ -2174,7 +2272,10 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
.artifact = {0}, .artifact = {0},
.variant = root_variant, .variant = root_variant,
.root = -1, .root = -1,
.variant_root = -1,
}; };
if (!package_only)
snprintf(product.artifact, sizeof product.artifact, "__root");
int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem, int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem,
extra_includes, linkflags, package_only, is_test, extra_includes, linkflags, package_only, is_test,
&product, 1, emit_asm, workdir, scratch, &product, 1, emit_asm, workdir, scratch,
@@ -2612,6 +2713,7 @@ do_test(int argc, char **argv)
products[nproducts].artifact[0] = '\0'; products[nproducts].artifact[0] = '\0';
products[nproducts].variant = variant; products[nproducts].variant = variant;
products[nproducts].root = -1; products[nproducts].root = -1;
products[nproducts].variant_root = -1;
nproducts++; nproducts++;
} else if (strcmp(argv[i], "-S") == 0) { } else if (strcmp(argv[i], "-S") == 0) {
emit_asm = 1; emit_asm = 1;

View File

@@ -2810,15 +2810,16 @@ the final component of its import path; two logical identities for one physical
directory are rejected rather than compiled twice. directory are rejected rather than compiled twice.
Packages compile serially in dependency-first postorder. The compiler emits the Packages compile serially in dependency-first postorder. The compiler emits the
existing deterministic `.wwi` interface for every importable package. Its existing deterministic `.wwi` interface for every directory-package action,
primary section contains that package's byte-sorted direct imports and exported including an executable root. Its primary section contains that package's
declarations. The compiler then appends byte-sorted, origin-tagged sections for byte-sorted direct imports and exported declarations. The compiler then appends
only the exported foreign type and constant facts recursively reachable from byte-sorted, origin-tagged sections for only the foreign type and constant facts
the primary public signatures. This makes each direct dependency interface recursively reachable from the primary public signatures. Reachable owner-local
self-contained for the public type information its consumers need while private nominal types are carried without `export`: they make the export
retaining the deeper declarations' original package identity. Checked fixed self-contained for type checking, but qualified source lookup still rejects
array dimensions are emitted as numeric type facts, so a public layout never their names. Checked fixed array dimensions are emitted as numeric type facts,
requires exposing the private constant spelling that produced its length. so a public layout never requires exposing the private constant spelling that
produced its length.
A package compilation unit contains only that package's own byte-sorted sources A package compilation unit contains only that package's own byte-sorted sources
and deterministic `//ww:module-reset` separators. Each **direct** import is a and deterministic `//ww:module-reset` separators. Each **direct** import is a
@@ -2835,19 +2836,25 @@ source-like `.wwi` syntax remains a transitional export encoding pending the
binary `.wwe` format described above, but the separate direct-input ownership binary `.wwe` format described above, but the separate direct-input ownership
boundary is live in production Cstage and WWstage compilers and drivers. boundary is live in production Cstage and WWstage compilers and drivers.
An ordinary root is linked with the full reachable object closure into the An ordinary executable directory root is one normal package action. Its
requested executable (legacy WW programs may use a package name other than declared package identity tags its owner-only unit; it receives only direct
`main`). `ww build -p -o lib.a DIR` explicitly requests a non-main package exports, emits `.wwi`, `.o`, and a deterministic `.a`, and is compiled exactly
product: it emits a deterministic archive at `lib.a` and its compiler interface once. The narrow compiler `--entry` flag controls only bare `main` codegen and is
at `lib.a.wwi`, without invoking the linker. A logical target retains its full independent of export production. The linker receives that root archive first,
then the complete reachable package-archive closure and runtime archive; it
never receives `.wwi`. The linkers seed `main` before archive selection, so the
existing WWAR member protocol needs no special root object or format change.
`ww build -p -o lib.a DIR` explicitly requests a non-main package product: it
emits a deterministic archive at `lib.a` and its compiler interface at
`lib.a.wwi`, without invoking the linker. A logical target retains its full
identity (`ww build -p -I ROOT -o bar.a foo.bar` emits `foo.bar.*` symbols), identity (`ww build -p -I ROOT -o bar.a foo.bar` emits `foo.bar.*` symbols),
while a literal directory uses its declared leaf package. Package output while a literal directory uses its declared leaf package. Package output
requires a directory and `-p` cannot be combined with assembly-only `-S`. Two requires a directory and `-p` cannot be combined with assembly-only `-S`. Two
cold builds with identical inputs are required to produce byte-identical cold builds with identical inputs are required to produce byte-identical
requested products. Compiler intrinsics keep their package-mode runtime ABI requested products. Compiler intrinsics keep their package-mode runtime ABI
independent of transitive source interfaces (for example, `alloc` lowers to the independent of transitive source interfaces (for example, `alloc` lowers to the
runtime allocator without requiring an `rt.wwi` compiler input), while the runtime allocator without requiring an `rt.wwi` compiler input).
linker still receives every reachable package archive plus the runtime archive.
### 11.7 Implemented directory package-test slice ### 11.7 Implemented directory package-test slice
@@ -2868,11 +2875,14 @@ semantic selections are only the directory and selected variant/package
identities; it also carries output destinations, coordinator-private completion identities; it also carries output destinations, coordinator-private completion
paths, import search roots, and the optional command-scoped work-directory paths, import search roots, and the optional command-scoped work-directory
policy. The command owns source selection, package loading, compiler inputs, policy. The command owns source selection, package loading, compiler inputs,
archive construction, and linking for every non-importable root: archive construction, generated-main construction, and linking for every
package variant:
- `same-test` selects the byte-sorted production files followed by the - The ordinary production action selects the directory's byte-sorted
byte-sorted matching `package p` test files. They form one compiler unit, so non-test files and is reused wherever that canonical package is imported.
tests can use private production declarations. - `same-test` is a distinct internal production-plus-test action. It selects
the byte-sorted production files followed by the byte-sorted matching
`package p` test files, so tests can use private production declarations.
- `external-test` selects only matching `package p_test` files. Its `import p` - `external-test` selects only matching `package p_test` files. Its `import p`
is a direct edge to the canonical production action for that directory. is a direct edge to the canonical production action for that directory.
That action compiles with module qualifier `p`, selects every production That action compiles with module qualifier `p`, selects every production
@@ -2882,17 +2892,23 @@ archive construction, and linking for every non-importable root:
owning root, such as `__ww-test-001-external-production`; a normal import of owning root, such as `__ww-test-001-external-production`; a normal import of
the same `p` and canonical directory reuses that action rather than creating the same `p` and canonical directory reuses that action rather than creating
a second compilation. a second compilation.
- Each selected internal or external variant gets a distinct generated-main
package action. Its generated owner-only unit declares `package main` and
imports exactly the selected variant and test-support package. It consumes
exactly those direct `.wwi` artifacts, emits its own `.wwi/.o/.a`, and alone
receives compiler `-T --entry`.
The command loads all roots into one command-scoped package universe. Each root The command loads all variants into one command-scoped package universe.
retains an injective artifact key derived from its deterministic request ordinal Directory variants retain injective artifact keys such as
and variant, such as `__ww-test-000-same` or `__ww-test-000-same` or `__ww-test-003-external`; their generated mains use
`__ww-test-003-external`. Hyphens make that namespace illegal as a WW import matching `__ww-test-NNN-main` keys and distinct internal package identities.
identity. Imports of the same canonical production directory intern to one Hyphens keep artifact keys illegal as WW import identities. Repeated requests
production node across every selected test directory. A deterministic for the same canonical directory and variant reuse one compile action, and
dependency-first traversal of the complete union therefore invokes the imports of the same canonical production directory intern to one production
compiler and archiver once for every reachable canonical production package, action across every selected test product. A deterministic dependency-first
even when many directory products need it. Each root is still compiled once traversal of the complete union therefore invokes the compiler and archiver
with its own selected sources and linked separately. The shared plan is once per package variant, even when many products share it. Generated-main
actions remain product-specific and are linked separately. The shared plan is
deliberately package-test-specific: it is not a generalized scheduler, action deliberately package-test-specific: it is not a generalized scheduler, action
schema, cache, or protocol. schema, cache, or protocol.
@@ -2914,26 +2930,30 @@ missing product as a build failure. The single union build and completed test
products share the coordinator's existing `-j` process bound; captured output products share the coordinator's existing `-j` process bound; captured output
is still emitted only in byte-sorted directory/package order. is still emitted only in byte-sorted directory/package order.
Every non-root dependency is always a production variant, so dependency Every source-imported dependency is a production variant, so dependency
`*_test.ww` files never enter the graph. Imports that occur only in selected `*_test.ww` files never enter the graph. Imports that occur only in selected
test files add edges only to that test root. Each compiler unit contains only test files add edges only to that internal or external variant. Each compiler
the variant's owned source set, while its invocation receives only the unit contains only its action's owned source set, while its invocation receives
byte-sorted direct dependency `.wwi` artifacts as separate inputs. The final only the byte-sorted direct dependency `.wwi` artifacts as separate inputs.
test link still receives the root object and the complete reverse-topological Variant compiles receive `--test-package`, which validates and retains private
archive closure. The compiler-generated `-T` dispatcher owns the implicit `@test` declarations as compiler-only export metadata without synthesizing an
direct test-runtime support edge and remains embedded in each independently entry point. The distinct generated-main action consumes that metadata from its
compiled test root; it is the narrow test-main variant, not a direct variant export and synthesizes the dispatcher with `-T`.
coordinator-generated graph package. The command-scoped plan compiles the
common runtime production package once for the complete test request. The The generated-main action, rather than the tested variant, owns the implicit
command resolves that edge from the selected toolchain source tree, not the direct test-runtime support edge. The command-scoped plan compiles the common
user search path; the support package's own imports are also loaded in that support production package once for the complete test request. The command
toolchain context. Normally its graph resolves that edge from the selected toolchain source tree, not the user search
path; the support package's own imports are also loaded in that toolchain
context. Normally its graph
qualifier is `test`, so an explicit source `import test` coalesces with the same qualifier is `test`, so an explicit source `import test` coalesces with the same
canonical package. When a real user package occupies that identity, the command canonical package. When a real user package occupies that identity, the command
presents the runtime edge to the compiler under the reserved `__wwtest` presents the runtime edge to the compiler under the reserved `__wwtest`
qualifier. This keeps a production package named `test` available to external qualifier. This keeps a production package named `test` available to external
tests while preserving raw `w6c -T` compatibility, whose default unresolved tests. Explicit raw single-file `ww test FILE` fixtures retain the narrow fused
qualifier remains `test`. `-T` compatibility path because an anonymous multi-package raw unit is not a
canonical directory package; that path still consumes support as a direct
export and emits a root `.wwi/.a`.
The reserved support action and an ordinary source-imported toolchain `test` The reserved support action and an ordinary source-imported toolchain `test`
action may coexist in the command universe because their compiler qualifiers action may coexist in the command universe because their compiler qualifiers
@@ -2947,24 +2967,25 @@ only narrow action-role exceptions: each product closure is checked to contain
at most one importable action for a compiler module qualifier, so unrelated at most one importable action for a compiler module qualifier, so unrelated
roles can never introduce duplicate linked package symbols. roles can never introduce duplicate linked package symbols.
The selected test roots and a production variant reached by their imports or The selected internal/external variants and a production action reached by
test-runtime closure are the only sanctioned graph nodes that may share a their imports or test-runtime closure are the sanctioned graph nodes that may
physical directory. This also lets a toolchain package's own tests coexist with share a physical directory. This also lets a toolchain package's own tests
the production variant required by the test runtime. A same-package root coexist with the production action required by the test runtime. An internal
already defines those production symbols, so the production archive is omitted variant already defines those production symbols, so the colocated production
from that product's final link while the production node's dependency archives archive is omitted from that product's final link while the production action's
remain in its closure. A reserved compiler-support action at that same physical dependency archives remain in the closure. The external product includes the
directory is still retained. The external product includes the production production archive. Variant-only archives are never linked into another
archive. product. All ordinary logical and physical package-identity collision checks
Variant-only archives are never linked into the other product. All ordinary remain unchanged.
logical and physical package-identity collision checks remain unchanged.
The Cstage linker, like the WWstage linker, passes the root object, every Both stage linkers receive the generated-main archive first, followed by the
reachable archive, the runtime, and explicit `-L`/`-l` values through a complete reverse-topological reachable package-archive closure, runtime, and
structured argument vector; no fixed flattened command buffer can truncate a explicit `-L`/`-l` values through a structured argument vector. No `.wwi` or
large closure. WWstage emits joined `-Ldir` and `-lname` arguments accepted by special root `.o` appears in linker argv, and no fixed flattened command buffer
its native linker, while Cstage preserves the equivalent split forms. Generated can truncate a large closure. WWstage emits joined `-Ldir` and `-lname`
artifact paths are bounds-checked before any unit is opened, so distinct root arguments accepted by its native linker, while Cstage preserves the equivalent
keys cannot alias by truncation. split forms. Generated artifact paths are bounds-checked before any unit is
opened, so distinct root keys cannot alias by truncation.
Recursive discovery groups by physical directory before sorting filenames, and Recursive discovery groups by physical directory before sorting filenames, and
one stable escaped request key names the persistent command work directory. one stable escaped request key names the persistent command work directory.
Every `*_test.ww` package variant is built even when a file only Every `*_test.ww` package variant is built even when a file only
@@ -3021,7 +3042,7 @@ package, compiler actions receive direct dependency exports as individual
arguments beside an owner-only `.unit.ww`, and links receive the complete arguments beside an owner-only `.unit.ww`, and links receive the complete
per-root `.a` closure. Repository-native coverage wraps all three real stage per-root `.a` closure. Repository-native coverage wraps all three real stage
tools at executable paths containing spaces, records every argument boundary, tools at executable paths containing spaces, records every argument boundary,
inspects `.unit.ww`, `.wwi`, `.a`, and root object placement, runs the published inspects `.unit.ww`, `.wwi`, `.a`, and root archive placement, runs the published
binary, compares repeated Cstage/WWstage artifacts and traces, and removes one binary, compares repeated Cstage/WWstage artifacts and traces, and removes one
direct export at compiler entry to compare package-attributed diagnostics. direct export at compiler entry to compare package-attributed diagnostics.
@@ -3198,22 +3219,26 @@ schema or identity record.
The exact-argv regression uses the real diamond The exact-argv regression uses the real diamond
`base -> {left,right} -> root`. It proves one compile per node; no input for `base -> {left,right} -> root`. It proves one compile per node; no input for
`base`; only `base.wwi` for each middle node; only sorted `left.wwi` and `base`; only `base.wwi` for each middle node; only sorted `left.wwi` and
`right.wwi` for `root`; exact owner-only unit bytes; the complete three-archive `right.wwi` for `root`; exact owner-only unit bytes; the complete four-package
link closure; no link-time `.wwi`; exit status 42; and byte-identical units, link closure including the root archive; no link-time `.wwi`; exit status 42;
exports, archives, executables, and tool argument vectors across two clean and byte-identical units, exports, archives, executables, and tool argument
vectors across two clean
Cstage builds and two clean WWstage builds. The existing directory-package Cstage builds and two clean WWstage builds. The existing directory-package
variant regression checks exact direct inputs for internal and external variant regression checks separate production, internal, external, and
generated roots and the external-production action; both stages also compile generated-main actions, exact generated-main direct variant/support exports,
and run ordinary production and support actions with owner-only units and the external-production action, and archive-only link closures. Both stages
byte-identical artifacts. compile and run those actions with owner-only units and byte-identical
artifacts.
The pinned Go 1.26.5 implementation supplies the design boundary: The pinned official Go 1.26.5 tag (commit
`c19862e5f8415b4f24b189d065ed739517c548ba`) supplies the design boundary:
- Directory identity starts with `ImportDir`'s named directory - Directory identity starts with `ImportDir`'s named directory
([`go/build/build.go`, lines 521524](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#521)); directory reads are name-sorted ([`go/build/build.go`, lines 521524](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#521)); directory reads are name-sorted
([lines 108111](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#108)), lookup selects directory candidates ([lines 108111](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#108)), lookup selects directory candidates
([lines 725809](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#725)), and the selected directory is enumerated ([lines 725809](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#725)), and the selected directory is enumerated
([lines 859900](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#859)). ([lines 859900](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#859)). The sorted iteration filters files and appends each source to exactly one ordinary/internal/external-test bucket
([lines 8951036](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#895)).
- The loader records direct imports - The loader records direct imports
([`load/pkg.go`, lines 220225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#220)), guarantees repeated cache loads return the same package pointer ([`load/pkg.go`, lines 220225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#220)), guarantees repeated cache loads return the same package pointer
([lines 633636](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#633)), keys reuse by canonical import path ([lines 633636](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#633)), keys reuse by canonical import path
@@ -3221,22 +3246,26 @@ The pinned Go 1.26.5 implementation supplies the design boundary:
([lines 27762795](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#2776)). ([lines 27762795](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#2776)).
- Build actions are interned by mode and package identity - Build actions are interned by mode and package identity
([`work/action.go`, lines 437447](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#437)); compilation depends only on direct imports ([`work/action.go`, lines 437447](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#437)); compilation depends only on direct imports
([lines 628659](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#628)), whereas link actions expand transitive dependencies ([lines 628659](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#628)), and even package `main` receives that normal interned archive-producing compile action
([lines 641647](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#641)). A link action places that main compile action at its first dependency and expands transitive dependencies
([lines 918957](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#918), ([lines 918957](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#918),
[lines 10341068](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#1034)). The executor derives compiler import inputs from those direct actions [lines 10341068](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#1034)). The executor derives compiler import inputs from those direct actions
([`work/exec.go`, lines 864884](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#864)), passes them separately from source files ([`work/exec.go`, lines 864884](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#864)), passes them separately from source files
([lines 928930](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#928)), and separately emits the expanded linker closure ([lines 928930](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#928)), packs remaining objects into the package archive and records it as the built result
([lines 15921647](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#1592)). ([lines 10171033](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#1017)), then supplies the root archive and all expanded package inputs to linking
([lines 15921624](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#1592),
[lines 16351647](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go#1635)).
- Tests preserve four package roles - Tests preserve four package roles
([`load/test.go`, lines 85102](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#85)): internal production-plus-test ([`load/test.go`, lines 85102](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#85)): internal production-plus-test
([lines 175225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#175)), external test ([lines 175225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#175)), external test
([lines 228265](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#228)), and generated main ([lines 228265](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#228)), and generated main
([lines 272293](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#272)), with their distinct imports attached at ([lines 272293](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#272)). Its support imports are loaded for that generated package
([lines 307332](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#307)), with the selected internal/external variants attached as distinct direct imports at
[lines 351373](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#351). [lines 351373](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#351).
- Unified export writing re-links, re-exports, and prunes facts - Unified export writing re-links, re-exports, and prunes facts
([`noder/unified.go`, lines 152165](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#152)), finalizes self-contained data ([`noder/unified.go`, lines 152165](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#152)), finalizes self-contained data
([lines 463470](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#463)), and sorts declarations and bodies before serialization ([lines 463470](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#463)), selects export roots, and sorts declaration and body indices before serialization
([lines 514570](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#514)). Compiler import lookup opens the separately mapped artifact ([lines 495570](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#495)). Compiler import lookup opens the separately mapped artifact
([`noder/import.go`, lines 61101](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/import.go#61)) and decodes its exports independently of source parsing ([`noder/import.go`, lines 61101](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/import.go#61)) and decodes its exports independently of source parsing
([lines 170225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/import.go#170)); `ReadPackage` consumes that package decoder ([lines 170225](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/import.go#170)); `ReadPackage` consumes that package decoder
([`importer/ureader.go`, lines 2862](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/importer/ureader.go#28)). ([`importer/ureader.go`, lines 2862](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/importer/ureader.go#28)).

View File

@@ -447,9 +447,11 @@ fn dirfileattest(dirpath: *u8, name: *u8) i32 = {
def SEP_VARIANT_PRODUCTION: i32 = 0; def SEP_VARIANT_PRODUCTION: i32 = 0;
def SEP_VARIANT_SAME_TEST: i32 = 1; def SEP_VARIANT_SAME_TEST: i32 = 1;
def SEP_VARIANT_EXTERNAL: i32 = 2; def SEP_VARIANT_EXTERNAL: i32 = 2;
def SEP_VARIANT_TEST_MAIN: i32 = 3;
def SEP_ROLE_NORMAL: i32 = 0; def SEP_ROLE_NORMAL: i32 = 0;
def SEP_ROLE_EXTERNAL_PRODUCTION: i32 = 1; def SEP_ROLE_EXTERNAL_PRODUCTION: i32 = 1;
def SEP_ROLE_TEST_SUPPORT: i32 = 2; def SEP_ROLE_TEST_SUPPORT: i32 = 2;
def SEP_ROLE_GENERATED_MAIN: i32 = 3;
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest"; def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
def SEP_MAXPRODUCT: i32 = 256; def SEP_MAXPRODUCT: i32 = 256;
def SEP_MAXCONTEXT: i32 = 257; def SEP_MAXCONTEXT: i32 = 257;
@@ -757,7 +759,7 @@ type sepbind = struct {
}; };
type seppkg = struct { type seppkg = struct {
path: *u8, // dotted import path, NUL-term; root path[0]==0 path: *u8, // canonical dotted package identity, NUL-term
entry: *u8, // resolved package dir (or file, file root), NUL-term entry: *u8, // resolved package dir (or file, file root), NUL-term
artifact: *u8, // non-importable product-root artifact key artifact: *u8, // non-importable product-root artifact key
name: *u8, // validated declared name; directory packages only name: *u8, // validated declared name; directory packages only
@@ -768,6 +770,8 @@ type seppkg = struct {
variant: i32, variant: i32,
role: i32, role: i32,
root: bool, root: bool,
linkentry: bool,
generatedmain: bool,
failed: bool, failed: bool,
testsupport: bool, testsupport: bool,
loaded: bool, loaded: bool,
@@ -802,6 +806,7 @@ type sepproduct = struct {
variant: i32, variant: i32,
context: i32, context: i32,
root: i32, root: i32,
variantroot: i32,
}; };
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
@@ -819,7 +824,17 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
if (root && g.pkg[i].root) { if (root && g.pkg[i].root) {
if (!samelocation) { i += 1; continue; }; if (!samelocation) { i += 1; continue; };
if (variant != g.pkg[i].variant) { i += 1; continue; }; if (variant != g.pkg[i].variant) { i += 1; continue; };
cerr("ww: duplicate package-test root "); 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 (g.pkg[i].role == role && cstreq(g.pkg[i].path, path)
&& sametest) {
// One directory variant is one compile action across products.
return i;
};
cerr("ww: incompatible package-test roots ");
cerr(pathstr(entry)); cerr("\n"); cerr(pathstr(entry)); cerr("\n");
return -1; return -1;
}; };
@@ -923,6 +938,8 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
g.pkg[g.n].variant = variant; g.pkg[g.n].variant = variant;
g.pkg[g.n].role = role; g.pkg[g.n].role = role;
g.pkg[g.n].root = root; g.pkg[g.n].root = root;
g.pkg[g.n].linkentry = false;
g.pkg[g.n].generatedmain = false;
g.pkg[g.n].failed = false; g.pkg[g.n].failed = false;
g.pkg[g.n].testsupport = false; g.pkg[g.n].testsupport = false;
g.pkg[g.n].loaded = false; g.pkg[g.n].loaded = false;
@@ -1356,6 +1373,79 @@ fn sepdepcmp(g: *sepgraph, a: i32, b: i32) i32 = {
pathstr(g.pkg[b].artifact)): i32; pathstr(g.pkg[b].artifact)): i32;
}; };
fn generatedmainpath(index: i32) *u8 = {
let buf: []u8 = alloc([], 64u64)!;
buf.len = 64;
let off: u64 = strinto(buf.ptr, 0u64, "__wwtestmain.");
buf[off] = (((index / 100) % 10) + 48): u8; off += 1u64;
buf[off] = (((index / 10) % 10) + 48): u8; off += 1u64;
buf[off] = ((index % 10) + 48): u8; off += 1u64;
off = strinto(buf.ptr, off, ".main");
cstrseal(buf.ptr, off);
return buf.ptr;
};
// Materialize the generated test dispatcher as a normal package action. It
// owns a generated source unit and imports exactly its test variant/support.
fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
support: i32) i32 = {
if (g.n >= SEP_MAXPKG) {
cerr("ww: too many packages\n");
return -1;
};
let variant: i32 = product.variantroot;
if (variant < 0 || variant >= g.n) { return -1; };
let p: *seppkg = &g.pkg[g.n];
p.path = generatedmainpath(ordinal);
p.entry = g.pkg[variant].entry;
p.artifact = productartifact(ordinal, SEP_VARIANT_TEST_MAIN);
p.name = arenadupcstr("main\0".ptr, 4u64);
p.testpackage = 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;
p.failed = false;
p.testsupport = false;
p.loaded = true;
p.exportchanged = false;
p.emitcontext = product.context;
let cslot: []u8 = alloc([], SEP_MAXCONTEXT: u64)!;
cslot.len = SEP_MAXCONTEXT;
p.contextstate = cslot;
p.contextstate[product.context] = 2u8;
let emptybindings: []sepbind;
p.bindings = emptybindings;
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
dslot.len = SEP_MAXPKG;
p.deps = dslot;
p.ndeps = 1;
p.deps[0] = variant;
if (support >= 0 && support != variant) {
p.deps[p.ndeps] = support;
p.ndeps += 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;
};
// Load pi once: a directory node takes ownership of its sorted production // Load pi once: a directory node takes ownership of its sorted production
// paths, then every selected-root context verifies the same canonical import // paths, then every selected-root context verifies the same canonical import
// bindings before the package is compiled once. // bindings before the package is compiled once.
@@ -1450,7 +1540,10 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
return rc; return rc;
}; };
if (g.pkg[pi].root && g.pkg[pi].path[0u64] == 0u8 // Directory roots acquire their canonical package identity. Explicit raw
// single-file compiler fixtures retain their anonymous multi-package reset.
if (g.pkg[pi].root && g.pkg[pi].isdir != 0
&& g.pkg[pi].path[0u64] == 0u8
&& g.pkg[pi].name != nil) { && g.pkg[pi].name != nil) {
g.pkg[pi].path = arenadupcstr(g.pkg[pi].name, g.pkg[pi].path = arenadupcstr(g.pkg[pi].name,
cstrlen(g.pkg[pi].name)); cstrlen(g.pkg[pi].name));
@@ -1598,7 +1691,27 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, unitf: *u8) i32 = {
return -1; return -1;
}; };
let bodyrc: i32 = 0; let bodyrc: i32 = 0;
if (g.pkg[pi].isdir != 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; let i: i32 = 0;
for (i < g.pkg[pi].nsources && bodyrc == 0) { for (i < g.pkg[pi].nsources && bodyrc == 0) {
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], g.pkg[pi].path); bodyrc = sepemitbody(u, g.pkg[pi].sources[i], g.pkg[pi].path);
@@ -1606,7 +1719,7 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, unitf: *u8) i32 = {
}; };
} else { } else {
bodyrc = sepemitbody(u, g.pkg[pi].entry, g.pkg[pi].path); bodyrc = sepemitbody(u, g.pkg[pi].entry, g.pkg[pi].path);
}; }; };
if (os.close(u) != 0) { if (os.close(u) != 0) {
cerr("ww: cannot close package unit\n"); cerr("ww: cannot close package unit\n");
return -1; return -1;
@@ -1691,8 +1804,8 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
// buildonesep — discover deps, reverse-topo, // buildonesep — discover deps, reverse-topo,
// the dependency-first producer loop (one `w6c -c -I` per package, // the dependency-first producer loop (one `w6c -c -I` per package,
// each dependency `.o` wrapped in its own deterministic per-package `.a`), then a // each package `.o` wrapped in its own deterministic per-package `.a`), then a
// reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.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 // Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
// build_one_sep. // build_one_sep.
@@ -1811,14 +1924,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = { fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) { if (istest != 0) {
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 7 mode test asm 1\n"; return "ww workdir fmt 8 mode test asm 1\n";
}; };
return "ww workdir fmt 7 mode test asm 0\n"; return "ww workdir fmt 8 mode test asm 0\n";
}; };
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 6 mode build asm 1\n"; return "ww workdir fmt 7 mode build asm 1\n";
}; };
return "ww workdir fmt 6 mode build asm 0\n"; return "ww workdir fmt 7 mode build asm 0\n";
}; };
fn stampmatches(path: *u8, want: str) bool = { fn stampmatches(path: *u8, want: str) bool = {
@@ -2097,7 +2210,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (graphout != nil) { *graphout = g; }; if (graphout != nil) { *graphout = g; };
let rootpath: *u8 = "\0".ptr; let rootpath: *u8 = "\0".ptr;
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; }; if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
let supportfor: []i32 = alloc([], nproducts: u64)!;
supportfor.len = nproducts;
let producti: i32 = 0; let producti: i32 = 0;
for (producti < nproducts) { supportfor[producti] = -1; producti += 1; };
producti = 0;
for (producti < nproducts) { for (producti < nproducts) {
let entry: *u8 = src; let entry: *u8 = src;
if (products[producti].dir != nil) { if (products[producti].dir != nil) {
@@ -2113,12 +2230,16 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
products[producti].testpackage, products[producti].testpackage,
SEP_ROLE_NORMAL, products[producti].artifact, true); SEP_ROLE_NORMAL, products[producti].artifact, true);
if (products[producti].root < 0) { return 1; }; if (products[producti].root < 0) { return 1; };
products[producti].variantroot = products[producti].root;
if (istest == 0 && packageonly == 0) {
g.pkg[products[producti].root].linkentry = true;
};
producti += 1; producti += 1;
}; };
let testsupportmodule: str = "test"; let testsupportmodule: str = "test";
// -T generates a dispatcher whose support qualifier is selected by the // -T generates a dispatcher whose support qualifier is selected by the
// command. Represent that compiler-generated requirement as a direct root // command. Represent that requirement as a direct generated-main edge. It
// edge. It normally coalesces with an explicit toolchain `import test`; // normally coalesces with an explicit toolchain `import test`;
// when user source occupies that identity, the reserved graph alias keeps // when user source occupies that identity, the reserved graph alias keeps
// it distinct. The linker receives the same support archive closure. // it distinct. The linker receives the same support archive closure.
if (istest != 0) { if (istest != 0) {
@@ -2168,6 +2289,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (rootissupport if (rootissupport
&& syntax.streq(testsupportmodule, "test") && syntax.streq(testsupportmodule, "test")
&& products[producti].variant != SEP_VARIANT_EXTERNAL) { && products[producti].variant != SEP_VARIANT_EXTERNAL) {
supportfor[producti] = root;
producti += 1; producti += 1;
continue; continue;
}; };
@@ -2181,25 +2303,32 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
if (ti < 0) { return 1; }; if (ti < 0) { return 1; };
g.pkg[ti].testsupport = true; g.pkg[ti].testsupport = true;
let seen: bool = false; supportfor[producti] = ti;
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;
};
};
producti += 1; producti += 1;
}; };
}; };
}; };
producti = 0; producti = 0;
for (producti < nproducts) { for (producti < nproducts) {
let root: i32 = products[producti].root; let root: i32 = products[producti].variantroot;
// 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 = supportfor[producti];
if (support >= 0 && support != root) {
let seen: bool = false;
let sk: i32 = 0;
for (sk < g.pkg[root].ndeps) {
if (g.pkg[root].deps[sk] == support) { seen = true; };
sk += 1;
};
if (!seen) {
g.pkg[root].deps[g.pkg[root].ndeps] = support;
g.pkg[root].ndeps += 1;
};
};
g.pkg[root].linkentry = true;
};
if (seploadpkg(g, root, products[producti].context) < 0) { if (seploadpkg(g, root, products[producti].context) < 0) {
g.pkg[root].failed = true; g.pkg[root].failed = true;
producti += 1; producti += 1;
@@ -2214,6 +2343,27 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
producti += 1; producti += 1;
}; };
if (istest != 0 && entryisdir != 0) {
producti = 0;
for (producti < nproducts) {
let variant: i32 = products[producti].variantroot;
let support: i32 = supportfor[producti];
if (support >= 0 && support != variant) {
if (seploadpkg(g, support, products[producti].context) < 0) {
g.pkg[variant].failed = true;
};
};
let mainpkg: i32 = sepaddgeneratedmain(g, &products[producti],
producti, support);
if (mainpkg < 0) { return 1; };
if (g.pkg[variant].failed
|| (support >= 0 && g.pkg[support].failed)) {
g.pkg[mainpkg].failed = true;
};
products[producti].root = mainpkg;
producti += 1;
};
};
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; }; if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
let rootpackage: bool = packageonly != 0; let rootpackage: bool = packageonly != 0;
if (rootpackage && !g.pkg[products[0].root].failed if (rootpackage && !g.pkg[products[0].root].failed
@@ -2239,8 +2389,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let ignored: i32 = 0; let ignored: i32 = 0;
if (septopovisit(g, root, order, if (septopovisit(g, root, order,
&ignored, stack, 0) < 0 &ignored, stack, 0) < 0
|| sepvalidatemoduleclosure(g, order, ignored, || sepvalidatemoduleclosure(g, order, ignored, true) < 0) {
rootpackage) < 0) {
g.pkg[root].failed = true; g.pkg[root].failed = true;
}; };
}; };
@@ -2257,14 +2406,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
producti += 1; producti += 1;
}; };
if (!rootpackage) {
producti = 0;
for (producti < nproducts) {
g.pkg[products[producti].root].path = "\0".ptr;
producti += 1;
};
};
let anyfailed: bool = false; let anyfailed: bool = false;
producti = 0; producti = 0;
for (producti < nproducts) { for (producti < nproducts) {
@@ -2307,8 +2448,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
cu = unitnew; cw = wwinew; cs = asmnew; cu = unitnew; cw = wwinew; cs = asmnew;
co = objnew; ca = anew; co = objnew; ca = anew;
}; };
let needsexport: bool = !g.pkg[pi].root || rootpackage;
let needsarchive: bool = !g.pkg[pi].root || rootpackage;
if (sepcomposeunit(g, pi, cu) < 0) { if (sepcomposeunit(g, pi, cu) < 0) {
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
anyfailed = true; anyfailed = true;
@@ -2328,20 +2467,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (!staleall && !depschanged) { if (!staleall && !depschanged) {
fresh = fileequal(unitnew, unitf); fresh = fileequal(unitnew, unitf);
if (fresh) { fresh = fileisreg(asmf); }; if (fresh) { fresh = fileisreg(asmf); };
if (fresh) { if (fresh) { fresh = fileisreg(wwi); };
if (needsexport) {
fresh = fileisreg(wwi);
};
};
if (fresh) { if (fresh) {
if (emitasm == 0) { if (emitasm == 0) {
fresh = filesizenonzero(objf); fresh = filesizenonzero(objf);
}; };
}; };
if (fresh) { if (fresh && emitasm == 0) {
if (emitasm == 0 && needsarchive) { fresh = filesizenonzero(apath);
fresh = filesizenonzero(apath);
};
}; };
}; };
}; };
@@ -2355,31 +2488,34 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
continue; continue;
}; };
{ {
// BUG-1 (#69): -I <wwi> is purely the root's UNUSED let rawtest: bool = (istest != 0) && g.pkg[pi].root
// `.wwi` output path, but it triggers wwiemit -> && g.pkg[pi].isdir == 0;
// checkexportedtype on the root. A terminal binary's let gent: bool = g.pkg[pi].generatedmain || rawtest;
// root legitimately has `export fn` over an unexported let testpkg: bool = g.pkg[pi].root && (istest != 0) && !gent;
// LOCAL type (the root is never imported), which the let entry: bool = g.pkg[pi].linkentry;
// export-check rejects. Build a shorter root argv let supportpkg: bool = g.pkg[pi].testsupport;
// without the -I/wwi pair; root's `.wwi` is unconsumed.
// #79: the root carries -T under `ww test` so w6c
// synthesizes the test main; deps never get -T.
let roott: bool = g.pkg[pi].root && (istest != 0);
let supportt: bool = g.pkg[pi].testsupport;
let alen: u64 = 8u64; let alen: u64 = 8u64;
if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; }; if (gent) { alen += 4u64; }
if (supportt) { alen += 2u64; }; else {
if (testpkg) { alen += 1u64; };
if (entry) { alen += 1u64; };
if (supportpkg) { alen += 2u64; };
};
alen += (g.pkg[pi].ndeps: u64) * 3u64; alen += (g.pkg[pi].ndeps: u64) * 3u64;
let argv: []str = alloc([], alen)!; let argv: []str = alloc([], alen)!;
append(argv, "w6c"); append(argv, "w6c");
if (roott) { if (gent) {
append(argv, "-T"); append(argv, "-T");
append(argv, "--entry");
append(argv, "--test-support-module"); append(argv, "--test-support-module");
append(argv, testsupportmodule); append(argv, testsupportmodule);
}; } else {
if (supportt) { if (testpkg) { append(argv, "--test-package"); };
append(argv, "--test-support-module"); if (entry) { append(argv, "--entry"); };
append(argv, testsupportmodule); if (supportpkg) {
append(argv, "--test-support-module");
append(argv, testsupportmodule);
};
}; };
append(argv, "-c"); append(argv, "-c");
let importk: i32 = 0; let importk: i32 = 0;
@@ -2390,10 +2526,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
append(argv, pathstr(sepfname(g, dj, scratch, ".wwi"))); append(argv, pathstr(sepfname(g, dj, scratch, ".wwi")));
importk += 1; importk += 1;
}; };
if (needsexport) { append(argv, "-I");
append(argv, "-I"); append(argv, pathstr(cw));
append(argv, pathstr(cw));
};
append(argv, "-o"); append(argv, "-o");
append(argv, pathstr(cs)); append(argv, pathstr(cs));
append(argv, pathstr(cu)); append(argv, pathstr(cu));
@@ -2418,10 +2552,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
continue; continue;
}; };
}; };
if (needsexport) { if (!warm || !fileequal(wwinew, wwi)) {
if (!warm || !fileequal(wwinew, wwi)) { g.pkg[pi].exportchanged = true;
g.pkg[pi].exportchanged = true;
};
}; };
if (emitasm == 0) { if (emitasm == 0) {
let argv: []str = alloc([], 4u64)!; let argv: []str = alloc([], 4u64)!;
@@ -2450,12 +2582,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
continue; continue;
}; };
}; };
// Wrap each DEP package's `.o` in its own deterministic `.a` // Every package action, including executable/generated roots, produces
// (5a). The ROOT stays a positional `.o` (force-loaded — it's // the existing deterministic single-member archive.
// the build target), so `main` is defined before any archive is if (emitasm == 0) {
// processed. The link
// consumes `.o`/`.a`, never `.wwi`.
if (emitasm == 0 && needsarchive) {
if (archiveo(co, ca) != 0) { if (archiveo(co, ca) != 0) {
cerr("ww: archive failed\n"); cerr("ww: archive failed\n");
g.pkg[pi].failed = true; g.pkg[pi].failed = true;
@@ -2468,10 +2597,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
// them, unit strictly last. // them, unit strictly last.
if (warm) { if (warm) {
let bad: bool = false; let bad: bool = false;
if (needsexport) { if (os.rename(pathstr(wwinew), pathstr(wwi)) != 0) {
if (os.rename(pathstr(wwinew), pathstr(wwi)) != 0) { bad = true;
bad = true;
};
}; };
if (!bad) { if (!bad) {
if (os.rename(pathstr(asmnew), pathstr(asmf)) != 0) { if (os.rename(pathstr(asmnew), pathstr(asmf)) != 0) {
@@ -2486,7 +2613,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
}; };
if (!bad) { if (!bad) {
if (emitasm == 0 && needsarchive) { if (emitasm == 0) {
if (os.rename(pathstr(anew), pathstr(apath)) != 0) { if (os.rename(pathstr(anew), pathstr(apath)) != 0) {
bad = true; bad = true;
}; };
@@ -2573,6 +2700,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
producti = 0; producti = 0;
for (producti < nproducts) { for (producti < nproducts) {
let root: i32 = products[producti].root; let root: i32 = products[producti].root;
let variantroot: i32 = products[producti].variantroot;
if (g.pkg[root].failed) { if (g.pkg[root].failed) {
anyfailed = true; anyfailed = true;
producti += 1; producti += 1;
@@ -2598,18 +2726,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let li: i32 = nlink - 1; let li: i32 = nlink - 1;
for (li >= 0) { for (li >= 0) {
let pi: i32 = linkorder[li]; let pi: i32 = linkorder[li];
if (g.pkg[root].variant == SEP_VARIANT_SAME_TEST if (variantroot >= 0
&& pi != root && g.pkg[variantroot].variant == SEP_VARIANT_SAME_TEST
&& pi != variantroot
&& g.pkg[pi].variant == SEP_VARIANT_PRODUCTION && g.pkg[pi].variant == SEP_VARIANT_PRODUCTION
&& g.pkg[pi].role != SEP_ROLE_TEST_SUPPORT && g.pkg[pi].role != SEP_ROLE_TEST_SUPPORT
&& os.samefile(pathstr(g.pkg[pi].entry), && os.samefile(pathstr(g.pkg[pi].entry),
pathstr(g.pkg[root].entry))) { pathstr(g.pkg[variantroot].entry))) {
li -= 1; li -= 1;
continue; continue;
}; };
let suf: str = ".a"; largv[pos] = sepfname(g, pi, scratch, ".a");
if (pi == root) { suf = ".o"; };
largv[pos] = sepfname(g, pi, scratch, suf);
pos += 1; pos += 1;
li -= 1; li -= 1;
}; };
@@ -2694,8 +2821,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
product.testpackage = testpackage; product.testpackage = testpackage;
product.status = nil; product.status = nil;
product.artifact = nil; product.artifact = nil;
if (packageonly == 0) { product.artifact = "__root\0".ptr; };
product.variant = rootvariant; product.variant = rootvariant;
product.root = -1; product.root = -1;
product.variantroot = -1;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity, let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
out, objstem, out, objstem,
incs, lf, packageonly, istest, &product, 1, incs, lf, packageonly, istest, &product, 1,
@@ -2755,9 +2884,11 @@ fn productartifact(index: i32, variant: i32) *u8 = {
off = byteinto(buf.ptr, off, '-': u8); off = byteinto(buf.ptr, off, '-': u8);
if (variant == SEP_VARIANT_SAME_TEST) { if (variant == SEP_VARIANT_SAME_TEST) {
off = strinto(buf.ptr, off, "same"); off = strinto(buf.ptr, off, "same");
} else { if (variant == SEP_VARIANT_TEST_MAIN) {
off = strinto(buf.ptr, off, "main");
} else { } else {
off = strinto(buf.ptr, off, "external"); off = strinto(buf.ptr, off, "external");
}; }; };
cstrseal(buf.ptr, off); cstrseal(buf.ptr, off);
return buf.ptr; return buf.ptr;
}; };
@@ -3446,6 +3577,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
product.artifact = nil; product.artifact = nil;
product.variant = variant; product.variant = variant;
product.root = -1; product.root = -1;
product.variantroot = -1;
append(products, product); append(products, product);
i += 6; i += 6;
continue; continue;

View File

@@ -14,9 +14,9 @@ package packedwwi_test;
// type.ha:122-126). Both driver stages must build the -I tree and run // type.ha:122-126). Both driver stages must build the -I tree and run
// exit 91 (9*10+1; a dropped @packed gives 168). Strengthened over // exit 91 (9*10+1; a dropped @packed gives 168). Strengthened over
// the carrier: the re-emitted `struct @packed {` is also asserted in // the carrier: the re-emitted `struct @packed {` is also asserted in
// the retained sepwork pk2.wwi. A single-file two-package form // the retained sepwork pk2.wwi. A single-file two-package form produces
// produces NO .wwi (one __root unit), so the real -I tree is // only one raw `__root.wwi`, not an independently importable pk2 package,
// irreducible here. // so the real directory-package -I tree is irreducible here.
// //
// identityreject: assigning packed A to a structurally-identical // identityreject: assigning packed A to a structurally-identical
// unpacked B — packed is type identity (harec types.c:621). STAGE- // unpacked B — packed is type identity (harec types.c:621). STAGE-

View File

@@ -786,6 +786,11 @@ fn workescape(s: str) str = {
let referencesameunit: str = ""; let referencesameunit: str = "";
let referenceexternalunit: str = ""; let referenceexternalunit: str = "";
let referenceproductionunit: str = ""; let referenceproductionunit: str = "";
let packageactions: []str = ["__ww-test-000-same",
"__ww-test-000-main", "__ww-test-001-external",
"__ww-test-001-main"];
let referenceactionexports: []str = ["", "", "", ""];
let referenceactionarchives: []str = ["", "", "", ""];
let i: i32 = 0; let i: i32 = 0;
for (i < drivers.len) { for (i < drivers.len) {
let av: []str = [driver(drivers[i]), "test", "-c", "-I", root, let av: []str = [driver(drivers[i]), "test", "-c", "-I", root,
@@ -807,6 +812,10 @@ fn workescape(s: str) str = {
"__ww-test-001-external.unit.ww")); "__ww-test-001-external.unit.ww"));
let externalproduction: str = readfile(strings.concat(sharedwork, let externalproduction: str = readfile(strings.concat(sharedwork,
"__ww-test-001-external-production.unit.ww")); "__ww-test-001-external-production.unit.ww"));
let samemainunit: str = readfile(strings.concat(sharedwork,
"__ww-test-000-main.unit.ww"));
let externalmainunit: str = readfile(strings.concat(sharedwork,
"__ww-test-001-main.unit.ww"));
assert(!os.exists(strings.concat(externalbin, ".sepwork"))); assert(!os.exists(strings.concat(externalbin, ".sepwork")));
assert(!has(sameunit, "//ww:module ")); assert(!has(sameunit, "//ww:module "));
assert(os.exists(strings.concat(sharedwork, assert(os.exists(strings.concat(sharedwork,
@@ -824,28 +833,43 @@ fn workescape(s: str) str = {
assert(!has(externalproduction, "//ww:module ")); assert(!has(externalproduction, "//ww:module "));
assert(!has(externalproduction, "SAME_TEST_SOURCE")); assert(!has(externalproduction, "SAME_TEST_SOURCE"));
assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE")); assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE"));
assert(same(samemainunit, strings.concat(
"//ww:module-reset __wwtestmain.000.main\n",
"package main;\nimport pkg;\nimport test;\n")));
assert(same(externalmainunit, strings.concat(
"//ww:module-reset __wwtestmain.001.main\n",
"package main;\nimport pkg_test;\nimport test;\n")));
assert(!has(samemainunit, "PACKAGE_PRODUCTION"));
assert(!has(samemainunit, "SAME_TEST_SOURCE"));
assert(!has(externalmainunit, "EXTERNAL_TEST_SOURCE"));
assert(!has(readfile(strings.concat(sharedwork, "api.unit.ww")), assert(!has(readfile(strings.concat(sharedwork, "api.unit.ww")),
"TEST_DEPENDENCY_MUST_NOT_COMPILE")); "TEST_DEPENDENCY_MUST_NOT_COMPILE"));
let artifacts: []str = ["implementation", "api", let artifacts: []str = ["implementation", "api",
"__ww-test-001-external-production", "__ww-test-001-external-production",
"__same", "__external", "test"]; "__same", "__external", "test", "__ww-test-000-same",
"__ww-test-000-main", "__ww-test-001-external",
"__ww-test-001-main"];
let ai: i32 = 0; let ai: i32 = 0;
for (ai < artifacts.len) { for (ai < artifacts.len) {
assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".wwi"))); assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".wwi")));
assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".a"))); assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".a")));
ai += 1; ai += 1;
}; };
assert(os.exists(strings.concat(sharedwork, "__ww-test-000-same.o"))); ai = 0;
assert(os.exists(strings.concat(sharedwork, for (ai < packageactions.len) {
"__ww-test-001-external.o"))); let ex: str = readfile(strings.concat(sharedwork,
assert(!os.exists(strings.concat(sharedwork, packageactions[ai], ".wwi"));
"__ww-test-000-same.wwi"))); let ar: str = readfile(strings.concat(sharedwork,
assert(!os.exists(strings.concat(sharedwork, packageactions[ai], ".a"));
"__ww-test-001-external.wwi"))); if (i == 0) {
assert(!os.exists(strings.concat(sharedwork, referenceactionexports[ai] = strings.dup(ex);
"__ww-test-000-same.a"))); referenceactionarchives[ai] = strings.dup(ar);
assert(!os.exists(strings.concat(sharedwork, } else {
"__ww-test-001-external.a"))); assert(same(referenceactionexports[ai], ex));
assert(same(referenceactionarchives[ai], ar));
};
ai += 1;
};
assert(os.exists(samebin)); assert(os.exists(samebin));
assert(os.exists(externalbin)); assert(os.exists(externalbin));
if (i == 0) { if (i == 0) {
@@ -862,7 +886,11 @@ fn workescape(s: str) str = {
assert(occurrences(ctrace, assert(occurrences(ctrace,
"__ww-test-001-external.unit.ww") == 1); "__ww-test-001-external.unit.ww") == 1);
assert(occurrences(ctrace, assert(occurrences(ctrace,
"-T --test-support-module") == 2); "__ww-test-000-main.unit.ww") == 1);
assert(occurrences(ctrace,
"__ww-test-001-main.unit.ww") == 1);
assert(occurrences(ctrace, "-T --entry --test-support-module") == 2);
assert(occurrences(ctrace, "--test-package") == 2);
let samecompile: str = linecontaining(ctrace, let samecompile: str = linecontaining(ctrace,
"__ww-test-000-same.unit.ww"); "__ww-test-000-same.unit.ww");
let externalcompile: str = linecontaining(ctrace, let externalcompile: str = linecontaining(ctrace,
@@ -870,18 +898,36 @@ fn workescape(s: str) str = {
let productioncompile: str = linecontaining(ctrace, let productioncompile: str = linecontaining(ctrace,
"__ww-test-001-external-production.unit.ww"); "__ww-test-001-external-production.unit.ww");
assert(same(samecompile, strings.concat( assert(same(samecompile, strings.concat(
"-T --test-support-module test -c --import __same ", "--test-package -c --import __same ",
sharedwork, "__same.wwi --import api ", sharedwork, sharedwork, "__same.wwi --import api ", sharedwork,
"api.wwi --import test ", sharedwork, "test.wwi -o ", "api.wwi -I ", sharedwork, "__ww-test-000-same.wwi -o ",
sharedwork, "__ww-test-000-same.s ", sharedwork, sharedwork, "__ww-test-000-same.s ", sharedwork,
"__ww-test-000-same.unit.ww"))); "__ww-test-000-same.unit.ww")));
assert(same(externalcompile, strings.concat( assert(same(externalcompile, strings.concat(
"-T --test-support-module test -c --import __external ", "--test-package -c --import __external ",
sharedwork, "__external.wwi --import pkg ", sharedwork, sharedwork, "__external.wwi --import pkg ", sharedwork,
"__ww-test-001-external-production.wwi --import test ", "__ww-test-001-external-production.wwi -I ", sharedwork,
sharedwork, "test.wwi -o ", sharedwork, "__ww-test-001-external.wwi -o ", sharedwork,
"__ww-test-001-external.s ", sharedwork, "__ww-test-001-external.s ", sharedwork,
"__ww-test-001-external.unit.ww"))); "__ww-test-001-external.unit.ww")));
let samemaincompile: str = linecontaining(ctrace,
"__ww-test-000-main.unit.ww");
let externalmaincompile: str = linecontaining(ctrace,
"__ww-test-001-main.unit.ww");
assert(same(samemaincompile, strings.concat(
"-T --entry --test-support-module test -c --import pkg ",
sharedwork, "__ww-test-000-same.wwi --import test ",
sharedwork, "test.wwi -I ", sharedwork,
"__ww-test-000-main.wwi -o ", sharedwork,
"__ww-test-000-main.s ", sharedwork,
"__ww-test-000-main.unit.ww")));
assert(same(externalmaincompile, strings.concat(
"-T --entry --test-support-module test -c --import pkg_test ",
sharedwork, "__ww-test-001-external.wwi --import test ",
sharedwork, "test.wwi -I ", sharedwork,
"__ww-test-001-main.wwi -o ", sharedwork,
"__ww-test-001-main.s ", sharedwork,
"__ww-test-001-main.unit.ww")));
assert(same(productioncompile, strings.concat( assert(same(productioncompile, strings.concat(
"-c --import api ", sharedwork, "api.wwi -I ", sharedwork, "-c --import api ", sharedwork, "api.wwi -I ", sharedwork,
"__ww-test-001-external-production.wwi -o ", sharedwork, "__ww-test-001-external-production.wwi -o ", sharedwork,
@@ -897,12 +943,22 @@ fn workescape(s: str) str = {
assert(has(samelink, assert(has(samelink,
strings.concat(sharedwork, "implementation.a"))); strings.concat(sharedwork, "implementation.a")));
assert(has(samelink, strings.concat(sharedwork, "__same.a"))); assert(has(samelink, strings.concat(sharedwork, "__same.a")));
assert(has(samelink, strings.concat(sharedwork,
"__ww-test-000-main.a")));
assert(has(samelink, strings.concat(sharedwork,
"__ww-test-000-same.a")));
assert(!has(samelink, strings.concat(sharedwork, assert(!has(samelink, strings.concat(sharedwork,
"__ww-test-001-external-production.a"))); "__ww-test-001-external-production.a")));
assert(!has(samelink, assert(!has(samelink,
strings.concat(sharedwork, "__external.a"))); strings.concat(sharedwork, "__external.a")));
assert(has(externallink, strings.concat(sharedwork, assert(has(externallink, strings.concat(sharedwork,
"__ww-test-001-external-production.a"))); "__ww-test-001-external-production.a")));
assert(has(externallink, strings.concat(sharedwork,
"__ww-test-001-main.a")));
assert(has(externallink, strings.concat(sharedwork,
"__ww-test-001-external.a")));
assert(!has(samelink, ".wwi"));
assert(!has(externallink, ".wwi"));
assert(has(externallink, strings.concat(sharedwork, "api.a"))); assert(has(externallink, strings.concat(sharedwork, "api.a")));
assert(has(externallink, assert(has(externallink,
strings.concat(sharedwork, "implementation.a"))); strings.concat(sharedwork, "implementation.a")));
@@ -1176,6 +1232,8 @@ fn workescape(s: str) str = {
let rootkeys: []str = ["__ww-test-000-same", let rootkeys: []str = ["__ww-test-000-same",
"__ww-test-001-external", "__ww-test-002-same", "__ww-test-001-external", "__ww-test-002-same",
"__ww-test-003-external"]; "__ww-test-003-external"];
let mainkeys: []str = ["__ww-test-000-main", "__ww-test-001-main",
"__ww-test-002-main", "__ww-test-003-main"];
let alphaartifact: str = "__ww-test-001-external-production"; let alphaartifact: str = "__ww-test-001-external-production";
let betaartifact: str = "__ww-test-003-external-production"; let betaartifact: str = "__ww-test-003-external-production";
let expectedtests: []str = ["alpha_same_runs ... ok\n", let expectedtests: []str = ["alpha_same_runs ... ok\n",
@@ -1248,7 +1306,11 @@ fn workescape(s: str) str = {
assert(!has(roots[3], "MULTIDIR_BETA_SAME")); assert(!has(roots[3], "MULTIDIR_BETA_SAME"));
let products: []str = ["leaf", "common", alphaartifact, betaartifact, let products: []str = ["leaf", "common", alphaartifact, betaartifact,
"_alpha_same", "_alpha_external", "_beta_same", "_alpha_same", "_alpha_external", "_beta_same",
"_beta_external", "test"]; "_beta_external", "test", "__ww-test-000-same",
"__ww-test-001-external", "__ww-test-002-same",
"__ww-test-003-external", "__ww-test-000-main",
"__ww-test-001-main", "__ww-test-002-main",
"__ww-test-003-main"];
let pi: i32 = 0; let pi: i32 = 0;
for (pi < products.len) { for (pi < products.len) {
assert(os.exists(strings.concat(sharedwork, products[pi], assert(os.exists(strings.concat(sharedwork, products[pi],
@@ -1260,8 +1322,11 @@ fn workescape(s: str) str = {
let ri: i32 = 0; let ri: i32 = 0;
for (ri < rootkeys.len) { for (ri < rootkeys.len) {
assert(os.exists(strings.concat(sharedwork, rootkeys[ri], ".o"))); assert(os.exists(strings.concat(sharedwork, rootkeys[ri], ".o")));
assert(!os.exists(strings.concat(sharedwork, rootkeys[ri], ".wwi"))); assert(os.exists(strings.concat(sharedwork, rootkeys[ri], ".wwi")));
assert(!os.exists(strings.concat(sharedwork, rootkeys[ri], ".a"))); assert(os.exists(strings.concat(sharedwork, rootkeys[ri], ".a")));
assert(os.exists(strings.concat(sharedwork, mainkeys[ri], ".unit.ww")));
assert(os.exists(strings.concat(sharedwork, mainkeys[ri], ".wwi")));
assert(os.exists(strings.concat(sharedwork, mainkeys[ri], ".a")));
assert(os.exists(bins[ri])); assert(os.exists(bins[ri]));
if (ri > 0) { if (ri > 0) {
assert(!os.exists(strings.concat(bins[ri], ".sepwork"))); assert(!os.exists(strings.concat(bins[ri], ".sepwork")));
@@ -1293,6 +1358,8 @@ fn workescape(s: str) str = {
for (ri < rootkeys.len) { for (ri < rootkeys.len) {
assert(occurrences(ctrace, strings.concat(rootkeys[ri], assert(occurrences(ctrace, strings.concat(rootkeys[ri],
".unit.ww")) == 1); ".unit.ww")) == 1);
assert(occurrences(ctrace, strings.concat(mainkeys[ri],
".unit.ww")) == 1);
ri += 1; ri += 1;
}; };
assert(pos(ctrace, "/leaf.unit.ww") assert(pos(ctrace, "/leaf.unit.ww")
@@ -1309,7 +1376,9 @@ fn workescape(s: str) str = {
for (ri < bins.len) { for (ri < bins.len) {
let link: str = linecontaining(ltrace, let link: str = linecontaining(ltrace,
strings.concat("-o ", bins[ri], " ")); strings.concat("-o ", bins[ri], " "));
assert(has(link, strings.concat(sharedwork, rootkeys[ri], ".o"))); assert(has(link, strings.concat(sharedwork, mainkeys[ri], ".a")));
assert(has(link, strings.concat(sharedwork, rootkeys[ri], ".a")));
assert(!has(link, ".wwi"));
assert(has(link, strings.concat(sharedwork, "common.a"))); assert(has(link, strings.concat(sharedwork, "common.a")));
assert(has(link, strings.concat(sharedwork, "leaf.a"))); assert(has(link, strings.concat(sharedwork, "leaf.a")));
assert(has(link, strings.concat(sharedwork, "test.a"))); assert(has(link, strings.concat(sharedwork, "test.a")));
@@ -1399,6 +1468,8 @@ fn workescape(s: str) str = {
for (ri < rootkeys.len) { for (ri < rootkeys.len) {
assert(occurrences(wwtrace, strings.concat(rootkeys[ri], assert(occurrences(wwtrace, strings.concat(rootkeys[ri],
".unit.ww")) == 1); ".unit.ww")) == 1);
assert(occurrences(wwtrace, strings.concat(mainkeys[ri],
".unit.ww")) == 1);
ri += 1; ri += 1;
}; };
assert(occurrences(readfile(wwlinkertrace), "\n") == 4); assert(occurrences(readfile(wwlinkertrace), "\n") == 4);
@@ -1747,7 +1818,7 @@ fn workescape(s: str) str = {
let cbin: str = readfile(bin); let cbin: str = readfile(bin);
let rootunit: str = readfile(strings.concat(work, let rootunit: str = readfile(strings.concat(work,
"__ww-test-000-same.unit.ww")); "__ww-test-000-same.unit.ww"));
assert(has(rootunit, "//ww:module-reset\npackage target;")); assert(has(rootunit, "//ww:module-reset target\npackage target;"));
assert(!has(rootunit, "//ww:module ")); assert(!has(rootunit, "//ww:module "));
let linkargs: str = readfile(trace); let linkargs: str = readfile(trace);
assert(linkargs.len > 8192); assert(linkargs.len > 8192);
@@ -2214,7 +2285,7 @@ fn workescape(s: str) str = {
let workroot: str = strings.concat(bin, ".sepwork"); let workroot: str = strings.concat(bin, ".sepwork");
let work: str = strings.concat(workroot, "/"); let work: str = strings.concat(workroot, "/");
let artifacts: []str = ["ww_root_parity_base_7f3", let artifacts: []str = ["ww_root_parity_base_7f3",
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3"]; "ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "__root"];
let unitartifacts: []str = ["ww_root_parity_base_7f3", let unitartifacts: []str = ["ww_root_parity_base_7f3",
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "__root"]; "ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "__root"];
let wantunits: []str = [strings.concat( let wantunits: []str = [strings.concat(
@@ -2224,10 +2295,10 @@ fn workescape(s: str) str = {
leftbody, "\n"), leftbody, "\n"),
strings.concat("//ww:module-reset ww_root_parity_right_7f3\n", strings.concat("//ww:module-reset ww_root_parity_right_7f3\n",
rightbody, "\n"), rightbody, "\n"),
strings.concat("//ww:module-reset\n", rootbody, "\n")]; strings.concat("//ww:module-reset main\n", rootbody, "\n")];
let referenceunits: []str = ["", "", "", ""]; let referenceunits: []str = ["", "", "", ""];
let referenceexports: []str = ["", "", ""]; let referenceexports: []str = ["", "", "", ""];
let referencearchives: []str = ["", "", ""]; let referencearchives: []str = ["", "", "", ""];
let referencebin: str = ""; let referencebin: str = "";
let referencecompiler: str = ""; let referencecompiler: str = "";
let referenceassembler: str = ""; let referenceassembler: str = "";
@@ -2327,8 +2398,6 @@ fn workescape(s: str) str = {
}; };
ai += 1; ai += 1;
}; };
assert(!os.exists(strings.concat(work, "__root.wwi")));
assert(!os.exists(strings.concat(work, "__root.a")));
assert(os.exists(bin)); assert(os.exists(bin));
let ctrace: str = readfile(compilertrace); let ctrace: str = readfile(compilertrace);
@@ -2351,11 +2420,12 @@ fn workescape(s: str) str = {
"ww_root_parity_right_7f3.wwi><-o><", work, "ww_root_parity_right_7f3.wwi><-o><", work,
"ww_root_parity_right_7f3.s><", work, "ww_root_parity_right_7f3.s><", work,
"ww_root_parity_right_7f3.unit.ww>"); "ww_root_parity_right_7f3.unit.ww>");
let rootline: str = strings.concat("BEGIN<-c><--import>", let rootline: str = strings.concat("BEGIN<--entry><-c><--import>",
"<ww_root_parity_left_7f3><", work, "<ww_root_parity_left_7f3><", work,
"ww_root_parity_left_7f3.wwi><--import>", "ww_root_parity_left_7f3.wwi><--import>",
"<ww_root_parity_right_7f3><", work, "<ww_root_parity_right_7f3><", work,
"ww_root_parity_right_7f3.wwi><-o><", work, "ww_root_parity_right_7f3.wwi><-I><", work,
"__root.wwi><-o><", work,
"__root.s><", work, "__root.unit.ww>"); "__root.s><", work, "__root.unit.ww>");
assert(same(linecontaining(ctrace, assert(same(linecontaining(ctrace,
"ww_root_parity_base_7f3.unit.ww"), baseline)); "ww_root_parity_base_7f3.unit.ww"), baseline));
@@ -2371,7 +2441,7 @@ fn workescape(s: str) str = {
"ww_root_parity_left_7f3.s>"))); "ww_root_parity_left_7f3.s>")));
assert(occurrences(ltrace, "\n") == 1); assert(occurrences(ltrace, "\n") == 1);
assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work, assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work,
"__root.o>"))); "__root.a>")));
ai = 0; ai = 0;
for (ai < artifacts.len) { for (ai < artifacts.len) {
assert(occurrences(ltrace, strings.concat("<", work, artifacts[ai], assert(occurrences(ltrace, strings.concat("<", work, artifacts[ai],

View File

@@ -97,12 +97,14 @@ fn samefile(a: str, b: str, why: str) void = {
|| testenv.has(foounit, "//ww:module ")) { || testenv.has(foounit, "//ww:module ")) {
fail("foo unit is not exactly its sorted, owned source set"); fail("foo unit is not exactly its sorted, owned source set");
}; };
let wantroot: str = strings.concat("//ww:module-reset\n", appsrc, "\n"); let wantroot: str = strings.concat("//ww:module-reset main\n", appsrc,
"\n");
if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork, if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork,
"__root.unit.ww")))) { "__root.unit.ww")))) {
fail("root unit is not exactly its owned source"); fail("root unit is not exactly its owned source");
}; };
let keys: []str = ["example.base", "example.bar", "example.foo"]; let keys: []str = ["example.base", "example.bar", "example.foo",
"__root"];
let suffixes: []str = [".unit.ww", ".wwi", ".a"]; let suffixes: []str = [".unit.ww", ".wwi", ".a"];
i = 0; i = 0;
for (i < keys.len) { for (i < keys.len) {
@@ -117,10 +119,6 @@ fn samefile(a: str, b: str, why: str) void = {
}; };
i += 1; i += 1;
}; };
samefile(strings.concat(cwork, "__root.unit.ww"), strings.concat(cwork2,
"__root.unit.ww"), "root unit changed across clean C builds");
samefile(strings.concat(cwork, "__root.unit.ww"), strings.concat(wwork,
"__root.unit.ww"), "root unit differs between stages");
samefile(stems[0], stems[1], "C executables are not deterministic"); samefile(stems[0], stems[1], "C executables are not deterministic");
samefile(stems[0], stems[2], "C/WW executables differ"); samefile(stems[0], stems[2], "C/WW executables differ");

View File

@@ -286,7 +286,8 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
|| testenv.has(apiiface, "hidden")) { || testenv.has(apiiface, "hidden")) {
fail("self-contained", "api export leaked unrelated or private declarations"); fail("self-contained", "api export leaked unrelated or private declarations");
}; };
let expectedunit: str = strings.concat("//ww:module-reset\n", mainsrc, "\n"); let expectedunit: str = strings.concat("//ww:module-reset main\n",
mainsrc, "\n");
if (!testenv.same(expectedunit, testenv.readfile(strings.concat(cwork, if (!testenv.same(expectedunit, testenv.readfile(strings.concat(cwork,
"__root.unit.ww"))) || !testenv.same(expectedunit, "__root.unit.ww"))) || !testenv.same(expectedunit,
testenv.readfile(strings.concat(wwork, "__root.unit.ww")))) { testenv.readfile(strings.concat(wwork, "__root.unit.ww")))) {
@@ -405,7 +406,7 @@ fn writediamond(td: str, reverse: bool) str = {
let unit: str = testenv.readfile(strings.concat(scratch, let unit: str = testenv.readfile(strings.concat(scratch,
"__root.unit.ww")); "__root.unit.ww"));
let rootbody: str = testenv.readfile(strings.concat(main, "/main.ww")); let rootbody: str = testenv.readfile(strings.concat(main, "/main.ww"));
let wantroot: str = strings.concat("//ww:module-reset\n", rootbody, let wantroot: str = strings.concat("//ww:module-reset main\n", rootbody,
"\n"); "\n");
if (!testenv.same(unit, wantroot) || testenv.has(unit, if (!testenv.same(unit, wantroot) || testenv.has(unit,
"//ww:module ")) { "//ww:module ")) {
@@ -530,6 +531,8 @@ fn writediamond(td: str, reverse: bool) str = {
let referenceunit: str = ""; let referenceunit: str = "";
let referencewwi: str = ""; let referencewwi: str = "";
let referencearchive: str = ""; let referencearchive: str = "";
let referencerootwwi: str = "";
let referencerootarchive: str = "";
let referencebin: str = ""; let referencebin: str = "";
let referencecompiler: str = ""; let referencecompiler: str = "";
let referenceassembler: str = ""; let referenceassembler: str = "";
@@ -567,7 +570,7 @@ fn writediamond(td: str, reverse: bool) str = {
"__root.unit.ww")); "__root.unit.ww"));
let depunit: str = testenv.readfile(strings.concat(work, let depunit: str = testenv.readfile(strings.concat(work,
"dep.unit.ww")); "dep.unit.ww"));
if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;")
|| testenv.has(rootunit, "//ww:module ") || testenv.has(rootunit, "//ww:module ")
|| !testenv.has(depunit, "//ww:module-reset dep\npackage dep;") || !testenv.has(depunit, "//ww:module-reset dep\npackage dep;")
|| testenv.has(depunit, "//ww:module ")) { || testenv.has(depunit, "//ww:module ")) {
@@ -578,8 +581,8 @@ fn writediamond(td: str, reverse: bool) str = {
|| !testenv.exists(strings.concat(work, "dep.wwi")) || !testenv.exists(strings.concat(work, "dep.wwi"))
|| !testenv.exists(strings.concat(work, "dep.a")) || !testenv.exists(strings.concat(work, "dep.a"))
|| !testenv.exists(strings.concat(work, "__root.o")) || !testenv.exists(strings.concat(work, "__root.o"))
|| testenv.exists(strings.concat(work, "__root.wwi")) || !testenv.exists(strings.concat(work, "__root.wwi"))
|| testenv.exists(strings.concat(work, "__root.a"))) { || !testenv.exists(strings.concat(work, "__root.a"))) {
fail("library-roots", "package artifacts do not match root ownership"); fail("library-roots", "package artifacts do not match root ownership");
}; };
let ctrace: str = testenv.readfile(compilertrace); let ctrace: str = testenv.readfile(compilertrace);
@@ -603,6 +606,10 @@ fn writediamond(td: str, reverse: bool) str = {
"BEGIN<-c><--import><leaf><", work, "leaf.wwi><-I><", work, "BEGIN<-c><--import><leaf><", work, "leaf.wwi><-I><", work,
"dep.wwi><-o><", work, "dep.wwi><-o><", work,
"dep.s><", work, "dep.unit.ww>")) "dep.s><", work, "dep.unit.ww>"))
|| !testenv.has(ctrace, strings.concat(
"BEGIN<--entry><-c><--import><dep><", work,
"dep.wwi><-I><", work, "__root.wwi><-o><", work,
"__root.s><", work, "__root.unit.ww>"))
|| testenv.occurrences(atrace, "\n") != 3 || testenv.occurrences(atrace, "\n") != 3
|| !testenv.has(atrace, strings.concat("BEGIN<-o><", work, || !testenv.has(atrace, strings.concat("BEGIN<-o><", work,
"dep.o><", work, "dep.s>"))) { "dep.o><", work, "dep.s>"))) {
@@ -610,7 +617,7 @@ fn writediamond(td: str, reverse: bool) str = {
}; };
if (testenv.occurrences(ltrace, "\n") != 1 if (testenv.occurrences(ltrace, "\n") != 1
|| !testenv.has(ltrace, strings.concat("BEGIN<-o><", bin, "><", || !testenv.has(ltrace, strings.concat("BEGIN<-o><", bin, "><",
work, "__root.o>")) work, "__root.a>"))
|| testenv.pos(ltrace, strings.concat("<", work, "dep.a>")) < 0 || testenv.pos(ltrace, strings.concat("<", work, "dep.a>")) < 0
|| testenv.pos(ltrace, strings.concat("<", work, "leaf.a>")) || testenv.pos(ltrace, strings.concat("<", work, "leaf.a>"))
< testenv.pos(ltrace, strings.concat("<", work, "dep.a>")) < testenv.pos(ltrace, strings.concat("<", work, "dep.a>"))
@@ -643,6 +650,10 @@ fn writediamond(td: str, reverse: bool) str = {
"dep.wwi"))); "dep.wwi")));
referencearchive = strings.dup(testenv.readfile(strings.concat(work, referencearchive = strings.dup(testenv.readfile(strings.concat(work,
"dep.a"))); "dep.a")));
referencerootwwi = strings.dup(testenv.readfile(strings.concat(work,
"__root.wwi")));
referencerootarchive = strings.dup(testenv.readfile(strings.concat(work,
"__root.a")));
referencebin = strings.dup(testenv.readfile(bin)); referencebin = strings.dup(testenv.readfile(bin));
referencecompiler = strings.dup(ctrace); referencecompiler = strings.dup(ctrace);
referenceassembler = strings.dup(atrace); referenceassembler = strings.dup(atrace);
@@ -653,6 +664,10 @@ fn writediamond(td: str, reverse: bool) str = {
work, "dep.wwi"))) work, "dep.wwi")))
|| !testenv.same(referencearchive, testenv.readfile(strings.concat( || !testenv.same(referencearchive, testenv.readfile(strings.concat(
work, "dep.a"))) work, "dep.a")))
|| !testenv.same(referencerootwwi,
testenv.readfile(strings.concat(work, "__root.wwi")))
|| !testenv.same(referencerootarchive,
testenv.readfile(strings.concat(work, "__root.a")))
|| !testenv.same(referencebin, testenv.readfile(bin)) || !testenv.same(referencebin, testenv.readfile(bin))
|| !testenv.same(referencecompiler, ctrace) || !testenv.same(referencecompiler, ctrace)
|| !testenv.same(referenceassembler, atrace) || !testenv.same(referenceassembler, atrace)
@@ -723,7 +738,7 @@ fn writediamond(td: str, reverse: bool) str = {
runav) != 42) { runav) != 42) {
fail("library-roots", "empty override did not use default roots"); fail("library-roots", "empty override did not use default roots");
}; };
if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;")
|| testenv.has(rootunit, "//ww:module ") || testenv.has(rootunit, "//ww:module ")
|| !testenv.exists(strings.concat(emptywork, "types.wwi")) || !testenv.exists(strings.concat(emptywork, "types.wwi"))
|| !testenv.exists(strings.concat(emptywork, "types.a")) || !testenv.exists(strings.concat(emptywork, "types.a"))
@@ -870,7 +885,7 @@ fn writediamond(td: str, reverse: bool) str = {
let depunitpath: str = strings.concat(work, "/dep.unit.ww"); let depunitpath: str = strings.concat(work, "/dep.unit.ww");
let rootunit: str = testenv.readfile(rootunitpath); let rootunit: str = testenv.readfile(rootunitpath);
let depunit: str = testenv.readfile(depunitpath); let depunit: str = testenv.readfile(depunitpath);
if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;")
|| testenv.has(rootunit, "//ww:module ") || testenv.has(rootunit, "//ww:module ")
|| !testenv.has(depunit, "//ww:module-reset dep\npackage dep;") || !testenv.has(depunit, "//ww:module-reset dep\npackage dep;")
|| testenv.has(depunit, "//ww:module ")) { || testenv.has(depunit, "//ww:module ")) {
@@ -880,8 +895,8 @@ fn writediamond(td: str, reverse: bool) str = {
|| !testenv.exists(strings.concat(work, "/leaf.a")) || !testenv.exists(strings.concat(work, "/leaf.a"))
|| !testenv.exists(strings.concat(work, "/dep.wwi")) || !testenv.exists(strings.concat(work, "/dep.wwi"))
|| !testenv.exists(strings.concat(work, "/dep.a")) || !testenv.exists(strings.concat(work, "/dep.a"))
|| testenv.exists(strings.concat(work, "/__root.wwi")) || !testenv.exists(strings.concat(work, "/__root.wwi"))
|| testenv.exists(strings.concat(work, "/__root.a")) || !testenv.exists(strings.concat(work, "/__root.a"))
|| !testenv.same(testenv.readfile(strings.concat(work, || !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.ww")), testenv.readfile(copied[si])) "/.wwtool.ww")), testenv.readfile(copied[si]))
|| !testenv.same(testenv.readfile(strings.concat(work, || !testenv.same(testenv.readfile(strings.concat(work,
@@ -890,7 +905,7 @@ fn writediamond(td: str, reverse: bool) str = {
"/.wwtool.w6a")), testenv.readfile(assembler)) "/.wwtool.w6a")), testenv.readfile(assembler))
|| !testenv.same(testenv.readfile(strings.concat(work, || !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.stamp")), "/.wwtool.stamp")),
"ww workdir fmt 6 mode build asm 0\n")) { "ww workdir fmt 7 mode build asm 0\n")) {
fail("driver-identity", "persistent artifacts or identities are incomplete"); fail("driver-identity", "persistent artifacts or identities are incomplete");
}; };
let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi")); let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi"));
@@ -905,7 +920,8 @@ fn writediamond(td: str, reverse: bool) str = {
"/dep.wwi.new><-o><", work, "/dep.wwi.new><-o><", work,
"/dep.s.new><", work, "/dep.unit.new>")) "/dep.s.new><", work, "/dep.unit.new>"))
|| !testenv.has(coldcompiler, strings.concat( || !testenv.has(coldcompiler, strings.concat(
"BEGIN<-c><--import><dep><", work, "/dep.wwi><-o><", work, "BEGIN<--entry><-c><--import><dep><", work,
"/dep.wwi><-I><", work, "/__root.wwi.new><-o><", work,
"/__root.s.new><", work, "/__root.s.new><", work,
"/__root.unit.new>")) "/__root.unit.new>"))
|| testenv.occurrences(coldassembler, "\n") != 3 || testenv.occurrences(coldassembler, "\n") != 3
@@ -916,7 +932,7 @@ fn writediamond(td: str, reverse: bool) str = {
}; };
if (testenv.occurrences(coldlinker, "\n") != 1 if (testenv.occurrences(coldlinker, "\n") != 1
|| !testenv.has(coldlinker, strings.concat("BEGIN<-o><", bin, || !testenv.has(coldlinker, strings.concat("BEGIN<-o><", bin,
"><", work, "/__root.o>")) "><", work, "/__root.a>"))
|| testenv.pos(coldlinker, strings.concat("<", work, || testenv.pos(coldlinker, strings.concat("<", work,
"/dep.a>")) < 0 "/dep.a>")) < 0
|| testenv.pos(coldlinker, strings.concat("<", work, || testenv.pos(coldlinker, strings.concat("<", work,

View File

@@ -8,19 +8,16 @@ package sepbuild_test;
// //
// sepbuild (#46 commit-3) — build_one_sep END-TO-END on the real lib // sepbuild (#46 commit-3) — build_one_sep END-TO-END on the real lib
// chain root -> os -> {rt,time} (transitive-closure discovery + topo): // chain root -> os -> {rt,time} (transitive-closure discovery + topo):
// build+run exit 7 both stages; {time,rt,os}.wwi + __root.s // build+run exit 7 both stages; {time,rt,os,__root}.wwi/.a
// materialize (#69: the root is compiled without -I, so no // materialize; per-package .s/.wwi/.a/.unit.ww and the final binary
// __root.wwi); per-package .s/.wwi/.unit.ww and the final binary
// byte-id cs vs ww; every .unit.ww is the package's own sorted source // byte-id cs vs ww; every .unit.ww is the package's own sorted source
// set; `ww run` routes through the same sole sep path (exit 7 both // set; `ww run` routes through the same sole sep path (exit 7 both
// stages). // stages).
// //
// seproot (#69 BUG-1) — a ROOT whose `export fn use(a: *t)` names an // seproot — a ROOT whose `export fn use(a: *t)` reaches an unexported
// unexported local `type t` builds (exit 37 both stages) because the // local `type t` builds and emits a deterministic self-contained .wwi/.a.
// root is compiled WITHOUT the -I .wwi-producer flag: __root.wwi must // The private type is carried as compiler export data without `export`, and
// NOT exist; replaying the driver's own __root.unit.ww plus c.wwi // an importing source still cannot qualify `main.t`.
// through w6c/w6c_ww with -I must REJECT (the isolated bug) while -c
// -o alone must accept; __root.s carries the exported fn.
// //
// sepstructdef (#70 BUG-2) — a dep exporting aggregate-init defs // sepstructdef (#70 BUG-2) — a dep exporting aggregate-init defs
// (struct-lit + array-lit) sep-builds and links (exit 20 both // (struct-lit + array-lit) sep-builds and links (exit 20 both
@@ -91,29 +88,25 @@ fn samefile(label: str, what: str, a: str, b: str) void = {
s += 1; s += 1;
}; };
// discovery/topo: the transitive package set materialized. #69: the // discovery/topo: every reachable package action materialized.
// root emits no .wwi (compiled without -I); its .s stands in.
let pkgs: []str = ["time", "rt", "os", "__root"]; let pkgs: []str = ["time", "rt", "os", "__root"];
let i: i32 = 0; let i: i32 = 0;
for (i < pkgs.len) { for (i < pkgs.len) {
let suffix: str = ".wwi";
if (testenv.same(pkgs[i], "__root")) { suffix = ".s"; };
if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/", if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/",
pkgs[i], suffix))) { pkgs[i], ".wwi")) || !testenv.exists(strings.concat(td,
fail("sepbuild", strings.concat(pkgs[i], suffix, "/prog.cs.sepwork/", pkgs[i], ".a"))) {
fail("sepbuild", strings.concat(pkgs[i], ".wwi/.a",
" missing (discovery/topo)")); " missing (discovery/topo)"));
}; };
i += 1; i += 1;
}; };
// cs==ww (rule 10): per-package artifacts + the final binary // cs==ww (rule 10): per-package artifacts + the final binary
let sufs: []str = [".s", ".wwi", ".unit.ww"]; let sufs: []str = [".s", ".wwi", ".a", ".unit.ww"];
let p: i32 = 0; let p: i32 = 0;
for (p < pkgs.len) { for (p < pkgs.len) {
let k: i32 = 0; let k: i32 = 0;
for (k < sufs.len) { for (k < sufs.len) {
if (testenv.same(pkgs[p], "__root")
&& testenv.same(sufs[k], ".wwi")) { k += 1; continue; };
samefile("sepbuild", strings.concat(pkgs[p], sufs[k]), samefile("sepbuild", strings.concat(pkgs[p], sufs[k]),
strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]), strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]),
strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k])); strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k]));
@@ -171,15 +164,13 @@ fn samefile(label: str, what: str, a: str, b: str) void = {
s += 1; s += 1;
}; };
// cs==ww; the root's .wwi is intentionally absent (asserted below) // cs==ww for the complete package artifacts, including the root archive.
let pkgs: []str = ["c", "__root"]; let pkgs: []str = ["c", "__root"];
let sufs: []str = [".s", ".wwi", ".unit.ww"]; let sufs: []str = [".s", ".wwi", ".a", ".unit.ww"];
let p: i32 = 0; let p: i32 = 0;
for (p < pkgs.len) { for (p < pkgs.len) {
let k: i32 = 0; let k: i32 = 0;
for (k < sufs.len) { for (k < sufs.len) {
if (testenv.same(pkgs[p], "__root")
&& testenv.same(sufs[k], ".wwi")) { k += 1; continue; };
samefile("seproot", strings.concat(pkgs[p], sufs[k]), samefile("seproot", strings.concat(pkgs[p], sufs[k]),
strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]), strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]),
strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k])); strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k]));
@@ -190,38 +181,51 @@ fn samefile(label: str, what: str, a: str, b: str) void = {
samefile("seproot", "the final binary", samefile("seproot", "the final binary",
strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww")); strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww"));
// the fix: the root is compiled WITHOUT -I, so no __root.wwi // The reachable private nominal is compiler data, not a public source name.
let s2: i32 = 0; let s2: i32 = 0;
for (s2 < 2) { for (s2 < 2) {
if (testenv.exists(strings.concat(td, "/prog.", tags[s2], let rootiface: str = strings.concat(td, "/prog.", tags[s2],
".sepwork/__root.wwi"))) { ".sepwork/__root.wwi");
let ifacebytes: str = testenv.readfile(rootiface);
if (!testenv.has(ifacebytes, "type t = struct { v: i32 }")
|| testenv.has(ifacebytes, "export type t")
|| !testenv.has(ifacebytes, "export fn use(a: *t) i32;")) {
fail("seproot", strings.concat(drvs[s2], fail("seproot", strings.concat(drvs[s2],
" produced __root.wwi (-I still passed for the root)")); " did not carry a private reachable type as compiler data"));
}; };
s2 += 1; s2 += 1;
}; };
// replay the pre-fix invocation on the driver's own __root.unit.ww: // Replay the package compile with -I: both compilers accept and emit the
// with -I the export-check FIRES (the isolated bug); without it the // same self-contained export. A source importer cannot name its private t.
// post-fix invocation accepts. Both compilers.
let unit: str = strings.concat(td, "/prog.cs.sepwork/__root.unit.ww"); let unit: str = strings.concat(td, "/prog.cs.sepwork/__root.unit.ww");
let ciface: str = strings.concat(td, "/prog.cs.sepwork/c.wwi"); let ciface: str = strings.concat(td, "/prog.cs.sepwork/c.wwi");
let consumer: str = strings.concat(td, "/consumer.ww");
testenv.writefile(consumer, strings.concat(
"package consumer;\n",
"import main;\n",
"fn forbidden(v: *main.t) void = {};\n"));
let comps: []str = ["w6c", "w6c_ww"]; let comps: []str = ["w6c", "w6c_ww"];
let c: i32 = 0; let c: i32 = 0;
for (c < 2) { for (c < 2) {
let wwi: str = strings.concat(td, "/nv.", comps[c], ".wwi"); let wwi: str = strings.concat(td, "/nv.", comps[c], ".wwi");
let asmf: str = strings.concat(td, "/nv.", comps[c], ".s"); let asmf: str = strings.concat(td, "/nv.", comps[c], ".s");
let rav: []str = [testenv.driver(comps[c]), "-c", "--import", "c", let rav: []str = [testenv.driver(comps[c]), "--entry", "-c",
ciface, "-I", wwi, "-o", asmf, unit]; "--import", "c", ciface, "-I", wwi, "-o", asmf, unit];
if (runcode(td, strings.concat("nvI_", comps[c]), rav) == 0) { if (runcode(td, strings.concat("reexport_", comps[c]), rav) != 0) {
fail("seproot", strings.concat(comps[c], " -c -I accepted the ", fail("seproot", strings.concat(comps[c],
"root export-over-unexported-type (vacuous gate)")); " rejected the self-contained root export"));
}; };
let aav: []str = [testenv.driver(comps[c]), "-c", "--import", "c", let rejectav: []str = [testenv.driver(comps[c]), "-c", "--import",
ciface, "-o", asmf, unit]; "main", wwi, "-o", asmf, consumer];
if (runcode(td, strings.concat("nvO_", comps[c]), aav) != 0) { let reject: testenv.commandout;
fail("seproot", strings.concat(comps[c], " -c -o (no -I) ", testenv.runcommand(td, td, strings.concat("private_", comps[c]),
"rejected the root unit (post-fix invocation)")); rejectav, tmo(), &reject);
if (reject.termination != exec.termination.EXIT || reject.code == 0
|| !testenv.has(reject.stderr,
"package 'main' has no exported declaration 't'")) {
fail("seproot", strings.concat(comps[c],
" exposed the compiler-private type to source importers"));
}; };
c += 1; c += 1;
}; };

View File

@@ -69,7 +69,8 @@ fn cmpsepwork(label: str, csdir: str, wwdir: str) void = {
let i: i32 = 0; let i: i32 = 0;
for (i < names.len) { for (i < names.len) {
if (strings.hassuffix(names[i], ".s") if (strings.hassuffix(names[i], ".s")
|| strings.hassuffix(names[i], ".wwi")) { || strings.hassuffix(names[i], ".wwi")
|| strings.hassuffix(names[i], ".a")) {
seen += 1; seen += 1;
if (!testenv.same( if (!testenv.same(
testenv.readfile(strings.concat(csdir, "/", names[i])), testenv.readfile(strings.concat(csdir, "/", names[i])),
@@ -81,7 +82,7 @@ fn cmpsepwork(label: str, csdir: str, wwdir: str) void = {
i += 1; i += 1;
}; };
// an existing-but-empty sepwork would pass the loop vacuously // an existing-but-empty sepwork would pass the loop vacuously
if (seen == 0) { fail(label, "no .s/.wwi in cs sepwork"); }; if (seen == 0) { fail(label, "no package artifacts in cs sepwork"); };
}; };
@test fn coloimport() void = { @test fn coloimport() void = {
@@ -210,11 +211,11 @@ fn depmainrow(label: str, entry: str, want: i32, deps: str,
l += 1; l += 1;
}; };
// the root emits no .wwi post-#69, so its suffix set omits .wwi // Both the dependency and raw explicit root emit complete package artifacts.
let parts: []str = ["aa.s", "aa.wwi", "aa.unit.ww", "__root.s", let parts: []str = ["aa.s", "aa.wwi", "aa.a", "aa.unit.ww",
"__root.unit.ww"]; "__root.s", "__root.wwi", "__root.a", "__root.unit.ww"];
// cs==ww (rule 10) per layout over the fixed 5-part table // cs==ww (rule 10) per layout over the complete package-artifact table
let l2: i32 = 0; let l2: i32 = 0;
for (l2 < 2) { for (l2 < 2) {
let p: i32 = 0; let p: i32 = 0;

View File

@@ -4,12 +4,11 @@ package seplink_test;
// retired native carriers test/wcc/989_separchive_run.c and // retired native carriers test/wcc/989_separchive_run.c and
// 989_sepcycle_dup.c; every assertion preserved. // 989_sepcycle_dup.c; every assertion preserved.
// //
// archive (#46 commit-5a) — `ww build` wraps each DEP package's .o in // archive — `ww build` wraps every package action's .o in the existing
// a deterministic single-member .a and links the ROOT as a positional // deterministic single-member .a and links the root archive first:
// .o (force-loaded): build+run exit 7 both stages; __root.a absent, // build+run exit 7 both stages; __root.a + helper.a present; both archives
// __root.o + helper.a present; cs helper.a == ww helper.a (rule 10, // are byte-identical across stages; 3 cold cstage rebuilds emit
// the .a byte-id substrate); 3 cold cstage rebuilds emit // byte-identical archives (zeroed mtime/uid/gid, fixed mode/member —
// byte-identical helper.a (zeroed mtime/uid/gid, fixed mode/member —
// a floating byte would poison the content cache key). // a floating byte would poison the content cache key).
// //
// archivedup (#31 PASS 3) — two dep packages force the same link // archivedup (#31 PASS 3) — two dep packages force the same link
@@ -94,22 +93,20 @@ fn runcode(dir: str, name: str, argv: []str) i32 = {
s += 1; s += 1;
}; };
// layout: root stays a positional force-loaded .o, deps become .a // layout: root and dependency are both package archives.
if (testenv.exists(strings.concat(td, "/prog.cs.sepwork/__root.a"))) { if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/__root.a"))
fail("archive", "root wrapped in .a (should stay positional .o)"); || !testenv.exists(strings.concat(td, "/prog.cs.sepwork/helper.a"))) {
}; fail("archive", "missing root or dependency archive");
if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/__root.o"))) {
fail("archive", "missing root .o");
};
if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/helper.a"))) {
fail("archive", "missing dep helper.a");
}; };
// rule 10: the .a byte-id substrate // rule 10: the complete .a byte-id substrate
if (!testenv.same( if (!testenv.same(
testenv.readfile(strings.concat(td, "/prog.cs.sepwork/helper.a")), testenv.readfile(strings.concat(td, "/prog.cs.sepwork/helper.a")),
testenv.readfile(strings.concat(td, "/prog.ww.sepwork/helper.a")))) { testenv.readfile(strings.concat(td, "/prog.ww.sepwork/helper.a")))
fail("archive", "cs helper.a != ww helper.a (rule 10 .a byte-id)"); || !testenv.same(
testenv.readfile(strings.concat(td, "/prog.cs.sepwork/__root.a")),
testenv.readfile(strings.concat(td, "/prog.ww.sepwork/__root.a")))) {
fail("archive", "cs package archives != ww package archives");
}; };
// determinism: 3 cold cstage rebuilds -> byte-identical .a // determinism: 3 cold cstage rebuilds -> byte-identical .a
@@ -132,9 +129,18 @@ fn runcode(dir: str, name: str, argv: []str) i32 = {
let a2: str = testenv.readfile(strings.concat(det2, let a2: str = testenv.readfile(strings.concat(det2,
".sepwork/helper.a")); ".sepwork/helper.a"));
if (!testenv.same(a0, a1) || !testenv.same(a1, a2)) { if (!testenv.same(a0, a1) || !testenv.same(a1, a2)) {
fail("archive", strings.concat(".a not deterministic across 3 ", fail("archive", strings.concat("dependency .a not deterministic across 3 ",
"builds (floating bytes poison the cache key)")); "builds (floating bytes poison the cache key)"));
}; };
let r0: str = testenv.readfile(strings.concat(det0,
".sepwork/__root.a"));
let r1: str = testenv.readfile(strings.concat(det1,
".sepwork/__root.a"));
let r2: str = testenv.readfile(strings.concat(det2,
".sepwork/__root.a"));
if (!testenv.same(r0, r1) || !testenv.same(r1, r2)) {
fail("archive", "root .a not deterministic across 3 builds");
};
testenv.clean(td); testenv.clean(td);
}; };

View File

@@ -54,8 +54,8 @@ slurp(const char *path, char **outbuf, size_t *outlen)
} }
/* #93 sep layout: the flip stops emitting a monolithic <tool>/main.o; /* #93 sep layout: the flip stops emitting a monolithic <tool>/main.o;
* the linkable unit is now the per-package .o + reverse-topo .a set the * the linkable unit is now the root .a + reverse-topo .a set the
* sep driver assembles (a raw `w6l <root>.o *.a libwwrt.a` from the test * sep driver assembles (a raw `w6l <root>.a *.a libwwrt.a` from the test
* side fails — `undefined reference` — because it can't reproduce the * side fails — `undefined reference` — because it can't reproduce the
* driver's topo order). Drive each real directory package through the * driver's topo order). Drive each real directory package through the
* full sep build twice; both compile directly and differ only in the * full sep build twice; both compile directly and differ only in the
@@ -139,7 +139,7 @@ main(void)
if (getcwd(cwd, sizeof cwd) == NULL) return 1; if (getcwd(cwd, sizeof cwd) == NULL) return 1;
/* Real bootstrap-style links: each selfhost tool's full sep build /* Real bootstrap-style links: each selfhost tool's full sep build
* (root .o + reverse-topo dep .a set + libwwrt.a). Both builds compile * (root .a + reverse-topo dep .a set + libwwrt.a). Both builds compile
* directly and differ only in the selected linker. (wwdump is excluded — * directly and differ only in the selected linker. (wwdump is excluded —
* it imports the compiler-internal cmd packages syntax/check/cgen, which don't resolve * it imports the compiler-internal cmd packages syntax/check/cgen, which don't resolve
* under the lib-path sep dep scan; w6a/w6l are self-contained * under the lib-path sep dep scan; w6a/w6l are self-contained
@@ -157,6 +157,6 @@ main(void)
return 1; return 1;
} }
printf("w6l_ww: byte-identical to C w6l on %d selfhost tool links " printf("w6l_ww: byte-identical to C w6l on %d selfhost tool links "
"(sep root .o + reverse-topo .a set + libwwrt.a)\n", n); "(sep root .a + reverse-topo .a set + libwwrt.a)\n", n);
return 0; return 0;
} }