Hive Hive
Sign in

XcodeProj integration: dynamic xcframework inside SPM package not embedded in app bundle

GitHub issue · Open

Metadata
Source
tuist/tuist #10296
Updated
Jun 11, 2026
Domains
Generated projects
Details

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

  1. Tuist correctly links DotLottiePlayer.xcframework in the DesignSystem target
  2. Tuist does not embed DotLottiePlayer.xcframework in the App target
  3. At runtime, dyld cannot find DotLottiePlayer.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
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
L
linear[bot] Apr 16, 2026
D
dosubot[bot] Apr 16, 2026

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.

Leave Feedback Ask Dosu about tuist Share Dosu with your team