diff --git a/docs/build-system.md b/docs/build-system.md index e7b72dce..ddeaa4e5 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -6286,11 +6286,11 @@ transaction prevents partial sibling publication and canonical result emission order remains unchanged. Direct invocation of a retained binary continues to inherit caller cwd, environment, and separate standard descriptors. -`-o` can accompany `-w`: unchanged semantic actions are reused, changed source -invalidates the applicable test actions, the always-run link refreshes the -private runnable and retained copy, and the test still runs. This is build -reuse, not a result cache. `-c` retains its established incompatibility with -`-w`; this slice does not invent persistent compile-only ownership. +`-c` and `-o` can accompany `-w`: the workdir owns only semantic actions while +the invocation/output path owns only the retained copy. Unchanged actions are +reused, changed source invalidates the applicable test actions, and the +always-run link refreshes the private runnable and retained copy. Only `-c` +suppresses execution. This is build reuse, not a result cache. No persisted byte schema changed. Build workdir format remains `18`, test workdir format remains `19`, and semantic storage remains `3`. diff --git a/docs/spec.md b/docs/spec.md index 0a4cdaf3..d15e6a38 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -665,6 +665,9 @@ destinations and removes temporary stages and invocation-created output prefixes. Test execution starts only after that transaction commits, so a runtime failure leaves an explicitly retained binary. A no-test product publishes no binary and does not create a directory solely for one. +`-w` may persist the unchanged semantic actions for either `-c` or running +retention without changing publication identity or introducing a test-result +cache. When `ww test` executes a directory-owned product, the child process working directory is that product's canonical absolute physical package source diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 67f33b04..5186df10 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -482,11 +482,12 @@ path as the one command-global semantic-action store; neither pattern spelling nor package-group name derives a persistent subdirectory. The delegated driver may create a missing store only after graph and request preflight, and rolls back newly created empty prefixes if later setup fails. Every reuse decision -stays with the driver's content-identity contract above. `-w` and test `-c` do -not mix — the coordinator rejects the combination rather than guess which -artifact tree the caller owns. `test-library` uses exactly this: one -`ww test -j $(JOBS) -w out/wwbuild/wwtest-lib lib/...` line owns library -behavior (measured: 2.3s cold, 1.0s warm at +stays with the driver's content-identity contract above. Test `-c` may use the +same store: the workdir owns semantic actions and the invocation/output path +owns the separately staged retained executable. The `test-library` route uses +the same command-global store contract: one `ww test -j $(JOBS) -w +out/wwbuild/wwtest-lib lib/...` line owns library behavior (measured: 2.3s +cold, 1.0s warm at `-j 4`, vs 4.7s for the retired 59-target per-file fan-out). `test/lang` currently uses one package per source file, so its complete gate diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index fc5e2374..80507ccf 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -1853,15 +1853,6 @@ export fn packagecommand(args: []str) int = { pkgusage(); return 2; }; - // -c suppresses execution and requests a caller-visible binary. Keep it - // separate from -w until compile-only persistent-action ownership is wired; - // -o without -c already permits a retained copy beside a persistent store. - if (workroot.len != 0 && compileonly && !buildonly) { - pkgputln(os.STDERR_FILENO, - "wwtest package: -w conflicts with -c"); - return 2; - }; - let ds: pkgdiscover; let emptypaths: []str; ds.paths = emptypaths; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 4fe6d3bc..54c06101 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -9215,18 +9215,26 @@ fn cwdwritedata(dir: str, label: str) void = { (30i64 * (time.second: i64)): time.duration, &outc); expectexit(&outc, 0); assert(same(outc.stdout, seq)); - // -c publishes caller-owned artifacts; a persistent workdir is a - // different ownership contract — the coordinator rejects the mix. + // The retained executable is separate from the persistent action store, so + // -c can suppress execution while reusing the same complete graph. + let wcbin: str = strings.concat(root, "/tree-wc-c.test"); + let wwbin: str = strings.concat(root, "/tree-wc-ww.test"); let wcc: []str = [driver("ww"), "test", "-c", "-w", wdroot, - "-I", tree, spec]; + "-o", wcbin, "-I", tree, strings.concat(tree, "/alpha")]; let wcw: []str = [driver("ww_ww"), "test", "-c", "-w", wdroot, - "-I", tree, spec]; - runcommand(root, "tree-wc-c", wcc, time.second, &outc); - runcommand(root, "tree-wc-ww", wcw, time.second, &outw); - expectexit(&outc, 2); - expectexit(&outw, 2); + "-o", wwbin, "-I", tree, strings.concat(tree, "/alpha")]; + runcommand(root, "tree-wc-c", wcc, + (30i64 * (time.second: i64)): time.duration, &outc); + runcommand(root, "tree-wc-ww", wcw, + (30i64 * (time.second: i64)): time.duration, &outw); + expectexit(&outc, 0); + expectexit(&outw, 0); + assert(outc.stdout.len == 0 && outc.stderr.len == 0); + assert(outw.stdout.len == 0 && outw.stderr.len == 0); + assert(os.exists(wcbin) && os.exists(wwbin)); + assert(same(readfile(wcbin), readfile(wwbin))); assert(same(outc.stderr, outw.stderr)); - assert(has(outc.stderr, "wwtest package: -w conflicts with -c\n")); + clean(wcbin); clean(wwbin); let bare: str = strings.concat(root, "/bare"); let barez: str = strings.concat(root, "/zz-bare"); @@ -12566,42 +12574,60 @@ fn runtimepath(relative: str) str = { if (si == 0) { failurebytes = readfile(failurebin); } else { assert(same(failurebytes, readfile(failurebin))); }; - // -w keeps semantic actions persistent while -o remains a presentation - // copy. Unchanged input reuses actions; changed input replaces the copy. + // -w keeps semantic actions persistent while -c and -o remain execution + // and presentation policy. Compile-only can cold-build and invalidate; + // an unchanged running request reuses those actions and the same bytes. rewritefile(persisttest, persistbase); let persistwork: str = strings.concat(root, "/persist-work-", tags[si]); - let persistbin: str = strings.concat(root, "/persist.test"); - if (os.exists(persistbin)) { clean(persistbin); }; + let persistcompilebin: str = strings.concat(root, "/persist-compile-", + tags[si], ".test"); + let persistbin: str = strings.concat(root, "/persist-run-", tags[si], + ".test"); rewritefile(ctrace, ""); rewritefile(ltrace, ""); + let persistcompileav: []str = [driver(stages[si]), "test", "-c", + "-w", persistwork, "-I", suite, "-o", persistcompilebin, + persist]; let persistav: []str = [driver(stages[si]), "test", "-w", persistwork, "-I", suite, "-o", persistbin, persist]; - runcommandenvdir(root, strings.concat("publish-persist-cold-", tags[si]), + runcommandenvdir(root, strings.concat("publish-persist-cold-c-", tags[si]), + persistcompileav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(readfile(ctrace).len != 0); + let basebytes: str = readfile(persistcompilebin); + if (si == 0) { persistbasebytes = strings.dup(basebytes); } + else { assert(same(persistbasebytes, basebytes)); }; + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("publish-persist-warm-run-", tags[si]), persistav, env, root, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(has(out.stdout, "persistent_base ... ok\n")); - let basebytes: str = readfile(persistbin); - if (si == 0) { persistbasebytes = strings.dup(basebytes); } - else { assert(same(persistbasebytes, basebytes)); }; - rewritefile(ctrace, ""); rewritefile(ltrace, ""); - runcommandenvdir(root, strings.concat("publish-persist-warm-", tags[si]), - persistav, env, root, - (120i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); assert(readfile(ctrace).len == 0); assert(same(basebytes, readfile(persistbin))); rewritefile(persisttest, persistchanged); rewritefile(ctrace, ""); rewritefile(ltrace, ""); - runcommandenvdir(root, strings.concat("publish-persist-change-", tags[si]), - persistav, env, root, + runcommandenvdir(root, strings.concat("publish-persist-change-c-", tags[si]), + persistcompileav, env, root, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(has(out.stdout, "persistent_changed ... ok\n")); + assert(out.stdout.len == 0 && out.stderr.len == 0); assert(readfile(ctrace).len != 0); - let changedbytes: str = readfile(persistbin); + let changedbytes: str = readfile(persistcompilebin); assert(!same(basebytes, changedbytes)); if (si == 0) { persistchangedbytes = strings.dup(changedbytes); } else { assert(same(persistchangedbytes, changedbytes)); }; + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("publish-persist-change-run-", + tags[si]), persistav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "persistent_changed ... ok\n")); + assert(readfile(ctrace).len == 0); + assert(same(changedbytes, readfile(persistbin))); + assert(!os.exists(strings.concat(persistcompilebin, ".new"))); + assert(!os.exists(strings.concat(persistbin, ".new"))); assert(!directoryhasnew(persistwork)); si += 1; };