ww: preserve command package identities
This commit is contained in:
8
Makefile
8
Makefile
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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*);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 757–775](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 799–805](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 433–440](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 202–206](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 437–447](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 450–455](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 628–658](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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user