Publishing KMP Libraries in Kotlin 1.4.0

An explanation on how to publish Kotlin 1.4.0 Multi-platform Libraries to Bintray

Publishing KMP Libraries in Kotlin 1.4.0

With the release of Kotlin 1.4.0, comes a new approach to authoring and using Kotlin Multi-platform (KMP) Libraries. For instance, a library User is now only required to declare a dependency in the common source set for a Kotlin Multi-platform Library and all of the platform implementations will automatically be added.

But library Authors need to take care when upgrading to Kotlin 1.4.0 to make sure their library artifacts get uploaded correctly to their artifact repository. This Migration Guide does a good job explaining the steps required to upgrade to Kotlin 1.4.0, including how to publish KMP libraries. But I ran into some issues publishing the Gradle Module Metadata to Bintray. In this article we'll look at the issue and solution.

Publishing a library requires an artifact repository. A popular artifact repository, and the one I use, is JFrog's Bintray which has a Gradle Plugin to help with publishing artifacts. Unfortunately, the Bintray Gradle Plugin doesn't natively support the new KMP Library Structure. Specifically, it doesn't upload the necessary Gradle Module Metadata. Without this metadata the library can't be properly resolved.

TL;DR

This article shows how to include the Gradle Metadata files of a Kotlin Multi-platform Library in the artifacts uploaded to Bintray for distribution.

The Issue

When building a Kotlin Multi-platform (KMP) Project, a module.json file is created for every target which contains information about the library, targets, and artifacts. These files are created in the Gradle Project's build directory in the publications and each target's folder ( build -> publications -> target -> module.json ).

These files are necessary to resolve a KMP 1.4.0 dependency. But the Bintray Plugin doesn't automatically upload them.

The Solution

Since the necessary Gradle Module Metadata files aren't automatically included in the upload to Bintray, we have to manually include them into the artifacts list. The Kotlin 1.4.0 Migration Guide also highlights this issue and points to this solution, but I ran into some unexpected issues with that solution so I adapted it slightly to get it to work. The other thing to note is that the name of the metadata file is expected to have a .module suffix (ex: com.chrynan.colors-core.module), so we have to change that as well.

In the root Project's build.gradle file, I have a subprojects block where I apply and setup the Bintray plugin for each Gradle Sub-project. In that block, I add a doFirst closure to the bintrayUpload plugin where we add the .module file to the artifacts that are uploaded.

bintrayUpload.doFirst {
        publications = publishing.publications.collect { publication ->
            File moduleFile = new File(project.buildDir, "publications/${publication.name}/module.json")

            if (moduleFile.exists()) {
                publication.artifact(new FileBasedMavenArtifact(moduleFile) {
                    protected String getDefaultExtension() {
                        return "module"
                    }
                })
            }

            publication.name
        }
    }

In the above code, I assign the publications to equal all the publications publications = publishing.publications.collect { publication -> publication.name }. That is what is different from the proposed solution from the Kotlin Migration Guide. For some reason, without doing this, the publications weren't correctly uploaded.

Then in the collect closure of the above code, I check for a module.json file. If one is present, I add it to the artifacts to be uploaded and change the extension to be .module. The entire block looks like this:

subprojects {
    apply plugin: "maven-publish"
    apply plugin: "com.jfrog.bintray"

    bintray {
        user = project.findProperty("BINTRAY_USER") ?: System.getenv("BINTRAY_USER")
        key = project.findProperty("BINTRAY_KEY") ?: System.getenv("BINTRAY_KEY")
        
        pkg {
            repo = LibraryConstants.bintrayRepo
            name = LibraryConstants.repoName
            licenses = [LibraryConstants.license]
            vcsUrl = LibraryConstants.vcsUrl
            version {
                name = LibraryConstants.versionName
                desc = LibraryConstants.versionDescription
                released = new Date()
                vcsTag = LibraryConstants.versionName
            }
        }
    }

    bintrayUpload.doFirst {
        publications = publishing.publications.collect { publication ->
            File moduleFile = new File(project.buildDir, "publications/${publication.name}/module.json")

            if (moduleFile.exists()) {
                publication.artifact(new FileBasedMavenArtifact(moduleFile) {
                    protected String getDefaultExtension() {
                        return "module"
                    }
                })
            }

            publication.name
        }
    }

    bintrayUpload.dependsOn publishToMavenLocal
}

The Conclusion

With the above approach and the information from the Kotlin 1.4.0 Migration Guide, a Kotlin Multi-platform Library can be published to Bintray. The publication includes all the necessary artifacts (jars, poms, and modules) for each source set, so that the library can be used by Kotlin 1.4.0, as well as, older Kotlin versions and non-multiplatform projects. For a full reference on how to publish a KMP library, check out my library: colors.

Update September 13th, 2020

This article was featured in Android Weekly Issue 431!

Badge