KMMBridgeKMMBridge SPM Quick Start

KMMBridge SPM Quick Start

Most of the teams we talk to are using GitHub and want to use SPM for their XCFramework file access. Configuring KMMBridge from scratch is complex. That is because configuring XCFramework publications is complex. There are many points of access and resource configuration necessary.

If you are using GitHub, however, we have built a template project to make this process simple.

We have another “SPM Quick Start” from the KMMBridge 0.5.x releases. If you are currently using KMMBridge from the 0.5.x era, please note several features have been simplified and/or removed. When upgrading, please carefully review your workflow script changes.

Create a KMP Repo

Open up our GitHub template repo

From the GitHub page, click “Use this template”, and create a repo in your org.

“Use this template” screenshot Use this template button screenshot

This repo can be public or private. Private repos will allow you to control access through GitHub.

Change the GROUP value

Open gradle.properties and change the value of GROUP to something that doesn’t start with co.touchlab.

GROUP=com.yourorg.yourrepo
Why GROUP needs to be changed

This only matters for GitHub Packages, which has historically had issues with conflicting maven “coordinates”, even though they are in separate GitHub Package repos. It’s not super important to have your domain, but our workflow checks and fails the build if it starts with co.touchlab.

Set your build version

Your builds publish with a version. That is controlled by LIBRARY_VERSION in gradle.properties. Whenever you publish, be sure to update that value first.

The value currently in the template should work, but I’d suggest changing it to:

LIBRARY_VERSION=0.1.0

Make sure to commit and push that change.

Publish your iOS Binaries

Open your new repo, then open the “Actions” tab. Find “KMMBridge iOS Publish” in the list of Workflows.

“Actions” tab screenshot Show how to find the GitHub Action

Look for “Run workflow”. Leave the branch default (main), and click the green “Run workflow” button.

“Run workflow” screenshot Show how to find the Run workflow button

⏳ Wait (~15 minutes)…

The workflow will build 3 release binaries for iOS. This can take a fair bit of time. Usually around 15 minutes.

Faster CI Builds

KMP iOS builds in CI can be slow. There are various ways to improve build times, but publishing versioned binaries should be relatively rare, so the longer build time is usually not a huge issue.

To speed up this build, if all of your devs have the same mac architecture (M1-3 or Intel), then you can remove the “other” simulator target. For example, all Touchlab devs have Mac arm machines (M1-3). We could remove iosX64() from the list of architectures to build, and that would cut the build time by 1/3.

For other CI build time tips see “Beware of Build Time Bloat”.

GitHub Access

To access your repo and binary packages, you’ll need to provide access to GitHub. That means creating a Personal Access Token (PAT). We’ll create one PAT to access everything from your local machine to test the builds, but every user will need to repeat this process for their access.

If your repo is public, you should be able to skip some of the scopes for the PAT, but I’m not sure how you’d access a public GitHub repo from the Xcode GUI. To make the process simpler for this tutorial, you should create a PAT with the full set of scopes.

GitHub requires auth for public repo binaries

GitHub requires users to set up authentication access to download binary files. This is obviously true for private repos, but it is also true for PUBLIC repos. It can be any valid GitHub user, but you still need to provide the auth info. This is a very common cause of confusion for KMMBridge users.

If you need to host SPM or Android binary artifacts in a place that doesn’t require auth, you’ll need to use a different cloud back end. You can use AWS S3, but you’ll need to configure your S3 bucket to allow public URLs. If you want to use a different back end, implement an ArtifactManager instance.

In GitHub, create a Personal Access Token. Go to your user settings, “Developer settings”, “Personal access tokens”, “Tokens (classic)“. “Generate new token” and select the “classic” option.

For all users, add the following scopes:

  • repo
  • read:packages

GitHub PAT scopes screenshot Show GitHub personal access token scopes

Xcode requires these additional scopes:

  • admin:public_key
  • write:discussion
  • user

GitHub Xcode screenshot GitHub PAT screenshot

After the token is created, make sure to get and keep a copy of the token string. We’ll need to add it in a few places.

We use the “classic” PAT option because we know what scopes are needed, and most docs talk about those scopes. The “Fine-grained tokens” should also work, but the scope names are different and we’d need to do some testing.

Some Highlights (while we’re waiting)

While your build is running, let’s take a few minutes to highlight important parts of the source and configuration for your new repo.

If you want to skip ahead go to Check Your Build

Build Version

SPM really likes valid semantic versioning. It’s possible to supply a different version and tell Xcode to only use that specific version, but Xcode won’t “like” it, for lack of a better description.

