ww: select build action from package declaration

This commit is contained in:
2026-08-13 13:36:10 +09:00
parent 61a2ee5221
commit dab3cb4998
61 changed files with 872 additions and 157 deletions

View File

@@ -24,7 +24,7 @@
static const char *usage =
"usage: ww [-V] <subcommand> [args...]\n"
" -V print version and exit\n"
" build [-p] [-S] [-w DIR] [-I DIR] [-o FILE] [path] build a local package graph\n"
" build [-S] [-w DIR] [-I DIR] [-o FILE] [path] build a local package graph\n"
" run [path] ... build then exec, passing extra args to the program\n"
" test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n"
" version print version and exit\n"
@@ -33,7 +33,7 @@ static const char *usage =
" foo.ww literal file\n"
" foo search cwd, -I dirs, then the source library for foo.ww or foo/\n"
" lib/foo directory: build its package sources\n"
" -p emits a non-main archive FILE + FILE.wwi\n"
" -o publishes a non-main archive FILE + FILE.wwi\n"
" lib/... every package under lib, recursively (test only)\n"
" . build the cwd's <basename>.ww\n";
@@ -969,6 +969,17 @@ sep_command_declared_name(const struct seppkg *p)
? strcmp(p->name, "main_test") == 0
: strcmp(p->name, "main") == 0;
}
/* Directory-package action kind comes only from the loaded declaration.
* An explicit package-less file is the retained raw-unit compatibility path;
* it has no directory package declaration and remains a command unit. */
static int
sep_root_is_command(const struct seppkg *p)
{
if (p->name == NULL) return !p->is_dir;
return strcmp(p->name, "main") == 0;
}
static int
sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep)
{
@@ -2663,6 +2674,20 @@ copy_file_atomic(const char *src, const char *dst)
return rename(tmp, dst);
}
/* A package publication writes OUT, OUT.new, OUT.wwi, and OUT.wwi.new.
* Validate the longest spelling before any producer tool can run. */
static int
validate_package_output_path(const char *out)
{
size_t n = strlen(out);
if ((size_t)PATH_MAX < sizeof ".wwi.new"
|| n > (size_t)PATH_MAX - sizeof ".wwi.new") {
fprintf(stderr, "ww: package output path is too long\n");
return -1;
}
return 0;
}
/* A coordinator-private completion marker distinguishes a newly linked
* product from a caller-owned binary left behind by an earlier invocation. */
static int
@@ -2681,13 +2706,13 @@ record_product_status(const char *path)
}
/* The stamp pins the non-content build inputs a unit compare cannot see:
* the -T/-S shape of the producer pass and the artifact protocol
* the -T/-S/root-action shape of the producer pass and the artifact protocol
* revision (bump "fmt" when the unit/archive/commit format changes). */
static void
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
{
snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n",
is_test ? 11 : 10, is_test ? "test" : "build", emit_asm);
is_test ? 11 : 12, is_test ? "test" : "build", emit_asm);
}
/* A stale global builder identity invalidates every committed unit voucher in
@@ -2732,7 +2757,8 @@ invalidate_workdir_units(const char *scratch)
static int
build_one_sep_impl(const char *src, int entry_is_dir,
const char *out, const char *objstem, const char *extra_includes,
const struct seplinkflags *linkflags, int package_only, int is_test,
const struct seplinkflags *linkflags, int publish_package,
int require_command, int is_test,
struct sepproduct *products, int nproducts, int emit_asm,
const char *workdir, char *scratchout, size_t scratchoutsz,
struct sepgraph **graphout)
@@ -2895,8 +2921,6 @@ build_one_sep_impl(const char *src, int entry_is_dir,
SEP_ROLE_NORMAL, products[i].artifact, 1);
if (products[i].root < 0) return 1;
products[i].variant_root = products[i].root;
if (!is_test && !package_only)
g->pkg[products[i].root].link_entry = 1;
}
const char *test_support_module = "test";
/* -T generates a dispatcher whose support qualifier is selected by the
@@ -3016,6 +3040,14 @@ build_one_sep_impl(const char *src, int entry_is_dir,
}
if (g->identity_failed) return 1;
if (sep_finalize_directory_identities(g) < 0) return 1;
if (!is_test) {
for (int i = 0; i < nproducts; i++) {
int root = products[i].variant_root;
if (!g->pkg[root].failed)
g->pkg[root].link_entry =
sep_root_is_command(&g->pkg[root]);
}
}
if (is_test && entry_is_dir) {
for (int i = 0; i < nproducts; i++) {
int variant = products[i].variant_root;
@@ -3031,18 +3063,15 @@ build_one_sep_impl(const char *src, int entry_is_dir,
products[i].root = mainpkg;
}
}
int root_package = !is_test
&& !g->pkg[products[0].root].failed
&& !sep_root_is_command(&g->pkg[products[0].root]);
if (sep_validate_artifact_paths(g, scratch) < 0)
return 1;
if (warm && sep_validate_workdir_owners(g, scratch) < 0)
return 1;
if (warm && stale_all && invalidate_workdir_units(scratch) != 0)
return 1;
int root_package = package_only;
if (root_package && !g->pkg[products[0].root].failed
&& strcmp(g->pkg[products[0].root].name, "main") == 0) {
fprintf(stderr, "ww: -p requires a non-main package\n");
return 1;
}
int *order = calloc((size_t)g->n, sizeof *order);
int *stack = calloc((size_t)g->n, sizeof *stack);
int norder = 0;
@@ -3065,6 +3094,23 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|| sep_validate_module_closure(g, order, ignored, 1) < 0)
g->pkg[root].failed = 1;
}
if (!is_test && require_command) {
int root = products[0].root;
if (!g->pkg[root].failed
&& !sep_root_is_command(&g->pkg[root])) {
fprintf(stderr, "ww: package %s is not a main package\n",
g->pkg[root].path[0] ? g->pkg[root].path
: g->pkg[root].canon);
free(stack); free(order);
return 1;
}
}
if (!g->pkg[products[0].root].failed
&& root_package && publish_package && !emit_asm
&& validate_package_output_path(out) < 0) {
free(stack); free(order);
return 1;
}
for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0;
for (int i = 0; i < nproducts; i++) {
int root = products[i].root;
@@ -3277,21 +3323,24 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (root_package) {
int root = products[0].root;
if (g->pkg[root].failed) { free(order); return 1; }
char archive[SEP_ARTIFACT_MAX], iface[SEP_ARTIFACT_MAX];
char outiface[SEP_ARTIFACT_MAX];
sep_fname(g, root, scratch, ".a", archive, sizeof archive);
sep_fname(g, root, scratch, ".wwi", iface, sizeof iface);
int on = snprintf(outiface, sizeof outiface, "%s.wwi", out);
if (on < 0 || (size_t)on >= sizeof outiface) {
fprintf(stderr, "ww: package output path is too long\n");
free(order);
return 1;
}
if (copy_file_atomic(archive, out) != 0
|| copy_file_atomic(iface, outiface) != 0) {
fprintf(stderr, "ww: cannot write package artifact %s\n", out);
free(order);
return 1;
if (publish_package) {
char archive[SEP_ARTIFACT_MAX], iface[SEP_ARTIFACT_MAX];
char outiface[SEP_ARTIFACT_MAX];
sep_fname(g, root, scratch, ".a", archive, sizeof archive);
sep_fname(g, root, scratch, ".wwi", iface, sizeof iface);
int on = snprintf(outiface, sizeof outiface, "%s.wwi", out);
if (on < 0 || (size_t)on >= sizeof outiface) {
fprintf(stderr, "ww: package output path is too long\n");
free(order);
return 1;
}
if (copy_file_atomic(archive, out) != 0
|| copy_file_atomic(iface, outiface) != 0) {
fprintf(stderr,
"ww: cannot write package artifact %s\n", out);
free(order);
return 1;
}
}
free(order);
return 0;
@@ -3414,7 +3463,8 @@ static int
build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
const char *out,
const char *objstem, const char *extra_includes,
const struct seplinkflags *linkflags, int package_only, int is_test,
const struct seplinkflags *linkflags, int publish_package,
int require_command, int is_test,
int root_variant, const char *test_package, int emit_asm,
int keepscratch, const char *workdir)
{
@@ -3432,10 +3482,10 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
.variant_root = -1,
.support = -1,
};
if (!package_only && !entry_is_dir)
if (!entry_is_dir)
product.artifact = "__root";
int r = build_one_sep_impl(src, entry_is_dir, out, objstem,
extra_includes, linkflags, package_only, is_test,
extra_includes, linkflags, publish_package, require_command, is_test,
&product, 1, emit_asm, workdir, scratch,
sizeof scratch, &g);
sep_graph_free(g);
@@ -3477,7 +3527,7 @@ build_package_tests(const char *src, const char *root_identity,
for (int i = 0; i < nproducts; i++)
products[i].identity = root_identity;
int r = build_one_sep_impl(src, 1, products[0].out,
products[0].out, extra_includes, NULL, 0, 1,
products[0].out, extra_includes, NULL, 0, 0, 1,
products, nproducts, 0, workdir, scratch, sizeof scratch, &g);
sep_graph_free(g);
return r;
@@ -3621,11 +3671,10 @@ parse_build_flags(const char *cmd, int argc, char **argv,
struct seplinkflags *linkflags,
char *outpath, size_t outsz,
char *workdir, size_t workdirsz,
const char **src_out, int *emit_asm_out, int *package_out)
const char **src_out, int *emit_asm_out)
{
*src_out = NULL;
if (emit_asm_out) *emit_asm_out = 0;
if (package_out) *package_out = 0;
int i = 0;
for (; i < argc; i++) {
if (strcmp(argv[i], "-S") == 0) {
@@ -3634,12 +3683,6 @@ parse_build_flags(const char *cmd, int argc, char **argv,
return -1;
}
*emit_asm_out = 1;
} else if (strcmp(argv[i], "-p") == 0) {
if (package_out == NULL) {
fprintf(stderr, "ww %s: unknown flag\n", cmd);
return -1;
}
*package_out = 1;
} else if (strcmp(argv[i], "-w") == 0) {
if (workdir == NULL) {
fprintf(stderr, "ww %s: unknown flag\n", cmd);
@@ -3738,20 +3781,14 @@ do_build(int argc, char **argv)
char outflag[PATH_MAX] = {0};
char workdir[PATH_MAX] = {0};
int emit_asm = 0;
int package_only = 0;
if (parse_build_flags("build", argc, argv, incs, incsz,
&linkflags,
outflag, sizeof outflag, workdir, sizeof workdir,
&src, &emit_asm, &package_only) < 0) {
&src, &emit_asm) < 0) {
free(incs);
return 2;
}
if (src == NULL) src = ".";
if (package_only && emit_asm) {
fprintf(stderr, "ww build: -p and -S cannot be combined\n");
free(incs);
return 2;
}
struct stat requested;
int literal = stat(src, &requested) == 0;
char resolved[PATH_MAX];
@@ -3761,11 +3798,6 @@ do_build(int argc, char **argv)
free(incs);
return 1;
}
if (package_only && !is_dir) {
fprintf(stderr, "ww build: -p needs a package directory\n");
free(incs);
return 2;
}
char out[PATH_MAX];
const char *objstem = NULL;
if (outflag[0]) {
@@ -3785,8 +3817,8 @@ do_build(int argc, char **argv)
}
const char *root_identity = !literal && is_dir ? src : NULL;
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
&linkflags, package_only, 0, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
1, workdir);
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 1, workdir);
free(incs);
return rc;
}
@@ -3802,7 +3834,7 @@ do_run(int argc, char **argv)
char outflag[PATH_MAX] = {0}; /* -o accepted+ignored: run always uses the temp */
int next = parse_build_flags("run", argc, argv, incs, incsz,
&linkflags,
outflag, sizeof outflag, NULL, 0, &src, NULL, NULL);
outflag, sizeof outflag, NULL, 0, &src, NULL);
if (next < 0) { free(incs); return 2; }
if (src == NULL) src = ".";
struct stat requested;
@@ -3831,7 +3863,7 @@ do_run(int argc, char **argv)
const char *root_identity = !literal && is_dir ? src : NULL;
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags,
0, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL);
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL);
free(incs);
if (buildrc != 0) {
if (unlink(tmp) != 0 && errno != ENOENT)
@@ -4181,7 +4213,7 @@ do_test(int argc, char **argv)
/* No-o redirects internal scratch to /tmp rather than beside the
* source. An explicit -o names the caller-owned artifact stem. */
int br = build_one_sep(resolved, is_dir, NULL, outp,
outstem[0] ? outstem : tmp, incs, NULL, 0, 1,
outstem[0] ? outstem : tmp, incs, NULL, 0, 0, 1,
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir);
if (br != 0) {
@@ -4249,7 +4281,7 @@ do_test(int argc, char **argv)
}
/* See module-mode note: no-o scratch is redirected to /tmp. */
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp,
incs, NULL, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)