How to include runtime dependency in jar generated from gradle jar task - java

I am creating a fat jar using gradle, build.gradle is as below:
...
dependencies {
compile files('local_path1')
compile files('local_path2')
runtime files('local_path3')
}
task customFatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'MyMainClass'
}
archiveName = 'my-jar'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
Now, once I run the "customFatJar" task, the jar generated contains dependent jars in paths "local_path1" and "local_path2". But the jar generated does not contain dependent jar in path "local_path3".
Please let me know correct dependency configuration to achieve this.

As you defined the local_path3 as runtime, you would need to add them into the script, too. Currently it just collecting all the runtime libs. It may looks like:
from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }

You should have a look at the documentation around the configurations created by the Java plugin and how they relate to each other.
In order to not miss a single runtime dependency in your fat jar, you should rely on the runtimeClasspath configuration:
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }

Related

A library corrupts classpath?

There's a strange issue I've never seen.
Adding a compile 'org.locationtech.spatial4j:spatial4j:0.7' to the dependencies list in my gradle project leads to a corrupt classpath. When I comment out that library and run java -verbose:class -jar sol_backend_full.jar > ok.log it outputs 4399 lines of class entries. However, with that library in classpath, java -verbose:class -jar sol_backend_full.jar > failed.log outputs only 953 lines, most of which are java.lang.* or sun.*.
It obviously results in Error: Could not find or load main class.
➥ Has anyone ever encountered that strange behaviour?
Of course, I can substitute that library with another spatial library, but what's happening is simply strange. It happens only with this library, removing/adding any other is fine.
Gradle version in question is 5.5.1, and that library manifest looks a bit long, but not suspicious at all. Falling back to 4.8 also reproduces it.
Here is the build script:
task customFatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'ru.rxproject.sol.backend.BackendApplication',
'Implementation-Version': version + System.getenv('BUILD_NUMBER').toString(),
'Commit-Hash': 'git-' + System.getenv('GIT_COMMIT'),
'Build-Date': java.time.LocalDateTime.now().toString()
}
archiveName = 'sol_backend_full.jar'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
The JAR dependancy org.locationtech.spatial4j:spatial4j:0.7 is a signed jar. When you create a fat jar, java Classloader is not able to load the other classes from your fat jar because these are not signed.
So, you can't create a fat jar with that dependancy without excluding the signatures.
Please refer - Gradle - FatJar - Could not find or load main class
Like mentioned in the above post, you may exclude the signatures like -
jar {
manifest {
attributes "Main-Class": mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
exclude 'META-INF/*.RSA'
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
}
But, I would suggest to keep the jar dependancy out of the fat jar.

Gradle - FatJar - Could not find or load main class

I know that question was asked a lot and has many answers, but i still get it and I don't understand why...
I am trying to generate a .jar from a projet with dependencies with gradle.
I have a class src/main/java/Launcher.java, in which I have my main method.
there is my build.gradle
plugins {
id 'java'
id 'application'
}
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
mainClassName = 'Launcher'
repositories {
mavenCentral()
}
dependencies {
compile 'commons-io:commons-io:2.1'
compile 'io.vertx:vertx-core:3.4.0'
compile 'io.vertx:vertx-web:3.4.0'
compile 'com.google.code.gson:gson:1.7.2'
compile "com.auth0:java-jwt:3.1.0"
compile 'org.mongodb:mongo-java-driver:3.4.1'
compile 'com.google.guava:guava:24.1-jre'
compile 'commons-io:commons-io:2.6'
}
jar {
manifest {
attributes "Main-Class": mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
I use $>gradle assemble to generate my jar
then $>java -jar path/to/my/.jar
And i get the error "could not find or load main class Launcher"...
I dont understand why, when I look in the .jar, I have Launcher class and in META-INF I have my manifest
Sorry for still asking this question in 2018 but i'm loosing my mind trying to figure out what's wrong. I hope somone will have the answer !
I reproduced your issue locally.
Just add exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' to the jar task.
This will exclude the signatures of interfering dependencies.
Example:
jar {
manifest {
attributes "Main-Class": mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
exclude 'META-INF/*.RSA'
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
}
You are running into the the one major problem when building a FAT JAR:
One of your source JARs is signed and merging it into one fat jar destroys the signature.
It looks like Java recognizes that there are unsigned classes and ignores everything but the signed classes. As all classes that do not belong to the signed library are unsigned (like your Launcher class) they are ignored and therefore can't be loaded.
In your case it looks like that the dependency org.bouncycastle:bcprov-jdk15on:1.55 of com.auth0:java-jwt:3.1.0 is the signed jar file. Because my sample project correctly executes Launcher when I uncomment this dependency.
Bouncy castle is a crypto provider that requires a valid signature otherwise it will not run from my experience. Therefore it is impossible to create a fat jar for your project that just contains all classes.
You can try to create a fat jar with everything except Bouncycastle and ship Bouncycastle JAR seperatly.
Or a fat jar that contains all the required JAR files inside (JAR inside JAR) and that uses a special class loader that is able to load classes from within such a JAR inside a JAR. See for example: https://stackoverflow.com/a/33420518/150978
Try to exclude .SF .DSA .RSA files, example below, Nipun
Hope this works out for you
task customFatJar(type: Jar) {
baseName = 'XXXXX'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
manifest {
attributes 'Main-Class': 'com.nipun.MyMainClass'
}
}
Adding
exclude 'META-INF/*.RSA'
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
Fixed my issue.

Make NON-FAT jar in Gradle on-demand

I'm using Gradle. Usually I'm build the FAT jar. There was no problem, but at this time, I also need to make NON-FAT jar.
I mean I want to exclude all depended library and run jar with -cp option like the following:
java -cp "/sample1/lib/myjar.jar:/sample2/lib/depended.jar" com.example.main.Runner
(In FAT jar, java -jar myjar.jar is the same thing and its contains depended.jar)
Here is my build.gradle.
apply plugin: 'java'
apply plugin: 'application'
jar.baseName = 'myjar'
version = ''
def mainClass = 'com.example.main.Runner'
mainClassName = mainClass
def defaultEncoding = 'UTF-8'
repositories {
flatDir dirs: "${projectDir}/libs"
}
dependencies {
compile ':commons-chain:1.2'
compile ':commons-io:2.4'
testCompile ':junit:4.12'
testCompile ':hamcrest-core:1.3'
}
jar {
manifest {
attributes 'Implementation-Version': version,
'Main-Class': mainClass
}
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
}
I still want to keep this for usual FAT jar creation.
So, I tried to append a task like the following:
TRY #1
task makeSlimJar {
jar.baseName = 'myjar.slim'
jar {
manifest {
// remove main class
attributes 'Implementation-Version': version
}
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
}
TRY #2
task makeSlimJar {
jar.baseName = 'myjar.slim'
jar {
manifest {
// remove main class
attributes 'Implementation-Version': version
}
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
tasks.Jar.execute()
}
TRY #3
task makeSlimJar {
jar.baseName = 'myjar.slim'
jar {
manifest {
// remove main class
attributes 'Implementation-Version': version
}
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
dependsOn Jar
}
After appended it, I ran the gradle makeSlimJar.
All of the above my tries were failed(programmatically, it were succeeded) and just created FAT jar with name myjar.slim.jar.
Is there way to live together FAT and NON-FAT jar on the same build.gradle file?
... Or am I something wrong?
I consider that removing apply plugin: 'application' for NON-FAT jar is my last resort.
Please help me, if you could.
Thanks,
the code which is actually adding the dependencies to your jar is this line:
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
your build script will normally build a 'slim jar' by default, except that you have added code to override this behaviour:
jar {
manifest {
attributes 'Implementation-Version': version,
'Main-Class': mainClass
}
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
}
I would recommend removing these lines. When you run gradle clean build this will then build a normal jar (with no dependencies).
You could then add an additional task to create your 'fatjar' like this:
task fatjar << {
jar.doFirst {
println "creating fatjar"
jar {
from {configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }}
}
}
}
You can use this task to build the fatjar by running gradle clean fatjar build.
Alternatively you could try looking at a framework such as springboot, which would do all this for you.

Gradle FatJar task creating multiple MANIFEST.MF files

When I run this FatJar/UberJar task, I end up with a .jar file whose META-INF\ folder contains two MANIFEST.MF files. One is the correct one with the attributes I inserted into the task definition, and the other simply contains "Manifest-Version: 1.0". This causes a "Could not find or load main class" error when attempting to run the application (I assume it is solely reading the first one and can't identify a main-class)
task fatJar(type: Jar) {
baseName = project.name
manifest {
attributes 'Implementation-Title': 'Synchronizer',
'Main-Class': 'net.xxx.yyy.Main'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
MANIFEST.MF #1
Manifest-Version: 1.0
MANIFEST.MF #2:
Manifest-Version: 1.0
Implementation-Title: Synchronizer
Main-Class: net.xxx.yyy.Main
Whats the reason for the duplicate Manifest?
I've tried excluding the MANIFEST.MF using
it.isDirectory() ? it : zipTree(it).matching{exclude{it.path.contains == 'META-INF'}}
it.isDirectory() ? it : zipTree(it).matching{exclude{it.name.contains == 'MANIFEST'}}
*** These do exclude the child JAR META-INF folders and Manifest files, so I'm not sure where MANIFEST.MF #1 is coming from. It appears to be a default?
On my version of gradle, I'm not getting 2 manifests with your code - I only end up with a single manifest containing Manifest-Version: 1.0 as you described.
The problem appears to be that your manifest closure isn't actually associated with the jar closure. The Jar task type assignement and "with jar" keyword don't do this for you. Therefore, it isn't recognized as the manifest file for the jar task. You should be able to fix this through one of these options:
task fatJar(type: Jar) {
baseName = project.name
jar.manifest {
attributes 'Implementation-Title': 'Synchronizer',
'Main-Class': 'net.xxx.yyy.Main'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } with jar
}
Or, a cleaner way to write the same this would be to include the manifest and from closures in a jar closure:
task fatJar(type: Jar) {
baseName = project.name
jar{
manifest {
attributes 'Implementation-Title': 'Synchronizer',
'Main-Class': 'net.xxx.yyy.Main'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
}

How to include CSS files of javafx project in gradle assemble?

I have a javafx project including css files. I want them directly in the main java src:
src/main/java/Foo.java
src/main/java/Foo.css
When I now use my gradle script to build a fat jar, it does not include the css files and running the jar ends up in a npe.
My gradle script looks like this:
apply plugin: 'java'
apply plugin: 'eclipse'
jar {
manifest {
attributes "Main-Class": "foo.Main"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
configurations.runtime.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'com.github.sarxos:webcam-capture:0.3.10'
}
What can I do to get a runnable jar file from gradle?
I tried already to explicitly include the css files - it did not work / I did it wrong ...
If you can't change your resource files to a resource directory, then you can use:
jar {
manifest {
attributes "Main-Class": "foo.Main"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
configurations.runtime.collect {
it.isDirectory() ? it : zipTree(it)
}
}
from('src/main/java') {
include '**/*.css'
}
}

Categories