wcc,ww: flip driver to separate-compilation only; delete build_one amalgamator (M4 E3-C1, #22)
build_one_sep (per-package compile + .wwi interfaces + link) becomes the sole build path. do_build/do_run/do_test and the ww twins all route through it; --sep is now an accepted no-op and the run-rejects-sep guard is removed. Deleted the single-file amalgamator, both stages: build_one, expand, expand_dir, peek_package (+ the wwstage twins + strictpkgmismatch). unit_has_package is retained -- the sep scan loop's inline-package check needs it. The sep-shared helpers (enumerate_dir_ww, locate_import*, import_path_form, ImportSet, and ww counterparts) stay; they back the surviving sep path. Restores missing-package enforcement under sep by construction: the sep scan loop loudly rejects an unresolvable import (cannot find package <name>) unless the package is defined inline in the same unit -- matching the deleted amalgamator and closing the silent-accept the flip would otherwise introduce. All 5 wwstage tools relink (each is built via the now-sep `ww build`); emitted asm is byte-identical to the combined build per bootstrap input, so the binary md5 delta is pure link layout, not codegen. selfhost/cmd/ww/main.combined.ww is now stale and unregenerable (its writer build_one is deleted); #90 deletes it next. Test retargets folded in (rule-11 carve-out, #61/#133 precedent): each asserts post-flip-only behavior, is un-pre-migratable unlike #93/#94/#103, and splitting reddens one side. Closes #97. - 989_slttypepref -> dir-package layout (xb imports xa so both same-leaf `invalid` types are in scope at xb.f); inline-multipackage was the amalgamator shape, deleted with the flip. - 989_sepbuild_run -> run --sep now genuinely runs (exit 7), not the old loud-reject (exit 2); + a private per-pid WW_PKGCACHE so the cs/ww per-package byte-id compare on the shared real lib pkgs (rt/time/os) no longer races concurrent siblings on the global out/.pkgcache (the flip made sep the sole path, so every test now contends that cache). - 737_direnum -> the deleted strictpkgmismatch "differs from" wording -> sep's "does not match import path" (shared substring, wwstage terser #68). - 989_lib_byteid -> corpus-completeness scan excludes generated .sepwork scratch (the old `! -name '*.combined.ww'` exclude didn't cover the new sep artifact).
This commit is contained in:
479
cmd/ww/main.c
479
cmd/ww/main.c
@@ -236,366 +236,13 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void expand(FILE *out, const char *path, struct ImportSet *visited,
|
|
||||||
const char *libdir, const char *modpath);
|
|
||||||
|
|
||||||
/* Scan `path` for its first non-comment-non-blank line; if it starts
|
|
||||||
* with `package <name>;` write the name into `out` (NUL-terminated)
|
|
||||||
* and return 1, else 0. Strict-same-package enforcement (task #23
|
|
||||||
* subset) is bundled here because the failure mode is dir-enum's
|
|
||||||
* own — a non-dir-enum compilation unit cannot trigger it. Hare's
|
|
||||||
* hare/module/srcs.ha:131 has the same constraint via the README
|
|
||||||
* gate; we encode it as same-package across all enumerated files. */
|
|
||||||
static int
|
|
||||||
peek_package(const char *path, char *out, size_t outsz)
|
|
||||||
{
|
|
||||||
FILE *in = fopen(path, "rb");
|
|
||||||
if (in == NULL) return 0;
|
|
||||||
char line[2048];
|
|
||||||
int found = 0;
|
|
||||||
while (fgets(line, sizeof line, in)) {
|
|
||||||
const char *p = line;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
if (*p == '\n' || *p == '\0') continue;
|
|
||||||
if (p[0] == '/' && p[1] == '/') continue;
|
|
||||||
if (strncmp(p, "package ", 8) != 0
|
|
||||||
&& strncmp(p, "package\t", 8) != 0) break;
|
|
||||||
p += 8;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
size_t i = 0;
|
|
||||||
while (i + 1 < outsz && ((p[i] >= 'a' && p[i] <= 'z')
|
|
||||||
|| (p[i] >= 'A' && p[i] <= 'Z')
|
|
||||||
|| p[i] == '_' || (p[i] >= '0' && p[i] <= '9')))
|
|
||||||
out[i] = p[i], i++;
|
|
||||||
out[i] = '\0';
|
|
||||||
found = (i > 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fclose(in);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unit_has_package — does `path` declare `package <leaf>;` ANYWHERE?
|
|
||||||
* #16 ENFORCE-driver (rob A): distinguishes a genuinely-missing import
|
|
||||||
* from one satisfied by an INLINE package in the same unit. Unlike
|
|
||||||
* peek_package (stops at the FIRST package decl), this scans every line
|
|
||||||
* — single-file multi-package fixtures carry several `package` decls. The
|
|
||||||
* comment-skip line scan + name match mirror peek_package, uncapped. The
|
|
||||||
* wwstage twin unithaspackage must stay byte-identical (rule 10). */
|
|
||||||
static int
|
|
||||||
unit_has_package(const char *path, const char *leaf)
|
|
||||||
{
|
|
||||||
FILE *in = fopen(path, "rb");
|
|
||||||
if (in == NULL) return 0;
|
|
||||||
char line[2048];
|
|
||||||
int found = 0;
|
|
||||||
while (fgets(line, sizeof line, in)) {
|
|
||||||
const char *p = line;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
if (p[0] == '/' && p[1] == '/') continue;
|
|
||||||
if (strncmp(p, "package ", 8) != 0
|
|
||||||
&& strncmp(p, "package\t", 8) != 0) continue;
|
|
||||||
p += 8;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
size_t i = 0;
|
|
||||||
while (leaf[i] != '\0' && leaf[i] == p[i]) i++;
|
|
||||||
if (leaf[i] == '\0') {
|
|
||||||
char c = p[i];
|
|
||||||
if (c == ';' || c == ' ' || c == '\t'
|
|
||||||
|| c == '\n' || c == '\0') { found = 1; break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(in);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expand_dir — enumerate <dirpath>/*.ww (skip *test.ww), byte-sort,
|
|
||||||
* recurse into each. Mirrors ref/hare/hare/module/srcs.ha:183
|
|
||||||
* `_findsrcs` minus tag handling. The visited set still keys on
|
|
||||||
* concrete file paths so multi-file modules are pulled once.
|
|
||||||
* Strict-same-package: all enumerated files must declare the same
|
|
||||||
* `package <name>;` (task #23 subset; failure mode native to
|
|
||||||
* dir-enum). */
|
|
||||||
static void
|
|
||||||
expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
|
||||||
const char *libdir, const char *modpath)
|
|
||||||
{
|
|
||||||
char **files = NULL;
|
|
||||||
int n = enumerate_dir_ww(dirpath, &files);
|
|
||||||
char dirpkg[128] = {0};
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
char fp[1024];
|
|
||||||
snprintf(fp, sizeof fp, "%s/%s", dirpath, files[i]);
|
|
||||||
char pkg[128];
|
|
||||||
if (peek_package(fp, pkg, sizeof pkg)) {
|
|
||||||
if (dirpkg[0] == '\0') {
|
|
||||||
snprintf(dirpkg, sizeof dirpkg, "%s", pkg);
|
|
||||||
} else if (strcmp(dirpkg, pkg) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"ww: %s: package %s differs from %s in same module dir %s\n",
|
|
||||||
fp, pkg, dirpkg, dirpath);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expand(out, fp, visited, libdir, modpath);
|
|
||||||
free(files[i]);
|
|
||||||
}
|
|
||||||
free(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Recursively expand `path`: for each top-level `import IDENT;` we
|
|
||||||
* find, resolve the import and expand it first, then append our own
|
|
||||||
* bytes. Already-visited paths are skipped. Each source carries its
|
|
||||||
* own `package <name>;` declaration (the parser stamps decls from
|
|
||||||
* it). */
|
|
||||||
static void
|
|
||||||
expand(FILE *out, const char *path, struct ImportSet *visited,
|
|
||||||
const char *libdir, const char *modpath)
|
|
||||||
{
|
|
||||||
if (import_seen(visited, path)) return;
|
|
||||||
import_add(visited, path);
|
|
||||||
|
|
||||||
FILE *in = fopen(path, "rb");
|
|
||||||
if (in == NULL) {
|
|
||||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char line[2048];
|
|
||||||
while (fgets(line, sizeof line, in)) {
|
|
||||||
const char *p = line;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
|
||||||
continue;
|
|
||||||
p += 7;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
char name[256] = {0};
|
|
||||||
int j = 0;
|
|
||||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
|
||||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
|
||||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
|
||||||
if (j == 0) continue;
|
|
||||||
char path_form[256];
|
|
||||||
import_path_form(name, path_form, sizeof path_form);
|
|
||||||
char ipath[1024];
|
|
||||||
int is_dir = 0;
|
|
||||||
/* #16 ENFORCE-driver (rob A): an unresolvable import is a hard
|
|
||||||
* error, not a silent skip — the old `continue` let a typo'd/
|
|
||||||
* missing package drop its symbols and surface later as a
|
|
||||||
* confusing downstream failure. BUT a locate-miss is legal when
|
|
||||||
* the package is defined INLINE in the same unit (single-file
|
|
||||||
* multi-package: `package aa; ... package main; import aa;`) —
|
|
||||||
* the bundler can't pull it as a file but the checker binds it.
|
|
||||||
* So: miss + inline `package <leaf>` present -> silent skip;
|
|
||||||
* miss + not inline -> fatal. */
|
|
||||||
if (!locate_import(libdir, path_form, ipath, sizeof ipath,
|
|
||||||
&is_dir)) {
|
|
||||||
const char *dot = strrchr(name, '.');
|
|
||||||
const char *leaf = dot ? dot + 1 : name;
|
|
||||||
if (unit_has_package(path, leaf))
|
|
||||||
continue; /* inline-satisfied */
|
|
||||||
fprintf(stderr, "ww: cannot find package %s\n", name);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
/* M1 #22 (isdir-gated, rob-ratified): a package IS a directory, so
|
|
||||||
* only DIRECTORY imports are package boundaries that path-mangle.
|
|
||||||
* A single-file import (`import opcodes;` → opcodes.ww declaring
|
|
||||||
* `package w6a`) is an intra-package file-split — it keeps its
|
|
||||||
* in-file `package` clause as its module (no directive). */
|
|
||||||
if (is_dir) expand_dir(out, ipath, visited, libdir, name);
|
|
||||||
else expand(out, ipath, visited, libdir, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #16 option-B: a package-less file's decls would otherwise inherit
|
|
||||||
* the preceding bundled module's sticky curmod (parser parse.c). Emit
|
|
||||||
* a curmod-reset boundary directive so the lexer/parser attribute the
|
|
||||||
* file to the primary module ("") — fixes the self-import false-fire
|
|
||||||
* and the leaked-prefix bug, codegen-neutral (bare symbols kept; not
|
|
||||||
* `package main`, which would main-prefix them). A packaged file's own
|
|
||||||
* `package` decl already sets curmod, so it needs nothing — keeping
|
|
||||||
* the directive out of every tracked combined.ww. (Task #11.) */
|
|
||||||
if (modpath != NULL && modpath[0] != '\0') {
|
|
||||||
/* M1 (#22): an import-reached file carries its full dotted
|
|
||||||
* import path so codegen mangles symbols on the path, not the
|
|
||||||
* leaf `package` clause. The directive's absence is the root
|
|
||||||
* marker (#32): root/primary files take the branch below. */
|
|
||||||
fprintf(out, "//ww:module %s\n", modpath);
|
|
||||||
} else {
|
|
||||||
/* Root/primary file: reset the bundle boundary so a preceding
|
|
||||||
* imported section's sticky pathmod (M1 #22) is cleared. A
|
|
||||||
* package-less file then stays primary ("") as before; a
|
|
||||||
* packaged primary's own `package` clause sets curmod fresh
|
|
||||||
* (pathmod now NULL → real clause, not an assertion). */
|
|
||||||
fputs("//ww:module-reset\n", out);
|
|
||||||
}
|
|
||||||
rewind(in);
|
|
||||||
int ch;
|
|
||||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
|
||||||
fputc('\n', out);
|
|
||||||
fclose(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* objstem (when non-NULL/non-empty) redirects the .s/.o/.combined.ww
|
|
||||||
* side files to live beside the build's OUTPUT instead of next to the
|
|
||||||
* source (task #15/T3). `ww run` and `ww build -o` pass it so concurrent
|
|
||||||
* builds never share the next-to-source fixed paths; the default
|
|
||||||
* (objstem == NULL) keeps the old next-to-source layout, load-bearing
|
|
||||||
* for make's tracked-combined.ww regen + the #110 freshness gate. */
|
|
||||||
static int
|
|
||||||
build_one(const char *src, int entry_is_dir, const char *out,
|
|
||||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
|
||||||
const char *extra_libdirs, int is_test)
|
|
||||||
{
|
|
||||||
const char *c6 = toolpath("WW_W6C", "w6c");
|
|
||||||
const char *a6 = toolpath("WW_W6A", "w6a");
|
|
||||||
const char *l6 = toolpath("WW_W6L", "w6l");
|
|
||||||
const char *libdir = getenv("WW_LIB");
|
|
||||||
if (libdir == NULL || libdir[0] == 0) {
|
|
||||||
static char libbuf[1024];
|
|
||||||
snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir);
|
|
||||||
libdir = libbuf;
|
|
||||||
}
|
|
||||||
const char *srcdir = getenv("WW_SRCLIB");
|
|
||||||
static char srcbuf[1024];
|
|
||||||
if (srcdir == NULL || srcdir[0] == 0) {
|
|
||||||
/* in-tree default: ../../lib relative to bin/ */
|
|
||||||
snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir);
|
|
||||||
if (access(srcbuf, 0) == 0) srcdir = srcbuf;
|
|
||||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
|
||||||
else srcdir = libdir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compose the search path: source-file's directory first, then any
|
|
||||||
* -I dirs, then srcdir. The source-dir lead matches Hare's CWD-first
|
|
||||||
* convention (its `hare test` is run from the module dir, making CWD
|
|
||||||
* == module-dir); our wrappers don't cd, so dirname(src) is the
|
|
||||||
* closest analog. Also matches cc -I. — source-dir wins ties over
|
|
||||||
* the system path. locate_import walks left-to-right.
|
|
||||||
*
|
|
||||||
* For a dir entry the source-dir IS src; for a file entry it's
|
|
||||||
* the dirname. */
|
|
||||||
char srcd[1024];
|
|
||||||
if (entry_is_dir) {
|
|
||||||
snprintf(srcd, sizeof srcd, "%s", src);
|
|
||||||
size_t n = strlen(srcd);
|
|
||||||
while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0';
|
|
||||||
} else {
|
|
||||||
const char *slash = strrchr(src, '/');
|
|
||||||
if (slash) {
|
|
||||||
size_t n = (size_t)(slash - src);
|
|
||||||
if (n >= sizeof srcd) n = sizeof srcd - 1;
|
|
||||||
memcpy(srcd, src, n);
|
|
||||||
srcd[n] = '\0';
|
|
||||||
} else {
|
|
||||||
srcd[0] = '.';
|
|
||||||
srcd[1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static char searchpath[4096];
|
|
||||||
if (extra_includes && extra_includes[0])
|
|
||||||
snprintf(searchpath, sizeof searchpath, "%s:%s:%s",
|
|
||||||
srcd, extra_includes, srcdir);
|
|
||||||
else
|
|
||||||
snprintf(searchpath, sizeof searchpath, "%s:%s", srcd, srcdir);
|
|
||||||
srcdir = searchpath;
|
|
||||||
|
|
||||||
/* Derive a stem for .s/.o/.combined.ww side files. For a file
|
|
||||||
* entry strip the .ww. For a dir entry use <dir>/<basename(dir)>
|
|
||||||
* so artifacts land inside the module directory. */
|
|
||||||
char stem[1024];
|
|
||||||
if (entry_is_dir) {
|
|
||||||
const char *b = strrchr(srcd, '/');
|
|
||||||
const char *base = b ? b + 1 : srcd;
|
|
||||||
snprintf(stem, sizeof stem, "%s/%s", srcd, base);
|
|
||||||
} else {
|
|
||||||
snprintf(stem, sizeof stem, "%s", src);
|
|
||||||
char *dot = strrchr(stem, '.');
|
|
||||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *ostem = (objstem && objstem[0]) ? objstem : stem;
|
|
||||||
char asmf[1024], obj[1024], combined[1024];
|
|
||||||
snprintf(asmf, sizeof asmf, "%s.s", ostem);
|
|
||||||
snprintf(obj, sizeof obj, "%s.o", ostem);
|
|
||||||
snprintf(combined, sizeof combined, "%s.combined.ww", ostem);
|
|
||||||
|
|
||||||
/* Resolve imports by concatenating sources into a temp file. The
|
|
||||||
* compiler then sees one flat source. Dir entry → enumerate the
|
|
||||||
* module dir's *.ww (less *test.ww); file entry → start at the
|
|
||||||
* file. */
|
|
||||||
{
|
|
||||||
FILE *cf = fopen(combined, "wb");
|
|
||||||
if (cf == NULL) {
|
|
||||||
fprintf(stderr, "ww: cannot open %s\n", combined);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
struct ImportSet visited = {0};
|
|
||||||
/* #17 auto-bundle lib/test: the -T synth's main calls lib/test's
|
|
||||||
* run(), but @test files don't `import test;`. Pull it like an
|
|
||||||
* implicit import through the same filename-keyed expand path
|
|
||||||
* (the visited set dedupes if a fixture imports it explicitly).
|
|
||||||
* The wwstage twin (selfhost/cmd/ww/main.ww) mirrors this. */
|
|
||||||
if (is_test) {
|
|
||||||
char tpath[1024];
|
|
||||||
int tdir = 0;
|
|
||||||
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
|
|
||||||
if (tdir) expand_dir(cf, tpath, &visited, srcdir, "test");
|
|
||||||
else expand(cf, tpath, &visited, srcdir, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir, NULL);
|
|
||||||
else expand(cf, src, &visited, srcdir, NULL);
|
|
||||||
fclose(cf);
|
|
||||||
for (int i = 0; i < visited.n; i++) free(visited.paths[i]);
|
|
||||||
free(visited.paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
char cmd[4096];
|
|
||||||
snprintf(cmd, sizeof cmd, "%s %s-o %s %s", c6, is_test ? "-T " : "",
|
|
||||||
asmf, combined);
|
|
||||||
if (run(cmd) != 0) {
|
|
||||||
fprintf(stderr, "ww: w6c failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd, "%s -o %s %s", a6, obj, asmf);
|
|
||||||
if (run(cmd) != 0) {
|
|
||||||
fprintf(stderr, "ww: w6a failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
/* Link runtime: prefer libwwrt.a (selective archive pull) but
|
|
||||||
* fall back to start.o + syscall.o in the in-tree obj/ dir if
|
|
||||||
* we're running uninstalled. */
|
|
||||||
char rtargs[2048] = {0};
|
|
||||||
char path[1024];
|
|
||||||
snprintf(path, sizeof path, "%s/libwwrt.a", libdir);
|
|
||||||
if (access(path, 0) == 0) {
|
|
||||||
snprintf(rtargs, sizeof rtargs, "%s", path);
|
|
||||||
} else {
|
|
||||||
char a1[1024], a2[1024];
|
|
||||||
snprintf(a1, sizeof a1, "%s/../obj/rt/start.o", self_dir);
|
|
||||||
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
|
||||||
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
|
||||||
}
|
|
||||||
/* -L<dir> goes before -l<name> so w6l can resolve the latter. */
|
|
||||||
const char *libargs = (extra_libs && extra_libs[0]) ? extra_libs : "";
|
|
||||||
const char *libdirset = (extra_libdirs && extra_libdirs[0]) ? extra_libdirs : "";
|
|
||||||
snprintf(cmd, sizeof cmd, "%s -o %s %s %s%s%s%s%s",
|
|
||||||
l6, out, obj, rtargs,
|
|
||||||
libdirset[0] ? " " : "", libdirset,
|
|
||||||
libargs[0] ? " " : "", libargs);
|
|
||||||
if (run(cmd) != 0) {
|
|
||||||
fprintf(stderr, "ww: w6l failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
* ww build --sep — M3-tail separate-compilation driver (task #46/c3).
|
* ww build — separate-compilation driver (task #46/c3).
|
||||||
* ====================================================================
|
* ====================================================================
|
||||||
* The `--sep` path materializes each imported package's `.wwi`
|
* This is the SOLE build path (E3-C1 flip, task #87): the legacy
|
||||||
* interface and compiles every package on its own (`w6c -c`), then
|
* single-file amalgamator (build_one/expand) is gone. Each imported
|
||||||
* flat-links the `.o` set. combined.ww stays the DEFAULT live path;
|
* package's `.wwi` interface is materialized and every package is
|
||||||
* --sep is purely additive (no existing invocation reaches it).
|
* compiled on its own (`w6c -c`), then the `.o` set is flat-linked.
|
||||||
*
|
*
|
||||||
* Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND
|
* Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND
|
||||||
* producer (writes this package's `.wwi` for its importers via -I), so
|
* producer (writes this package's `.wwi` for its importers via -I), so
|
||||||
@@ -664,6 +311,40 @@ sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
|||||||
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* unit_has_package — does `path` declare `package <leaf>;` ANYWHERE?
|
||||||
|
* #16 ENFORCE-driver (rob A): distinguishes a genuinely-missing import
|
||||||
|
* from one satisfied by an INLINE package in the same unit. Unlike
|
||||||
|
* peek_package (stops at the FIRST package decl), this scans every line
|
||||||
|
* — single-file multi-package fixtures carry several `package` decls. The
|
||||||
|
* comment-skip line scan + name match mirror peek_package, uncapped. The
|
||||||
|
* wwstage twin unithaspackage must stay byte-identical (rule 10). */
|
||||||
|
static int
|
||||||
|
unit_has_package(const char *path, const char *leaf)
|
||||||
|
{
|
||||||
|
FILE *in = fopen(path, "rb");
|
||||||
|
if (in == NULL) return 0;
|
||||||
|
char line[2048];
|
||||||
|
int found = 0;
|
||||||
|
while (fgets(line, sizeof line, in)) {
|
||||||
|
const char *p = line;
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
if (p[0] == '/' && p[1] == '/') continue;
|
||||||
|
if (strncmp(p, "package ", 8) != 0
|
||||||
|
&& strncmp(p, "package\t", 8) != 0) continue;
|
||||||
|
p += 8;
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
size_t i = 0;
|
||||||
|
while (leaf[i] != '\0' && leaf[i] == p[i]) i++;
|
||||||
|
if (leaf[i] == '\0') {
|
||||||
|
char c = p[i];
|
||||||
|
if (c == ';' || c == ' ' || c == '\t'
|
||||||
|
|| c == '\n' || c == '\0') { found = 1; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(in);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
/* Scan one source file for top-level `import IDENT;`, resolving each. A
|
/* Scan one source file for top-level `import IDENT;`, resolving each. A
|
||||||
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||||
* `pi`. A FILE import is an intra-package split — fold its imports into
|
* `pi`. A FILE import is an intra-package split — fold its imports into
|
||||||
@@ -699,12 +380,21 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
|||||||
import_path_form(name, path_form, sizeof path_form);
|
import_path_form(name, path_form, sizeof path_form);
|
||||||
char ipath[1024];
|
char ipath[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
/* a locate-miss is an inline-satisfied (single-file multi-
|
/* #16 ENFORCE-driver: an unresolvable import is a hard error,
|
||||||
* package) import; --sep targets directory packages, so the
|
* not a silent skip — EXCEPT when the package is defined INLINE
|
||||||
* combined path owns that case. Skip, mirroring expand. */
|
* in the same unit (single-file multi-package; leaf = the last
|
||||||
|
* dotted component). E3-C1: the combined path that used to own
|
||||||
|
* the genuine-missing case is gone, so the sep producer enforces
|
||||||
|
* it here (INV-2, by construction). Mirrors the deleted expand. */
|
||||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||||
&is_dir))
|
&is_dir)) {
|
||||||
continue;
|
const char *dot = strrchr(name, '.');
|
||||||
|
const char *leaf = dot ? dot + 1 : name;
|
||||||
|
if (unit_has_package(file, leaf))
|
||||||
|
continue; /* inline-satisfied */
|
||||||
|
fprintf(stderr, "ww: cannot find package %s\n", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
if (is_dir) {
|
if (is_dir) {
|
||||||
int di = sep_find_or_add(g, name, ipath, 1);
|
int di = sep_find_or_add(g, name, ipath, 1);
|
||||||
if (di < 0) { rc = -1; break; }
|
if (di < 0) { rc = -1; break; }
|
||||||
@@ -1470,24 +1160,17 @@ parse_build_flags(const char *cmd, int argc, char **argv,
|
|||||||
char *libdirs, size_t libdirsz,
|
char *libdirs, size_t libdirsz,
|
||||||
char *libs, size_t libsz,
|
char *libs, size_t libsz,
|
||||||
char *outpath, size_t outsz,
|
char *outpath, size_t outsz,
|
||||||
const char **src_out, int *want_sep)
|
const char **src_out)
|
||||||
{
|
{
|
||||||
*src_out = NULL;
|
*src_out = NULL;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < argc; i++) {
|
for (; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--sep") == 0) {
|
if (strcmp(argv[i], "--sep") == 0) {
|
||||||
/* M3-tail c3: separate-compilation path, build-only.
|
/* E3-C1 flip: separate compilation is now the sole build
|
||||||
* `run` has no sep-compile-then-run path (out of #46
|
* path, so --sep no longer selects anything. Retained as an
|
||||||
* commit-3 scope), so it LOUD-REJECTS rather than
|
* accepted no-op (every subcommand) so the existing --sep
|
||||||
* silently swallowing a typed flag — both stages reject
|
* gate corpus keeps driving the default path. (Task #87.) */
|
||||||
* identically (rule 10; want_sep==NULL marks the run
|
;
|
||||||
* caller). */
|
|
||||||
if (want_sep == NULL) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"ww %s: --sep is only valid with build\n", cmd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*want_sep = 1;
|
|
||||||
} else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
} else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||||
size_t n = strlen(libs);
|
size_t n = strlen(libs);
|
||||||
snprintf(libs + n, libsz - n,
|
snprintf(libs + n, libsz - n,
|
||||||
@@ -1553,10 +1236,9 @@ do_build(int argc, char **argv)
|
|||||||
char libdirs[2048] = {0};
|
char libdirs[2048] = {0};
|
||||||
char incs[2048] = {0};
|
char incs[2048] = {0};
|
||||||
char outflag[1024] = {0};
|
char outflag[1024] = {0};
|
||||||
int want_sep = 0;
|
|
||||||
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
||||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||||
outflag, sizeof outflag, &src, &want_sep) < 0)
|
outflag, sizeof outflag, &src) < 0)
|
||||||
return 2;
|
return 2;
|
||||||
if (src == NULL) src = "."; /* default: build cwd */
|
if (src == NULL) src = "."; /* default: build cwd */
|
||||||
char resolved[1024];
|
char resolved[1024];
|
||||||
@@ -1582,10 +1264,8 @@ do_build(int argc, char **argv)
|
|||||||
} else {
|
} else {
|
||||||
basename_no_ext(resolved, out, sizeof out);
|
basename_no_ext(resolved, out, sizeof out);
|
||||||
}
|
}
|
||||||
if (want_sep)
|
return build_one_sep(resolved, is_dir, out, objstem, incs, libs,
|
||||||
return build_one_sep(resolved, is_dir, out, objstem, incs,
|
libdirs, 0);
|
||||||
libs, libdirs, 0);
|
|
||||||
return build_one(resolved, is_dir, out, objstem, incs, libs, libdirs, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -1598,7 +1278,7 @@ do_run(int argc, char **argv)
|
|||||||
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
||||||
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
||||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||||
outflag, sizeof outflag, &src, NULL);
|
outflag, sizeof outflag, &src);
|
||||||
if (next < 0) return 2;
|
if (next < 0) return 2;
|
||||||
if (src == NULL) src = ".";
|
if (src == NULL) src = ".";
|
||||||
char resolved[1024];
|
char resolved[1024];
|
||||||
@@ -1611,7 +1291,7 @@ do_run(int argc, char **argv)
|
|||||||
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
|
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
|
||||||
/* objstem = tmp → intermediates land at /tmp/ww_run_<pid>.{s,o,
|
/* objstem = tmp → intermediates land at /tmp/ww_run_<pid>.{s,o,
|
||||||
* combined.ww}, never next to the source (T3). */
|
* combined.ww}, never next to the source (T3). */
|
||||||
if (build_one(resolved, is_dir, tmp, tmp, incs, libs, libdirs, 0) != 0)
|
if (build_one_sep(resolved, is_dir, tmp, tmp, incs, libs, libdirs, 0) != 0)
|
||||||
return 1;
|
return 1;
|
||||||
/* exec the built binary with any trailing argv as its argv. */
|
/* exec the built binary with any trailing argv as its argv. */
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
@@ -1648,11 +1328,6 @@ do_test(int argc, char **argv)
|
|||||||
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
|
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
|
||||||
int compileonly = 0;
|
int compileonly = 0;
|
||||||
char outstem[1024] = {0};
|
char outstem[1024] = {0};
|
||||||
/* #79 E1: --sep routes a single-file/module test through the
|
|
||||||
* separate-compilation producer (build_one_sep, is_test=1) instead of
|
|
||||||
* the amalgamator. Additive (combined stays default); dir-mode --sep is
|
|
||||||
* deferred to E2. */
|
|
||||||
int use_sep = 0;
|
|
||||||
/* #17: an optional second positional after the target is a fnmatch
|
/* #17: an optional second positional after the target is a fnmatch
|
||||||
* name-filter pattern, forwarded to the test binary as argv[1]. Only
|
* name-filter pattern, forwarded to the test binary as argv[1]. Only
|
||||||
* meaningful for a single test file/module — rejected in dir mode. */
|
* meaningful for a single test file/module — rejected in dir mode. */
|
||||||
@@ -1660,7 +1335,8 @@ do_test(int argc, char **argv)
|
|||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
if (argv[i][0] == '-') {
|
if (argv[i][0] == '-') {
|
||||||
if (strcmp(argv[i], "--sep") == 0) {
|
if (strcmp(argv[i], "--sep") == 0) {
|
||||||
use_sep = 1;
|
/* E3-C1 flip: sep is the sole path; --sep is an
|
||||||
|
* accepted no-op (task #87). */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (argv[i][1] == 'I') {
|
if (argv[i][1] == 'I') {
|
||||||
@@ -1715,11 +1391,8 @@ do_test(int argc, char **argv)
|
|||||||
const char *outp;
|
const char *outp;
|
||||||
if (outstem[0]) outp = outstem;
|
if (outstem[0]) outp = outstem;
|
||||||
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
||||||
int br = use_sep
|
int br = build_one_sep(resolved, is_dir, outp,
|
||||||
? build_one_sep(resolved, is_dir, outp,
|
outstem[0] ? outstem : NULL, incs, "", "", 1);
|
||||||
outstem[0] ? outstem : NULL, incs, "", "", 1)
|
|
||||||
: build_one(resolved, is_dir, outp,
|
|
||||||
outstem[0] ? outstem : NULL, incs, "", "", 1);
|
|
||||||
if (br != 0) return 1;
|
if (br != 0) return 1;
|
||||||
if (compileonly) return 0;
|
if (compileonly) return 0;
|
||||||
int rc = run_test_bin(outp, pattern);
|
int rc = run_test_bin(outp, pattern);
|
||||||
@@ -1732,11 +1405,8 @@ do_test(int argc, char **argv)
|
|||||||
const char *outp;
|
const char *outp;
|
||||||
if (outstem[0]) outp = outstem;
|
if (outstem[0]) outp = outstem;
|
||||||
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
||||||
int br = use_sep
|
int br = build_one_sep(target, 0, outp, outstem[0] ? outstem : NULL,
|
||||||
? build_one_sep(target, 0, outp, outstem[0] ? outstem : NULL,
|
incs, "", "", 1);
|
||||||
incs, "", "", 1)
|
|
||||||
: build_one(target, 0, outp, outstem[0] ? outstem : NULL,
|
|
||||||
incs, "", "", 1);
|
|
||||||
if (br != 0) return 1;
|
if (br != 0) return 1;
|
||||||
if (compileonly) return 0;
|
if (compileonly) return 0;
|
||||||
int rc = run_test_bin(outp, pattern);
|
int rc = run_test_bin(outp, pattern);
|
||||||
@@ -1751,13 +1421,6 @@ do_test(int argc, char **argv)
|
|||||||
fprintf(stderr, "ww test: -c/-o need a single test file\n");
|
fprintf(stderr, "ww test: -c/-o need a single test file\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
/* #79 E1: dir-mode --sep is deferred to E2 — single-file/module is
|
|
||||||
* enough to prove the sep test path. Loud-reject rather than silently
|
|
||||||
* fall back to the combined per-file build. */
|
|
||||||
if (use_sep) {
|
|
||||||
fprintf(stderr, "ww test: --sep needs a single test file\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
/* #17: a name-filter pattern is per-binary; directory mode builds one
|
/* #17: a name-filter pattern is per-binary; directory mode builds one
|
||||||
* binary per *_test.ww, so a single pattern can't sensibly route. */
|
* binary per *_test.ww, so a single pattern can't sensibly route. */
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
@@ -1781,7 +1444,7 @@ do_test(int argc, char **argv)
|
|||||||
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d_%d", getpid(), i);
|
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d_%d", getpid(), i);
|
||||||
const char *label = strrchr(files[i], '/');
|
const char *label = strrchr(files[i], '/');
|
||||||
label = label ? label + 1 : files[i];
|
label = label ? label + 1 : files[i];
|
||||||
int rc = build_one(files[i], 0, tmp, NULL, target, "", "", 1);
|
int rc = build_one_sep(files[i], 0, tmp, NULL, target, "", "", 1);
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
fprintf(stderr, "FAIL %s (build)\n", label);
|
fprintf(stderr, "FAIL %s (build)\n", label);
|
||||||
fail++;
|
fail++;
|
||||||
|
|||||||
@@ -181,12 +181,13 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
|||||||
return code;
|
return code;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- `use` resolution + source concatenation --------------------------
|
// ---- import resolution + visited-set -----------------------------------
|
||||||
//
|
//
|
||||||
// Recursive expansion: for each `use IDENT;` we find at the top of
|
// The separate-compilation producer scans each unit's top-of-file
|
||||||
// `path`, resolve via the colon-separated `dirs`, expand the imported
|
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
|
||||||
// file first, then append our own bytes. Already-visited paths are
|
// search path. A per-scan visited set (linear; typical builds visit a
|
||||||
// skipped (linear scan; typical builds visit a handful of modules).
|
// handful of modules) breaks cycles. (E3-C1: the legacy single-file
|
||||||
|
// source concatenator was deleted — sep is the sole path. Task #87.)
|
||||||
|
|
||||||
type strnode = struct {
|
type strnode = struct {
|
||||||
s: str,
|
s: str,
|
||||||
@@ -509,307 +510,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
|||||||
return src + idstart, idlen;
|
return src + idstart, idlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
// expand — emit one file's bytes verbatim into the combined stream,
|
|
||||||
// after recursive-expanding its top-of-file `import X;` imports.
|
|
||||||
// Each source declares its own `package <name>;` (parser stamps
|
|
||||||
// decls).
|
|
||||||
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
|
|
||||||
let plen: u64 = cstrlen(pathcs);
|
|
||||||
let view: str;
|
|
||||||
view.ptr = pathcs;
|
|
||||||
view.len = plen: i32;
|
|
||||||
let pathstr: str = strings.dup(view);
|
|
||||||
if (visitseen(c, pathstr)) { return; };
|
|
||||||
visitadd(c, pathstr);
|
|
||||||
|
|
||||||
let bufp: *u8;
|
|
||||||
let blen: u64;
|
|
||||||
bufp, blen = slurp(pathcs);
|
|
||||||
if (bufp == nil) {
|
|
||||||
cerr("ww: cannot read source\n");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pass 1: scan top-of-file `import X;` lines, recursively expand.
|
|
||||||
let i: u64 = 0u64;
|
|
||||||
for (i < blen) {
|
|
||||||
let j: u64 = i;
|
|
||||||
for (j < blen) {
|
|
||||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
|
||||||
j += 1u64;
|
|
||||||
};
|
|
||||||
let idp: *u8;
|
|
||||||
let idn: u64;
|
|
||||||
idp, idn = scanuse(bufp + i, j - i);
|
|
||||||
if (idp != nil) {
|
|
||||||
let isdir: i32 = 0;
|
|
||||||
let ipath: *u8 = locateimport(c.dirs, idp, idn,
|
|
||||||
&isdir);
|
|
||||||
if (ipath != nil) {
|
|
||||||
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
|
|
||||||
// imports are package boundaries that path-mangle. A
|
|
||||||
// single-file import (`import opcodes;` → opcodes.ww
|
|
||||||
// declaring `package w6a`) is an intra-package file-split:
|
|
||||||
// it keeps its in-file `package` clause as its module
|
|
||||||
// (no directive → reset → package-clause mangling).
|
|
||||||
if (isdir != 0) {
|
|
||||||
let mv: str;
|
|
||||||
mv.ptr = idp;
|
|
||||||
mv.len = idn: i32;
|
|
||||||
let modstr: str = strings.dup(mv);
|
|
||||||
expanddir(c, ipath, modstr);
|
|
||||||
} else {
|
|
||||||
expand(c, ipath, "");
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// #16 ENFORCE-driver (rob A): a locate-miss is
|
|
||||||
// legal when the package is defined INLINE in the
|
|
||||||
// same unit (single-file multi-package). leaf = the
|
|
||||||
// last dotted component of the import name; if an
|
|
||||||
// inline `package <leaf>` exists -> silent skip
|
|
||||||
// (the checker binds it), else fatal. cstage twin in
|
|
||||||
// cmd/ww/main.c; fatal text identical.
|
|
||||||
let lstart: u64 = 0u64;
|
|
||||||
let lk: u64 = 0u64;
|
|
||||||
for (lk < idn) {
|
|
||||||
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
|
|
||||||
lk += 1u64;
|
|
||||||
};
|
|
||||||
let leafp: *u8 = idp + lstart;
|
|
||||||
let leafn: u64 = idn - lstart;
|
|
||||||
if (!unithaspackage(bufp, blen, leafp, leafn)) {
|
|
||||||
cerr("ww: cannot find package ");
|
|
||||||
os.write(2, idp, idn);
|
|
||||||
cerr("\n");
|
|
||||||
os.exit(1);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
i = j + 1u64;
|
|
||||||
};
|
|
||||||
|
|
||||||
// #16 option-B: a package-less file's decls would otherwise inherit
|
|
||||||
// the preceding bundled module's sticky curmod (parser parse.ww). Emit
|
|
||||||
// a curmod-reset boundary directive so the lexer/parser attribute the
|
|
||||||
// file to the primary module ("") — fixes the self-import false-fire
|
|
||||||
// and the leaked-prefix bug, codegen-neutral (bare symbols kept; not
|
|
||||||
// `package main`, which would main-prefix them). A packaged file's own
|
|
||||||
// `package` decl already sets curmod, so it needs nothing — keeping
|
|
||||||
// the directive out of every tracked combined.ww. (Task #11.)
|
|
||||||
if (modpath.len != 0) {
|
|
||||||
// M1 #22: import-reached file carries its full dotted path so
|
|
||||||
// codegen mangles symbols on the path, not the leaf clause.
|
|
||||||
let dm: str = "//ww:module ";
|
|
||||||
os.writeall(c.out, dm.ptr, dm.len: u64);
|
|
||||||
os.writeall(c.out, modpath.ptr, modpath.len: u64);
|
|
||||||
os.writeall(c.out, "\n".ptr, 1u64);
|
|
||||||
} else {
|
|
||||||
// Root/primary: always reset so a preceding imported section's
|
|
||||||
// sticky pathmod is cleared; a packaged primary's own `package`
|
|
||||||
// clause then sets curmod fresh (pathmod NULL → real clause).
|
|
||||||
let d: str = "//ww:module-reset\n";
|
|
||||||
os.writeall(c.out, d.ptr, d.len: u64);
|
|
||||||
};
|
|
||||||
|
|
||||||
os.writeall(c.out, bufp, blen);
|
|
||||||
os.writeall(c.out, "\n".ptr, 1u64);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Scan `pathcs` for its first non-comment-non-blank line; if it
|
|
||||||
// starts with `package <name>;` return the package name as a fresh
|
|
||||||
// heap-allocated NUL-terminated *u8, else nil. Same shape as cstage
|
|
||||||
// peek_package.
|
|
||||||
//
|
|
||||||
// Reads the WHOLE file (via slurp), not a fixed prefix: cstage's
|
|
||||||
// peek_package scans line-by-line with fgets and no total cap, stopping
|
|
||||||
// at the first non-comment-non-blank line. A prior 2048-byte read cap
|
|
||||||
// here diverged from cstage on files whose `package` decl sits behind a
|
|
||||||
// >2048-byte comment header (strconv decimal/ftos/stof, memio) —
|
|
||||||
// returning nil and making the #16 D-i injection asymmetric (rule-10
|
|
||||||
// break, byte-id divergence in the regenerated combined). The corpus has
|
|
||||||
// no line >2047 chars, so a whole-file line scan matches cstage's
|
|
||||||
// per-line fgets byte-for-byte on every real input.
|
|
||||||
fn peekpackage(pathcs: *u8) *u8 = {
|
|
||||||
let bufp: *u8;
|
|
||||||
let nu: u64;
|
|
||||||
bufp, nu = slurp(pathcs);
|
|
||||||
if (bufp == nil) { return nil; };
|
|
||||||
let p: u64 = 0u64;
|
|
||||||
for (p < nu) {
|
|
||||||
let q: u64 = p;
|
|
||||||
for (q < nu) {
|
|
||||||
if (bufp[q] == 10u8) { break; }; // '\n'
|
|
||||||
q += 1u64;
|
|
||||||
};
|
|
||||||
let s: u64 = p;
|
|
||||||
for (s < q) {
|
|
||||||
if (bufp[s] != 32u8) {
|
|
||||||
if (bufp[s] != 9u8) { break; };
|
|
||||||
};
|
|
||||||
s += 1u64;
|
|
||||||
};
|
|
||||||
if (s < q) {
|
|
||||||
let line: str;
|
|
||||||
line.ptr = bufp + s;
|
|
||||||
line.len = (q - s): i32;
|
|
||||||
// hasprefix("//") subsumes the old s+1<q guard.
|
|
||||||
if (strings.hasprefix(line, "//")) {
|
|
||||||
p = q + 1u64;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// s+8<=q guard kept: hasprefix("package") needs only 7
|
|
||||||
// bytes, but the sep read at bufp[s+7] needs s+7 < q.
|
|
||||||
if (s + 8u64 <= q) {
|
|
||||||
if (strings.hasprefix(line, "package")) {
|
|
||||||
let sep: u8 = bufp[s + 7u64];
|
|
||||||
if (sep == 32u8) { }
|
|
||||||
else { if (sep != 9u8) { return nil; }; };
|
|
||||||
let t: u64 = s + 8u64;
|
|
||||||
for (t < q) {
|
|
||||||
if (bufp[t] != 32u8) {
|
|
||||||
if (bufp[t] != 9u8) { break; };
|
|
||||||
};
|
|
||||||
t += 1u64;
|
|
||||||
};
|
|
||||||
let start: u64 = t;
|
|
||||||
for (t < q) {
|
|
||||||
let ch: u8 = bufp[t];
|
|
||||||
let isalpha: bool = false;
|
|
||||||
if (ch >= 97u8) { if (ch <= 122u8) { isalpha = true; }; };
|
|
||||||
if (ch >= 65u8) { if (ch <= 90u8) { isalpha = true; }; };
|
|
||||||
if (ch >= 48u8) { if (ch <= 57u8) { isalpha = true; }; };
|
|
||||||
if (ch == 95u8) { isalpha = true; };
|
|
||||||
if (!isalpha) { break; };
|
|
||||||
t += 1u64;
|
|
||||||
};
|
|
||||||
let plen: u64 = t - start;
|
|
||||||
if (plen == 0u64) { return nil; };
|
|
||||||
let r: []u8 = alloc([], plen + 1u64)!;
|
|
||||||
let k: u64 = 0u64;
|
|
||||||
for (k < plen) { r[k] = bufp[start + k]; k += 1u64; };
|
|
||||||
r[plen] = 0u8;
|
|
||||||
return r.ptr;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return nil;
|
|
||||||
};
|
|
||||||
p = q + 1u64;
|
|
||||||
};
|
|
||||||
return nil;
|
|
||||||
};
|
|
||||||
|
|
||||||
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
|
|
||||||
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
|
|
||||||
// genuinely-missing import from one satisfied by an INLINE package in the
|
|
||||||
// same single-file multi-package unit (`package aa; ... package main;
|
|
||||||
// import aa;`). Scans EVERY line (comment-skip) — not just the first
|
|
||||||
// package decl (peekpackage stops there). Decision byte-identical to
|
|
||||||
// cstage so the skip/fatal choice + driver output match (rule 10).
|
|
||||||
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
|
|
||||||
let p: u64 = 0u64;
|
|
||||||
for (p < buflen) {
|
|
||||||
let q: u64 = p;
|
|
||||||
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
|
|
||||||
let s: u64 = p;
|
|
||||||
for (s < q) {
|
|
||||||
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
|
|
||||||
s += 1u64;
|
|
||||||
};
|
|
||||||
if (s < q) {
|
|
||||||
let line: str;
|
|
||||||
line.ptr = buf + s;
|
|
||||||
line.len = (q - s): i32;
|
|
||||||
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
|
|
||||||
if (s + 8u64 <= q) {
|
|
||||||
if (strings.hasprefix(line, "package")) {
|
|
||||||
let sep: u8 = buf[s + 7u64];
|
|
||||||
let oksep: bool = false;
|
|
||||||
if (sep == 32u8) { oksep = true; }
|
|
||||||
else { if (sep == 9u8) { oksep = true; }; };
|
|
||||||
if (oksep) {
|
|
||||||
let t: u64 = s + 8u64;
|
|
||||||
for (t < q) {
|
|
||||||
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
|
|
||||||
t += 1u64;
|
|
||||||
};
|
|
||||||
let m: u64 = 0u64;
|
|
||||||
let eq: bool = true;
|
|
||||||
for (m < leafn) {
|
|
||||||
if (t + m >= q) { eq = false; break; };
|
|
||||||
if (buf[t + m] != leafp[m]) { eq = false; break; };
|
|
||||||
m += 1u64;
|
|
||||||
};
|
|
||||||
if (eq) {
|
|
||||||
let after: u64 = t + leafn;
|
|
||||||
let term: bool = false;
|
|
||||||
if (after >= q) { term = true; }
|
|
||||||
else {
|
|
||||||
let c: u8 = buf[after];
|
|
||||||
if (c == 59u8) { term = true; } // ';'
|
|
||||||
else { if (c == 32u8) { term = true; }
|
|
||||||
else { if (c == 9u8) { term = true; }; }; };
|
|
||||||
};
|
|
||||||
if (term) { return true; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
p = q + 1u64;
|
|
||||||
};
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Strict-same-package error helper. Bundled here per task #22
|
|
||||||
// brief — failure mode is dir-enum's own.
|
|
||||||
fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
|
|
||||||
cerr("ww: ");
|
|
||||||
os.write(2, file, cstrlen(file));
|
|
||||||
cerr(": package ");
|
|
||||||
os.write(2, pkg, cstrlen(pkg));
|
|
||||||
cerr(" differs from ");
|
|
||||||
os.write(2, dirpkg, cstrlen(dirpkg));
|
|
||||||
cerr(" in same module dir ");
|
|
||||||
os.write(2, dirpath, cstrlen(dirpath));
|
|
||||||
cerr("\n");
|
|
||||||
os.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// expanddir — enumerate <dirpath>/*.ww (skip *test.ww and
|
|
||||||
// *.combined.ww), byte-sort, recurse into each. Mirrors
|
|
||||||
// ref/hare/hare/module/srcs.ha:183 `_findsrcs` minus tag handling.
|
|
||||||
// The visited set keys on concrete file paths so multi-file modules
|
|
||||||
// are pulled once. Strict-same-package: all enumerated files must
|
|
||||||
// declare the same `package <name>;` (task #23 subset; failure
|
|
||||||
// mode native to dir-enum).
|
|
||||||
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
|
|
||||||
let names: **u8;
|
|
||||||
let n: i32;
|
|
||||||
names, n = enumeratedir(dirpath);
|
|
||||||
let dlen: u64 = cstrlen(dirpath);
|
|
||||||
let dirpkg: *u8 = nil;
|
|
||||||
let i: i32 = 0;
|
|
||||||
for (i < n) {
|
|
||||||
let nlen: u64 = cstrlen(names[i]);
|
|
||||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
|
||||||
let k: u64 = 0u64;
|
|
||||||
for (k < dlen) { fp[k] = dirpath[k]; k += 1u64; };
|
|
||||||
fp[dlen] = 47u8; // '/'
|
|
||||||
k = 0u64;
|
|
||||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
|
||||||
fp[dlen + 1u64 + nlen] = 0u8;
|
|
||||||
let pkg: *u8 = peekpackage(fp.ptr);
|
|
||||||
if (pkg != nil) {
|
|
||||||
if (dirpkg == nil) { dirpkg = pkg; }
|
|
||||||
else { if (!cstreq(dirpkg, pkg)) {
|
|
||||||
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
|
|
||||||
}; };
|
|
||||||
};
|
|
||||||
expand(c, fp.ptr, modpath);
|
|
||||||
i += 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Build pipeline ---------------------------------------------------
|
// ---- Build pipeline ---------------------------------------------------
|
||||||
|
|
||||||
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
|
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
|
||||||
@@ -933,6 +633,68 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
|
||||||
|
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
|
||||||
|
// genuinely-missing import from one satisfied by an INLINE package in the
|
||||||
|
// same single-file multi-package unit (`package aa; ... package main;
|
||||||
|
// import aa;`). Scans EVERY line (comment-skip) — not just the first
|
||||||
|
// package decl (peekpackage stops there). Decision byte-identical to
|
||||||
|
// cstage so the skip/fatal choice + driver output match (rule 10).
|
||||||
|
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
|
||||||
|
let p: u64 = 0u64;
|
||||||
|
for (p < buflen) {
|
||||||
|
let q: u64 = p;
|
||||||
|
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
|
||||||
|
let s: u64 = p;
|
||||||
|
for (s < q) {
|
||||||
|
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
|
||||||
|
s += 1u64;
|
||||||
|
};
|
||||||
|
if (s < q) {
|
||||||
|
let line: str;
|
||||||
|
line.ptr = buf + s;
|
||||||
|
line.len = (q - s): i32;
|
||||||
|
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
|
||||||
|
if (s + 8u64 <= q) {
|
||||||
|
if (strings.hasprefix(line, "package")) {
|
||||||
|
let sep: u8 = buf[s + 7u64];
|
||||||
|
let oksep: bool = false;
|
||||||
|
if (sep == 32u8) { oksep = true; }
|
||||||
|
else { if (sep == 9u8) { oksep = true; }; };
|
||||||
|
if (oksep) {
|
||||||
|
let t: u64 = s + 8u64;
|
||||||
|
for (t < q) {
|
||||||
|
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
|
||||||
|
t += 1u64;
|
||||||
|
};
|
||||||
|
let m: u64 = 0u64;
|
||||||
|
let eq: bool = true;
|
||||||
|
for (m < leafn) {
|
||||||
|
if (t + m >= q) { eq = false; break; };
|
||||||
|
if (buf[t + m] != leafp[m]) { eq = false; break; };
|
||||||
|
m += 1u64;
|
||||||
|
};
|
||||||
|
if (eq) {
|
||||||
|
let after: u64 = t + leafn;
|
||||||
|
let term: bool = false;
|
||||||
|
if (after >= q) { term = true; }
|
||||||
|
else {
|
||||||
|
let c: u8 = buf[after];
|
||||||
|
if (c == 59u8) { term = true; } // ';'
|
||||||
|
else { if (c == 32u8) { term = true; }
|
||||||
|
else { if (c == 9u8) { term = true; }; }; };
|
||||||
|
};
|
||||||
|
if (term) { return true; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
p = q + 1u64;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
|
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
|
||||||
// is a package boundary: add as a direct dep of pi. A FILE import is an
|
// is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||||
// intra-package split: fold its imports into pi. Mirrors cstage
|
// intra-package split: fold its imports into pi. Mirrors cstage
|
||||||
@@ -989,6 +751,29 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// #16 ENFORCE-driver (rob A): a locate-miss is legal
|
||||||
|
// when the package is defined INLINE in the same unit
|
||||||
|
// (single-file multi-package). leaf = last dotted
|
||||||
|
// component; inline `package <leaf>` -> skip (the
|
||||||
|
// checker binds it), else fatal. E3-C1: the combined
|
||||||
|
// path that owned the genuine-missing case is gone, so
|
||||||
|
// the sep producer enforces it here (INV-2). cstage
|
||||||
|
// twin; fatal text identical.
|
||||||
|
let lstart: u64 = 0u64;
|
||||||
|
let lk: u64 = 0u64;
|
||||||
|
for (lk < idn) {
|
||||||
|
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
|
||||||
|
lk += 1u64;
|
||||||
|
};
|
||||||
|
let leafp: *u8 = idp + lstart;
|
||||||
|
let leafn: u64 = idn - lstart;
|
||||||
|
if (!unithaspackage(bufp, blen, leafp, leafn)) {
|
||||||
|
cerr("ww: cannot find package ");
|
||||||
|
os.write(2, idp, idn);
|
||||||
|
cerr("\n");
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
i = j + 1u64;
|
i = j + 1u64;
|
||||||
@@ -1786,235 +1571,6 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// buildone — compile `src` (file or directory) into the executable
|
|
||||||
// named `out`.
|
|
||||||
// selfdir: NUL-terminated dir containing this driver and the
|
|
||||||
// wwstage tools (w6c_ww/w6a_ww/w6l_ww)
|
|
||||||
// src: NUL-terminated entry path (file or directory).
|
|
||||||
// entryisdir: non-zero when src is a module directory.
|
|
||||||
// out: NUL-terminated desired output path
|
|
||||||
// objstem: when non-nil, redirects the .s/.o/.combined.ww side
|
|
||||||
// files to live beside this stem instead of next to the
|
|
||||||
// source (T3 / task #15). `ww run` and `ww build -o` pass
|
|
||||||
// it so concurrent builds never share next-to-source fixed
|
|
||||||
// paths; nil keeps the old next-to-source layout (the make
|
|
||||||
// tracked-combined.ww regen + #110 gate depend on it). Twin
|
|
||||||
// of cmd/ww/main.c build_one's objstem.
|
|
||||||
// incs: NUL-terminated colon-list of -I dirs (may be empty)
|
|
||||||
// lf: extra linker flags (-L<dir>, -l<name>); may be nil
|
|
||||||
//
|
|
||||||
// The ww-side driver shells to the ww-side tools so a `ww_ww build`
|
|
||||||
// touches no C-built code at runtime. The C `ww` driver in cmd/ww/
|
|
||||||
// still drives the C-built w6c/w6a/w6l. Test 993 pins the two
|
|
||||||
// pipelines to byte-identical output on a corpus.
|
|
||||||
fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
|
|
||||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
|
||||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
|
||||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
|
||||||
|
|
||||||
// Default lib search path: <selfdir>/../../lib
|
|
||||||
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
dotdotlib.len = os.PATH_MAX;
|
|
||||||
{
|
|
||||||
let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir);
|
|
||||||
off = strinto(dotdotlib.ptr, off, "/../../lib");
|
|
||||||
cstrseal(dotdotlib.ptr, off);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compute the source directory. For a file entry: bytes of `src`
|
|
||||||
// up to the last '/' (or "." when src has no '/'). For a dir
|
|
||||||
// entry: the dir itself (less trailing slashes). Hare's CWD-first
|
|
||||||
// convention assumes you're running from the module dir; our
|
|
||||||
// wrappers don't cd, so dirname(src) stands in as the closest
|
|
||||||
// analog. Source-dir wins ties over the system path (cc -I.).
|
|
||||||
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
srcd.len = os.PATH_MAX;
|
|
||||||
if (entryisdir != 0) {
|
|
||||||
let slen: u64 = cstrlen(src);
|
|
||||||
let k: u64 = 0u64;
|
|
||||||
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
|
|
||||||
for (slen > 1u64) {
|
|
||||||
if (srcd[slen - 1u64] != 47u8) { break; };
|
|
||||||
slen -= 1u64;
|
|
||||||
};
|
|
||||||
srcd[slen] = 0u8;
|
|
||||||
} else {
|
|
||||||
let slen: u64 = cstrlen(src);
|
|
||||||
let last: u64 = slen;
|
|
||||||
let found: bool = false;
|
|
||||||
let i: u64 = slen;
|
|
||||||
for (i > 0u64) {
|
|
||||||
i -= 1u64;
|
|
||||||
if (src[i] == 47u8) { // '/'
|
|
||||||
last = i;
|
|
||||||
found = true;
|
|
||||||
i = 0u64;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (found) {
|
|
||||||
let k: u64 = 0u64;
|
|
||||||
for (k < last) { srcd[k] = src[k]; k += 1u64; };
|
|
||||||
srcd[last] = 0u8;
|
|
||||||
} else {
|
|
||||||
srcd[0] = 46u8; // '.'
|
|
||||||
srcd[1] = 0u8;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compose searchpath: srcd + ':' + incs + ':' + dotdotlib.
|
|
||||||
let searchpath: []u8 = alloc([], (os.PATH_MAX: u64) * 3u64)!;
|
|
||||||
searchpath.len = ((os.PATH_MAX: u64) * 3u64): i32;
|
|
||||||
{
|
|
||||||
let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr);
|
|
||||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
|
||||||
if (incs[0u64] != 0u8) {
|
|
||||||
off = cstrinto(searchpath.ptr, off, incs);
|
|
||||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
|
||||||
};
|
|
||||||
off = cstrinto(searchpath.ptr, off, dotdotlib.ptr);
|
|
||||||
cstrseal(searchpath.ptr, off);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stem for .s/.o/.combined.ww side files. Dir entry: <dir>/<base>;
|
|
||||||
// file entry: src stripped of .ww.
|
|
||||||
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
stem.len = os.PATH_MAX;
|
|
||||||
if (entryisdir != 0) {
|
|
||||||
let dlen: u64 = cstrlen(srcd.ptr);
|
|
||||||
let bo: u64 = basenameoff(srcd.ptr, dlen);
|
|
||||||
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
|
|
||||||
stem[off] = 47u8; off += 1u64; // '/'
|
|
||||||
let i: u64 = bo;
|
|
||||||
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
|
|
||||||
cstrseal(stem.ptr, off);
|
|
||||||
} else {
|
|
||||||
makestem(stem.ptr, src);
|
|
||||||
};
|
|
||||||
let effstem: *u8 = stem.ptr;
|
|
||||||
if (objstem != nil) { effstem = objstem; };
|
|
||||||
let asmf: *u8 = appendlit(effstem, ".s");
|
|
||||||
let objf: *u8 = appendlit(effstem, ".o");
|
|
||||||
let combined: *u8 = appendlit(effstem, ".combined.ww");
|
|
||||||
|
|
||||||
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
|
|
||||||
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
libwwrt.len = os.PATH_MAX;
|
|
||||||
{
|
|
||||||
let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir);
|
|
||||||
off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a");
|
|
||||||
cstrseal(libwwrt.ptr, off);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 1: expand imports into the combined file. Dir entry →
|
|
||||||
// enumerate the module dir; file entry → start at the file.
|
|
||||||
let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
|
||||||
if (cf < 0) {
|
|
||||||
cerr("ww: cannot open combined\n");
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
{
|
|
||||||
let c: expctx;
|
|
||||||
c.out = cf;
|
|
||||||
c.dirs = searchpath.ptr;
|
|
||||||
c.visit = nil;
|
|
||||||
// #17 auto-bundle lib/test: the -T synth's main calls lib/test's
|
|
||||||
// run(), but @test files don't `import test;`. Pull it like an
|
|
||||||
// implicit import through the same locate+expand path (the visit
|
|
||||||
// set dedupes a fixture that imports it explicitly). cstage twin
|
|
||||||
// in cmd/ww/main.c buildone.
|
|
||||||
if (istest != 0) {
|
|
||||||
let td: i32 = 0;
|
|
||||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
|
||||||
if (tp != nil) {
|
|
||||||
if (td != 0) { expanddir(&c, tp, "test"); }
|
|
||||||
else { expand(&c, tp, ""); };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
|
|
||||||
else { expand(&c, src, ""); };
|
|
||||||
};
|
|
||||||
os.close(cf);
|
|
||||||
|
|
||||||
// Step 2: w6c [-T] -o <stem>.s <stem>.combined.ww
|
|
||||||
{
|
|
||||||
let argv: []*u8 = alloc([], 6u64)!;
|
|
||||||
let p: i32 = 0;
|
|
||||||
argv[p] = "w6c\0".ptr; p += 1;
|
|
||||||
if (istest != 0) { argv[p] = "-T\0".ptr; p += 1; };
|
|
||||||
argv[p] = "-o\0".ptr; p += 1;
|
|
||||||
argv[p] = asmf; p += 1;
|
|
||||||
argv[p] = combined; p += 1;
|
|
||||||
argv[p] = nil; p += 1;
|
|
||||||
argv.len = p;
|
|
||||||
if (procrun(c6, argv.ptr) != 0) {
|
|
||||||
cerr("ww: w6c failed\n");
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 3: w6a -o <stem>.o <stem>.s
|
|
||||||
{
|
|
||||||
let argv: []*u8 = alloc([], 5u64)!;
|
|
||||||
argv.len = 5;
|
|
||||||
argv[0] = "w6a\0".ptr;
|
|
||||||
argv[1] = "-o\0".ptr;
|
|
||||||
argv[2] = objf;
|
|
||||||
argv[3] = asmf;
|
|
||||||
argv[4] = nil;
|
|
||||||
if (procrun(a6, argv.ptr) != 0) {
|
|
||||||
cerr("ww: w6a failed\n");
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 4: w6l -o <out> <stem>.o libwwrt.a [-L<dir>...] [-l<name>...]
|
|
||||||
{
|
|
||||||
let nldirs: i32 = 0;
|
|
||||||
let nllibs: i32 = 0;
|
|
||||||
let ldirs: **u8 = nil;
|
|
||||||
let llibs: **u8 = nil;
|
|
||||||
if (lf != nil) {
|
|
||||||
nldirs = lf.nlibdirs;
|
|
||||||
nllibs = lf.nlibs;
|
|
||||||
ldirs = lf.libdirs;
|
|
||||||
llibs = lf.libs;
|
|
||||||
};
|
|
||||||
// argv slots: 5 fixed (w6l, -o, out, objf, libwwrt)
|
|
||||||
// + 2 * nlibdirs (-L, dir)
|
|
||||||
// + 2 * nlibs (-l, name)
|
|
||||||
// + 1 nil terminator.
|
|
||||||
let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1;
|
|
||||||
let argv: []*u8 = alloc([], total: u64)!;
|
|
||||||
argv.len = total;
|
|
||||||
argv[0] = "w6l\0".ptr;
|
|
||||||
argv[1] = "-o\0".ptr;
|
|
||||||
argv[2] = out;
|
|
||||||
argv[3] = objf;
|
|
||||||
argv[4] = libwwrt.ptr;
|
|
||||||
let pos: i32 = 5;
|
|
||||||
let k: i32 = 0;
|
|
||||||
for (k < nldirs) {
|
|
||||||
argv[pos] = "-L\0".ptr;
|
|
||||||
argv[pos + 1] = ldirs[k];
|
|
||||||
pos += 2;
|
|
||||||
k += 1;
|
|
||||||
};
|
|
||||||
k = 0;
|
|
||||||
for (k < nllibs) {
|
|
||||||
argv[pos] = "-l\0".ptr;
|
|
||||||
argv[pos + 1] = llibs[k];
|
|
||||||
pos += 2;
|
|
||||||
k += 1;
|
|
||||||
};
|
|
||||||
argv[pos] = nil;
|
|
||||||
if (procrun(l6, argv.ptr) != 0) {
|
|
||||||
cerr("ww: w6l failed\n");
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Module-by-name resolution ----------------------------------------
|
// ---- Module-by-name resolution ----------------------------------------
|
||||||
//
|
//
|
||||||
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
|
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
|
||||||
@@ -2154,7 +1710,6 @@ fn defaultoutpath(src: *u8) *u8 = {
|
|||||||
|
|
||||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||||
let src: *u8 = nil;
|
let src: *u8 = nil;
|
||||||
let wantsep: i32 = 0; // --sep: M3-tail separate-compilation path
|
|
||||||
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
||||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||||
@@ -2173,8 +1728,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
for (i < argc) {
|
for (i < argc) {
|
||||||
let p: *u8 = argv[i];
|
let p: *u8 = argv[i];
|
||||||
if (p[0u64] == 45u8) { // '-'
|
if (p[0u64] == 45u8) { // '-'
|
||||||
if (cstreqlit(p, "--sep")) { // M3-tail separate-compile
|
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||||
wantsep = 1;
|
|
||||||
} else { if (p[1u64] == 73u8) { // '-I'
|
} else { if (p[1u64] == 73u8) { // '-I'
|
||||||
let dir: *u8 = nil;
|
let dir: *u8 = nil;
|
||||||
if (p[2u64] != 0u8) {
|
if (p[2u64] != 0u8) {
|
||||||
@@ -2290,10 +1844,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.nlibdirs = nlibdirs;
|
lf.nlibdirs = nlibdirs;
|
||||||
lf.libs = libs.ptr;
|
lf.libs = libs.ptr;
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
if (wantsep != 0) {
|
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
|
||||||
};
|
|
||||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
|
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
|
||||||
@@ -2348,9 +1899,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
else {
|
else {
|
||||||
let p: *u8 = argv[i];
|
let p: *u8 = argv[i];
|
||||||
if (p[0u64] == 45u8) {
|
if (p[0u64] == 45u8) {
|
||||||
if (cstreqlit(p, "--sep")) { // build-only; run rejects (rule 10)
|
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||||
cerr("ww run: --sep is only valid with build\n");
|
|
||||||
return 2;
|
|
||||||
} else { if (p[1u64] == 73u8) {
|
} else { if (p[1u64] == 73u8) {
|
||||||
let dir: *u8 = nil;
|
let dir: *u8 = nil;
|
||||||
if (p[2u64] != 0u8) {
|
if (p[2u64] != 0u8) {
|
||||||
@@ -2452,7 +2001,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
// objstem = tmp → intermediates at /tmp/ww_run_<pid>.{s,o,combined.ww},
|
// objstem = tmp → intermediates at /tmp/ww_run_<pid>.{s,o,combined.ww},
|
||||||
// never next to the source (T3).
|
// never next to the source (T3).
|
||||||
if (buildone(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
|
if (buildonesep(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
|
||||||
os.remove(pathstr(tmp.ptr));
|
os.remove(pathstr(tmp.ptr));
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -2482,7 +2031,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
// directory: open the dir, getdents64, build+run each *_test.ww,
|
// directory: open the dir, getdents64, build+run each *_test.ww,
|
||||||
// report ok/FAIL per file, return 0 iff all pass.
|
// report ok/FAIL per file, return 0 iff all pass.
|
||||||
|
|
||||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8, usesep: i32) i32 = {
|
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
tmp.len = os.PATH_MAX;
|
||||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||||
@@ -2496,19 +2045,13 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
|||||||
makeruntmp(tmp.ptr);
|
makeruntmp(tmp.ptr);
|
||||||
outp = tmp.ptr;
|
outp = tmp.ptr;
|
||||||
};
|
};
|
||||||
// #79 E1: --sep routes the test build through the separate-compilation
|
// E3-C1: separate compilation is the sole build path (task #87).
|
||||||
// producer (buildonesep, istest=1) instead of the amalgamator.
|
let lf: lflags;
|
||||||
let bres: i32 = 0;
|
lf.libdirs = nil;
|
||||||
if (usesep != 0) {
|
lf.nlibdirs = 0;
|
||||||
let lf: lflags;
|
lf.libs = nil;
|
||||||
lf.libdirs = nil;
|
lf.nlibs = 0;
|
||||||
lf.nlibdirs = 0;
|
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
||||||
lf.libs = nil;
|
|
||||||
lf.nlibs = 0;
|
|
||||||
bres = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
|
||||||
} else {
|
|
||||||
bres = buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32);
|
|
||||||
};
|
|
||||||
if (bres != 0) {
|
if (bres != 0) {
|
||||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||||
return 1;
|
return 1;
|
||||||
@@ -2576,7 +2119,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
|||||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
tmp.len = os.PATH_MAX;
|
||||||
makeruntmp(tmp.ptr);
|
makeruntmp(tmp.ptr);
|
||||||
let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
|
let bres: i32 = buildonesep(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
|
||||||
if (bres != 0) {
|
if (bres != 0) {
|
||||||
fail += 1;
|
fail += 1;
|
||||||
cerr("FAIL ");
|
cerr("FAIL ");
|
||||||
@@ -2639,16 +2182,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
// twin: cmd/ww/main.c do_test (error wording identical).
|
// twin: cmd/ww/main.c do_test (error wording identical).
|
||||||
let compileonly: i32 = 0;
|
let compileonly: i32 = 0;
|
||||||
let outstem: *u8 = nil;
|
let outstem: *u8 = nil;
|
||||||
// #79 E1: --sep routes a single-file test through the separate-
|
|
||||||
// compilation producer (buildonesep, istest=1). Dir-mode --sep is
|
|
||||||
// deferred to E2. cstage twin: do_test use_sep.
|
|
||||||
let usesep: i32 = 0;
|
|
||||||
let i: i32 = start;
|
let i: i32 = start;
|
||||||
for (i < argc) {
|
for (i < argc) {
|
||||||
let p: *u8 = argv[i];
|
let p: *u8 = argv[i];
|
||||||
if (p[0u64] == 45u8) { // '-'
|
if (p[0u64] == 45u8) { // '-'
|
||||||
if (cstreqlit(p, "--sep")) { // #79: separate-compile test
|
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||||
usesep = 1;
|
|
||||||
} else { if (p[1u64] == 73u8) { // '-I'
|
} else { if (p[1u64] == 73u8) { // '-I'
|
||||||
let dir: *u8 = nil;
|
let dir: *u8 = nil;
|
||||||
if (p[2u64] != 0u8) {
|
if (p[2u64] != 0u8) {
|
||||||
@@ -2698,7 +2236,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
// single-file mode: literal *.ww that exists
|
// single-file mode: literal *.ww that exists
|
||||||
if (cstrendswithlit(target, ".ww")) {
|
if (cstrendswithlit(target, ".ww")) {
|
||||||
if (os.access(pathstr(target), 0i32) == 0) {
|
if (os.access(pathstr(target), 0i32) == 0) {
|
||||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg, usesep);
|
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2706,11 +2244,6 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
cerr("ww test: -c/-o need a single test file\n");
|
cerr("ww test: -c/-o need a single test file\n");
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
// #79 E1: dir-mode --sep deferred to E2; single-file proves the path.
|
|
||||||
if (usesep != 0) {
|
|
||||||
cerr("ww test: --sep needs a single test file\n");
|
|
||||||
return 2;
|
|
||||||
};
|
|
||||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||||
if (patarg != nil) {
|
if (patarg != nil) {
|
||||||
|
|||||||
@@ -88,8 +88,12 @@ main(void)
|
|||||||
(void)tmp;
|
(void)tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* bad: mismatched package decls in same dir → strict-same-package
|
/* bad: mismatched package decls in same dir → import-path mismatch
|
||||||
* error. Both stages must surface "differs from" in stderr. */
|
* error. E3-C1: the amalgamator's strict-same-package "differs from"
|
||||||
|
* check is deleted; under sep w6c rejects the composed unit with
|
||||||
|
* "package <p> does not match import path <i>" (wwstage is terser per
|
||||||
|
* #68, so match the shared "does not match import path" substring).
|
||||||
|
* Both stages must surface it in stderr. */
|
||||||
{
|
{
|
||||||
const char *src = "test/wcc/data/direnum/bad_entry.ww";
|
const char *src = "test/wcc/data/direnum/bad_entry.ww";
|
||||||
snprintf(errp, sizeof errp, "/tmp/direnum_%d_bad.err", getpid());
|
snprintf(errp, sizeof errp, "/tmp/direnum_%d_bad.err", getpid());
|
||||||
@@ -102,8 +106,8 @@ main(void)
|
|||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n");
|
fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n");
|
||||||
fail++;
|
fail++;
|
||||||
} else if (!stderr_contains(errp, "differs from")) {
|
} else if (!stderr_contains(errp, "does not match import path")) {
|
||||||
fprintf(stderr, "737[bad-cstage]: stderr missing 'differs from'\n");
|
fprintf(stderr, "737[bad-cstage]: stderr missing 'does not match import path'\n");
|
||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
unlink(errp);
|
unlink(errp);
|
||||||
@@ -116,8 +120,8 @@ main(void)
|
|||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n");
|
fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n");
|
||||||
fail++;
|
fail++;
|
||||||
} else if (!stderr_contains(errp, "differs from")) {
|
} else if (!stderr_contains(errp, "does not match import path")) {
|
||||||
fprintf(stderr, "737[bad-wwstage]: stderr missing 'differs from'\n");
|
fprintf(stderr, "737[bad-wwstage]: stderr missing 'does not match import path'\n");
|
||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
unlink(errp);
|
unlink(errp);
|
||||||
@@ -129,7 +133,7 @@ main(void)
|
|||||||
* (#16): the old 2048-capped peek missed that decl, so the strict-
|
* (#16): the old 2048-capped peek missed that decl, so the strict-
|
||||||
* same-package check skipped the file and the mismatch went
|
* same-package check skipped the file and the mismatch went
|
||||||
* undetected (build wrongly succeeded); the whole-file peek finds it
|
* undetected (build wrongly succeeded); the whole-file peek finds it
|
||||||
* → "differs from". Both stages. (reviewer-batcha pin.) */
|
* → "does not match import path". Both stages. (reviewer-batcha pin.) */
|
||||||
{
|
{
|
||||||
const char *src = "test/wcc/data/direnum/bad_deep_entry.ww";
|
const char *src = "test/wcc/data/direnum/bad_deep_entry.ww";
|
||||||
snprintf(errp, sizeof errp, "/tmp/direnum_%d_baddeep.err", getpid());
|
snprintf(errp, sizeof errp, "/tmp/direnum_%d_baddeep.err", getpid());
|
||||||
@@ -142,8 +146,8 @@ main(void)
|
|||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
fprintf(stderr, "737[bad-deep-cstage]: expected build failure, succeeded\n");
|
fprintf(stderr, "737[bad-deep-cstage]: expected build failure, succeeded\n");
|
||||||
fail++;
|
fail++;
|
||||||
} else if (!stderr_contains(errp, "differs from")) {
|
} else if (!stderr_contains(errp, "does not match import path")) {
|
||||||
fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'differs from'\n");
|
fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'does not match import path'\n");
|
||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
unlink(errp);
|
unlink(errp);
|
||||||
@@ -156,8 +160,8 @@ main(void)
|
|||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
fprintf(stderr, "737[bad-deep-wwstage]: expected build failure, succeeded\n");
|
fprintf(stderr, "737[bad-deep-wwstage]: expected build failure, succeeded\n");
|
||||||
fail++;
|
fail++;
|
||||||
} else if (!stderr_contains(errp, "differs from")) {
|
} else if (!stderr_contains(errp, "does not match import path")) {
|
||||||
fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'differs from'\n");
|
fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'does not match import path'\n");
|
||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
unlink(errp);
|
unlink(errp);
|
||||||
|
|||||||
@@ -421,8 +421,12 @@ static int
|
|||||||
corpus_complete(const char *cwd)
|
corpus_complete(const char *cwd)
|
||||||
{
|
{
|
||||||
char cmd[2048];
|
char cmd[2048];
|
||||||
|
/* E3-C1: the flip routes lib @test builds through sep, which drops
|
||||||
|
* <stem>.sepwork/<pkg>.unit.ww scratch next to source; exclude it so
|
||||||
|
* the scan enrols real source dirs only, not generated work dirs. */
|
||||||
snprintf(cmd, sizeof cmd, "cd %s && find lib -name '*.ww' "
|
snprintf(cmd, sizeof cmd, "cd %s && find lib -name '*.ww' "
|
||||||
"! -name '*.combined.ww' | sed 's|/[^/]*$||' | sort -u", cwd);
|
"! -name '*.combined.ww' ! -path '*.sepwork/*' "
|
||||||
|
"| sed 's|/[^/]*$||' | sort -u", cwd);
|
||||||
FILE *p = popen(cmd, "r");
|
FILE *p = popen(cmd, "r");
|
||||||
if (!p) return -1;
|
if (!p) return -1;
|
||||||
char dir[512];
|
char dir[512];
|
||||||
|
|||||||
@@ -236,6 +236,18 @@ main(void)
|
|||||||
runwait(cmd);
|
runwait(cmd);
|
||||||
mkdir(td, 0755);
|
mkdir(td, 0755);
|
||||||
|
|
||||||
|
/* E3-C1: with the flip making sep the sole build path, every concurrent
|
||||||
|
* test now writes the global out/.pkgcache; this test's cs/ww per-package
|
||||||
|
* byte-id compare on the heavily-shared real lib pkgs (rt/time/os) then
|
||||||
|
* races a sibling building the same pkg. Pin a private per-pid cache so
|
||||||
|
* the cs and ww legs are isolated (system() builds inherit this env).
|
||||||
|
* pkgcache writes are non-atomic (cp -f), the real torn-read bug; this
|
||||||
|
* hermetic isolation is correct standalone, pending #104 (atomic
|
||||||
|
* temp+rename, both stages). */
|
||||||
|
char cachedir[80];
|
||||||
|
snprintf(cachedir, sizeof cachedir, "%s/pkgcache", td);
|
||||||
|
setenv("WW_PKGCACHE", cachedir, 1);
|
||||||
|
|
||||||
char rootww[1024];
|
char rootww[1024];
|
||||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||||
if (write_file(rootww, root_src)) { fail++; goto out; }
|
if (write_file(rootww, root_src)) { fail++; goto out; }
|
||||||
@@ -339,13 +351,11 @@ main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* run --sep symmetry (#46 c3): `run` has no sep-compile-then-run
|
/* run --sep symmetry: E3-C1 makes sep the sole compile path, so
|
||||||
* path (out of commit-3 scope), so BOTH stages must LOUD-REJECT
|
* `run` now genuinely sep-compiles-then-runs (the old "no sep run
|
||||||
* `run --sep` identically (rule 10) — not one silently ignore it.
|
* path → loud-reject exit 2" is gone with the amalgamator). BOTH
|
||||||
* The reject exits 2 (the driver's usage/flag-error code); assert
|
* stages must build+run the cross-boundary fixture identically
|
||||||
* cs and ww agree AND actually rejected (exit 2, not a build+run
|
* (rule 10) and exit EXPECT_EXIT — `--sep` is an accepted no-op. */
|
||||||
* that happened to exit nonzero). This row is the gate-visible
|
|
||||||
* guard whose absence let the divergence hide. */
|
|
||||||
{
|
{
|
||||||
int rc_cs, rc_ww;
|
int rc_cs, rc_ww;
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
@@ -354,9 +364,10 @@ main(void)
|
|||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/ww_ww run --sep %s >/dev/null 2>&1", bin, rootww);
|
"%s/ww_ww run --sep %s >/dev/null 2>&1", bin, rootww);
|
||||||
rc_ww = runwait(cmd);
|
rc_ww = runwait(cmd);
|
||||||
if (rc_cs != rc_ww || rc_cs != 2) {
|
if (rc_cs != rc_ww || rc_cs != EXPECT_EXIT) {
|
||||||
fprintf(stderr, "sepbuild FAIL: run --sep cs=%d ww=%d "
|
fprintf(stderr, "sepbuild FAIL: run --sep cs=%d ww=%d "
|
||||||
"(both must loud-reject, exit 2)\n", rc_cs, rc_ww);
|
"(both must run via sep, exit %d)\n", rc_cs, rc_ww,
|
||||||
|
EXPECT_EXIT);
|
||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,19 @@
|
|||||||
* The shadow row was RED pre-c1 (ww resolved xa's !i8 -> exit 1, the wrong
|
* The shadow row was RED pre-c1 (ww resolved xa's !i8 -> exit 1, the wrong
|
||||||
* module's type under rc=0). The noshadow control pins the non-shadow path
|
* module's type under rc=0). The noshadow control pins the non-shadow path
|
||||||
* (scopelookupprefer lands on the SK_TYPE directly) so the fix can't
|
* (scopelookupprefer lands on the SK_TYPE directly) so the fix can't
|
||||||
* perturb it. Single-file multi-package source is the sanctioned shape
|
* perturb it.
|
||||||
* (cmd/ww/main.c).
|
*
|
||||||
|
* E3-C1 retarget: the single-file multi-package source the original used
|
||||||
|
* is the amalgamator shape, deleted with the flip (ww is now strictly
|
||||||
|
* dir=module). Re-expressed as a dir-package tree: xb/ and xa/ both export
|
||||||
|
* `type invalid`, and xb IMPORTS xa so both `invalid` leaves are in scope
|
||||||
|
* during xb's sep-compile — the exact two-module same-leaf collision the
|
||||||
|
* scopelookuptype mod-preference fix governs. A regression flips to xa's
|
||||||
|
* !i8 -> exit 1.
|
||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -45,62 +50,70 @@ runwait(const char *cmd)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct row { const char *label; const char *src; int want_exit; };
|
/* Only xb's param name varies between rows; xa and the root are shared.
|
||||||
|
* `xb_param` is the parameter spelling: "invalid" shadows the type leaf,
|
||||||
|
* "x" does not. */
|
||||||
|
struct row { const char *label; const char *xb_param; int want_exit; };
|
||||||
|
|
||||||
static const struct row rows[] = {
|
static const struct row rows[] = {
|
||||||
{ "shadow",
|
{ "shadow", "invalid", 8 },
|
||||||
"package xb;\n"
|
{ "noshadow", "x", 8 },
|
||||||
"export type invalid = !i64;\n"
|
|
||||||
"export fn f(invalid: i32) i64 = { return size(invalid): i64; };\n"
|
|
||||||
"package xa;\n"
|
|
||||||
"export type invalid = !i8;\n"
|
|
||||||
"package main;\n"
|
|
||||||
"import xb;\n"
|
|
||||||
"import xa;\n"
|
|
||||||
"fn main() int = { return xb.f(0): int; };\n",
|
|
||||||
8 },
|
|
||||||
|
|
||||||
{ "noshadow",
|
|
||||||
"package xb;\n"
|
|
||||||
"export type invalid = !i64;\n"
|
|
||||||
"export fn f(x: i32) i64 = { return size(invalid): i64; };\n"
|
|
||||||
"package xa;\n"
|
|
||||||
"export type invalid = !i8;\n"
|
|
||||||
"package main;\n"
|
|
||||||
"import xb;\n"
|
|
||||||
"import xa;\n"
|
|
||||||
"fn main() int = { return xb.f(0): int; };\n",
|
|
||||||
8 },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
write_file(const char *path, const char *content)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(content, f);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_build(const char *driver, const struct row *r, int i)
|
run_build(const char *driver, const struct row *r, int i)
|
||||||
{
|
{
|
||||||
char src[64], tmpdir[64], cmd[1024];
|
char dir[64], sub[128], path[256], xbsrc[512], cmd[1024];
|
||||||
snprintf(src, sizeof src, "/tmp/sltp_%d_%d.ww", getpid(), i);
|
snprintf(dir, sizeof dir, "/tmp/sltp_%d_d_%d", getpid(), i);
|
||||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/sltp_%d_d_%d", getpid(), i);
|
|
||||||
|
|
||||||
FILE *f = fopen(src, "wb");
|
snprintf(cmd, sizeof cmd, "rm -rf %s && mkdir -p %s/xa %s/xb",
|
||||||
if (!f) return -2;
|
dir, dir, dir);
|
||||||
fputs(r->src, f);
|
if (runwait(cmd) != 0) return -2;
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
mkdir(tmpdir, 0755);
|
snprintf(path, sizeof path, "%s/xa/xa.ww", dir);
|
||||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
if (write_file(path, "package xa;\nexport type invalid = !i8;\n"))
|
||||||
tmpdir, driver, src);
|
return -2;
|
||||||
|
|
||||||
|
snprintf(sub, sizeof sub, "%s/xb/xb.ww", dir);
|
||||||
|
snprintf(xbsrc, sizeof xbsrc,
|
||||||
|
"package xb;\n"
|
||||||
|
"import xa;\n"
|
||||||
|
"export type invalid = !i64;\n"
|
||||||
|
"export fn f(%s: i32) i64 = { return size(invalid): i64; };\n",
|
||||||
|
r->xb_param);
|
||||||
|
if (write_file(sub, xbsrc)) return -2;
|
||||||
|
|
||||||
|
snprintf(path, sizeof path, "%s/root.ww", dir);
|
||||||
|
if (write_file(path,
|
||||||
|
"package main;\n"
|
||||||
|
"import xb;\n"
|
||||||
|
"import xa;\n"
|
||||||
|
"fn main() int = { return xb.f(0): int; };\n"))
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
/* canonical dir-package build: cd into the package root, build the
|
||||||
|
* root unit; the binary lands in cwd (#22 dir=module). */
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s build root.ww 2>/dev/null",
|
||||||
|
dir, driver);
|
||||||
int brc = runwait(cmd);
|
int brc = runwait(cmd);
|
||||||
|
|
||||||
const char *base = strrchr(src, '/');
|
|
||||||
base = base ? base + 1 : src;
|
|
||||||
char outbin[128];
|
char outbin[128];
|
||||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
snprintf(outbin, sizeof outbin, "%s/root", dir);
|
||||||
char *dot = strrchr(outbin, '.');
|
|
||||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
|
||||||
|
|
||||||
int got = -1;
|
int got = -1;
|
||||||
if (brc == 0) got = runwait(outbin);
|
if (brc == 0) got = runwait(outbin);
|
||||||
|
|
||||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||||
|
runwait(cmd);
|
||||||
return brc == 0 ? got : -1;
|
return brc == 0 ? got : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user