To publish a new version of your KMP library, open gradle.properties and edit LIBRARY_VERSION. You’ll usually want to add 1 to the third number. In the following case, the next version would be 0.1.1.

LIBRARY_VERSION=0.1.0

Android Publication

You can also publish Android AAR dependencies from the KMP repo.

Assuming you ran the iOS publication step above, you’ll need to bump the version in gradle.properties:

LIBRARY_VERSION=0.1.1

Look for “KMMBridge Android and iOS Publish” in the GitHub Actions workflow list and run that instead of “KMMBridge iOS Publish” that we used earlier. That will publish iOS binaries, but also Android binaries.

The Android dependencies will publish to GitHub Packages. That is a maven repo, so you can add them from Gradle in your Android project.

Code Modules

The code in the sample is essentially the code in our KaMP Kit KMP sample app, but split out into an external “SDK” format. For your own code, you should replace the sample modules and packaging with whatever your project needs. The sample intends to show several libraries and a few basic features fully implemented.

analytics

This is a “typed” analytics library. The idea is that analytics is critical to many apps, but most analytics libraries are just “maps of strings” that marketing people put in a spreadsheet. Implementing and maintaining these values correctly across platforms is critical, but very difficult to verify. There’s also no “joy” for the developers. They never get to see the outcome. It’s just a tedious coding chore that nobody enjoys.

Having an analytics library with typed and named function calls is a method some teams use to improve analytics maintenance.

Because these functions are intended to be called directly from Swift, this whole module is exported to the Xcode framework. See allshared/build.gradle.kts:

listOf(
    iosX64(),
    iosArm64(),
    iosSimulatorArm64()
).forEach {
    it.binaries.framework {
        export(project(":analytics")) // <-- This line
        isStatic = true
    }
}

Only expose what you need

Usually your Swift code only needs to call a small subset of the KMP code directly. The rest is used internally by the library. You should minimize the API surface that is exported to Swift for several reasons. The simple rules to follow:

  • Don’t “export” modules unless you intend to call most of their public API directly
  • For modules exported to Swift, which includes the module creating the Xcode framework, mark Kotlin structures as “internal” if you don’t need to call them from Swift
  • Never transitively export unless you really need to, and you really never need to (so don’t)

breeds

This module provides the “data” of the app. It calls the Dog CEO REST API to get a list of dog breeds and images for those breeds. The module uses ktor for network calls, Sqldelight for local storage, and various other architectural structures to manage everything.

This module is not exported for a few reasons. The majority of the code is not called directly from Swift. The entry points are provided in allshared. Also, Sqldelight generates Kotlin code, which includes references to parts of the Sqldelight library that Swift definitely doesn’t need. There’s no way to make that generated code “internal”, so “hiding” it inside another module is a common approach to API surface hygiene.

allshared

This is the Xcode framework “umbrella” module. There is no Android code here. The build.gradle.kts file configures the Xcode framework structure, and the Kotlin code provides structured SDK initialization specific to iOS.

This is the module that KMMBridge is configured to publish from.

testapps

These are not intended to be “sample apps” for this tutorial. A helpful option separate KMP repos is to have local test apps that you can run and build the KMP code directly with. It is not required to do this, but depending on your setup, you may find them useful. Both apps are configured to build the code locally.

✋ Check your build

Go back to the GitHub Actions page to check on your build. If done, continue to the next steps. If the build failed, here are some possible causes:

The GROUP value wasn’t changed, or the change wasn’t pushed. The build will fail at “Touchlab Sample Sanity Check”

“Sanity check error” screenshot Show how to find the Run workflow button

The build was run more than once If the release exists, the build will fail.

For other issues, reach out or file an issue in the quick start template repo.

👍 Next Steps

If the build succeeded, you should see a new release on your repo GitHub page.

GitHub release screenshot

Xcode Integration

Now that the build is complete you can try integrating it.

Go to touchlab/KMMBridgeSPMQuickStart-iOS and create another repo using that template (or just clone it, if you don’t plan on starting with it).

Open ios.xcodeproj, either directly with Xcode or by open it from a terminal.

To open from the terminal, cd to the directory you just cloned and run:

open ios.xcodeproj

SPM and Xcode

Xcode can understand and use SPM dependencies, but SPM is an independent build system that can be used to build Swift-oriented projects without the GUI Xcode tools. SPM has its own conventions and rules as well as config. The details of SPM config are way beyond this post, for now we’ll go over the basics.

For more details, you can find the SPM documentation on the Swift Website or on the GitHub Repo.

