Hive
Cached dynamic framework puts both slices of a transitive static Swift XCFramework in SWIFT_INCLUDE_PATHS, breaking simulator builds
GitHub issue · Open
What happened?
When a local Swift package library is turned into a dynamic framework with PackageSettings(productTypes: [...: .framework]) and is replaced by its cached XCFramework, targets that depend on it get both slices of its transitive binary dependency in SWIFT_INCLUDE_PATHS, without an SDK condition:
SWIFT_INCLUDE_PATHS = (
"$(inherited)",
"$(SRCROOT)/../Deps/BinaryLib.xcframework/ios-arm64",
"$(SRCROOT)/../Deps/BinaryLib.xcframework/ios-arm64_x86_64-simulator",
);
The binary dependency is a static Swift library XCFramework whose slices contain BinaryLib.a and BinaryLib.swiftmodule/*.swiftinterface (the same layout as GooglePlacesSwift.xcframework from googlemaps/ios-places-sdk, where we first hit this). Because the device slice comes first, a simulator build of an app that imports the binary module compiles ios-arm64/BinaryLib.swiftmodule/arm64.private.swiftinterface for the simulator target and fails.
Expected: the simulator build uses only the simulator slice, as it does when the dependency is built from source or kept static (for example, by putting the paths under SWIFT_INCLUDE_PATHS[sdk=iphoneos*] / [sdk=iphonesimulator*], as is already done for FRAMEWORK_SEARCH_PATHS).
Observations:
- Without
productTypes(the library stays a static framework), the same steps build successfully with the cached binary. - With the dynamic library built from source (no cached binary), the build also succeeds, even though
SWIFT_INCLUDE_PATHSlists both slices there too. - Workaround: keep the dynamic library out of the binary cache (a cache profile with
except:), or include it in the focused targets oftuist generate.
How do we reproduce it?
Sample project (all files below):
build-xcframework.sh
src/BinaryLib.swift
Deps/Package.swift
Deps/Sources/Kit/Kit.swift
App/Tuist.swift
App/Tuist/Package.swift
App/Project.swift
App/Sources/AppMain.swift
./build-xcframework.sh(createsDeps/BinaryLib.xcframeworkwithios-arm64andios-arm64_x86_64-simulatorslices)cd App && tuist install && tuist cache warm && tuist generate --no-open(Using cache binaries for the following targets: Kit)xcodebuild build -workspace App.xcworkspace -scheme App -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest'- The build fails with the error below.
build-xcframework.sh
#!/bin/bash
# Builds Deps/BinaryLib.xcframework: a static Swift library XCFramework with a .swiftmodule (swiftinterface)
# next to the .a in each slice, the same layout as GooglePlacesSwift.xcframework
# (ios-arm64 and ios-arm64_x86_64-simulator).
set -euo pipefail
cd "$(dirname "$0")"
rm -rf build Deps/BinaryLib.xcframework
compile() { # sdk target arch outdir
mkdir -p "$4/BinaryLib.swiftmodule"
xcrun -sdk "$1" swiftc -target "$2" -parse-as-library -swift-version 5 -module-name BinaryLib \
-emit-library -static -enable-library-evolution \
-emit-module-interface-path "$4/BinaryLib.swiftmodule/$3.swiftinterface" \
-o "$4/BinaryLib-$3.a" src/BinaryLib.swift
}
compile iphoneos arm64-apple-ios17.0 arm64 build/ios
compile iphonesimulator arm64-apple-ios17.0-simulator arm64 build/sim
compile iphonesimulator x86_64-apple-ios17.0-simulator x86_64 build/sim
mv build/ios/BinaryLib-arm64.a build/ios/BinaryLib.a
lipo -create build/sim/BinaryLib-arm64.a build/sim/BinaryLib-x86_64.a -output build/sim/BinaryLib.a
rm build/sim/BinaryLib-*.a
xcodebuild -create-xcframework -library build/ios/BinaryLib.a -library build/sim/BinaryLib.a -output Deps/BinaryLib.xcframework
cp -R build/ios/BinaryLib.swiftmodule Deps/BinaryLib.xcframework/ios-arm64/
cp -R build/sim/BinaryLib.swiftmodule Deps/BinaryLib.xcframework/ios-arm64_x86_64-simulator/
src/BinaryLib.swift
public func binaryGreeting() -> String { "hello from BinaryLib" }
Deps/Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "Deps",
platforms: [.iOS(.v17)],
products: [
.library(name: "Kit", targets: ["Kit"]),
],
targets: [
.binaryTarget(name: "BinaryLib", path: "BinaryLib.xcframework"),
.target(name: "Kit", dependencies: ["BinaryLib"]),
]
)
Deps/Sources/Kit/Kit.swift
import BinaryLib
public func kitGreeting() -> String { binaryGreeting() }
App/Tuist.swift
import ProjectDescription
let tuist = Tuist(project: .tuist(cacheOptions: .options(storages: [.local])))
App/Tuist/Package.swift
// swift-tools-version: 5.9
import PackageDescription
#if TUIST
import ProjectDescription
let packageSettings = PackageSettings(productTypes: ["Kit": .framework])
#endif
let package = Package(
name: "App",
dependencies: [
.package(path: "../../Deps"),
]
)
App/Project.swift
import ProjectDescription
let project = Project(
name: "App",
targets: [
.target(
name: "App",
destinations: .iOS,
product: .app,
bundleId: "dev.tuist.repro.App",
deploymentTargets: .iOS("17.0"),
infoPlist: .default,
sources: ["Sources/**"],
dependencies: [.external(name: "Kit")]
),
]
)
App/Sources/AppMain.swift
import BinaryLib
import Kit
import SwiftUI
@main
struct ReproApp: App {
var body: some Scene {
WindowGroup { Text(kitGreeting() + " / " + binaryGreeting()) }
}
}
Error log
The failing command compiles the device slice’s interface for the simulator:
swift-frontend -frontend -compile-module-from-interface -target arm64-apple-ios17.0-simulator ... <repro>/Deps/BinaryLib.xcframework/ios-arm64/BinaryLib.swiftmodule/arm64.private.swiftinterface ...
<unknown>:0: error: module 'Swift' was created for incompatible target arm64-apple-ios27.2-simulator: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/prebuilt-modules/27.2/Swift.swiftmodule/arm64-apple-ios-simulator.swiftmodule
** BUILD FAILED **
macOS version
27.0
Tuist version
4.212.0
Xcode version
27.2 beta 3 (27B5038j)
Generated by AI.
Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.
Start a Flight and preserve its objective, outcome, and session here.
No GitHub comments yet.