What happened?
Problem description:
We use a local SPM package added via .package(path:) and .package(product:) that exposes multiple products, each wrapping a binary target (xcframework). In its Package.swift, several targets (wrappers) depend on these binary targets, and also declare dependencies on other packages from our internal registry:
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "ExamplePackage",
platforms: [.iOS(.v13)],
products: [
.library(name: "FeatureA", targets: ["FeatureAWrapper"]),
.library(name: "FeatureB", targets: ["FeatureBWrapper"]),
.library(name: "CoreUtils", targets: ["CoreUtils"]),
],
dependencies: [
.package(id: "MyCompany.TransitiveDepA", exact: "1.2.3"),
.package(id: "MyCompany.TransitiveDepB", exact: "4.5.6"),
],
targets: [
// Wrapper depending on a binary xcframework and a registry product
.target(
name: "FeatureAWrapper",
dependencies: [
"FeatureA", // binary
.product(name: "TransitiveDepA", package: "MyCompany.TransitiveDepA")
]
),
.binaryTarget(
name: "FeatureA",
path: "Frameworks/FeatureA.xcframework"
),
// Another wrapper that depends on its own binary and (optionally) the first wrapper and another registry product
.target(
name: "FeatureBWrapper",
dependencies: [
"FeatureB", // binary
"FeatureAWrapper", // intra-package dependency
.product(name: "TransitiveDepB", package: "MyCompany.TransitiveDepB")
]
),
.binaryTarget(
name: "FeatureB",
path: "Frameworks/FeatureB.xcframework"
),
// A pure (non-binary) target also exposed as a product
.target(
name: "CoreUtils",
dependencies: []
)
]
)
When building the app with Tuist, the transitive dependencies of these binary targets are not resolved: the dependencies listed in Package.swift are not propagated to the final Xcode project and are not linked with xcframework wrappers.
Some of these transitive dependencies are used directly by the app target as well, but the linker log clearly shows a linking error for symbols that should come from the transitive dependency of the xcframework. This happens for static xcframeworks (symbols not linked into the .o), and at runtime for dynamic xcframeworks (dependency not found via @rpath).
Where it happens:
According to my research of Tuist’s source code, this happens because in resolveExternalDependencies (PackageInfoMapper.swift), only the direct product.targets are mapped for SPM products, and their dependencies are not traversed recursively. As a result, if a binary target depends on other packages, those dependencies are ignored.
How do we reproduce it?
- Create a local SPM package with multiple products in Package.swift. Each product wraps a binary target (xcframework), and some wrapper targets declare dependencies on internal registry packages.
- Add this package to your Tuist project via
.project.
- Build the project with Tuist. Observe that the transitive dependencies of the binary targets are not linked in the final Xcode project, and you get linking/runtime errors when the app uses the xcframework wrappers (even if some dependencies are used by the app target).
Error log
There is no explicit error message from Tuist, but Xcode linker output shows missing symbols for types/functions defined in the transitive dependency of the xcframework (for static). For dynamic xcframeworks, you get a runtime error about missing dependency by @rpath.
Example linker log:
ld: warning: Could not find or use auto-linked library '[NDA_MODULE]_core.a': library '[NDA_MODULE]_core.a' not found
ld: warning: Could not find or use auto-linked library '[NDA_MODULE]_chat.a': library '[NDA_MODULE]_chat.a' not found
ld: warning: Could not find or use auto-linked framework 'SwiftProtobuf': framework 'SwiftProtobuf' not found
ld: warning: Could not find or use auto-linked framework 'Testing': framework 'Testing' not found
ld: warning: Could not find or use auto-linked framework 'XCTest': framework 'XCTest' not found
Undefined symbols for architecture arm64:
"method descriptor for static SwiftProtobuf._ProtoNameProviding._protobuf_nameMap.getter : SwiftProtobuf._NameMap", referenced from:
...
"protocol descriptor for Testing.SuiteTrait", referenced from:
...
"XCTest.XCTFail(_: Swift.String, file: Swift.StaticString, line: Swift.UInt) -> ()", referenced from:
...
"_OBJC_CLASS_$_XCTestContext", referenced from:
...
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1
macOS version
15.5
Tuist version
4.55.6
Xcode version
16.4.0