gradle-kotlin-dsljsonschema2pojo

Gradle: sharing plugins with convention plugins cause build to fail due to missing classes


I have a Micronaut-based project written in Java with several sub-projects. My project-setup is as follows:

|-> gradle
|   |-> plugins
|       |-> micronaut-plugins
|       |   |-> src/main/kotlin
|       |   |   |-> my-app.gradle.kts
|       |   |   |-> my-base.gradle.kts
|       |   |-> build.gradle.kts
|       |-> settings.gradle.kts
|-> core
|   |-> build.gradle.kts
|-> app1
|   |-> build.gradle.kts
|   |-> gradle.properties
|-> gradle.properties
|-> settings.gradle.kts

I simplified the structure to keep things as easy as possible, the main point here is, that I have two other sub-projects like app1. These three sub-projects are meant to be built as docker images. Each of them applies the plugin my-app. Also, they depend on the core sub-project, which is a library not meant to be shipped alone. This core-project again applies the plugin my-base. Furthermore, my-base is also applied by my-app.gradle. To sum it up:

Good news is, that I was successful in setting up Spotbugs, Checkstyle, PMD and Jacoco as well as the Micronaut dependency shared by all the sub-projects as planned. However, the sub-projects meant to be built as docker images also need to generate Java-code from jsonschema files. There is some common configuration, so I'd like to centralize that part as well, but I fail, because once apply the necessary plugin (jsonschema2pojo) in my-app.gradle.kts, Gradle fails to find the classes used by that plugin resulting in messages like:

Task :plugins:micronaut-plugins:compileKotlin FAILED e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath: class org.jsonschema2pojo.gradle.JsonSchemaExtension, unresolved supertypes: org.jsonschema2pojo.GenerationConfig Adding -Xextended-compiler-checks argument might provide additional information.

I suppose that the dependency to the plugin is added too late within the build-process, so classes are not available in time.

gradle/plugins/micronaut-plugins/src/main/kotlin/my-app.gradle.kts (note the line I have commented out as it would cause trouble):

import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.project

plugins {
    id("my-base")
    id("io.micronaut.application")
    //id("org.jsonschema2pojo")
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(project(":core"))
    implementation("io.micronaut:micronaut-jackson-databind")
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2")

    testImplementation("org.testcontainers:junit-jupiter:1.17.6")
    testImplementation("org.testcontainers:testcontainers:1.17.6")
    testImplementation("org.testcontainers:kafka:1.17.6")
    testImplementation("org.awaitility:awaitility:4.1.1")
    testImplementation("io.micronaut:micronaut-http-client")
}

tasks.dockerfile {
    baseImage.set("ghcr.io/graalvm/jdk:ol9-java17-22.3.0")
}

tasks.dockerBuild {
    images.add("${project.name}:${project.version}")
}

gradle/plugins/micronaut-plugins/src/main/kotlin/my-base.gradle.kts:

import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.repositories

plugins {
    id("java")
    id("com.github.spotbugs")
    jacoco
    checkstyle
    pmd
}

repositories {
    mavenCentral()
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
}

dependencies {
    annotationProcessor("io.micronaut:micronaut-http-validation")
    implementation("io.micronaut:micronaut-jackson-databind")
    implementation("io.micronaut.kafka:micronaut-kafka")
    implementation("io.micronaut.kafka:micronaut-kafka-streams")
    implementation("jakarta.annotation:jakarta.annotation-api")
    implementation("io.micronaut:micronaut-validation")

    testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
    testImplementation("io.micronaut.test:micronaut-test-junit5")
    testImplementation("org.mockito:mockito-core:5.1.1")
    testImplementation("org.apache.kafka:kafka-streams-test-utils:3.4.0")
}

tasks.jacocoTestReport {
    dependsOn(tasks.test) // tests are required to run before generating the report

    reports {
        xml.required.set(true)
        html.required.set(true)
        csv.required.set(false)
    }
}

spotbugs {
    ignoreFailures.set(true)
    excludeFilter.set(file(layout.projectDirectory.toString() + "/../etc/codeanalyse/spotbugs-excludes.xml"))
    layout.projectDirectory.toString()
}

