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

@@ -1402,6 +1402,14 @@ fn sepcommanddeclaredname(p: *seppkg) bool = {
return cstreqlit(p.name, "main");
};
// Directory-package action kind comes only from the loaded declaration. An
// explicit package-less file is the retained raw-unit compatibility path and
// remains a command unit.
fn seprootiscommand(p: *seppkg) bool = {
if (p.name == nil) { return p.isdir == 0; };
return cstreqlit(p.name, "main");
};
fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = {
let from: *seppkg = &g.pkg[importer];
let to: *seppkg = &g.pkg[dep];
@@ -3324,8 +3332,20 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
return os.rename(pathstr(tmpp), pathstr(dst));
};
// Package publication writes OUT, OUT.new, OUT.wwi, and OUT.wwi.new. Check
// the longest spelling before any producer tool can run.
fn validatepackageoutputpath(out: *u8) i32 = {
let suffix: u64 = ".wwi.new".len: u64 + 1u64;
let limit: u64 = os.PATH_MAX: u64;
if (suffix > limit || cstrlen(out) > limit - suffix) {
cerr("ww: package output path is too long\n");
return -1;
};
return 0;
};
// 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).
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
@@ -3335,9 +3355,9 @@ fn workdirstamptext(istest: i32, emitasm: i32) str = {
return "ww workdir fmt 11 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 10 mode build asm 1\n";
return "ww workdir fmt 12 mode build asm 1\n";
};
return "ww workdir fmt 10 mode build asm 0\n";
return "ww workdir fmt 12 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {
@@ -3444,7 +3464,7 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
out: *u8, objstem: *u8, incs: *u8, lf: *lflags,
packageonly: i32, istest: i32,
publishpackage: i32, requirecommand: i32, istest: i32,
products: *sepproduct, nproducts: i32, emitasm: i32,
workdir: *u8, scratchout: **u8,
graphout: **sepgraph) i32 = {
@@ -3663,9 +3683,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
SEP_ROLE_NORMAL, products[producti].artifact, true);
if (products[producti].root < 0) { return 1; };
products[producti].variantroot = products[producti].root;
if (istest == 0 && packageonly == 0) {
g.pkg[products[producti].root].linkentry = true;
};
producti += 1;
};
let testsupportmodule: str = "test";
@@ -3785,6 +3802,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
if (g.identityfailed) { return 1; };
if (sepfinalizedirectoryidentities(g) < 0) { return 1; };
if (istest == 0) {
producti = 0;
for (producti < nproducts) {
let root: i32 = products[producti].variantroot;
if (!g.pkg[root].failed) {
g.pkg[root].linkentry =
seprootiscommand(&g.pkg[root]);
};
producti += 1;
};
};
if (istest != 0 && entryisdir != 0) {
producti = 0;
for (producti < nproducts) {
@@ -3803,16 +3831,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
producti += 1;
};
};
let rootpackage: bool = istest == 0
&& !g.pkg[products[0].root].failed
&& !seprootiscommand(&g.pkg[products[0].root]);
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
if (warm && sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
if (warm && staleall && invalidateworkdirunits(scratch) != 0) { return 1; };
let rootpackage: bool = packageonly != 0;
if (rootpackage && !g.pkg[products[0].root].failed
&& cstreqlit(g.pkg[products[0].root].name, "main")) {
cerr("ww: -p requires a non-main package\n");
return 1;
};
let ci: i32 = 0;
let order: []i32;
let stack: []i32;
@@ -3839,6 +3863,21 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
producti += 1;
};
if (istest == 0 && requirecommand != 0) {
let root: i32 = products[0].root;
if (!g.pkg[root].failed && !seprootiscommand(&g.pkg[root])) {
let identity: *u8 = g.pkg[root].canon;
if (g.pkg[root].path[0u64] != 0u8) {
identity = g.pkg[root].path;
};
cerrpath("ww: package ", identity,
" is not a main package\n");
return 1;
};
};
if (!g.pkg[products[0].root].failed
&& rootpackage && publishpackage != 0 && emitasm == 0
&& validatepackageoutputpath(out) < 0) { return 1; };
ci = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
producti = 0;
@@ -4149,14 +4188,18 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (rootpackage) {
let root: i32 = products[0].root;
if (g.pkg[root].failed) { return 1; };
let archive: *u8 = sepfname(g, root, scratch, ".a");
let iface: *u8 = sepfname(g, root, scratch, ".wwi");
let outiface: *u8 = sepappendlit(out, ".wwi");
if (archive == nil || iface == nil || outiface == nil) { return 1; };
if (copyfileatomic(archive, out) != 0
|| copyfileatomic(iface, outiface) != 0) {
cerrpath("ww: cannot write package artifact ", out, "\n");
return 1;
if (publishpackage != 0) {
let archive: *u8 = sepfname(g, root, scratch, ".a");
let iface: *u8 = sepfname(g, root, scratch, ".wwi");
let outiface: *u8 = sepappendlit(out, ".wwi");
if (archive == nil || iface == nil || outiface == nil) {
return 1;
};
if (copyfileatomic(archive, out) != 0
|| copyfileatomic(iface, outiface) != 0) {
cerrpath("ww: cannot write package artifact ", out, "\n");
return 1;
};
};
return 0;
};
@@ -4305,7 +4348,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
// exact tree. Twin of the cstage build_one_sep wrapper.
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
objstem: *u8, incs: *u8, lf: *lflags, publishpackage: i32,
requirecommand: i32, istest: i32,
rootvariant: i32, testpackage: *u8, emitasm: i32,
keepscratch: i32, workdir: *u8) i32 = {
let scratch: *u8 = nil;
@@ -4317,7 +4361,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
product.testpackage = testpackage;
product.status = nil;
product.artifact = nil;
if (packageonly == 0 && entryisdir == 0) {
if (entryisdir == 0) {
product.artifact = "__root\0".ptr;
};
product.variant = rootvariant;
@@ -4325,7 +4369,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
product.variantroot = -1;
product.support = -1;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem,
incs, lf, packageonly, istest, &product, 1,
incs, lf, publishpackage, requirecommand, istest, &product, 1,
emitasm, workdir, &scratch, &g);
sepgraphfree(g);
if (keepscratch == 0 && scratch != nil) {
@@ -4367,7 +4411,7 @@ fn buildpackagetests(selfdir: *u8, src: *u8, rootidentity: *u8,
i += 1;
};
let r: i32 = buildonesepimpl(selfdir, src, 1,
products[0].out, products[0].out, incs, &lf, 0, 1,
products[0].out, products[0].out, incs, &lf, 0, 0, 1,
products, nproducts, 0, workdir, &scratch, &g);
sepgraphfree(g);
return r;
@@ -4464,7 +4508,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
};
fn writeusage(fd: i32) void = {
let s: str = "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 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\n path forms:\n 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 lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\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\n path forms:\n 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 -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";
os.write(fd, s.ptr, s.len: u64);
};
@@ -4508,7 +4552,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
let workdir: *u8 = nil; // -w persistent package-artifact workdir
let emitasm: i32 = 0;
let packageonly: i32 = 0;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
@@ -4535,8 +4578,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "-S")) {
emitasm = 1;
} else { if (cstreqlit(p, "-p")) {
packageonly = 1;
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -4616,7 +4657,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
} else {
cerr("ww build: unknown flag\n");
return 2;
}; }; }; }; }; }; };
}; }; }; }; }; };
} else {
if (src == nil) { src = p; };
};
@@ -4627,10 +4668,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0];
};
if (packageonly != 0 && emitasm != 0) {
cerr("ww build: -p and -S cannot be combined\n");
return 2;
};
let requestedliteral: bool = false;
let requestedstat: os.filestat;
match (os.stat(&requestedstat, pathstr(src))) {
@@ -4643,13 +4680,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerrpath("ww build: cannot find module ", src, "\n");
return 1;
};
if (packageonly != 0 && isdir == 0) {
cerr("ww build: -p needs a package directory\n");
return 2;
};
let out: *u8 = nil;
let objstem: *u8 = nil;
if (outflag != nil) {
if (outflag != nil && outflag[0u64] != 0u8) {
// -o sets both the binary path and the intermediate stem so
// artifacts land beside the requested output (T3).
out = outflag;
@@ -4678,9 +4711,11 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibs = nlibs;
let rootidentity: *u8 = nil;
if (!requestedliteral && isdir != 0) { rootidentity = src; };
let publishpackage: i32 = 0;
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
return buildonesep(selfdir, resolved, isdir, rootidentity,
out, objstem, incs.ptr, &lf,
packageonly, 0i32, SEP_VARIANT_PRODUCTION, nil,
publishpackage, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, 1i32, workdir);
};
@@ -4862,7 +4897,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (!requestedliteral && isdir != 0) { rootidentity = src; };
if (buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 0i32, SEP_VARIANT_PRODUCTION, nil, 0i32, 0i32, nil) != 0) {
0i32, 1i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
0i32, 0i32, nil) != 0) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
@@ -4952,7 +4988,8 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
let keep: i32 = 0;
if (outstem != nil) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 1i32, SEP_VARIANT_PRODUCTION, nil, emitasm, keep, workdir);
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, keep, workdir);
if (bres != 0) {
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));