SPM is configured with a file called Package.swift. Unless you’re doing something complex (and you really* know what you’re doing), that file lives in the root of your KMP repo.

SPM uses git to organize libraries and versions. The Package.swift lives at the root of the repo, and git tags mark versions. SPM really does not like version tags that aren’t proper semver. You can try to get clever with this, but I would not. At least not until everything is working, and you’re very familiar with Xcode and SPM.

You can publish your Package.swift to a different repo than your KMP library, but I would suggest not doing so unless you have a very good reason. Its confusing, and the CI config is significantly more complex.

There are two parts to integrating a KMP SPM library. This is very important. You add the git repo through the Xcode GUI, but you also need to add auth to grab the binary file. These are separate steps, and you’ll still need to add auth for the binary if you are using GitHub, even for public repos. That’s a GitHub restriction, not Xcode, SPM, KMMBridge, or KMP. If you don’t want to require auth for a public library, you’ll need to use a different hosting service for the binary. That’s a “later” problem, though. Let’s work through this tutorial first.

Xcode GitHub Access

This should only be necessary for private GitHub repos, but I’m not exactly sure how to access the public repo from the Xcode GUI, so let’s auth to GitHub anyway.

In Xcode, go to “Xcode > Settings”, then the “Accounts” tab. If there is already a GitHub entry, you can probably skip this step. If there is not a GitHub entry, click the ”+” on the bottom/left, and add “GitHub”.

GitHub Xcode screenshot Xcode GitHub screenshot

In the dialog, add your GitHub username to the “Account” field, and paste the PAT we created earlier into the “Token” field.

GitHub Xcode Sign in screenshot GitHub PAT screenshot

Artifact Authentication

GitHub requires you to authenticate to download binary files. This is true for public repos, and obviously true for private repos. Adding this authentication is not complex, but if any of the steps are incorrect, Xcode and SPM aren’t very helpful explaining what went wrong. Make sure you double-check everything.

You can read about this in detail at Xcode and Binary File Authentication. We’ll summarize for this tutorial.

Create or open the file ~/.netrc. This provides “curl” and other tools with authentication information.

touch ~/.netrc
open ~/.netrc

Add the following to the file:

machine api.github.com
  login <Github User>
  password <PAT>

The login is your GitHub username, and the password is the same PAT we created earlier. Save the file and exit.

GitHub requires auth for all binaries

You’ll see this point repeated in several places throughout the KMMBridge docs, but it is something that confuses people regularly.

GitHub Packages requires authentication for accessing files in all repos, public or private. To avoid forcing users to auth to GitHub, you’ll need to host your binaries somewhere else.

Adding the library

In Xcode, make sure you have the iOS quick-start sample project open. To add an SPM package, go to File > Add Package Dependencies... in the Xcode menu. You can usually browse for the package at that point, but depending on how many repos you have, it may be easier to copy/paste the repo URL in the top/right search bar.

Xcode Packages screenshot Xcode Packages screenshot

After finding the package, select it, leave “Dependency Rule” as “Up to Next Major Version” and “Add to Project” as “ios”.

On the next screen, make sure “ios” is selected in the “Add to Target” column:

Xcode Targets screenshot Xcode Targets screenshot

Assuming you’ve configured your auth correctly, Xcode should download the binary file, and you should be able to access your KMP code!

In Xcode, click the “Run” button, but make sure you have an iPhone simulator selected.

Xcode Run screenshot

Android Publication

You can publish both iOS and Android binaries by running the “KMMBridge Android and iOS Publish” action in the GitHub Actions panel. With our template as it is currently configured, the Xcode XCFramework binaries are attached to the GitHub release that is created, while the Android dependencies are published to GitHub packages.

Very Important: First open gradle.properties and change LIBRARY_VERSION to 0.1.1. Add/commit/push in git.

Now go back to the Actions tab on the GitHub repo page, and run “KMMBridge Android and iOS Publish”.

This will also take around 15 minutes, or possibly more with the additional publications.

Clone the Android Sample

Open our Android sample repo. You can either use it as a template, or clone it directly.

Add the maven repo

In the Android sample project, open settings.gradle.kts. Find dependencyResolutionManagement at line 9. Change the uri to point to your KMP library repo:

dependencyResolutionManagement {
    val GITHUB_PACKAGES_USERNAME: String by settings
    val GITHUB_PACKAGES_PASSWORD: String by settings

    @Suppress("UnstableApiUsage")
    repositories {
        google()
        mavenCentral()
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/touchlab/KMMBridgeSPMQuickStart") // <-- Change this
            credentials {
                username = GITHUB_PACKAGES_USERNAME
                password = GITHUB_PACKAGES_PASSWORD
            }
        }
    }
}

