ww: preserve command package identities

This commit is contained in:
2026-08-13 03:37:08 +09:00
parent 10d24a3237
commit 4377634bb9
10 changed files with 348 additions and 43 deletions

View File

@@ -226,6 +226,7 @@ $(BIN)/w6a_ww: selfhost/cmd/w6a/main.ww selfhost/cmd/w6a/opcodes.ww \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(WWBUILD)/w6a_ww
@$(CURDIR)/$(BIN)/ww build -w $(WWBUILD)/w6a_ww \
-I $(CURDIR)/selfhost/cmd \
-o $(WWBUILD)/w6a_ww/main \
$(CURDIR)/selfhost/cmd/w6a
@mv $(WWBUILD)/w6a_ww/main $@
@@ -244,6 +245,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(WWBUILD)/w6l_ww
@$(CURDIR)/$(BIN)/ww build -w $(WWBUILD)/w6l_ww \
-I $(CURDIR)/selfhost/cmd \
-o $(WWBUILD)/w6l_ww/main \
$(CURDIR)/selfhost/cmd/w6l
@mv $(WWBUILD)/w6l_ww/main $@
@@ -961,9 +963,11 @@ nocc:
-I $(CURDIR)/lib/ww \
-I $(CURDIR)/selfhost/cmd \
-I $(CURDIR)/lib $(CURDIR)/selfhost/cmd/w6c/main.ww && mv main w6c_ww1
@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/lib \
@cd $(NOCC_BIN) && ./ww build -o main \
-I $(CURDIR)/selfhost/cmd -I $(CURDIR)/lib \
$(CURDIR)/selfhost/cmd/w6a && mv main w6a_ww1
@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/lib \
@cd $(NOCC_BIN) && ./ww build -o main \
-I $(CURDIR)/selfhost/cmd -I $(CURDIR)/lib \
$(CURDIR)/selfhost/cmd/w6l && mv main w6l_ww1
@cd $(NOCC_BIN) && ./ww build -o main -I $(CURDIR)/selfhost/cmd/wcc \
-I $(CURDIR)/lib $(CURDIR)/selfhost/cmd/ww/main.ww && mv main ww_ww1

View File

