Lukas Pistrol Logo

Lukas Pistrol

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 the SCOPE nor the PACKAGE-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 each package-name directory a releases.json file should be present as well as further directories for each tagged version, each containing a Package.swift and a release_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.

  1. On the artifactory filter the artifacts by Swift and select a repository of your choice.
  2. Then select Set Me Up on the top right.
  3. Enter your password and press Generate Token & Create Instructions
  4. 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.

  1. Open your project in Xcode
  2. Go to File > New > Workspace... and name it accordingly.
  3. Close your project.
  4. In the empty workspace go to File > Add Files to ...
  5. Select the *.xcodeproj file and make sure to select the Create folder references option and keep Copy items if needed disabled
  6. Now select the + button on the bottom-left and select New Package.... This will create a new top-level package inside your project directory.
  7. In the Package.swift file add the dependencies like mentioned above.
  8. 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 the Xcode 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).

References

Another article you might like: