Hive
Duplicate Symbols when Linking Static SPM Dependency into Shared Framework Target
GitHub issue · Open
What happened?
We stumbled over a very strange linking issue when (finally) trying to migrate from ancient Package enum to modern Package.swift approach leading to multiple runtime warnings like this (and subsequent casting failures and crashes):
<SPMDependency.SomeThing> is implemented in both _ and _. This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
This is somewhat similar to https://github.com/tuist/tuist/issues/6339 and maybe also https://github.com/tuist/tuist/issues/6081, but not quite the same I think.
How do we reproduce it?
- Check out https://github.com/iteracticman/tuist-double-linking
- Run App Target and observe warnings in console
- and/or
- Create archive and inspect binary using included script
The same will likely happen with any iOS app project that has
- multiple layers of dynamic framework targets, like Framework
Adepends onBdepends onC - where
Cdepends on some static, non-binary SPM library productP - and some part of
Pis used byAand/orB
Error log
<SPMDependency.SomeThing> is implemented in both _ and _. This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
macOS version
15.6.1
Tuist version
4.89.0
Xcode version
26.0 and 16.4
Hi @iteracticman! I’m Dosu and I’m helping the tuist team.
This issue is caused by static SPM libraries being linked into multiple dynamic frameworks, which leads to duplicate symbol warnings and runtime crashes when those frameworks are used together. The migration from the old Package enum to the modern Package.swift approach in Tuist gives you more flexibility, but also requires more explicit management of how dependencies are linked.
Tuist currently does not automatically deduplicate or propagate product types for nested SPM dependencies, so static libraries can end up linked in multiple places in your dependency graph. This is especially problematic in multi-layered dynamic frameworks where a static SPM product is used by more than one layer. The result is the runtime warning and casting failures you’re seeing.
Workarounds include: explicitly setting the product type for all relevant SPM dependencies (including nested ones) to .framework (dynamic) in your PackageSettings, or restructuring your dependency graph to avoid sharing static SPM libraries across multiple dynamic frameworks. You may also need to ensure that all transitive dependencies are explicitly declared in your Tuist target dependencies, as Tuist does not support implicit imports and will not resolve them recursively for you [source 1] [source 2].
There is ongoing discussion about making this more automatic in Tuist, but for now, careful manual configuration is required to avoid these duplicate symbol issues. If you use custom xcconfig files, make sure they include all necessary search paths and linker flags, as missing settings can also cause or exacerbate linking problems [source 3].
To reply, just mention @dosu.
Docs are dead. Just use Dosu.
If this can’t be fully solved by Tuist in a reasonable way, maybe it could at least emit warnings/errors at generation time?
I’d consider this pretty important since this is very unexpected and could lead to serious, hard to debug runtime issues for anyone trying to migrate from SPM to Tuist, expecting everything would basically work like before.
Tuist already has generation-time linting that emits warnings when static products (like SPM libraries) are linked from multiple targets, since this can cause unwanted side effects or duplicate symbols. This is handled by the StaticProductsGraphLinter and related linting infrastructure, which runs during project generation and can flag problematic static dependencies before you hit runtime issues. You can see this in action with warnings about SwiftSyntax or other static targets being linked from multiple places [source].
Currently, these warnings are focused on certain scenarios (like macros or SwiftSyntax), but the mechanism could be extended to cover more cases—such as static SPM dependencies that are linked into multiple dynamic frameworks, which is the root of your issue. The linting system is modular and already checks for duplicated product names and other dependency graph problems at generation time [source].
So, while Tuist doesn’t yet emit a specific warning for your exact scenario, the infrastructure is there and could be enhanced to catch these duplicate symbol/static SPM dependency issues at generation time. This would help prevent the kind of hard-to-debug runtime problems you described, especially for teams migrating from SPM to Tuist who expect similar safety guarantees.
To reply, just mention @dosu.
Docs are dead. Just use Dosu.
If this can’t be fully solved by Tuist in a reasonable way, maybe it could at least emit warnings/errors at generation time?
I’d consider this pretty important since this is very unexpected and could lead to serious, hard to debug runtime issues for anyone trying to migrate from SPM to Tuist, expecting everything would basically work like before.
@iteracticman the graph that you are trying to declare is not valid, and the warning is connected to that. We can’t be more specific about what the impact of that is because we don’t know at generation time. It could be your app is just larger, or it could be that the app blows up. What would you expect Tuist to do in this case?
Hmm, maybe I am not fully understanding the problem, but I thought it should be relatively easy to detect this (potential) issue at generation-time. It happens whenever a framework depends on a framework that depends on a static SPM library. (Which btw I would have expected to be perfectly valid, especially since it works perfectly fine when using native SPM).
@iteracticman I tried the repo linked above with tuist 4.107.2 and I don’t get any issues neither at generation time nor at runtime. Could you try to reproduce it with that version?