Using a non-standard plugin in your Android project Kotlin DSL build

How do you add a Gradle plugin for your Android application when your build scripts use Kotlin DSL, but the plugin’s setup instruction are for Groovy DSL, and the plugin has not been published with Gradle plugin markers?

TL;DR: Skip to this if you’re having the same issue and just want the work-around

org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.someNamespace.sometool', version: '6.1.0'] was not found in any of the following sources:

Gradle Core Plugins (plugin is not in 'org.gradle' namespace)

Included Builds (No included builds contain this plugin)

Plugin Repositories (could not resolve plugin artifact 'com.someNamespace.sometool:com.someNamespace.sometool.gradle.plugin:6.1.0')
Searched in the following repositories: (…)

If you are seeing an error like this, Gradle is attempting to find the plugin with its standard « plugin markers », but your plugin may not be registered with the expected marker in the repo. You may also have other issues, of course (wrong version, wrong name, proxy, etc.), which I will not be delving into.

The fix in this specific case is as follows.

Define the plugin, for example in your libs.versions.toml file (or directly as a string in your build file)

[versions]
(..)
gradlePlugins-sometool = "6.1.0"


[plugins]
(…)
sometool = { id = "com.someNamespace.sometool", version.ref = "gradlePlugins-sometool" }

Use the defined plugin name in your app modules build.gradle.kts file:

plugins {
   (..)
    alias(libs.plugins.sometool)
}

And the missing piece: you need to help Gradle find the plugin by defining a custom resolution strategy in your project’s settings.gradle.kts file.

pluginManagement {
    repositories { 
		(..)
    }
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "sometool" -> {
                    useModule("com.someNamespace:sometool:${requested.version}")
                }
            }
        }
    }
}

Gradle can now resolve the needed artifact for the plugin, as long as it is available in one of the repositories you have defined.

Once upon a time… (yes, this is the storytelling part, based on a true story)

Context: your Android app has a Kotlin DSL build script

So, not only are you using a versions catalog in your app’s build script, but also these build scripts are all written in Kotlin DSL, the most groovy option. Yay!

Situation: you wish to use a third-party plugin where the set-up description uses Groovy DSL

Then, you want to use a “library” which tells you to set up their dependency like this, with Groovy DSL:

Modify your root build.gradle file:

buildscript {
// …
dependencies {
// …
classpath ‘com.someNamespace:theTool:<LATEST_RELEASE>’
}
}

Apply the plugin from any of the modules configured in your project. In a simple Android app your app/build.gradle file:

apply plugin: ‘theTool’

So, you adapt this setup to your own build script, using your Kotlin DSL knowledge! Yay!

It should work in no time, right?

In your versions catalog libs.versions.toml file:

[versions]
(..)
gradlePlugins-sometool = "6.1.0"


[plugins]
(…)
sometool = { id = "com.someNamespace.sometool", version.ref = "gradlePlugins-sometool" }

In the app module gradle.build.kts file:

alias(libs.plugins.sometool)

A sad, sad state of affairs

Alas, the build fails with the error:

org.gradle.api.plugins.UnknownPluginException: Plugin [id: ‘com.someNamespace.sometool’, version: ‘6.1.0’] was not found in any of the following sources:

  • Gradle Core Plugins (plugin is not in ‘org.gradle’ namespace)
  • Included Builds (No included builds contain this plugin)
  • Plugin Repositories (could not resolve plugin artifact ‘com.someNamespace.sometool:com.someNamespace.sometool.gradle.plugin:6.1.0’)
    Searched in the following repositories: (…)

Well, that’s not good.

What is happening?

I’ll cite this explanation from Tom Ritchie (Jan 24 2023 on Stackoverflow):

In gradle, plugins are specified by an id with some exception for embedded plugins.
(..)
Gradle looks in each defined repository in the order that the repositories are defined. It gets the implementation from the first repository that contains the plugin with the given id. How does Gradle find the implementation jar associated with a given id? Let’s assume you’re looking for « io.spring.convention.root ». Gradle looks for the artifact (group= »io.spring.convention.root », artifact= »io.spring.convention.root.gradle.plugin », version=?) If it can’t find that artifact, that repository does not contain that plugin. If it finds that artifact, it then retrieves the « maven-metadata.xml » file associated with that plugin. That metadata will contain a dependency on the artifact that contains the jar file that implements the plugin. Gradle then downloads that implementation.

Often, plugins can be found in the Gradle plugins page, which is the default used in your build script, as defined in your settings.gradle.kts file:

pluginManagement {
    repositories {
        //...
        gradlePluginPortal()
    }
}

The Gradle plugins page’s content can be seen and searched for by hand, here; https://plugins.gradle.org.

But not all plugins are published to the Gradle Plugin Portal, nor are they in any other repository.

You may have “local” and external plugins defined in your own buildSrc directory. Then, some plugins are published to other repositories.

Gradle documentation

From the current documentation:

Since the plugins{} DSL block only allows for declaring plugins by their globally unique plugin id and version properties, Gradle needs a way to look up the coordinates of the plugin implementation artifact. To do so, Gradle will look for a Plugin Marker Artifact with the coordinates plugin.id:plugin.id.gradle.plugin:plugin.version.

This is where my error message got its strange value that was helpfully posted in the error message.

Gradle expects plugins to be published with specific « plugin markers ». This should look like this in terms of infrastructure in a maven-type repository:

Notice the artifactId defined with a « .gradle.plugin » suffix, and with a dependency to the actual code module.

Troubleshooting how-to

I hope this post can help someone with the same problem! In my case, I found the solution by searching github for others using the same plugin with Kotlin DSL build scripts to see how they referenced it.

I initially searched for a solution with the error message, but it pulled up a lot of similar issues linked to typos in the build scripts themselves (wrong version, wrong name, or incompatible Gradle plugin version…) or due to network issues (proxy configuration).

My real example

In case someone has the exact same issue as me, the plugin I was trying to set up is this awesome tool called « Shot ».

I wanted to use the lovely Screenshot testing library called Shot, which supports Jetpack Compose. It was initially developed as part of the Facebook SDK, and published by Pedro Gómez on GitHub. It is written in Kotlin and Scala.

To set up the Gradle plugin, Pedro gives the Groovy script modifications needed. As I described above, this failed in my case when I adapted it to my Kotlin DSL build scripts.

error is: org.gradle.api.plugins.UnknownPluginException: Plugin [id: ‘com.karumi.shot’, version: ‘6.1.0’] was not found in any of the following sources:

  • Gradle Core Plugins (plugin is not in ‘org.gradle’ namespace)
  • Included Builds (No included builds contain this plugin)
  • Plugin Repositories (could not resolve plugin artifact ‘com.karumi.shot:com.karumi.shot.gradle.plugin:6.1.0’)
    Searched in the following repositories: (…)

This plugin is actually published on Maven central, as shown on Maven Repository website, at
https://mvnrepository.com/artifact/com.karumi/shot
Its location is thus https://repo1.maven.org/maven2/. You can see all the files at:

https://repo1.maven.org/maven2/com/karumi/shot/maven-metadata.xml, which contains the identifying groupId and artifactId.

<groupId>com.karumi</groupId>
<artifactId>shot</artifactId>

Unfortunately, the referencing system for Gradle in my Kotlin DSL script works by looking for something called
‘com.karumi.shot:com.karumi.shot.gradle.plugin:6.1.0’. As described above, I had to customize the resolution strategy to use the right identifier.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *