Swift Packages in multiple targets results in “This will result in duplication of library code.” errors

The error looks correct to me:

Swift package target 'A' is linked as a static library by 'C' and 'A-stub'. This will result in duplication of library code.

According to the manifest, the "A-stub" target does declare a dependency on "A". Is this right: https://github.com/rsebbe/Bug_Xcode12_DoubleLinkError/blob/master/A/Package.swift#L10?

The difference between 11.5 and 12.0 is that we amended the diagnostic to also detect duplicated linkage of package targets. In 11.5, we were only looking at package products.

Oh I see, what you're saying, we're incorrectly interpreting the dependency that libB.a has as linking statically.

We've been fighting with the tools (Xcode) over the last 10+ years with all kinds of workarounds, because although these are 2 different concepts (dependency and linking), they really want hard to make it a single concept. Dynamic libs have further masked this problem, but they are otherwise not optimal (load time + typically not stripped to just the symbols needed).

This (basic, common, useful) example, that Xcode targets are unable to implement, shows it:

App depends_on libB
libB depends_on libA
App (possibly) depends_on libA
App links libB
App links libA

I think SPM wants to allow that, but there remains friction on Xcode side. We sure hope to move the 5.3 (that now support resources) and remove many our "stub" workaround targets. + there is a lot less state in SPM targets than in Xcode's, which means easier maintenance.

2 Likes

I think setting DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC may reveal the root cause:

/warning:1:1: file: /Users/neonacho1/Library/Developer/Xcode/DerivedData/W1-drxqtaioopukohfdphknquwsadbh/Build/Products/Debug-iphonesimulator/PackageFrameworks/A-stub.framework/A-stub is a dynamic library, not added to the static library

So the build system thinks the A-stub dynamic product is being linked, but it just doesn't happen in practice.

Another parallel example is an app being comprised of multiple targets, all of which depend on some library A.

You have this structure:

App
  - Dependency A
  - Extension A
    - Dependency A
  - Extension B
    - Dependency A

This architecture is incredibly common, more so as additional extension points become available to iOS apps. Dependency A could be an analytics library, Alamofire, or an internal framework that encapsulates business logic in a shared codebase. Whatever it is, SPM and Xcode should be able to handle this gracefully. We should end up with one copy of Dependency A, and all of the binaries should link to it.

5 Likes

yes. We use "stub" dynamic libs because we know dynamic libs are never linked into a static lib, yet correctly setup build oder and module generation.

Again, this is a workaround. I'd rather not have such stub libs at all, and just declare depends_on and link_against relationships in Xcode. The former is not possible across targets in distinct projects in an Xcode workspace, only the latter is, which is why we need these stubs in the first place.

Hopefully SPM is smarter here, and we can progressively replace Xcode lib targets with SPM, which will do the right thing.

I'm struggling with this now. Not having previews has stalled my progress. For this particular issue the DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC=YES work-around hasn't worked for me.

We have a single SPM package in our Xcode project; I'll refer to it as the "root". That root package depends on a number of other SPM packages that specify no linking; neither dynamic nor static.

Wether we mark the single root package as "static" or "dynamic" we receive the error about duplicated code when attempting to use previews. DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC=YES does nothing.

You must fix this ASAP. As it stands SPM is useless because it disables the most valuable feature of Xcode.

I think this thread is starting to get a little confusing for everyone, since we are really discussing several different problems here, which just happen to have the same outcome. To summarise:

  • There were issues with just the diagnostic which have mostly been fixed at this point, except for the problem that @rsebbe is describing.
  • There is the general problem of actual diamond-shaped linkage in projects, for which currently the only solution is manually changing your project as a workaround. We aware that this is an issue for many people and are investigating a solution.
  • There is an entirely separate issue which happens only for Xcode previews which is related to all package products being built dynamically in Xcode 12 to enable previews. It is also something we're actively investigating. There is no workaround for this, since users do not have control over how builds for preview are being done.

The DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC setting only helps in the first case, because there isn't a real problem being flagged. In all other cases, it won't actually help because the diamond-shaped linkage is real.

9 Likes

The Xcode previews issue discussed here should be resolved in Xcode 12 beta 5.

5 Likes

Confirmed that the Swift UI Preview issue is resolved for Firebase with beta 5. Details at https://github.com/firebase/firebase-ios-sdk/issues/6280. Thanks!

1 Like

Still experiencing this issue in the just-released Xcode 12 public build. Anyone have an idea how far away we are to a resolution?

With Xcode 12.0.1 the issue is the same.

@NeoNacho @Aciid We have complex project which has multiple sub projects inside workspace, where each target depends internally, but this modularization works fine with Cocoapods but not with SPM(Xcode 12/ swift 5.3), any idea when this problem would be solved?

Thanks

This is a great summary, thank you.

Could you explain the workaround for issue 2?

Thanks :slight_smile:

The general idea for the workaround is making whatever is linked statically multiple times dynamic.

In the case of package products, that is relatively straightforward, you can change the type of the library to dynamic (https://developer.apple.com/documentation/swift_packages/product/library/librarytype/dynamic). The main annoyance here is that you might not directly control this dependency, so it could require creating a fork.

If the issue happens with a package target, the solution is unfortunately quite complicated, since packages don't give control over the type of targets, they are always static. One solution is creating a framework in Xcode which bundles up the products in question to avoid having to link them twice. Another solution is to restructure the package in question so that the given target can become a product, but this will require breaking it out into its own package.

As said before, we aware that this is an issue for many people and are investigating a solution, especially because the workarounds aren't particularly easy to deploy.

9 Likes

Are there any news on this Topic? I think Swift Package Manager could be awesome but without being able to add a Dependency to multiple Frameworks I can't use it for my projects. :frowning:

Tested it with Xcode 12.3 and it's not working.

1 Like

We are still running into this problem today in Xcode 12.3.

We have a Swift Package that declares two dynamic library products:

  1. Fooable
  2. Foo

Foo depends on Fooable.

Some frameworks in our app just depend on Fooable, and this works just fine.

However when we made the app itself link to both Fooable and Foo, now compile breaks with:

"Swift package target 'Fooable' is linked as a static library by 'App' and 'Foo'. This will result in duplication of library code.

Why is Xcode statically linking to a dynamic library target...? What is the fix?

I've found two workarounds so far:

  1. If I only link the app to Foo then, the error goes away.
  2. If I turn on "DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC" then the error goes away.

But I would like to understand first of all, why this problem is happening in the first place? Considering that the Swift package itself declares both of its products as dynamic libraries, why would Xcode try to statically link anything? Is there a way to prevent it from statically linking these, and if so, how? If not, why not?

I don't want duplicated code anywhere in the app.

1 Like

@NeoNacho thanks for chiming in here. Looking forward to a desined solution.

Another solution is to restructure the package in question so that the given target can become a product, but this will require breaking it out into its own package.

Would it be possible to expand on this (or point me/us to documentation)? What is the relationship between products and targets and how would a target become a product?

1 Like

@NeoNacho thanks for all the details you've included on this thread so far. Do you happen to know what the status is on this nowadays?