This question is related to this one — however, due to the deprecation of compile
in favor of implementation
, it doesn’t work. It does pick up dependencies that are declared with compile
. However, with it being deprecated, using it isn’t an option (and we’d be right back here when it’s removed anyway)
I got this Gradle task:
task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } baseName = project.name from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
And there’s just one dependency, looking aside test dependencies:
dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation group: 'junit', name: 'junit', version: '4.12' }
Running from the IDE works fine. However, when I deploy to my Raspberry Pi (or use the jar gradlew fatJar
results in locally), I get this exception:
$ java -jar java-sense-hat-1.0a.jar Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics at io.github.lunarwatcher.pi.sensehat.UtilsKt.getSenseHat(Utils.kt:18) at io.github.lunarwatcher.pi.sensehat.SenseHat.<init>(SenseHat.java:12) at io.github.lunarwatcher.pi.sensehat.Tests.main(Tests.java:9) Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) at java.base/java.lang.ClassLoader.loadClass(Unknown Source) ... 3 more
Which is triggered by this line:
return Optional.empty()
in a Kotlin method returning an Optional<File>
. Answer on related question:
Either kotlin-runtime has to be in classpath and verify with $ echo $CLASSPATH.
Or you have to add kotlin-runtime to maven and then assemble inside the jar itself with mvn compile assembly:single,
Which means the kotlin-runtime isn’t included in the classpath. Before you go ahead and answer “add kotlin-runtime to your dependencies”, it’s a part of the stdlib:
Kotlin Runtime (deprecated, use kotlin-stdlib artifact instead)
I use kotlin-stdlib-jdk8
and it works in the IDE. Just for testing purposes, using kotlin-stdlib
instead does not change anything.
In addition, replacing implementation
with compile
fixes it.
In the post I linked at the top of the question, there’s a suggestion to use runtime
in the fatJar task. So:
task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } baseName = project.name from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
The dependency is still not included, and the program crashes.
So why not add implementation as a configuration to copy from?
I tried. I ended up with this:
task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } baseName = project.name from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } configurations.implementation.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
And an exception:
Resolving configuration ‘implementation’ directly is not allowed
So, considering:
compile
instead ofimplementation
works- The runtime exception is from Kotlin not being in the classpath
- It works in the IDE
- Adding an
implementation
clause to thefatJar
task crashes the compiling with a separate exception
How do I generate a jar with all dependencies in Gradle 4.4 when using the implementation
keyword?
Advertisement
Answer
Have you tried the Shadow Plugin like:
shadowJar { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } configurations = [project.configurations.compile, project.configurations.runtime] }
Edit:
You can also do this (as described in an answer for this question):
configurations { fatJar } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation group: 'junit', name: 'junit', version: '4.12' fatJar "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } baseName = project.name from { configurations.fatJar.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
but then you have to repeat all implementation dependencies as fatJar dependencies. For your current project it’s fine since you have only one dependency, but for anything bigger it will become a mess…
Edit 2:
As @Zoe pointed out in the comments this is also an acceptable solution:
task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'rpi-sense-hat-lib', 'Implementation-Version': version, 'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests' } baseName = project.name from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
however make notice as per source code that runtimeClasspath
is a combination of runtimeOnly
, runtime
and implementation
, which may or may not be desirable depending on the situation – for instance you may not want to include runtime
dependencies because they are provided by the container.