Swift packages: Sharing code between libraries (while exposing some APIs from the shared code)

Hello :wave:

How should I share code between modules (targets in Swift package terms) defined in Package.swift while still exposing APIs from the shared code to library users? Another way to phrase this question is: How to share internal API in module with specific other modules (other libraries) but not library users.

For example, say I have 2 libraries, Human and Dog, and users only need 1 of them (or use both if they want). I came up with 2 approaches:

  • I could make another library, Core, which contains public APIs that should be shared to library users (of Human and Dog), and use a separate internal library Internal.
    • This prevents setting access control for entities (methods, enums, classes, etc) in each file separately, to avoid overexposing things in Core which might not be able to go into Internal. This is because I always need to set the entities to public for access in Human or Dog, meaning they'll be public to library users too.
    • Library users who want to use library Human will also have to know to import both Human and Core to their Xcode project/ source files.
  • Explicitly add the shared code directories to both targets instead of 1, using target(sources:) documented here.

I think the 2nd option is better, but perhaps there is a better option I don't know about?

The reason why they need to be in separate modules is because one (Human) depends on a rather large dependency which should not be required for Dog.

Welp, my preferred option 2 throws a build error:

target 'Library Human' has sources overlapping sources:
lists all the source files that are shared between Library Human and Dog

So my current setup is 3 libraries: Animal, Human and Core exposing public and internal API details, which is not good, since library users will be able to access them.

I've also posted this on Stack Overflow. Feel free to answer on Stack Overflow as well (to get reputation :sweat_smile:), otherwise I will update it.

This is deliberately prohibited because it can break everything for a C target. For Swift it could theoretically be made legal, but portions of the binary can end up needlessly duplicated, so it is still generally a bad idea. If absolutely necessary, I think you can trick SwiftPM using symlinks.

These are covered by the incomplete experimental features @_spi and @_exported (which are nonetheless mostly functional). You can learn about them by searching for them in the source code and on the forums, and I am sure any help towards completing them would be welcome.

1 Like

This is not possible in Swift. The only way to share APIs accross module boundaries is to make them public.

1 Like

If absolutely necessary, I think you can trick SwiftPM using symlinks.

I tried and that works, thanks! Unfortunately using symlinks was a bit of a mess.