This is essentially where I stand, and raised a similar concern about @package(...) import
s for SwiftPM scripting: Pre-Pitch: `@package` argument syntax. It feels like a layering violation to use "package" in this sense in the frontend.
Now, we could hand-wave it/rationalize it by saying that we're defining a new concept in the frontend for a group of related modules and we're calling it a "package", and SwiftPM will treat all the targets in the same SPM package as being in the same "package access scope" and other build systems can define a package however they want. That feels close to sufficient.
But, then we have to weigh @taylorswift's concern that you emphasized about whether targets like examples should be in the same "package access scope". I think this is a valid concern and more likely than it seems. Take swift-format for example; we might benefit from a "package access scope" among our internal modules, but we also have an explicitly defined public API surface. The swift-format
executable target should only be able to use the public APIs from the SwiftFormat
and SwiftFormatConfiguration
modules; it should not have access to any non-public APIs and I wouldn't want to accidentally use one by having it subsumed into the same access scope.
So, SPM would need a way to semantically delineate certain kinds of targets. Could we say that executable targets don't belong in the same package access scope? I'm not sure; answering that "no" satisfies the swift-format case, but there may be other use cases where a tool would want to use APIs from the same package but not make them public. The same goes for testing support targets; maybe you want something that can be imported by all your test targets in the same package but isn't public to external clients of your package or to other non-test targets in the same package.
Then, if we instead need a way to let certain targets opt-out of this kind of access, the once simple analogy of "anything in the same SPM package can use package
decls" falls apart and it becomes a lot harder to explain to users and to reason about.
I think the reason folks are reaching for @_spi
as a comparison is because it feels like what this proposal does is create an implicit, automatic, unutterable @_spi
among a group of modules, but we're treating it as a separate concept (and I think the implementation uses a different concept instead of building on SPI). Does it make sense to align these in some fashion? Let's ignore the name "package" for the time being and steal John's "frog" instead. If we said that -frog-name
did two things:
- Treat decls like
frog class
as if they were declared@_spi(«generated frog SPI name») public class
- Treat
import X
as@_spi(«generated frog SPI name») import X
ifX
was compiled with the same-frog-name
as the importing module
Does that reveal any possible improvements/directions?
If we avoid the package
name, maybe we could have SPM treat all targets in the same package as having the same -frog-name
by default, but also provide a way for targets in the package manifest to provide a custom frogName:
instead? And if any target in the manifest provides a custom frogName:
then the default behavior goes away and you have to be explicit throughout. So swift-format could look something like this:
let package = Package(
.executableTarget("swift-format"),
.target("SwiftFormat", frogName: "SwiftFormat"),
.target("SwiftFormatConfiguration", frogName: "SwiftFormat"),
.target("SwiftFormatRules", frogName: "SwiftFormat"),
.target("SwiftFormatPrettyPrint", frogName: "SwiftFormat")
)
That might strike a balance where we make the easy thing easy and obvious while still making the more interesting cases possible. And folks would still have explicit @_spi
for those more fine-grained cases where the access boundaries aren't clean partitions around groups of modules.
But, we would still need to figure out what to call this frog
; under a design like the above, I don't think package
is suitable.