@@ -31,13 +31,14 @@ struct importin {
static Node *
parseinput(Arena *a, const char *file, char *buf, u64 len,
const char *mod, const char *testsupport, int *bad)
const char *mod, const char *testsupport, int commandpackage, int *bad)
{
Lex l;
Parser p;
lexinit(&l, a, file, buf, len);
parserinit(&p, a, &l);
p.testmodule = testsupport;
p.commandpackage = commandpackage;
if (mod != NULL) {
p.pathmod = mod;
p.curmod = mod;
@@ -66,6 +67,7 @@ main(int argc, char **argv)
const char *testsupport = NULL;
int testmode = 0;
int testpackage = 0;
int commandpackage = 0;
int entrymode = 0;
int sepmode = 0; /* -c: #22 M3 separate-compile / primary-
* only codegen (emit imported==0 decls
@@ -86,6 +88,8 @@ main(int argc, char **argv)
testmode = 1;
} else if (strcmp(a, "--test-package") == 0) {
testpackage = 1;
} else if (strcmp(a, "--command-package") == 0) {
commandpackage = 1;
} else if (strcmp(a, "--entry") == 0) {
entrymode = 1;
} else if (strcmp(a, "--test-support-module") == 0) {
@@ -115,7 +119,7 @@ main(int argc, char **argv)
}
}
if (src == NULL) {
fputs("usage: w6c [-T|--test-package] [--entry] [-c] [-I out.wwi] "
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] "
"[--import path dep.wwi]... [-o out.s] file.ww\n", stderr);
return 2;
}
@@ -123,8 +127,8 @@ main(int argc, char **argv)
fputs("w6c: --import requires -c\n", stderr);
return 2;
}
if ((entrymode || testpackage) && !sepmode) {
fputs("w6c: --entry and --test-package require -c\n", stderr);
if ((entrymode || testpackage || commandpackage) && !sepmode) {
fputs("w6c: --entry, --test-package, and --command-package require -c\n", stderr);
return 2;
}
if (testmode && testpackage) {
@@ -162,7 +166,7 @@ main(int argc, char **argv)
}
int bad = 0;
Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, &bad);
imports[i].len, imports[i].path, testsupport, 0, &bad);
if (bad) return 1;
appendnodes(&head, &tail, f->list);
}
@@ -174,7 +178,11 @@ main(int argc, char **argv)
return 1;
}
int bad = 0;
Node *file = parseinput(a, src, buf, len, NULL, testsupport, &bad);
/* --entry classifies an ordinary selected command. The separate
* --command-package marker carries the same parser-only fact for command
* test variants whose code generation must not expose bare main. */
Node *file = parseinput(a, src, buf, len, NULL, testsupport,
commandpackage || entrymode, &bad);
if (bad) return 1;
if (head != NULL) {
tail->next = file->list;

View File

@@ -1575,7 +1575,12 @@ parsefile(Parser *p)
&& strcmp(p->testmodule, "__wwtest") == 0
&& strcmp(active, "__wwtest") == 0
&& strcmp(name, "test") == 0;
if (strcmp(name, last) != 0 && !testsupport) {
int commandpackage = p->commandpackage
&& p->pathmod == NULL && p->resetmod != NULL
&& (strcmp(name, "main") == 0
|| strcmp(name, "main_test") == 0);
if (strcmp(name, last) != 0 && !testsupport
&& !commandpackage) {
errorf(p->cur.pos,
"package %s does not match import path %s",
name, active);

View File

@@ -381,6 +381,8 @@ struct Parser {
* component) instead of overwriting curmod. */
const char *testmodule; /* hidden package-driver alias for toolchain
* `package test`; NULL outside that compile */
int commandpackage; /* selected command family: package main/main_test
* validates kind without replacing resetmod identity */
};
void parserinit(Parser*, Arena*, Lex*);

View File

@@ -638,6 +638,37 @@ sep_import_base_valid(const char *path)
return 0;
}
/* Go command packages retain their canonical import identity while declaring
* package main. An external command-test variant declares main_test. These
* declarations classify package kind but never participate in interning. */
static int
sep_command_declared_name(const struct seppkg *p)
{
return p->variant == SEP_VARIANT_EXTERNAL
? strcmp(p->name, "main_test") == 0
: strcmp(p->name, "main") == 0;
}
static int
sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep)
{
const struct seppkg *from = &g->pkg[importer];
const struct seppkg *to = &g->pkg[dep];
if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL)
return 0;
/* An external test's exact import of its colocated command production is
* test-variant wiring, not a general source-importable command edge. */
return !(from->variant == SEP_VARIANT_EXTERNAL
&& sep_command_declared_name(from)
&& strcmp(from->canon, to->canon) == 0);
}
static int
sep_command_compiler_marker(const struct sepgraph *g, int pi)
{
return sep_command_declared_name(&g->pkg[pi])
&& !g->pkg[pi].link_entry;
}
/* Bind the canonical ordinary import identity of one provisional directory
* action. The action's compiler path is derived from that base and its semantic
* variant; neither requested-root state nor artifact naming participates. */
@@ -1223,7 +1254,9 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
}
int self = strcmp(canon, g->pkg[pi].canon) == 0;
free(canon);
if (self && sep_external_production_name(&g->pkg[pi], name, 1))
if (self
&& (sep_external_production_name(&g->pkg[pi], name, 1)
|| sep_command_declared_name(&g->pkg[pi])))
external_production = 1;
if (self && !external_production) {
const char *owner = g->pkg[pi].path[0]
@@ -1409,7 +1442,8 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
&& !g->pkg[pi].test_support) {
const char *dot = strrchr(g->pkg[pi].path, '.');
const char *leaf = dot ? dot + 1 : g->pkg[pi].path;
if (strcmp(g->pkg[pi].name, leaf) != 0) {
if (strcmp(g->pkg[pi].name, leaf) != 0
&& !sep_command_declared_name(&g->pkg[pi])) {
fprintf(stderr,
"ww: package %s does not match import path %s\n",
g->pkg[pi].name, g->pkg[pi].path);
@@ -1461,11 +1495,20 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
/* Mark before recursion so a source cycle terminates here; topo emits the
* stable cycle diagnostic after all direct bindings are known. */
g->pkg[pi].context_state[context] = 2;
for (int k = 0; k < g->pkg[pi].ndeps; k++)
if (sep_load_pkg(g, g->pkg[pi].deps[k], context) < 0) {
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
int dep = g->pkg[pi].deps[k];
if (sep_load_pkg(g, dep, context) < 0) {
g->pkg[pi].failed = 1;
return -1;
}
if (dep != pi && sep_forbidden_command_import(g, pi, dep)) {
fprintf(stderr,
"ww: package %s is a program, not an importable package\n",
g->pkg[dep].path[0] ? g->pkg[dep].path : g->pkg[dep].canon);
g->pkg[pi].failed = 1;
return -1;
}
}
return 0;
}
@@ -1677,7 +1720,8 @@ sep_finalize_directory_identities(struct sepgraph *g)
int support_alias = p->role == SEP_ROLE_TEST_SUPPORT
&& strcmp(p->path, SEP_TEST_SUPPORT_MODULE) == 0
&& strcmp(p->name, "test") == 0;
if (!support_alias && strcmp(p->name, leaf) != 0) {
if (!support_alias && strcmp(p->name, leaf) != 0
&& !sep_command_declared_name(p)) {
fprintf(stderr, "ww: package %s does not match import path %s\n",
p->name, p->path);
return -1;
@@ -2437,6 +2481,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
cargv[cpos++] = "--test-package";
if (sep_command_compiler_marker(g, pi))
cargv[cpos++] = "--command-package";
if (g->pkg[pi].link_entry)
cargv[cpos++] = "--entry";
if (g->pkg[pi].test_support) {

View File

@@ -2805,9 +2805,14 @@ compilation.
A directory package consists of its immediate regular non-symlink `.ww` files,
excluding `*_test.ww`, in byte-sorted filename order. Every selected file must
declare the same package. An imported directory's declared package must equal
the final component of its import path; two logical identities for one physical
directory are rejected rather than compiled twice.
declare the same package. An ordinary importable directory's declaration must
equal the final component of its import path. A selected command directory
instead declares `package main` to validate command kind while retaining its
complete canonical import identity. A source import of a command package from a
different directory is rejected; the one same-directory exception is an
external `main_test` variant's canonical import of its production action. Two
logical identities for one physical directory are rejected rather than compiled
twice.
Packages compile serially in dependency-first postorder. The compiler emits the
existing deterministic `.wwi` interface for every directory-package action,
@@ -2839,13 +2844,18 @@ boundary is live in production Cstage and WWstage compilers and drivers.
An ordinary executable directory root is one normal package action. Its
finalized canonical import identity tags its owner-only unit; it receives only
direct exports, emits `.wwi`, `.o`, and a deterministic `.a`, and is compiled
exactly once. The declared package name only validates the last component of
that identity. The narrow compiler `--entry` flag controls only bare `main`
codegen and is 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.
exactly once. The declared package name validates the last component of that
identity for ordinary importable packages. A selected command root may declare
`package main`; `main` validates its executable role and never replaces or
truncates an identity such as `cmd.tool`. The narrow compiler `--entry` flag
both classifies that primary unit as a command and controls bare `main` codegen;
the package driver uses the parser-only `--command-package` marker for command
test variants that must remain ordinary archive code. Neither flag changes
export identity, and imported interfaces remain under strict leaf validation.
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
@@ -2941,7 +2951,11 @@ invocation, each still-unbound directory is finalized by this exact algorithm:
The selected full identity's final component is then validated against the
ordinary declared package name (or against the ordinary leaf obtained by
removing `_test` for the external variant). There is no fallback from an empty
removing `_test` for the external variant). The one command-kind rule is that a
selected command family declares `main`/`main_test` while keeping the finalized
ordinary identity unchanged. A source import of that command from another
directory rejects as a program before tools; a colocated external command test
may reuse the canonical production action. There is no fallback from an empty
import path to a declaration name. Relative, absolute, and symlink spellings
converge through the canonical directory; two unrelated local directories with
the same declaration therefore remain distinct. One bound import path mapping
@@ -3292,7 +3306,14 @@ and `b/foo` directories declaring the same `package foo` publish distinct
coexist in one command under distinct reversible local identities. Equivalent
recursive spellings reuse the same persistent request directory without new
compilation, while source imports of the reserved local namespace reject before
tool invocation.
tool invocation. The exact-argv command root declares `package main` but keeps
its non-`main` canonical identity in the unit, export, archive, compiler argv,
and linker argv; the bootstrap self-rebuild independently proves the same rule
for the real `w6a` and `w6l` command directories. A focused command-test row
proves distinct production, internal, external, and generated-main actions,
colocated external-production reuse, parser-only command markers, stage-equal
unit/export/archive bytes, both test binaries' runtime behavior, and pre-tool
rejection when another directory tries to import the command.
The exact-argv regression uses the real diamond
`base -> {left,right} -> root`. It proves one compile per node; no input for
@@ -3350,6 +3371,10 @@ The pinned official Go 1.26.5 tag (commit
and the command-global package cache returns the existing package pointer for
a later root or import of the resolved identity
([lines 757775](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L757-L775)).
Go keeps a command-line `Name == "main"` package as the selected command and
rejects an import from a different directory, while permitting the
same-directory test-loader edge
([lines 799805](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L799-L805)).
The package's parsed import list becomes its direct package dependencies,
rather than a transitive flattening
([lines 433440](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L433-L440),
@@ -3358,6 +3383,9 @@ The pinned official Go 1.26.5 tag (commit
pointer ([`action.go`, lines 202206](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L202-L206))
and returns the already-interned action for that key
([lines 437447](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L437-L447)).
A selected package whose independent `Name` is `main` receives a link action
([lines 450455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L450-L455))
while retaining its ordinary compiled archive action.
A compile action depends on only `p.Internal.Imports`
([lines 628658](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L628-L658));
an executable link asks for that same cached root compile action

View File

@@ -49,6 +49,9 @@ export type parser = struct {
// Hidden package-driver alias for the toolchain `package test` source.
// Empty outside that one separate-compilation edge.
testmodule: str,
// Selected command-family validation: main/main_test classifies the
// package without replacing the resetmod canonical identity.
commandpackage: bool,
};
fn refill(p: *parser) void = {
@@ -71,6 +74,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
p.pathmod = "";
p.resetmod = "";
p.testmodule = "";
p.commandpackage = false;
refill(p);
};
@@ -619,7 +623,12 @@ export fn parsefile(p: *parser) *node = {
p.testmodule, "__wwtest") == 0
&& strings.compare(active, "__wwtest") == 0
&& strings.compare(name, "test") == 0;
if (strings.compare(name, last) != 0 && !testsupport) {
let commandpackage: bool = p.commandpackage
&& p.pathmod.len == 0 && p.resetmod.len != 0
&& (strings.compare(name, "main") == 0
|| strings.compare(name, "main_test") == 0);
if (strings.compare(name, last) != 0 && !testsupport
&& !commandpackage) {
errmsg(p, "package does not match import path");
};
} else {

View File

@@ -67,6 +67,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
let testsupport: *u8 = nil;
let testmode: i32 = 0i32; // #15: `-T` test-mode
let testpackage: i32 = 0i32;
let commandpackage: i32 = 0i32;
let entrymode: i32 = 0i32;
let sepmode: i32 = 0i32; // -c: #22 M3 separate-compile / primary-
// only codegen (emit imported==0 decls
@@ -100,6 +101,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
testmode = 1i32;
} else { if (cstreq(a, "--test-package")) {
testpackage = 1i32;
} else { if (cstreq(a, "--command-package")) {
commandpackage = 1i32;
} else { if (cstreq(a, "--entry")) {
entrymode = 1i32;
} else { if (cstreq(a, "--test-support-module")) {
@@ -133,12 +136,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
return 2;
};
src = a;
}; }; }; }; }; }; }; }; };
}; }; }; }; }; }; }; }; }; };
i += 1;
};
if (src == nil) {
let m: str = "usage: w6c_ww [-T|--test-package] [--entry] [-c] [-I out.wwi] [--import path dep.wwi]... [-o out.s] file.ww\n";
let m: str = "usage: w6c_ww [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] [--import path dep.wwi]... [-o out.s] file.ww\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
@@ -147,8 +150,9 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, m.ptr, m.len: u64);
return 2;
};
if ((entrymode != 0 || testpackage != 0) && sepmode == 0) {
let m: str = "w6c: --entry and --test-package require -c\n";
if ((entrymode != 0 || testpackage != 0 || commandpackage != 0)
&& sepmode == 0) {
let m: str = "w6c: --entry, --test-package, and --command-package require -c\n";
os.write(2, m.ptr, m.len: u64);
return 2;
};
@@ -233,6 +237,9 @@ export fn main(argc: i32, argv: **u8) i32 = {
let ps: parser;
parserinit(&ps, &l);
if (testsupport != nil) { ps.testmodule = pathstr(testsupport); };
// Entry compilation classifies an ordinary command package. The explicit
// marker carries only that parser fact for non-entry command test variants.
ps.commandpackage = commandpackage != 0 || entrymode != 0;
let f: *node = parsefile(&ps);
// Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's
// `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches

View File

@@ -899,6 +899,33 @@ fn sepimportbasevalid(base: *u8) bool = {
return false;
};
// Go-style command packages keep their canonical import identity while
// declaring main; an external command-test variant declares main_test. These
// declarations classify package kind but never enter action identity.
fn sepcommanddeclaredname(p: *seppkg) bool = {
if (p.name == nil) { return false; };
if (p.variant == SEP_VARIANT_EXTERNAL) {
return cstreqlit(p.name, "main_test");
};
return cstreqlit(p.name, "main");
};
fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
if (to.name == nil || !cstreqlit(to.name, "main")
|| to.role != SEP_ROLE_NORMAL) { return false; };
// An external test's exact colocated production edge is variant wiring,
// not a general source-importable command-package alias.
return !(from.variant == SEP_VARIANT_EXTERNAL
&& sepcommanddeclaredname(from)
&& cstreq(from.canon, to.canon));
};
fn sepcommandcompilermarker(g: *sepgraph, pi: i32) bool = {
return sepcommanddeclaredname(&g.pkg[pi]) && !g.pkg[pi].linkentry;
};
// Bind a provisional directory action to its canonical ordinary import
// identity. The compiler path is derived from that base and the semantic
// variant; root/product/artifact state never participates.
@@ -1458,7 +1485,8 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
sepbindadd(bindings, 'D': u8, u.usepath, ipath);
let self: bool = os.samefile(pathstr(ipath),
pathstr(g.pkg[pi].entry));
if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) {
if (self && (sepexternalname(&g.pkg[pi], idp, idn, true)
|| sepcommanddeclaredname(&g.pkg[pi]))) {
externalproduction = true;
};
if (self && !externalproduction) {
@@ -1719,7 +1747,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
if (g.pkg[pi].path[j] == '.') { leaf = g.pkg[pi].path + j + 1u64; };
j += 1u64;
};
if (!cstreq(g.pkg[pi].name, leaf)) {
if (!cstreq(g.pkg[pi].name, leaf)
&& !sepcommanddeclaredname(&g.pkg[pi])) {
cerr("ww: package ");
cerr(pathstr(g.pkg[pi].name));
cerr(" does not match import path ");
@@ -1765,7 +1794,17 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
g.pkg[pi].contextstate[context] = 2u8;
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
if (seploadpkg(g, g.pkg[pi].deps[k], context) < 0) {
let dep: i32 = g.pkg[pi].deps[k];
if (seploadpkg(g, dep, context) < 0) {
g.pkg[pi].failed = true;
return -1;
};
if (dep != pi && sepforbiddencommandimport(g, pi, dep)) {
cerr("ww: package ");
if (g.pkg[dep].path[0u64] != 0u8) {
cerr(pathstr(g.pkg[dep].path));
} else { cerr(pathstr(g.pkg[dep].canon)); };
cerr(" is a program, not an importable package\n");
g.pkg[pi].failed = true;
return -1;
};
@@ -1997,7 +2036,8 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = {
let supportalias: bool = p.role == SEP_ROLE_TEST_SUPPORT
&& cstreqlit(p.path, SEP_TEST_SUPPORT_MODULE)
&& cstreqlit(p.name, "test");
if (!supportalias && !cstreq(p.name, leaf)) {
if (!supportalias && !cstreq(p.name, leaf)
&& !sepcommanddeclaredname(p)) {
cerr("ww: package "); cerr(pathstr(p.name));
cerr(" does not match import path "); cerr(pathstr(p.path));
cerr("\n");
@@ -2969,12 +3009,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let gent: bool = g.pkg[pi].generatedmain || rawtest;
let testpkg: bool = g.pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g.pkg[pi].variant == SEP_VARIANT_EXTERNAL;
let commandpkg: bool = sepcommandcompilermarker(g, pi);
let entry: bool = g.pkg[pi].linkentry;
let supportpkg: bool = g.pkg[pi].testsupport;
let alen: u64 = 8u64;
if (gent) { alen += 4u64; }
else {
if (testpkg) { alen += 1u64; };
if (commandpkg) { alen += 1u64; };
if (entry) { alen += 1u64; };
if (supportpkg) { alen += 2u64; };
};
@@ -2988,6 +3030,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
append(argv, testsupportmodule);
} else {
if (testpkg) { append(argv, "--test-package"); };
if (commandpkg) { append(argv, "--command-package"); };
if (entry) { append(argv, "--entry"); };
if (supportpkg) {
append(argv, "--test-support-module");

View File

@@ -2609,7 +2609,8 @@ fn localidentity(dir: str, leaf: str) str = {
let base: str = strings.concat(source, "/ww_root_parity_base_7f3");
let left: str = strings.concat(source, "/ww_root_parity_left_7f3");
let right: str = strings.concat(source, "/ww_root_parity_right_7f3");
let target: str = strings.concat(source, "/main");
let rootidentity: str = "ww_root_parity_target_7f3";
let target: str = strings.concat(source, "/", rootidentity);
let tools: str = strings.concat(root, "/tool wrappers");
assert(os.mkdir(source, 448i32) == 0);
assert(os.mkdir(runtime, 448i32) == 0);
@@ -2703,9 +2704,11 @@ fn localidentity(dir: str, leaf: str) str = {
let workroot: str = strings.concat(bin, ".sepwork");
let work: str = strings.concat(workroot, "/");
let artifacts: []str = ["ww_root_parity_base_7f3",
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "main"];
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3",
rootidentity];
let unitartifacts: []str = ["ww_root_parity_base_7f3",
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "main"];
"ww_root_parity_left_7f3", "ww_root_parity_right_7f3",
rootidentity];
let wantunits: []str = [strings.concat(
"//ww:module-reset ww_root_parity_base_7f3\n", basea,
"\n//ww:module-reset ww_root_parity_base_7f3\n", basez, "\n"),
@@ -2713,7 +2716,8 @@ fn localidentity(dir: str, leaf: str) str = {
leftbody, "\n"),
strings.concat("//ww:module-reset ww_root_parity_right_7f3\n",
rightbody, "\n"),
strings.concat("//ww:module-reset main\n", rootbody, "\n")];
strings.concat("//ww:module-reset ", rootidentity, "\n", rootbody,
"\n")];
let referenceunits: []str = ["", "", "", ""];
let referenceexports: []str = ["", "", "", ""];
let referencearchives: []str = ["", "", "", ""];
@@ -2807,6 +2811,11 @@ fn localidentity(dir: str, leaf: str) str = {
".wwi"));
let archive: str = readfile(strings.concat(work, artifacts[ai],
".a"));
if (ai == 3) {
assert(strings.hasprefix(exportf, strings.concat("package ",
rootidentity, ";\n")));
assert(!strings.hasprefix(exportf, "package main;\n"));
};
if (si == 0) {
referenceexports[ai] = strings.dup(exportf);
referencearchives[ai] = strings.dup(archive);
@@ -2843,16 +2852,28 @@ fn localidentity(dir: str, leaf: str) str = {
"ww_root_parity_left_7f3.wwi><--import>",
"<ww_root_parity_right_7f3><", work,
"ww_root_parity_right_7f3.wwi><-I><", work,
"main.wwi><-o><", work,
"main.s><", work, "main.unit.ww>");
rootidentity, ".wwi><-o><", work,
rootidentity, ".s><", work, rootidentity, ".unit.ww>");
assert(same(linecontaining(ctrace,
"ww_root_parity_base_7f3.unit.ww"), baseline));
assert(same(linecontaining(ctrace,
"ww_root_parity_left_7f3.unit.ww"), leftline));
assert(same(linecontaining(ctrace,
"ww_root_parity_right_7f3.unit.ww"), rightline));
assert(same(linecontaining(ctrace, "main.unit.ww"), rootline));
assert(same(linecontaining(ctrace, strings.concat(rootidentity,
".unit.ww")), rootline));
assert(!has(rootline, "ww_root_parity_base_7f3.wwi"));
// The primary command declaration is accepted only when the compiler
// receives command classification from --entry. Imported interfaces and
// ordinary package compiles retain strict identity-leaf validation.
let rejectwwi: str = strings.concat(root, "/reject-", tags[si], ".wwi");
let rejectasm: str = strings.concat(root, "/reject-", tags[si], ".s");
let rejectav: []str = [driver(compilers[si]), "-c", "-I", rejectwwi,
"-o", rejectasm, strings.concat(work, rootidentity, ".unit.ww")];
runcommand(root, strings.concat("command-without-entry-", tags[si]),
rejectav, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "does not match import path"));
assert(occurrences(atrace, "\n") == 4);
ai = 0;
for (ai < artifacts.len) {
@@ -2864,7 +2885,7 @@ fn localidentity(dir: str, leaf: str) str = {
};
assert(occurrences(ltrace, "\n") == 1);
assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work,
"main.a>")));
rootidentity, ".a>")));
ai = 0;
for (ai < artifacts.len) {
assert(occurrences(ltrace, strings.concat("<", work, artifacts[ai],
@@ -2935,6 +2956,138 @@ fn localidentity(dir: str, leaf: str) str = {
clean(root);
};
@test fn command_package_test_variants_keep_canonical_identity() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let target: str = strings.concat(source, "/cmdtool");
let importer: str = strings.concat(source, "/importer");
let wrapper: str = strings.concat(root, "/command-w6c.sh");
assert(os.mkdir(source, 448i32) == 0);
assert(os.mkdir(target, 448i32) == 0);
assert(os.mkdir(importer, 448i32) == 0);
writefile(strings.concat(target, "/main.ww"), strings.concat(
"package main;\n",
"export fn value() i32 = { return 41; };\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(target, "/internal_test.ww"), strings.concat(
"package main;\n",
"@test fn command_internal() void = { assert(value() == 41); };\n"));
writefile(strings.concat(target, "/external_test.ww"), strings.concat(
"package main_test;\n",
"import cmdtool;\n",
"@test fn command_external() void = { ",
"assert(cmdtool.value() == 41); };\n"));
writefile(strings.concat(importer, "/importer.ww"), strings.concat(
"package importer;\n",
"import cmdtool;\n",
"export fn value() i32 = { return cmdtool.value(); };\n"));
writeexecutable(wrapper, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_COMMAND_COMPILER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_COMMAND_COMPILER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_COMMAND_COMPILER_TRACE\"\n",
"exec \"$WW_COMMAND_REAL_COMPILER\" \"$@\"\n"));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let artifacts: []str = ["cmdtool", "cmdtool-internal-test",
"cmdtool_test-external-test"];
let suffixes: []str = [".unit.ww", ".wwi", ".a"];
let reference: []str = ["", "", "", "", "", "", "", "", ""];
let samebin: str = strings.concat(target, "/main.test");
let externalbin: str = strings.concat(target, "/main_test.test");
let workroot: str = strings.concat(samebin, ".sepwork");
let work: str = strings.concat(workroot, "/");
let baseenv: []str = os.getenvs();
let si: i32 = 0;
for (si < stages.len) {
let trace: str = strings.concat(root, "/", stages[si], ".trace");
writefile(trace, "");
let env: []str = alloc([], (baseenv.len + 3): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei],
"WW_COMMAND_COMPILER_TRACE=")
&& !strings.hasprefix(baseenv[ei],
"WW_COMMAND_REAL_COMPILER=")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", wrapper));
append(env, strings.concat("WW_COMMAND_COMPILER_TRACE=", trace));
append(env, strings.concat("WW_COMMAND_REAL_COMPILER=",
driver(compilers[si])));
let av: []str = [driver(stages[si]), "test", "-c", "-I", source,
target];
let out: commandout;
runcommandenv(root, strings.concat("command-variants-", stages[si]), av,
env, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0);
let ctrace: str = readfile(trace);
let ai: i32 = 0;
for (ai < artifacts.len) {
let line: str = linecontaining(ctrace,
strings.concat(artifacts[ai], ".unit.ww"));
assert(has(line, "<--command-package>"));
let sj: i32 = 0;
for (sj < suffixes.len) {
let body: str = readfile(strings.concat(work, artifacts[ai],
suffixes[sj]));
let ri: i32 = ai * suffixes.len + sj;
if (si == 0) { reference[ri] = strings.dup(body); }
else { assert(same(reference[ri], body)); };
sj += 1;
};
ai += 1;
};
assert(has(readfile(strings.concat(work, "cmdtool.unit.ww")),
"//ww:module-reset cmdtool\n"));
assert(has(readfile(strings.concat(work,
"cmdtool-internal-test.unit.ww")),
"//ww:module-reset cmdtool\n"));
assert(has(readfile(strings.concat(work,
"cmdtool_test-external-test.unit.ww")),
"//ww:module-reset cmdtool_test\n"));
let runav: []str = [samebin, "-package=main"];
runcommand(root, strings.concat("command-internal-run-", stages[si]),
runav, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "command_internal ... ok\n"));
let externalav: []str = [externalbin, "-package=main_test"];
runcommand(root, strings.concat("command-external-run-", stages[si]),
externalav, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "command_external ... ok\n"));
// The same command action is not generally source-importable. It is
// classified before leaf validation so this Go-like diagnostic is stable,
// and graph failure occurs before any compiler invocation.
clean(trace);
writefile(trace, "");
let importout: str = strings.concat(root, "/importer-", stages[si], ".a");
let importav: []str = [driver(stages[si]), "build", "-p", "-I", source,
"-o", importout, importer];
runcommandenv(root, strings.concat("command-import-", stages[si]),
importav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr,
"package cmdtool is a program, not an importable package\n"));
assert(readfile(trace).len == 0);
clean(workroot);
clean(samebin);
clean(externalbin);
si += 1;
};
clean(root);
};
@test fn package_graph_diagnostics_are_stable() void = {
let root: str = fresh();
let missing: str = strings.concat(root, "/missing");