Hiding internal product dependencies from consumers in a Swift package

Swift Package Manager defines targets and products separately: targets constitute Swift modules, and products constitute targets that are exposed to other packages.

This implies that targets that are not part of a product are not exposed. Unfortunately, that doesn’t seem to be the case in Swift 5.2: consumers are able to import any target that is a dependency of a product. This is extremely inconvenient for sharing code between targets, especially when it comes to extensions. Even Apple’s own packages, like Swift Numerics and swift-format, suffer from this shortcoming.

Is there a way around this? I do not want to pollute the namespace of everything downstream. Personally, I think declarations that are not inside direct dependencies of packages should not be accessible outside the package at all, except through runtime behavior.

7 Likes

Not at the moment. This thread has a little more information:

2 Likes

I think that actually accomplishes a lot of what I want. Not everything, and private attributes aren’t ideal, but a lot.

Unfortunately this hasn’t been solved from a runtime perspective, AFAIK. All libraries that gets linked into an executable would share a flat name-space within the same process. This makes it impossible to have two different instances of the same library to be linked into the same process. In fact, if you have the same name defined in two different libraries, you’ll get either a link-time or a run-time warning like “... x is defined in y and z, which one will be called is undefined ...”

Suppose you have a library Foo and it links to Bar version 1.1, then the entire process at runtime would see the symbols exported by Bar 1.1. If the process loads Bar 2.0 at runtime, then most of the symbols exported by Bar 1.1 would probably be overwritten by Bar 2.0 – except for those that were removed by Bar 2.0 and thus not implemented.

So these “internal library dependencies” problem needs to be solved upstream and not in the scope of a package manager. Likely at the ABI level.

Otherwise if you have the luxury of multiple processes (i.e. XPC Services), you could implement most of your functionality as a server process and thus the library is merely a wrapper to IPC to that process. That way the server process can have as many “internal dependencies” as required.

2 Likes

Coming back to this, would some form of runtime name-mangling be possible? If you combined the name of a hidden module with the names of every module that can use it, you’d get a new unique name.

Name mangling is not necessary. Such transitive dependencies were never supposed to be importable. It was only possible because of an accident of the layout of SwiftPM’s build directory layout and the absence of a deliberate verification step. The latter is now seeing progress: