Hi ! I have a module based gradle project. Module ...
# gradle
m
Hi ! I have a module based gradle project. Module A and B is api. Module C is where the application it self is, where I can start the application. Module A and B just have their own dependencies in their own
build.gradle.kts
. Module C where the application itself is I have a task named "Jar". This is the tasks now:
Copy code
tasks {
    named<Jar>("jar") {
        archiveBaseName.set("app")

        manifest {
            attributes["Main-Class"] = "no.nav.sokos.app.ApplicationKt"
            attributes["Class-Path"] = configurations.runtimeClasspath.get().joinToString(separator = " ") {
                it.name
            }
        }

        doLast {
            configurations.runtimeClasspath.get().forEach {
                val file = File("$buildDir/deps/${it.name}")
                if (!file.exists())
                    it.copyTo(file)
            }
        }
    }
}
How can I use
ShadowJar
instead? I am new to setting up this so I appreciate for all help I get 🙂
Unsolved reference to
.runtimeClasspath
. Anyone can explain?
And with the configuration above with task "Jar" i get following error in my Github action:
Copy code
1 problem was found storing the configuration cache.
- Task `:app:jar` of type `org.gradle.api.tasks.bundling.Jar`: cannot serialize Gradle script object references as these are not supported with the configuration cache.
  See <https://docs.gradle.org/8.1/userguide/configuration_cache.html#config_cache:requirements:disallowed_types>

See the complete report at file:///home/runner/work/sokos-nav-oppdrag/sokos-nav-oppdrag/build/reports/configuration-cache/ai66dng8t0twxzt5rs7pxez1/6zbm4f9ywq54fxnfhgwuzm8cj/configuration-cache-report.html
I am using the
gradle/gradle-build-action@v2.9.0
in my github action to build the project.
Copy code
- uses: gradle/gradle-build-action@v2.9.0
        env: # Eksluder test dependencies
          DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS: compileClasspath|runtimeClasspath
        with:
          dependency-graph: generate-and-submit
          arguments: --configuration-cache build
v
My recommendation is, do not even try to build a bad practice fat jar, whether you use the shadow plugin or doing it manually. See https://fatjar.net for some quotes about them. Instead better use the
application
plugin to build a proper distribution of your app with your code, all dependencies, and generated start scripts that properly assemble everything. If you insist on following that bad route, some hints: • you probably try to do that in a precompiled script plugin. There generated type-safe accessors like
configurations.runtimeClasspath
are only available for things unconditionally created by plugins applied in the
plugins { ... }
block of the very same script. So you need to apply a plugin that adds this configuration in that script, or get the configuration by String name instead • If you set the
Class-Path
attribute like that, you put the paths to the jars in the Gradle cache directory in the, so the jar will effectively only work on your computer and only until Gradle cleans up the cache and throws out those files • If you really want to use the shadow plugin, you probably should throw out practically all configuration you showed anyway, maybe except for the name configuration, and just let the plugin do its dirty and fragile work. And if you need more help with it, you should probably ask that plugin for help.
m
Any example with
application
plugin when you have submodules?
v
Submodules or not does not matter, they are just dependencies like any other
Usually Example 1 and 2 is all you need, unless you want or need more customization
I personally also like to configure having the start scripts at the root of the distribution and not within
bin/
, but that's a matter of taste
m
So basically, like this example:
Copy code
application {
    mainClass.set("no.nav.sokos.skattekort.person.ApplicationKt")
}

sourceSets {
    main {
        java {
            srcDirs("$buildDir/generated/src/main/kotlin")
        }
    }
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

tasks {

    withType<ShadowJar>().configureEach {
        enabled = true
        archiveFileName.set("app.jar")
        manifest {
            attributes["Main-Class"] = "no.nav.sokos.skattekort.person.ApplicationKt"
        }
    }

    ("jar") {
        enabled = false
    }

    withType<Test>().configureEach {
        useJUnitPlatform()

        testLogging {
            showExceptions = true
            showStackTraces = true
            exceptionFormat = FULL
            events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
        }

        reports.forEach { report -> report.required.value(false) }
    }
}
Its recommended using the application plugin and get rid of the shadowjar ?
v
So basically, like this example:
No, • you do not apply the
application
plugin • you still have shadow configuration stuff in there • you must not disable the
jar
task
Its recommended using the application plugin and get rid of the shadowjar ?
It is my recommendation, yes. As I said, see https://fatjar.net for some quotes about why.
m
This is now the updated
build.gradle.kts
setup:
Copy code
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
    kotlin("jvm") version "1.9.10"
    kotlin("plugin.serialization") version "1.9.10"

    application
}