You’ll also need to supply GitHub auth info, similar to the way we did for Xcode. This is also true for public repos, for the same reasons.

Each user will need their own credentials. To do that, we reference Gradle properties in the build file. For each user, those values are set in the global Gradle properties file. Open or create ~/.gradle/gradle.properties. Your GitHub username is your actual username. The “password” is the PAT we created earlier:

GITHUB_PACKAGES_USERNAME=[GitHub username]
GITHUB_PACKAGES_PASSWORD=[Your PAT]

Add the new dependencies

Open gradle/libs.versions.toml. Find sharedlib-analytics on line 66. Change sharedlib-analytics and sharedlib-breeds to match the GROUP value from your KMP library.

sharedlib-analytics = { module = "[your GROUP]:analytics-android-debug", version.ref = "sharedlib" }
sharedlib-breeds = { module = "[your GROUP]:breeds-android-debug", version.ref = "sharedlib" }

Find sharedlib = on line 24. This is the version of your KMP library. Right now, that should be set to 0.1.1, but when you make future builds, this is what you’ll update.

sharedlib = "0.1.1"

If all is configured correct, reload Gradle, and run the app!

Workflow Notes

Both GitHub Actions workflows are fairly standard-looking for KMP builds. There are a handful of notes to keep in mind.

When using GitHub Releases, KMMBridge will actually create the release on GitHub during the build. Note the final two steps:

      - name: Build Main
        run: ./gradlew kmmBridgePublish -PENABLE_PUBLISHING=true -PGITHUB_PUBLISH_TOKEN=${{ secrets.GITHUB_TOKEN }} -PGITHUB_REPO=${{ github.repository }} --no-daemon --info --stacktrace
        env:
          GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m"

      - uses: touchlab/ga-update-release-tag@v1
        id: update-release-tag
        with:
          commitMessage: "KMP SPM package release for ${{ steps.versionPropertyValue.outputs.propVal }}"
          tagMessage: "KMP release version ${{ steps.versionPropertyValue.outputs.propVal }}"
          tagVersion: ${{ steps.versionPropertyValue.outputs.propVal }}

-PGITHUB_PUBLISH_TOKEN=${{ secrets.GITHUB_TOKEN }} Gives the auth info that KMMBridge needs to call the GitHub API. If you are publishing to a different repo, you’ll need to have access to a different token and pass that in, as well as a different repo reference.

The update-release-tag step force-updates the tag created by the GitHub Release we’re publishing to. Why this needs to happen is a long explanation, but in summary, we need the release to exist so we can push to it, after which we can generate the Package.swift file. For everything to work together, the release tag needs to point at the commit with the updated Package.swift file. You can update a GitHub Release tag after the release is created, which sounds odd, but you can.

The tagged commit will not be in a branch. We don’t push to the main branch because the Package.swift file in your repo may be set up for local SPM dev.

Publishing to a different repo

This would be a much longer post, but if you want to experiment, here’s the summary. The last two steps should look more like this:

      - name: Build Main
        run: ./gradlew kmmBridgePublish -PENABLE_PUBLISHING=true -PGITHUB_PUBLISH_TOKEN=${{ secrets.GITHUB_PUBLISH_TOKEN }} -PGITHUB_REPO=myorg/spmrepo --no-daemon --info --stacktrace
        env:
          GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m"

      - uses: touchlab/ga-push-remote-swift-package@v1
        id: push-swift-package
        with:
          commitMessage: "KMP SPM package release for ${{ steps.versionPropertyValue.outputs.propVal }}"
          remoteRepo: "myorg/spmrepo"
          remoteBranch: "main"
          tagMessage: "KMP release version ${{ steps.versionPropertyValue.outputs.propVal }}"
          tagVersion: ${{ steps.versionPropertyValue.outputs.propVal }}

For this to work you’ll need to set up GitHub DeployKeys between the repos, as well as create a PAT that can publish to another repo (referenced here with secrets.GITHUB_PUBLISH_TOKEN).

We’ll publish a longer post/doc on how to do this in the future.

Next Steps

The template is meant as a starting point. Rip out our sample code and add some of your own.

If you use a different CI, or need to publish to other locations, you can still use KMMBridge. It is meant to be easily extended. Be prepared, though, that any production build config can be complex, and Xcode builds are no exception. We’ve also found that the people setting up the KMP builds are usually familiar with Android and Gradle. Less so with Xcode and SPM. We would highly encourage you to recruit help from your iOS team.