From 44c47c3ea06f25f178053d1532f1ea980298ec68 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 10 Jun 2026 17:02:28 +0900 Subject: [PATCH] ww: build intermediates follow -o output stem, not the source dir (test-perf T3a) build_one/buildone gain an explicit objstem: 'ww build -o X' derives main.{combined.ww,s,o} beside X; 'ww run' uses its per-pid /tmp stem; default no--o stays next-to-source (load-bearing for the make regen of tracked combined.ww and the freshness gate). Realizes the redirect TODO in 995_self_rebuild.c. Kills the concurrent-test torn-read race on selfhost/cmd//main.* (the 991 transient). 993/995/949 pass -o into their per-pid workdirs; 949's '-o /dev/null' relied on the old driver ignoring -o (audited: the only live driver case). Atomic combined write (os.rename) deferred to its own commit - lib/os lacks rename and that addition is an import-floor regen. --- cmd/ww/main.c | 48 ++++++++++++++++------ selfhost/cmd/ww/main.combined.ww | 70 +++++++++++++++++++++++++------- selfhost/cmd/ww/main.ww | 70 +++++++++++++++++++++++++------- test/wcc/949_missingpkg.c | 15 ++++--- test/wcc/993_ww_ww.c | 17 ++++---- test/wcc/995_self_rebuild.c | 18 ++++---- 6 files changed, 178 insertions(+), 60 deletions(-) diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 4aec6a78..9267a17f 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -375,9 +375,15 @@ expand(FILE *out, const char *path, struct ImportSet *visited, fclose(in); } +/* objstem (when non-NULL/non-empty) redirects the .s/.o/.combined.ww + * side files to live beside the build's OUTPUT instead of next to the + * source (task #15/T3). `ww run` and `ww build -o` pass it so concurrent + * builds never share the next-to-source fixed paths; the default + * (objstem == NULL) keeps the old next-to-source layout, load-bearing + * for make's tracked-combined.ww regen + the #110 freshness gate. */ static int build_one(const char *src, int entry_is_dir, const char *out, - const char *extra_includes, const char *extra_libs, + const char *objstem, const char *extra_includes, const char *extra_libs, const char *extra_libdirs) { const char *c6 = toolpath("WW_W6C", "w6c"); @@ -447,10 +453,11 @@ build_one(const char *src, int entry_is_dir, const char *out, if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } + const char *ostem = (objstem && objstem[0]) ? objstem : stem; char asmf[1024], obj[1024], combined[1024]; - snprintf(asmf, sizeof asmf, "%s.s", stem); - snprintf(obj, sizeof obj, "%s.o", stem); - snprintf(combined, sizeof combined, "%s.combined.ww", stem); + snprintf(asmf, sizeof asmf, "%s.s", ostem); + snprintf(obj, sizeof obj, "%s.o", ostem); + snprintf(combined, sizeof combined, "%s.combined.ww", ostem); /* Resolve imports by concatenating sources into a temp file. The * compiler then sees one flat source. Dir entry → enumerate the @@ -629,6 +636,7 @@ parse_build_flags(int argc, char **argv, char *incs, size_t incsz, char *libdirs, size_t libdirsz, char *libs, size_t libsz, + char *outpath, size_t outsz, const char **src_out) { *src_out = NULL; @@ -658,6 +666,10 @@ parse_build_flags(int argc, char **argv, size_t n = strlen(incs); snprintf(incs + n, incsz - n, "%s%s", n ? ":" : "", argv[i] + 2); + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + snprintf(outpath, outsz, "%s", argv[++i]); + } else if (strncmp(argv[i], "-o", 2) == 0 && argv[i][2]) { + snprintf(outpath, outsz, "%s", argv[i] + 2); } else if (*src_out == NULL) { *src_out = argv[i]; } else { @@ -674,8 +686,10 @@ do_build(int argc, char **argv) char libs[2048] = {0}; char libdirs[2048] = {0}; char incs[2048] = {0}; + char outflag[1024] = {0}; parse_build_flags(argc, argv, incs, sizeof incs, - libdirs, sizeof libdirs, libs, sizeof libs, &src); + libdirs, sizeof libdirs, libs, sizeof libs, + outflag, sizeof outflag, &src); if (src == NULL) src = "."; /* default: build cwd */ char resolved[1024]; int is_dir = 0; @@ -684,7 +698,13 @@ do_build(int argc, char **argv) return 1; } char out[1024]; - if (is_dir) { + const char *objstem = NULL; + if (outflag[0]) { + /* -o sets both the binary path and the intermediate stem so + * artifacts land beside the requested output (T3). */ + snprintf(out, sizeof out, "%s", outflag); + objstem = out; + } else if (is_dir) { char tmp[1024]; snprintf(tmp, sizeof tmp, "%s", resolved); size_t n = strlen(tmp); @@ -694,7 +714,7 @@ do_build(int argc, char **argv) } else { basename_no_ext(resolved, out, sizeof out); } - return build_one(resolved, is_dir, out, incs, libs, libdirs); + return build_one(resolved, is_dir, out, objstem, incs, libs, libdirs); } static int @@ -704,8 +724,10 @@ do_run(int argc, char **argv) char libs[2048] = {0}; char libdirs[2048] = {0}; char incs[2048] = {0}; + char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */ int next = parse_build_flags(argc, argv, incs, sizeof incs, - libdirs, sizeof libdirs, libs, sizeof libs, &src); + libdirs, sizeof libdirs, libs, sizeof libs, + outflag, sizeof outflag, &src); if (src == NULL) src = "."; char resolved[1024]; int is_dir = 0; @@ -715,7 +737,9 @@ do_run(int argc, char **argv) } char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid()); - if (build_one(resolved, is_dir, tmp, incs, libs, libdirs) != 0) + /* objstem = tmp → intermediates land at /tmp/ww_run_.{s,o, + * combined.ww}, never next to the source (T3). */ + if (build_one(resolved, is_dir, tmp, tmp, incs, libs, libdirs) != 0) return 1; /* exec the built binary with any trailing argv as its argv. */ pid_t pid = fork(); @@ -754,7 +778,7 @@ do_test(int argc, char **argv) } char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); - if (build_one(resolved, is_dir, tmp, "", "", "") != 0) return 1; + if (build_one(resolved, is_dir, tmp, NULL, "", "", "") != 0) return 1; int rc = run(tmp); unlink(tmp); return rc; @@ -763,7 +787,7 @@ do_test(int argc, char **argv) /* single .ww file — build+run it. */ char tmp[1024]; snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); - if (build_one(target, 0, tmp, "", "", "") != 0) return 1; + if (build_one(target, 0, tmp, NULL, "", "", "") != 0) return 1; int rc = run(tmp); unlink(tmp); return rc; @@ -789,7 +813,7 @@ do_test(int argc, char **argv) snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d_%d", getpid(), i); const char *label = strrchr(files[i], '/'); label = label ? label + 1 : files[i]; - int rc = build_one(files[i], 0, tmp, target, "", ""); + int rc = build_one(files[i], 0, tmp, NULL, target, "", ""); if (rc != 0) { fprintf(stderr, "FAIL %s (build)\n", label); fail++; diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index c4798617..49c0c6e4 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -3581,8 +3581,11 @@ fn appendlit(stem: *u8, suffix: str) *u8 = { return buf.ptr; }; -// linker flags bundled as a struct so buildone stays at the wwstage -// w6c's 6-argument calling-convention limit. +// linker flags (-L, -l) grouped as one struct so buildone's +// param list stays readable. (The earlier note here claimed a 6-argument +// wwstage calling-convention cap; that is stale — emittuplerowrelocs in +// cgen.ww takes 7 params and self-compiles green, and buildone itself now +// takes 7.) type lflags = struct { libdirs: **u8, nlibdirs: i32, @@ -3597,6 +3600,13 @@ type lflags = struct { // src: NUL-terminated entry path (file or directory). // entryisdir: non-zero when src is a module directory. // out: NUL-terminated desired output path +// objstem: when non-nil, redirects the .s/.o/.combined.ww side +// files to live beside this stem instead of next to the +// source (T3 / task #15). `ww run` and `ww build -o` pass +// it so concurrent builds never share next-to-source fixed +// paths; nil keeps the old next-to-source layout (the make +// tracked-combined.ww regen + #110 gate depend on it). Twin +// of cmd/ww/main.c build_one's objstem. // incs: NUL-terminated colon-list of -I dirs (may be empty) // lf: extra linker flags (-L, -l); may be nil // @@ -3604,7 +3614,7 @@ type lflags = struct { // touches no C-built code at runtime. The C `ww` driver in cmd/ww/ // still drives the C-built w6c/w6a/w6l. Test 993 pins the two // pipelines to byte-identical output on a corpus. -fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *lflags) i32 = { +fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, incs: *u8, lf: *lflags) i32 = { let c6: *u8 = joinpathlit(selfdir, "w6c_ww"); let a6: *u8 = joinpathlit(selfdir, "w6a_ww"); let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); @@ -3687,9 +3697,11 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l } else { makestem(stem.ptr, src); }; - let asmf: *u8 = appendlit(stem.ptr, ".s"); - let objf: *u8 = appendlit(stem.ptr, ".o"); - let combined: *u8 = appendlit(stem.ptr, ".combined.ww"); + let effstem: *u8 = stem.ptr; + if (objstem != nil) { effstem = objstem; }; + let asmf: *u8 = appendlit(effstem, ".s"); + let objf: *u8 = appendlit(effstem, ".o"); + let combined: *u8 = appendlit(effstem, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!; @@ -3934,6 +3946,7 @@ fn defaultoutpath(src: *u8) *u8 = { fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let src: *u8 = nil; + let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3 let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; incs.len = ((os.PATH_MAX: u64) * 2u64): i32; let incoff: u64 = 0u64; @@ -4005,10 +4018,21 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; libs[nlibs] = nm; nlibs += 1; + } else { if (p[1u64] == 111u8) { // '-o' + if (p[2u64] != 0u8) { + outflag = p + 2u64; + } else { + if (i + 1 >= argc) { + cerr("ww build: -o needs an argument\n"); + return 2; + }; + i += 1; + outflag = argv[i]; + }; } else { cerr("ww build: unknown flag\n"); return 2; - }; }; }; + }; }; }; }; } else { if (src == nil) { src = p; }; }; @@ -4027,7 +4051,13 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return 1; }; let out: *u8 = nil; - if (isdir != 0) { + let objstem: *u8 = nil; + if (outflag != nil) { + // -o sets both the binary path and the intermediate stem so + // artifacts land beside the requested output (T3). + out = outflag; + objstem = outflag; + } else { if (isdir != 0) { let rlen: u64 = cstrlen(resolved); for (rlen > 1u64) { if (resolved[rlen - 1u64] != 47u8) { break; }; @@ -4043,13 +4073,13 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cstrseal(out, off); } else { out = defaultoutpath(resolved); - }; + }; }; let lf: lflags; lf.libdirs = libdirs.ptr; lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; - return buildone(selfdir, resolved, isdir, out, incs.ptr, &lf); + return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf); }; // Format the scratch path /tmp/ww_run_ into buf. Returns NUL- @@ -4158,10 +4188,20 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; libs[nlibs] = nm; nlibs += 1; + } else { if (p[1u64] == 111u8) { // '-o' + // run always execs the temp binary; -o is accepted+ + // ignored, mirroring the C driver's shared flag parser. + if (p[2u64] == 0u8) { + if (i + 1 >= argc) { + cerr("ww run: -o needs an argument\n"); + return 2; + }; + i += 1; + }; } else { cerr("ww run: unknown flag\n"); return 2; - }; }; }; + }; }; }; }; i += 1; } else { if (src == nil) { @@ -4193,7 +4233,9 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; - if (buildone(selfdir, resolved, isdir, tmp.ptr, incs.ptr, &lf) != 0) { + // objstem = tmp → intermediates at /tmp/ww_run_.{s,o,combined.ww}, + // never next to the source (T3). + if (buildone(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; @@ -4227,7 +4269,7 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = { let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!; tmp.len = os.PATH_MAX; makeruntmp(tmp.ptr); - if (buildone(selfdir, src, 0, tmp.ptr, "\0".ptr, nil) != 0) { + if (buildone(selfdir, src, 0, tmp.ptr, nil, "\0".ptr, nil) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; @@ -4282,7 +4324,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!; tmp.len = os.PATH_MAX; makeruntmp(tmp.ptr); - let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, tincs.ptr, nil); + let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil); if (bres != 0) { fail += 1; cerr("FAIL "); diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 1b31a382..844b4af3 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -795,8 +795,11 @@ fn appendlit(stem: *u8, suffix: str) *u8 = { return buf.ptr; }; -// linker flags bundled as a struct so buildone stays at the wwstage -// w6c's 6-argument calling-convention limit. +// linker flags (-L, -l) grouped as one struct so buildone's +// param list stays readable. (The earlier note here claimed a 6-argument +// wwstage calling-convention cap; that is stale — emittuplerowrelocs in +// cgen.ww takes 7 params and self-compiles green, and buildone itself now +// takes 7.) type lflags = struct { libdirs: **u8, nlibdirs: i32, @@ -811,6 +814,13 @@ type lflags = struct { // src: NUL-terminated entry path (file or directory). // entryisdir: non-zero when src is a module directory. // out: NUL-terminated desired output path +// objstem: when non-nil, redirects the .s/.o/.combined.ww side +// files to live beside this stem instead of next to the +// source (T3 / task #15). `ww run` and `ww build -o` pass +// it so concurrent builds never share next-to-source fixed +// paths; nil keeps the old next-to-source layout (the make +// tracked-combined.ww regen + #110 gate depend on it). Twin +// of cmd/ww/main.c build_one's objstem. // incs: NUL-terminated colon-list of -I dirs (may be empty) // lf: extra linker flags (-L, -l); may be nil // @@ -818,7 +828,7 @@ type lflags = struct { // touches no C-built code at runtime. The C `ww` driver in cmd/ww/ // still drives the C-built w6c/w6a/w6l. Test 993 pins the two // pipelines to byte-identical output on a corpus. -fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *lflags) i32 = { +fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, incs: *u8, lf: *lflags) i32 = { let c6: *u8 = joinpathlit(selfdir, "w6c_ww"); let a6: *u8 = joinpathlit(selfdir, "w6a_ww"); let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); @@ -901,9 +911,11 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l } else { makestem(stem.ptr, src); }; - let asmf: *u8 = appendlit(stem.ptr, ".s"); - let objf: *u8 = appendlit(stem.ptr, ".o"); - let combined: *u8 = appendlit(stem.ptr, ".combined.ww"); + let effstem: *u8 = stem.ptr; + if (objstem != nil) { effstem = objstem; }; + let asmf: *u8 = appendlit(effstem, ".s"); + let objf: *u8 = appendlit(effstem, ".o"); + let combined: *u8 = appendlit(effstem, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!; @@ -1148,6 +1160,7 @@ fn defaultoutpath(src: *u8) *u8 = { fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let src: *u8 = nil; + let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3 let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; incs.len = ((os.PATH_MAX: u64) * 2u64): i32; let incoff: u64 = 0u64; @@ -1219,10 +1232,21 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; libs[nlibs] = nm; nlibs += 1; + } else { if (p[1u64] == 111u8) { // '-o' + if (p[2u64] != 0u8) { + outflag = p + 2u64; + } else { + if (i + 1 >= argc) { + cerr("ww build: -o needs an argument\n"); + return 2; + }; + i += 1; + outflag = argv[i]; + }; } else { cerr("ww build: unknown flag\n"); return 2; - }; }; }; + }; }; }; }; } else { if (src == nil) { src = p; }; }; @@ -1241,7 +1265,13 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return 1; }; let out: *u8 = nil; - if (isdir != 0) { + let objstem: *u8 = nil; + if (outflag != nil) { + // -o sets both the binary path and the intermediate stem so + // artifacts land beside the requested output (T3). + out = outflag; + objstem = outflag; + } else { if (isdir != 0) { let rlen: u64 = cstrlen(resolved); for (rlen > 1u64) { if (resolved[rlen - 1u64] != 47u8) { break; }; @@ -1257,13 +1287,13 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cstrseal(out, off); } else { out = defaultoutpath(resolved); - }; + }; }; let lf: lflags; lf.libdirs = libdirs.ptr; lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; - return buildone(selfdir, resolved, isdir, out, incs.ptr, &lf); + return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf); }; // Format the scratch path /tmp/ww_run_ into buf. Returns NUL- @@ -1372,10 +1402,20 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; libs[nlibs] = nm; nlibs += 1; + } else { if (p[1u64] == 111u8) { // '-o' + // run always execs the temp binary; -o is accepted+ + // ignored, mirroring the C driver's shared flag parser. + if (p[2u64] == 0u8) { + if (i + 1 >= argc) { + cerr("ww run: -o needs an argument\n"); + return 2; + }; + i += 1; + }; } else { cerr("ww run: unknown flag\n"); return 2; - }; }; }; + }; }; }; }; i += 1; } else { if (src == nil) { @@ -1407,7 +1447,9 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { lf.nlibdirs = nlibdirs; lf.libs = libs.ptr; lf.nlibs = nlibs; - if (buildone(selfdir, resolved, isdir, tmp.ptr, incs.ptr, &lf) != 0) { + // objstem = tmp → intermediates at /tmp/ww_run_.{s,o,combined.ww}, + // never next to the source (T3). + if (buildone(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; @@ -1441,7 +1483,7 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = { let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!; tmp.len = os.PATH_MAX; makeruntmp(tmp.ptr); - if (buildone(selfdir, src, 0, tmp.ptr, "\0".ptr, nil) != 0) { + if (buildone(selfdir, src, 0, tmp.ptr, nil, "\0".ptr, nil) != 0) { os.remove(pathstr(tmp.ptr)); return 1; }; @@ -1496,7 +1538,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!; tmp.len = os.PATH_MAX; makeruntmp(tmp.ptr); - let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, tincs.ptr, nil); + let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil); if (bres != 0) { fail += 1; cerr("FAIL "); diff --git a/test/wcc/949_missingpkg.c b/test/wcc/949_missingpkg.c index ab9b648e..e0dd350a 100644 --- a/test/wcc/949_missingpkg.c +++ b/test/wcc/949_missingpkg.c @@ -35,21 +35,26 @@ static int run_miss(const char *bin) { int pid = getpid(); - char src[64], errf[64], cmd[2048], line[4096]; + char src[64], errf[64], outb[64], comb[80], cmd[2048], line[4096]; snprintf(src, sizeof src, "/tmp/mp949_miss_%d.ww", pid); snprintf(errf, sizeof errf, "/tmp/mp949_miss_%d.err", pid); + /* -o a writable /tmp stem (not /dev/null): post-T3 the intermediate + * .combined.ww/.s/.o follow -o, so /dev/null would yield an + * uncreatable /dev/null.combined.ww and mask the missing-pkg fatal. */ + snprintf(outb, sizeof outb, "/tmp/mp949_miss_%d.out", pid); + snprintf(comb, sizeof comb, "/tmp/mp949_miss_%d.out.combined.ww", pid); FILE *f = fopen(src, "w"); if (!f) { fprintf(stderr, "949 FAIL: stage miss\n"); return 1; } fputs("import nosuchpkg;\n" "export fn main() i32 = { return 0; };\n", f); fclose(f); - snprintf(cmd, sizeof cmd, "%s/ww build %s -o /dev/null 2>%s", - bin, src, errf); + snprintf(cmd, sizeof cmd, "%s/ww build %s -o %s 2>%s", + bin, src, outb, errf); int rc = runwait(cmd); if (rc == 0) { fprintf(stderr, "949 FAIL: ww accepted a missing import\n"); - unlink(src); unlink(errf); + unlink(src); unlink(errf); unlink(outb); unlink(comb); return 1; } int found = 0; @@ -61,7 +66,7 @@ run_miss(const char *bin) } fclose(f); } - unlink(src); unlink(errf); + unlink(src); unlink(errf); unlink(outb); unlink(comb); if (!found) { fprintf(stderr, "949 FAIL: no 'cannot find package nosuchpkg' " "on stderr\n"); diff --git a/test/wcc/993_ww_ww.c b/test/wcc/993_ww_ww.c index 11329a9a..257c444f 100644 --- a/test/wcc/993_ww_ww.c +++ b/test/wcc/993_ww_ww.c @@ -62,17 +62,20 @@ slurp_eq(const char *a, const char *b) * success. */ static int build_via(const char *bin, const char *driver, const char *src, - const char *workdir, const char *incs) + const char *workdir, const char *incs, const char *out_basename) { char cmd[4096]; + /* -o / keeps the binary where the diff expects it AND + * routes the .combined.ww/.s/.o into the workdir (T3) — no longer + * next to the source, so concurrent driver builds never collide. */ if (incs && incs[0]) { snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/%s build -I %s %s 2>/dev/null", - workdir, bin, driver, incs, src); + "cd %s && timeout 180 %s/%s build -o %s/%s -I %s %s 2>/dev/null", + workdir, bin, driver, workdir, out_basename, incs, src); } else { snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/%s build %s 2>/dev/null", - workdir, bin, driver, src); + "cd %s && timeout 180 %s/%s build -o %s/%s %s 2>/dev/null", + workdir, bin, driver, workdir, out_basename, src); } return runwait(cmd); } @@ -90,11 +93,11 @@ diff_one(const char *bin, const char *cwd, const char *label, snprintf(cmd, sizeof cmd, "rm -rf %s %s && mkdir -p %s %s", dc, dw, dc, dw); if (runwait(cmd) != 0) return -1; - if (build_via(bin, "ww", src, dc, incs) != 0) { + if (build_via(bin, "ww", src, dc, incs, out_basename) != 0) { fprintf(stderr, "ww_ww FAIL: C ww errored on %s\n", label); return -1; } - if (build_via(bin, "ww_ww", src, dw, incs) != 0) { + if (build_via(bin, "ww_ww", src, dw, incs, out_basename) != 0) { fprintf(stderr, "ww_ww FAIL: ww ww errored on %s\n", label); return -1; } diff --git a/test/wcc/995_self_rebuild.c b/test/wcc/995_self_rebuild.c index a188c15c..d4156ea0 100644 --- a/test/wcc/995_self_rebuild.c +++ b/test/wcc/995_self_rebuild.c @@ -82,10 +82,12 @@ struct buildjob { /* Fork a child that runs the `ww_ww build` for this tool. The child * inherits no concurrent siblings — system() spawns a fresh /bin/sh -c. * stderr lands in /build.err so concurrent builds don't merge - * their diagnostics. The driver leaves intermediates next to every - * source it traverses (selfhost/cmd//main.s, etc.) — task #15 in - * the queue will let us redirect via `-o `; until then, test/run - * serializes 990/995 after the parallel phase. */ + * their diagnostics. `-o /main` (T3) makes the driver write its + * intermediates (main.{combined.ww,s,o}) beside the output in + * instead of next to every traversed source — so concurrent driver + * builds no longer share the next-to-source fixed paths, and this gate + * may run in the parallel group. The rebuilt binary still lands at + * /main where collect_build diffs it. */ static int spawn_build(const char *bin, const char *cwd, struct buildjob *j) { @@ -99,17 +101,17 @@ spawn_build(const char *bin, const char *cwd, struct buildjob *j) char cmd[4096]; if (j->inc_local && j->inc_local[0]) { snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/ww_ww build -I %s/%s " + "cd %s && timeout 180 %s/ww_ww build -o %s/main -I %s/%s " "-I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse " "-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err", - j->workdir, bin, cwd, j->inc_local, + j->workdir, bin, j->workdir, cwd, j->inc_local, cwd, cwd, cwd, cwd, cwd, j->src_rel, j->workdir); } else { snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/ww_ww build " + "cd %s && timeout 180 %s/ww_ww build -o %s/main " "-I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse " "-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err", - j->workdir, bin, cwd, cwd, cwd, cwd, cwd, j->src_rel, j->workdir); + j->workdir, bin, j->workdir, cwd, cwd, cwd, cwd, cwd, j->src_rel, j->workdir); } pid_t p = fork();