← Back to Blog
swift swift-package ios ci/cd

Publishing a Swift package with CI/CD

This guide is going to be a bit more complicated as it is going to deploy a compiled binary framework from a private repository to a public repository using CI/CD.

Here is the scheme of what we are going to do:

Release Schema

As we can see the process doesn't look that complicated except one single tiny thing that we need to do. We need to synchronize 2 repos so when a git tag is created from the private repo where the code is located the public repo gets a new GitHub release and updated Package.swift file with a fresh checksum and url.

On the private repo a new git tag will trigger the code to get tested & compiled into a XCFramework. When we have the XCFramework we are going to then get its checksum (SHA256) and update the public repos Package.swift with the checksum but there is an issue here we also need to provide a public url of where the new XCFramework has been hosted. To solve this we are going to leverage GitHub's release and artifacts and upload the XCFramework there.

Step 1: Compiling

We are going to start off by creating a basic compilation script to compile our project and create an XCFramework:

FRAMEWORK_NAME="MyFramework"
PLATFORM_LIST=("iOS" "iOS Simulator")

# Create Archives
for platform in "${PLATFORM_LIST[@]}"
do
echo "Archiving platform: $platform"
xcodebuild -verbose archive \
    -workspace "$FRAMEWORK_NAME.xcworkspace" \
    -scheme $FRAMEWORK_NAME \
    -destination "generic/platform=$platform" \
    -archivePath "path/to/archive/$platform" \
done

After we have created a standalone .framework, we need to wrap all the platforms into a single XCFramework:

echo "Creating $FRAMEWORK_NAME XCFramework..."
xcodebuild -create-xcframework \
    -archive $RELEASE_DIR/$ARCHIVE_DIR/iOS.xcarchive -framework $FRAMEWORK_NAME.framework \
    -archive $RELEASE_DIR/$ARCHIVE_DIR/iOS\ Simulator.xcarchive -framework $FRAMEWORK_NAME.framework \
    -output $RELEASE_DIR/$FRAMEWORK_NAME.xcframework

Step 2: Store the XCFramework

We have our product that we can deploy out to the wild. Here is a demo code that uploads our compiled XCFramework and creates a GitHub release using the gh CLI:

gh release create $GIT_TAG ./Release/MyFramework -t "v${GIT_TAG}" -F ./Changelog.md

Step 3: Triggering other pipelines

We are using CircleCI as our build server. On our private build server, trigger the HTTP request to start the public build server pipeline:

curl --request POST \
  --url $PUBLIC_CIRCLE_PROJECT \
  --header "Circle-Token: $CIRCLECI_API_KEY" \
  --header "content-type: application/json" \
  --data '{"parameters":{"run-release-workflow": true, "version":"'$CIRCLE_TAG'"}}'

Step 4: Publishing

When the pipeline on the public build server gets triggered, we need to download the framework and get the SHA-256:

gh release download --repo $PRIVATE_REPO_URL $VERSION

Calculate the CHECKSUM:

echo "CHECKSUM=$(shasum -a 256 MyFramework.zip | awk '{print $1}')" >> $BASH_ENV

Make sure that Package.swift has a top variable called let version:

// swift-tools-version: 5.6
import PackageDescription

let version = "<VERSION>"

let package = Package(
  targets: [
    .binaryTarget(
      name: "MyFramework",
      url: "https://github.com/org/repo/releases/download/\(version)/MyFramework.zip",
      checksum: "<CHECKSUM>"
    ),
  ]
)

Then create the release:

git add Package.swift
git commit -m "release: $VERSION [skip ci]"
git push origin main
gh release create $VERSION ./MyFramework.zip -t "v$VERSION" -F ./CHANGELOG.md

Conclusion

That's all, folks. We have successfully created two pipelines that both validate our work, update all manifests, and publish them whenever we push out a new git tag.

Break Zero