diff --git a/docs/build-system.md b/docs/build-system.md index 6e81107c..273f0ab8 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -3445,6 +3445,182 @@ The pinned official Go 1.26.5 tag (commit WW adopts those practical ownership and action semantics while retaining its small direct CLI representation and existing self-contained `.wwi` encoding. +### 11.13 Implemented unbounded semantic package-identity storage slice + +Canonical package identity is no longer stored in, derived from, or bounded by +one internal filesystem component. In both drivers every package action now +keeps these values separately: + +- `path`: the complete compiler/import identity; +- `import_base`: the complete canonical ordinary directory identity; +- `canon`: the complete canonical directory location; +- `variant` and `role`: the semantic test/action tags; and +- `storage`: an internal scratch basename that is never passed as package + identity. + +The first four values, not `storage`, participate in directory-action +interning, source-import edges, module-reset ownership, compiler `--import` +arguments, export ownership, diagnostics, generated-main construction, and +symbol qualification. The reversible outside-root form remains +`__wwlocal.p.` and is +allocated to its exact length. It is neither truncated nor replaced by a +digest, and `__wwlocal` remains unavailable to source imports. Package +declarations continue to validate the selected package kind and leaf; they do +not supply a missing identity and do not alter a command package's canonical +path. + +Short actions retain their established `.unit.ww`, `.wwi`, `.s`, `.o`, and +`.a` basenames when the basename plus `.unit.new` fits the 255-byte supported +filesystem component bound and the complete path fits the host pathname API. +An action that does not fit uses this bounded storage locator: + +```text +__wwpkg.v.r.h +``` + +The SHA-256 byte input is exactly: + +```text +"ww-package-storage-v2:" +|| ASCII( ":" ":") +|| complete semantic path +|| NUL +|| complete canonical directory +``` + +WW package paths and host paths cannot contain NUL, so that boundary is +unambiguous. Variant and role are present in both the digest input and the +visible locator tag. The digest is only an action-storage address: units still +begin with `//ww:module-reset `, exports begin with +`//ww:module `, compiler imports carry the complete path, and +qualified declarations use it in generated symbols. A selected executable +entry retains its intentional bare linker spelling. User-selected `-o` +publication paths bypass this derivation completely. + +Storage assignment is command-global and finishes before any tool is invoked. +If two actions prefer the same legacy basename, every unhashed member is +readdressed through the complete-action formula instead of rejecting a valid +package graph. If two already-addressed, unequal complete actions ever produce +the same locator, both drivers issue the same full-identity storage-collision +diagnostic before compilation. A persistent workdir also validates every +existing regular `.unit.ww` voucher against the requested complete semantic +owner before stale-tool invalidation or reuse; a missing voucher is cold state, +while a malformed, non-regular, or wrong-owner voucher is a pre-tool error. +Thus ordinary preferred-name collisions are resolved, and a digest collision +cannot silently alias two live or warm package actions. The workdir format is +now build version 10 and test version 11 so an older flat-layout voucher is +never accepted as current state. + +The persisted semantic owner is deliberately the complete canonical import +path, not the canonical host directory: host location must not enter compiler +artifacts. Variant and role are encoded in the locator itself. Canonical +directory remains part of command-local action interning and the digest input, +so distinct locations normally receive distinct slots. In the hypothetical +case that two locations with the same import path, variant, and role also +collide in SHA-256, they are still one semantic package identity: the freshly +composed owner unit must byte-equal the committed unit before reuse, so +different sources rebuild and identical sources produce the same deterministic +artifacts. A digest collision between different semantic paths fails the +existing complete-path owner check before tools. This preserves collision +checking without serializing machine-specific canonical directories into units +or exports and without a sidecar, registry, or new metadata protocol. + +The package coordinator similarly preserves the reversible existing +`d_` component while it fits. A longer +canonical request directory uses: + +```text +d_ +``` + +This key selects only the driver workdir container; it is never a package +identity or action address. Equivalent logical, relative, absolute, `.` and +explicit root-symlink requests canonicalize before this derivation. Inside the +container the driver independently derives every action locator from its full +semantic tuple and validates the committed semantic owner, so even a +coordinator-key collision cannot silently reuse an unrelated package action or +invoke a tool with a shortened identity. Explicit root symlinks are followed +and canonicalized; source-file symlinks and symlinked recursive children retain +their existing rejection/skipping rules. + +The old `SEP_IMPORT_PATH_MAX` and all corresponding 255-byte WWstage import, +variant, local-identity, and generated-main checks are removed. Package names, +canonical identities, reverse-resolved dotted identities, generated-main +identities, compiler import paths, and Cstage assembler symbols now use exact +allocations. Cstage's assembler no longer copies a line, operand, `TEXT`, or +`DATA` symbol through 256-byte arrays, and the C checker no longer resolves a +qualified type through a 128-byte prefix buffer. `PATH_MAX` remains only at +actual host pathname and syscall boundaries; the 255-byte constant remains +only as the conservative internal basename component bound. `SEP_MAXPKG`, +`SEP_MAXPRODUCT`, and `SEP_MAXCONTEXT` remain action-count limits, not byte +limits on semantic identity. + +The existing native observers exercise the new boundary with ordinary dotted +identities over 255 bytes and punctuation-heavy reversible local identities +over 255 bytes. They inspect the complete owner in units and exports, exact +direct sorted/deduplicated `.wwi` compiler inputs, long mangled assembler +symbols, complete archive-only link closures, runtime results, and independent +Cstage/WWstage artifact bytes. Two deep outside-root directories declaring +the same leaf coexist under distinct reversible identities. A long command +package and its production, internal, external, and two generated-main actions +remain distinct. Logical and literal roots, dependency-first and root-first +discovery, equivalent and symlink spellings, reordered products, and warm +requests reuse the same production action and persistent slot. The persistent +diamond observer also changes a shared dependency export, proves rebuilding of +its direct importers, and proves propagation stops when the regenerated export +is byte-identical. + +This separation follows the pinned official Go 1.26.5 tag at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`, without adopting Go's cache, +build IDs, importcfg, module machinery, or scheduler: + +- Source imports are expanded to canonical paths before being recorded + ([`pkg.go`, lines 658–669](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L658-L669), + [`pkg.go`, lines 1150–1178](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1150-L1178)). + Local directories receive a deterministic pseudo-import identity derived + from the full directory while directory and import path stay separate + ([`pkg.go`, lines 633–647](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L633-L647), + [`pkg.go`, lines 863–907](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L863-L907)). + Resolution/package caches retain and reuse those complete values + ([`pkg.go`, lines 833–842](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L833-L842), + [`pkg.go`, lines 909–985](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L909-L985), + [`pkg.go`, lines 1008–1029](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1008-L1029)). +- `go/build` reverse-resolves directory ownership through ordered roots and + performs the matching forward lookup while keeping directory, import path, + declared name, and source lists separate + ([`build.go`, lines 612–665](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L612-L665), + [`build.go`, lines 725–767](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L725-L767), + [`build.go`, lines 436–493](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L436-L493)). +- Go interns an action by operation and package pointer, returns an existing + action for repeated compilation requests, and attaches only direct package + dependencies + ([`action.go`, lines 202–206](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L202-L206), + [`action.go`, lines 437–447](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L437-L447), + [`action.go`, lines 628–658](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L628-L658)). +- Its `Action` keeps `Package`, `Objdir`, and `Target` as independent fields, + then assigns a short `bNNN/` object directory unrelated to import identity + ([`action.go`, lines 84–109](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L84-L109), + [`action.go`, lines 383–394](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L383-L394)). + Direct dependency identities map independently to archive paths, and the + owner is compiled to fixed `_pkg_.a` + ([`exec.go`, lines 864–884](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L864-L884), + [`exec.go`, lines 928–935](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L928-L935), + [`exec.go`, lines 1017–1033](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1017-L1033)). +- Linking reuses the compiled root action and separately expands the complete + reachable archive closure + ([`action.go`, lines 919–957](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L919-L957), + [`action.go`, lines 1034–1068](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L1034-L1068), + [`exec.go`, lines 1592–1624](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1592-L1624), + [`exec.go`, lines 1635–1647](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1635-L1647)). + User `-o` separately controls the publication target + ([`build.go`, lines 508–548](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L508-L548)). +- Go's test loader models ordinary production, internal production-plus-test, + external `_test`, and generated-main packages separately, reusing ordinary + production when possible + ([`test.go`, lines 85–102](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L85-L102), + [`test.go`, lines 175–226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L175-L226), + [`test.go`, lines 228–293](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L228-L293)). + ## 12. Candidate architectures and hard-gate decision Five candidates were developed as coherent systems, not as feature bins. diff --git a/test/byteid/wwi_test.ww b/test/byteid/wwi_test.ww index 09b590c1..35f82abc 100644 --- a/test/byteid/wwi_test.ww +++ b/test/byteid/wwi_test.ww @@ -26,7 +26,8 @@ package wwi_test; // decl-less shapes routing the wwi_emit fallback (empty, comment-only, // nested dotted path a.b.c -> leaf c). Each row drives the REAL // producer->importer flow on BOTH stages: (a) the dep `.wwi` package -// line equals the real leaf, (b) the dep `.wwi` is byte-identical +// owner marker is the complete path and its package line equals the real leaf, +// (b) the dep `.wwi` is byte-identical // across stages, (c) a root importing the dep RESOLVES on both stages // and the two importer `.s` are byte-identical. The owner units and // separate exports are fed straight to w6c / w6c_ww (not `ww build`): @@ -55,13 +56,6 @@ fn runok(dir: str, name: str, argv: []str) bool = { return co.termination == exec.termination.EXIT && co.code == 0; }; -fn firstline(path: str) str = { - let body: str = testenv.readfile(path); - let nl: i32 = testenv.pos(body, "\n"); - if (nl < 0) { return body; }; - return strings.sub(body, 0, nl); -}; - // one positive package: build the target as primary, produce the `.wwi` // on both stages, require byte-id + re-parse. fn m2positive(pkg: str) void = { @@ -345,9 +339,10 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", wwwwi, "-o", wws, prod]; if (!runok(td, "wsprod", wav)) { fail(tag, "w6c_ww producer errored"); }; - let wantline: str = strings.concat("package ", want, ";"); - if (!testenv.same(firstline(cswwi), wantline)) { - fail(tag, ".wwi package line is not the real leaf (BUG-C)"); + let wanthead: str = strings.concat(strings.concat(strings.concat( + "//ww:module ", path), "\npackage "), strings.concat(want, ";\n")); + if (!strings.hasprefix(testenv.readfile(cswwi), wanthead)) { + fail(tag, ".wwi owner or package leaf is incomplete (BUG-C)"); }; // Leg b — the dep `.wwi` is byte-identical across stages (rule 10). diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 7683a6a8..20f9a9e3 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -3,6 +3,8 @@ package package_test; // Make builds and invokes this one binary; discovery, subprocess ownership, // output interpretation, and assertions remain WW code. +import crypto.sha256; +import hash; import os; import os.exec; import strings; @@ -287,6 +289,98 @@ fn localidentity(dir: str, leaf: str) str = { return strings.frombytes(out); }; +fn digesthex(h: *hash.hash) str = { + let digest: [32]u8; + hash.sum(h, digest[0:32]); + let out: []u8 = alloc([], 65u64)!; + let digits: str = "0123456789abcdef"; + let i: i32 = 0; + for (i < 32) { + let high: i32 = (digest[i] / 16u8): i32; + let low: i32 = (digest[i] % 16u8): i32; + append(out, digits[high]); + append(out, digits[low]); + i += 1; + }; + return strings.frombytes(out); +}; + +fn packagestoragekey(path: str, canon: str, variant: i32, role: i32) str = { + let state: sha256.state = sha256.sha256(); + let h: *hash.hash = (&state): *hash.hash; + hash.write(h, strings.toutf8("ww-package-storage-v2:")); + let tag: [4]u8; + tag[0] = ('0': i32 + variant): u8; + tag[1] = ':': u8; + tag[2] = ('0': i32 + role): u8; + tag[3] = ':': u8; + hash.write(h, tag[0:4]); + hash.write(h, strings.toutf8(path)); + let zero: [1]u8; + zero[0] = 0u8; + hash.write(h, zero[0:1]); + hash.write(h, strings.toutf8(canon)); + let out: []u8 = alloc([], 81u64)!; + let prefix: str = "__wwpkg.v"; + let i: i32 = 0; + for (i < prefix.len) { append(out, prefix[i]); i += 1; }; + append(out, tag[0]); append(out, '.'); append(out, 'r'); + append(out, tag[2]); append(out, '.'); append(out, 'h'); + let hex: str = digesthex(h); + i = 0; + for (i < hex.len) { append(out, hex[i]); i += 1; }; + return strings.frombytes(out); +}; + +fn requestworkkey(dir: str) str = { + let state: sha256.state = sha256.sha256(); + let h: *hash.hash = (&state): *hash.hash; + hash.write(h, strings.toutf8("ww-request-workdir-v1:")); + hash.write(h, strings.toutf8(dir)); + return strings.concat("d_", digesthex(h)); +}; + +fn importrelative(path: str) str = { + let out: []u8 = alloc([], (path.len + 1): u64)!; + let i: i32 = 0; + for (i < path.len) { + if (path[i] == '.') { append(out, '/'); } + else { append(out, path[i]); }; + i += 1; + }; + return strings.frombytes(out); +}; + +fn longidentity(leaf: str) str = { + let out: []u8 = alloc([], 384u64)!; + let component: str = "segmentabcdefghijklmnop"; + let n: i32 = 0; + for (n < 13) { + let i: i32 = 0; + for (i < component.len) { append(out, component[i]); i += 1; }; + append(out, '.'); + n += 1; + }; + let i: i32 = 0; + for (i < leaf.len) { append(out, leaf[i]); i += 1; }; + return strings.frombytes(out); +}; + +fn deeppunctuationpath(root: str, branch: str) str = { + let out: str = strings.concat(root, "/outside +% @ _/", branch); + let component: str = "/punctuation + percent% at@ under_score"; + let n: i32 = 0; + for (n < 9) { out = strings.concat(out, component); n += 1; }; + return out; +}; + +fn mkdirall(path: str) void = { + match (os.mkdirs(path, 448i32)) { + case void => void; + case let e: os.oserror => abort("mkdirs failed"); + }; +}; + @test fn deterministic_routing_and_filters() void = { let root: str = fresh(); let target: str = packagepath("routing"); @@ -515,22 +609,97 @@ fn localidentity(dir: str, leaf: str) str = { clean(root); }; -@test fn symlink_package_is_rejected() void = { +@test fn symlink_package_root_reuses_canonical_directory() void = { let root: str = fresh(); - let real: str = strings.concat(root, "/real"); + let real: str = deeppunctuationpath(root, "symlink target"); let link: str = strings.concat(root, "/link"); - assert(os.mkdir(real, 448i32) == 0); + let wrapper: str = strings.concat(root, "/compiler wrapper.sh"); + mkdirall(real); writefile(strings.concat(real, "/real.ww"), "package real;\nexport fn value() int = { return 1; };\n"); + writefile(strings.concat(real, "/real_test.ww"), + "package real;\n@test fn value_is_one() void = { assert(value() == 1); };\n"); let lav: []str = ["/bin/ln", "-s", real, link]; let out: commandout; runcommand(root, "link", lav, time.second, &out); expectexit(&out, 0); - let av: []str = [driver("ww"), "test", link]; - runcommand(root, "reject", av, - (30i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "symlink traversal is not allowed")); + writeexecutable(wrapper, strings.concat( + "#!/bin/sh\n", + "printf 'BEGIN' >> \"$WW_SYMLINK_COMPILER_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_SYMLINK_COMPILER_TRACE\"; done\n", + "printf '\\n' >> \"$WW_SYMLINK_COMPILER_TRACE\"\n", + "exec \"$WW_SYMLINK_REAL_COMPILER\" \"$@\"\n")); + let actionid: str = localidentity(real, "real"); + assert(actionid.len > 255); + let action: str = packagestoragekey(actionid, real, 1, 0); + assert(action.len + 9 <= 255); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let references: []str = ["", "", ""]; + let baseenv: []str = os.getenvs(); + let si: i32 = 0; + for (si < stages.len) { + let work: str = strings.concat(root, "/work-", stages[si]); + let trace: str = strings.concat(root, "/trace-", stages[si]); + assert(os.mkdir(work, 448i32) == 0); + writefile(trace, ""); + let env: []str = alloc([], (baseenv.len + 3): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], + "WW_SYMLINK_COMPILER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_SYMLINK_REAL_COMPILER=")) { + append(env, baseenv[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=", wrapper)); + append(env, strings.concat("WW_SYMLINK_COMPILER_TRACE=", trace)); + append(env, strings.concat("WW_SYMLINK_REAL_COMPILER=", + driver(compilers[si]))); + let av: []str = [driver(stages[si]), "test", "-w", work, real]; + runcommandenv(root, strings.concat("real-cold-", stages[si]), av, env, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "real.value_is_one ... ok\n")); + let coldtrace: str = readfile(trace); + assert(coldtrace.len > 0); + assert(has(coldtrace, strings.concat("<--import><", actionid, "><"))); + let key: str = strings.concat(work, "/", requestworkkey(real)); + assert(os.exists(key)); + let suffixes: []str = [".unit.ww", ".wwi", ".a"]; + let xi: i32 = 0; + for (xi < suffixes.len) { + let artifact: str = readfile(strings.concat(key, "/", action, + suffixes[xi])); + if (xi == 0) { + assert(has(artifact, strings.concat("//ww:module-reset ", + actionid, "\n"))); + } else { if (xi == 1) { + assert(strings.hasprefix(artifact, strings.concat("//ww:module ", + actionid, "\n"))); + }; }; + if (si == 0) { references[xi] = strings.dup(artifact); } + else { assert(same(references[xi], artifact)); }; + xi += 1; + }; + let linkav: []str = [driver(stages[si]), "test", "-w", work, link]; + runcommandenv(root, strings.concat("symlink-warm-", stages[si]), + linkav, env, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "real.value_is_one ... ok\n")); + assert(same(coldtrace, readfile(trace))); + xi = 0; + for (xi < suffixes.len) { + assert(same(references[xi], readfile(strings.concat(key, "/", + action, suffixes[xi])))); + xi += 1; + }; + si += 1; + }; clean(root); }; @@ -2606,44 +2775,48 @@ fn localidentity(dir: str, leaf: str) str = { let root: str = fresh(); let source: str = strings.concat(root, "/source tree"); let runtime: str = strings.concat(root, "/runtime library"); - let base: str = strings.concat(source, "/ww_root_parity_base_7f3"); - let left: str = strings.concat(source, "/ww_root_parity_left_7f3"); - let right: str = strings.concat(source, "/ww_root_parity_right_7f3"); - let rootidentity: str = "ww_root_parity_target_7f3"; - let target: str = strings.concat(source, "/", rootidentity); + let baseidentity: str = longidentity("paritybase"); + let leftidentity: str = longidentity("parityleft"); + let rightidentity: str = longidentity("parityright"); + let rootidentity: str = longidentity("paritycommand"); + assert(baseidentity.len > 255 && rootidentity.len > 255); + let base: str = strings.concat(source, "/", importrelative(baseidentity)); + let left: str = strings.concat(source, "/", importrelative(leftidentity)); + let right: str = strings.concat(source, "/", importrelative(rightidentity)); + let target: str = strings.concat(source, "/", importrelative(rootidentity)); let tools: str = strings.concat(root, "/tool wrappers"); assert(os.mkdir(source, 448i32) == 0); assert(os.mkdir(runtime, 448i32) == 0); writefile(strings.concat(runtime, "/libwwrt.a"), readfile(strings.concat(repo(), "/out/lib/libwwrt.a"))); - assert(os.mkdir(base, 448i32) == 0); - assert(os.mkdir(left, 448i32) == 0); - assert(os.mkdir(right, 448i32) == 0); - assert(os.mkdir(target, 448i32) == 0); + mkdirall(base); + mkdirall(left); + mkdirall(right); + mkdirall(target); assert(os.mkdir(tools, 448i32) == 0); let basea: str = strings.concat( - "package ww_root_parity_base_7f3;\n", + "package paritybase;\n", "export fn value() i32 = { return 20; };\n"); let basez: str = strings.concat( - "package ww_root_parity_base_7f3;\n", + "package paritybase;\n", "fn private_value() i32 = { return 99; };\n"); let leftbody: str = strings.concat( - "package ww_root_parity_left_7f3;\n", - "import ww_root_parity_base_7f3;\n", - "import ww_root_parity_base_7f3;\n", + "package parityleft;\n", + "import ", baseidentity, ";\n", + "import ", baseidentity, ";\n", "export fn value() i32 = { return ", - "ww_root_parity_base_7f3.value(); };\n"); + "paritybase.value(); };\n"); let rightbody: str = strings.concat( - "package ww_root_parity_right_7f3;\n", - "import ww_root_parity_base_7f3;\n", + "package parityright;\n", + "import ", baseidentity, ";\n", "export fn value() i32 = { return ", - "ww_root_parity_base_7f3.value() + 1; };\n"); + "paritybase.value() + 1; };\n"); let rootbody: str = strings.concat( "package main;\n", - "import ww_root_parity_right_7f3;\n", - "import ww_root_parity_left_7f3;\n", - "fn main() i32 = { return ww_root_parity_left_7f3.value() + ", - "ww_root_parity_right_7f3.value() + 1; };\n"); + "import ", rightidentity, ";\n", + "import ", leftidentity, ";\n", + "fn main() i32 = { return parityleft.value() + ", + "parityright.value() + 1; };\n"); writefile(strings.concat(base, "/a.ww"), basea); writefile(strings.concat(base, "/z.ww"), basez); writefile(strings.concat(left, "/left.ww"), leftbody); @@ -2703,18 +2876,25 @@ fn localidentity(dir: str, leaf: str) str = { let bin: str = strings.concat(root, "/published binary"); let workroot: str = strings.concat(bin, ".sepwork"); let work: str = strings.concat(workroot, "/"); - let artifacts: []str = ["ww_root_parity_base_7f3", - "ww_root_parity_left_7f3", "ww_root_parity_right_7f3", - rootidentity]; - let unitartifacts: []str = ["ww_root_parity_base_7f3", - "ww_root_parity_left_7f3", "ww_root_parity_right_7f3", + let identities: []str = [baseidentity, leftidentity, rightidentity, rootidentity]; + let artifacts: []str = [packagestoragekey(baseidentity, base, 0, 0), + packagestoragekey(leftidentity, left, 0, 0), + packagestoragekey(rightidentity, right, 0, 0), + packagestoragekey(rootidentity, target, 0, 0)]; + let unitartifacts: []str = artifacts; + let aki: i32 = 0; + for (aki < artifacts.len) { + assert(strings.hasprefix(artifacts[aki], "__wwpkg.v0.r0.h")); + assert(artifacts[aki].len <= 246); + aki += 1; + }; let wantunits: []str = [strings.concat( - "//ww:module-reset ww_root_parity_base_7f3\n", basea, - "\n//ww:module-reset ww_root_parity_base_7f3\n", basez, "\n"), - strings.concat("//ww:module-reset ww_root_parity_left_7f3\n", + "//ww:module-reset ", baseidentity, "\n", basea, + "\n//ww:module-reset ", baseidentity, "\n", basez, "\n"), + strings.concat("//ww:module-reset ", leftidentity, "\n", leftbody, "\n"), - strings.concat("//ww:module-reset ww_root_parity_right_7f3\n", + strings.concat("//ww:module-reset ", rightidentity, "\n", rightbody, "\n"), strings.concat("//ww:module-reset ", rootidentity, "\n", rootbody, "\n")]; @@ -2728,6 +2908,7 @@ fn localidentity(dir: str, leaf: str) str = { let referenceout: str = ""; let referenceerr: str = ""; let referencefailure: str = ""; + let referencecollision: str = ""; let baseenv: []str = os.getenvs(); let si: i32 = 0; for (si < stages.len) { @@ -2788,8 +2969,10 @@ fn localidentity(dir: str, leaf: str) str = { append(env, strings.concat("WW_ARGV_REAL_LINKER=", driver(linkers[si]))); + let request: str = target; + if (si == 1 || si == 3) { request = rootidentity; }; let av: []str = [driver(stages[si]), "build", "-o", bin, - target]; + request]; let out: commandout; runcommandenv(root, strings.concat("exact-argv-", tags[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); @@ -2809,12 +2992,19 @@ fn localidentity(dir: str, leaf: str) str = { for (ai < artifacts.len) { let exportf: str = readfile(strings.concat(work, artifacts[ai], ".wwi")); + let assembly: str = readfile(strings.concat(work, artifacts[ai], + ".s")); let archive: str = readfile(strings.concat(work, artifacts[ai], ".a")); + assert(strings.hasprefix(exportf, strings.concat("//ww:module ", + identities[ai], "\n"))); + if (ai < 3) { + assert(has(assembly, strings.concat(identities[ai], ".value"))); + }; if (ai == 3) { - assert(strings.hasprefix(exportf, strings.concat("package ", - rootidentity, ";\n"))); - assert(!strings.hasprefix(exportf, "package main;\n")); + assert(has(exportf, strings.concat("\npackage paritycommand", + ";\n"))); + assert(!has(exportf, "\npackage main;\n")); }; if (si == 0) { referenceexports[ai] = strings.dup(exportf); @@ -2832,48 +3022,62 @@ fn localidentity(dir: str, leaf: str) str = { let ltrace: str = readfile(linkertrace); assert(occurrences(ctrace, "\n") == 4); let baseline: str = strings.concat("BEGIN<-c><-I><", work, - "ww_root_parity_base_7f3.wwi><-o><", work, - "ww_root_parity_base_7f3.s><", work, - "ww_root_parity_base_7f3.unit.ww>"); + artifacts[0], ".wwi><-o><", work, + artifacts[0], ".s><", work, + artifacts[0], ".unit.ww>"); let leftline: str = strings.concat("BEGIN<-c><--import>", - "<", work, - "ww_root_parity_base_7f3.wwi><-I><", work, - "ww_root_parity_left_7f3.wwi><-o><", work, - "ww_root_parity_left_7f3.s><", work, - "ww_root_parity_left_7f3.unit.ww>"); + "<", baseidentity, "><", work, + artifacts[0], ".wwi><-I><", work, + artifacts[1], ".wwi><-o><", work, + artifacts[1], ".s><", work, + artifacts[1], ".unit.ww>"); let rightline: str = strings.concat("BEGIN<-c><--import>", - "<", work, - "ww_root_parity_base_7f3.wwi><-I><", work, - "ww_root_parity_right_7f3.wwi><-o><", work, - "ww_root_parity_right_7f3.s><", work, - "ww_root_parity_right_7f3.unit.ww>"); + "<", baseidentity, "><", work, + artifacts[0], ".wwi><-I><", work, + artifacts[2], ".wwi><-o><", work, + artifacts[2], ".s><", work, + artifacts[2], ".unit.ww>"); let rootline: str = strings.concat("BEGIN<--entry><-c><--import>", - "<", work, - "ww_root_parity_left_7f3.wwi><--import>", - "<", work, - "ww_root_parity_right_7f3.wwi><-I><", work, - rootidentity, ".wwi><-o><", work, - rootidentity, ".s><", work, rootidentity, ".unit.ww>"); + "<", leftidentity, "><", work, + artifacts[1], ".wwi><--import>", + "<", rightidentity, "><", work, + artifacts[2], ".wwi><-I><", work, + artifacts[3], ".wwi><-o><", work, + artifacts[3], ".s><", work, artifacts[3], ".unit.ww>"); assert(same(linecontaining(ctrace, - "ww_root_parity_base_7f3.unit.ww"), baseline)); + strings.concat(artifacts[0], ".unit.ww")), baseline)); assert(same(linecontaining(ctrace, - "ww_root_parity_left_7f3.unit.ww"), leftline)); + strings.concat(artifacts[1], ".unit.ww")), leftline)); assert(same(linecontaining(ctrace, - "ww_root_parity_right_7f3.unit.ww"), rightline)); - assert(same(linecontaining(ctrace, strings.concat(rootidentity, + strings.concat(artifacts[2], ".unit.ww")), rightline)); + assert(same(linecontaining(ctrace, strings.concat(artifacts[3], ".unit.ww")), rootline)); - assert(!has(rootline, "ww_root_parity_base_7f3.wwi")); + assert(!has(rootline, strings.concat(artifacts[0], ".wwi"))); // The primary command declaration is accepted only when the compiler // receives command classification from --entry. Imported interfaces and // ordinary package compiles retain strict identity-leaf validation. let rejectwwi: str = strings.concat(root, "/reject-", tags[si], ".wwi"); let rejectasm: str = strings.concat(root, "/reject-", tags[si], ".s"); let rejectav: []str = [driver(compilers[si]), "-c", "-I", rejectwwi, - "-o", rejectasm, strings.concat(work, rootidentity, ".unit.ww")]; + "-o", rejectasm, strings.concat(work, artifacts[3], ".unit.ww")]; runcommand(root, strings.concat("command-without-entry-", tags[si]), rejectav, (60i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); assert(has(out.stderr, "does not match import path")); + assert(has(out.stderr, rootidentity)); + let ownerrejectwwi: str = strings.concat(root, "/owner-reject-", + tags[si], ".wwi"); + let ownerrejectasm: str = strings.concat(root, "/owner-reject-", + tags[si], ".s"); + let ownerrejectav: []str = [driver(compilers[si]), "-c", "--import", + leftidentity, strings.concat(work, artifacts[0], ".wwi"), "-I", + ownerrejectwwi, "-o", ownerrejectasm, + strings.concat(work, artifacts[1], ".unit.ww")]; + runcommand(root, strings.concat("export-owner-reject-", tags[si]), + ownerrejectav, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stderr, strings.concat("w6c: import ", leftidentity, + ": export owner mismatch in ", work, artifacts[0], ".wwi\n"))); assert(occurrences(atrace, "\n") == 4); ai = 0; for (ai < artifacts.len) { @@ -2885,7 +3089,7 @@ fn localidentity(dir: str, leaf: str) str = { }; assert(occurrences(ltrace, "\n") == 1); assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work, - rootidentity, ".a>"))); + artifacts[3], ".a>"))); ai = 0; for (ai < artifacts.len) { assert(occurrences(ltrace, strings.concat("<", work, artifacts[ai], @@ -2917,6 +3121,127 @@ fn localidentity(dir: str, leaf: str) str = { assert(same(referenceout, out.stdout)); assert(same(referenceerr, out.stderr)); }; + + // Select the same long ordinary directory as both an explicit product + // root and the dependency of another product in one command. Then build + // it alone through logical and literal spellings. All routes must intern + // one production action and emit the dependency-built bytes above. + let combinedbase: str = strings.concat(root, "/combined-base-", tags[si]); + let combinedleft: str = strings.concat(root, "/combined-left-", tags[si]); + let combinedbasestatus: str = strings.concat(combinedbase, ".status"); + let combinedleftstatus: str = strings.concat(combinedleft, ".status"); + clean(compilertrace); writefile(compilertrace, ""); + clean(assemblertrace); writefile(assemblertrace, ""); + clean(linkertrace); writefile(linkertrace, ""); + let combinedav: []str = [driver(stages[si]), "test", "-c", "-I", + source, + "--ww-package-test", "production", "paritybase", base, + combinedbase, combinedbasestatus, + "--ww-package-test", "production", "parityleft", left, + combinedleft, combinedleftstatus, left]; + runcommandenv(root, strings.concat("long-combined-", tags[si]), + combinedav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let combinedtrace: str = readfile(compilertrace); + assert(occurrences(combinedtrace, strings.concat(artifacts[0], + ".unit.ww>")) == 1); + let combinedwork: str = strings.concat(combinedbase, ".sepwork/"); + let combinedunit: str = readfile(strings.concat(combinedwork, + artifacts[0], ".unit.ww")); + let combinedwwi: str = readfile(strings.concat(combinedwork, + artifacts[0], ".wwi")); + let combinedarchive: str = readfile(strings.concat(combinedwork, + artifacts[0], ".a")); + assert(same(referenceunits[0], combinedunit)); + assert(same(referenceexports[0], combinedwwi)); + assert(same(referencearchives[0], combinedarchive)); + + let rootspellings: []str = [baseidentity, base]; + let rootlabels: []str = ["logical", "literal"]; + let ri: i32 = 0; + for (ri < rootspellings.len) { + let rootout: str = strings.concat(root, "/long-root-", + rootlabels[ri], "-", tags[si], ".a"); + clean(compilertrace); writefile(compilertrace, ""); + let rootav: []str = [driver(stages[si]), "build", "-p", "-I", + source, "-o", rootout, rootspellings[ri]]; + runcommandenv(root, strings.concat("long-root-", rootlabels[ri], + "-", tags[si]), rootav, env, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(occurrences(readfile(compilertrace), strings.concat( + artifacts[0], ".unit.ww>")) == 1); + let rootwork: str = strings.concat(rootout, ".sepwork/"); + assert(same(combinedunit, readfile(strings.concat(rootwork, + artifacts[0], ".unit.ww")))); + assert(same(combinedwwi, readfile(strings.concat(rootwork, + artifacts[0], ".wwi")))); + assert(same(combinedarchive, readfile(strings.concat(rootwork, + artifacts[0], ".a")))); + clean(strings.concat(rootout, ".sepwork")); + clean(rootout); clean(strings.concat(rootout, ".wwi")); + ri += 1; + }; + clean(strings.concat(combinedbase, ".sepwork")); + clean(combinedbase); clean(combinedleft); + clean(combinedbasestatus); clean(combinedleftstatus); + let seedav: []str = [driver(stages[si]), "build", "-w", workroot, + "-o", bin, request]; + runcommandenv(root, strings.concat("persistent-seed-", tags[si]), + seedav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(os.remove(compilertrace) == 0); + assert(os.remove(assemblertrace) == 0); + writefile(compilertrace, ""); + writefile(assemblertrace, ""); + let equivalent: str = rootidentity; + if (same(request, rootidentity)) { equivalent = target; }; + let warmav: []str = [driver(stages[si]), "build", "-w", workroot, + "-o", bin, equivalent]; + runcommandenv(root, strings.concat("persistent-equivalent-", tags[si]), + warmav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + let baseafile: str = strings.concat(base, "/a.ww"); + assert(os.remove(baseafile) == 0); + writefile(baseafile, strings.concat(basea, + "export fn marker() i32 = { return 7; };\n")); + runcommandenv(root, strings.concat("persistent-export-", tags[si]), + warmav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let changedtrace: str = readfile(compilertrace); + assert(occurrences(changedtrace, "\n") == 3); + assert(has(changedtrace, strings.concat(artifacts[0], ".unit.new"))); + assert(has(changedtrace, strings.concat(artifacts[1], ".unit.new"))); + assert(has(changedtrace, strings.concat(artifacts[2], ".unit.new"))); + assert(!has(changedtrace, strings.concat(artifacts[3], ".unit.new"))); + let badunit: str = strings.concat(work, artifacts[0], ".unit.ww"); + assert(os.remove(badunit) == 0); + writefile(badunit, "//ww:module-reset wrong.owner\n"); + assert(os.remove(compilertrace) == 0); + assert(os.remove(assemblertrace) == 0); + assert(os.remove(linkertrace) == 0); + writefile(compilertrace, ""); + writefile(assemblertrace, ""); + writefile(linkertrace, ""); + let collisionav: []str = [driver(stages[si]), "build", "-w", + workroot, "-o", bin, request]; + runcommandenv(root, strings.concat("storage-owner-", tags[si]), + collisionav, env, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let collisiondiag: str = strings.concat( + "ww: package storage owner mismatch at ", artifacts[0], + " for ", baseidentity, "\n"); + assert(same(out.stderr, collisiondiag)); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(readfile(linkertrace).len == 0); + if (si == 0) { referencecollision = strings.dup(out.stderr); } + else { assert(same(referencecollision, out.stderr)); }; + assert(os.remove(baseafile) == 0); + writefile(baseafile, basea); clean(workroot); clean(bin); @@ -2933,20 +3258,20 @@ fn localidentity(dir: str, leaf: str) str = { append(failenv, strings.concat("WW_W6C=", failurewrapper)); append(failenv, strings.concat("WW_ARGV_FAILURE_TRACE=", failuretrace)); append(failenv, strings.concat("WW_ARGV_MISSING_EXPORT=", work, - "ww_root_parity_base_7f3.wwi")); + artifacts[0], ".wwi")); append(failenv, strings.concat("WW_ARGV_MISSING_OWNER=", work, - "ww_root_parity_left_7f3.unit.ww")); + artifacts[1], ".unit.ww")); runcommandenv(root, strings.concat("exact-argv-fail-", tags[si]), av, failenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); let wantfailure: str = strings.concat( - "w6c: import ww_root_parity_base_7f3: cannot read ", work, - "ww_root_parity_base_7f3.wwi\n", - "ww: w6c failed for ww_root_parity_left_7f3\n"); + "w6c: import ", baseidentity, ": cannot read ", work, + artifacts[0], ".wwi\n", + "ww: w6c failed for ", leftidentity, "\n"); assert(same(out.stderr, wantfailure)); assert(has(readfile(failuretrace), strings.concat( - "<--import><", work, - "ww_root_parity_base_7f3.wwi>"))); + "<--import><", baseidentity, "><", work, + artifacts[0], ".wwi>"))); if (si == 0) { referencefailure = strings.dup(out.stderr); } else { assert(same(referencefailure, out.stderr)); }; clean(workroot); @@ -2959,11 +3284,13 @@ fn localidentity(dir: str, leaf: str) str = { @test fn command_package_test_variants_keep_canonical_identity() void = { let root: str = fresh(); let source: str = strings.concat(root, "/source"); - let target: str = strings.concat(source, "/cmdtool"); + let commandid: str = longidentity("cmdtool"); + assert(commandid.len > 255); + let target: str = strings.concat(source, "/", importrelative(commandid)); let importer: str = strings.concat(source, "/importer"); let wrapper: str = strings.concat(root, "/command-w6c.sh"); assert(os.mkdir(source, 448i32) == 0); - assert(os.mkdir(target, 448i32) == 0); + mkdirall(target); assert(os.mkdir(importer, 448i32) == 0); writefile(strings.concat(target, "/main.ww"), strings.concat( "package main;\n", @@ -2974,12 +3301,12 @@ fn localidentity(dir: str, leaf: str) str = { "@test fn command_internal() void = { assert(value() == 41); };\n")); writefile(strings.concat(target, "/external_test.ww"), strings.concat( "package main_test;\n", - "import cmdtool;\n", + "import ", commandid, ";\n", "@test fn command_external() void = { ", "assert(cmdtool.value() == 41); };\n")); writefile(strings.concat(importer, "/importer.ww"), strings.concat( "package importer;\n", - "import cmdtool;\n", + "import ", commandid, ";\n", "export fn value() i32 = { return cmdtool.value(); };\n")); writeexecutable(wrapper, strings.concat( "#!/bin/sh\n", @@ -2991,17 +3318,33 @@ fn localidentity(dir: str, leaf: str) str = { let stages: []str = ["ww", "ww_ww"]; let compilers: []str = ["w6c", "w6c_ww"]; - let artifacts: []str = ["cmdtool", "cmdtool-internal-test", - "cmdtool_test-external-test"]; + let internalid: str = commandid; + let externalid: str = strings.concat(commandid, "_test"); + let internalmainid: str = strings.concat("__wwtestmain.", commandid, + ".internal.main"); + let externalmainid: str = strings.concat("__wwtestmain.", externalid, + ".external.main"); + let actionids: []str = [commandid, internalid, externalid, + internalmainid, externalmainid]; + let artifacts: []str = [packagestoragekey(commandid, target, 0, 0), + packagestoragekey(internalid, target, 1, 0), + packagestoragekey(externalid, target, 2, 0), + packagestoragekey(internalmainid, + strings.concat(target, "#internal-test-main"), 3, 2), + packagestoragekey(externalmainid, + strings.concat(target, "#external-test-main"), 3, 2)]; let suffixes: []str = [".unit.ww", ".wwi", ".a"]; - let reference: []str = ["", "", "", "", "", "", "", "", ""]; + let reference: []str = ["", "", "", "", "", "", "", "", "", + "", "", "", "", "", ""]; let samebin: str = strings.concat(target, "/main.test"); let externalbin: str = strings.concat(target, "/main_test.test"); - let workroot: str = strings.concat(samebin, ".sepwork"); - let work: str = strings.concat(workroot, "/"); let baseenv: []str = os.getenvs(); let si: i32 = 0; for (si < stages.len) { + let workroot: str = strings.concat(root, "/command-work-", stages[si]); + assert(os.mkdir(workroot, 448i32) == 0); + let work: str = strings.concat(workroot, "/", requestworkkey(target), + "/"); let trace: str = strings.concat(root, "/", stages[si], ".trace"); writefile(trace, ""); let env: []str = alloc([], (baseenv.len + 3): u64)!; @@ -3020,24 +3363,34 @@ fn localidentity(dir: str, leaf: str) str = { append(env, strings.concat("WW_COMMAND_COMPILER_TRACE=", trace)); append(env, strings.concat("WW_COMMAND_REAL_COMPILER=", driver(compilers[si]))); - let av: []str = [driver(stages[si]), "test", "-c", "-I", source, - target]; + let av: []str = [driver(stages[si]), "test", "-w", workroot, + "-I", source, target]; let out: commandout; runcommandenv(root, strings.concat("command-variants-", stages[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(out.stderr.len == 0); + assert(has(out.stdout, "main.command_internal ... ok\n")); + assert(has(out.stdout, "main_test.command_external ... ok\n")); let ctrace: str = readfile(trace); let ai: i32 = 0; for (ai < artifacts.len) { let line: str = linecontaining(ctrace, - strings.concat(artifacts[ai], ".unit.ww")); - assert(has(line, "<--command-package>")); + strings.concat(artifacts[ai], ".unit.new")); + if (ai < 3) { assert(has(line, "<--command-package>")); } + else { assert(has(line, "<-T><--entry>")); }; let sj: i32 = 0; for (sj < suffixes.len) { let body: str = readfile(strings.concat(work, artifacts[ai], suffixes[sj])); + if (sj == 0) { + assert(has(body, strings.concat("//ww:module-reset ", + actionids[ai], "\n"))); + } else { if (sj == 1) { + assert(strings.hasprefix(body, strings.concat("//ww:module ", + actionids[ai], "\n"))); + }; }; let ri: i32 = ai * suffixes.len + sj; if (si == 0) { reference[ri] = strings.dup(body); } else { assert(same(reference[ri], body)); }; @@ -3045,25 +3398,50 @@ fn localidentity(dir: str, leaf: str) str = { }; ai += 1; }; - assert(has(readfile(strings.concat(work, "cmdtool.unit.ww")), - "//ww:module-reset cmdtool\n")); - assert(has(readfile(strings.concat(work, - "cmdtool-internal-test.unit.ww")), - "//ww:module-reset cmdtool\n")); - assert(has(readfile(strings.concat(work, - "cmdtool_test-external-test.unit.ww")), - "//ww:module-reset cmdtool_test\n")); - - let runav: []str = [samebin, "-package=main"]; - runcommand(root, strings.concat("command-internal-run-", stages[si]), - runav, (60i64 * (time.second: i64)): time.duration, &out); + let externalline: str = linecontaining(ctrace, + strings.concat(artifacts[2], ".unit.new")); + assert(has(externalline, strings.concat("<--import><", commandid, + "><", work, artifacts[0], ".wwi>"))); + let internalmainline: str = linecontaining(ctrace, + strings.concat(artifacts[3], ".unit.new")); + let externalmainline: str = linecontaining(ctrace, + strings.concat(artifacts[4], ".unit.new")); + assert(has(internalmainline, strings.concat("<--import><", internalid, + "><", work, artifacts[1], ".wwi>"))); + assert(has(externalmainline, strings.concat("<--import><", externalid, + "><", work, artifacts[2], ".wwi>"))); + assert(occurrences(ctrace, strings.concat("<", work, artifacts[0], + ".unit.new>")) == 1); + assert(has(readfile(strings.concat(work, artifacts[0], ".s")), + strings.concat(commandid, ".value"))); + let warmrefs: []str = alloc([], reference.len: u64)!; + warmrefs.len = reference.len; + ai = 0; + for (ai < artifacts.len) { + let sj: i32 = 0; + for (sj < suffixes.len) { + warmrefs[ai * suffixes.len + sj] = strings.dup(readfile( + strings.concat(work, artifacts[ai], suffixes[sj]))); + sj += 1; + }; + ai += 1; + }; + runcommandenv(root, strings.concat("command-warm-", stages[si]), av, + env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(has(out.stdout, "command_internal ... ok\n")); - let externalav: []str = [externalbin, "-package=main_test"]; - runcommand(root, strings.concat("command-external-run-", stages[si]), - externalav, (60i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); - assert(has(out.stdout, "command_external ... ok\n")); + assert(has(out.stdout, "main.command_internal ... ok\n")); + assert(has(out.stdout, "main_test.command_external ... ok\n")); + assert(same(ctrace, readfile(trace))); + ai = 0; + for (ai < artifacts.len) { + let sj: i32 = 0; + for (sj < suffixes.len) { + assert(same(warmrefs[ai * suffixes.len + sj], readfile( + strings.concat(work, artifacts[ai], suffixes[sj])))); + sj += 1; + }; + ai += 1; + }; // The same command action is not generally source-importable. It is // classified before leaf validation so this Go-like diagnostic is stable, @@ -3076,8 +3454,8 @@ fn localidentity(dir: str, leaf: str) str = { runcommandenv(root, strings.concat("command-import-", stages[si]), importav, env, (60i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); - assert(has(out.stderr, - "package cmdtool is a program, not an importable package\n")); + assert(has(out.stderr, strings.concat("package ", commandid, + " is a program, not an importable package\n"))); assert(readfile(trace).len == 0); clean(workroot); @@ -3426,14 +3804,15 @@ fn localidentity(dir: str, leaf: str) str = { @test fn persistent_package_request_workdir_is_stable() void = { let root: str = fresh(); - let tree: str = strings.concat(root, "/tree"); + let tree: str = deeppunctuationpath(root, "request tree"); + assert(tree.len > 255); let adir: str = strings.concat(tree, "/a"); let bdir: str = strings.concat(tree, "/b"); let a: str = strings.concat(adir, "/foo"); let b: str = strings.concat(bdir, "/foo"); let workc: str = strings.concat(root, "/work-c"); let workww: str = strings.concat(root, "/work-ww"); - assert(os.mkdir(tree, 448i32) == 0); + mkdirall(tree); assert(os.mkdir(adir, 448i32) == 0); assert(os.mkdir(bdir, 448i32) == 0); assert(os.mkdir(a, 448i32) == 0); @@ -3459,8 +3838,9 @@ fn localidentity(dir: str, leaf: str) str = { " [foo, same-package]\n"))); assert(has(cold, strings.concat("ok ", b, " [foo, same-package]\n"))); - let ckey: str = strings.concat(workc, "/d_", workescape(tree)); + let ckey: str = strings.concat(workc, "/", requestworkkey(tree)); assert(os.exists(ckey)); + assert(requestworkkey(tree).len == 66); let aaction: str = "a.foo-internal-test"; let baction: str = "b.foo-internal-test"; assert(os.exists(strings.concat(ckey, "/", aaction, ".unit.ww"))); @@ -3483,7 +3863,7 @@ fn localidentity(dir: str, leaf: str) str = { (60i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(same(cold, out.stdout)); - let wwkey: str = strings.concat(workww, "/d_", workescape(tree)); + let wwkey: str = strings.concat(workww, "/", requestworkkey(tree)); assert(same(aunit, readfile(strings.concat(wwkey, "/", aaction, ".unit.ww")))); assert(same(bunit, readfile(strings.concat(wwkey, "/", baction, @@ -3515,10 +3895,10 @@ fn localidentity(dir: str, leaf: str) str = { // Outside every active import root, equal declaration leaves remain two // deterministic command-local identities derived from canonical dirs. - let localleft: str = strings.concat(root, "/local-left"); - let localright: str = strings.concat(root, "/local-right"); - assert(os.mkdir(localleft, 448i32) == 0); - assert(os.mkdir(localright, 448i32) == 0); + let localleft: str = deeppunctuationpath(root, "local left"); + let localright: str = deeppunctuationpath(root, "local right"); + mkdirall(localleft); + mkdirall(localright); writefile(strings.concat(localleft, "/foo.ww"), "package foo;\nexport fn value() i32 = { return 11; };\n"); writefile(strings.concat(localright, "/foo.ww"), @@ -3526,6 +3906,10 @@ fn localidentity(dir: str, leaf: str) str = { let localids: []str = [localidentity(localleft, "foo"), localidentity(localright, "foo")]; assert(!same(localids[0], localids[1])); + assert(localids[0].len > 255 && localids[1].len > 255); + let localstorage: []str = [packagestoragekey(localids[0], localleft, 0, 0), + packagestoragekey(localids[1], localright, 0, 0)]; + assert(!same(localstorage[0], localstorage[1])); let stages: []str = ["ww", "ww_ww"]; let localrefs: []str = ["", "", "", "", "", ""]; let si: i32 = 0; @@ -3550,7 +3934,14 @@ fn localidentity(dir: str, leaf: str) str = { let xi: i32 = 0; for (xi < suffixes.len) { let artifact: str = readfile(strings.concat(localwork, - localids[li], suffixes[xi])); + localstorage[li], suffixes[xi])); + if (xi == 0) { + assert(has(artifact, strings.concat("//ww:module-reset ", + localids[li], "\n"))); + } else { if (xi == 1) { + assert(strings.hasprefix(artifact, strings.concat( + "//ww:module ", localids[li], "\n"))); + }; }; let refi: i32 = li * 3 + xi; if (si == 0) { localrefs[refi] = strings.dup(artifact); } else { assert(same(localrefs[refi], artifact)); }; diff --git a/test/sep/localbuild_test.ww b/test/sep/localbuild_test.ww index 9f2a2e2d..f2694ad9 100644 --- a/test/sep/localbuild_test.ww +++ b/test/sep/localbuild_test.ww @@ -907,7 +907,7 @@ fn writediamond(td: str, reverse: bool) str = { "/.wwtool.w6a")), testenv.readfile(assembler)) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.stamp")), - "ww workdir fmt 8 mode build asm 0\n")) { + "ww workdir fmt 10 mode build asm 0\n")) { fail("driver-identity", "persistent artifacts or identities are incomplete"); }; let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi"));