Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a jar with all dependencies using Gradle 4.4?

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 of implementation works
  • The runtime exception is from Kotlin not being in the classpath
  • It works in the IDE
  • Adding an implementation clause to the fatJar 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?

like image 764
Zoe stands with Ukraine Avatar asked Jun 26 '18 21:06

Zoe stands with Ukraine


People also ask

How can I create an executable jar with dependencies using Gradle?

Right click the module > Open module settings > Artifacts > + > JAR > from modules with dependencies. Set the main class.

Where are Gradle dependency JARs?

Gradle declares dependencies on JAR files inside your project's module_name /libs/ directory (because Gradle reads paths relative to the build.gradle file). This declares a dependency on version 12.3 of the "app-magic" library, inside the "com.example.android" namespace group.

What is compileJava in Gradle?

compileJava — JavaCompile. Depends on: All tasks which contribute to the compilation classpath, including jar tasks from projects that are on the classpath via project dependencies. Compiles production Java source files using the JDK compiler.


Video Answer


1 Answers

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.

like image 166
Konrad Botor Avatar answered Oct 27 '22 21:10

Konrad Botor