Hi all! So I have an external artifact with shared...
# community-support
t
Hi all! So I have an external artifact with shared build conventions that I import into another project. These build conventions consist of several Gradle plugins all distributed via a single JAR. The conventions are then used in two different ways, (a) directly in
build.gradle.kts
, so applied via the
plugins { ... }
block and (b) via project-specific build conventions, that apply the plugin and preconfigure it. Now it can happen that the JAR of one plugin of the shared build conventions is applied via the project-specific build conventions and directly in the
build.gradle.kts
(of course, different plugins) and in this case Gradle tells me
Copy code
Error resolving plugin [id: 'plugin.applied.in.build.gradle.kts', version: '0.1.0']
> The request for this plugin could not be satisfied because the plugin is already on the classpath with an unknown version, so compatibility cannot be checked.
so I remove the
version
identifier from the
plugins {}
block. But then for another build convention plugin, that I apply on the root build, I get
Copy code
Plugin [id: 'other.plugin.applied.in.root.build.gradle.kts'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Included Builds (None of the included builds contain this plugin)
- Plugin Repositories (plugin dependency must include a version number for this source)
even though the root build has project-specific build conventions applied before as well, which carry the JAR as a dependency as well. So I'm kind of puzzled why some
plugins {}
blocks need a version and others must not get one. I feel I'm doing something wrong.
j
we have a similar setup, we use a pluginManagement section in our settings.gradle.kts to align all plugin versions. we have a single property in our gradle.properties which defines the version of the plugin jar to pull. settings.gradle.kts looks like
Copy code
// <https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_management>
// > The pluginManagement {} block may only appear in either the settings.gradle
// > file, where it must be the first block in the file, or in an Initialization
// > Script.
pluginManagement {

    val ourPluginVersion: String = providers.gradleProperties("ourPluginVersion")
    val openapiPluginVersion: String = providers.gradleProperties("openapiPluginVersion")

    // /!\ Make sure plugins are loaded in the root build.gradle.kts, with `apply false` if not
    // used. This will ensure Gradle loads them only once, which saved a few seconds of
    // configuration.
    plugins {

        id("com.sample.api-conventions") version ourPluginVersion
        id("com.sample.app-conventions") version ourPluginVersion
        id("com.sample.common-conventions") version ourPluginVersion
        id("com.sample.contract-first") version ourPluginVersion
        id("com.sample.contract-first-client") version ourPluginVersion
        id("com.sample.contract-first-common") version ourPluginVersion
        id("com.sample.contract-first-ws") version ourPluginVersion
        id("com.sample.idea-exclusion-project") version ourPluginVersion
        id("com.sample.idea-exclusion-root") version ourPluginVersion
        id("com.sample.jooq-generation") version ourPluginVersion
        id("com.sample.library-conventions") version ourPluginVersion
        id("com.sample.lombok-conventions") version ourPluginVersion
        id("com.sample.mjml-components") version ourPluginVersion
        id("com.sample.mjml-emails") version ourPluginVersion
        id("com.sample.no-dep-app-conventions") version ourPluginVersion
        id("com.sample.openapi-contract") version ourPluginVersion
        id("com.sample.phase-duration") version ourPluginVersion
        id("com.sample.project-description") version ourPluginVersion
        id("com.sample.sonar") version ourPluginVersion
        id("com.sample.typescript-generation") version ourPluginVersion
        id("org.springdoc.openapi-gradle-plugin") version openapiPluginVersion
    }
then in our build.gradle.kts we apply the plugins but never provide an explicit version.
t
I tried that in a similar fashion, by having some
Copy code
pluginManagement {
   resolutionStrategy {
       eachPlugin {
         if (requested.id.namespace?.startsWith("my.plugin.prefix") == true) {
             useModule("my.group:my.artifact:$myBuildLogicVersion")
         }
       }
   }
}
but the problem is that this
pluginManagement
only works for plugins applied to build scripts, if you apply the plugin in a build convention itself (which is a build by itself) this block is not executed, so you have to define "my.group:..." in the
dependencies
block of that build. Thinking about it, maybe one needs a combination of both, only having a
compilyOnly
dependency in the build convention build and then the above thing for the actual application of the plugins.
v
Would it be possible to show an MCVE with the behavior? Probably much easier to reason on the situation then.
Also what you say "similar fashion" is something completely different. What Jean showed just defines default versions if no version is given, so should effectively be the same as specifying the same version where you apply the pluign. What you showed caters for missing plugin marker artifacts that resolve the plugin id to an actual code-containing jar.
💯 1
j
The problem is that this pluginManagement only works for plugins applied to build scripts, if you apply the plugin in a build convention itself (which is a build by itself) this block is not executed, so you have to define "my.group:..." in the dependencies block of that build
hmm in my example
com.sample.common-conventions
's definition contains
Copy code
plugins {
    `java-library`
    kotlin("jvm")
    kotlin("plugin.spring")
    id("com.sample.idea-exclusion-project")
    id("com.sample.jacoco")
    id("com.sample.project-description")
}
and we do have the dependencies declared same as for external plugins ....
Copy code
dependencies {
    implementation(project(":conventions:jacoco-convention"))
    implementation(project(":conventions:sonar-convention"))
    implementation(project(":idea-exclusion-plugin"))
    implementation(buildLibs.git.properties.gradle.plugin)
    implementation(buildLibs.spring.boot.gradle.plugin)
    implementation(buildLibs.kotlin.gradle.plugin)
    implementation(buildLibs.kotlin.allopen)
    implementation(buildLibs.kotlin.jvm.plugin)
    implementation(buildLibs.kotlin.spring.plugin)
}
Our plugins build is itself a multiproject build so we don't need to explicit the versions there, all our plugins are always released together. We only enforce the versions for the build scripts and thus have a single version to pull.
v
The "is already on the classpath with an unknown version" message usually comes if the jar containing the plugin was already somehow added to the classpath without being added through a
plugins { ... }
block, but for example as dependency in
buildSrc
. But also if you bring the jar to the classpath with one plugin id and then apply another plugin id. For example if you have
kotlin("jvm") version "1.6.10" apply false
in the settings script and
_kotlin_("android") _version_ "1.6.10" _apply_ false
in the build script, or one in a parent build script and the other in a child build script. Because Gradle does not know about the actual code artifact being the same here, but it is about plugin ID marker artifacts. As the two are different plugin IDs but result to the same code JAR, the higher one already adds the plugin of the lower one to the classpath but not knowing about the plugin id. I'd say this has room for improvement in Gradle, so that where the higher plugin is added to the classpath it recognizes all plugin IDs in it as added with a known version, so that it would properly complain if a different version is used and support it if the same version is used.
Hm, no, I don't think Gradle can improve here. The problem is, that versions do not have to be aligned. A plugin author could release plugin with ID A and version 1 pointing to code artifact F with version 2, and plugin with ID B and version 3 also pointing to code artifact F with version 2. So all three versions could theoretically be different and thus cannot be reasoned about.
So it is probably best to add one of the plugins with version (or the actual code artifact) to the highest common classpath, eventually with
apply false
if there none should be applied and use no version everywhere else.