Xcode 13.3 supports SPM binary dependency in private GitHub release

Xcode 13.3 supports SPM binary dependency in private GitHub release

Β·

3 min read

In this blog post I will share details about a new feature not mentioned in the Xcode 13.3 Beta 3 release notes. This feature is of interest to developers who need to make their code available as binaries to protect their intellectual property when developing proprietary, closed-source libraries.

To distribute code in binary form as a Swift package, create an XCFramework bundle that contains the binaries. Then, make the bundle available locally or on a server. Here is an example of a Package.swift manifest to distribute a binary framework stored on a server:

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .macOS(.v10_14), .iOS(.v13), .tvOS(.v13)
    ],
    products: [
        .library(
            name: "MyLibrary",
            targets: ["SomeRemoteBinaryPackage"])
    ],
    dependencies: [],
    targets: [
        .binaryTarget(
            name: "SomeRemoteBinaryPackage",
            url: "https://url/to/some/remote/xcframework.zip",
            checksum: "The checksum of the ZIP archive that contains the XCFramework."
        )
    ]
)

The xcframework.zip file needed to be publicly accessible on a server. But what if you need to limit public access to authenticated users?

The .netrc file format is commonly used for the automatic authentication of HTTP requests.

machine <example.com> login <my-user-name> password <my-password>

It generally resides in the user’s home directory (~/.netrc). Learn more about netrc by reading the gnu documentation.

My former co-worker Stan Stadelman introduced netrc into SPM to support basic auth aiming non-git binary dependency hosts.

The user is not required to opt-in to use netrc as the option is turned on per default in the Swift Package Manager.

    /// Whether to load .netrc files for authenticating with remote servers
    /// when downloading binary artifacts or communicating with a registry.
    @Flag(inversion: .prefixedEnableDisable,
          exclusivity: .exclusive,
          help: "Load credentials from a .netrc file")
    var netrc: Bool = true

SPM will automatically detect the .netrc file and apply the required Authentication header. Also, with option netrc-file <netrc-file> you can specify a different location.

This feature became available with Xcode 12.5. However, the community pointed out that this does not work for a private GitHub release.

First, you cannot use the browser download URL as an HTTP request results in a 302 redirect. You have to lookup the asset name and then use the asset url.

  • Incorrect URL: https://github.com/:owner/:repo/releases/download/:tag/some.xcframework.zip
  • Correct URL: https://api.github.com/repos/:owner/:repo/releases/assets/\(assetId).zip

But more importantly, there is another issue that SPM was not aware of.

// netrc support is NOT enough to make this work for a private repo asset
.binaryTarget(
    name: privateName,
    url: "https://api.github.com/repos/:owner/:repo/releases/assets/\(assetId).zip",
    checksum: checksum
)

The Github API states the Accept header of the request to application/octet-stream is required to download the asset's binary content. Otherwise the API JSON response is returned.

Hence the following change was introduced in SPM, by Jimmy Arts, to set the header automatically:

Xcode 13.3 Beta 3 includes this change. Hence, using Xcode 13.3 Beta 3 and a .netrc file with a GitHub personal access token will finally allow you to download binary assets from release in a private GitHub repository. πŸ˜ŠπŸŽ‰

Example: I have a repository MyPrivateRepo under my GitHub user MarcoEidinger and I created release 1.0.0 with asset MyBinaryModuleName.xcframework.zip. Hence the Package.swift would be like

.binaryTarget(
    name: "MyBinaryModuleName",
    url: "https://api.github.com/repos/MarcoEidinger/MyPrivateRepo/releases/assets/58858051.zip",
    checksum: "c533f08210ac21def782d48c2ff1e1a538b05b051e128aa88ffcd44051ddc2b3"
),

and my ~/.netrc file would be something like

machine api.github.com login MarcoEidinger password ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Netrc is a great solution which works on Mac and also on Linux. If you are exclusively working on Mac then you can leverage Apple's Keychain as alternative. Instead of creating an entry in the ~/.netrc file you can create an internet password containing

  • Keychain Item Name: api.github.com
  • Account Name: GitHub user name
  • Password: personal access token

New Password Item

Create Internet Password

Xcode (SPM) will read from keychain once you granted permission. Such a dialogue may come up during package resolution.

Xcode requires permission to access keychain

P.S.: The whole history can be traced in the following thread in the Swift Forum:

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!