Specify dependencies should be built as dynamic or static

Currently, only the library author defines how the library should be built. This is not good because the consumers of the package should define how they want their dependencies to be built since they are used in different ways. Sometimes, they are pulled directly in the app target and other times they are embedded in other packages.

Library authors are forced to expose this option to consumers by doing something like this:

products: [
     .library(name: "Kingfisher", targets: ["Kingfisher"]),
     .library(name: "KingfisherDynamic", type: .dynamic, targets: ["Kingfisher"]),
     .library(name: "KingfisherStatic", type: .static, targets: ["Kingfisher"])
]

However, the package consumer should be able to do something like this instead:

dependencies: [
    .package(url: "git@github.com:onevcat/Kingfisher.git", type: .dynamic, .upToNextMinor(from: "5.13.3"))
]

Does this make sense or am I missing something?

1 Like

.dynamic and .static should only be used if the library is only capable of functioning that way, or if the product is intended as the very top‚Äźlevel thing. An example would be SwiftPM needing its PackageDescription library to generate a separate dynamic library so that it can be linked into other things at run time.

The duplicate entries in your example cannot co‚Äźexist at runtime, and hence invite trouble by letting the package graph decide what to do.

The right thing to do here is to declare only the automatic one. Any client that specifically cares should create their own package which links however many dependencies they want into an explicitly .dynamic or .static library of their own.

1 Like

This is a good solution. I needed to use a package in a project that has both a command line app (which needed it static) and a bunch of frameworks and SwiftUI app (that needed it dynamic). This solution of two targets works great. I agree that it would make more sense to allow the consumer to decide, but if you control the package, this works well.

Xcode seems to always choose to link statically - any automatic heuristics are broken and have never worked, see this: How to link dynamically

There is also no way for a user to manually change the import to dynamic from static.

I would in fact advise that you add a dynamic library to your module so the consumer can choose to link once, especially if the target app uses multiple app extensions where each extension in-turn depends on the same library. This will increase the overall size of the app, and in case a shared framework is using the library, you'll see dozens of runtime errors about "duplicate symbols found" given both the framework plus the app is now statically linked to the swift package.

All popular swift packages at the moment offer both a static and a dynamic variant. For instance:

That is ill‚Äźadvised. A package that offers the same module both ways has two distinct, incompatible products. If a client‚Äôs package graph ends up in a diamond shape with your package at the bottom of the diamond, everything will break, and he will blame it on you.

If his IDE lacks a way to control the linking directly, the top‚Äźlevel user should create his own package with a single .dynamic library, which in turn depends (statically) on all nodes that are common to the various executable components he plans to produce. Then each executable should depend solely on that library instead.

A package that offers the same module both ways has two distinct, incompatible products

Sure - but that's a good thing since both products are differently named (one MyLibrary and another MyLibrary-dynamic presumably). When you import a package, you have to specify what product (type) you're importing. Even if you weren't using an IDE, you would specify the product(s) you wish to use from the package. A swift package is perfectly capable of offering (legitimately) multiple products. In this particular instances, the same product is being offered in two different flavors: static and dynamic.

I don't see how a client's package graph would end up in a diamond shape?

In addition, this is no different to a traditional Framework that offers the same library in two flavors: static and dynamic (i.e the project produces a static library as well as a .framework). This has been common practice for framework developers for as long as frameworks have been around.

Sometimes it makes sense to link statically (such as a streamlined library with just code) while other times a dynamic library based on the same code makes sense given it which would allow you to bundle resources such as images / audio files etc that you just cannot with static libraries. It would be up to the consumer to pick one from the two depending on their needs.

There's nothing wrong in providing two variants of your library in my view.

Coming back to the OP, the following is what I'd argue against though (so in part I agree with @SDGGiesbrecht ) - drop .library(name: "Kingfisher", targets: ["Kingfisher"]), given you're providing two separate types already. Here you're confusing Xcode (as @SDGGiesbrecht pointed out)

products: [
     .library(name: "Kingfisher", targets: ["Kingfisher"]),
     .library(name: "KingfisherDynamic", type: .dynamic, targets: ["Kingfisher"]),
     .library(name: "KingfisherStatic", type: .static, targets: ["Kingfisher"])
]

Instead it should be:

products: [
     .library(name: "Kingfisher", type: .static, targets: ["Kingfisher"]),
     .library(name: "KingfisherDynamic", type: .dynamic, targets: ["Kingfisher"]),
]

Unless the distinct libraries also have distinct modules, you have clashing symbols once they are used together.

Besides the library‚Äôs own modules, you also need to make sure that all its dependencies are unique to it. That can only reasonably be done if the dynamic library either has do external dependencies, or has no external clients. That is why I recommend specifying the linkage style only in a top‚Äźlevel project.

Of course. But that is what .automatic is for.

.static is not the same as .automatic. You should only ever use .static if you actually want a .a produced to then use in some other build system. With .automatic, the IDE is allowed to bypass that part of the build, improving compiler optimization and saving both space and time.

You do not need either .static or .dynamic unless you are the top‚Äźlevel project. (Unless you plan on doing manual dynamic loading, or some other niche use.)

By the way, Apple's own example displays how they produce both static + dynamic libraries for the same package:

https://developer.apple.com/documentation/swift_packages/product

That is only intending to concisely demonstrate all the various ways of using the API. It should not be construed as a recommendation to actually vend your own products in triplicate.

We ran into this problem in SwiftPM itself. The original product was .dynamic, because it was destined to be copied into the Swift toolchain. Then third party clients wanted to use it directly, so an .automatic variant was added. Because clients did not all switch at once, the transition was a rocky period for some of their transitive dependencies, and involved a lot of forking and patching to remove the distinction. SwiftPM needed to be that way because of the toolchain builds, but unless you have a good external reason like that, that sort of mess is best avoided by simply using .automatic everywhere but the very top.

.static is not the same as .automatic. You should only ever use .static if you actually want a .a produced to then use in some other build system.

Got it - thank you - so not specifying static essentially skips producing a .a and helps with compiler optimizations; linking without having to first produce a .a. :+1:

You do not need either .static or .dynamic unless you are the top‚Äźlevel project.

I understand what you're saying, I suppose given how Xcode is currently unable to link dynamically (or rather allow users to manually link dynamically), it seems to be more convenient to simply produce a dynamic variant as a library producer.

(Unless you plan on doing manual dynamic loading, or some other niche use.)

The idea, in fact, is to support such a configuration.

Having said that, I appreciate the insight. It's clearer how automatic works. The solution of using a dynamic package wrapper around a static library is what I've personally been doing with 3rd party libraries, but I find this cumbersome as Xcode (do we have a choice?) is unable to refresh dependent packages when dependencies update. There's also a matter of manually bumping versions of your wrapper package each time its dependencies change.

The convenience of having 3rd party libraries offer a dynamic variant is that you don't have to rely on any of this, especially where you're consuming the package into multiple targets within your app and wish to share a single dynamic framework / library instead.