I have a Java project with multiple dependencies that I would like to package into a fat jar with Gradle using Kotlin DSL.
When I run ./gradlew jar, the build succeeds, but it cannot find the main class when I try to run it:
$ ./gradlew jar
BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
$ java -jar build/libs/myapp-0.0.1-SNAPSHOT.jar
Error: Could not find or load main class mypackage.Hello
Caused by: java.lang.ClassNotFoundException: mypackage.Hello
$ jar xf build/libs/myapp-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF
$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: mypackage.Hello
$ jar -tf build/libs/myapp-0.0.1-SNAPSHOT.jar | grep mypackage 1
mypackage/
mypackage/Hello.class
Here is the full build.gradle.kts file:
group = "myapp"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
}
plugins {
java
}
repositories {
mavenCentral()
maven {
url = uri("https://mvnrepository.com/artifact/com.microsoft.azure/azure-storage")
}
}
dependencies {
implementation("com.azure:azure-storage-blob:12.12.0")
}
val mainClassName = "mypackage.Hello"
tasks.jar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
manifest.attributes.apply {
put("Class-Path", configurations.runtimeClasspath.get().asPath)
put("Main-Class", mainClassName)
}
from(configurations.compileClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}
tasks.test {
useJUnitPlatform()
}
And a link to my repo: https://github.com/darkasphalt/myapp
Check the content of jar. The problem is, that you include all the contents of the the libs. This also also adds files from the java module system like the module-info.class. This may hide your Main class.
Simple Fix
Your main works, if you:
a) Remove the import
implementation("com.azure:azure-storage-blob:12.12.0")
or
b) Remove the classpath config:
from(configurations.compileClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
Than a simple java -jar myapp-0.0.1-SNAPSHOT.jar will print out the:
Hello
Setup with ShadowJar
To solve the problem of packaging a fat jar the right way, you might use the ShadowJar plugin. With this setup, the bundled jar works:
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
group = "myapp"
version = "0.0.1-SNAPSHOT"
plugins {
java
application
id("com.github.johnrengelman.shadow").version("7.0.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
}
repositories {
mavenCentral()
maven {
url = uri("https://mvnrepository.com/artifact/com.microsoft.azure/azure-storage")
}
}
application() {
mainClass.set("mypackage.Hello")
}
dependencies {
implementation("com.azure:azure-storage-blob:12.12.0")
}
// Configure Shadow to output with normal jar file name:
tasks.named<ShadowJar>("shadowJar").configure {
minimize()
}
tasks {
build {
dependsOn(shadowJar)
}
}
tasks.test {
useJUnitPlatform()
}
Related
I have a script build.gradle, which created the IDEA development environment when creating a JavaFX project with Gradle support:
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.10'
id 'org.beryx.jlink' version '2.24.4'
id 'org.javamodularity.moduleplugin' version '1.8.10' apply false
}
group 'com.prototype'
version '1.0'
repositories {
mavenCentral()
}
ext {
junitVersion = '5.8.2'
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
sourceCompatibility = '17'
targetCompatibility = '17'
}
application {
mainModule = 'com.prototype.simulationcrystalgrowth'
mainClass = 'com.prototype.simulationcrystalgrowth.SimulationApplication'
}
javafx {
version = '17.0.1'
modules = ['javafx.controls', 'javafx.fxml', 'javafx.web']
}
dependencies {
implementation('org.controlsfx:controlsfx:11.1.1')
implementation('com.dlsc.formsfx:formsfx-core:11.4.2') {
exclude(group: 'org.openjfx')
}
implementation('net.synedra:validatorfx:0.2.1') {
exclude(group: 'org.openjfx')
}
implementation('org.kordamp.ikonli:ikonli-javafx:12.2.0')
implementation('org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0')
implementation('eu.hansolo:tilesfx:17.0.11') {
exclude(group: 'org.openjfx')
}
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}
test {
useJUnitPlatform()
}
jlink {
imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'app'
}
}
jlinkZip {
group = 'distribution'
}
After the "build" task is completed, the "distributions" folder appears in the build folder. It contains a zip archive with the following contents:
The bin folder contains two scripts, sh and bat.
The lib folder contains, as I understand it, all the required jar modules.
If JAVA_HOME is installed on Java 17 in my environment, then when executing the bat script, my program starts.
I expected that jlink is a kind of analogue of a more user-friendly assembly and packaging of the application, which will help to create something like an exe application launcher.
I also noticed that there are no tasks related to jlink in build.gradle is not called during the build process using the "build" task.
I tried to run them myself, and I got the same error:
I am confused by the mention of the "distributions/app" path in build.gradle, I expect there should be something else after the build.
What am I doing wrong?
What should I get at the output using jlink ?
The problem is solved.
The exclude of the org.openjfx module was removed from all dependencies.
Useful links:
https://openjfx.io/openjfx-docs/#gradle
https://github.com/openjfx/samples
https://developer.tizen.org/development/articles/openjdk-and-openjfx-installation-guide
I been working on a project that uses an external jar file. Whenever, I build my project using build.grade, and the external files does not get added to the final jar file. Inside my workspace, I have a "libs" folder which holds the external jar file (Discord Rich Presence). Can anyone help me sort this out?
buildscript {
repositories {
jcenter()
maven {
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT'
}
}
apply plugin: 'net.minecraftforge.gradle.forge'
version = "1.0"
group= "com.yourname.modid"
archivesBaseName = "example"
minecraft {
version = "1.8.9-11.15.1.1722"
runDir = "run"
mappings = "stable_20"
}
repositories {
flatDir {
dirs "libs"
}
}
dependencies {
compile name: 'discord-rpc'
}
processResources {
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version
from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
expand 'version':project.version, 'mcversion':project.minecraft.version
}
from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}
The default task jar only packages the files of project source code. To include all the dependencies inside the JAR file, you could try https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow:
plugins {
...
id "com.github.johnrengelman.shadow" version "7.0.0"
}
...
Run this command to package all:
$ ./gradlew shadowJar
I can run my project using gradle run, but I can't run the jar file using java -jar. I've recreated the error with this sample project: link to project on GitHub
This is the output from running the project via gradlew
$ ./gradlew run
> Task :run
Hello world.
BUILD SUCCESSFUL in 4s
This is the output from running the project java -jar
$ ./gradlew build
BUILD SUCCESSFUL in 6s
$ java -jar build/libs/emailer.jar
Error: Could not find or load main class us.company.emailer.App
But when I unzip the jar, I can see App.class
user#computer:../libs$ unzip emailer.jar
Archive: emailer.jar
creating: META-INF/
inflating: META-INF/MANIFEST.MF
creating: us/
creating: us/company/
creating: us/company/emailer/
inflating: us/company/emailer/App.class
Here's the build.gradle
plugins {
id 'groovy'
id 'application'
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
implementation 'org.codehaus.groovy:groovy-all:2.5.6'
testImplementation 'org.spockframework:spock-core:1.2-groovy-2.5'
compile 'org.apache.commons:commons-email:1.5'
}
mainClassName = 'us.company.emailer.App'
jar {
manifest {
attributes(
'Class-Path': configurations.compile.collect { it.getName() }.join(' '),
'Main-Class': 'us.company.emailer.App'
)
}
}
sourceSets.main.java.srcDirs = ['src/main/groovy']
Here's the App.groovy
package us.company.emailer
class App {
String getGreeting() {
return 'Hello world.'
}
static void main(String[] args) {
println new App().greeting
}
}
EDIT: Adding MANIFEST.MF in response to the comment from #tkruse
Manifest-Version: 1.0
Class-Path: commons-email-1.5.jar javax.mail-1.5.6.jar activation-1.1.
jar
Main-Class: us.company.emailer.App
The problem is the classpath. If you look inside the META-INF/MANIFEST.mf file, you can see it's set to:
Class-Path: commons-email-1.5.jar javax.mail-1.5.6.jar activation-1.1.
jar
When java runs, it has no idea where any of these things are, it also requires the groovy runtime in order to understand your groovy code.
The simplest way of doing this is to bundle all your dependencies into a "fat-jar", and the simplest way of doing this with Gradle is the excellent Shadow-jar plugin.
If you add the following to your plugins block in build.gradle:
id 'com.github.johnrengelman.shadow' version '5.0.0'
(You can delete the jar block and the line that manipulates the sourceSets)
Then run ./gradlew shadowJar
You'll get a jar file emailer-all.jar
Which can be run:
$ java -jar build/libs/emailer-all.jar
Hello world.
For completeness, here's the complete build.gradle file:
plugins {
id 'groovy'
id 'application'
id 'com.github.johnrengelman.shadow' version '5.0.0'
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
implementation 'org.codehaus.groovy:groovy-all:2.5.6'
testImplementation 'org.spockframework:spock-core:1.2-groovy-2.5'
implementation 'org.apache.commons:commons-email:1.5'
}
mainClassName = 'us.company.emailer.App'
I have a library project in Java which is several folders, each one doing specific parts and having its own dependencies.
Since I am working locally I would like to deploy this library locally and get the Jar to import to another project.
For this reason I am using gradle and what I did was going to the directory where I have all the folders of the library and gradle init and then gradle build.
Since I want the files locally, I saw that I can use gradle publishToMavenLocal, which I did and it created a jar file under ~/.m2/..... Now the issue is that this jar file appear to only contain a META-INF folder and inside of it a manifest.mf file.
This is the build.gradle file used.
What am I doing wrong? Should I do something different?
check gradle docs
there is also a complete example.
be sure to add your sourceSets that you want to compile and build in the jar.
build.gradle
plugins {
id 'java'
id 'maven-publish'
}
repositories {
mavenLocal()
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceSets {
main {
java { srcDir 'src/main/java' }
resources {
srcDirs 'src/main/resources'
}
}
test {
java { srcDir 'src/test/java' }
resources {
srcDirs 'src/test/resources'
}
}
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'org.gradle.sample'
artifactId = 'project1-sample'
version = '1.1'
from components.java
}
}
}
You could also add your library project to your main project like this :
build.gradle
dependencies {
compile project(':library_project')
}
settings.gradle
rootProject.name = 'Project'
include ":library_project"
project(':library_project').projectDir = new File(settingsDir, '../library_project')
I am using gradle to package some java code into a jar. I am using some classes from tools.jar. I have had success in gradle building it and making a jar, but when I run that jar using java -jar <package>.jar I get the folowing
java.lang.NoClassDefFoundError: com/sun/tools/attach/VirtualMachine.
Since tools.jar is something you get with a jdk, not a jre. Is there a way I can bundle tools.jar with my package.jar and have my jar work anywhere?
Here is my build.gradle so far.
buildscript {
repositories {
maven {
url 'https://plugins.gradle.org/m2/'
}
}
}
description = "A java program"
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
flatDir {
dirs System.properties['java.home'] + '/../lib'
}
}
jar {
archiveName = "jProg.jar"
manifest {
attributes(
'Dependencies': 'com.sun.tools'
)
}
}
dependencies {
compile group: 'com.sun', name: 'tools'
}
Probably what you need is called 'fat jar' (Gradle packs all dependencies to single jar)