cmd: build directory package test variants

This commit is contained in:
2026-08-12 03:46:47 +09:00
parent e59881cb30
commit 9862f0c05f
4 changed files with 914 additions and 270 deletions

View File

@@ -238,79 +238,226 @@ strs_cmp(const void *a, const void *b)
return strcmp(sa, sb);
}
/* Go's contract: only *_test.ww is a test source. A line-leading @test
* declaration anywhere else would be silently dropped by a non-T build
* (#6, the Hare model), so directory enumeration rejects it loudly. */
static int sep_slurp(const char*, char**, u64*);
/* Go's contract: only *_test.ww is a test source. Ask the compiler parser,
* rather than a textual attribute scan, whether a production source contains
* @test; otherwise valid whitespace/comments could silently drop a test. */
static int
file_has_line_test(const char *path)
source_has_test_decl(const char *path)
{
char line[4096];
FILE *f = fopen(path, "rb");
if (f == NULL) return 0;
int found = 0;
while (fgets(line, sizeof line, f) != NULL) {
char *p = line;
while (*p == ' ' || *p == '\t' || *p == '\r') p++;
if (strncmp(p, "@test", 5) == 0
&& (p[5] == ' ' || p[5] == '\t')) {
found = 1;
break;
}
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) return -1;
/* Keep directory-loader package-clause diagnostics stable. The full
* parser reports its language-level "missing package clause" first;
* the imports-only pass owns the package-loader wording and also avoids
* stage-specific recovery diagnostics for a malformed clause. */
Arena *ia = newarena();
Lex il;
Parser ip;
lexinit(&il, ia, path, buf, len);
parserinit(&ip, ia, &il);
Node *imports = parseimports(&ip);
if (il.errs || ip.errs) {
freearena(ia);
free(buf);
return -1;
}
fclose(f);
if (imports->module == NULL) {
Pos pp = { path, 1, 1 };
errorf(pp, "invalid or missing package clause");
freearena(ia);
free(buf);
return -1;
}
freearena(ia);
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, path, buf, len);
parserinit(&p, a, &l);
Node *file = parsefile(&p);
if (l.errs || p.errs) {
freearena(a);
free(buf);
return -1;
}
int found = 0;
for (Node *d = file->list; d != NULL && !found; d = d->next)
if (d->kind == N_FNDECL)
for (Node *at = d->attr; at != NULL; at = at->next)
if (at->str != NULL && strcmp(at->str, "test") == 0) {
found = 1;
break;
}
freearena(a);
free(buf);
return found;
}
/* A line-leading @test in a production source is diagnosed here and
* returns -2. This is the sole directory-membership discovery path; the
* owning seppkg retains the returned list. */
#define SEP_VARIANT_PRODUCTION 0
#define SEP_VARIANT_SAME_TEST 1
#define SEP_VARIANT_EXTERNAL 2
#define SEP_TEST_SUPPORT_MODULE "__wwtest"
/* Test-file package classification uses the compiler's imports-only parser.
* The coordinator chooses variants, but the command owns which real source
* paths enter a package compilation. */
static int
enumerate_dir_ww(const char *dirpath, char ***out_files)
source_package_name(const char *path, char *out, size_t outsz)
{
char *buf;
u64 len;
if (sep_slurp(path, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path);
return -1;
}
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, path, buf, len);
parserinit(&p, a, &l);
Node *imports = parseimports(&p);
if (l.errs || p.errs) {
freearena(a);
free(buf);
return -1;
}
if (imports->module == NULL) {
Pos pp = { path, 1, 1 };
errorf(pp, "invalid or missing package clause");
freearena(a);
free(buf);
return -1;
}
if (strlen(imports->module) >= outsz) {
errorf(imports->pos, "package name is too long");
freearena(a);
free(buf);
return -1;
}
snprintf(out, outsz, "%s", imports->module);
freearena(a);
free(buf);
return 0;
}
static int
source_list_add(char ***list, int *n, int *cap, const char *path)
{
if (*n + 1 > *cap) {
int ncap = *cap ? *cap * 2 : 8;
char **next = realloc(*list, (size_t)ncap * sizeof *next);
if (next == NULL) return -1;
*list = next;
*cap = ncap;
}
char *copy = strdup(path);
if (copy == NULL) return -1;
(*list)[(*n)++] = copy;
return 0;
}
static void
source_list_free(char **list, int n)
{
for (int i = 0; i < n; i++) free(list[i]);
free(list);
}
/* Production packages select production files only. A same-package test root
* selects production files followed by matching same-package test files; an
* external root selects only matching external-test files. Each partition is
* byte-sorted so compiler test discovery is deterministic without generated
* package amalgamation. */
static int
enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
char ***out_files)
{
DIR *d = opendir(dirpath);
if (d == NULL) { *out_files = NULL; return -1; }
char **arr = NULL;
int n = 0, cap = 0;
char **prod = NULL, **tests = NULL;
int nprod = 0, capprod = 0, ntests = 0, captests = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
if (nl <= 3) continue;
if (strcmp(nm + nl - 3, ".ww") != 0) continue;
int is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0;
if (variant == SEP_VARIANT_PRODUCTION && is_test) continue;
if (variant == SEP_VARIANT_EXTERNAL && !is_test) continue;
char path[2048];
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
if (nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
continue;
struct stat st;
if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
fprintf(stderr,
"ww: %s: package source is not a regular file\n", path);
for (int i = 0; i < n; i++) free(arr[i]);
free(arr);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
closedir(d);
*out_files = NULL;
return -2;
}
if (file_has_line_test(path)) {
int has_test = !is_test ? source_has_test_decl(path) : 0;
if (has_test < 0) {
source_list_free(prod, nprod);
source_list_free(tests, ntests);
closedir(d);
*out_files = NULL;
return -2;
}
if (has_test > 0) {
fprintf(stderr,
"ww: %s: @test declaration outside *_test.ww\n",
path);
for (int i = 0; i < n; i++) free(arr[i]);
free(arr);
source_list_free(prod, nprod);
source_list_free(tests, ntests);
closedir(d);
*out_files = NULL;
return -2;
}
if (n + 1 > cap) {
cap = cap ? cap * 2 : 8;
arr = realloc(arr, cap * sizeof *arr);
if (is_test) {
char package[256];
if (source_package_name(path, package, sizeof package) < 0) {
source_list_free(prod, nprod);
source_list_free(tests, ntests);
closedir(d);
*out_files = NULL;
return -2;
}
if (test_package == NULL || strcmp(package, test_package) != 0)
continue;
}
char ***list = is_test ? &tests : &prod;
int *n = is_test ? &ntests : &nprod;
int *cap = is_test ? &captests : &capprod;
if (source_list_add(list, n, cap, path) < 0) {
source_list_free(prod, nprod);
source_list_free(tests, ntests);
closedir(d);
*out_files = NULL;
return -2;
}
arr[n++] = strdup(path);
}
closedir(d);
if (n > 1) qsort(arr, n, sizeof *arr, strs_cmp);
*out_files = arr;
return n;
if (nprod > 1) qsort(prod, (size_t)nprod, sizeof *prod, strs_cmp);
if (ntests > 1) qsort(tests, (size_t)ntests, sizeof *tests, strs_cmp);
int total = nprod + ntests;
char **all = total ? malloc((size_t)total * sizeof *all) : NULL;
if (total && all == NULL) {
source_list_free(prod, nprod);
source_list_free(tests, ntests);
*out_files = NULL;
return -2;
}
for (int i = 0; i < nprod; i++) all[i] = prod[i];
for (int i = 0; i < ntests; i++) all[nprod + i] = tests[i];
free(prod);
free(tests);
*out_files = all;
return total;
}
/* ww build — separate-compilation driver (task #46/c3).
@@ -343,9 +490,12 @@ struct seppkg {
char entry[1024]; /* resolved package dir (or file, for a file root) */
char canon[1024]; /* canonical location; never package identity */
char name[256]; /* validated declared name; directory packages only */
char **sources; /* owned, byte-sorted production paths; dirs only */
char test_package[256]; /* selected test package; root variants only */
char **sources; /* owned, byte-sorted selected paths; dirs only */
int nsources;
int is_dir;
int variant; /* SEP_VARIANT_*; dependencies are production */
int test_support; /* compiler-generated -T support package */
int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */
int ndeps;
int color; /* tri-color DFS: 0 white, 1 gray, 2 black */
@@ -357,8 +507,8 @@ struct sepgraph {
};
static int
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
int is_dir)
sep_find_or_add_variant(struct sepgraph *g, const char *path,
const char *entry, int is_dir, int variant, const char *test_package)
{
if (strlen(path) >= sizeof g->pkg[0].path) {
fprintf(stderr, "ww: package path is too long (limit %zu bytes)\n",
@@ -376,8 +526,16 @@ sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
return -1;
}
for (int i = 0; i < g->n; i++) {
int test_production_pair = i == 0
&& g->pkg[i].variant != SEP_VARIANT_PRODUCTION
&& variant == SEP_VARIANT_PRODUCTION && is_dir
&& g->pkg[i].is_dir
&& strcmp(g->pkg[i].canon, canon) == 0;
if (strcmp(g->pkg[i].path, path) == 0) {
if (strcmp(g->pkg[i].canon, canon) != 0) {
if (test_production_pair)
continue;
if (strcmp(g->pkg[i].canon, canon) != 0
|| g->pkg[i].variant != variant) {
fprintf(stderr,
"ww: package %s resolves to both %s and %s\n",
path[0] ? path : "(root)", g->pkg[i].entry,
@@ -389,6 +547,11 @@ sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
return i;
}
if (strcmp(g->pkg[i].canon, canon) == 0) {
/* A test root and a production variant reached by its imports or
* runtime closure intentionally may share one directory. No other
* physical-directory alias is permitted. */
if (test_production_pair)
continue;
fprintf(stderr,
"ww: package directory %s has identities %s and %s\n",
entry, g->pkg[i].path[0] ? g->pkg[i].path : "(root)",
@@ -409,7 +572,13 @@ sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
snprintf(p->canon, sizeof p->canon, "%s", canon);
free(canon);
p->is_dir = is_dir;
p->variant = variant;
p->test_support = 0;
p->name[0] = '\0';
p->test_package[0] = '\0';
if (test_package != NULL)
snprintf(p->test_package, sizeof p->test_package, "%s",
test_package);
p->sources = NULL;
p->nsources = 0;
p->ndeps = 0;
@@ -417,6 +586,14 @@ sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
return g->n++;
}
static int
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
int is_dir)
{
return sep_find_or_add_variant(g, path, entry, is_dir,
SEP_VARIANT_PRODUCTION, NULL);
}
/* Release the one package-owned directory-membership list. Every graph exit
* funnels through this function; regular-file nodes own no source list. */
static void
@@ -481,6 +658,30 @@ use_node_cmp(const void *a, const void *b)
return x->pos.col - y->pos.col;
}
static int
sep_external_production_name(const struct seppkg *pkg, const char *path,
int leaf_only)
{
if (pkg->variant != SEP_VARIANT_EXTERNAL
|| pkg->test_package[0] == '\0')
return 0;
const char *name = path;
if (leaf_only) {
const char *dot = strrchr(path, '.');
if (dot != NULL) name = dot + 1;
}
size_t n = strlen(name);
size_t tn = strlen(pkg->test_package);
return tn == n + 5 && strncmp(pkg->test_package, name, n) == 0
&& strcmp(pkg->test_package + n, "_test") == 0;
}
static int
sep_external_production_import(const struct seppkg *pkg, const char *path)
{
return sep_external_production_name(pkg, path, 0);
}
/* A 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` (its bytes join pi's body at emit time). Collects package PATHS
@@ -587,8 +788,18 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
}
char ipath[1024];
int is_dir = 0;
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
&is_dir)) {
int external_production = sep_external_production_import(
&g->pkg[pi], name);
int located = 0;
if (external_production) {
snprintf(ipath, sizeof ipath, "%s", g->pkg[pi].entry);
is_dir = 1;
located = 1;
} else {
located = locate_import(searchpath, path_form, ipath,
sizeof ipath, &is_dir);
}
if (!located) {
const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name;
int inline_package = 0;
@@ -613,7 +824,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) {
if (self && sep_external_production_name(&g->pkg[pi], name, 1))
external_production = 1;
if (self && !external_production) {
const char *owner = g->pkg[pi].path[0]
? g->pkg[pi].path : g->pkg[pi].name;
errorf(u->pos, "self-import: package '%s' cannot import itself",
@@ -653,8 +866,10 @@ sep_load_pkg(struct sepgraph *g, int pi, const char *searchpath)
struct ImportSet fv = {0};
int rc = 0;
if (g->pkg[pi].is_dir) {
const char *test_package = g->pkg[pi].test_package[0]
? g->pkg[pi].test_package : NULL;
g->pkg[pi].nsources = enumerate_dir_ww(g->pkg[pi].entry,
&g->pkg[pi].sources);
g->pkg[pi].variant, test_package, &g->pkg[pi].sources);
if (g->pkg[pi].nsources == -2) {
rc = -1; /* diagnosed in enumerate_dir_ww */
} else if (g->pkg[pi].nsources < 0) {
@@ -670,7 +885,8 @@ sep_load_pkg(struct sepgraph *g, int pi, const char *searchpath)
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
searchpath, &fv, 1);
if (rc == 0 && g->pkg[pi].path[0] != '\0') {
if (rc == 0 && g->pkg[pi].path[0] != '\0'
&& !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) {
@@ -751,7 +967,7 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
* body); FILE imports fold in (intra-package split). */
static int
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
const char *searchpath, const char *modpath)
const char *searchpath, const char *modpath, const struct seppkg *pkg)
{
if (import_seen(visited, path)) return 0;
import_add(visited, path);
@@ -788,6 +1004,8 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
for (int i = 0; i < nuse; i++) {
Node *u = uses[i];
const char *name = u->usepath ? u->usepath : u->str;
if (sep_external_production_import(pkg, name))
continue;
char path_form[1024];
import_path_form(name, path_form, sizeof path_form);
char ipath[1024];
@@ -796,7 +1014,7 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
&is_dir))
continue;
if (!is_dir && sep_emit_body(out, ipath, visited, searchpath,
modpath) < 0) {
modpath, pkg) < 0) {
free(uses);
freearena(a);
free(buf);
@@ -866,10 +1084,10 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
if (g->pkg[pi].is_dir) {
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i], &bodyvisit,
searchpath, g->pkg[pi].path);
searchpath, g->pkg[pi].path, &g->pkg[pi]);
} else {
bodyrc = sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
g->pkg[pi].path);
g->pkg[pi].path, &g->pkg[pi]);
}
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
free(bodyvisit.paths);
@@ -1027,7 +1245,7 @@ copy_file_atomic(const char *src, const char *dst)
static void
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
{
snprintf(buf, bufsz, "ww workdir fmt 2 mode %s asm %d\n",
snprintf(buf, bufsz, "ww workdir fmt 3 mode %s asm %d\n",
is_test ? "test" : "build", emit_asm);
}
@@ -1041,7 +1259,8 @@ static int
build_one_sep_impl(const char *src, int entry_is_dir,
const char *root_identity, const char *out,
const char *objstem, const char *extra_includes, const char *extra_libs,
const char *extra_libdirs, int package_only, int is_test, int emit_asm,
const char *extra_libdirs, int package_only, int is_test,
int root_variant, const char *test_package, int emit_asm,
const char *workdir, char *scratchout, size_t scratchoutsz,
struct sepgraph **graphout)
{
@@ -1062,6 +1281,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
else if (access("lib", 0) == 0) srcdir = "lib";
else srcdir = libdir;
}
const char *toolsrcdir = srcdir;
char srcd[1024];
if (entry_is_dir) {
snprintf(srcd, sizeof srcd, "%s", src);
@@ -1145,27 +1365,68 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (graphout) *graphout = g;
const char *rootpath = package_only && root_identity
? root_identity : "";
int root = sep_find_or_add(g, rootpath, src, entry_is_dir);
int root = sep_find_or_add_variant(g, rootpath, src, entry_is_dir,
root_variant, test_package);
if (root < 0) return 1;
/* #79 (-T): lib/test is the synth main's `test.run` callee but @test
* files never `import test;`. Inject it as a direct dep of the root so
* sep_load_pkg pulls test + its transitive deps; the producer adds -T
* to the root and `test.run` links against test's `.a` — via the
* sep_scan_file dedup-guarded dep append. */
const char *test_support_module = "test";
/* -T generates a dispatcher whose support qualifier is selected by the
* command. Represent that compiler-generated requirement as a direct root
* edge. It normally coalesces with an explicit toolchain `import test`;
* when user source occupies that identity, the reserved graph alias keeps
* it distinct. The linker receives the same support archive closure. */
if (is_test) {
char tpath[1024];
int tdir = 0;
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
int ti = sep_find_or_add(g, "test", tpath, tdir);
if (ti < 0) return 1;
int seen = 0;
for (int k = 0; k < g->pkg[root].ndeps; k++)
if (g->pkg[root].deps[k] == ti) { seen = 1; break; }
if (!seen && g->pkg[root].ndeps < SEP_MAXPKG)
g->pkg[root].deps[g->pkg[root].ndeps++] = ti;
if (locate_import(toolsrcdir, "test", tpath, sizeof tpath, &tdir)) {
char *tc = realpath(tpath, NULL);
char *rc = entry_is_dir ? realpath(src, NULL) : NULL;
int root_is_support = tc != NULL && rc != NULL
&& strcmp(tc, rc) == 0;
free(tc);
free(rc);
int collision = !root_is_support && test_package != NULL
&& (strcmp(test_package, "test") == 0
|| strcmp(test_package, "test_test") == 0);
char userpath[1024];
int userdir = 0;
if (!root_is_support && !collision
&& locate_import(srcdir, "test", userpath,
sizeof userpath, &userdir)) {
(void)userdir;
tc = realpath(tpath, NULL);
char *uc = realpath(userpath, NULL);
if (tc != NULL && uc != NULL && strcmp(tc, uc) != 0)
collision = 1;
free(tc);
free(uc);
}
if (collision) test_support_module = SEP_TEST_SUPPORT_MODULE;
/* A same-test build of the runtime package already owns run
* and its source imports. An external test still needs the
* colocated production node, which is also its one support dep. */
if (!root_is_support || root_variant == SEP_VARIANT_EXTERNAL) {
int ti = sep_find_or_add(g, test_support_module, tpath,
tdir);
if (ti < 0) return 1;
g->pkg[ti].test_support = 1;
int seen = 0;
for (int k = 0; k < g->pkg[root].ndeps; k++)
if (g->pkg[root].deps[k] == ti) {
seen = 1; break;
}
if (!seen && g->pkg[root].ndeps < SEP_MAXPKG)
g->pkg[root].deps[g->pkg[root].ndeps++] = ti;
}
}
}
if (sep_load_pkg(g, root, srcdir) < 0) return 1;
if (root_variant != SEP_VARIANT_PRODUCTION
&& (test_package == NULL
|| strcmp(g->pkg[root].name, test_package) != 0)) {
fprintf(stderr,
"ww: package-test selector does not match loaded package\n");
return 1;
}
int root_package = package_only;
if (root_package && strcmp(g->pkg[root].name, "main") == 0) {
fprintf(stderr, "ww: -p requires a non-main package\n");
@@ -1227,15 +1488,24 @@ build_one_sep_impl(const char *src, int entry_is_dir,
* LOCAL type (the root is never imported), which the
* export-check rejects. Skip -I for the root; its `.wwi`
* is never consumed. */
if (!needs_export)
if (!needs_export) {
/* #79: the root carries -T under `ww test`
* so w6c synthesizes the test main. Deps never
* get -T. */
snprintf(cmd, sizeof cmd, "%s %s-c -o %s %s",
c6, is_test ? "-T " : "", cs, cu);
else
snprintf(cmd, sizeof cmd, "%s -c -I %s -o %s %s",
c6, cw, cs, cu);
if (is_test)
snprintf(cmd, sizeof cmd,
"%s -T --test-support-module %s -c -o %s %s",
c6, test_support_module, cs, cu);
else
snprintf(cmd, sizeof cmd, "%s -c -o %s %s",
c6, cs, cu);
} else
snprintf(cmd, sizeof cmd, "%s %s%s%s-c -I %s -o %s %s",
c6, g->pkg[pi].test_support
? "--test-support-module " : "",
g->pkg[pi].test_support ? test_support_module : "",
g->pkg[pi].test_support ? " " : "",
cw, cs, cu);
if (run(cmd) != 0) {
fprintf(stderr, "ww: w6c failed for %s\n",
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
@@ -1321,8 +1591,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
/* reverse-topo link: root `.o` first (order[norder-1], force-loaded),
* then transitive dep `.a` in reverse-topo order, then libwwrt.a —
* each archive selectively pulls only members satisfying a live
* undef. */
* each archive selectively pulls only members satisfying a live undef.
* A same-test root already contains its production sources; if that
* production variant is also reached through the runtime closure, keep
* traversing its dependencies but do not link its duplicate archive. */
char rtargs[2048] = {0};
char rtpath[1024];
snprintf(rtpath, sizeof rtpath, "%s/libwwrt.a", libdir);
@@ -1336,9 +1608,15 @@ build_one_sep_impl(const char *src, int entry_is_dir,
}
char objs[8192] = {0};
for (int oi = norder - 1; oi >= 0; oi--) {
int pi = order[oi];
if (g->pkg[root].variant == SEP_VARIANT_SAME_TEST
&& pi != root
&& g->pkg[pi].variant == SEP_VARIANT_PRODUCTION
&& strcmp(g->pkg[pi].canon, g->pkg[root].canon) == 0)
continue;
char path[1024];
sep_fname(g, order[oi], scratch,
order[oi] == root ? ".o" : ".a", path, sizeof path);
sep_fname(g, pi, scratch,
pi == root ? ".o" : ".a", path, sizeof path);
size_t n = strlen(objs);
snprintf(objs + n, sizeof objs - n, "%s%s", n ? " " : "", path);
}
@@ -1365,14 +1643,16 @@ static int
build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
const char *out,
const char *objstem, const char *extra_includes, const char *extra_libs,
const char *extra_libdirs, int package_only, int is_test, int emit_asm,
const char *extra_libdirs, int package_only, int is_test,
int root_variant, const char *test_package, int emit_asm,
int keepscratch, const char *workdir)
{
char scratch[1100] = {0};
struct sepgraph *g = NULL;
int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem,
extra_includes, extra_libs, extra_libdirs, package_only, is_test, emit_asm,
workdir, scratch, sizeof scratch, &g);
extra_includes, extra_libs, extra_libdirs, package_only, is_test,
root_variant, test_package, emit_asm, workdir, scratch,
sizeof scratch, &g);
sep_graph_free(g);
if (!keepscratch && scratch[0]) {
size_t sl = strlen(scratch);
@@ -1626,7 +1906,8 @@ do_build(int argc, char **argv)
}
const char *root_identity = package_only && !literal ? src : NULL;
return build_one_sep(resolved, is_dir, root_identity, out, objstem, incs, libs,
libdirs, package_only, 0, emit_asm, 1, workdir);
libdirs, package_only, 0, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
1, workdir);
}
static int
@@ -1658,7 +1939,7 @@ do_run(int argc, char **argv)
/* The freshly acquired directory owns both the executable and the
* adjacent main.sepwork tree. Nothing outside it is adopted or removed. */
if (build_one_sep(resolved, is_dir, NULL, tmp, tmp, incs, libs, libdirs,
0, 0, 0, 0, NULL) != 0) {
0, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL) != 0) {
if (unlink(tmp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
if (rmdir(tmpdir) != 0)
@@ -1704,6 +1985,8 @@ static int
do_test(int argc, char **argv)
{
const char *src = NULL;
const char *package_test_kind = NULL;
const char *package_test_name = NULL;
char incs[2048] = {0};
/* -c (Go's `go test -c`) builds the test binary without running it.
* -S + -o <stem> stops after the lib/test-inclusive package `.s`
@@ -1748,6 +2031,26 @@ do_test(int argc, char **argv)
"%s%s", n ? ":" : "", dir);
} else if (strcmp(argv[i], "-c") == 0) {
compileonly = 1;
} else if (strcmp(argv[i], "--ww-package-test") == 0) {
if (i + 2 >= argc || package_test_kind != NULL) {
fprintf(stderr,
"ww test: --ww-package-test needs kind and package\n");
return 2;
}
package_test_kind = argv[++i];
package_test_name = argv[++i];
size_t pn = strlen(package_test_name);
if ((strcmp(package_test_kind, "same") != 0
&& strcmp(package_test_kind, "external") != 0)
|| pn == 0 || pn >= sizeof ((struct seppkg *)0)->name
|| (strcmp(package_test_kind, "external") == 0
&& (pn <= 5
|| strcmp(package_test_name + pn - 5,
"_test") != 0))) {
fprintf(stderr,
"ww test: invalid --ww-package-test variant\n");
return 2;
}
} else if (strcmp(argv[i], "-S") == 0) {
emit_asm = 1;
} else if (strcmp(argv[i], "-list") == 0) {
@@ -1798,12 +2101,22 @@ do_test(int argc, char **argv)
fprintf(stderr, "ww test: -S needs -o\n");
return 2;
}
if (package_test_kind != NULL && packageopts) {
fprintf(stderr,
"ww test: package-test variant rejects package options\n");
return 2;
}
/* Go's ./... form: a trailing "..." element is a package-tree
* request for the coordinator, never a literal path — recognized
* before stat, with the directory-mode rejects. */
size_t tlen = strlen(target);
if (strcmp(target, "...") == 0 ||
(tlen >= 4 && strcmp(target + tlen - 4, "/...") == 0)) {
if (package_test_kind != NULL) {
fprintf(stderr,
"ww test: package-test variant needs one directory\n");
return 2;
}
if (emit_asm) {
fprintf(stderr,
"ww test: -S needs a single test file\n");
@@ -1852,6 +2165,18 @@ do_test(int argc, char **argv)
"ww test: pattern needs a single test file\n");
return 2;
}
if (package_test_kind != NULL) {
if (!compileonly || !outstem[0]) {
fprintf(stderr,
"ww test: package-test variant needs -c -o\n");
return 2;
}
int variant = strcmp(package_test_kind, "same") == 0
? SEP_VARIANT_SAME_TEST : SEP_VARIANT_EXTERNAL;
return build_one_sep(resolved, 1, NULL, outstem, outstem,
incs, "", "", 0, 1, variant, package_test_name, 0,
1, workdir);
}
return exec_package_tests(argc, argv, src, resolved, 0);
}
if (packageopts) {
@@ -1859,6 +2184,11 @@ do_test(int argc, char **argv)
"ww test: package options need a directory\n");
return 2;
}
if (package_test_kind != NULL) {
fprintf(stderr,
"ww test: package-test variant needs one directory\n");
return 2;
}
char tmpdir[1024] = {0}, tmp[1024];
const char *outp;
int owntmp = !outstem[0] && !workdir[0];
@@ -1882,7 +2212,8 @@ do_test(int argc, char **argv)
* source. An explicit -o names the caller-owned artifact stem. */
int br = build_one_sep(resolved, is_dir, NULL, outp,
outstem[0] ? outstem : tmp, incs, "", "", 0, 1,
emit_asm, outstem[0] ? 1 : 0, workdir);
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -1914,6 +2245,11 @@ do_test(int argc, char **argv)
return rc;
}
if (S_ISREG(st.st_mode)) {
if (package_test_kind != NULL) {
fprintf(stderr,
"ww test: package-test variant needs one directory\n");
return 2;
}
if (packageopts) {
fprintf(stderr,
"ww test: package options need a directory\n");
@@ -1938,7 +2274,8 @@ do_test(int argc, char **argv)
}
/* See module-mode note: no-o scratch is redirected to /tmp. */
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp,
incs, "", "", 0, 1, emit_asm, outstem[0] ? 1 : 0, workdir);
incs, "", "", 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -1987,6 +2324,17 @@ do_test(int argc, char **argv)
fprintf(stderr, "ww test: pattern needs a single test file\n");
return 2;
}
if (package_test_kind != NULL) {
if (!compileonly || !outstem[0]) {
fprintf(stderr,
"ww test: package-test variant needs -c -o\n");
return 2;
}
int variant = strcmp(package_test_kind, "same") == 0
? SEP_VARIANT_SAME_TEST : SEP_VARIANT_EXTERNAL;
return build_one_sep(target, 1, NULL, outstem, outstem, incs,
"", "", 0, 1, variant, package_test_name, 0, 1, workdir);
}
return exec_package_tests(argc, argv, src, NULL, src == NULL);
}