cmd: build directory package test variants
This commit is contained in:
514
cmd/ww/main.c
514
cmd/ww/main.c
@@ -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 : ∏
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user