checkstyle {
    isIgnoreFailures = true
    configFile = file(layout.projectDirectory.toString() + "/../etc/codeanalyse/checkstyle-config.xml")
    setConfigProperties(
            "checkstyle.suppressionfilter.config" to layout.projectDirectory.toString() + "/../etc/codeanalyse/checkstyle-supressions.xml"
    )
}

pmd {
    isIgnoreFailures = true
    ruleSetFiles = files(layout.projectDirectory.toString() + "/../etc/codeanalyse/pmd-excludes.xml")
}

gradle/plugins/micronaut-plugins/build.gradle.kts:

plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
}

dependencies {
    implementation("io.micronaut.gradle:micronaut-gradle-plugin:3.7.0")
    implementation("org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:1.1.3")
    implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13")
}

gradle/plugins/settings.gradle.kts:

pluginManagement {
    repositories.gradlePluginPortal()
}

dependencyResolutionManagement {
    repositories.gradlePluginPortal()
}

include("micronaut-plugins")

core/build.gradle.kts:

plugins {
    id("my-base")
    id("io.micronaut.minimal.library") version "3.7.0"
}

app1/build.gradle.kts:

plugins {
    id("my-app")
    id("org.jsonschema2pojo") version "1.1.3"
    id("io.micronaut.test-resources") version "3.7.0"
}

application {
    mainClass.set("app1.Application")
}

// that's were I'd like to save a few lines:
jsonSchema2Pojo {
    setSource(files("src/main/resources/jsonschema"))
    targetPackage = "app1.outgoing"
    targetDirectory = file(layout.buildDirectory.dir("generated-sources/jsonschema2pojo"))
    setAnnotationStyle("jackson2")
    formatTypeMapping = hashMapOf(
        "uuid" to "java.lang.String"
    )
}

app1/gradle.properties:

version=1.0.0

gradle.properties (Micronaut version not up-to-date as we have troubles migrating):

org.gradle.caching = true
micronautVersion=3.4.4

settings.gradle.kts:

pluginManagement {
    repositories.gradlePluginPortal()
    includeBuild("gradle/plugins")
}

dependencyResolutionManagement {
    repositories.mavenCentral()
}

rootProject.name = "the-project"

include("core")
include("app1")

So, can someone please tell me where my mistake is? My best bet is the scope when giving the dependency, but no other scope I tried did the job. Please note that I am relatively new to Gradle, so please provide Kotlin-code if possible. I am on Gradle 7.6.0. Gradle 8.0 failed, probably because Micronaut dependencies are too old.


Solution

  • The error

    Task :plugins:micronaut-plugins:compileKotlin FAILED 
    e: Supertypes of the following classes cannot be resolved. 
      Please make sure you have the required dependencies in the classpath: 
      class org.jsonschema2pojo.gradle.JsonSchemaExtension, 
        unresolved supertypes: org.jsonschema2pojo.GenerationConfig 
    

    Indicates that there's a problem in :plugins:micronaut-plugins, so that's where to focus.

    It also says that a 'supertype' is missing, org.jsonschema2pojo.GenerationConfig.

    Looking in the source code, we can see that the GenerationConfig class is defined in the subproject named jsonschema2pojo-core.

    Looking at the build script for the org.jsonschema2pojo Gradle plugin shows that it has a dependency on org.jsonschema2pojo:jsonschema2pojo-core

    // jsonschema2pojo/jsonschema2pojo-gradle-plugin/build.gradle
    
    dependencies {
      implementation "org.jsonschema2pojo:jsonschema2pojo-core:${project.version}"
    }
    

    However, this dependency is defined using implementation, not api (see: Gradle Implementation vs API configuration), so any Gradle projects that depend on the jsonschema2pojo-gradle-plugin project will not automatically depend on jsonschema2pojo-core.

    Aside: changing from implementation to api would be a good improvement in the jsonschema2pojo project

    Therefore, when adding a dependency on the jsonschema2pojo plugin, you should also depend on jsonschema2pojo-core.

    dependencies {
      implementation("org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:1.1.3")
      implementation("org.jsonschema2pojo:jsonschema2pojo-core:1.1.3")
    }