group = "no.nav.sokos"

repositories {
    mavenCentral()
    maven { url = uri("<https://maven.pkg.jetbrains.space/public/p/ktor/eap>") }
}

val ktorVersion = "2.3.5"
val logbackVersion = "1.4.11"
val logstashVersion = "7.4"
val jacksonVersion = "2.15.2"
val prometheusVersion = "1.11.4"
val kotlinLoggingVersion = "3.0.5"
val janionVersion = "3.1.10"
val natpryceVersion = "1.6.10.0"
val kotestVersion = "5.7.2"
val mockkVersion = "1.13.8"
val restAssuredVersion = "5.3.2"
val swaggerRequestValidatorVersion = "2.37.0"
val mockOAuth2ServerVersion = "2.0.0"
val ojdbc10 = "19.20.0.0"
val papertrailappVersion = "1.0.0"

dependencies {

    // Ktor server
    implementation("io.ktor:ktor-server-core-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-call-logging-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-call-id-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-status-pages:$ktorVersion")
    implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-request-validation:$ktorVersion")
    implementation("io.ktor:ktor-server-swagger:$ktorVersion")

    // Ktor client
    implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
    implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
    implementation("io.ktor:ktor-client-apache-jvm:$ktorVersion")

    implementation("io.ktor:ktor-serialization-jackson-jvm:$ktorVersion")

    // Security
    implementation("io.ktor:ktor-server-auth-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktorVersion")

    // Jackson
    implementation("io.ktor:ktor-serialization-jackson:$ktorVersion")
    implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
    implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion")

    // Monitorering
    implementation("io.ktor:ktor-server-metrics-micrometer-jvm:$ktorVersion")
    implementation("io.micrometer:micrometer-registry-prometheus:$prometheusVersion")

    // Logging
    implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion")
    runtimeOnly("org.codehaus.janino:janino:$janionVersion")
    runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")
    runtimeOnly("net.logstash.logback:logstash-logback-encoder:$logstashVersion")
    runtimeOnly("com.papertrailapp:logback-syslog4j:$papertrailappVersion")

    // Config
    implementation("com.natpryce:konfig:$natpryceVersion")

    // Database
    implementation("com.zaxxer:HikariCP:5.0.1")
    implementation("com.oracle.database.jdbc:ojdbc10:$ojdbc10")

    // Test
    testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
    testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
    testImplementation("io.ktor:ktor-server-test-host-jvm:$ktorVersion")
    testImplementation("io.mockk:mockk:$mockkVersion")
    testImplementation("io.rest-assured:rest-assured:$restAssuredVersion")
    testImplementation("com.atlassian.oai:swagger-request-validator-restassured:$swaggerRequestValidatorVersion")
    testImplementation("no.nav.security:mock-oauth2-server:$mockOAuth2ServerVersion")

}

application {
    mainClass.set("no.nav.sokos.skattekort.person.ApplicationKt")
}

sourceSets {
    main {
        java {
            srcDirs("$buildDir/generated/src/main/kotlin")
        }
    }
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

tasks {

    withType<Test>().configureEach {
        useJUnitPlatform()

        testLogging {
            showExceptions = true
            showStackTraces = true
            exceptionFormat = FULL
            events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
        }

        reports.forEach { report -> report.required.value(false) }
    }
}
But when I open the jar, and check the MANIFEST.MF file, the only content is
Copy code
Manifest-Version: 1.0
Should there not be a line which also contain where the Main file class is?
CleanShot 2023-10-13 at 15.59.53.png
v
Should there not be a line which also contain where the Main file class is?
No, it shouldn't. The
application
plugin builds a proper distribution, not a fat jar, as described above. Also have a look at the documentation I linked you to.
Btw. I also recommend using a version catalog instead of local variables for dependency versions. And if you are on a recent Gradle version, you do not need to use
.set(...)
anymore, but can use
=
even in Kotlin DSL.
👀 1