What is Swift-Package-Registry?
Since the introduction of Swift Package Manager (SwiftPM), it has become the de-facto standard for managing dependencies in Swift projects. It is pretty easy to use and (mostly) works well with Xcode.
However there are some performance related issues when using SwiftPM in Xcode. You might have noticed already, that resolving dependencies can really take a while. This is especially true when working on larger projects with a lot of dependencies, as well as when a dependency's git history is very long.
Why is the git history relevant you ask? Swift Package Manager always clones the entire git repository of a dependency which can easily accumulate to a couple of gigabytes of data. Very annoying when on a slow or metered internet connection.
Enter Swift Package Registry
The Swift Package Registry introduced with SE-0292 in Swift 5.7 aims to solve this problem by providing an interface to a package registry that Swift Package Manager can use to resolve and download source archives from.
A registry is basically a server that hosts scoped source archives that package managers can look up and download. They are extensively used in other ecosystems like npm, maven, gradle, etc.
That means instead of cloning the entire git repository (with all of its history) every time a dependency is resolved, the Swift Package Manager can now find the required version of the dependency in the registry and just download a "snapshot" of the source code at that tag
.
This really speeds up resolving dependencies by a lot. Additionally it saves a lot of disk space and bandwidth.
JFrog Artifactory
In this article we will focus on deploying a Swift package to a JFrog Artifactory instance, since it is the only one (I know of) that currently supports Swift packages.
JFrog Artifactory is a universal artifact repository manager that supports software packages created by most languages or technologies. It is a very powerful tool that can be used to host your own package registry.
This will probably only be feasible in a corporate environment, or larger open-source projects. For smaller projects, the GitHub package registry is probably the better choice once they add support for Swift packages.
Deployment
To deploy a release to the JFrog Artifactory a couple of things need to be kept in mind.
Create Archive
First of all we need to create an archive of the package. This can be done using the archive-source
command and would likely happen in a CI/CD pipeline.
swift package archive-source -o "<package-name>-<TAG>.zip"
Here we provide the output file name in the format <PACKAGE-NAME>-<TAG>.zip
(e.g. my-package-1.0.2.zip
).
It is highly recommended to use the
archive-source
command. Don't try to zip it on your own. Also make sure that the output file name follows the right scheme as mentioned above to avoid any issues later on.
Upload to Artifactory
The following steps assume that Artifactory is already set up and configured to host Swift packages like described in the official documentation.
First we need to install the jfrog-cli
on our machine. On macOS this can be done using homebrew
:
brew install jfrog-cli
Next we use the jfrog-cli
to login to the artifactory instance:
jf c add --url <ARTIFACTORY_URL> --user <ARTIFACTORY_USER> --password <ARTIFACTORY_KEY>
Once logged in, we can finally upload the previously created archive to the Artifactory:
jf rt upload "PATH/TO/ARCHIVE.zip" "<SCOPE>/<PACKAGE-NAME>/{PACKAGE-NAME}-{TAG}.zip"
The
SCOPE
is required. Use the name of your project or organization name. Neither theSCOPE
nor thePACKAGE-NAME
are case-sensitive.
Verify on Artifactory
To verify everything went right, open the artifactory in the browser and navigate to the registry.
- Check that the uploaded
*.zip
artifact is indeed available. - After a few minutes the artifactory will re-index all packages and generate a
.swift
directory. - Inside this directory you'll find the same structure
{scope}/{package-name}/
and inside of eachpackage-name
directory areleases.json
file should be present as well as further directories for each tagged version, each containing aPackage.swift
and arelease_info.json
file.
The folder structure on the Artifactory should look something like this:
<registry-name>
├┬ .swift
│└┬ <scope>
│ └┬ <package-name>
│ ├─ 0.0.1
│ ├┬ 0.0.2
│ │├─ Package.swift
│ │└─ release_info.json
│ └─ releases.json
└┬ <scope>
└┬ <package-name>
├─ <package-name>-0.0.1.zip
└─ <package-name>-0.0.2.zip
Initial Setup (Client)
Now it's time to set up the client (your machine & project) to use the registry.
Get a Token
First we need to obtain a token from the artifactory.
- On the artifactory filter the artifacts by
Swift
and select a repository of your choice. - Then select
Set Me Up
on the top right. - Enter your password and press
Generate Token & Create Instructions
- Write down the token in a safe place.
Set Registry
The registry needs to be set using the swift package-registry set
command:
swift package-registry set --global <ARTIFACTORY_URL>/api/swift/<REGISTRY_NAME>
The
--global
flag can be omitted. With the global flag, the registry will be added to~/.swiftpm/configuration/registries.json
. Otherwise, it will be added to your project's.swiftpm/configuration/registries.json
file relative to your working directory.
Login
To log in, enter the following command:
swift package-registry login <ARTIFACTORY_URL>/api/swift/<REGISTRY_NAME>
A prompt for a token will appear. Enter the previously saved token and press enter
.
The token will be saved in the macOS login keychain. There are other options available to store the token, but this is the only one that works with Xcode.
Setup in your Project
In your Package.swift
manifest add your dependency using the .package(id:from:)
variant:
...
dependencies: [
.package(id: "scope.package-name", from: "0.0.1"),
],
...
Now when resolving the dependencies using either the swift CLI or Xcode, the package manager will look up the dependency in the registry and download the source archive instead of cloning the entire git repository.
Note that Xcode 15 is required dor this to work. Xcode 14.3 will not include the token in the resolving request which leads to a
401 Unauthorized
error code. See this issue. For a workaround see the section at the end of this article.
Usage in a Xcode project
Unfortunately it is currently not possible to add registry dependencies to an Xcode project like you would with a standard Swift package from a git
hosting provider (GitHub, GitLab, ...). Hopefully this will be possible in the future. For now, however, we can create a local Swift package and use it in our Xcode project (ideally converting the project to a workspace) as kind of a proxy.
- Open your project in Xcode
- Go to
File > New > Workspace...
and name it accordingly. - Close your project.
- In the empty workspace go to
File > Add Files to ...
- Select the
*.xcodeproj
file and make sure to select theCreate folder references
option and keepCopy items if needed
disabled - Now select the
+
button on the bottom-left and selectNew Package...
. This will create a new top-level package inside your project directory. - In the
Package.swift
file add the dependencies like mentioned above. - Once the dependencies are resolved, add the libraries to your project's target.
Xcode 14.3 workaround (deprecated)
There is a workaround available for injecting the credentials using Xcode 14.3:
- Install Proxyman
- Listen for a
HTTP GET
request by theXcode
app that calls the artifactory URL. - Right-Click on that request, and open
Tools > Scripting
. - Add the following function to the newly created script and fill in your token.
async function onRequest(context, url, request) {
console.log(url);
request.headers["Authorization"] = "Bearer <YOUR_TOKEN>"
return request;
}
- Save and activate the script.
This will intercept each call that Xcode makes to the artifactory while resolving the dependencies and inject the authorization token.
Conclusion
The Swift Package Registry, once adopted by the big players like GitHub, will be a great addition to the Swift ecosystem. It will make working with Swift packages a lot easier and faster. In the meantime we can use JFrog Artifactory to host our own Swift packages. Although I really only recommend this for corporate environments or larger open-source projects.
What are your thoughts on the Swift Package Registry? Let me know on X (formerly Twitter).