diff --git a/Sources/SakeKit/WineBuilder.swift b/Sources/SakeKit/WineBuilder.swift index 7b015dd..b00ae6f 100644 --- a/Sources/SakeKit/WineBuilder.swift +++ b/Sources/SakeKit/WineBuilder.swift @@ -4,6 +4,7 @@ public enum WinePhase: String, Sendable { case patch case configure case sonames + case headers case make case install case verify @@ -223,6 +224,17 @@ public struct WineBuilder: Sendable { onPhase(.sonames) try pinSonames(log: log) + // A make of its own, before anything is compiled: makedep does not follow + // `#include "x.idl"` when it works out what a generated header includes, so some + // objects miss a dependency on headers theirs include -- actxprxy_mshtml_p.o on + // include/exdisp.h -- and -j can compile one first. Every header it misses is in + // include/. See docs/wine-build.md. + onPhase(.headers) + try await runPhase( + .headers, make, ["-j\(jobs)", "include/all"], + environment: environment, log: log, onOutput: onOutput + ) + onPhase(.make) try await runPhase(.make, make, ["-j\(jobs)"], environment: environment, log: log, onOutput: onOutput) diff --git a/Tests/SakeKitTests/WineTests.swift b/Tests/SakeKitTests/WineTests.swift index e56eb8a..776f60a 100644 --- a/Tests/SakeKitTests/WineTests.swift +++ b/Tests/SakeKitTests/WineTests.swift @@ -103,7 +103,10 @@ private func makeFakeTree( HEADER cat > Makefile <<'MAKEFILE' all: + \t@test -f include/generated || { echo 'compiled before include/ was generated'; exit 1; } \t@echo building wine + include/all: + \t@touch include/generated install: \t@mkdir -p $$(cat prefix)/bin $$(cat prefix)/lib/wine/x86_64-unix \t@touch $$(cat prefix)/bin/wine @@ -194,11 +197,28 @@ private func failure(in events: [WineEvent]) -> (reason: String, log: URL?)? { let phases = events.compactMap { event -> WinePhase? in if case .phase(let phase) = event { phase } else { nil } } - #expect(phases == [.patch, .configure, .sonames, .make, .install, .verify]) + #expect(phases == [.patch, .configure, .sonames, .headers, .make, .install, .verify]) #expect(events.contains(.installed(version: "Wine version 11.0"))) #expect(builder.isBuilt) } +@Test func includeIsGeneratedByAMakeOfItsOwnBeforeAnythingIsCompiled() async throws { + let paths = temporaryRoot() + defer { remove(paths) } + try makeFakeTree(in: paths) + + let builder = builder(in: paths) + let events = await collect(builder.build()) + + // The fake `all` refuses to run until `include/all` has, which is the order a real + // build needs. See docs/wine-build.md. + #expect(failure(in: events) == nil) + let log = try String(contentsOf: builder.logURL, encoding: .utf8) + #expect(log.contains( + "=== headers /usr/bin/make -j\(ProcessInfo.processInfo.activeProcessorCount) include/all\n" + )) +} + @Test func wineIsBuiltOutOfTreeSoTheOnlyCopyOfItsSourceStaysClean() async throws { let paths = temporaryRoot() defer { remove(paths) } diff --git a/docs/wine-build.md b/docs/wine-build.md index f43eded..46f622f 100644 --- a/docs/wine-build.md +++ b/docs/wine-build.md @@ -149,6 +149,29 @@ clang is *not* required; the Mach-O side builds with stock Apple clang. `DYLD_LIBRARY_PATH`, and that does not reach Wine's child processes. sake rewrites the four in `include/config.h` to `@loader_path`-relative paths between configure and make; see `layout.md`. +- **`include/` is generated by a make of its own before anything else is compiled.** + makedep works out what a widl-generated header includes from its IDL's own `import` and + `cpp_quote` lines, and drops an `#include "x.idl"` there (`parse_file` in + `tools/makedep.c`), so whatever `x.idl` imports never becomes a dependency of the objects + that include the header. Under `-j` one of them can be compiled before that header exists. + + Reported from another machine on 2026-09-27, as + `dlls/actxprxy/actxprxy_mshtml.h:3287:10: fatal error: 'exdisp.h' file not found`, and not + reproduced on this one. `actxprxy_mshtml.idl` includes `shdeprecated.idl`, which imports + `exdisp.idl`. That file is CrossOver's and has no history upstream, but upstream's makedep + drops the same lines as of 2026-09-28, and `actxprxy_shobjidl.idl`, which upstream has too, + is missing six headers the same way in CrossOver's copy. + + Measured in sake on 2026-09-28, against CrossOver 26.3.0's tree. It is not a matter of + parallelism: on a freshly configured tree, `make -j1` of + `dlls/actxprxy/i386-windows/actxprxy_mshtml_p.o` alone fails on `dimm.h`. A whole build + usually gets away with it because `libs/uuid`, which `activeds.dll` links and whose + `uuid.c` includes nearly every header, needs them too and is reached earlier: a serial dry + run generates `exdisp.h` 400 lines before the compile, and this machine's build log of + 2026-09-20 does it 53,000 lines before. So `-j1` is no fix either; it passes by the same + luck. 105 IDL files in the tree include another, the headers makedep drops for them come + to 38, all of them in `include/`, and `include/all` generates all 38. The phase took 6.9s + of a 5m33s build on ten cores. - **Nothing x86_64 may exec an xcode-select shim.** `/usr/bin/clang`, `/usr/bin/m4` and their neighbours are universal shims that `dlopen` an arm64-only `libxcrun.dylib`, so started x86_64 they die. This bit in three unrelated places before it was recognised as one