In Swift Package Manager, currently targets can depend on other targets in the same package, and on products in packages their package depends on.
There are cases in which it would make sense to allow a target to depend on a product in the same package (examples are at the end of the pitch). I propose we make this possible. The package format allows the syntax already, but leads to errors if a target references a product in the same package.
Here is an example of how a changed Package.swift
generated by swift-package init
could look like:
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Example",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Example",
targets: ["Example"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets or products in this package, and on products in packages this package depends on.
.target(
name: "Example",
dependencies: []),
.testTarget(
name: "ExampleTests",
dependencies: [.product(name: "Example", package: "Example")]),
]
)
In the manifest above (you may have to scroll a bit to see it), the comment on target dependencies changed to "Targets can depend on other targets or products in this package", and the test target depends on the product, not the target to illustrate how it would look like.
Example Usages
Example 1: Splitting interface and implementation into separate products
Example: A package Authorization
with two targets.
- One target (
Authorization
) specifies a protocolAuthorization
to be used by other packages of the app. - The second target (
AppAuthorization
) contains a type conforming to the protocolAuthorization
, only to be used in the app itself.
What I can do currently is have a package 'Authorization' with two libraries AppAuthorization
and Authorization
which contain the respective targets. The target AppAuthorization
dependends on the target Authorization
.
This works, however in the case of dynamic libraries the code in Authorization
is duplicated in two libraries. To prevent that, I could move the libraries into separate packages, but semantically they belong together.
Example 2: Features for an app family
Example: A local package "Features" which contains code for features of an app family (Ticketing
, Account
, Payment
, Routing
, âŚ). Each app of the family can pick the features needed to build the app.
To only have code for the features of each app inside the binary, we can split the features into targets. Now we are facing a similar problem as before: Let's say Ticketing
requires Payment
, Payment
requires Account
, and Routing
needs Account
to store favorite routes.
We could create libraries for Ticketing
, Account
, Payment
, Routing
, in case of dynamic linking an app that contains Ticketing
, Account
, and Routing
would contain binary for Account
in two libraries.
We could move all features into separate packages, but then we would end up with lots of small packages, each containing only one product.