ww: intern canonical directory package actions
This commit is contained in:
582
cmd/ww/main.c
582
cmd/ww/main.c
@@ -324,10 +324,10 @@ source_has_test_decl(const char *path)
|
||||
#define SEP_VARIANT_EXTERNAL 2
|
||||
#define SEP_VARIANT_TEST_MAIN 3
|
||||
#define SEP_ROLE_NORMAL 0
|
||||
#define SEP_ROLE_EXTERNAL_PRODUCTION 1
|
||||
#define SEP_ROLE_TEST_SUPPORT 2
|
||||
#define SEP_ROLE_GENERATED_MAIN 3
|
||||
#define SEP_ROLE_TEST_SUPPORT 1
|
||||
#define SEP_ROLE_GENERATED_MAIN 2
|
||||
#define SEP_TEST_SUPPORT_MODULE "__wwtest"
|
||||
#define SEP_IMPORT_PATH_MAX 256
|
||||
#define SEP_MAXPRODUCT 256
|
||||
#define SEP_MAXCONTEXT (SEP_MAXPRODUCT + 1)
|
||||
|
||||
@@ -510,18 +510,19 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
|
||||
#define SEP_MAXPKG 256
|
||||
|
||||
struct seppkg {
|
||||
char path[256]; /* canonical dotted package identity */
|
||||
char path[512]; /* compiler/import identity; derived from import_base */
|
||||
char import_base[SEP_IMPORT_PATH_MAX]; /* canonical directory import identity */
|
||||
char entry[1024]; /* resolved package dir (or file, for a file root) */
|
||||
char canon[1024]; /* canonical location; never package identity */
|
||||
char artifact[64]; /* non-importable product-root artifact key */
|
||||
char artifact[512]; /* stable non-importable variant artifact key */
|
||||
char name[256]; /* validated declared name; directory packages only */
|
||||
char test_package[256]; /* selected test package; root variants only */
|
||||
char **sources; /* owned, byte-sorted selected paths; dirs only */
|
||||
int nsources;
|
||||
int is_dir;
|
||||
int variant; /* SEP_VARIANT_*; dependencies are production */
|
||||
int role; /* normal, external-production, or test support */
|
||||
int root; /* requested package action (possibly a test variant) */
|
||||
int role; /* normal, reserved test support, or generated main */
|
||||
int root; /* requested usage; never package-action identity */
|
||||
int link_entry; /* package supplies the executable's bare main */
|
||||
int generated_main; /* compiler-owned generated test-main package */
|
||||
int failed; /* discovery/compile failure reaches this action */
|
||||
@@ -547,6 +548,7 @@ struct sepgraph {
|
||||
struct sepcontext context[SEP_MAXCONTEXT];
|
||||
int ncontext;
|
||||
int support_context;
|
||||
int identity_failed; /* command-global canonical identity collision */
|
||||
};
|
||||
|
||||
struct sepproduct {
|
||||
@@ -570,14 +572,116 @@ struct seplinkflags {
|
||||
int nlibs;
|
||||
};
|
||||
|
||||
static int
|
||||
sep_directory_variant(int variant)
|
||||
{
|
||||
return variant == SEP_VARIANT_PRODUCTION
|
||||
|| variant == SEP_VARIANT_SAME_TEST
|
||||
|| variant == SEP_VARIANT_EXTERNAL;
|
||||
}
|
||||
|
||||
static int
|
||||
sep_variant_path(int variant, const char *base, char *out, size_t outsz)
|
||||
{
|
||||
int n;
|
||||
if (variant == SEP_VARIANT_EXTERNAL)
|
||||
n = snprintf(out, outsz, "%s_test", base);
|
||||
else
|
||||
n = snprintf(out, outsz, "%s", base);
|
||||
return n >= 0 && (size_t)n < outsz ? 0 : -1;
|
||||
}
|
||||
|
||||
static void
|
||||
sep_diag_path_locations(const char *path, const char *a, const char *b)
|
||||
{
|
||||
if (strcmp(a, b) > 0) { const char *t = a; a = b; b = t; }
|
||||
fprintf(stderr, "ww: package %s resolves to directories %s and %s\n",
|
||||
path, a, b);
|
||||
}
|
||||
|
||||
static void
|
||||
sep_diag_directory_identities(const char *entry, const char *a, const char *b)
|
||||
{
|
||||
if (strcmp(a, b) > 0) { const char *t = a; a = b; b = t; }
|
||||
fprintf(stderr, "ww: package directory %s has import identities %s and %s\n",
|
||||
entry, a, b);
|
||||
}
|
||||
|
||||
/* Bind the canonical ordinary import identity of one provisional directory
|
||||
* action. The action's compiler path is derived from that base and its semantic
|
||||
* variant; neither requested-root state nor artifact naming participates. */
|
||||
static int
|
||||
sep_bind_import_base(struct sepgraph *g, int pi, const char *base)
|
||||
{
|
||||
struct seppkg *p = &g->pkg[pi];
|
||||
if (base == NULL || base[0] == '\0'
|
||||
|| strlen(base) >= SEP_IMPORT_PATH_MAX) {
|
||||
fprintf(stderr, "ww: package path is too long (limit %d bytes)\n",
|
||||
SEP_IMPORT_PATH_MAX - 1);
|
||||
return -1;
|
||||
}
|
||||
if (p->import_base[0] != '\0') {
|
||||
if (strcmp(p->import_base, base) == 0) return 0;
|
||||
g->identity_failed = 1;
|
||||
sep_diag_directory_identities(p->entry, p->import_base, base);
|
||||
return -1;
|
||||
}
|
||||
char path[sizeof p->path];
|
||||
if (sep_variant_path(p->variant, base, path, sizeof path) < 0) {
|
||||
fprintf(stderr, "ww: package variant path is too long\n");
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (i == pi || !g->pkg[i].is_dir || g->pkg[i].generated_main)
|
||||
continue;
|
||||
int same_location = strcmp(g->pkg[i].canon, p->canon) == 0;
|
||||
int support_alias = p->role == SEP_ROLE_TEST_SUPPORT
|
||||
|| g->pkg[i].role == SEP_ROLE_TEST_SUPPORT;
|
||||
if (!support_alias && !same_location
|
||||
&& g->pkg[i].import_base[0] != '\0'
|
||||
&& strcmp(g->pkg[i].import_base, base) == 0) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_path_locations(base, g->pkg[i].entry, p->entry);
|
||||
return -1;
|
||||
}
|
||||
if (same_location && !support_alias
|
||||
&& g->pkg[i].import_base[0] != '\0'
|
||||
&& strcmp(g->pkg[i].import_base, base) != 0) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_directory_identities(p->entry,
|
||||
g->pkg[i].import_base, base);
|
||||
return -1;
|
||||
}
|
||||
if (g->pkg[i].path[0] != '\0'
|
||||
&& strcmp(g->pkg[i].path, path) == 0) {
|
||||
if (!same_location) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_path_locations(path, g->pkg[i].entry, p->entry);
|
||||
return -1;
|
||||
}
|
||||
if (support_alias
|
||||
|| g->pkg[i].variant == p->variant) {
|
||||
g->identity_failed = 1;
|
||||
fprintf(stderr,
|
||||
"ww: package action identity collision for %s in %s\n",
|
||||
path, p->entry);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
snprintf(p->import_base, sizeof p->import_base, "%s", base);
|
||||
snprintf(p->path, sizeof p->path, "%s", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sep_find_or_add_variant(struct sepgraph *g, const char *path,
|
||||
const char *entry, int is_dir, int variant, const char *test_package,
|
||||
int role, const char *artifact, int root)
|
||||
{
|
||||
if (strlen(path) >= sizeof g->pkg[0].path) {
|
||||
fprintf(stderr, "ww: package path is too long (limit %zu bytes)\n",
|
||||
sizeof g->pkg[0].path - 1);
|
||||
if (path != NULL && strlen(path) >= SEP_IMPORT_PATH_MAX) {
|
||||
fprintf(stderr, "ww: package path is too long (limit %d bytes)\n",
|
||||
SEP_IMPORT_PATH_MAX - 1);
|
||||
return -1;
|
||||
}
|
||||
char *canon = realpath(entry, NULL);
|
||||
@@ -590,104 +694,83 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path,
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
const char *base = path ? path : "";
|
||||
char incoming_path[sizeof g->pkg[0].path];
|
||||
incoming_path[0] = '\0';
|
||||
if (is_dir && base[0] != '\0'
|
||||
&& sep_variant_path(variant, base, incoming_path,
|
||||
sizeof incoming_path) < 0) {
|
||||
fprintf(stderr, "ww: package variant path is too long\n");
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
int same_location = strcmp(g->pkg[i].canon, canon) == 0;
|
||||
if (root || g->pkg[i].root) {
|
||||
if (root && g->pkg[i].root) {
|
||||
if (!same_location) continue;
|
||||
if (variant != g->pkg[i].variant) continue;
|
||||
if (g->pkg[i].role == role
|
||||
&& strcmp(g->pkg[i].path, path) == 0
|
||||
&& strcmp(g->pkg[i].test_package,
|
||||
test_package ? test_package : "") == 0) {
|
||||
/* One canonical directory variant is one compile action,
|
||||
* even when more than one product requests it. */
|
||||
free(canon);
|
||||
return i;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"ww: incompatible package-test roots %s\n", entry);
|
||||
struct seppkg *q = &g->pkg[i];
|
||||
int same_location = strcmp(q->canon, canon) == 0;
|
||||
if (is_dir && q->is_dir && !q->generated_main) {
|
||||
int support_alias = role == SEP_ROLE_TEST_SUPPORT
|
||||
|| q->role == SEP_ROLE_TEST_SUPPORT;
|
||||
if (!support_alias && !same_location && base[0] != '\0'
|
||||
&& q->import_base[0] != '\0'
|
||||
&& strcmp(base, q->import_base) == 0) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_path_locations(base, q->entry, entry);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
/* A cycle back to an ordinary production root reuses that root
|
||||
* after loading has assigned its declared package identity. Test
|
||||
* roots remain non-importable and distinct from production. */
|
||||
if (same_location
|
||||
&& variant == SEP_VARIANT_PRODUCTION
|
||||
&& g->pkg[i].variant == SEP_VARIANT_PRODUCTION
|
||||
&& strcmp(g->pkg[i].path, path) == 0) {
|
||||
if (incoming_path[0] != '\0' && q->path[0] != '\0'
|
||||
&& strcmp(incoming_path, q->path) == 0
|
||||
&& !same_location) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_path_locations(incoming_path, q->entry, entry);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
if (same_location && q->variant == variant && q->role == role) {
|
||||
if (variant != SEP_VARIANT_PRODUCTION
|
||||
&& strcmp(q->test_package,
|
||||
test_package ? test_package : "") != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: incompatible package-test roots %s\n", entry);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
if (base[0] != '\0'
|
||||
&& sep_bind_import_base(g, i, base) < 0) {
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
q->root = q->root || root;
|
||||
free(canon);
|
||||
return i;
|
||||
}
|
||||
if (!same_location) continue;
|
||||
int root_variant = root ? variant : g->pkg[i].variant;
|
||||
int production_variant = root ? g->pkg[i].variant : variant;
|
||||
if (is_dir && g->pkg[i].is_dir
|
||||
&& root_variant != SEP_VARIANT_PRODUCTION
|
||||
&& production_variant == SEP_VARIANT_PRODUCTION)
|
||||
continue;
|
||||
fprintf(stderr,
|
||||
"ww: package directory %s has incompatible root and production variants\n",
|
||||
entry);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
int same_path = strcmp(g->pkg[i].path, path) == 0;
|
||||
int same_action = same_path && same_location
|
||||
&& g->pkg[i].variant == variant && g->pkg[i].role == role
|
||||
&& (role != SEP_ROLE_EXTERNAL_PRODUCTION
|
||||
|| strcmp(g->pkg[i].artifact,
|
||||
artifact ? artifact : "") == 0);
|
||||
if (same_action) {
|
||||
free(canon);
|
||||
return i;
|
||||
}
|
||||
/* External tests still consume the directory's one canonical
|
||||
* production action. The role only disambiguates genuinely distinct
|
||||
* physical packages that share a source qualifier in this request. */
|
||||
if (same_path && same_location
|
||||
&& g->pkg[i].variant == SEP_VARIANT_PRODUCTION
|
||||
&& variant == SEP_VARIANT_PRODUCTION
|
||||
&& ((role == SEP_ROLE_EXTERNAL_PRODUCTION
|
||||
&& g->pkg[i].role == SEP_ROLE_NORMAL)
|
||||
|| (role == SEP_ROLE_NORMAL
|
||||
&& g->pkg[i].role == SEP_ROLE_EXTERNAL_PRODUCTION))) {
|
||||
free(canon);
|
||||
return i;
|
||||
}
|
||||
/* Differing physical packages with one source qualifier may need an
|
||||
* owning-product external action in the command-global universe. A
|
||||
* per-product closure check below still forbids linking both. */
|
||||
if (same_path && (role == SEP_ROLE_EXTERNAL_PRODUCTION
|
||||
|| g->pkg[i].role == SEP_ROLE_EXTERNAL_PRODUCTION))
|
||||
continue;
|
||||
if (same_path) {
|
||||
if (!same_location || g->pkg[i].variant != variant
|
||||
|| g->pkg[i].role != role) {
|
||||
if (same_location) {
|
||||
if (support_alias) continue;
|
||||
if (q->import_base[0] != '\0' && base[0] != '\0'
|
||||
&& strcmp(q->import_base, base) != 0) {
|
||||
g->identity_failed = 1;
|
||||
sep_diag_directory_identities(entry,
|
||||
q->import_base, base);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
if (sep_directory_variant(q->variant)
|
||||
&& sep_directory_variant(variant))
|
||||
continue;
|
||||
fprintf(stderr,
|
||||
"ww: package %s resolves to more than one location\n",
|
||||
path[0] ? path : "(root)");
|
||||
"ww: package directory %s has incompatible variants\n",
|
||||
entry);
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (same_location && strcmp(q->path, base) == 0
|
||||
&& q->variant == variant && q->role == role) {
|
||||
q->root = q->root || root;
|
||||
free(canon);
|
||||
return i;
|
||||
}
|
||||
if (same_location) {
|
||||
/* A reserved compiler-generated test-support action is a
|
||||
* deliberately distinct qualifier for the same toolchain sources.
|
||||
* No ordinary source import can create this role. */
|
||||
if (role == SEP_ROLE_TEST_SUPPORT
|
||||
|| g->pkg[i].role == SEP_ROLE_TEST_SUPPORT)
|
||||
continue;
|
||||
fprintf(stderr,
|
||||
"ww: package directory %s has identities %s and %s\n",
|
||||
entry, g->pkg[i].path[0] ? g->pkg[i].path : "(root)",
|
||||
path[0] ? path : "(root)");
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (g->n >= SEP_MAXPKG) {
|
||||
fprintf(stderr, "ww: too many packages (limit %d)\n",
|
||||
@@ -695,39 +778,43 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path,
|
||||
free(canon);
|
||||
return -1;
|
||||
}
|
||||
struct seppkg *p = &g->pkg[g->n];
|
||||
snprintf(p->path, sizeof p->path, "%s", path);
|
||||
int ni = g->n++;
|
||||
struct seppkg *p = &g->pkg[ni];
|
||||
memset(p, 0, sizeof *p);
|
||||
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
||||
snprintf(p->canon, sizeof p->canon, "%s", canon);
|
||||
p->artifact[0] = '\0';
|
||||
if (artifact != NULL)
|
||||
snprintf(p->artifact, sizeof p->artifact, "%s", artifact);
|
||||
free(canon);
|
||||
p->is_dir = is_dir;
|
||||
p->variant = variant;
|
||||
p->role = role;
|
||||
p->root = root;
|
||||
p->link_entry = 0;
|
||||
p->generated_main = 0;
|
||||
p->failed = 0;
|
||||
p->test_support = 0;
|
||||
p->loaded = 0;
|
||||
p->export_changed = 0;
|
||||
p->emit_context = -1;
|
||||
memset(p->context_state, 0, sizeof p->context_state);
|
||||
p->bindings.paths = NULL;
|
||||
p->bindings.n = 0;
|
||||
p->bindings.cap = 0;
|
||||
p->name[0] = '\0';
|
||||
p->test_package[0] = '\0';
|
||||
if (test_package != NULL)
|
||||
snprintf(p->test_package, sizeof p->test_package, "%s",
|
||||
test_package);
|
||||
p->sources = NULL;
|
||||
p->nsources = 0;
|
||||
p->ndeps = 0;
|
||||
p->color = 0;
|
||||
return g->n++;
|
||||
if (is_dir) {
|
||||
const char *inherited = base;
|
||||
if (inherited[0] == '\0')
|
||||
for (int i = 0; i < ni; i++)
|
||||
if (g->pkg[i].is_dir && !g->pkg[i].generated_main
|
||||
&& g->pkg[i].role != SEP_ROLE_TEST_SUPPORT
|
||||
&& role != SEP_ROLE_TEST_SUPPORT
|
||||
&& strcmp(g->pkg[i].canon, p->canon) == 0
|
||||
&& g->pkg[i].import_base[0] != '\0') {
|
||||
inherited = g->pkg[i].import_base;
|
||||
break;
|
||||
}
|
||||
if (inherited[0] != '\0'
|
||||
&& sep_bind_import_base(g, ni, inherited) < 0) {
|
||||
g->n--;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
snprintf(p->path, sizeof p->path, "%s", base);
|
||||
if (artifact != NULL)
|
||||
snprintf(p->artifact, sizeof p->artifact, "%s", artifact);
|
||||
}
|
||||
return ni;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -823,6 +910,7 @@ static int
|
||||
sep_validate_artifact_paths(const struct sepgraph *g, const char *scratch)
|
||||
{
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (g->pkg[i].failed || !g->pkg[i].loaded) continue;
|
||||
const char *base = g->pkg[i].artifact[0] != '\0'
|
||||
? g->pkg[i].artifact
|
||||
: (g->pkg[i].path[0] != '\0' ? g->pkg[i].path : "__root");
|
||||
@@ -832,6 +920,19 @@ sep_validate_artifact_paths(const struct sepgraph *g, const char *scratch)
|
||||
fprintf(stderr, "ww: package artifact path is too long\n");
|
||||
return -1;
|
||||
}
|
||||
for (int j = i + 1; j < g->n; j++) {
|
||||
if (g->pkg[j].failed || !g->pkg[j].loaded) continue;
|
||||
const char *other = g->pkg[j].artifact[0] != '\0'
|
||||
? g->pkg[j].artifact
|
||||
: (g->pkg[j].path[0] != '\0'
|
||||
? g->pkg[j].path : "__root");
|
||||
if (strcmp(base, other) == 0) {
|
||||
fprintf(stderr,
|
||||
"ww: package actions share artifact identity %s\n",
|
||||
base);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1082,15 +1183,6 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
||||
break;
|
||||
}
|
||||
int self = strcmp(canon, g->pkg[pi].canon) == 0;
|
||||
int runtime_production = 0;
|
||||
if (external_production)
|
||||
for (int gi = 0; gi < g->n; gi++)
|
||||
if (g->pkg[gi].test_support
|
||||
&& strcmp(g->pkg[gi].path, name) == 0
|
||||
&& strcmp(g->pkg[gi].canon, canon) == 0) {
|
||||
runtime_production = 1;
|
||||
break;
|
||||
}
|
||||
free(canon);
|
||||
if (self && sep_external_production_name(&g->pkg[pi], name, 1))
|
||||
external_production = 1;
|
||||
@@ -1102,20 +1194,10 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
int di;
|
||||
if (external_production && !runtime_production) {
|
||||
char artifact[64];
|
||||
int an = snprintf(artifact, sizeof artifact, "%s-production",
|
||||
g->pkg[pi].artifact);
|
||||
if (an < 0 || (size_t)an >= sizeof artifact) {
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
di = sep_find_or_add_role(g, name, ipath, 1,
|
||||
SEP_ROLE_EXTERNAL_PRODUCTION, artifact);
|
||||
} else {
|
||||
di = sep_find_or_add(g, name, ipath, 1);
|
||||
}
|
||||
/* The external package's self-production import is the ordinary
|
||||
* canonical production action for this directory. Discovery role and
|
||||
* the external product's artifact name never create another action. */
|
||||
int di = sep_find_or_add(g, name, ipath, 1);
|
||||
if (di < 0) { rc = -1; break; }
|
||||
int seen = 0;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
@@ -1146,50 +1228,75 @@ sep_dep_cmp(const struct sepgraph *g, int a, int b)
|
||||
{
|
||||
int r = strcmp(g->pkg[a].path, g->pkg[b].path);
|
||||
if (r != 0) return r;
|
||||
if (g->pkg[a].variant != g->pkg[b].variant)
|
||||
return g->pkg[a].variant - g->pkg[b].variant;
|
||||
if (g->pkg[a].role != g->pkg[b].role)
|
||||
return g->pkg[a].role - g->pkg[b].role;
|
||||
return strcmp(g->pkg[a].artifact, g->pkg[b].artifact);
|
||||
return strcmp(g->pkg[a].canon, g->pkg[b].canon);
|
||||
}
|
||||
|
||||
/* Add the compiler-owned test main as a real package action. Its semantic
|
||||
* identity and artifact key are distinct from every directory variant; its
|
||||
* only dependencies are the selected test variant and dispatcher support. */
|
||||
/* Add the compiler-owned test main as a real package action. Its identity is a
|
||||
* pure function of the selected variant (and its one command-global support
|
||||
* edge), never a product ordinal. Equivalent products therefore reuse it. */
|
||||
static int
|
||||
sep_add_generated_main(struct sepgraph *g, struct sepproduct *product,
|
||||
int ordinal, int support)
|
||||
{
|
||||
if (g->n >= SEP_MAXPKG) {
|
||||
fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG);
|
||||
return -1;
|
||||
}
|
||||
(void)ordinal;
|
||||
int variant = product->variant_root;
|
||||
if (variant < 0 || variant >= g->n) return -1;
|
||||
char variantcanon[sizeof g->pkg[0].canon];
|
||||
char variantentry[sizeof g->pkg[0].entry];
|
||||
snprintf(variantcanon, sizeof variantcanon, "%s", g->pkg[variant].canon);
|
||||
snprintf(variantentry, sizeof variantentry, "%s", g->pkg[variant].entry);
|
||||
struct seppkg *p = &g->pkg[g->n];
|
||||
memset(p, 0, sizeof *p);
|
||||
int pn = snprintf(p->path, sizeof p->path,
|
||||
"__wwtestmain.%03d.main", ordinal);
|
||||
int an = snprintf(p->artifact, sizeof p->artifact,
|
||||
"__ww-test-%03d-main", ordinal);
|
||||
int cn = snprintf(p->canon, sizeof p->canon, "%s#test-main-%03d",
|
||||
variantcanon, ordinal);
|
||||
if (pn < 0 || (size_t)pn >= sizeof p->path
|
||||
|| an < 0 || (size_t)an >= sizeof p->artifact
|
||||
|| cn < 0 || (size_t)cn >= sizeof p->canon) {
|
||||
const char *kind = "production";
|
||||
if (g->pkg[variant].variant == SEP_VARIANT_SAME_TEST)
|
||||
kind = "internal";
|
||||
else if (g->pkg[variant].variant == SEP_VARIANT_EXTERNAL)
|
||||
kind = "external";
|
||||
char path[sizeof g->pkg[0].path];
|
||||
char artifact[sizeof g->pkg[0].artifact];
|
||||
char canon[sizeof g->pkg[0].canon];
|
||||
char entry[sizeof g->pkg[0].entry];
|
||||
snprintf(entry, sizeof entry, "%s", g->pkg[variant].entry);
|
||||
const char *variant_artifact = g->pkg[variant].artifact[0]
|
||||
? g->pkg[variant].artifact : g->pkg[variant].path;
|
||||
int pn = snprintf(path, sizeof path, "__wwtestmain.%s.%s.main",
|
||||
g->pkg[variant].path, kind);
|
||||
int an = snprintf(artifact, sizeof artifact, "%s-main", variant_artifact);
|
||||
int cn = snprintf(canon, sizeof canon, "%s#%s-test-main",
|
||||
g->pkg[variant].canon, kind);
|
||||
if (pn < 0 || (size_t)pn >= sizeof path
|
||||
|| an < 0 || (size_t)an >= sizeof artifact
|
||||
|| cn < 0 || (size_t)cn >= sizeof canon) {
|
||||
fprintf(stderr, "ww: generated test-main identity is too long\n");
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (strcmp(g->pkg[i].path, p->path) != 0) continue;
|
||||
if (strcmp(g->pkg[i].path, path) != 0) continue;
|
||||
if (g->pkg[i].generated_main) {
|
||||
int wants_support = support >= 0 && support != variant;
|
||||
int has_variant = 0, has_support = 0;
|
||||
for (int k = 0; k < g->pkg[i].ndeps; k++) {
|
||||
if (g->pkg[i].deps[k] == variant) has_variant = 1;
|
||||
if (wants_support && g->pkg[i].deps[k] == support)
|
||||
has_support = 1;
|
||||
}
|
||||
if (has_variant && has_support == wants_support
|
||||
&& g->pkg[i].ndeps == 1 + wants_support)
|
||||
return i;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"ww: generated test-main package identity collides with source import %s\n",
|
||||
p->path);
|
||||
path);
|
||||
return -1;
|
||||
}
|
||||
snprintf(p->entry, sizeof p->entry, "%s", variantentry);
|
||||
if (g->n >= SEP_MAXPKG) {
|
||||
fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG);
|
||||
return -1;
|
||||
}
|
||||
struct seppkg *p = &g->pkg[g->n];
|
||||
memset(p, 0, sizeof *p);
|
||||
snprintf(p->path, sizeof p->path, "%s", path);
|
||||
snprintf(p->artifact, sizeof p->artifact, "%s", artifact);
|
||||
snprintf(p->canon, sizeof p->canon, "%s", canon);
|
||||
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
||||
snprintf(p->name, sizeof p->name, "main");
|
||||
p->variant = SEP_VARIANT_TEST_MAIN;
|
||||
p->role = SEP_ROLE_GENERATED_MAIN;
|
||||
@@ -1303,15 +1410,6 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
|
||||
g->pkg[pi].failed = 1;
|
||||
return -1;
|
||||
}
|
||||
/* Give a directory root its declared package identity before recursively
|
||||
* loading dependencies. Explicit raw single-file compiler fixtures retain
|
||||
* their historical anonymous multi-package boundary. */
|
||||
if (g->pkg[pi].root && g->pkg[pi].is_dir
|
||||
&& g->pkg[pi].path[0] == '\0'
|
||||
&& g->pkg[pi].name[0] != '\0') {
|
||||
size_t n = strlen(g->pkg[pi].name);
|
||||
memcpy(g->pkg[pi].path, g->pkg[pi].name, n + 1);
|
||||
}
|
||||
for (int i = 1; i < g->pkg[pi].ndeps; i++) {
|
||||
int v = g->pkg[pi].deps[i];
|
||||
int j = i;
|
||||
@@ -1332,6 +1430,85 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Finalize every provisional directory root after source discovery but before
|
||||
* generated-main construction or compilation. A source import binding wins;
|
||||
* otherwise a manifest-free literal root uses its validated declared package
|
||||
* name (external p_test roots bind the ordinary base p). */
|
||||
static int
|
||||
sep_finalize_directory_identities(struct sepgraph *g)
|
||||
{
|
||||
for (int pi = 0; pi < g->n; pi++) {
|
||||
struct seppkg *p = &g->pkg[pi];
|
||||
if (!p->is_dir || p->generated_main || p->failed || !p->loaded
|
||||
|| p->import_base[0] != '\0')
|
||||
continue;
|
||||
const char *base = NULL;
|
||||
for (int i = 0; i < g->n; i++) {
|
||||
if (i == pi || !g->pkg[i].is_dir || g->pkg[i].generated_main
|
||||
|| g->pkg[i].role == SEP_ROLE_TEST_SUPPORT
|
||||
|| p->role == SEP_ROLE_TEST_SUPPORT
|
||||
|| strcmp(g->pkg[i].canon, p->canon) != 0
|
||||
|| g->pkg[i].import_base[0] == '\0')
|
||||
continue;
|
||||
base = g->pkg[i].import_base;
|
||||
break;
|
||||
}
|
||||
char fallback[SEP_IMPORT_PATH_MAX];
|
||||
if (base == NULL) {
|
||||
if (p->name[0] == '\0') {
|
||||
fprintf(stderr,
|
||||
"ww: package directory %s has no canonical import identity\n",
|
||||
p->entry);
|
||||
return -1;
|
||||
}
|
||||
if (p->variant == SEP_VARIANT_EXTERNAL) {
|
||||
size_t n = strlen(p->name);
|
||||
if (n <= 5 || strcmp(p->name + n - 5, "_test") != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: package-test selector does not name an external package\n");
|
||||
return -1;
|
||||
}
|
||||
memcpy(fallback, p->name, n - 5);
|
||||
fallback[n - 5] = '\0';
|
||||
} else {
|
||||
snprintf(fallback, sizeof fallback, "%s", p->name);
|
||||
}
|
||||
base = fallback;
|
||||
}
|
||||
if (sep_bind_import_base(g, pi, base) < 0) return -1;
|
||||
}
|
||||
for (int pi = 0; pi < g->n; pi++) {
|
||||
struct seppkg *p = &g->pkg[pi];
|
||||
if (!p->is_dir || p->generated_main || p->failed || !p->loaded)
|
||||
continue;
|
||||
const char *dot = strrchr(p->path, '.');
|
||||
const char *leaf = dot ? dot + 1 : p->path;
|
||||
int support_alias = p->role == SEP_ROLE_TEST_SUPPORT
|
||||
&& strcmp(p->path, SEP_TEST_SUPPORT_MODULE) == 0
|
||||
&& strcmp(p->name, "test") == 0;
|
||||
if (!support_alias && strcmp(p->name, leaf) != 0) {
|
||||
fprintf(stderr, "ww: package %s does not match import path %s\n",
|
||||
p->name, p->path);
|
||||
return -1;
|
||||
}
|
||||
p->artifact[0] = '\0';
|
||||
int n = 0;
|
||||
char action_path[sizeof p->path];
|
||||
snprintf(action_path, sizeof action_path, "%s", p->path);
|
||||
if (p->variant == SEP_VARIANT_SAME_TEST)
|
||||
n = snprintf(p->artifact, sizeof p->artifact,
|
||||
"%s-internal-test", action_path);
|
||||
else if (p->variant == SEP_VARIANT_EXTERNAL)
|
||||
n = snprintf(p->artifact, sizeof p->artifact,
|
||||
"%s-external-test", action_path);
|
||||
if (n < 0 || (size_t)n >= sizeof p->artifact) {
|
||||
fprintf(stderr, "ww: package variant artifact identity is too long\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DFS post-order over the dep DAG → reverse-topo (deps before importer),
|
||||
* cite Hare gather (deps.ha:123). Tri-color: a gray back-edge is a loud
|
||||
* dep-cycle reject naming the chain (Hare deps.ha:243); `stack[0..depth)`
|
||||
@@ -1364,10 +1541,9 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Special external-production actions may share a compiler module qualifier
|
||||
* with an unrelated normal action in the command-global universe, but never
|
||||
* in one product. A single link closure must remain an unambiguous package
|
||||
* namespace. */
|
||||
/* Internal test variants replace their colocated production action in the
|
||||
* corresponding test link closure. Canonical package identity has already
|
||||
* made every remaining compiler qualifier globally unambiguous. */
|
||||
static int
|
||||
sep_internal_replaces_production(const struct sepgraph *g, int a, int b)
|
||||
{
|
||||
@@ -1822,8 +1998,10 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
products[i].context = sep_context_for(g, contextroot,
|
||||
extra_includes, toolsrcdir);
|
||||
if (products[i].context < 0) return 1;
|
||||
const char *selector = products[i].variant == SEP_VARIANT_PRODUCTION
|
||||
? NULL : products[i].test_package;
|
||||
products[i].root = sep_find_or_add_variant(g, rootpath, entry,
|
||||
entry_is_dir, products[i].variant, products[i].test_package,
|
||||
entry_is_dir, products[i].variant, selector,
|
||||
SEP_ROLE_NORMAL, products[i].artifact, 1);
|
||||
if (products[i].root < 0) return 1;
|
||||
products[i].variant_root = products[i].root;
|
||||
@@ -1919,10 +2097,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
g->pkg[root].failed = 1;
|
||||
continue;
|
||||
}
|
||||
if (products[i].variant != SEP_VARIANT_PRODUCTION
|
||||
&& (products[i].test_package == NULL
|
||||
|| strcmp(g->pkg[root].name,
|
||||
products[i].test_package) != 0)) {
|
||||
if (products[i].test_package != NULL
|
||||
&& strcmp(g->pkg[root].name, products[i].test_package) != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: package-test selector does not match loaded package\n");
|
||||
g->pkg[root].failed = 1;
|
||||
@@ -1935,12 +2111,22 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
if (support >= 0 && support != variant
|
||||
&& sep_load_pkg(g, support, products[i].context) < 0)
|
||||
g->pkg[variant].failed = 1;
|
||||
}
|
||||
}
|
||||
if (g->identity_failed) return 1;
|
||||
if (sep_finalize_directory_identities(g) < 0) return 1;
|
||||
if (is_test && entry_is_dir) {
|
||||
for (int i = 0; i < nproducts; i++) {
|
||||
int variant = products[i].variant_root;
|
||||
int support = support_for[i];
|
||||
if (g->pkg[variant].failed
|
||||
|| (support >= 0 && g->pkg[support].failed)) {
|
||||
products[i].root = variant;
|
||||
continue;
|
||||
}
|
||||
int mainpkg = sep_add_generated_main(g, &products[i], i,
|
||||
support);
|
||||
if (mainpkg < 0) return 1;
|
||||
if (g->pkg[variant].failed
|
||||
|| (support >= 0 && g->pkg[support].failed))
|
||||
g->pkg[mainpkg].failed = 1;
|
||||
products[i].root = mainpkg;
|
||||
}
|
||||
}
|
||||
@@ -2058,7 +2244,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
cargv[cpos++] = "--test-support-module";
|
||||
cargv[cpos++] = (char *)test_support_module;
|
||||
} else {
|
||||
if (is_test && g->pkg[pi].root)
|
||||
if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST
|
||||
|| g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
|
||||
cargv[cpos++] = "--test-package";
|
||||
if (g->pkg[pi].link_entry)
|
||||
cargv[cpos++] = "--entry";
|
||||
@@ -2297,7 +2484,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||
.root = -1,
|
||||
.variant_root = -1,
|
||||
};
|
||||
if (!package_only)
|
||||
if (!package_only && !entry_is_dir)
|
||||
snprintf(product.artifact, sizeof product.artifact, "__root");
|
||||
int r = build_one_sep_impl(src, entry_is_dir, root_identity, out, objstem,
|
||||
extra_includes, linkflags, package_only, is_test,
|
||||
@@ -2714,9 +2901,13 @@ do_test(int argc, char **argv)
|
||||
const char *output = argv[++i];
|
||||
const char *status = argv[++i];
|
||||
size_t pn = strlen(name);
|
||||
int variant = strcmp(kind, "same") == 0
|
||||
? SEP_VARIANT_SAME_TEST : SEP_VARIANT_EXTERNAL;
|
||||
if ((strcmp(kind, "same") != 0
|
||||
int variant = SEP_VARIANT_EXTERNAL;
|
||||
if (strcmp(kind, "production") == 0)
|
||||
variant = SEP_VARIANT_PRODUCTION;
|
||||
else if (strcmp(kind, "same") == 0)
|
||||
variant = SEP_VARIANT_SAME_TEST;
|
||||
if ((strcmp(kind, "production") != 0
|
||||
&& strcmp(kind, "same") != 0
|
||||
&& strcmp(kind, "external") != 0)
|
||||
|| pn == 0 || pn >= sizeof ((struct seppkg *)0)->name
|
||||
|| dir[0] == '\0' || output[0] == '\0'
|
||||
@@ -2806,11 +2997,10 @@ do_test(int argc, char **argv)
|
||||
"ww test: duplicate --ww-package-test variant for directory\n");
|
||||
return 2;
|
||||
}
|
||||
snprintf(products[i].artifact, sizeof products[i].artifact,
|
||||
"__ww-test-%03d-%s", i,
|
||||
products[i].variant == SEP_VARIANT_SAME_TEST
|
||||
? "same" : "external");
|
||||
}
|
||||
/* Artifact identity is derived from canonical package identity after
|
||||
* discovery; product position is deliberately not an action key. */
|
||||
products[i].artifact[0] = '\0';
|
||||
}
|
||||
if (nproducts != 0 && packageopts) {
|
||||
fprintf(stderr,
|
||||
"ww test: package-test variant rejects package options\n");
|
||||
|
||||
Reference in New Issue
Block a user