diff --git a/docs/build-system.md b/docs/build-system.md index 9ea6857d..8e277962 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -260,11 +260,12 @@ until `ww init` gives it a stable module/package identity. ### 3.1 One directory, one package -A package directory contains its immediate regular source files only. The build -does not follow source symlinks. Nested directories are separate packages. Every -selected production source MUST begin with the same canonical `package name;` -clause. The declared name MUST be a valid WW identifier. It need not repeat the -directory leaf because identity and source qualifier are separate concepts. +A package directory contains its immediate regular source files and source-name +symlinks that resolve to regular files. A source-name symlink to a directory is +ignored. Nested directories are separate packages. Every selected production +source MUST begin with the same canonical `package name;` clause. The declared +name MUST be a valid WW identifier. It need not repeat the directory leaf +because identity and source qualifier are separate concepts. The following current forms are errors after the migration: @@ -279,41 +280,47 @@ still the smallest package and needs no extra metadata. ### 3.2 File membership and target selection -Production candidates are immediate regular files ending `.ww`, excluding -`*_test.ww` and generated outputs. Names are byte-sorted after UTF-8 validity and -normalization checks. A source symlink, duplicate normalized name, case-fold -collision, or non-regular candidate is a loud error on every host. +The current toolchain has one honest target, `linux/amd64`. Production +candidates are immediate visible names ending `.ww`, excluding selected +`*_test.ww` files. Directory entries beginning `.` or `_` are ignored. Candidate +names are byte-sorted before any selected source is opened, parsed, or checked. -Target variants use this only convention: +WW applies Go 1.26.5's filename suffix algorithm to the portion of the basename +before its first dot. A final `_test` token is removed for this decision. If the +last two remaining underscore-delimited tokens are a known OS followed by a +known architecture, both must match `linux/amd64`. Otherwise a final known OS +or architecture must match. A known mismatch excludes the file; an unknown or +misplaced token leaves it ordinary. The pinned known sets are: ```text -stem[+os][+arch][+environment].ww +OS: aix android darwin dragonfly freebsd hurd illumos ios js linux nacl + netbsd openbsd plan9 solaris wasip1 windows zos +ARCH: 386 amd64 amd64p32 arm armbe arm64 arm64be loong64 mips mipsle + mips64 mips64le mips64p32 mips64p32le ppc ppc64 ppc64le riscv + riscv64 s390 s390x sparc sparc64 wasm ``` -Recognized tags come from the selected target descriptor, not from the host. -Files are grouped by `stem`. The matching member with the greatest number of -tags wins; the untagged member is the fallback. Two equally specific matches are -an error. Examples are `poll.ww`, `poll+linux.ww`, and -`poll+linux+amd64+gnu.ww`. This is replacement selection, not additive feature -selection; additive code uses a distinct stem. Unknown tags are errors. There -are no boolean selectors, glob expressions, or manifest-defined tag meanings. +The suffix requires a nonempty prefix and an underscore. Thus `linux.ww` and +`plan9_test.ww` are ordinary files, `x_plan9_test.ww` is excluded, +`x_linux_amd64.ww` is selected, and `x_windows_amd64.ww` is excluded. The first +dot ends inspection: `x.extra_windows.ww` is ordinary. Pair recognition takes +precedence over the final single token; `x_windows_amd64.ww` does not match just +because `amd64` does. Conversely `x_amd64_linux.ww` has no OS/architecture pair +and matches its final single `linux` token, exactly as Go does. -Test variants put the same tags before the reserved suffix, for example -`poll+linux_test.ww`; their grammar is `stem[+tags]_test.ww` and the identical -most-specific rule applies within the test set. A production stem ending -`_test` is reserved and rejected, preventing a tagged test from being mistaken -for production source. +Selection is additive, not replacement-based: every matching file belongs to +the package. The production variant then excludes `*_test.ww`; internal and +external test classification uses only the already platform-selected test +files. An excluded file creates no source occurrence, import, dependency edge, +package/action/variant identity, compiler input, export, archive member, link +input, artifact, status, or persistence dependency. Adding or editing one is a +producer no-op. Adding, removing, or editing a selected file changes the owning +unit normally. -The selected file-name list is itself an action-key input. Therefore adding or -removing a more-specific file invalidates the package even when the old files' -bytes do not change. - -CPU features and optimization mode always enter the compile key but do not add -another WW source-selection language. WW-level specialization uses compiler -intrinsics/runtime dispatch or a distinct package; CPU/float-ABI/PIC-sensitive C -or assembly uses the finite native `when` constraints in section 8.4. This keeps -ordinary source membership conventional while still making exceptional native -selection exact and inspectable. +WW implements no source-level build expressions, user tags, target descriptor, +`UseAllFiles` escape, `+tag` replacement scheme, or manifest-defined selector. +Those would introduce a second build language or a manifest model and are +outside the local, manifest-free product. ### 3.3 Imports, names, and resolution @@ -5132,7 +5139,7 @@ first regenerated byte-identical semantic export. The owner source voucher remains `.unit.ww`; a linked product root also owns `.init.unit.ww` for its dispatcher unit, `.init.s`, `.init.o`, and -two-member archive. Current persistent formats are build 17 and test 16. Warm +two-member archive. Current persistent formats are build 18 and test 17. Warm consumers select a dependency's staged `.wwi.new` or `.a.new` when that exact action changed in the same request. All action artifacts, init artifacts, tool identity copies, stamp, library/executable publications, and test statuses are @@ -5192,6 +5199,151 @@ persistent-workdir, rejection-state, byte-identity, and bootstrap observers own their unchanged broader boundaries. Grouped imports, quoted imports, and dot imports remain deliberately unimplemented. +### 11.21 Implemented Go platform filename eligibility + +Directory packages now apply Go 1.26.5's OS/architecture filename rule before +a source can enter WW's production or test graph. This closes a loader-wide +divergence rather than adding a syntax feature: WW remains a local, +manifest-free toolchain with unquoted dotted imports and one supported target, +`linux/amd64`. + +#### Pinned Go evidence and pre-fix divergence + +The reference is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `Context.matchFile` first rejects leading-dot/underscore names and unrelated + extensions, calls `goodOSArchFile`, and only then joins and opens the source + ([`go/build/build.go`, lines 1438–1509](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1438-L1509)). +- `goodOSArchFile` cuts at the first dot, requires an underscore-prefixed + suffix, removes a final `test` token, gives a known OS/architecture pair + precedence over a final known single token, and treats every other suffix as + ordinary + ([`go/build/build.go`, lines 1980–2027](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1980-L2027)). + Its match operation is against the selected `GOOS`/`GOARCH`, with only the + documented Android/Linux, illumos/Solaris, and iOS/Darwin aliases + ([`go/build/build.go`, lines 1933–1977](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1933-L1977)). +- The exact past, present, and future filename-recognition sets are + `syslist.KnownOS` and `syslist.KnownArch`; they are intentionally broader than + currently supported targets and explicitly must not lose old names + ([`internal/syslist/syslist.go`, lines 14–36 and 56–83](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/syslist/syslist.go#L14-L83)). +- `go/build` requires sorted directory presentation, its ordinary reader uses + byte-sorted names, and package classification consumes that order + ([`go/build/build.go`, lines 108–111 and 193–207](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L108-L207), + [lines 859–914](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L859-L914), + [`os/dir.go`, lines 109–125](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/dir.go#L109-L125)). +- The official `TestMatchFile` table accepts `android.go`, `plan9.go`, and + `plan9_test.go` as whole-name ordinary files, accepts matching architecture + and Android/Linux aliases, and rejects a mismatching `foo_darwin.go` + ([`go/build/build_test.go`, lines 381–425](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L381-L425)). + Command testdata independently proves that a selected Linux suffix contributes + its file and import on `linux/amd64` and both disappear on Darwin + ([`cmd/go/testdata/script/list_constraints.txt`, lines 1–29 and 57–60](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/list_constraints.txt#L1-L60)). + An explicit package whose files are all excluded is rejected + ([`build_no_go.txt`, lines 1–17 and 31–41](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_no_go.txt#L1-L41)). + +Before this slice, both drivers accepted every visible `.ww` entry apart from +the production/test partition, and `internal/wwpackage` discovered every such +entry recursively. They opened and parsed candidates in raw filesystem order. +On Linux/amd64, a malformed `bad_windows.ww` therefore rejected the request; +an otherwise valid `platform_windows_arm64.ww` could add imports, actions, +direct `.wwi` inputs, archives, linker inputs, initialization, and runtime +behavior; wrong-target internal and external tests ran; a directory containing +only `only_windows.ww` was selected recursively; and editing an ineligible file +recompiled its owner. Cstage and WWstage agreed with each other but were both +wrong. + +#### Final source and graph ownership + +The basename predicate is exact and allocation-free. It examines the stem +before the first dot, removes final `_test` for suffix analysis, then recognizes +only the pinned Go `KnownOS`/`KnownArch` sets. A recognized pair must be +`linux_amd64`; a recognized final single must be `linux` or `amd64`. Unknown or +misplaced tokens remain ordinary. There is no alias-, declared-name-, path-leaf-, +physical-directory-, artifact-, or request-order input to this decision. + +Both drivers first collect every visible `.ww` basename in checked dynamically +grown storage, byte-sort the names, then apply target and production/test +eligibility before source stat/open/parse and package validation. This removes +the former filesystem-order diagnostic race and adds no fixed file bound. The +shared coordinator already sorts directory entries; its source predicate now +removes a mismatching basename before it is appended to discovery or grouped +into a package/test product. A recursive pattern skips a directory with no +eligible sources. An explicit directory with no eligible production source +retains WW's stable `directory contains no WW package sources` rejection. + +Eligibility owns whether a source occurrence exists. For a selected file, the +parser and checker retain its exact file-local imports, aliases, blank +occurrences, positions, and declarations, and the package graph deduplicates +their resolved canonical targets exactly as before. For an excluded file there +is no occurrence to resolve: missing, self, cycle, final-`internal`, vendor, and +imported-`main` validation do not run, and the file contributes no canonical +dependency or action. This is source/file ownership before package-graph +ownership, never another identity dimension. + +#### Build, test, artifacts, and execution + +Production sees all matching non-test sources. The internal-test variant sees +that production category followed by matching same-package `*_test.ww` files; +the external variant sees only matching external `*_test.ww` files. The suffix +rule therefore removes wrong-target test-only imports and initialization before +variant construction, support generation, or generated-main generation. +`plan9_test.ww` remains ordinary because the suffix has no nonempty prefix; +`x_plan9_test.ww` is excluded; first-dot and pair-precedence cases behave like +the pinned Go table. + +No checker, interface writer, assembler, archiver, or linker protocol changed. +The drivers simply stop excluded bytes before those owners. Each selected +package unit still contains its category-ordered source files and exact import +occurrences. The compiler still receives one byte-sorted direct `.wwi` input per +canonical edge; `.wwi` still contains only semantic exports; archives still +contain only their canonical package action (plus the command root dispatcher +member where applicable); and the linker still receives the root plus reachable +archive-only closure. An import found only in an excluded file therefore +creates no `.wwi`, object, archive, init task, dispatcher edge, linker argument, +binary effect, or test execution. + +#### Persistence, rejection, and stage responsibility + +Persistent formats are build 18 and test 17 so a pre-slice workdir performs one +complete reachable-action refresh under the new membership contract. Thereafter +an excluded-file add, removal, or content edit changes no unit voucher, `.wwi`, +assembly, object, archive, dispatcher, test status, or reverse action. Existing +product policy may still relink an explicitly requested executable from its +unchanged archives. A selected private implementation edit rebuilds its owner; +if its `.wwi` is byte-identical, reverse compilation stops and only affected +products relink. + +Wrong-target malformed sources and wrong-target structural import sites are +ignored without producers. Selected structural failures are reported in +byte-sorted filename order before producers. Any later selected-source compiler +failure remains inside the existing request transaction: staged dependency +changes are discarded, all prior actions/tool records/stamps/publications stay +byte-identical, no `.new` generation survives, and no mixed package or test +result is published. + +`cmd/ww/main.c` and `selfhost/cmd/ww/main.ww` mechanically mirror direct +enumeration, sorting, target filtering, and checked allocation. The shared +`internal/wwpackage/package.ww` predicate owns recursive build/test discovery. +The compiler/checker/writer consume only selected units and require no special +case; `w6a` and `w6l` remain unchanged. The focused native +`platform_filename_source_selection` observer generates independent cold and +persistent Cstage/WWstage work roots and proves exact suffix edge cases, +sorted diagnostics, direct and recursive build/test selection, production/test +isolation, repeated-edge canonicalization, exact compiler/assembler/linker +argv, archive-only closure, artifact/assembly/binary equality, reversed-root +independence, runtime results, ignored-edit reuse, `.wwi`-stable reverse +propagation, and late-failure rollback. Existing dynamic-allocation, +no-follow, byte-identity, bootstrap, internal, vendor, and imported-command +observers retain their broader ownership. + +Source-level `//go:build`/`+build` equivalents, arbitrary tags, cross-target +selection, grouped/quoted/dot imports, modules, manifests, registries, and a +programmable build language remain deliberately unsupported. Go's `UseAllFiles` +escape is also not exposed. The separate remaining Go test-product topology +divergence—one generated main for combined internal and external variants—is +not hidden or changed by this slice. + ## 12. Candidate architectures and hard-gate decision Five candidates were developed as coherent systems, not as feature bins. diff --git a/docs/spec.md b/docs/spec.md index 436f177d..e3a4c981 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -255,6 +255,20 @@ ImportPath = ident { "." ident } . `.ww` files sharing one declared package name compiles as one package. The declared name need not equal the directory name or the final component of its canonical import identity. +- Directory source eligibility uses Go 1.26.5 filename suffix semantics for + WW's fixed `linux/amd64` target. In the basename stem before the first dot, a + final `_test` token is ignored for platform matching. A final known OS or + architecture token must match `linux` or `amd64`; a final known OS followed + by a known architecture takes precedence and both must match. The known-name + sets are Go 1.26.5's `syslist.KnownOS` and `syslist.KnownArch`. Unknown or + misplaced suffixes are ordinary, and a platform word without a nonempty + underscore prefix is ordinary (`linux.ww` and `plan9_test.ww` are selected; + `x_windows.ww` and `x_plan9_test.ww` are not). Leading-dot/underscore entries + are ignored. Eligible names are byte-sorted before source validation. + Production excludes selected `*_test.ww`; test variants classify only those + selected test files. An excluded file contributes no declarations, imports, + package edge, action, export, artifact, initialization, test, or persistent + invalidation. - `import acme.codec;` loads the canonical package `acme.codec`. If that package declares `package wire;`, the importing file sees its exported names as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 4159f1c3..3a505127 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -224,6 +224,19 @@ run always executes from the temp root. Without `-c`, it removes the temporary binary and scratch with its workspace. The language runtime owns individual `@test` functions. +Before package grouping, both directory drivers and the shared recursive +coordinator apply Go 1.26.5's filename OS/architecture rule for WW's fixed +`linux/amd64` target. The basename stem ends at its first dot; a final `_test` +token is removed for matching; a recognized OS/architecture pair takes +precedence over a recognized final single token; and unknown or misplaced +tokens remain ordinary. Wrong-target production, same-package test, and +external-test files therefore create no imports, variants, generated-main +inputs, runtime tests, artifacts, or persistent invalidation. Names are +byte-sorted before selected-source validation, so diagnostics do not depend on +directory entry order. A recursive pattern omits a directory with no eligible +source, while an explicit wrong-target-only build rejects it as having no WW +package source. There is no source-level build-expression or user-tag mode. + Separate compilation is the only driver build path; no compatibility mode switch remains. @@ -255,6 +268,19 @@ exact-argv, command, and persistent-workdir observers, the package suite proves archive-only link argv and exact warm/rejection-state behavior without duplicating those broader mechanisms in this observer. +The same package owner contains the focused +`platform_filename_source_selection` observer. It independently generates the +suffix matrix, production/internal/external sources, wrong-target import and +malformed sentinels, wrong-target-only directories, and reverse-created +diagnostic files. Across cold and persistent Cstage/WWstage roots it compares +normalized compiler/assembler/linker argv, units, `.wwi`, assembly, objects, +archives, generated-main archives, binaries, runtime/test output, direct and +recursive behavior, reversed roots, ignored-edit reuse, selected private-change +propagation, and request rollback after a dependency has staged and the root +compiler fails. The existing bounded-memory package-initialization observer +continues to own allocation-failure parity for the shared dynamically grown +action universe. + `test/sep/sepinit_test.ww` is the single focused package-initialization owner. It generates all source trees temporarily and runs independent cold/persistent Cstage and WWstage legs. Its matrix covers blank-only reachability; dependency, diff --git a/test/package/package_test.ww b/test/package/package_test.ww index f447be88..d288ddde 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -5118,6 +5118,637 @@ fn hexbytes(value: str) str = { clean(root); }; +@test fn platform_filename_source_selection() void = { + let root: str = fresh(); + let source: str = strings.concat(root, "/source"); + let app: str = strings.concat(source, "/app"); + let good: str = strings.concat(source, "/dep/good"); + let pkg: str = strings.concat(source, "/pkg"); + let testgood: str = strings.concat(source, "/test/good"); + let wrongonly: str = strings.concat(source, "/wrongonly"); + let notests: str = strings.concat(source, "/notests"); + let internal: str = strings.concat(source, "/foreign/internal/secret"); + let command: str = strings.concat(source, "/command"); + let vendored: str = strings.concat(app, "/vendor/vendored"); + let order: str = strings.concat(root, "/order"); + let tools: str = strings.concat(root, "/tools"); + mkdirall(app); mkdirall(good); mkdirall(pkg); mkdirall(testgood); + mkdirall(wrongonly); mkdirall(notests); mkdirall(internal); + mkdirall(command); mkdirall(vendored); mkdirall(order); mkdirall(tools); + + let good5: str = strings.concat("package good;\n", + "export fn value() i32 = { return 5; };\n"); + let good6: str = strings.concat("package good;\n", + "export fn value() i32 = { return 6; };\n"); + let good7: str = strings.concat("package good;\n", + "export fn value() i32 = { return 7; };\n"); + let appmain: str = strings.concat( + "package main;\nimport dep.good;\n", + "fn main() i32 = { return good.value() + wholelinux() + ", + "afterdot() + arch() + reversed() + future() + osname() + pair(); };\n"); + let wholelinux: str = + "package main;\nfn wholelinux() i32 = { return 1; };\n"; + let afterdot: str = + "package main;\nfn afterdot() i32 = { return 1; };\n"; + let arch: str = + "package main;\nfn arch() i32 = { return 1; };\n"; + let reversed: str = + "package main;\nfn reversed() i32 = { return 1; };\n"; + let future: str = + "package main;\nfn future() i32 = { return 1; };\n"; + let osname: str = strings.concat( + "package main;\nimport _ dep.good;\n", + "fn osname() i32 = { return 1; };\n"); + let pair: str = + "package main;\nfn pair() i32 = { return 1; };\n"; + let ignoredparse: str = + "this wrong-target source is deliberately malformed\n"; + let ignoredparseedit: str = + "a different malformed wrong-target edit {\n"; + let ignoreddep: str = strings.concat( + "package main;\nimport dep.wrong;\n", + "fn wrong_dep() i32 = { return wrong.value(); };\n"); + let ignoredinternal: str = strings.concat( + "package main;\nimport foreign.internal.secret;\n", + "fn wrong_internal() i32 = { return secret.value(); };\n"); + let ignoredmain: str = strings.concat( + "package main;\nimport command;\n", + "fn wrong_main() i32 = { return 0; };\n"); + let ignoredvendor: str = strings.concat( + "package main;\nimport vendored;\n", + "fn wrong_vendor() i32 = { return vendored.value(); };\n"); + writefile(strings.concat(good, "/good.ww"), good5); + writefile(strings.concat(app, "/main.ww"), appmain); + writefile(strings.concat(app, "/linux.ww"), wholelinux); + writefile(strings.concat(app, "/platform.extra_windows.ww"), afterdot); + writefile(strings.concat(app, "/platform_amd64.ww"), arch); + writefile(strings.concat(app, "/platform_amd64_linux.ww"), reversed); + writefile(strings.concat(app, "/platform_future.ww"), future); + writefile(strings.concat(app, "/platform_linux.ww"), osname); + writefile(strings.concat(app, "/platform_linux_amd64.ww"), pair); + writefile(strings.concat(app, "/badparse_windows.ww"), ignoredparse); + writefile(strings.concat(app, "/baddep_arm64.ww"), ignoreddep); + writefile(strings.concat(app, "/badinternal_windows_amd64.ww"), + ignoredinternal); + writefile(strings.concat(app, "/badmain_linux_arm64.ww"), ignoredmain); + writefile(strings.concat(app, "/badvendor_windows.ww"), ignoredvendor); + writefile(strings.concat(internal, "/secret.ww"), strings.concat( + "package secret;\n", + "export fn value() i32 = { return 19; };\n")); + writefile(strings.concat(command, "/main.ww"), + "package main;\nfn main() i32 = { return 0; };\n"); + writefile(strings.concat(vendored, "/vendored.ww"), strings.concat( + "package vendored;\n", + "export fn value() i32 = { return 23; };\n")); + + let pkgbase: str = strings.concat("package pkg;\n", + "export fn value() i32 = { return 7; };\n"); + let samepair: str = strings.concat( + "package pkg;\nimport test.good;\n", + "@test fn selected_internal_pair() void = { ", + "assert(value() + good.value() == 18); };\n"); + let samewhole: str = strings.concat( + "package pkg;\n", + "@test fn selected_whole_plan9_name() void = { assert(value() == 7); };\n"); + let samereversed: str = strings.concat( + "package pkg;\n", + "@test fn selected_reversed_suffix() void = { assert(value() == 7); };\n"); + let externalpair: str = strings.concat( + "package pkg_test;\nimport pkg;\nimport test.good;\n", + "@test fn selected_external_pair() void = { ", + "assert(pkg.value() + good.value() == 18); };\n"); + let externaldot: str = strings.concat( + "package pkg_test;\nimport pkg;\n", + "@test fn selected_external_after_dot() void = { ", + "assert(pkg.value() == 7); };\n"); + let wrongtest: str = strings.concat( + "package pkg;\nimport test.wrong;\n", + "@test fn wrong_target_test() void = { abort(\"wrong test ran\"); };\n"); + let wrongtestedit: str = + "this edited wrong-target test remains deliberately malformed\n"; + writefile(strings.concat(pkg, "/base.ww"), pkgbase); + writefile(strings.concat(pkg, "/internal_linux_amd64_test.ww"), samepair); + writefile(strings.concat(pkg, "/plan9_test.ww"), samewhole); + writefile(strings.concat(pkg, "/reverse_amd64_linux_test.ww"), + samereversed); + writefile(strings.concat(pkg, "/external_linux_amd64_test.ww"), + externalpair); + writefile(strings.concat(pkg, "/external.extra_windows_test.ww"), + externaldot); + writefile(strings.concat(pkg, "/internal_windows_amd64_test.ww"), + wrongtest); + writefile(strings.concat(pkg, "/external_linux_arm64_test.ww"), + wrongtest); + writefile(strings.concat(pkg, "/x_plan9_test.ww"), wrongtest); + writefile(strings.concat(testgood, "/good.ww"), strings.concat( + "package good;\n", + "export fn value() i32 = { return 11; };\n")); + writefile(strings.concat(wrongonly, "/only_windows.ww"), ignoredparse); + writefile(strings.concat(notests, "/base.ww"), + "package notests;\nexport fn value() i32 = { return 1; };\n"); + writefile(strings.concat(notests, "/only_windows_test.ww"), ignoredparse); + // Creation order is deliberately the inverse of byte order. + writefile(strings.concat(order, "/z_linux.ww"), + "package order;\n@test fn z() void = { };\n"); + writefile(strings.concat(order, "/a_linux.ww"), + "package order;\n@test fn a() void = { };\n"); + + let compilerwrapper: str = strings.concat(tools, "/w6c.sh"); + let assemblerwrapper: str = strings.concat(tools, "/w6a.sh"); + let linkerwrapper: str = strings.concat(tools, "/w6l.sh"); + writeexecutable(compilerwrapper, strings.concat( + "#!/bin/sh\nprintf 'BEGIN' >> \"$WW_PLATFORM_COMPILER_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_PLATFORM_COMPILER_TRACE\"; done\n", + "printf '\\n' >> \"$WW_PLATFORM_COMPILER_TRACE\"\n", + "exec \"$WW_PLATFORM_REAL_COMPILER\" \"$@\"\n")); + writeexecutable(assemblerwrapper, strings.concat( + "#!/bin/sh\nprintf 'BEGIN' >> \"$WW_PLATFORM_ASSEMBLER_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_PLATFORM_ASSEMBLER_TRACE\"; done\n", + "printf '\\n' >> \"$WW_PLATFORM_ASSEMBLER_TRACE\"\n", + "exec \"$WW_PLATFORM_REAL_ASSEMBLER\" \"$@\"\n")); + writeexecutable(linkerwrapper, strings.concat( + "#!/bin/sh\nprintf 'BEGIN' >> \"$WW_PLATFORM_LINKER_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_PLATFORM_LINKER_TRACE\"; done\n", + "printf '\\n' >> \"$WW_PLATFORM_LINKER_TRACE\"\n", + "exec \"$WW_PLATFORM_REAL_LINKER\" \"$@\"\n")); + + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let assemblers: []str = ["w6a", "w6a_ww"]; + let linkers: []str = ["w6l", "w6l_ww"]; + let labels: []str = ["c", "ww"]; + let artifactnames: []str = ["app.unit.ww", "app.wwi", "app.s", + "app.o", "app.a", "app.init.unit.ww", "app.init.s", "app.init.o", + "dep.good.unit.ww", "dep.good.wwi", "dep.good.s", "dep.good.o", + "dep.good.a"]; + let artifactrefs: []str = ["", "", "", "", "", "", "", "", "", + "", "", "", ""]; + let testartifactnames: []str = ["pkg.unit.ww", "pkg.wwi", "pkg.a", + "pkg-internal-test.unit.ww", "pkg-internal-test.wwi", + "pkg-internal-test.a", "pkg_test-external-test.unit.ww", + "pkg_test-external-test.wwi", "pkg_test-external-test.a", + "pkg-internal-test-main.a", "pkg_test-external-test-main.a"]; + let testartifactrefs: []str = ["", "", "", "", "", "", "", "", + "", "", ""]; + let binref: str = ""; + let compilerref: str = ""; + let assemblerref: str = ""; + let linkerref: str = ""; + let prodarchiveref: str = ""; + let prodwwiref: str = ""; + let testoutref: str = ""; + let testerrref: str = ""; + let faildiagref: str = ""; + let recursiveoutref: str = ""; + let recursiveerrref: str = ""; + let baseenv: []str = os.getenvs(); + let si: i32 = 0; + for (si < stages.len) { + rewritefile(strings.concat(good, "/good.ww"), good5); + rewritefile(strings.concat(app, "/platform_future.ww"), future); + rewritefile(strings.concat(app, "/badparse_windows.ww"), ignoredparse); + rewritefile(strings.concat(pkg, "/internal_windows_amd64_test.ww"), + wrongtest); + let work: str = strings.concat(root, "/", labels[si], "-work"); + let reversework: str = strings.concat(root, "/", labels[si], + "-reverse-work"); + let outdir: str = strings.concat(root, "/", labels[si], "-out"); + let reverseout: str = strings.concat(root, "/", labels[si], + "-reverse-out"); + let compilertrace: str = strings.concat(root, "/", labels[si], + "-compiler"); + let assemblertrace: str = strings.concat(root, "/", labels[si], + "-assembler"); + let linkertrace: str = strings.concat(root, "/", labels[si], + "-linker"); + assert(os.mkdir(work, 448i32) == 0); + assert(os.mkdir(reversework, 448i32) == 0); + assert(os.mkdir(outdir, 448i32) == 0); + assert(os.mkdir(reverseout, 448i32) == 0); + writefile(compilertrace, ""); writefile(assemblertrace, ""); + writefile(linkertrace, ""); + let env: []str = alloc([], (baseenv.len + 9): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6A=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_COMPILER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_ASSEMBLER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_LINKER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_REAL_COMPILER=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_REAL_ASSEMBLER=") + && !strings.hasprefix(baseenv[ei], + "WW_PLATFORM_REAL_LINKER=")) { + append(env, baseenv[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=", compilerwrapper)); + append(env, strings.concat("WW_W6A=", assemblerwrapper)); + append(env, strings.concat("WW_W6L=", linkerwrapper)); + append(env, strings.concat("WW_PLATFORM_COMPILER_TRACE=", compilertrace)); + append(env, strings.concat("WW_PLATFORM_ASSEMBLER_TRACE=", assemblertrace)); + append(env, strings.concat("WW_PLATFORM_LINKER_TRACE=", linkertrace)); + append(env, strings.concat("WW_PLATFORM_REAL_COMPILER=", + driver(compilers[si]))); + append(env, strings.concat("WW_PLATFORM_REAL_ASSEMBLER=", + driver(assemblers[si]))); + append(env, strings.concat("WW_PLATFORM_REAL_LINKER=", + driver(linkers[si]))); + + let forward: []str = [driver(stages[si]), "build", "-w", work, + "-I", source, "-o", strings.concat(outdir, "/"), app, good]; + let reverse: []str = [driver(stages[si]), "build", "-w", reversework, + "-I", source, "-o", strings.concat(reverseout, "/"), good, app]; + let out: commandout; + runcommandenv(root, strings.concat("platform-cold-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + let ctrace: str = readfile(compilertrace); + let atrace: str = readfile(assemblertrace); + let ltrace: str = readfile(linkertrace); + assert(occurrences(ctrace, "\n") == 2); + assert(occurrences(atrace, "\n") == 3); + assert(occurrences(ltrace, "\n") == 1); + let appline: str = linecontaining(ctrace, "/app.unit.new"); + assert(occurrences(appline, "<--import>") == 1); + assert(occurrences(appline, "/dep.good.wwi.new>") == 1); + assert(!has(ctrace, "dep.wrong")); + assert(!has(ctrace, "foreign.internal.secret")); + assert(!has(ctrace, "vendored")); + assert(!has(ctrace, "command.wwi")); + assert(occurrences(ltrace, strings.concat(work, "/app.a.new")) == 1); + assert(occurrences(ltrace, strings.concat(work, + "/dep.good.a.new")) == 1); + assert(!has(ltrace, ".wwi")); + assert(!has(ltrace, "dep.wrong")); + let unitprefix: str = strings.concat( + "//ww:module-reset app\n", wholelinux, "\n", + "//ww:module-reset app\n", appmain, "\n", + "//ww:module-reset app\n", afterdot, "\n", + "//ww:module-reset app\n", arch, "\n", + "//ww:module-reset app\n", reversed, "\n", + "//ww:module-reset app\n", future, "\n", + "//ww:module-reset app\n", osname, "\n", + "//ww:module-reset app\n", pair, "\n"); + let appunit: str = readfile(strings.concat(work, "/app.unit.ww")); + assert(strings.hasprefix(appunit, unitprefix)); + assert(has(appunit, "//ww:direct-export dep.good ")); + assert(!has(appunit, "wrong_dep")); + assert(!has(appunit, "wrong_internal")); + assert(!has(appunit, "wrong_main")); + assert(!has(appunit, "wrong_vendor")); + assert(!os.exists(strings.concat(work, "/dep.wrong.unit.ww"))); + let bin: str = strings.concat(outdir, "/app"); + let runav: []str = [bin]; + runcommand(root, strings.concat("platform-run-", labels[si]), runav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 12); + let ai: i32 = 0; + for (ai < artifactnames.len) { + let bytes: str = readfile(strings.concat(work, "/", + artifactnames[ai])); + if (si == 0) { artifactrefs[ai] = strings.dup(bytes); } + else { assert(same(artifactrefs[ai], bytes)); }; + ai += 1; + }; + let normalizedcompiler: str = normalizedtrace(ctrace, + strings.concat(work, "/"), outdir); + let normalizedassembler: str = normalizedtrace(atrace, + strings.concat(work, "/"), outdir); + let normalizedlinker: str = normalizedtrace(ltrace, + strings.concat(work, "/"), outdir); + if (si == 0) { + compilerref = strings.dup(normalizedcompiler); + assemblerref = strings.dup(normalizedassembler); + linkerref = strings.dup(normalizedlinker); + binref = strings.dup(readfile(bin)); + } else { + assert(same(compilerref, normalizedcompiler)); + assert(same(assemblerref, normalizedassembler)); + assert(same(linkerref, normalizedlinker)); + assert(same(binref, readfile(bin))); + }; + + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-reverse-", labels[si]), + reverse, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(readfile(bin), readfile(strings.concat(reverseout, "/app")))); + assert(same(normalizedcompiler, normalizedtrace(readfile(compilertrace), + strings.concat(reversework, "/"), reverseout))); + assert(same(normalizedassembler, normalizedtrace(readfile(assemblertrace), + strings.concat(reversework, "/"), reverseout))); + assert(same(normalizedlinker, normalizedtrace(readfile(linkertrace), + strings.concat(reversework, "/"), reverseout))); + ai = 0; + for (ai < artifactnames.len) { + assert(same(readfile(strings.concat(work, "/", artifactnames[ai])), + readfile(strings.concat(reversework, "/", artifactnames[ai])))); + ai += 1; + }; + + let stableunit: str = strings.dup(readfile(strings.concat(work, + "/app.unit.ww"))); + let stablewwi: str = strings.dup(readfile(strings.concat(work, + "/app.wwi"))); + let stablearchive: str = strings.dup(readfile(strings.concat(work, + "/app.a"))); + let stabledepwwi: str = strings.dup(readfile(strings.concat(work, + "/dep.good.wwi"))); + let stablebin: str = strings.dup(readfile(bin)); + let stablestamp: str = strings.dup(readfile(strings.concat(work, + "/.wwtool.stamp"))); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-warm-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(occurrences(readfile(linkertrace), "\n") == 1); + + rewritefile(strings.concat(app, "/badparse_windows.ww"), + ignoredparseedit); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-ignored-edit-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(occurrences(readfile(linkertrace), "\n") == 1); + assert(same(stableunit, readfile(strings.concat(work, "/app.unit.ww")))); + assert(same(stablewwi, readfile(strings.concat(work, "/app.wwi")))); + assert(same(stablearchive, readfile(strings.concat(work, "/app.a")))); + assert(same(stabledepwwi, readfile(strings.concat(work, + "/dep.good.wwi")))); + assert(same(stablebin, readfile(bin))); + assert(same(stablestamp, readfile(strings.concat(work, + "/.wwtool.stamp")))); + + rewritefile(strings.concat(good, "/good.ww"), good6); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-private-edit-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(occurrences(readfile(compilertrace), "\n") == 1); + assert(has(readfile(compilertrace), "/dep.good.unit.new")); + assert(!has(readfile(compilertrace), "/app.unit.new")); + assert(occurrences(readfile(assemblertrace), "\n") == 1); + assert(occurrences(readfile(linkertrace), "\n") == 1); + assert(same(stabledepwwi, readfile(strings.concat(work, + "/dep.good.wwi")))); + assert(same(stableunit, readfile(strings.concat(work, "/app.unit.ww")))); + assert(same(stablewwi, readfile(strings.concat(work, "/app.wwi")))); + assert(same(stablearchive, readfile(strings.concat(work, "/app.a")))); + assert(!same(stablebin, readfile(bin))); + let runchanged: []str = [bin]; + runcommand(root, strings.concat("platform-run-edited-", labels[si]), + runchanged, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 13); + let committeddepunit: str = strings.dup(readfile(strings.concat(work, + "/dep.good.unit.ww"))); + let committeddepa: str = strings.dup(readfile(strings.concat(work, + "/dep.good.a"))); + let committedbin: str = strings.dup(readfile(bin)); + rewritefile(strings.concat(good, "/good.ww"), good7); + rewritefile(strings.concat(app, "/platform_future.ww"), + "package main;\nfn future() i32 = { return missing_name; };\n"); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-late-failure-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "undefined: missing_name")); + let normalizedfailure: str = normalizedtrace(out.stderr, + strings.concat(work, "/"), bin); + if (si == 0) { faildiagref = strings.dup(normalizedfailure); } + else { assert(same(faildiagref, normalizedfailure)); }; + assert(occurrences(readfile(compilertrace), "\n") == 2); + assert(occurrences(readfile(assemblertrace), "\n") == 1); + assert(readfile(linkertrace).len == 0); + assert(same(committeddepunit, readfile(strings.concat(work, + "/dep.good.unit.ww")))); + assert(same(committeddepa, readfile(strings.concat(work, + "/dep.good.a")))); + assert(same(committedbin, readfile(bin))); + assert(same(stablestamp, readfile(strings.concat(work, + "/.wwtool.stamp")))); + assert(!os.exists(strings.concat(work, "/dep.good.unit.new"))); + assert(!os.exists(strings.concat(work, "/dep.good.wwi.new"))); + assert(!os.exists(strings.concat(work, "/dep.good.a.new"))); + assert(!os.exists(strings.concat(work, "/app.unit.new"))); + assert(!os.exists(strings.concat(work, "/app.wwi.new"))); + assert(!os.exists(strings.concat(work, "/app.a.new"))); + rewritefile(strings.concat(good, "/good.ww"), good6); + rewritefile(strings.concat(app, "/platform_future.ww"), future); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-restored-", labels[si]), + forward, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(occurrences(readfile(linkertrace), "\n") == 1); + assert(same(committedbin, readfile(bin))); + + rewritefile(strings.concat(good, "/good.ww"), good5); + rewritefile(strings.concat(app, "/badparse_windows.ww"), ignoredparse); + let prodwork: str = strings.concat(root, "/", labels[si], "-prod-work"); + let prodout: str = strings.concat(root, "/", labels[si], "-pkg.a"); + assert(os.mkdir(prodwork, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let prodav: []str = [driver(stages[si]), "build", "-w", prodwork, + "-I", source, "-o", prodout, pkg]; + runcommandenv(root, strings.concat("platform-production-", labels[si]), + prodav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(occurrences(readfile(compilertrace), "\n") == 1); + assert(!has(readfile(compilertrace), "test.good")); + assert(!has(readfile(strings.concat(prodwork, "/pkg.unit.ww")), + "selected_internal")); + if (si == 0) { + prodarchiveref = strings.dup(readfile(prodout)); + prodwwiref = strings.dup(readfile(strings.concat(prodout, ".wwi"))); + } else { + assert(same(prodarchiveref, readfile(prodout))); + assert(same(prodwwiref, readfile(strings.concat(prodout, ".wwi")))); + }; + + let testwork: str = strings.concat(root, "/", labels[si], "-test-work"); + assert(os.mkdir(testwork, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let testav: []str = [driver(stages[si]), "test", "-w", testwork, + "-I", source, pkg]; + runcommandenv(root, strings.concat("platform-test-", labels[si]), testav, + env, (180i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "selected_internal_pair ... ok\n")); + assert(has(out.stdout, "selected_whole_plan9_name ... ok\n")); + assert(has(out.stdout, "selected_reversed_suffix ... ok\n")); + assert(has(out.stdout, "selected_external_pair ... ok\n")); + assert(has(out.stdout, "selected_external_after_dot ... ok\n")); + assert(!has(out.stdout, "wrong_target_test")); + assert(!has(out.stderr, "wrong-target")); + let testctrace: str = readfile(compilertrace); + let testltrace: str = readfile(linkertrace); + assert(!has(testctrace, "test.wrong")); + let sameline: str = linecontaining(testctrace, + "/pkg-internal-test.unit.new"); + let externalline: str = linecontaining(testctrace, + "/pkg_test-external-test.unit.new"); + assert(occurrences(sameline, "<--import>") == 1); + assert(occurrences(externalline, "<--import>") == 1); + assert(occurrences(externalline, "<--import>") == 1); + assert(!has(testltrace, ".wwi")); + let sameunit: str = readfile(strings.concat(testwork, + "/pkg-internal-test.unit.ww")); + let externalunit: str = readfile(strings.concat(testwork, + "/pkg_test-external-test.unit.ww")); + assert(has(sameunit, "selected_internal_pair")); + assert(has(sameunit, "selected_whole_plan9_name")); + assert(has(sameunit, "selected_reversed_suffix")); + assert(!has(sameunit, "wrong_target_test")); + assert(has(externalunit, "selected_external_pair")); + assert(has(externalunit, "selected_external_after_dot")); + assert(!has(externalunit, "wrong_target_test")); + ai = 0; + for (ai < testartifactnames.len) { + let bytes: str = readfile(strings.concat(testwork, "/", + testartifactnames[ai])); + if (si == 0) { testartifactrefs[ai] = strings.dup(bytes); } + else { assert(same(testartifactrefs[ai], bytes)); }; + ai += 1; + }; + if (si == 0) { + testoutref = strings.dup(out.stdout); + testerrref = strings.dup(out.stderr); + } else { + assert(same(testoutref, out.stdout)); + assert(same(testerrref, out.stderr)); + }; + let stabletestunit: str = strings.dup(sameunit); + rewritefile(strings.concat(pkg, "/internal_windows_amd64_test.ww"), + wrongtestedit); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + runcommandenv(root, strings.concat("platform-test-warm-", labels[si]), + testav, env, (180i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(testoutref, out.stdout)); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(occurrences(readfile(linkertrace), "\n") == 2); + assert(same(stabletestunit, readfile(strings.concat(testwork, + "/pkg-internal-test.unit.ww")))); + rewritefile(strings.concat(pkg, "/internal_windows_amd64_test.ww"), + wrongtest); + + let onlywork: str = strings.concat(root, "/", labels[si], "-only-work"); + let onlyout: str = strings.concat(root, "/", labels[si], "-only-bin"); + assert(os.mkdir(onlywork, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let onlyav: []str = [driver(stages[si]), "build", "-w", onlywork, + "-o", onlyout, wrongonly]; + runcommandenv(root, strings.concat("platform-only-", labels[si]), onlyav, + env, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stderr, strings.concat("ww: ", wrongonly, + ": directory contains no WW package sources\n"))); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(readfile(linkertrace).len == 0); + assert(!os.exists(onlyout)); + assert(!os.exists(strings.concat(onlywork, "/.wwtool.stamp"))); + + let orderwork: str = strings.concat(root, "/", labels[si], + "-order-work"); + assert(os.mkdir(orderwork, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let orderav: []str = [driver(stages[si]), "build", "-w", orderwork, + "-o", strings.concat(root, "/", labels[si], "-order.a"), order]; + runcommandenv(root, strings.concat("platform-order-", labels[si]), + orderav, env, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stderr, strings.concat("ww: ", order, + "/a_linux.ww: @test declaration outside *_test.ww\n"))); + assert(readfile(compilertrace).len == 0); + assert(readfile(assemblertrace).len == 0); + assert(readfile(linkertrace).len == 0); + assert(!os.exists(strings.concat(orderwork, "/.wwtool.stamp"))); + + let recursivework: str = strings.concat(root, "/", labels[si], + "-recursive-work"); + let recursiveout: str = strings.concat(root, "/", labels[si], + "-recursive-out"); + assert(os.mkdir(recursivework, 448i32) == 0); + assert(os.mkdir(recursiveout, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let recursiveav: []str = [driver(stages[si]), "build", "-w", + recursivework, "-I", source, "-o", strings.concat(recursiveout, + "/"), strings.concat(source, "/...")]; + runcommandenv(root, strings.concat("platform-recursive-build-", + labels[si]), recursiveav, env, + (180i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(!has(readfile(compilertrace), "wrongonly")); + assert(!has(readfile(compilertrace), "dep.wrong")); + let recbin: str = strings.concat(recursiveout, "/app"); + let recrun: []str = [recbin]; + runcommand(root, strings.concat("platform-recursive-run-", labels[si]), + recrun, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 12); + assert(same(binref, readfile(recbin))); + + let recursivetestwork: str = strings.concat(root, "/", labels[si], + "-recursive-test-work"); + assert(os.mkdir(recursivetestwork, 448i32) == 0); + rewritefile(compilertrace, ""); rewritefile(assemblertrace, ""); + rewritefile(linkertrace, ""); + let recursivetestav: []str = [driver(stages[si]), "test", "-w", + recursivetestwork, "-I", source, strings.concat(source, "/...")]; + runcommandenv(root, strings.concat("platform-recursive-test-", + labels[si]), recursivetestav, env, + (240i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "selected_internal_pair ... ok\n")); + assert(has(out.stdout, "selected_external_pair ... ok\n")); + assert(has(out.stdout, strings.concat("? ", notests, + " [no tests]\n"))); + assert(!has(out.stdout, "wrong_target_test")); + assert(!has(out.stderr, "only_windows")); + assert(!has(readfile(compilertrace), "dep.wrong")); + if (si == 0) { + recursiveoutref = strings.dup(out.stdout); + recursiveerrref = strings.dup(out.stderr); + } else { + assert(same(recursiveoutref, out.stdout)); + assert(same(recursiveerrref, out.stderr)); + }; + si += 1; + }; + clean(root); +}; + // Invalid @test attribute shapes reject at build with stable text on // BOTH frontends (the -T synth checker owns them; the fixture corpus // cannot reach -T, so these rows live here). Fragments only — the