diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 417d1b32..538872e9 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -32,6 +32,7 @@ static const char *usage = " . build the cwd's .ww\n"; static char *self_dir; +static const char *self_path; static const char * envpath(const char *name) @@ -1533,8 +1534,8 @@ archive_o(const char *objpath, const char *apath) * package-artifact tree that replaces the fresh `.sepwork` scratch. * Staleness is pure content * identity, never mtime: a package is reused only when its freshly - * composed unit byte-equals the committed unit AND the tool copies - * recorded in the dir byte-equal the live tools — every decision is + * composed unit byte-equals the committed unit AND the driver/tool copies + * recorded in the dir byte-equal the live executables — every decision is * reproducible by hand with cmp(1) against plain files. Artifacts commit * via temp + rename with the unit renamed last, so a killed build can * never leave a committed unit vouching for uncommitted artifacts. The @@ -1629,10 +1630,10 @@ 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 ? 5 : 4, is_test ? "test" : "build", emit_asm); + is_test ? 6 : 5, is_test ? "test" : "build", emit_asm); } -/* A stale global tool identity invalidates every committed unit voucher in +/* A stale global builder identity invalidates every committed unit voucher in * this driver-owned workdir before compilation starts. Artifacts may remain, * but without their unit they cannot be reused. That makes it safe to record * the new identity after a partial multi-root pass: successful actions have @@ -1753,9 +1754,16 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (scratchout) snprintf(scratchout, scratchoutsz, "%s", scratch); } int stale_all = 0, stampok = 0; - char toolc[1200] = {0}, toola[1200] = {0}, stampf[1200] = {0}; + char toolw[1200] = {0}, toolc[1200] = {0}, toola[1200] = {0}; + char stampf[1200] = {0}; char stampwant[128]; if (warm) { + if (self_path == NULL || !file_is_reg(self_path)) { + fprintf(stderr, "ww: cannot read driver identity %s\n", + self_path ? self_path : "(unknown)"); + return 1; + } + snprintf(toolw, sizeof toolw, "%s/.wwtool.ww", scratch); snprintf(toolc, sizeof toolc, "%s/.wwtool.w6c", scratch); snprintf(toola, sizeof toola, "%s/.wwtool.w6a", scratch); snprintf(stampf, sizeof stampf, "%s/.wwtool.stamp", scratch); @@ -1768,7 +1776,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, fclose(sf); } stampok = strcmp(stampwant, got) == 0; - if (!stampok || !file_equal(toolc, c6) + if (!stampok || !file_equal(toolw, self_path) + || !file_equal(toolc, c6) || (!emit_asm && !file_equal(toola, a6))) stale_all = 1; if (stale_all && invalidate_workdir_units(scratch) != 0) @@ -2069,6 +2078,11 @@ build_one_sep_impl(const char *src, int entry_is_dir, * root failed; a killed pass leaves the old identity and forces another * invalidating pass, never false reuse. */ if (warm) { + if (!file_equal(toolw, self_path) + && copy_file_atomic(self_path, toolw) != 0) { + fprintf(stderr, "ww: cannot record %s\n", toolw); + free(order); return 1; + } if (!file_equal(toolc, c6) && copy_file_atomic(c6, toolc) != 0) { fprintf(stderr, "ww: cannot record %s\n", toolc); @@ -2973,6 +2987,7 @@ int main(int argc, char **argv) { if (argc >= 1) { + self_path = argv[0]; char buf[1024]; snprintf(buf, sizeof buf, "%s", argv[0]); self_dir = strdup(dirname(buf)); diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index bc7868ee..8aea8355 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -25,6 +25,8 @@ import syntax; // the cgen #127 mod-mangle attribution bug consumer per rule-7. def CMD_MAX: u64 = 8192u64; +let selfpath: *u8; + // Tool-local (NOT a lib wrapper): messages are built from many // fragments and we route through os.write to avoid libc stdio. // .len replaces the error-prone hand-counted byte literals these sites @@ -1803,8 +1805,8 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = { // A `-w DIR` workdir is a caller-owned persistent package-artifact tree // that replaces the fresh `.sepwork` scratch. Staleness is pure content // identity, never mtime: a package is reused only when its freshly -// composed unit byte-equals the committed unit AND the tool copies -// recorded in the dir byte-equal the live tools — every decision is +// composed unit byte-equals the committed unit AND the driver/tool copies +// recorded in the dir byte-equal the live executables — every decision is // reproducible by hand with cmp(1) against plain files. Artifacts commit // via temp + rename with the unit renamed last, so a killed build can // never leave a committed unit vouching for uncommitted artifacts. The @@ -1914,14 +1916,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = { fn workdirstamptext(istest: i32, emitasm: i32) str = { if (istest != 0) { if (emitasm != 0) { - return "ww workdir fmt 5 mode test asm 1\n"; + return "ww workdir fmt 6 mode test asm 1\n"; }; - return "ww workdir fmt 5 mode test asm 0\n"; + return "ww workdir fmt 6 mode test asm 0\n"; }; if (emitasm != 0) { - return "ww workdir fmt 4 mode build asm 1\n"; + return "ww workdir fmt 5 mode build asm 1\n"; }; - return "ww workdir fmt 4 mode build asm 0\n"; + return "ww workdir fmt 5 mode build asm 0\n"; }; fn stampmatches(path: *u8, want: str) bool = { @@ -1976,7 +1978,7 @@ fn recordproductstatus(path: *u8) i32 = { return os.rename(pathstr(tmpp), pathstr(path)); }; -// Remove every committed unit voucher before a stale-tool pass. Remaining +// Remove every committed unit voucher before a stale-builder pass. Remaining // artifacts cannot be reused without their matching unit, so a partial pass // may safely record its new tool identity: successful actions have current // units and failed/no-longer-requested actions have none. @@ -2138,17 +2140,26 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; let staleall: bool = false; let stampok: bool = false; + let toolw: *u8 = nil; let toolc: *u8 = nil; let toola: *u8 = nil; let stampf: *u8 = nil; let stampwant: str = ""; if (warm) { + if (!fileisreg(selfpath)) { + cerrpath("ww: cannot read driver identity ", selfpath, "\n"); + return 1; + }; + toolw = joinpathlit(scratch, ".wwtool.ww"); toolc = joinpathlit(scratch, ".wwtool.w6c"); toola = joinpathlit(scratch, ".wwtool.w6a"); stampf = joinpathlit(scratch, ".wwtool.stamp"); stampwant = workdirstamptext(istest, emitasm); stampok = stampmatches(stampf, stampwant); staleall = !stampok; + if (!staleall) { + if (!fileequal(toolw, selfpath)) { staleall = true; }; + }; if (!staleall) { if (!fileequal(toolc, c6)) { staleall = true; }; }; @@ -2589,6 +2600,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, // successful units remain safe to vouch for when a sibling root fails; // a killed pass retains the old identity and invalidates again next time. if (warm) { + if (!fileequal(toolw, selfpath)) { + if (copyfileatomic(selfpath, toolw) != 0) { + cerrpath("ww: cannot record ", toolw, "\n"); + return 1; + }; + }; if (!fileequal(toolc, c6)) { if (copyfileatomic(c6, toolc) != 0) { cerrpath("ww: cannot record ", toolc, "\n"); @@ -3732,6 +3749,7 @@ export fn main(argc: i32, argv: **u8) i32 = { writeusage(2); return 2; }; + selfpath = argv[0]; let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!; selfdir.len = os.PATH_MAX;