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);
|
return strcmp(sa, sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Go's contract: only *_test.ww is a test source. A line-leading @test
|
static int sep_slurp(const char*, char**, u64*);
|
||||||
* declaration anywhere else would be silently dropped by a non-T build
|
|
||||||
* (#6, the Hare model), so directory enumeration rejects it loudly. */
|
/* 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
|
static int
|
||||||
file_has_line_test(const char *path)
|
source_has_test_decl(const char *path)
|
||||||
{
|
{
|
||||||
char line[4096];
|
char *buf;
|
||||||
FILE *f = fopen(path, "rb");
|
u64 len;
|
||||||
if (f == NULL) return 0;
|
if (sep_slurp(path, &buf, &len) < 0) return -1;
|
||||||
int found = 0;
|
/* Keep directory-loader package-clause diagnostics stable. The full
|
||||||
while (fgets(line, sizeof line, f) != NULL) {
|
* parser reports its language-level "missing package clause" first;
|
||||||
char *p = line;
|
* the imports-only pass owns the package-loader wording and also avoids
|
||||||
while (*p == ' ' || *p == '\t' || *p == '\r') p++;
|
* stage-specific recovery diagnostics for a malformed clause. */
|
||||||
if (strncmp(p, "@test", 5) == 0
|
Arena *ia = newarena();
|
||||||
&& (p[5] == ' ' || p[5] == '\t')) {
|
Lex il;
|
||||||
found = 1;
|
Parser ip;
|
||||||
break;
|
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;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A line-leading @test in a production source is diagnosed here and
|
#define SEP_VARIANT_PRODUCTION 0
|
||||||
* returns -2. This is the sole directory-membership discovery path; the
|
#define SEP_VARIANT_SAME_TEST 1
|
||||||
* owning seppkg retains the returned list. */
|
#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
|
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);
|
DIR *d = opendir(dirpath);
|
||||||
if (d == NULL) { *out_files = NULL; return -1; }
|
if (d == NULL) { *out_files = NULL; return -1; }
|
||||||
char **arr = NULL;
|
char **prod = NULL, **tests = NULL;
|
||||||
int n = 0, cap = 0;
|
int nprod = 0, capprod = 0, ntests = 0, captests = 0;
|
||||||
struct dirent *ent;
|
struct dirent *ent;
|
||||||
while ((ent = readdir(d)) != NULL) {
|
while ((ent = readdir(d)) != NULL) {
|
||||||
const char *nm = ent->d_name;
|
const char *nm = ent->d_name;
|
||||||
size_t nl = strlen(nm);
|
size_t nl = strlen(nm);
|
||||||
if (nl <= 3) continue;
|
if (nl <= 3) continue;
|
||||||
if (strcmp(nm + nl - 3, ".ww") != 0) 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];
|
char path[2048];
|
||||||
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
|
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
|
||||||
if (nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
|
|
||||||
continue;
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
|
if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ww: %s: package source is not a regular file\n", path);
|
"ww: %s: package source is not a regular file\n", path);
|
||||||
for (int i = 0; i < n; i++) free(arr[i]);
|
source_list_free(prod, nprod);
|
||||||
free(arr);
|
source_list_free(tests, ntests);
|
||||||
closedir(d);
|
closedir(d);
|
||||||
*out_files = NULL;
|
*out_files = NULL;
|
||||||
return -2;
|
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,
|
fprintf(stderr,
|
||||||
"ww: %s: @test declaration outside *_test.ww\n",
|
"ww: %s: @test declaration outside *_test.ww\n",
|
||||||
path);
|
path);
|
||||||
for (int i = 0; i < n; i++) free(arr[i]);
|
source_list_free(prod, nprod);
|
||||||
free(arr);
|
source_list_free(tests, ntests);
|
||||||
closedir(d);
|
closedir(d);
|
||||||
*out_files = NULL;
|
*out_files = NULL;
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
if (n + 1 > cap) {
|
if (is_test) {
|
||||||
cap = cap ? cap * 2 : 8;
|
char package[256];
|
||||||
arr = realloc(arr, cap * sizeof *arr);
|
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);
|
closedir(d);
|
||||||
if (n > 1) qsort(arr, n, sizeof *arr, strs_cmp);
|
if (nprod > 1) qsort(prod, (size_t)nprod, sizeof *prod, strs_cmp);
|
||||||
*out_files = arr;
|
if (ntests > 1) qsort(tests, (size_t)ntests, sizeof *tests, strs_cmp);
|
||||||
return n;
|
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).
|
/* 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 entry[1024]; /* resolved package dir (or file, for a file root) */
|
||||||
char canon[1024]; /* canonical location; never package identity */
|
char canon[1024]; /* canonical location; never package identity */
|
||||||
char name[256]; /* validated declared name; directory packages only */
|
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 nsources;
|
||||||
int is_dir;
|
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 deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */
|
||||||
int ndeps;
|
int ndeps;
|
||||||
int color; /* tri-color DFS: 0 white, 1 gray, 2 black */
|
int color; /* tri-color DFS: 0 white, 1 gray, 2 black */
|
||||||
@@ -357,8 +507,8 @@ struct sepgraph {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
sep_find_or_add_variant(struct sepgraph *g, const char *path,
|
||||||
int is_dir)
|
const char *entry, int is_dir, int variant, const char *test_package)
|
||||||
{
|
{
|
||||||
if (strlen(path) >= sizeof g->pkg[0].path) {
|
if (strlen(path) >= sizeof g->pkg[0].path) {
|
||||||
fprintf(stderr, "ww: package path is too long (limit %zu bytes)\n",
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < g->n; i++) {
|
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].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,
|
fprintf(stderr,
|
||||||
"ww: package %s resolves to both %s and %s\n",
|
"ww: package %s resolves to both %s and %s\n",
|
||||||
path[0] ? path : "(root)", g->pkg[i].entry,
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
if (strcmp(g->pkg[i].canon, canon) == 0) {
|
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,
|
fprintf(stderr,
|
||||||
"ww: package directory %s has identities %s and %s\n",
|
"ww: package directory %s has identities %s and %s\n",
|
||||||
entry, g->pkg[i].path[0] ? g->pkg[i].path : "(root)",
|
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);
|
snprintf(p->canon, sizeof p->canon, "%s", canon);
|
||||||
free(canon);
|
free(canon);
|
||||||
p->is_dir = is_dir;
|
p->is_dir = is_dir;
|
||||||
|
p->variant = variant;
|
||||||
|
p->test_support = 0;
|
||||||
p->name[0] = '\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->sources = NULL;
|
||||||
p->nsources = 0;
|
p->nsources = 0;
|
||||||
p->ndeps = 0;
|
p->ndeps = 0;
|
||||||
@@ -417,6 +586,14 @@ sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
|||||||
return g->n++;
|
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
|
/* Release the one package-owned directory-membership list. Every graph exit
|
||||||
* funnels through this function; regular-file nodes own no source list. */
|
* funnels through this function; regular-file nodes own no source list. */
|
||||||
static void
|
static void
|
||||||
@@ -481,6 +658,30 @@ use_node_cmp(const void *a, const void *b)
|
|||||||
return x->pos.col - y->pos.col;
|
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
|
/* 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`. 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
|
* `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];
|
char ipath[1024];
|
||||||
int is_dir = 0;
|
int is_dir = 0;
|
||||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
int external_production = sep_external_production_import(
|
||||||
&is_dir)) {
|
&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 *dot = strrchr(name, '.');
|
||||||
const char *leaf = dot ? dot + 1 : name;
|
const char *leaf = dot ? dot + 1 : name;
|
||||||
int inline_package = 0;
|
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;
|
int self = strcmp(canon, g->pkg[pi].canon) == 0;
|
||||||
free(canon);
|
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]
|
const char *owner = g->pkg[pi].path[0]
|
||||||
? g->pkg[pi].path : g->pkg[pi].name;
|
? g->pkg[pi].path : g->pkg[pi].name;
|
||||||
errorf(u->pos, "self-import: package '%s' cannot import itself",
|
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};
|
struct ImportSet fv = {0};
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
if (g->pkg[pi].is_dir) {
|
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].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) {
|
if (g->pkg[pi].nsources == -2) {
|
||||||
rc = -1; /* diagnosed in enumerate_dir_ww */
|
rc = -1; /* diagnosed in enumerate_dir_ww */
|
||||||
} else if (g->pkg[pi].nsources < 0) {
|
} 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++)
|
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
|
||||||
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
|
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
|
||||||
searchpath, &fv, 1);
|
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 *dot = strrchr(g->pkg[pi].path, '.');
|
||||||
const char *leaf = dot ? dot + 1 : g->pkg[pi].path;
|
const char *leaf = dot ? dot + 1 : g->pkg[pi].path;
|
||||||
if (strcmp(g->pkg[pi].name, leaf) != 0) {
|
if (strcmp(g->pkg[pi].name, leaf) != 0) {
|
||||||
@@ -751,7 +967,7 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
|
|||||||
* body); FILE imports fold in (intra-package split). */
|
* body); FILE imports fold in (intra-package split). */
|
||||||
static int
|
static int
|
||||||
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
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;
|
if (import_seen(visited, path)) return 0;
|
||||||
import_add(visited, path);
|
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++) {
|
for (int i = 0; i < nuse; i++) {
|
||||||
Node *u = uses[i];
|
Node *u = uses[i];
|
||||||
const char *name = u->usepath ? u->usepath : u->str;
|
const char *name = u->usepath ? u->usepath : u->str;
|
||||||
|
if (sep_external_production_import(pkg, name))
|
||||||
|
continue;
|
||||||
char path_form[1024];
|
char path_form[1024];
|
||||||
import_path_form(name, path_form, sizeof path_form);
|
import_path_form(name, path_form, sizeof path_form);
|
||||||
char ipath[1024];
|
char ipath[1024];
|
||||||
@@ -796,7 +1014,7 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
|||||||
&is_dir))
|
&is_dir))
|
||||||
continue;
|
continue;
|
||||||
if (!is_dir && sep_emit_body(out, ipath, visited, searchpath,
|
if (!is_dir && sep_emit_body(out, ipath, visited, searchpath,
|
||||||
modpath) < 0) {
|
modpath, pkg) < 0) {
|
||||||
free(uses);
|
free(uses);
|
||||||
freearena(a);
|
freearena(a);
|
||||||
free(buf);
|
free(buf);
|
||||||
@@ -866,10 +1084,10 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
|||||||
if (g->pkg[pi].is_dir) {
|
if (g->pkg[pi].is_dir) {
|
||||||
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
|
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
|
||||||
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i], &bodyvisit,
|
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 {
|
} else {
|
||||||
bodyrc = sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
|
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]);
|
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
||||||
free(bodyvisit.paths);
|
free(bodyvisit.paths);
|
||||||
@@ -1027,7 +1245,7 @@ copy_file_atomic(const char *src, const char *dst)
|
|||||||
static void
|
static void
|
||||||
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
|
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);
|
is_test ? "test" : "build", emit_asm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,7 +1259,8 @@ static int
|
|||||||
build_one_sep_impl(const char *src, int entry_is_dir,
|
build_one_sep_impl(const char *src, int entry_is_dir,
|
||||||
const char *root_identity, const char *out,
|
const char *root_identity, const char *out,
|
||||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
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,
|
const char *workdir, char *scratchout, size_t scratchoutsz,
|
||||||
struct sepgraph **graphout)
|
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 if (access("lib", 0) == 0) srcdir = "lib";
|
||||||
else srcdir = libdir;
|
else srcdir = libdir;
|
||||||
}
|
}
|
||||||
|
const char *toolsrcdir = srcdir;
|
||||||
char srcd[1024];
|
char srcd[1024];
|
||||||
if (entry_is_dir) {
|
if (entry_is_dir) {
|
||||||
snprintf(srcd, sizeof srcd, "%s", src);
|
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;
|
if (graphout) *graphout = g;
|
||||||
const char *rootpath = package_only && root_identity
|
const char *rootpath = package_only && root_identity
|
||||||
? 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;
|
if (root < 0) return 1;
|
||||||
/* #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
const char *test_support_module = "test";
|
||||||
* files never `import test;`. Inject it as a direct dep of the root so
|
/* -T generates a dispatcher whose support qualifier is selected by the
|
||||||
* sep_load_pkg pulls test + its transitive deps; the producer adds -T
|
* command. Represent that compiler-generated requirement as a direct root
|
||||||
* to the root and `test.run` links against test's `.a` — via the
|
* edge. It normally coalesces with an explicit toolchain `import test`;
|
||||||
* sep_scan_file dedup-guarded dep append. */
|
* when user source occupies that identity, the reserved graph alias keeps
|
||||||
|
* it distinct. The linker receives the same support archive closure. */
|
||||||
if (is_test) {
|
if (is_test) {
|
||||||
char tpath[1024];
|
char tpath[1024];
|
||||||
int tdir = 0;
|
int tdir = 0;
|
||||||
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
|
if (locate_import(toolsrcdir, "test", tpath, sizeof tpath, &tdir)) {
|
||||||
int ti = sep_find_or_add(g, "test", tpath, tdir);
|
char *tc = realpath(tpath, NULL);
|
||||||
if (ti < 0) return 1;
|
char *rc = entry_is_dir ? realpath(src, NULL) : NULL;
|
||||||
int seen = 0;
|
int root_is_support = tc != NULL && rc != NULL
|
||||||
for (int k = 0; k < g->pkg[root].ndeps; k++)
|
&& strcmp(tc, rc) == 0;
|
||||||
if (g->pkg[root].deps[k] == ti) { seen = 1; break; }
|
free(tc);
|
||||||
if (!seen && g->pkg[root].ndeps < SEP_MAXPKG)
|
free(rc);
|
||||||
g->pkg[root].deps[g->pkg[root].ndeps++] = ti;
|
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 (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;
|
int root_package = package_only;
|
||||||
if (root_package && strcmp(g->pkg[root].name, "main") == 0) {
|
if (root_package && strcmp(g->pkg[root].name, "main") == 0) {
|
||||||
fprintf(stderr, "ww: -p requires a non-main package\n");
|
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
|
* LOCAL type (the root is never imported), which the
|
||||||
* export-check rejects. Skip -I for the root; its `.wwi`
|
* export-check rejects. Skip -I for the root; its `.wwi`
|
||||||
* is never consumed. */
|
* is never consumed. */
|
||||||
if (!needs_export)
|
if (!needs_export) {
|
||||||
/* #79: the root carries -T under `ww test`
|
/* #79: the root carries -T under `ww test`
|
||||||
* so w6c synthesizes the test main. Deps never
|
* so w6c synthesizes the test main. Deps never
|
||||||
* get -T. */
|
* get -T. */
|
||||||
snprintf(cmd, sizeof cmd, "%s %s-c -o %s %s",
|
if (is_test)
|
||||||
c6, is_test ? "-T " : "", cs, cu);
|
snprintf(cmd, sizeof cmd,
|
||||||
else
|
"%s -T --test-support-module %s -c -o %s %s",
|
||||||
snprintf(cmd, sizeof cmd, "%s -c -I %s -o %s %s",
|
c6, test_support_module, cs, cu);
|
||||||
c6, cw, 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) {
|
if (run(cmd) != 0) {
|
||||||
fprintf(stderr, "ww: w6c failed for %s\n",
|
fprintf(stderr, "ww: w6c failed for %s\n",
|
||||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
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),
|
/* reverse-topo link: root `.o` first (order[norder-1], force-loaded),
|
||||||
* then transitive dep `.a` in reverse-topo order, then libwwrt.a —
|
* then transitive dep `.a` in reverse-topo order, then libwwrt.a —
|
||||||
* each archive selectively pulls only members satisfying a live
|
* each archive selectively pulls only members satisfying a live undef.
|
||||||
* 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 rtargs[2048] = {0};
|
||||||
char rtpath[1024];
|
char rtpath[1024];
|
||||||
snprintf(rtpath, sizeof rtpath, "%s/libwwrt.a", libdir);
|
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};
|
char objs[8192] = {0};
|
||||||
for (int oi = norder - 1; oi >= 0; oi--) {
|
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];
|
char path[1024];
|
||||||
sep_fname(g, order[oi], scratch,
|
sep_fname(g, pi, scratch,
|
||||||
order[oi] == root ? ".o" : ".a", path, sizeof path);
|
pi == root ? ".o" : ".a", path, sizeof path);
|
||||||
size_t n = strlen(objs);
|
size_t n = strlen(objs);
|
||||||
snprintf(objs + n, sizeof objs - n, "%s%s", n ? " " : "", path);
|
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,
|
build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||||
const char *out,
|
const char *out,
|
||||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
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)
|
int keepscratch, const char *workdir)
|
||||||
{
|
{
|
||||||
char scratch[1100] = {0};
|
char scratch[1100] = {0};
|
||||||
struct sepgraph *g = NULL;
|
struct sepgraph *g = NULL;
|
||||||
int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem,
|
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,
|
extra_includes, extra_libs, extra_libdirs, package_only, is_test,
|
||||||
workdir, scratch, sizeof scratch, &g);
|
root_variant, test_package, emit_asm, workdir, scratch,
|
||||||
|
sizeof scratch, &g);
|
||||||
sep_graph_free(g);
|
sep_graph_free(g);
|
||||||
if (!keepscratch && scratch[0]) {
|
if (!keepscratch && scratch[0]) {
|
||||||
size_t sl = strlen(scratch);
|
size_t sl = strlen(scratch);
|
||||||
@@ -1626,7 +1906,8 @@ do_build(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
const char *root_identity = package_only && !literal ? src : NULL;
|
const char *root_identity = package_only && !literal ? src : NULL;
|
||||||
return build_one_sep(resolved, is_dir, root_identity, out, objstem, incs, libs,
|
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
|
static int
|
||||||
@@ -1658,7 +1939,7 @@ do_run(int argc, char **argv)
|
|||||||
/* The freshly acquired directory owns both the executable and the
|
/* The freshly acquired directory owns both the executable and the
|
||||||
* adjacent main.sepwork tree. Nothing outside it is adopted or removed. */
|
* adjacent main.sepwork tree. Nothing outside it is adopted or removed. */
|
||||||
if (build_one_sep(resolved, is_dir, NULL, tmp, tmp, incs, libs, libdirs,
|
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)
|
if (unlink(tmp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
if (rmdir(tmpdir) != 0)
|
if (rmdir(tmpdir) != 0)
|
||||||
@@ -1704,6 +1985,8 @@ static int
|
|||||||
do_test(int argc, char **argv)
|
do_test(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *src = NULL;
|
const char *src = NULL;
|
||||||
|
const char *package_test_kind = NULL;
|
||||||
|
const char *package_test_name = NULL;
|
||||||
char incs[2048] = {0};
|
char incs[2048] = {0};
|
||||||
/* -c (Go's `go test -c`) builds the test binary without running it.
|
/* -c (Go's `go test -c`) builds the test binary without running it.
|
||||||
* -S + -o <stem> stops after the lib/test-inclusive package `.s`
|
* -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);
|
"%s%s", n ? ":" : "", dir);
|
||||||
} else if (strcmp(argv[i], "-c") == 0) {
|
} else if (strcmp(argv[i], "-c") == 0) {
|
||||||
compileonly = 1;
|
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) {
|
} else if (strcmp(argv[i], "-S") == 0) {
|
||||||
emit_asm = 1;
|
emit_asm = 1;
|
||||||
} else if (strcmp(argv[i], "-list") == 0) {
|
} 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");
|
fprintf(stderr, "ww test: -S needs -o\n");
|
||||||
return 2;
|
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
|
/* Go's ./... form: a trailing "..." element is a package-tree
|
||||||
* request for the coordinator, never a literal path — recognized
|
* request for the coordinator, never a literal path — recognized
|
||||||
* before stat, with the directory-mode rejects. */
|
* before stat, with the directory-mode rejects. */
|
||||||
size_t tlen = strlen(target);
|
size_t tlen = strlen(target);
|
||||||
if (strcmp(target, "...") == 0 ||
|
if (strcmp(target, "...") == 0 ||
|
||||||
(tlen >= 4 && strcmp(target + tlen - 4, "/...") == 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) {
|
if (emit_asm) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ww test: -S needs a single test file\n");
|
"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");
|
"ww test: pattern needs a single test file\n");
|
||||||
return 2;
|
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);
|
return exec_package_tests(argc, argv, src, resolved, 0);
|
||||||
}
|
}
|
||||||
if (packageopts) {
|
if (packageopts) {
|
||||||
@@ -1859,6 +2184,11 @@ do_test(int argc, char **argv)
|
|||||||
"ww test: package options need a directory\n");
|
"ww test: package options need a directory\n");
|
||||||
return 2;
|
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];
|
char tmpdir[1024] = {0}, tmp[1024];
|
||||||
const char *outp;
|
const char *outp;
|
||||||
int owntmp = !outstem[0] && !workdir[0];
|
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. */
|
* source. An explicit -o names the caller-owned artifact stem. */
|
||||||
int br = build_one_sep(resolved, is_dir, NULL, outp,
|
int br = build_one_sep(resolved, is_dir, NULL, outp,
|
||||||
outstem[0] ? outstem : tmp, incs, "", "", 0, 1,
|
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 (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
fputs("ww: cannot remove temporary output\n", stderr);
|
||||||
@@ -1914,6 +2245,11 @@ do_test(int argc, char **argv)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
if (S_ISREG(st.st_mode)) {
|
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) {
|
if (packageopts) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ww test: package options need a directory\n");
|
"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. */
|
/* See module-mode note: no-o scratch is redirected to /tmp. */
|
||||||
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : 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 (br != 0) {
|
||||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||||
fputs("ww: cannot remove temporary output\n", stderr);
|
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");
|
fprintf(stderr, "ww test: pattern needs a single test file\n");
|
||||||
return 2;
|
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);
|
return exec_package_tests(argc, argv, src, NULL, src == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2839,6 +2839,67 @@ independent of transitive source interfaces (for example, `alloc` lowers to the
|
|||||||
runtime allocator without requiring an `rt.wwi` compiler input), while the
|
runtime allocator without requiring an `rt.wwi` compiler input), while the
|
||||||
linker still receives every reachable package archive plus the runtime archive.
|
linker still receives every reachable package archive plus the runtime archive.
|
||||||
|
|
||||||
|
### 11.7 Implemented directory package-test slice
|
||||||
|
|
||||||
|
Directory tests now enter that same local package loader and build path. The
|
||||||
|
supported manifest-free commands are `ww test DIR`, `ww test DIR/...`, and
|
||||||
|
their existing `-run`, `-filter`, `-list`, `-timeout-ms`, `-j`, `-c`, and `-w`
|
||||||
|
forms. `ww test -c -o test.bin DIR` names the result when the selected directory
|
||||||
|
has one test variant; the coordinator rejects one output name for a multi-variant
|
||||||
|
or recursive request. Explicit `ww test FILE` retains its compatibility path.
|
||||||
|
|
||||||
|
The test coordinator still discovers requested directories, enumerates the
|
||||||
|
test package names, schedules independent binaries, executes them, and emits
|
||||||
|
captured results in byte-sorted package order. It no longer concatenates a
|
||||||
|
generated production/test root or resolves imports. Instead it asks the Cstage
|
||||||
|
or WWstage command to build one of two non-importable root variants from the
|
||||||
|
real directory:
|
||||||
|
|
||||||
|
- `same-test` selects the byte-sorted production files followed by the
|
||||||
|
byte-sorted matching `package p` test files. They form one compiler unit, so
|
||||||
|
tests can use private production declarations.
|
||||||
|
- `external-test` selects only matching `package p_test` files. Its `import p`
|
||||||
|
is a direct edge to a distinct production node for the same canonical
|
||||||
|
directory. That node selects every production file, emits `p.wwi` and `p.a`,
|
||||||
|
and exposes no private declaration to the external root.
|
||||||
|
|
||||||
|
Every non-root dependency is always a production variant, so dependency
|
||||||
|
`*_test.ww` files never enter the graph. Imports that occur only in selected
|
||||||
|
test files add edges only to that test root. The compiler unit for each package
|
||||||
|
contains only its byte-sorted direct dependency `.wwi` artifacts; the final
|
||||||
|
test link still receives the root object and the complete reverse-topological
|
||||||
|
archive closure. The compiler-generated `-T` dispatcher owns the implicit
|
||||||
|
direct test-runtime support edge and remains embedded in each independent test
|
||||||
|
root. The command resolves that edge from the selected toolchain source tree,
|
||||||
|
not the user search path. Normally its graph qualifier is `test`, so an explicit
|
||||||
|
source `import test` coalesces with the same canonical package. When a real user
|
||||||
|
package occupies that identity, the command presents the runtime edge to the
|
||||||
|
compiler under the reserved `__wwtest` qualifier. This keeps a production
|
||||||
|
package named `test` available to external tests while preserving raw `w6c -T`
|
||||||
|
compatibility, whose default unresolved qualifier remains `test`.
|
||||||
|
|
||||||
|
The selected test root and a production variant reached by its imports or test
|
||||||
|
runtime closure are the only sanctioned pair of graph nodes that may share a
|
||||||
|
physical directory. This also lets a toolchain package's own tests coexist with
|
||||||
|
the production variant required by the test runtime. A same-package root
|
||||||
|
already defines those production symbols, so the duplicate production archive
|
||||||
|
is omitted from its final link while that node's dependency archives remain in
|
||||||
|
the closure. All ordinary logical and physical package-identity collision
|
||||||
|
checks remain unchanged. Recursive
|
||||||
|
discovery groups by physical directory before sorting filenames, and persistent
|
||||||
|
coordinator work directories use an injective escaped `(directory, test
|
||||||
|
package)` key. Every `*_test.ww` package variant is built even when a file only
|
||||||
|
declares helpers and contains no `@test`, so its package clause and imports are
|
||||||
|
still checked by the shared loader. Every built variant is run, and a successful
|
||||||
|
compiler-owned dispatcher with empty output is reported as `[no tests]`.
|
||||||
|
|
||||||
|
This ownership split follows Go 1.26.5's separation of production,
|
||||||
|
same-package-test, external-test, and generated test-main inputs in
|
||||||
|
[`cmd/go/internal/load/test.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go),
|
||||||
|
while retaining WW's compiler-owned dispatcher. Direct compile dependencies and
|
||||||
|
the separately expanded link closure follow the boundary in
|
||||||
|
[`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go).
|
||||||
|
|
||||||
## 12. Candidate architectures and hard-gate decision
|
## 12. Candidate architectures and hard-gate decision
|
||||||
|
|
||||||
Five candidates were developed as coherent systems, not as feature bins.
|
Five candidates were developed as coherent systems, not as feature bins.
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ type pkgsource = struct {
|
|||||||
dir: str,
|
dir: str,
|
||||||
pkg: str,
|
pkg: str,
|
||||||
test: bool,
|
test: bool,
|
||||||
attest: bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type pkgfolder = struct {
|
type pkgfolder = struct {
|
||||||
@@ -20,18 +19,13 @@ type pkgfolder = struct {
|
|||||||
start: i32,
|
start: i32,
|
||||||
end: i32,
|
end: i32,
|
||||||
prodpkg: str,
|
prodpkg: str,
|
||||||
hastests: bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type pkggroup = struct {
|
type pkggroup = struct {
|
||||||
dir: str,
|
dir: str,
|
||||||
pkg: str,
|
pkg: str,
|
||||||
start: i32,
|
|
||||||
end: i32,
|
|
||||||
prodpkg: str,
|
|
||||||
external: bool,
|
external: bool,
|
||||||
root: str,
|
root: str,
|
||||||
combined: str,
|
|
||||||
workdir: str,
|
workdir: str,
|
||||||
bin: str,
|
bin: str,
|
||||||
buildout: str,
|
buildout: str,
|
||||||
@@ -53,7 +47,6 @@ def PKGDONE: i32 = 3;
|
|||||||
// pkggroup.fail values recorded at launch, reported at ordered emission.
|
// pkggroup.fail values recorded at launch, reported at ordered emission.
|
||||||
def PKGFAILNONE: i32 = 0;
|
def PKGFAILNONE: i32 = 0;
|
||||||
def PKGFAILSETUP: i32 = 1;
|
def PKGFAILSETUP: i32 = 1;
|
||||||
def PKGFAILCOMPOSE: i32 = 2;
|
|
||||||
|
|
||||||
def pkgpoll: time.duration = 1000000i64: time.duration;
|
def pkgpoll: time.duration = 1000000i64: time.duration;
|
||||||
|
|
||||||
@@ -212,22 +205,6 @@ fn pkgclause(src: str, out: *str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn pkgattest(src: str) bool = {
|
|
||||||
let i: i32 = 0;
|
|
||||||
for (i < src.len) {
|
|
||||||
for (i < src.len && (src[i] == ' ' || src[i] == '\t' ||
|
|
||||||
src[i] == '\r')) { i += 1; };
|
|
||||||
if (i + 5 < src.len && src[i] == '@' && src[i + 1] == 't' &&
|
|
||||||
src[i + 2] == 'e' && src[i + 3] == 's' && src[i + 4] == 't' &&
|
|
||||||
(src[i + 5] == ' ' || src[i + 5] == '\t')) {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
for (i < src.len && src[i] != '\n') { i += 1; };
|
|
||||||
if (i < src.len) { i += 1; };
|
|
||||||
};
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn pkgdirname(path: str) str = {
|
fn pkgdirname(path: str) str = {
|
||||||
let i: i32 = path.len - 1;
|
let i: i32 = path.len - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
@@ -271,7 +248,6 @@ fn pkgisdir(path: str) bool = {
|
|||||||
|
|
||||||
fn pkgkeepfile(name: str) bool = {
|
fn pkgkeepfile(name: str) bool = {
|
||||||
if (!strings.hassuffix(name, ".ww")) { return false; };
|
if (!strings.hassuffix(name, ".ww")) { return false; };
|
||||||
if (strings.hassuffix(name, ".combined.ww")) { return false; };
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -372,11 +348,19 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
|
|||||||
os.close(fd);
|
os.close(fd);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Group paths by containing directory before filename order. A plain full-path
|
||||||
|
// sort can interleave a child directory between two files in its parent and
|
||||||
|
// split one package into multiple folder records during recursive discovery.
|
||||||
fn pkgsort(ss: []str) void = {
|
fn pkgsort(ss: []str) void = {
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < ss.len) {
|
for (i < ss.len) {
|
||||||
let j: i32 = i;
|
let j: i32 = i;
|
||||||
for (j > 0 && strings.compare(ss[j - 1], ss[j]) > 0) {
|
for (j > 0) {
|
||||||
|
let a: str = pkgdirname(ss[j - 1]);
|
||||||
|
let b: str = pkgdirname(ss[j]);
|
||||||
|
let c: int = strings.compare(a, b);
|
||||||
|
if (c == 0) { c = strings.compare(ss[j - 1], ss[j]); };
|
||||||
|
if (c <= 0) { break; };
|
||||||
let t: str = ss[j];
|
let t: str = ss[j];
|
||||||
ss[j] = ss[j - 1];
|
ss[j] = ss[j - 1];
|
||||||
ss[j - 1] = t;
|
ss[j - 1] = t;
|
||||||
@@ -505,46 +489,25 @@ fn pkgemitfile(path: str, fd: i32) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn pkgcombined(g: *pkggroup, srcs: []pkgsource) bool = {
|
fn pkgworkescape(s: str) str = {
|
||||||
let fd: i32 = os.open(g.combined,
|
let out: []u8 = alloc([], (s.len * 2 + 1): u64)!;
|
||||||
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32);
|
let i: i32 = 0;
|
||||||
if (fd < 0) { return false; };
|
for (i < s.len) {
|
||||||
let ok: bool = true;
|
if (s[i] == '_') {
|
||||||
if (!g.external) {
|
append(out, '_'); append(out, 'u');
|
||||||
let i: i32 = g.start;
|
} else { if (s[i] == '/') {
|
||||||
for (i < g.end) {
|
append(out, '_'); append(out, 's');
|
||||||
if (!srcs[i].test) {
|
} else {
|
||||||
if (!pkgwrite(fd, "//ww:module-reset\n")) { ok = false; };
|
append(out, s[i]);
|
||||||
let s: str;
|
}; };
|
||||||
if (!pkgread(srcs[i].path, &s) || !pkgwrite(fd, s)
|
|
||||||
|| !pkgwrite(fd, "\n")) { ok = false; };
|
|
||||||
};
|
|
||||||
i += 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
let i: i32 = g.start;
|
|
||||||
for (i < g.end) {
|
|
||||||
if (srcs[i].test && strings.compare(srcs[i].pkg, g.pkg) == 0) {
|
|
||||||
if (!pkgwrite(fd, "//ww:module-reset\n")) { ok = false; };
|
|
||||||
let s: str;
|
|
||||||
if (!pkgread(srcs[i].path, &s) || !pkgwrite(fd, s)
|
|
||||||
|| !pkgwrite(fd, "\n")) { ok = false; };
|
|
||||||
};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
os.close(fd);
|
return strings.frombytes(out);
|
||||||
return ok;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn pkgworkkey(dir: str, pkg: str) str = {
|
fn pkgworkkey(dir: str, pkg: str) str = {
|
||||||
let s: str = strings.dup(strings.concat(dir, "_", pkg));
|
return strings.concat("d_", pkgworkescape(dir), "_p_",
|
||||||
let b: []u8 = strings.toutf8(s);
|
pkgworkescape(pkg));
|
||||||
let i: i32 = 0;
|
|
||||||
for (i < b.len) {
|
|
||||||
if (b[i] == '/') { b[i] = '_'; };
|
|
||||||
i += 1;
|
|
||||||
};
|
|
||||||
return strings.frombytes(b);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn pkgsetpaths(g: *pkggroup, root: str, index: i32,
|
fn pkgsetpaths(g: *pkggroup, root: str, index: i32,
|
||||||
@@ -552,7 +515,6 @@ fn pkgsetpaths(g: *pkggroup, root: str, index: i32,
|
|||||||
let num: str = strconv.i32tos(index, strconv.base.DEC);
|
let num: str = strconv.i32tos(index, strconv.base.DEC);
|
||||||
g.root = strings.concat(root, "/group-", num);
|
g.root = strings.concat(root, "/group-", num);
|
||||||
if (!pkgmakedir(g.root)) { return false; };
|
if (!pkgmakedir(g.root)) { return false; };
|
||||||
g.combined = strings.concat(g.root, "/package.ww");
|
|
||||||
// A caller-owned persistent workdir root keys one driver -w dir
|
// A caller-owned persistent workdir root keys one driver -w dir
|
||||||
// per (dir, pkg) group; the driver's content-identity contract
|
// per (dir, pkg) group; the driver's content-identity contract
|
||||||
// owns every reuse decision, so this stays a pure path policy.
|
// owns every reuse decision, so this stays a pure path policy.
|
||||||
@@ -615,14 +577,16 @@ fn pkgreportcommand(kind: str, g: *pkggroup, r: *exec.result) void = {
|
|||||||
|
|
||||||
fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str,
|
fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str,
|
||||||
h: *exec.process) void = {
|
h: *exec.process) void = {
|
||||||
let ba: []str = alloc([], (11 + includes.len * 2): u64)!;
|
let ba: []str = alloc([], (13 + includes.len * 2): u64)!;
|
||||||
append(ba, builder);
|
append(ba, builder);
|
||||||
append(ba, "test");
|
append(ba, "test");
|
||||||
append(ba, "-c");
|
append(ba, "-c");
|
||||||
append(ba, "-o");
|
append(ba, "-o");
|
||||||
append(ba, g.bin);
|
append(ba, g.bin);
|
||||||
append(ba, "-I");
|
append(ba, "--ww-package-test");
|
||||||
append(ba, g.dir);
|
if (g.external) { append(ba, "external"); }
|
||||||
|
else { append(ba, "same"); };
|
||||||
|
append(ba, g.pkg);
|
||||||
let ii: i32 = 0;
|
let ii: i32 = 0;
|
||||||
for (ii < includes.len) {
|
for (ii < includes.len) {
|
||||||
append(ba, "-I");
|
append(ba, "-I");
|
||||||
@@ -633,7 +597,7 @@ fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str,
|
|||||||
append(ba, "-w");
|
append(ba, "-w");
|
||||||
append(ba, g.workdir);
|
append(ba, g.workdir);
|
||||||
};
|
};
|
||||||
append(ba, g.combined);
|
append(ba, g.dir);
|
||||||
let bcmd: exec.command;
|
let bcmd: exec.command;
|
||||||
bcmd.path = builder;
|
bcmd.path = builder;
|
||||||
bcmd.argv = ba;
|
bcmd.argv = ba;
|
||||||
@@ -675,6 +639,12 @@ fn pkgbuildok(g: *pkggroup) bool = {
|
|||||||
&& g.buildres.code == 0;
|
&& g.buildres.code == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn pkgrunok(g: *pkggroup) bool = {
|
||||||
|
return g.runres.errno == 0 && g.runres.cleanuperrno == 0
|
||||||
|
&& g.runres.termination == exec.termination.EXIT
|
||||||
|
&& g.runres.code == 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Ordered emission: a group's captures, diagnostics, and verdict line are
|
// Ordered emission: a group's captures, diagnostics, and verdict line are
|
||||||
// written only here, strictly in group order, so concurrent scheduling
|
// written only here, strictly in group order, so concurrent scheduling
|
||||||
// produces the byte stream sequential scheduling produced.
|
// produces the byte stream sequential scheduling produced.
|
||||||
@@ -683,10 +653,6 @@ fn pkgemitgroup(g: *pkggroup, tmproot: str, compileonly: bool) bool = {
|
|||||||
pkgfailpath(tmproot, "cannot create group temporary directory");
|
pkgfailpath(tmproot, "cannot create group temporary directory");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (g.fail == PKGFAILCOMPOSE) {
|
|
||||||
pkgfailpath(g.combined, "cannot compose package test source");
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let buildcaptures: bool = pkgemitfile(g.buildout, os.STDOUT_FILENO);
|
let buildcaptures: bool = pkgemitfile(g.buildout, os.STDOUT_FILENO);
|
||||||
buildcaptures = pkgemitfile(g.builderr, os.STDERR_FILENO) && buildcaptures;
|
buildcaptures = pkgemitfile(g.builderr, os.STDERR_FILENO) && buildcaptures;
|
||||||
if (!buildcaptures) {
|
if (!buildcaptures) {
|
||||||
@@ -704,15 +670,24 @@ fn pkgemitgroup(g: *pkggroup, tmproot: str, compileonly: bool) bool = {
|
|||||||
pkgputln(os.STDOUT_FILENO, g.bin);
|
pkgputln(os.STDOUT_FILENO, g.bin);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
let runcaptures: bool = pkgemitfile(g.runout, os.STDOUT_FILENO);
|
let runstdout: str;
|
||||||
runcaptures = pkgemitfile(g.runerr, os.STDERR_FILENO) && runcaptures;
|
let runstderr: str;
|
||||||
if (!runcaptures) {
|
if (!pkgread(g.runout, &runstdout) || !pkgread(g.runerr, &runstderr)) {
|
||||||
pkgfailpath(g.root, "cannot read test capture");
|
pkgfailpath(g.root, "cannot read test capture");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (g.runres.errno != 0 || g.runres.cleanuperrno != 0
|
// The compiler-owned dispatcher is the authority on whether a variant
|
||||||
|| g.runres.termination != exec.termination.EXIT
|
// contains runnable tests. With an empty generated table, test.run returns
|
||||||
|| g.runres.code != 0) {
|
// zero without output; no coordinator-side source scan is involved.
|
||||||
|
if (pkgrunok(g) && runstdout.len == 0 && runstderr.len == 0) {
|
||||||
|
pkgput(os.STDOUT_FILENO, "? ");
|
||||||
|
pkgput(os.STDOUT_FILENO, g.dir);
|
||||||
|
pkgputln(os.STDOUT_FILENO, " [no tests]");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
pkgput(os.STDOUT_FILENO, runstdout);
|
||||||
|
pkgput(os.STDERR_FILENO, runstderr);
|
||||||
|
if (!pkgrunok(g)) {
|
||||||
pkgreportcommand("test", g, &g.runres);
|
pkgreportcommand("test", g, &g.runres);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -874,15 +849,7 @@ export fn packagecommand(args: []str) int = {
|
|||||||
s.path = ds.paths[i];
|
s.path = ds.paths[i];
|
||||||
s.dir = strings.dup(pkgdirname(ds.paths[i]));
|
s.dir = strings.dup(pkgdirname(ds.paths[i]));
|
||||||
s.pkg = strings.dup(pn);
|
s.pkg = strings.dup(pn);
|
||||||
s.attest = pkgattest(body);
|
|
||||||
s.test = strings.hassuffix(pkgbase(ds.paths[i]), "_test.ww");
|
s.test = strings.hassuffix(pkgbase(ds.paths[i]), "_test.ww");
|
||||||
// Go's contract: only *_test.ww is a test source; @test
|
|
||||||
// anywhere else fails loudly rather than run or drop silently.
|
|
||||||
if (s.attest && !s.test) {
|
|
||||||
pkgfailpath(ds.paths[i],
|
|
||||||
"@test declaration outside *_test.ww");
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
append(srcs, s);
|
append(srcs, s);
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
@@ -895,15 +862,9 @@ export fn packagecommand(args: []str) int = {
|
|||||||
f.start = i;
|
f.start = i;
|
||||||
f.end = i;
|
f.end = i;
|
||||||
f.prodpkg = "";
|
f.prodpkg = "";
|
||||||
f.hastests = false;
|
|
||||||
for (f.end < srcs.len && strings.compare(srcs[f.end].dir, f.path) == 0) {
|
for (f.end < srcs.len && strings.compare(srcs[f.end].dir, f.path) == 0) {
|
||||||
if (srcs[f.end].test) {
|
if (!srcs[f.end].test && f.prodpkg.len == 0) {
|
||||||
if (srcs[f.end].attest) { f.hastests = true; };
|
|
||||||
} else if (f.prodpkg.len == 0) {
|
|
||||||
f.prodpkg = srcs[f.end].pkg;
|
f.prodpkg = srcs[f.end].pkg;
|
||||||
} else if (strings.compare(f.prodpkg, srcs[f.end].pkg) != 0) {
|
|
||||||
pkgfailpath(f.path, "production sources declare conflicting packages");
|
|
||||||
return 1;
|
|
||||||
};
|
};
|
||||||
f.end += 1;
|
f.end += 1;
|
||||||
};
|
};
|
||||||
@@ -915,21 +876,16 @@ export fn packagecommand(args: []str) int = {
|
|||||||
i = 0;
|
i = 0;
|
||||||
for (i < folders.len) {
|
for (i < folders.len) {
|
||||||
let f: pkgfolder = folders[i];
|
let f: pkgfolder = folders[i];
|
||||||
if (!f.hastests) {
|
|
||||||
pkgput(os.STDOUT_FILENO, "? ");
|
|
||||||
pkgput(os.STDOUT_FILENO, f.path);
|
|
||||||
pkgputln(os.STDOUT_FILENO, " [no tests]");
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let externalpkg: str = "";
|
let externalpkg: str = "";
|
||||||
if (f.prodpkg.len != 0) {
|
if (f.prodpkg.len != 0) {
|
||||||
externalpkg = strings.concat(f.prodpkg, "_test");
|
externalpkg = strings.concat(f.prodpkg, "_test");
|
||||||
};
|
};
|
||||||
let standalonepkg: str = "";
|
let standalonepkg: str = "";
|
||||||
|
let sawtestfile: bool = false;
|
||||||
let j: i32 = f.start;
|
let j: i32 = f.start;
|
||||||
for (j < f.end) {
|
for (j < f.end) {
|
||||||
if (srcs[j].test) {
|
if (srcs[j].test) {
|
||||||
|
sawtestfile = true;
|
||||||
if (f.prodpkg.len != 0
|
if (f.prodpkg.len != 0
|
||||||
&& strings.compare(srcs[j].pkg, f.prodpkg) != 0
|
&& strings.compare(srcs[j].pkg, f.prodpkg) != 0
|
||||||
&& strings.compare(srcs[j].pkg, externalpkg) != 0) {
|
&& strings.compare(srcs[j].pkg, externalpkg) != 0) {
|
||||||
@@ -947,7 +903,6 @@ export fn packagecommand(args: []str) int = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (!srcs[j].attest) { j += 1; continue; };
|
|
||||||
let found: bool = false;
|
let found: bool = false;
|
||||||
let k: i32 = 0;
|
let k: i32 = 0;
|
||||||
for (k < groups.len) {
|
for (k < groups.len) {
|
||||||
@@ -962,9 +917,6 @@ export fn packagecommand(args: []str) int = {
|
|||||||
let g: pkggroup;
|
let g: pkggroup;
|
||||||
g.dir = f.path;
|
g.dir = f.path;
|
||||||
g.pkg = srcs[j].pkg;
|
g.pkg = srcs[j].pkg;
|
||||||
g.start = f.start;
|
|
||||||
g.end = f.end;
|
|
||||||
g.prodpkg = f.prodpkg;
|
|
||||||
g.external = f.prodpkg.len != 0
|
g.external = f.prodpkg.len != 0
|
||||||
&& strings.compare(f.prodpkg, g.pkg) != 0;
|
&& strings.compare(f.prodpkg, g.pkg) != 0;
|
||||||
append(groups, g);
|
append(groups, g);
|
||||||
@@ -972,6 +924,14 @@ export fn packagecommand(args: []str) int = {
|
|||||||
};
|
};
|
||||||
j += 1;
|
j += 1;
|
||||||
};
|
};
|
||||||
|
if (!sawtestfile) {
|
||||||
|
let g: pkggroup;
|
||||||
|
g.dir = f.path;
|
||||||
|
g.pkg = f.prodpkg;
|
||||||
|
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
|
||||||
|
g.external = false;
|
||||||
|
append(groups, g);
|
||||||
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
if (groups.len == 0) { return 0; };
|
if (groups.len == 0) { return 0; };
|
||||||
@@ -1012,9 +972,6 @@ export fn packagecommand(args: []str) int = {
|
|||||||
g.fail = PKGFAILSETUP;
|
g.fail = PKGFAILSETUP;
|
||||||
g.state = PKGDONE;
|
g.state = PKGDONE;
|
||||||
stopped = true;
|
stopped = true;
|
||||||
} else if (!pkgcombined(g, srcs)) {
|
|
||||||
g.fail = PKGFAILCOMPOSE;
|
|
||||||
g.state = PKGDONE;
|
|
||||||
} else {
|
} else {
|
||||||
pkgstartbuild(g, builder, includes,
|
pkgstartbuild(g, builder, includes,
|
||||||
&handles[launched]);
|
&handles[launched]);
|
||||||
|
|||||||
@@ -353,21 +353,20 @@ fn locateimport(dirs: *u8, name: *u8, namelen: u64,
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Go's contract: only *_test.ww is a test source. A line-leading @test
|
// Go's contract: only *_test.ww is a test source. Ask the compiler parser,
|
||||||
// declaration anywhere else would be silently dropped by a non-T build
|
// rather than a textual attribute scan, whether a production source contains
|
||||||
// (#6, the Hare model), so directory enumeration rejects it loudly.
|
// @test; otherwise valid whitespace/comments could silently drop a test.
|
||||||
// This is the wwstage twin of cmd/ww/main.c:file_has_line_test.
|
fn dirfileattest(dirpath: *u8, name: *u8) i32 = {
|
||||||
fn dirfileattest(dirpath: *u8, name: *u8) bool = {
|
|
||||||
let path: *u8 = joinpath(dirpath, name);
|
let path: *u8 = joinpath(dirpath, name);
|
||||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return false; };
|
if (fd < 0) { return -1; };
|
||||||
let sr: (i64 | os.oserror) = os.filesize(fd);
|
let sr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = -1i64;
|
let n: i64 = -1i64;
|
||||||
match (sr) {
|
match (sr) {
|
||||||
case let v: i64 => n = v;
|
case let v: i64 => n = v;
|
||||||
case let e: os.oserror => { os.close(fd); return false; };
|
case let e: os.oserror => { os.close(fd); return -1; };
|
||||||
};
|
};
|
||||||
if (n <= 0i64) { os.close(fd); return false; };
|
if (n < 0i64) { os.close(fd); return -1; };
|
||||||
let b: []u8 = alloc([], n: u64)!;
|
let b: []u8 = alloc([], n: u64)!;
|
||||||
b.len = n: i32;
|
b.len = n: i32;
|
||||||
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
|
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
|
||||||
@@ -375,29 +374,55 @@ fn dirfileattest(dirpath: *u8, name: *u8) bool = {
|
|||||||
let got: i64 = -1i64;
|
let got: i64 = -1i64;
|
||||||
match (rr) {
|
match (rr) {
|
||||||
case let v: i64 => got = v;
|
case let v: i64 => got = v;
|
||||||
case let e: os.oserror => return false;
|
case let e: os.oserror => return -1;
|
||||||
};
|
};
|
||||||
if (got != n) { return false; };
|
if (got != n) { return -1; };
|
||||||
let i: i32 = 0;
|
// Preserve the directory loader's normalized package-clause diagnostic
|
||||||
for (i < b.len) {
|
// before the full parser performs language-level recovery. This is also
|
||||||
for (i < b.len && (b[i] == ' ' || b[i] == '\t'
|
// the C/WW parity boundary for malformed clauses.
|
||||||
|| b[i] == '\r')) { i += 1; };
|
let il: syntax.lex;
|
||||||
if (i + 5 < b.len && b[i] == '@' && b[i + 1] == 't'
|
syntax.lexinit(&il, pathstr(path), b.ptr, n: u64);
|
||||||
&& b[i + 2] == 'e' && b[i + 3] == 's'
|
let ips: syntax.parser;
|
||||||
&& b[i + 4] == 't'
|
syntax.parserinit(&ips, &il);
|
||||||
&& (b[i + 5] == ' ' || b[i + 5] == '\t')) {
|
let imports: *syntax.node = syntax.parseimports(&ips);
|
||||||
return true;
|
if (il.errs > 0 || ips.errs > 0) { return -1; };
|
||||||
|
if (imports.nmod.len == 0) {
|
||||||
|
cerrpos(pathstr(path), 1, 1);
|
||||||
|
cerr(": error: invalid or missing package clause\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
let l: syntax.lex;
|
||||||
|
syntax.lexinit(&l, pathstr(path), b.ptr, n: u64);
|
||||||
|
let ps: syntax.parser;
|
||||||
|
syntax.parserinit(&ps, &l);
|
||||||
|
let f: *syntax.node = syntax.parsefile(&ps);
|
||||||
|
if (l.errs > 0 || ps.errs > 0) { return -1; };
|
||||||
|
let d: *syntax.node = f.list;
|
||||||
|
for (d != nil) {
|
||||||
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
||||||
|
let at: *syntax.node = d.attr;
|
||||||
|
for (at != nil) {
|
||||||
|
if (at.kind == syntax.nkind.N_ATTR
|
||||||
|
&& syntax.streq(at.str, "test")) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
at = at.next;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
for (i < b.len && b[i] != '\n') { i += 1; };
|
d = d.next;
|
||||||
if (i < b.len) { i += 1; };
|
|
||||||
};
|
};
|
||||||
return false;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classify a directory entry for production enumeration: 1 keep,
|
def SEP_VARIANT_PRODUCTION: i32 = 0;
|
||||||
// 0 skip (non-source or *_test.ww), -1 @test outside *_test.ww
|
def SEP_VARIANT_SAME_TEST: i32 = 1;
|
||||||
// (caller diagnoses and fails).
|
def SEP_VARIANT_EXTERNAL: i32 = 2;
|
||||||
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
|
||||||
|
|
||||||
|
// Classify a selected directory entry: 1 production, 2 test, 0 skipped,
|
||||||
|
// -1 @test outside *_test.ww, -2 non-regular source.
|
||||||
|
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64,
|
||||||
|
variant: i32) i32 = {
|
||||||
// nlen<=3 guard kept: a bare ".ww" (len 3) is rejected here but
|
// nlen<=3 guard kept: a bare ".ww" (len 3) is rejected here but
|
||||||
// would pass strings.hassuffix(".ww"); preserves cstage parity.
|
// would pass strings.hassuffix(".ww"); preserves cstage parity.
|
||||||
if (nlen <= 3u64) { return 0; };
|
if (nlen <= 3u64) { return 0; };
|
||||||
@@ -405,7 +430,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
|||||||
s.ptr = name;
|
s.ptr = name;
|
||||||
s.len = nlen: i32;
|
s.len = nlen: i32;
|
||||||
if (!strings.hassuffix(s, ".ww")) { return 0; };
|
if (!strings.hassuffix(s, ".ww")) { return 0; };
|
||||||
if (strings.hassuffix(s, "_test.ww")) { return 0; };
|
let istest: bool = strings.hassuffix(s, "_test.ww");
|
||||||
|
if (istest && variant == SEP_VARIANT_PRODUCTION) { return 0; };
|
||||||
|
if (!istest && variant == SEP_VARIANT_EXTERNAL) { return 0; };
|
||||||
let source: *u8 = joinpath(dirpath, name);
|
let source: *u8 = joinpath(dirpath, name);
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
|
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
|
||||||
@@ -422,10 +449,45 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
|
|||||||
cerr(": package source is not a regular file\n");
|
cerr(": package source is not a regular file\n");
|
||||||
return -2;
|
return -2;
|
||||||
};
|
};
|
||||||
if (dirfileattest(dirpath, name)) { return -1; };
|
if (!istest) {
|
||||||
|
let attest: i32 = dirfileattest(dirpath, name);
|
||||||
|
if (attest < 0) { return -2; };
|
||||||
|
if (attest > 0) { return -1; };
|
||||||
|
};
|
||||||
|
if (istest) { return 2; };
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn dirpackagename(path: *u8) *u8 = {
|
||||||
|
let view: str;
|
||||||
|
view.ptr = path;
|
||||||
|
view.len = cstrlen(path): i32;
|
||||||
|
let bufp: *u8;
|
||||||
|
let blen: u64;
|
||||||
|
bufp, blen = slurp(path);
|
||||||
|
if (bufp == nil) {
|
||||||
|
cerr("ww: cannot read source\n");
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
let l: syntax.lex;
|
||||||
|
syntax.lexinit(&l, view, bufp, blen);
|
||||||
|
let ps: syntax.parser;
|
||||||
|
syntax.parserinit(&ps, &l);
|
||||||
|
let imports: *syntax.node = syntax.parseimports(&ps);
|
||||||
|
if (l.errs > 0 || ps.errs > 0) { return nil; };
|
||||||
|
if (imports.nmod.len == 0) {
|
||||||
|
cerrpos(view, 1, 1);
|
||||||
|
cerr(": error: invalid or missing package clause\n");
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
if (imports.nmod.len >= 256) {
|
||||||
|
cerrpos(imports.file, imports.line, imports.col);
|
||||||
|
cerr(": error: package name is too long\n");
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
return arenadupcstr(imports.nmod.ptr, imports.nmod.len: u64);
|
||||||
|
};
|
||||||
|
|
||||||
// Rule-10 byte-id requires cstage and wwstage sort the same way;
|
// Rule-10 byte-id requires cstage and wwstage sort the same way;
|
||||||
// memcmp is the locale-independent total order (mirrors
|
// memcmp is the locale-independent total order (mirrors
|
||||||
// ref/hare/sort/cmp/cmp.ha strs).
|
// ref/hare/sort/cmp/cmp.ha strs).
|
||||||
@@ -445,12 +507,10 @@ fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// enumeratedir — list production *.ww paths of `dirpath` (less *_test.ww
|
// Enumerate a production, same-test, or external-test directory variant.
|
||||||
// test sources), byte-sort. A line-leading @test in any other source is
|
// Production files precede matching test files; each partition is byte-sorted.
|
||||||
// diagnosed here and returns -2. Returns one exact pointer array of
|
fn enumeratedir(dirpath: *u8, variant: i32,
|
||||||
// NUL-terminated full paths. This is the sole directory-membership
|
testpackage: *u8) (**u8, i32) = {
|
||||||
// discovery path; the owning seppkg retains the list.
|
|
||||||
fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|
||||||
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
|
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
|
||||||
if (fd < 0) { return nil: **u8, -1; };
|
if (fd < 0) { return nil: **u8, -1; };
|
||||||
// #65: grow-dynamic (mirror cstage enumerate_dir_ww realloc-doubling,
|
// #65: grow-dynamic (mirror cstage enumerate_dir_ww realloc-doubling,
|
||||||
@@ -460,6 +520,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
let cap: i32 = 8;
|
let cap: i32 = 8;
|
||||||
let names: []*u8 = alloc([], cap: u64)!;
|
let names: []*u8 = alloc([], cap: u64)!;
|
||||||
let nlens: []u64 = alloc([], cap: u64)!;
|
let nlens: []u64 = alloc([], cap: u64)!;
|
||||||
|
let kinds: []i32 = alloc([], cap: u64)!;
|
||||||
let n: i32 = 0;
|
let n: i32 = 0;
|
||||||
let buf: []u8 = alloc([], 8192u64)!;
|
let buf: []u8 = alloc([], 8192u64)!;
|
||||||
buf.len = 8192;
|
buf.len = 8192;
|
||||||
@@ -473,7 +534,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
let reclen: u64 = blo + (bhi * 256u64);
|
let reclen: u64 = blo + (bhi * 256u64);
|
||||||
let nm: *u8 = buf.ptr + off + 19u64;
|
let nm: *u8 = buf.ptr + off + 19u64;
|
||||||
let nl: u64 = cstrlen(nm);
|
let nl: u64 = cstrlen(nm);
|
||||||
let cls: i32 = dirfileclass(dirpath, nm, nl);
|
let cls: i32 = dirfileclass(dirpath, nm, nl, variant);
|
||||||
if (cls == -1) {
|
if (cls == -1) {
|
||||||
cerr("ww: ");
|
cerr("ww: ");
|
||||||
cerr(pathstr(joinpath(dirpath, nm)));
|
cerr(pathstr(joinpath(dirpath, nm)));
|
||||||
@@ -486,23 +547,38 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
return nil: **u8, -2;
|
return nil: **u8, -2;
|
||||||
};
|
};
|
||||||
if (cls > 0) {
|
if (cls > 0) {
|
||||||
|
let full: *u8 = joinpath(dirpath, nm);
|
||||||
|
if (cls == 2) {
|
||||||
|
let pn: *u8 = dirpackagename(full);
|
||||||
|
if (pn == nil) {
|
||||||
|
os.close(fd);
|
||||||
|
return nil: **u8, -2;
|
||||||
|
};
|
||||||
|
if (testpackage == nil || !cstreq(pn, testpackage)) {
|
||||||
|
off += reclen;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
};
|
||||||
if (n >= cap) {
|
if (n >= cap) {
|
||||||
let ncap: i32 = cap * 2;
|
let ncap: i32 = cap * 2;
|
||||||
let nn: []*u8 = alloc([], ncap: u64)!;
|
let nn: []*u8 = alloc([], ncap: u64)!;
|
||||||
let nl2: []u64 = alloc([], ncap: u64)!;
|
let nl2: []u64 = alloc([], ncap: u64)!;
|
||||||
|
let nk: []i32 = alloc([], ncap: u64)!;
|
||||||
let k: i32 = 0;
|
let k: i32 = 0;
|
||||||
for (k < n) {
|
for (k < n) {
|
||||||
nn[k] = names[k];
|
nn[k] = names[k];
|
||||||
nl2[k] = nlens[k];
|
nl2[k] = nlens[k];
|
||||||
|
nk[k] = kinds[k];
|
||||||
k += 1;
|
k += 1;
|
||||||
};
|
};
|
||||||
names = nn;
|
names = nn;
|
||||||
nlens = nl2;
|
nlens = nl2;
|
||||||
|
kinds = nk;
|
||||||
cap = ncap;
|
cap = ncap;
|
||||||
};
|
};
|
||||||
let full: *u8 = joinpath(dirpath, nm);
|
|
||||||
names[n] = full;
|
names[n] = full;
|
||||||
nlens[n] = cstrlen(full);
|
nlens[n] = cstrlen(full);
|
||||||
|
kinds[n] = cls;
|
||||||
n += 1;
|
n += 1;
|
||||||
};
|
};
|
||||||
off += reclen;
|
off += reclen;
|
||||||
@@ -524,8 +600,12 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
for (i < n) {
|
for (i < n) {
|
||||||
let j: i32 = i;
|
let j: i32 = i;
|
||||||
for (j > 0) {
|
for (j > 0) {
|
||||||
if (bytecmp(names[j - 1], nlens[j - 1],
|
let c: i32 = kinds[j - 1] - kinds[j];
|
||||||
names[j], nlens[j]) <= 0) { j = 0; }
|
if (c == 0) {
|
||||||
|
c = bytecmp(names[j - 1], nlens[j - 1],
|
||||||
|
names[j], nlens[j]);
|
||||||
|
};
|
||||||
|
if (c <= 0) { j = 0; }
|
||||||
else {
|
else {
|
||||||
let t: *u8 = names[j];
|
let t: *u8 = names[j];
|
||||||
names[j] = names[j - 1];
|
names[j] = names[j - 1];
|
||||||
@@ -533,6 +613,9 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
let tl: u64 = nlens[j];
|
let tl: u64 = nlens[j];
|
||||||
nlens[j] = nlens[j - 1];
|
nlens[j] = nlens[j - 1];
|
||||||
nlens[j - 1] = tl;
|
nlens[j - 1] = tl;
|
||||||
|
let tk: i32 = kinds[j];
|
||||||
|
kinds[j] = kinds[j - 1];
|
||||||
|
kinds[j - 1] = tk;
|
||||||
j -= 1;
|
j -= 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -541,6 +624,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
||||||
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
||||||
|
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
|
||||||
return nil: **u8, 0;
|
return nil: **u8, 0;
|
||||||
};
|
};
|
||||||
let exact: []*u8 = alloc([], n: u64)!;
|
let exact: []*u8 = alloc([], n: u64)!;
|
||||||
@@ -551,6 +635,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
|
|||||||
// shape correct for the driver's allocations.
|
// shape correct for the driver's allocations.
|
||||||
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
|
||||||
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
|
||||||
|
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
|
||||||
return exact.ptr, n;
|
return exact.ptr, n;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -637,9 +722,12 @@ type seppkg = struct {
|
|||||||
path: *u8, // dotted import path, NUL-term; root path[0]==0
|
path: *u8, // dotted import path, NUL-term; root path[0]==0
|
||||||
entry: *u8, // resolved package dir (or file, file root), NUL-term
|
entry: *u8, // resolved package dir (or file, file root), NUL-term
|
||||||
name: *u8, // validated declared name; directory packages only
|
name: *u8, // validated declared name; directory packages only
|
||||||
sources: **u8, // owned, byte-sorted production paths; dirs only
|
testpackage: *u8,
|
||||||
|
sources: **u8, // owned, byte-sorted selected paths; dirs only
|
||||||
nsources: i32,
|
nsources: i32,
|
||||||
isdir: i32,
|
isdir: i32,
|
||||||
|
variant: i32,
|
||||||
|
testsupport: bool,
|
||||||
deps: []i32, // direct-dep indices into sepgraph.pkg
|
deps: []i32, // direct-dep indices into sepgraph.pkg
|
||||||
ndeps: i32,
|
ndeps: i32,
|
||||||
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
|
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
|
||||||
@@ -650,15 +738,26 @@ type sepgraph = struct {
|
|||||||
n: i32,
|
n: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
|
||||||
|
isdir: i32, variant: i32, testpackage: *u8) i32 = {
|
||||||
if (cstrlen(path) >= 256u64) {
|
if (cstrlen(path) >= 256u64) {
|
||||||
cerr("ww: package path is too long (limit 255 bytes)\n");
|
cerr("ww: package path is too long (limit 255 bytes)\n");
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.n) {
|
for (i < g.n) {
|
||||||
|
let testproductionpair: bool = i == 0
|
||||||
|
&& g.pkg[i].variant != SEP_VARIANT_PRODUCTION
|
||||||
|
&& variant == SEP_VARIANT_PRODUCTION && isdir != 0
|
||||||
|
&& g.pkg[i].isdir != 0
|
||||||
|
&& os.samefile(pathstr(g.pkg[i].entry), pathstr(entry));
|
||||||
if (cstreq(g.pkg[i].path, path)) {
|
if (cstreq(g.pkg[i].path, path)) {
|
||||||
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
if (testproductionpair) {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))
|
||||||
|
|| g.pkg[i].variant != variant) {
|
||||||
cerr("ww: package "); cerr(pathstr(path));
|
cerr("ww: package "); cerr(pathstr(path));
|
||||||
cerr(" resolves to more than one location\n");
|
cerr(" resolves to more than one location\n");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -666,6 +765,10 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
|||||||
return i;
|
return i;
|
||||||
};
|
};
|
||||||
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
|
||||||
|
if (testproductionpair) {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
cerr("ww: package directory "); cerr(pathstr(entry));
|
cerr("ww: package directory "); cerr(pathstr(entry));
|
||||||
cerr(" has identities ");
|
cerr(" has identities ");
|
||||||
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
|
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
|
||||||
@@ -687,9 +790,16 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
|||||||
g.pkg[g.n].path = arenadupcstr(path, plen);
|
g.pkg[g.n].path = arenadupcstr(path, plen);
|
||||||
g.pkg[g.n].entry = arenadupcstr(entry, elen);
|
g.pkg[g.n].entry = arenadupcstr(entry, elen);
|
||||||
g.pkg[g.n].name = nil;
|
g.pkg[g.n].name = nil;
|
||||||
|
g.pkg[g.n].testpackage = nil;
|
||||||
|
if (testpackage != nil) {
|
||||||
|
g.pkg[g.n].testpackage = arenadupcstr(testpackage,
|
||||||
|
cstrlen(testpackage));
|
||||||
|
};
|
||||||
g.pkg[g.n].sources = nil;
|
g.pkg[g.n].sources = nil;
|
||||||
g.pkg[g.n].nsources = 0;
|
g.pkg[g.n].nsources = 0;
|
||||||
g.pkg[g.n].isdir = isdir;
|
g.pkg[g.n].isdir = isdir;
|
||||||
|
g.pkg[g.n].variant = variant;
|
||||||
|
g.pkg[g.n].testsupport = false;
|
||||||
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
|
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
|
||||||
dslot.len = SEP_MAXPKG;
|
dslot.len = SEP_MAXPKG;
|
||||||
g.pkg[g.n].deps = dslot;
|
g.pkg[g.n].deps = dslot;
|
||||||
@@ -700,6 +810,11 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||||
|
return sepfindoraddvariant(g, path, entry, isdir,
|
||||||
|
SEP_VARIANT_PRODUCTION, nil);
|
||||||
|
};
|
||||||
|
|
||||||
// Release the package-owned directory-membership lists through one graph
|
// Release the package-owned directory-membership lists through one graph
|
||||||
// cleanup function. rt_free is a no-op in today's no-free runtime, but this
|
// cleanup function. rt_free is a no-op in today's no-free runtime, but this
|
||||||
// records the same ownership boundary as the C bootstrap twin.
|
// records the same ownership boundary as the C bootstrap twin.
|
||||||
@@ -741,6 +856,36 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn sepexternalname(pkg: *seppkg, path: *u8, n: u64,
|
||||||
|
leafonly: bool) bool = {
|
||||||
|
if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let begin: u64 = 0u64;
|
||||||
|
if (leafonly) {
|
||||||
|
let i: u64 = 0u64;
|
||||||
|
for (i < n) {
|
||||||
|
if (path[i] == '.') { begin = i + 1u64; };
|
||||||
|
i += 1u64;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let leafn: u64 = n - begin;
|
||||||
|
let tn: u64 = cstrlen(pkg.testpackage);
|
||||||
|
if (tn != leafn + 5u64) { return false; };
|
||||||
|
if (bytecmp(pkg.testpackage, leafn, path + begin, leafn) != 0) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return pkg.testpackage[leafn] == '_'
|
||||||
|
&& pkg.testpackage[leafn + 1u64] == 't'
|
||||||
|
&& pkg.testpackage[leafn + 2u64] == 'e'
|
||||||
|
&& pkg.testpackage[leafn + 3u64] == 's'
|
||||||
|
&& pkg.testpackage[leafn + 4u64] == 't';
|
||||||
|
};
|
||||||
|
|
||||||
|
fn sepexternalproduction(pkg: *seppkg, path: *u8, n: u64) bool = {
|
||||||
|
return sepexternalname(pkg, path, n, false);
|
||||||
|
};
|
||||||
|
|
||||||
// Scan one already-selected source file for its leading package clause
|
// Scan one already-selected source file for its leading package clause
|
||||||
// (when it is an owned directory source) and top-level imports. A DIRECTORY
|
// (when it is an owned directory source) and top-level imports. A DIRECTORY
|
||||||
// import is a package boundary: add as a direct dep of pi. A FILE import is an
|
// import is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||||
@@ -776,6 +921,11 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
|
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
|
||||||
let declared: *u8 = imports.nmod.ptr;
|
let declared: *u8 = imports.nmod.ptr;
|
||||||
let declaredn: u64 = imports.nmod.len: u64;
|
let declaredn: u64 = imports.nmod.len: u64;
|
||||||
|
if (declaredn >= 256u64) {
|
||||||
|
cerrpos(imports.file, imports.line, imports.col);
|
||||||
|
cerr(": error: package name is too long\n");
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
if (g.pkg[pi].name == nil) {
|
if (g.pkg[pi].name == nil) {
|
||||||
g.pkg[pi].name = arenadupcstr(declared, declaredn);
|
g.pkg[pi].name = arenadupcstr(declared, declaredn);
|
||||||
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
|
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
|
||||||
@@ -852,10 +1002,24 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
let externalproduction: bool = sepexternalproduction(
|
||||||
|
&g.pkg[pi], idp, idn);
|
||||||
|
let ipath: *u8 = nil;
|
||||||
|
if (externalproduction) {
|
||||||
|
ipath = g.pkg[pi].entry;
|
||||||
|
isdir = 1;
|
||||||
|
} else {
|
||||||
|
ipath = locateimport(searchpath, idp, idn, &isdir);
|
||||||
|
};
|
||||||
if (ipath != nil) {
|
if (ipath != nil) {
|
||||||
if (isdir != 0) {
|
if (isdir != 0) {
|
||||||
if (os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry))) {
|
let self: bool = os.samefile(pathstr(ipath),
|
||||||
|
pathstr(g.pkg[pi].entry));
|
||||||
|
if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) {
|
||||||
|
externalproduction = true;
|
||||||
|
};
|
||||||
|
if (self
|
||||||
|
&& !externalproduction) {
|
||||||
cerrpos(u.file, u.line, u.col);
|
cerrpos(u.file, u.line, u.col);
|
||||||
cerr(": error: self-import: package '");
|
cerr(": error: self-import: package '");
|
||||||
if (g.pkg[pi].path[0u64] != 0u8) {
|
if (g.pkg[pi].path[0u64] != 0u8) {
|
||||||
@@ -931,7 +1095,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
|||||||
if (g.pkg[pi].isdir != 0) {
|
if (g.pkg[pi].isdir != 0) {
|
||||||
let sources: **u8;
|
let sources: **u8;
|
||||||
let nsources: i32;
|
let nsources: i32;
|
||||||
sources, nsources = enumeratedir(g.pkg[pi].entry);
|
sources, nsources = enumeratedir(g.pkg[pi].entry,
|
||||||
|
g.pkg[pi].variant, g.pkg[pi].testpackage);
|
||||||
g.pkg[pi].sources = sources;
|
g.pkg[pi].sources = sources;
|
||||||
g.pkg[pi].nsources = nsources;
|
g.pkg[pi].nsources = nsources;
|
||||||
if (g.pkg[pi].nsources == -2) {
|
if (g.pkg[pi].nsources == -2) {
|
||||||
@@ -956,7 +1121,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
|||||||
};
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8) {
|
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8
|
||||||
|
&& !g.pkg[pi].testsupport) {
|
||||||
let plen: u64 = cstrlen(g.pkg[pi].path);
|
let plen: u64 = cstrlen(g.pkg[pi].path);
|
||||||
let leaf: *u8 = g.pkg[pi].path;
|
let leaf: *u8 = g.pkg[pi].path;
|
||||||
let j: u64 = 0u64;
|
let j: u64 = 0u64;
|
||||||
@@ -1056,7 +1222,7 @@ fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
||||||
modpath: *u8) i32 = {
|
modpath: *u8, pkg: *seppkg) i32 = {
|
||||||
let pview: str;
|
let pview: str;
|
||||||
pview.ptr = path;
|
pview.ptr = path;
|
||||||
pview.len = cstrlen(path): i32;
|
pview.len = cstrlen(path): i32;
|
||||||
@@ -1114,12 +1280,16 @@ fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
|
|||||||
u = uses[ui];
|
u = uses[ui];
|
||||||
let idp: *u8 = u.usepath.ptr;
|
let idp: *u8 = u.usepath.ptr;
|
||||||
let idn: u64 = u.usepath.len: u64;
|
let idn: u64 = u.usepath.len: u64;
|
||||||
|
if (sepexternalproduction(pkg, idp, idn)) {
|
||||||
|
ui += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||||
if (ipath != nil) {
|
if (ipath != nil) {
|
||||||
if (isdir == 0) {
|
if (isdir == 0) {
|
||||||
if (sepemitbody(fd, ipath, visit, searchpath,
|
if (sepemitbody(fd, ipath, visit, searchpath,
|
||||||
modpath) < 0) { return -1; };
|
modpath, pkg) < 0) { return -1; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
ui += 1;
|
ui += 1;
|
||||||
@@ -1195,12 +1365,12 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
|
|||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < g.pkg[pi].nsources && bodyrc == 0) {
|
for (i < g.pkg[pi].nsources && bodyrc == 0) {
|
||||||
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
|
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
|
||||||
g.pkg[pi].path);
|
g.pkg[pi].path, &g.pkg[pi]);
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
|
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
|
||||||
g.pkg[pi].path);
|
g.pkg[pi].path, &g.pkg[pi]);
|
||||||
};
|
};
|
||||||
if (os.close(u) != 0) {
|
if (os.close(u) != 0) {
|
||||||
cerr("ww: cannot close package unit\n");
|
cerr("ww: cannot close package unit\n");
|
||||||
@@ -1405,14 +1575,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
|
|||||||
fn workdirstamptext(istest: i32, emitasm: i32) str = {
|
fn workdirstamptext(istest: i32, emitasm: i32) str = {
|
||||||
if (istest != 0) {
|
if (istest != 0) {
|
||||||
if (emitasm != 0) {
|
if (emitasm != 0) {
|
||||||
return "ww workdir fmt 2 mode test asm 1\n";
|
return "ww workdir fmt 3 mode test asm 1\n";
|
||||||
};
|
};
|
||||||
return "ww workdir fmt 2 mode test asm 0\n";
|
return "ww workdir fmt 3 mode test asm 0\n";
|
||||||
};
|
};
|
||||||
if (emitasm != 0) {
|
if (emitasm != 0) {
|
||||||
return "ww workdir fmt 2 mode build asm 1\n";
|
return "ww workdir fmt 3 mode build asm 1\n";
|
||||||
};
|
};
|
||||||
return "ww workdir fmt 2 mode build asm 0\n";
|
return "ww workdir fmt 3 mode build asm 0\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
fn stampmatches(path: *u8, want: str) bool = {
|
fn stampmatches(path: *u8, want: str) bool = {
|
||||||
@@ -1460,7 +1630,8 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
|
|||||||
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||||
rootidentity: *u8, out: *u8,
|
rootidentity: *u8, out: *u8,
|
||||||
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
||||||
emitasm: i32, workdir: *u8, scratchout: **u8,
|
rootvariant: i32, testpackage: *u8, emitasm: i32,
|
||||||
|
workdir: *u8, scratchout: **u8,
|
||||||
graphout: **sepgraph) i32 = {
|
graphout: **sepgraph) i32 = {
|
||||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||||
@@ -1605,34 +1776,65 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
if (graphout != nil) { *graphout = g; };
|
if (graphout != nil) { *graphout = g; };
|
||||||
let rootpath: *u8 = "\0".ptr;
|
let rootpath: *u8 = "\0".ptr;
|
||||||
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
|
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
|
||||||
let root: i32 = sepfindoradd(g, rootpath, src, entryisdir);
|
let root: i32 = sepfindoraddvariant(g, rootpath, src, entryisdir,
|
||||||
|
rootvariant, testpackage);
|
||||||
if (root < 0) { return 1; };
|
if (root < 0) { return 1; };
|
||||||
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
let testsupportmodule: str = "test";
|
||||||
// files never `import test;`. Inject it as a direct dep of the root so
|
// -T generates a dispatcher whose support qualifier is selected by the
|
||||||
// seploadpkg pulls test + its transitive deps; the producer adds -T to
|
// command. Represent that compiler-generated requirement as a direct root
|
||||||
// the root and `test.run` links against test's `.a` — via the
|
// edge. It normally coalesces with an explicit toolchain `import test`;
|
||||||
// sepscanfile dedup-guarded dep append.
|
// when user source occupies that identity, the reserved graph alias keeps
|
||||||
|
// it distinct. The linker receives the same support archive closure.
|
||||||
if (istest != 0) {
|
if (istest != 0) {
|
||||||
let td: i32 = 0;
|
let td: i32 = 0;
|
||||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
let tp: *u8 = locateimport(dotdotlib.ptr, "test".ptr,
|
||||||
|
"test".len: u64, &td);
|
||||||
if (tp != nil) {
|
if (tp != nil) {
|
||||||
let ti: i32 = sepfindoradd(g, "test\0".ptr, tp, td);
|
let rootissupport: bool = entryisdir != 0
|
||||||
if (ti < 0) { return 1; };
|
&& os.samefile(pathstr(tp), pathstr(src));
|
||||||
let seen: bool = false;
|
let collision: bool = false;
|
||||||
let m: i32 = 0;
|
if (!rootissupport && testpackage != nil) {
|
||||||
for (m < g.pkg[root].ndeps) {
|
collision = cstreqlit(testpackage, "test")
|
||||||
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
|| cstreqlit(testpackage, "test_test");
|
||||||
m += 1;
|
|
||||||
};
|
};
|
||||||
if (!seen) {
|
if (!rootissupport && !collision) {
|
||||||
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
let ud: i32 = 0;
|
||||||
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
let up: *u8 = locateimport(searchpath.ptr, "test".ptr,
|
||||||
g.pkg[root].ndeps += 1;
|
"test".len: u64, &ud);
|
||||||
|
if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) {
|
||||||
|
collision = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (collision) { testsupportmodule = 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, also its one support dependency.
|
||||||
|
if (!rootissupport || rootvariant == SEP_VARIANT_EXTERNAL) {
|
||||||
|
let ti: i32 = sepfindoradd(g,
|
||||||
|
testsupportmodule.ptr, tp, td);
|
||||||
|
if (ti < 0) { return 1; };
|
||||||
|
g.pkg[ti].testsupport = true;
|
||||||
|
let seen: bool = false;
|
||||||
|
let m: i32 = 0;
|
||||||
|
for (m < g.pkg[root].ndeps) {
|
||||||
|
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
||||||
|
m += 1;
|
||||||
|
};
|
||||||
|
if (!seen) {
|
||||||
|
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
||||||
|
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
||||||
|
g.pkg[root].ndeps += 1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||||
|
if (rootvariant != SEP_VARIANT_PRODUCTION
|
||||||
|
&& (testpackage == nil || !cstreq(g.pkg[root].name, testpackage))) {
|
||||||
|
cerr("ww: package-test selector does not match loaded package\n");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
let rootpackage: bool = packageonly != 0;
|
let rootpackage: bool = packageonly != 0;
|
||||||
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
|
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
|
||||||
cerr("ww: -p requires a non-main package\n");
|
cerr("ww: -p requires a non-main package\n");
|
||||||
@@ -1719,11 +1921,21 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
// #79: the root carries -T under `ww test` so w6c
|
// #79: the root carries -T under `ww test` so w6c
|
||||||
// synthesizes the test main; deps never get -T.
|
// synthesizes the test main; deps never get -T.
|
||||||
let roott: bool = (pi == root) && (istest != 0);
|
let roott: bool = (pi == root) && (istest != 0);
|
||||||
|
let supportt: bool = g.pkg[pi].testsupport;
|
||||||
let alen: u64 = 8u64;
|
let alen: u64 = 8u64;
|
||||||
if (!needsexport) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; };
|
||||||
|
if (supportt) { alen += 2u64; };
|
||||||
let argv: []str = alloc([], alen)!;
|
let argv: []str = alloc([], alen)!;
|
||||||
append(argv, "w6c");
|
append(argv, "w6c");
|
||||||
if (roott) { append(argv, "-T"); };
|
if (roott) {
|
||||||
|
append(argv, "-T");
|
||||||
|
append(argv, "--test-support-module");
|
||||||
|
append(argv, testsupportmodule);
|
||||||
|
};
|
||||||
|
if (supportt) {
|
||||||
|
append(argv, "--test-support-module");
|
||||||
|
append(argv, testsupportmodule);
|
||||||
|
};
|
||||||
append(argv, "-c");
|
append(argv, "-c");
|
||||||
if (needsexport) {
|
if (needsexport) {
|
||||||
append(argv, "-I");
|
append(argv, "-I");
|
||||||
@@ -1859,8 +2071,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
|
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
|
||||||
// files after, then libwwrt.a (which still
|
// files after, then libwwrt.a (which still selectively pulls only the
|
||||||
// selectively pulls only the runtime members a live undef needs).
|
// runtime members a live undef needs). A same-test root already contains
|
||||||
|
// its production sources; if that production variant is also reached
|
||||||
|
// through the runtime closure, retain its dependencies but omit its
|
||||||
|
// duplicate archive.
|
||||||
// argv: 3 fixed (w6l,-o,out) + one root object/archive per package
|
// argv: 3 fixed (w6l,-o,out) + one root object/archive per package
|
||||||
// + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
|
// + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
|
||||||
let nldirs: i32 = 0;
|
let nldirs: i32 = 0;
|
||||||
@@ -1882,10 +2097,19 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
let pos: i32 = 3;
|
let pos: i32 = 3;
|
||||||
let li: i32 = norder - 1;
|
let li: i32 = norder - 1;
|
||||||
for (li >= 0) {
|
for (li >= 0) {
|
||||||
|
let pi: i32 = order[li];
|
||||||
|
if (g.pkg[root].variant == SEP_VARIANT_SAME_TEST
|
||||||
|
&& pi != root
|
||||||
|
&& g.pkg[pi].variant == SEP_VARIANT_PRODUCTION
|
||||||
|
&& os.samefile(pathstr(g.pkg[pi].entry),
|
||||||
|
pathstr(g.pkg[root].entry))) {
|
||||||
|
li -= 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
// root: positional `.o` (force-load); deps: `.a` (selective).
|
// root: positional `.o` (force-load); deps: `.a` (selective).
|
||||||
let suf: str = ".a";
|
let suf: str = ".a";
|
||||||
if (order[li] == root) { suf = ".o"; };
|
if (pi == root) { suf = ".o"; };
|
||||||
largv[pos] = sepfname(g, order[li], scratch, suf);
|
largv[pos] = sepfname(g, pi, scratch, suf);
|
||||||
pos += 1;
|
pos += 1;
|
||||||
li -= 1;
|
li -= 1;
|
||||||
};
|
};
|
||||||
@@ -1934,12 +2158,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
|||||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||||
rootidentity: *u8, out: *u8,
|
rootidentity: *u8, out: *u8,
|
||||||
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
|
||||||
emitasm: i32, keepscratch: i32, workdir: *u8) i32 = {
|
rootvariant: i32, testpackage: *u8, emitasm: i32,
|
||||||
|
keepscratch: i32, workdir: *u8) i32 = {
|
||||||
let scratch: *u8 = nil;
|
let scratch: *u8 = nil;
|
||||||
let g: *sepgraph = nil;
|
let g: *sepgraph = nil;
|
||||||
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
|
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
|
||||||
out, objstem,
|
out, objstem,
|
||||||
incs, lf, packageonly, istest, emitasm, workdir, &scratch, &g);
|
incs, lf, packageonly, istest, rootvariant, testpackage,
|
||||||
|
emitasm, workdir, &scratch, &g);
|
||||||
sepgraphfree(g);
|
sepgraphfree(g);
|
||||||
if (keepscratch == 0 && scratch != nil) {
|
if (keepscratch == 0 && scratch != nil) {
|
||||||
if (cstrendswithlit(scratch, ".sepwork")) {
|
if (cstrendswithlit(scratch, ".sepwork")) {
|
||||||
@@ -2248,7 +2474,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
|
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
|
||||||
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
return buildonesep(selfdir, resolved, isdir, rootidentity,
|
||||||
out, objstem, incs.ptr, &lf,
|
out, objstem, incs.ptr, &lf,
|
||||||
packageonly, 0i32, emitasm, 1i32, workdir);
|
packageonly, 0i32, SEP_VARIANT_PRODUCTION, nil,
|
||||||
|
emitasm, 1i32, workdir);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
|
||||||
@@ -2413,7 +2640,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
// The freshly acquired directory owns both main and main.sepwork.
|
// The freshly acquired directory owns both main and main.sepwork.
|
||||||
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
|
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
|
||||||
0i32, 0i32, 0i32, 0i32, nil) != 0) {
|
0i32, 0i32, SEP_VARIANT_PRODUCTION, nil, 0i32, 0i32, nil) != 0) {
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
if (cleanrc != 0 && cleanrc != -2i32) {
|
if (cleanrc != 0 && cleanrc != -2i32) {
|
||||||
cerr("ww: cannot remove temporary output\n");
|
cerr("ww: cannot remove temporary output\n");
|
||||||
@@ -2503,7 +2730,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
|||||||
let keep: i32 = 0;
|
let keep: i32 = 0;
|
||||||
if (outstem != nil) { keep = 1; };
|
if (outstem != nil) { keep = 1; };
|
||||||
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
|
||||||
0i32, 1i32, emitasm, keep, workdir);
|
0i32, 1i32, SEP_VARIANT_PRODUCTION, nil, emitasm, keep, workdir);
|
||||||
if (bres != 0) {
|
if (bres != 0) {
|
||||||
if (owntmp) {
|
if (owntmp) {
|
||||||
let cleanrc: i32 = os.remove(pathstr(outp));
|
let cleanrc: i32 = os.remove(pathstr(outp));
|
||||||
@@ -2590,6 +2817,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let emitasm: i32 = 0;
|
let emitasm: i32 = 0;
|
||||||
let outstem: *u8 = nil;
|
let outstem: *u8 = nil;
|
||||||
let workdir: *u8 = nil;
|
let workdir: *u8 = nil;
|
||||||
|
let packagetestkind: *u8 = nil;
|
||||||
|
let packagetestname: *u8 = nil;
|
||||||
let packageopts: bool = false;
|
let packageopts: bool = false;
|
||||||
let afterdash: bool = false;
|
let afterdash: bool = false;
|
||||||
let i: i32 = start;
|
let i: i32 = start;
|
||||||
@@ -2600,6 +2829,27 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
if (cstreqlit(p, "--")) {
|
if (cstreqlit(p, "--")) {
|
||||||
packageopts = true; afterdash = true; i += 1; continue;
|
packageopts = true; afterdash = true; i += 1; continue;
|
||||||
};
|
};
|
||||||
|
if (cstreqlit(p, "--ww-package-test")) {
|
||||||
|
if (i + 2 >= argc || packagetestkind != nil) {
|
||||||
|
cerr("ww test: --ww-package-test needs kind and package\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
packagetestkind = argv[i + 1];
|
||||||
|
packagetestname = argv[i + 2];
|
||||||
|
let pn: u64 = cstrlen(packagetestname);
|
||||||
|
if ((!cstreqlit(packagetestkind, "same")
|
||||||
|
&& !cstreqlit(packagetestkind, "external"))
|
||||||
|
|| pn == 0u64 || pn >= 256u64
|
||||||
|
|| (cstreqlit(packagetestkind, "external")
|
||||||
|
&& (pn <= 5u64
|
||||||
|
|| !cstrendswithlit(packagetestname,
|
||||||
|
"_test")))) {
|
||||||
|
cerr("ww test: invalid --ww-package-test variant\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
i += 3;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
if (p[1u64] == 73u8) { // '-I'
|
if (p[1u64] == 73u8) { // '-I'
|
||||||
let dir: *u8 = nil;
|
let dir: *u8 = nil;
|
||||||
if (p[2u64] != 0u8) {
|
if (p[2u64] != 0u8) {
|
||||||
@@ -2679,6 +2929,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
cerr("ww test: -S needs -o\n");
|
cerr("ww test: -S needs -o\n");
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
if (packagetestkind != nil && packageopts) {
|
||||||
|
cerr("ww test: package-test variant rejects package options\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
|
||||||
// Go's ./... form: a trailing "..." element is a package-tree
|
// Go's ./... form: a trailing "..." element is a package-tree
|
||||||
// request for the coordinator, never a literal path — recognized
|
// request for the coordinator, never a literal path — recognized
|
||||||
@@ -2692,6 +2946,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
&& target[tlen - 1u64] == '.';
|
&& target[tlen - 1u64] == '.';
|
||||||
};
|
};
|
||||||
if (istree) {
|
if (istree) {
|
||||||
|
if (packagetestkind != nil) {
|
||||||
|
cerr("ww test: package-test variant needs one directory\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
if (emitasm != 0) {
|
if (emitasm != 0) {
|
||||||
cerr("ww test: -S needs a single test file\n");
|
cerr("ww test: -S needs a single test file\n");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -2733,6 +2991,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (isdir == 0) {
|
if (isdir == 0) {
|
||||||
|
if (packagetestkind != nil) {
|
||||||
|
cerr("ww test: package-test variant needs one directory\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
if (packageopts) {
|
if (packageopts) {
|
||||||
cerr("ww test: package options need a directory\n"); return 2;
|
cerr("ww test: package options need a directory\n"); return 2;
|
||||||
};
|
};
|
||||||
@@ -2752,6 +3014,22 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
cerr("ww test: pattern needs a single test file\n");
|
cerr("ww test: pattern needs a single test file\n");
|
||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
if (packagetestkind != nil) {
|
||||||
|
if (compileonly == 0 || outstem == nil) {
|
||||||
|
cerr("ww test: package-test variant needs -c -o\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
let variant: i32 = SEP_VARIANT_EXTERNAL;
|
||||||
|
if (cstreqlit(packagetestkind, "same")) {
|
||||||
|
variant = SEP_VARIANT_SAME_TEST;
|
||||||
|
};
|
||||||
|
let lf: lflags;
|
||||||
|
lf.libdirs = nil; lf.nlibdirs = 0;
|
||||||
|
lf.libs = nil; lf.nlibs = 0;
|
||||||
|
return buildonesep(selfdir, resolved, 1, nil, outstem, outstem,
|
||||||
|
incs.ptr, &lf, 0i32, 1i32, variant, packagetestname,
|
||||||
|
0i32, 1i32, workdir);
|
||||||
|
};
|
||||||
let replacement: *u8 = nil;
|
let replacement: *u8 = nil;
|
||||||
if (resolved != target) { replacement = resolved; };
|
if (resolved != target) { replacement = resolved; };
|
||||||
return execpackagetests(selfdir, argv, argc, start, targetindex,
|
return execpackagetests(selfdir, argv, argc, start, targetindex,
|
||||||
|
|||||||
Reference in New Issue
Block a user