Hive
XcodeProj integration: dynamic xcframework inside SPM package not embedded in app bundle
GitHub issue · Open
Description
When an SPM package’s library product wraps both a source target and a binary target (where the binary is a dynamic xcframework), Tuist’s XcodeProj-based integration links the xcframework but never embeds it in the app bundle. This causes a runtime crash:
Library not loaded: @rpath/DotLottiePlayer.framework/DotLottiePlayer
Referenced from: .../NotPink.app/Frameworks/DesignSystem.framework/DesignSystem
SPM package structure (dotlottie-ios)
// Package.swift
.library(name: "DotLottie", targets: ["DotLottie", "DotLottiePlayer"]),
.target(name: "DotLottie", dependencies: ["DotLottiePlayer"]),
.binaryTarget(name: "DotLottiePlayer", path: "./Sources/DotLottieCore/DotLottiePlayer.xcframework")
The xcframework contains a dynamic library (confirmed via file):
DotLottiePlayer: Mach-O 64-bit dynamically linked shared library arm64
Tuist configuration (XcodeProj-based integration)
// Tuist/Package.swift
let packageSettings = PackageSettings(
productTypes: [
// DotLottie is NOT listed — defaults to .staticFramework
]
)
// Package dependency
.package(url: "https://github.com/LottieFiles/dotlottie-ios", exact: "0.15.2"),
// Packages/Core/Project.swift — DesignSystem depends on DotLottie
.module(name: "DesignSystem", in: "Core", dependencies: [
.external(name: "DotLottie"),
])
The App target depends on DesignSystem transitively.
What happens
- Tuist correctly links
DotLottiePlayer.xcframeworkin the DesignSystem target - Tuist does not embed
DotLottiePlayer.xcframeworkin the App target - At runtime,
dyldcannot findDotLottiePlayer.framework→ crash
During tuist generate, Tuist warns:
Xcframework 'DotLottiePlayer.xcframework' has been linked from target 'DesignSystem' and target 'DotLottie',
it is a static product so may introduce unwanted side effects.
The xcframework is not static — it’s dynamic. Tuist misclassifies it.
What I tried (none of these embed the xcframework)
| Approach | Result |
|---|---|
"DotLottie": .framework in productTypes |
Embeds DotLottie.framework but NOT DotLottiePlayer.xcframework |
"DotLottiePlayer": .framework in productTypes |
Ignored — not a product name |
.external(name: "DotLottie") on App target |
Links xcframework but doesn’t embed it |
.xcframework(path:) on App target |
Links xcframework but doesn’t embed it |
tuist clean + fresh install/generate |
No change |
Why it works for other xcframeworks
Packages like powersync-sqlite-core-swift work because their library product wraps only a binary target:
.library(name: "PowerSyncSQLiteCore", targets: ["PowerSyncSQLiteCore"])
.binaryTarget(name: "PowerSyncSQLiteCore", url: "...")
Tuist correctly detects this as a binary xcframework and embeds it. The issue is specific to packages where a library product bundles both source and binary targets.
Expected behavior
Tuist should inspect the Mach-O type of the binary inside the xcframework. If it’s dynamic (MH_DYLIB), the xcframework should be embedded in the app bundle regardless of how it’s referenced (direct or transitive dependency).
Environment
- Tuist: 4.180.0
- Xcode: 26.4
- macOS: 26.4.1
Related issues
- #6638 — nested packages don’t respect product type of parent
- #6683 — SPM packages with dynamic libraries not embedded (native SPM integration)
- PR #9356 — stopped embedding static xcframeworks, but misclassifies dynamic ones as static
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.
Hi @thomasflad! I’m Dosu and I’m helping the tuist team.
This is a well-documented issue. The root cause is in PackageInfoMapper.swift, where Tuist’s product type resolution follows a static-first rule: when an SPM library product contains both source and binary targets, if any target resolves as static, the entire product is treated as static [1]. This means the dynamic DotLottiePlayer.xcframework gets treated as part of a static product, and since PR #9356 removed embedding of static xcframeworks entirely [2], it’s never embedded.
Notably, the xcframework’s actual Mach-O type is correctly detected as dynamic by XCFrameworkMetadataProvider [3] — the misclassification happens at the product level, not the binary inspection level.
This is a genuine bug in how mixed source/binary SPM products are handled. For debugging this further in your specific project, I’d recommend installing this skill following the steps in this repo and using a coding agent (Claude, Codex, etc.) to inspect the generated project and confirm the exact embedding state. If that doesn’t surface a workaround, providing a minimal reproducible project would help the maintainers fix this.
For a workaround, you could try forking the dotlottie-ios package and splitting it into two separate library products — one for the binary target and one for the source target — so the binary product is treated as a standalone xcframework (which is why packages like powersync-sqlite-core-swift work correctly).
If this needs a longer discussion on a potential fix, https://community.tuist.dev (under the “Help” category) would be the right place to continue the conversation.
To reply, just mention @dosu.
Docs are dead. Just use Dosu.