Need/problem
We’ve been working on a Tuist implementation of the Package Registry to improve the resolve times. This RFC is focused on the integration at the Tuist CLI and the Xcode level with the already implemented Tuist Package Registry server.
Motivation
Package Registry is a Swift standard to allow downloading source archives for a specific dependency version instead of having deep clones.
We conform to the API contract on our server. There are a couple of things that need to happen to connect to the registry.
Detailed design
The integration looks a bit different based on the type of project at hand, so I’m dividing the detailed design into two sections.
Vanilla SwiftPM projects and Tuist projects
Since we use a vanilla Package.swift
for package resolution for Tuist projects with the XcodeProj-based integration, the setup is the same for both.
The configuration of the Package Registry is done in the .swiftpm/configuration/registries.json
(or Tuist/.swiftpm/configuration/registries.json
).
The JSON needs to roughly look like this:
{
"security": {
"default": {
"signing": {
"onUnsigned": "silentAllow",
}
},
"authentication": {
"tuist.dev": {
"loginAPIPath": "/api/accounts/{account-handle}/registry/swift/login",
"type": "token"
}
},
"registries": {
"[default]": {
"supportsAvailability": false,
"url": "https://tuist.dev/api/accounts/{account-handle}/registry/swift"
}
},
"version": 1
}
Note the path is dependent on the account that you want to authenticate with.
I’m proposing to add a new command tuist registry setup <account-handle>
that will generate the JSON in the right path for the right account. This command would be run once for a given project and then checked in with .git
to share the configuration with the rest of the team.
Before resolving packages, developers need to authenticate with the registry. The swift
command for this is swift package-registry login <url> --token "Your token"
. The token is then stored in the user’s keychain. We can also specify the --netrc
file, but as per this that can’t be used when Xcode resolves the packages. I’d try to use the keychain but using a keychain could be problematic on the CI as discussed here. If that turns out to be the case, we’d only store the credentials in the keychain for vanilla SwiftPM and Xcode projects, but not for Tuist projects with the XcodeProj-based integration.
Either way, I’m proposing to add a new command tuist registry login
that would run the swift package-registry
command under the hood with the right URL and the right options. The command would also get a long-lived account access token that can be passed to the swift package-registry login
command. On the CI, the token would be equal to the project token stored in the TUIST_CONFIG_TOKEN
variable. The token needs to be long-lived as swift
CLI doesn’t have a built-in way to refresh an access token (as far as I’m aware).
Last but not least, we need to specify to SwiftPM that it should use the registry instead of the source control when the dependencies are available in the registry. This is done by running swift package --replace-scm-with-registry resolve
. We can pass that flag ourselves when tuist install
is invoked and we recognize that a registry is set up. We’d provide docs for vanilla SwiftPM projects.
Final flow overview
As a summary, the usage would look as following:
tuist registry setup <account-handle>
– generates aconfiguration.json
at the right location.<account-handle>
is optional – if not passed, we derive it from theTuist.swift
(if possible). Needs to be run once per project.tuist registry login <account-handle>
– authenticates with the registry. This will need to be run once by every developer in the team.tuist registry logout <account-handle>
– logs out of a given registry. Runsswift package-registry logout <url>
.tuist install
– augments theswift package resolve
with the--replace-scm-with-registry
flag.
I don’t think we need a command to remove the registry configuration, documenting how to do that in the docs should suffice.
Xcode projects with the vanilla SwiftPM integration
Currently, the Xcode registry integration is somewhat lacking – there’s no way, afaik, to permanently configure the swizzling between source control and registry resolution like with the --replace-scm-with-registry
swift
flag.
The configuration and logging in to the registry is the same as for Tuist and SwiftPM projects – with the extra requirement that the credentials to the registry need to be stored in the keychain. Additionally, the path to configure the registry is slightly different and is placed in {name}.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/configuration/registries.json
, so we would need to cater for that when tuist registry setup
is run.
What’s different is that developers need to explicitly specify which dependencies should be used from the registry.
For this, we can use Package Collections. Package collections are added through the Xcode Packages UI. Once the collection is added, Xcode will automatically recognize that a dependency was found in the registry:
You can then choose whether you want to download the dependency from the registry or from source control:
The registry identifier is then encoded in the .pbxproj
:
/* Begin XCRemoteSwiftPackageReference section */
F8410D022D0B3F810052EC6E /* XCRemoteSwiftPackageReference "Alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = Alamofire.Alamofire;
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.10.2;
};
};
/* End XCRemoteSwiftPackageReference section */
Note the repositoryURL
is equal to the registry identifier. For source control, you would see https://github.com/Alamofire/Alamofire.git
instead.
To sum up, developers would need to:
- Set up and login to registry the same way as you would in vanilla SwiftPM or Tuist projects.
- Add a Tuist Registry collection in their Xcode projects. We’d provide the URL in our docs.
- Replace already integrated packages with the registry counterparts
- Profit
Drawbacks
Some of the commands are convenience wrappers for the swift
CLI. Developers could instead use the swift
CLI directly with the right options that would be specified in the docs.
However, I believe the tuist
commands would provide enough of a convenience to justify their cost.
Alternatives
Instead of tuist registry setup
, we could add new options in the Tuist.swift
file such as:
import ProjectDescription
let tuist = Tuist(
fullHandle: "tuist/tuist",
registryEnabled: true
)
We would then generate the configuration.json
on every tuist install
invocation. However, for vanilla Xcode projects, this is not an option as the packages are resolved directly through Xcode. I believe having the configuration.json
generated once and then tracked via .git
will lead to a better experience.
Adoption strategy
The registry is opt-in – developers need to run tuist registry setup
to start using it. We’ll provide docs explaining the how and why.
Unresolved questions
What do you think about the proposed CLI interface? Any other concerns that you think should be answered here?