Direct & indirect static libs dependencies with SPM + Xcode

Hi,

I'm trying to move towards building parts of large projects with SPM, progressively replacing a number of xcodeproj for intermediary libs. The rationale is that SPM is easier to maintain when compared to state-full Xcode projects & build settings (that we often don't need).

Typically we build an app through an Xcode workspace that references a number of Xcode projects. One of the projects is the "app" project, and other ones are libraries. We use static libraries targets with implicit dependencies that are handled by Xcode (as opposed to explicit Xcode project embedding, which we avoid because it does not scale well on large projects). Conceptually, it looks like this:

W.xcworkspace/
    L1.xcodeproj/L1.target (static lib)
    L2.xcodeproj/L2.target (static lib)
    A.xcodeproj/A.target (app)

In this example, L1 has no dependency, L2 depends on L1, A depends on L1 (both directly and indirectly) and L2 (directly). To efficiently build under this situation, we create a "stub" library in L1.xcodeproj, L1-stub that generates a mostly empty static lib, L1-stub.a. L1-stub is made so that it will build L1 (but not link against it) through Xcode dependency pane (only for same project targets). L2 is set to link against L1-stub (across project with implicit deps) in a way to trigger L1 build before itself (so that it can find headers / modules) instead of linking directly against L2, which would create duplicate symbols when A links against L1 & L2. We have used that technique over the years with tens of libs/xcodeprojs with complex dependency graphs, both in Swift and Obj-C. We prefer static libs because of load time efficiency, keeping the dynamic ones (or frameworks) for when it is actually needed (shared code between app and an extension, for instance). Dependency graph is well defined & everything builds well and efficiently through parallelization.

Now I'm trying to progressively replace some of the L1.xcodeproj with SPM packages.

W.xcworkspace/
    L1/Package.swift (static lib)
    L2.xcodeproj/L2.target (static lib)
    A.xcodeproj/A.target (app)

I still need a stub lib for the same reason as expressed above. The problem I'm experiencing is that when defining L1, and L1-stub (that depends on L1), and adding L1-stub to the L2 target link phase in Xcode, it also links (statically) against L1 when building L2. L1-stub seems to propagate L1 dependency information that Xcode uses. That leads to duplicate symbols when A links against L1 and L2.

(by the way, this is very similar to WWDC'19 408, at time 21:28, but I'm mixing an Xcode target with an SPM one)

It appears to be limitations of both systems (Xcode / SPM) that prevent this from working.

  • Xcode would need a way to define dependencies without linking
  • SPM would need a way to define a dependency that does not propagate linking information.

My understanding is that it could be correctly handled by using SPM only, or Xcode only (as we did before). But converting everything to SPM only in one step is not possible because not everything is covered with SPM (resources, app bundles, etc), and the projects are just too large.

Is this a known problem, or am I not seeing a more obvious way of handling the situation? Are there possible workarounds for this? It certainly looks very promising to make things a lot more maintainable in the future, and I sure hope to be able to start using it this year.

Thanks.

PS: I'd also like to move away from using stub libs like this, but AFAIK, this is the only way to define dependencies across projects in an Xcode workspace. If there are other options, please share.