· 5 min read Posted by Kevin Schildhorn

Adding a Custom Artifact Manager

How to create a Custom Artifact Manager for KMMBridge

KMMBridge allows you to publish artifacts to some of the most popular repositories online, but what if you want to publish somewhere else? This two part post will go through how to create a custom Artifact Manager for KMMBridge that will store artifacts in Google Cloud Storage
Tatiana P https://unsplash.com/photos/a-large-bridge-spanning-over-a-large-body-of-water-UfFSO6JOlKE
Credit: Tatiana P https://unsplash.com/photos/a-large-bridge-spanning-over-a-large-body-of-water-UfFSO6JOlKE
How to create a Custom Artifact Manager for KMMBridge (2 Post Series)

KMMBridge is a great gradle plugin that allows you to publish Xcode XCFrameworks from your Kotlin Multiplatform projects. It works by uploading XCFrameworks to an artifact repository and then configures your project to point to the uploaded framework. KMMBridge supports uploading to GitHub, Maven and Amazon S3 out of the box, but what if you want to store your frameworks somewhere else? Somewhere like Google Cloud Storage?

Well KMMBridge supports this by including an interface called ArtifactManager that can be implemented and passed into KMMBridge. This interface tells KMMBridge how to upload your framework and where to look for it. This two part post will go over how to create a custom Artifact Manager that hosts your frameworks in Google Cloud Storage.

Writing an Artifact Manager

The KMMBridge Docs cover the anatomy of an ArtifactManager, but it can still be confusing without know what you’re looking for.

class ExampleArtifactManager(  
    private val folder: String  
) : ArtifactManager {  
  
    lateinit var specialToken: String
  
    override fun configure(  
        project: Project, 
        version: String, 
        uploadTask: TaskProvider<Task>, 
        kmmPublishTask: TaskProvider<Task>  
    ) {   
        this.specialToken = project.specialToken  
    }  
  
    override fun deployArtifact(
	    task: Task, 
	    zipFilePath: File, 
	    version: String
    ): String { 
        // Use specialToken here 
        return "https://touchlab.co/$folder/myframework-$version.zip"  
    }  
}

Here you can see a barebones ArtifactManager. You can see that there are two functions: configure and deployArtifact.

The first function configure gives you a chance to configure your artifact using gradle properties and tasks. In this example we are getting a special token that would be used later in uploading.

The second function deployArtifact should handle deployment. This of course isn’t a full example (we’ll go over this more in the second part), but notice how it uses the version passed in from configure and the folder variable passed in the constructor. The ArtifactManager is constructed in your build.gradle.kts file in the KMMBridge block:

kmmbridge {   
    artifactManager.set(ExampleArtifactManager(folder = ""))
}

So now we know how to create an ArtifactManager, but where should it go?

Where to put an Artifact Manager

There’s one very important detail when creating a custom ArtifactManager that you need to know:

ArtifactManagers are referenced from gradle files (gradle.kts), not from regular kotlin files (.kt).

The manager is used when building your project with gradle, which means you can’t just define it in your apps codebase and expect to be able to reference it. It needs to be defined in either a gradle plugin or in the build.gradle.kts itself. Lets go over the two options and what they look like.

There is a third option as well, to add it to your buildSrc. This should also be technically possible however I ran into many issues trying to get this to work so I wouldn’t recommend it personally.

Option 1. In the build.gradle

If you want a really simple approach to adding an ArtifactManager, then you can take the quick and dirty approach and create the class in your build.gradle.kts. This might look something like this:

// build.gradle.kts

plugins {
	...
}

buildscript {
    dependencies {
	    // Any dependencies for your ArtifactManager
        classpath(...)
    }
}

class YourArtifactManager : ArtifactManager {
    override fun deployArtifact(task: Task, zipFilePath: File, version: String): String {
        TODO("")
    }
}

kmmbridge {
    artifactManager.set(YourArtifactManager())
    spm(swiftToolVersion = "5.8") {
        iOS { v("14") }
    }
}

Here you can see you’re creating the class YourArtifactManager in the gradle file and then referencing it in the kmmbridge block. While this is quick and simple there are some negatives with this approach:

  1. it’s messy to be defining classes in your gradle file
  2. It’s not reusable. If you have two modules or projects that need this manager then you need to define it each time
  3. If your ArtifactManager has dependencies then it will require including the buildscript block, which is not really recommended anymore. You can still use it but it’s but it’s considered legacy and has been replaced.

Option 2. As a Plugin

While it requires some additional work, adding your ArtifactManager as a separate plugin is much cleaner, allows re-use and doesn’t use the buildscript block. If you have multiple projects using your ArtifactManager then you can create a separate project and publish your plugin to maven. If you’re only using it in one project you can create a new module for your ArtifactManager. Here is an example of how you can do that.

First, create a new module. In your modules build.gradle.kts add the following code

plugins {
    `kotlin-dsl`
}

@Suppress("UnstableApiUsage")
gradlePlugin {
   plugins {
        register("your-plugin") {
            id = "com.example.yourid"
            implementationClass = "com.example.yourid.YourPlugin"
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("co.touchlab.kmmbridge:kmmbridge:1.2.0")
	// Additional dependencies
}

Then add a file for your plugin

package com.example.yourid

import org.gradle.api.Plugin
import org.gradle.api.Project

@Suppress("unused")
class YourPlugin : Plugin<Project> {
    override fun apply(target: Project) {}
}

This class is just so that gradle can find your plugin so there doesn’t need to be much there. On that note, you also need to tell gradle where to find your plugin. To do that, update your settings.gradle.kts

pluginManagement {
    includeBuild("yourModuleName")
    ...
}

This is basically the plugin equivalent of implementation(project(":myModule")).

Do not include your plugin module in your settings.gradle.kts file (i.e. include(":yourModuleName")). The module is already included in the pluginManagement and this will cause issues.

Finally add your ArtifactManager in your new module. Add any dependencies needed and build your library. Assuming everything builds correctly, add the plugin to your app.

import com.example.yourid.YourArtifactManager

plugins {
    ...
    id("com.example.yourid")
}

kmmbridge {
    artifactManager.set(YourArtifactManager())
    spm(swiftToolVersion = "5.8") {
        iOS { v("14") }
    }
}

Sync your project and everything should be ready to go! You don’t need to call a publish gradle function, it should be handled automatically.

Next Section : Publishing artifacts to Google Cloud Storage

Hopefully now you have a foundation with your plugin and ArtifactManager, plus a good understanding of the Artifact Manager. In the next section, we’ll go over uploading to Google Cloud Storage using our new manager.

Comments

Reply on Bluesky here to join the conversation.

No comments yet. Be the first to comment!