Creating a fat jar using Kotlin and Gradle - compile vs implementation? - gradle

I'm tinkering with a simple "hello world" project in Kotlin & Gradle (see below). I've added the "fat jar" collection stuff to bring in the Kotlin dependency, but when I try to run java -jar build/libs/hello-1.0-SNAPSHOT.jar I get the java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics error because the dependencies aren't available at runtime.
I've solved that problem by changing implementation to compile, which makes everything work fine. But from what I understand, we shouldn't be using compile anymore, and neither api nor implementation makes the "fat jar" collection process work, and as I look at the other options for dependencies I'm not sure which to use.
Question: what's the "right" thing to do in a case like this?
// build.gradle
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.41'
}
group 'com.example.test'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
// if I change "implementation" to "compile", running the jar works
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
jar {
manifest {
attributes "Main-Class": "ApplicationKt"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
// Application.kt
fun main(args: Array<String>) {
println("hello world")
}

The compile dependency is deprecated. You should use implementation to declare your implementation dependencies, and compileClasspath to get all the compilation dependencies.

Related

How to create a fat Jar in Gradle with Kotlin DSL and plugin

I did a lot of research on how to create fat jars with Gradle.
However, I can not figure out how to it with Kotlin DSL and a plugin.
I have this code:
plugins {
application
id("org.openjfx.javafxplugin") version "0.0.9"
id("com.github.johnrengelman.shadow") version "6.1.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation("javax.vecmath", "vecmath", "1.5.2")
implementation("org.apache.commons", "commons-csv", "1.8")
}
application {
mainModule.set("de.weisbrja")
mainClass.set("de.weisbrja.App")
}
javafx {
modules("javafx.controls")
}
modularity.disableEffectiveArgumentsAdjustment()
But I do not know how to specify the main class for the fat jar manifest.
The tutorial I followed did this:
jar {
manifest {
attributes "Main-Class": "com.baeldung.fatjar.Application"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
But that is Groovy DSL and not Kotlin DSL and I am not really familiar with the Kotlin DSL yet, so I do not know how to convert this to Kotlin DSL.
Help very much appreciated.
try this vanilla solution in kotlin dsl (build.gradle.kts)
dependencies {
implementation(group="org.mycomp",name="foo",version="1.0")
}
tasks {
jar {
//package org.mycomp.foo inside the .jar file
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}
}
alternatively you can use the shadowJar plugin
plugins {
id("com.github.johnrengelman.shadow") version "7.0.0"
}
then just run ./gradlew :shadowJar

QueryDSL annotation processor and gradle plugin

Cannot understand how to configure build.gradle for using querydsl annotation processor without any jpa/jdo/mongo. I want to use #QueryEntity annotation to generate Q classes so then I will be able to compose dynamic SQL queries using DSL support then convert query to plain text and provide it to Spring R2DBC DatabaseClient executor.
Is there a way what gradle querydsl apt plugin and querydsl annotation processor to use for generating Q classes with #QueryEntity annotations in build.gradle file?
I'm using gradle 5, Spring Data R2DBC, Spring Boot, plan to integrate queryDsl with annotation processsor.
That's my currect build.gradle:
plugins {
id 'java'
id 'org.springframework.boot' version '2.2.1.RELEASE'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.8"
}
apply plugin: 'io.spring.dependency-management'
group = 'com.whatever'
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
ext {
springR2dbcVersion = '1.0.0.RELEASE'
queryDslVersion = '4.2.2'
}
dependencies {
implementation("com.querydsl:querydsl-sql:${queryDslVersion}")
implementation("com.querydsl:querydsl-apt:${queryDslVersion}")
implementation('org.springframework.boot:spring-boot-starter-webflux')
compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}")
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation('io.projectreactor:reactor-test')
}
test {
useJUnitPlatform()
}
Generally speaking, you shouldn't use the QueryDSL plugin.
In order to configure QueryDSL generation you just need the relevant querydsl module, the annotation processors and the generated source dir. For instance, with lombok integration, this configuration should work (you might need to play with the exact QueryDSL modules you need):
buildscript {
ext {
springBootVersion = '${springBootVersion}'
queryDslVersion = '4.2.2'
javaxVersion = '1.3.2'
}
}
plugins {
id 'idea'
}
idea {
module {
sourceDirs += file('generated/')
generatedSourceDirs += file('generated/')
}
}
dependencies {
// QueryDSL
compile "com.querydsl:querydsl-sql:${queryDslVersion}"
annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:general")
// Lombok
compileOnly "org.projectlombok:lombok:${lombokVersion}"
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
implementation("org.projectlombok:lombok:${lombokVersion}")
// Possibly annotation processors for additional Data annotations
annotationProcessor("javax.annotation:javax.annotation-api:${javaxVersion}")
/* TEST */
// Querydsl
testCompile "com.querydsl:querydsl-sql:${queryDslVersion}"
testAnnotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:general")
// Lombok
testImplementation("org.projectlombok:lombok:${lombokVersion}")
testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}")
testCompileOnly("org.projectlombok:lombok:${lombokVersion}")
}
Additional information: https://github.com/querydsl/querydsl/issues/2444#issuecomment-489538997
I want to leave this answer here as I struggled for several hours finding a solution that works for Kotlin (The question doesn't have a Java restriction as far as I can tell and there is really little information around for this). Kotlin supports annotation processing with the kapt plugin. In order to use this plugin for QueryDSL you need to 1. define the kapt plugin, and 2. specify the dependency that will do the processing, in this case com.querydsl:querydsl-apt. I used the general task for my plugin execution, but according to the documentation there are other options available (probably this can be useful for JPA, JDO, Hiberante with some extra tweaks)
plugins {
kotlin("kapt") version "1.4.10"
}
dependencies {
implementation("com.querydsl:querydsl-mongodb:4.4.0")
kapt("com.querydsl:querydsl-apt:4.4.0:general")
}
Now, whenever you run gradle build the annotation processing will trigger the DSL query class generation for your classes annotated with #QueryEntity. I hope it helps in case someone needs this for Kotlin.
This worked for me (Please follow the exact same order in the dependency)
sourceSets {
generated {
java {
srcDirs = ['build/generated/sources/annotationProcessor/java/main']
}
}
}
dependencies {
api 'com.querydsl:querydsl-jpa:4.4.0'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor('com.querydsl:querydsl-apt:4.4.0:jpa')
annotationProcessor('javax.annotation:javax.annotation-api')
}
This works!!!
ext {
queryDslVersion = '4.2.1'
}
sourceSets {
main {
java {
srcDirs = ['src/main/java', 'build/generated/sources/annotationProcessor/java/main']
}
}
}
dependencies {
compile("com.querydsl:querydsl-core:${queryDslVersion}")
compile("com.querydsl:querydsl-jpa:${queryDslVersion}")
}
dependencies {
compile "com.querydsl:querydsl-jpa:${queryDslVersion}"
compileOnly 'org.projectlombok:lombok:1.16.18'
annotationProcessor(
"com.querydsl:querydsl-apt:${queryDslVersion}:jpa",
"org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final",
"javax.annotation:javax.annotation-api:1.3.2",
"org.projectlombok:lombok"
)
for sinle gradle project just add next lines to the same build.gradle
for multi module gradle project add next lines to build.gradle of module where are jpa entities:
implementation("com.querydsl:querydsl-core:${queryDslVersion}")
annotationProcessor(
"com.querydsl:querydsl-apt:${queryDslVersion}:jpa",
"org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final",
"javax.annotation:javax.annotation-api:1.3.2")
and next line to build.gradle of module where are jpa repositories:
implementation("com.querydsl:querydsl-jpa:${queryDslVersion}")

dependencies added in subprojects.forEach in gradle multi-module kotlin DSL is not visible to sub projects

I have multi-module gradle project with kotlin dsl called stream-data-processing. It is in github here.
The build.gradle.kts file of root project is -
plugins {
base
java
}
allprojects {
group = "streams-data-processing"
version = "1.0"
repositories {
jcenter()
mavenCentral()
mavenLocal()
}
dependencies {
subprojects.forEach {
compile("org.apache.kafka:kafka-streams:2.2.0")
testImplementation("junit:junit:4.12")
}
}
}
settings.gradle.kts is -
rootProject.name = "stream-data-processing"
include ("word-count-demo")
I have some sub-project called word-count-demo.
The build.gradle.kts file for this sub project is -
plugins {
java
application
}
But the classes in kafka-streams are not available in word-count-demo.
when I did `gradle word-count-demo:dependencies, it doesn't show the kafka dependencies available to the sub project.
I don't want to explicitly specify the dependencies in every project.
What is the mistake that went wrong here?
It appears this would be adding the same dependencies multiple times. I think you need to flip it around and call dependencies inside subprojects, and outside of allprojects, like so:
allprojects {
group ...
version ...
repositories ...
}
subprojects {
dependencies {
compile("org.apache.kafka:kafka-streams:2.2.0")
testImplementation("junit:junit:4.12")
}
}

Kotlin Gradle Could not find or load main class

I tried to copy the Spring Boot Kotlin sample project https://github.com/JetBrains/kotlin-examples/tree/master/tutorials/spring-boot-restful. I Added some more dependencies and when I tried to build the executable jar and run it, I got the error:
Could not find or load main class...
Gradle build script:
buildscript {
ext.kotlin_version = '1.1.3' // Required for Kotlin integration
ext.spring_boot_version = '1.5.4.RELEASE'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Required for Kotlin integration
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
}
}
/*plugins {
id 'org.springframework.boot' version '2.0.0.RELEASE'
}*/
apply plugin: 'kotlin' // Required for Kotlin integration
apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-rest-service'
version = '0.1.0'
from {
(configurations.runtime).collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.Applicationkt'
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin/'
test.java.srcDirs += 'src/test/kotlin/'
}
repositories {
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // Required for Kotlin integration
compile("org.springframework.boot:spring-boot-starter-web")
compile group: 'org.apache.camel', name: 'camel-quartz2', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-http4', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-docker', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-aws', version: '2.20.2'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Change Applicationkt to ApplicationKt will work, and BTW you may upgrade Kotlin version to 1.3.50.
By Applicationkt I mean the one in this line:
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.Applicationkt'
Kotlin compiles the Application file in two different files:
one file called Application.class with the Springboot things
another file called ApplicationKt.class with the main method
In this second file is where the main function is located at, so you have to use this name in the build.gradle file.
mainClassName = 'org.jetbrains.kotlin.demo.ApplicationKt'
Update your build.gradle to
jar {
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.ApplicationKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
with an upper case K in ApplicationKt.
This is required because of the way Kotlin compiles to Java Bytecode. The fun main() function in Kotlin is not attached to any class, but Java always requires a class and does not support classless functions.
The Kotlin compiler has to create a Java class. Because you already defined a class Application it created one with the suffix Kt for the functions in your Kotlin file org/jetbrains/kotlin/demo/Application.kt. You have to set this class so that the JVM can find it.
BTW a Jar file is just a Zip file, you can unpack it and see for yourself if the ApplicationKt.class is there.
For me the main function needed to be outside the class body
#SpringBootApplication
#Configuration
class Application
(private val locationRepository: LocationRepository,
) : CommandLineRunner {
override fun run(vararg args: String?) {
whatever()
}
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
Indeed, Kotlin create file ApplicationKt.class in the jar if your main class file is named Application.kt. You have to add the following lines:
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = 'org.jetbrains.kotlin.demo.ApplicationKt'
If you use the classic jar plugin, you can do as below (which is described in previous responses):
jar {
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.ApplicationKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
However, my preference is to use bootJar plugin which is much clear and which allow me to use layered jars for example:
bootJar {
layered() // Not useful if you don't want to use layered jars
}

Import Cucumber with Gradle

I want to import cucumber.api.java.en.* into my groovy files, but cucmber.api will not be recognized as in my classpath. Thus every #Given or #When annotation is not recognized.
When I build with ./gradlew cucumber the .feature file is found and missing snippets are shown in the console. What do I have to include in my build.gradle to add above import into my classpath?
My gradle version is 2.2 and the cucumber related parts of my build.gradle file look like this:
dependencies {
testCompile 'info.cukes:cucumber-java:1.2.4'
testCompile 'info.cukes:cucumber-junit:1.2.4'
}
test {
testLogging.showStandardStreams = true
systemProperties System.getProperties()
}
configurations {
cucumberRuntime {
extendsFrom testRuntime
}
}
task cucumber() {
dependsOn assemble, compileTestJava
doLast {
javaexec {
main = "cucumber.api.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
args = ['-f', 'pretty', '--glue', 'gradle.cucumber', 'src/test/resources']
}
}
}
What am I missing?
You include info.cukes:cucumber-java:1.2.4 which is the jar containing the annotations you are missing. They are expected to be available in your test classpath.
To me, it sounds as an issue with your IDE.
If you are using IntelliJ IDEA, try to re-import the project. Click on the two rotating arrows in your Gradle tab and refresh the project.

Resources