Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sources/SakeKit/WineBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum WinePhase: String, Sendable {
case patch
case configure
case sonames
case headers
case make
case install
case verify
Expand Down Expand Up @@ -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)

Expand Down
22 changes: 21 additions & 1 deletion Tests/SakeKitTests/WineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) }
Expand Down
23 changes: 23 additions & 0 deletions docs/wine-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down