Hive Hive
Sign in

Cached dynamic framework puts both slices of a transitive static Swift XCFramework in SWIFT_INCLUDE_PATHS, breaking simulator builds

GitHub issue · Open

Metadata
Source
tuist/tuist #14312
Updated
Oct 17, 2026
Details

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_PATHS lists 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 of tuist 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
  1. ./build-xcframework.sh (creates Deps/BinaryLib.xcframework with ios-arm64 and ios-arm64_x86_64-simulator slices)
  2. cd App && tuist install && tuist cache warm && tuist generate --no-open (Using cache binaries for the following targets: Kit)
  3. xcodebuild build -workspace App.xcworkspace -scheme App -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest'
  4. 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.

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments

No GitHub comments yet.