Gradle ShadowJar jar of jars without unzipping classes - gradle

I am trying to create a jar of jars without unzipping class files from each jar. Unfortunately shadowJar unzip's all jars and resulting jars contain directories instead of jars.
My build.gradle file is:
apply plugin: 'com.github.johnrengelman.shadow'
dependencies {
compile("ldapjdk:ldapjdk:1.0"){
transitive = false
}
compile("support:activemq-core:5.3.1"){
transitive = false
}
compile("support:concurrent:0.0"){
transitive = false
}
compile("dom4j:dom4j:${dom4j_version}"){
transitive = false
}
compile("commons-lang:commons-lang:${commons_lang_version}") {
transitive = false
}
}
task copyRuntimeLibs(type: Copy) {
from(project(':server').configurations.runtime)
include 'commons-lang2*'
include 'ldapjdk*'
include 'dom4j*'
include 'concurrent*'
}
task copyFiles(dependsOn: [copyRuntimeLibs])
task copyToLib( type: Copy ) {
into "$buildDir/lib"
from configurations.runtime
}
jar { dependsOn copyToLib }
shadowJar {
zip64 true
baseName = "service"
from("$buildDir/lib") {
include '**'
}
}
I've tried from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }. This also extracts classes, MANIFEST, etc.
Is there a way to not extract classes from each jar & instead make a jar of jars?
Thanks a lot.

You may want to see this GitHub issue.
https://github.com/johnrengelman/shadow/issues/111
It is actually not solved yet in the Shadow plugin, there some workarounds are shown there.

Related

Switching between regular and fat jars with Gradle script

I have a tiny java project using gradle, and would like to be able to choose between generating a regular jar and a fat jar.
plugins {
id 'java-library'
}
repositories {
mavenCentral()
}
jar {
manifest {
attributes(
'Main-Class': 'com.myTest'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
dependencies {
implementation('org.apache.commons:commons-lang3:3.0')
testImplementation 'junit:junit:4.12'
}
If I use the above script to build, i will get a fat jar. In order for me to get a jar, i will need to remove the "jar" block. What do I need to do so that I can switch between the two from the command line?
Thanks so much!
Create new task for the fatJar task like below,
task fatJar(type: org.gradle.jvm.tasks.Jar, group: "build") {
manifest {
attributes(
'Main-Class': 'com.myTest'
)
}
baseName(project.name.concat("-fatJar"))
from {
configurations.runtimeClasspath.collect { it.directory ? it : zipTree(it)
}
}
Refer: https://www.baeldung.com/gradle-fat-jar

Use gradle to build a jarfile from other jarfiles

I have what should be a simple problem: I have a directory full of jarfiles, and I want to use gradle (v4.4.1) to combine the contents of these jarfiles into a single jarfile (i.e., when I do jar tf big-jar.jar, I want to see a bunch of classes, not a bunch of jars). I tried the following:
task bigJar(type: Jar) {
inputs.dir "$distLibsJar"
outputs.file "$distDir/big-jar.jar"
destinationDir = file("$distDir")
baseName = "big-jar"
from("$distLibsDir")
}
but this produces a jarfile with other jars as its contents rather than the contents of those other jars as the contents of the combined jar.
Any ideas? Thanks...
In this case best way to proceed is with a shadow jar. for this you can have this kind of configuration in build.gradle .posting some sample
apply plugin: 'com.github.johnrengelman.shadow'
shadowJar {
zip64 true
//classifier "shadow" can be mentioned according to need
mergeServiceFiles()
artifacts {
shadow(tasks.shadowJar.archivePath) {
builtBy shadowJar
}
}
}
artifacts {
archives shadowJar
}
distZip {
dependsOn shadowJar
//appendix='software'
eachFile { file ->
String path = file.relativePath
file.setPath(path.substring(path.indexOf("/")+1,path.length()))
}
doLast{
//operation to perform
}
}
distributions {
main {
contents {
//from and to can be mentioned here
}
}
}
hope it helps

How to include jars from libs folder to another jar?

I have Gradle JAR (Main) project. I have other commons-io jars (dependent) that I would like to package them with the Main JAR. I tried below, but when I decompile Main JAR, I see all the dependent JARS inside libs folder with .jar extension.
jar {
from("$projectDir") {
include 'libs/**'
}
}
What I want is the class files from all the dependent JARs into Main JAR. I am doing this because the Main JAR is going to be used by multiple projects. So that way, I do not have to include all the dependent JARs.
Thanks
As far as I know You cannot pack other jars into single jar just like that. What You need to do is to extract all the jars and pack the content into single file.
Following piece of code prepares such jar for declared dependencies
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.guava:guava:16.0.1'
compile 'com.google.inject:guice:3.0'
}
task fatJar(type: Jar, dependsOn: jar) {
baseName = project.name + '-fat'
deps = configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }
from(deps) {
exclude 'META-INF/MANIFEST.MF'
}
}
use this.
into(libForderName){
from {
configurations.compile.collect {
it.isDirectory() ? it : it.getAbsoluteFile()
}
}
}

Gradle: 'clone' original jar task to create a new task for a jar including dependencies

I would like to create a new task in my project that creates a jar archive with the class files of my project and the class files of the dependencies (also called 'shaded jar' or 'fat jar').
The solution proposed by the Gradle cookbook modifies the standard jar task of the JavaPlugin:
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
However, I would like to keep the original jar taks as it is and have an additional task for the shaeded jar, i.e. a task that behaves exactly like the jar task, but includes the additional files according to
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
and has another classifier ('shaded').
I tried to take over the configuration of the jar task by copying properties like this:
task shadedJar(type: Jar, dependsOn: configurations.compile) {
dependencies = tasks.jar.taskDependencies
source = tasks.jar.source
manifest = tasks.jar.manifest
includes = tasks.jar.includes
classifier = 'shaded'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
But the resulting tasks does not take over the dependencies of 'jar' and the resulting jar does not include the project's class files. Additionally, this approach seems to cumbersome to be the recommended way of using an existing task as a template for a new one.
What would a recommendable approach to my specific need (the seperate shadedJar task) and for 'cloning' tasks to use them as templates for additional tasks in general?
(I am currently still on Gradle 1.3,but solutions for the current Gradle version are also welcome)
There is no built-in way to clone tasks. However, it's easy to configure the fatJar task to include the same files as the java plugin's jar task:
task fatJar(type: Jar) {
appendix = "fat"
from sourceSets.main.output // that's it
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
Task autowiring will automatically establish the necessary task dependencies.
If the build script goes on to customize the jar task, you can apply the customizations to both tasks simultaneously:
configure([jar, fatJar]) {
version = "2.0"
entryCompression = "STORED"
}
If, unlike in the jar task's case, you are defining the "template" yourself, you can "instantiate" it with a factory method:
def getMeAnotherOne(String name) {
task(name, type: Jar) {
version = "2.0"
entryCompression = "STORED"
}
}
getMeAnotherOne("jar1")
getMeAnotherOne("jar2")

Using Gradle to build a JAR with dependencies

I have a multiproject build and I put a task to build a fat JAR in one of the subprojects. I created the task similar to the one described in this cookbook.
jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
manifest { attributes 'Main-Class': 'com.benmccann.gradle.test.WebServer' }
}
Running it results in the following error:
Cause: You can't change a
configuration which is not in
unresolved state!
I'm not sure what this error means. I also reported this on the Gradle JIRA in case it is a bug.
I posted a solution in JIRA against Gradle:
// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"
jar {
manifest {
attributes "Main-Class": "$mainClassName"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Note that mainClassName must appear BEFORE jar {.
The answer by #felix almost brought me there. I had two issues:
With Gradle 1.5, the manifest tag was not recognised inside the fatJar task, so the Main-Class attribute could not directly be set
the jar had conflicting external META-INF files.
The following setup resolves this
jar {
manifest {
attributes(
'Main-Class': 'my.project.main',
)
}
}
task fatJar(type: Jar) {
manifest.from jar.manifest
classifier = 'all'
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
with jar
}
To add this to the standard assemble or build task, add:
artifacts {
archives fatJar
}
Edit: thanks to #mjaggard: in recent versions of Gradle, change configurations.runtime to configurations.runtimeClasspath
If you want the jar task to behave normally and also have an additional fatJar task, use the following:
task fatJar(type: Jar) {
classifier = 'all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
The important part is with jar. Without it, the classes of this project are not included.
Since use of compile to list dependencies is now deprecated and all should switch to implementation the solution to build a Jar with all dependencies should use the example from this website.
https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example
Specifically this command:
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it)
Here is full gradle section:
[1]: https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example
task uberJar(type: Jar) {
archiveClassifier = 'uber'
from sourceSets.main.output
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}}
This works fine for me.
My Main class:
package com.curso.online.gradle;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class);
logger.debug("Starting demo");
String s = "Some Value";
if (!StringUtils.isEmpty(s)) {
System.out.println("Welcome ");
}
logger.debug("End of demo");
}
}
And it is the content of my file build.gradle:
apply plugin: 'java'
apply plugin: 'eclipse'
repositories {
mavenCentral()
}
dependencies {
compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
testCompile group: 'junit', name: 'junit', version: '4.+'
compile 'org.apache.commons:commons-lang3:3.0'
compile 'log4j:log4j:1.2.16'
}
task fatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'com.curso.online.gradle.Main'
}
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
And I write the following in my console:
java -jar ProyectoEclipseTest-all.jar
And the output is great:
log4j:WARN No appenders could be found for logger (com.curso.online.gradle.Main)
.
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more in
fo.
Welcome
The answer from #ben almost works for me except that my dependencies are too big and I got the following error
Execution failed for task ':jar'.
> archive contains more than 65535 entries.
To build this archive, please enable the zip64 extension.
To fix this problem, I have to use the following code
mainClassName = "com.company.application.Main"
jar {
manifest {
attributes "Main-Class": "$mainClassName"
}
zip64 = true
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
To generate a fat JAR with a main executable class, avoiding problems with signed JARs, I suggest gradle-one-jar plugin. A simple plugin that uses the One-JAR project.
Easy to use:
apply plugin: 'gradle-one-jar'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.github.rholder:gradle-one-jar:1.0.4'
}
}
task myjar(type: OneJar) {
mainClass = 'com.benmccann.gradle.test.WebServer'
}
Based on the proposed solution by #blootsvoets, I edited my jar target this way :
jar {
manifest {
attributes('Main-Class': 'eu.tib.sre.Main')
}
// Include the classpath from the dependencies
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// This help solve the issue with jar lunch
{
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
}
Simple sulution
jar {
manifest {
attributes 'Main-Class': 'cova2.Main'
}
doFirst {
from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
I use next script for Gradle 7.3.3. It resolves errors and exceptions that I was faced with when I was trying to implement solutions from this question.
jar {
manifest {
attributes(
"Main-Class": "path.to.main.Application",
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
For those who need to build more than one jar from the project.
Create a function in gradle:
void jarFactory(Jar jarTask, jarName, mainClass) {
jarTask.doFirst {
println 'Build jar ' + jarTask.name + + ' started'
}
jarTask.manifest {
attributes(
'Main-Class': mainClass
)
}
jarTask.classifier = 'all'
jarTask.baseName = jarName
jarTask.from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
{
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
jarTask.with jar
jarTask.doFirst {
println 'Build jar ' + jarTask.name + ' ended'
}
}
then call:
task makeMyJar(type: Jar) {
jarFactory(it, 'MyJar', 'org.company.MainClass')
}
Works on gradle 5.
Jar will be placed at ./build/libs.
I use task shadowJar by plugin .
com.github.jengelman.gradle.plugins:shadow:5.2.0
Usage just run ./gradlew app::shadowJar
result file will be at MyProject/app/build/libs/shadow.jar
top level build.gradle file :
apply plugin: 'kotlin'
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
}
}
app module level build.gradle file
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
sourceCompatibility = 1.8
kapt {
generateStubs = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"
shadow "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"
implementation project(":module_remote")
shadow project(":module_remote")
}
jar {
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
manifest {
attributes(
'Main-Class': 'com.github.kolyall.TheApplication',
'Class-Path': configurations.compile.files.collect { "lib/$it.name" }.join(' ')
)
}
}
shadowJar {
baseName = 'shadow'
classifier = ''
archiveVersion = ''
mainClassName = 'com.github.kolyall.TheApplication'
mergeServiceFiles()
}
Excluding unwanted Manifest entries fixed the MainClass file not found error in a Gradle build jar file.
jar{
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
from {
-----
}
}
There is gradle plugin shadow jar with seamless setup.
plugins {
id "com.github.johnrengelman.shadow" version "5.0.0"
}
shadowJar {
mergeServiceFiles()
}
Please check about version compatibilities with your gradle version here:
https://github.com/johnrengelman/shadow#latest-test-compatibility
Gradle 6.3, Java library. The code from "jar task" adds the dependencies to the "build/libs/xyz.jar" when running "gradle build" task.
plugins {
id 'java-library'
}
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
There's something to keep in mind about this type of solution:
task fatJar(type: Jar) {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
It works so long as you're using "compile" dependencies. It doesn't work if you're using "implementation" dependencies.
Try "runtimeClasspath" if "compile" and "implementation" not working.
jar {
manifest {
attributes "Main-Class": "com.example.app"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
This is for Kotlin DSL (build.gradle.kts).
Method 1 (no need for application or other plugins)
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
// OR another notation
// manifest {
// attributes["Main-Class"] = "com.example.MyMainClass"
// }
}
If you use any external libraries, use below code. Copy library JARs in libs sub-directory of where you put your result JAR. Make sure your library JAR files do not contain space in their file name.
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
manifest.attributes["Class-Path"] = configurations
.runtimeClasspath
.get()
.joinToString(separator = " ") { file ->
"libs/${file.name}"
}
}
Note that Java requires us to use relative URLs for the Class-Path attribute. So, we cannot use the absolute path of Gradle dependencies (which is also prone to being changed and not available on other systems). If you want to use absolute paths, maybe this workaround will work.
Create the JAR with the following command:
./gradlew jar
The result JAR will be created in build/libs/ directory by default.
Method 2: Embedding libraries (if any) in the result JAR (fat or uber JAR)
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
val dependencies = configurations
.runtimeClasspath
.get()
.map(::zipTree) // OR .map { zipTree(it) }
from(dependencies)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
Creating the JAR is exactly the same as the previous method.
Method 3: Using the Shadow plugin (to create a fat or uber JAR)
plugins {
id("com.github.johnrengelman.shadow") version "6.0.0"
}
// Shadow task depends on Jar task, so these will be reflected for Shadow as well
tasks.jar {
manifest.attributes["Main-Class"] = "org.example.MainKt"
}
Create the JAR with this command:
./gradlew shadowJar
See Shadow documentations for more information about configuring the plugin.
Running the created JAR
java -jar my-artifact.jar
The above solutions were tested with:
Java 17
Gradle 7.1 (which uses Kotlin 1.4.31 for .kts build scripts)
See the official Gradle documentation for creating uber (fat) JARs.
For more information about manifests, see Oracle Java Documentation: Working with Manifest files.
Note that your resource files will be included in the JAR file automatically (assuming they were placed in /src/main/resources/ directory or any custom directory set as resources root in the build file). To access a resource file in your application, use this code (note the / at the start of names):
Kotlin
val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText()
// Alternative ways:
// val vegetables = object{}.javaClass.getResource("/vegetables.txt").readText()
// val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText()
// val vegetables = object{}.javaClass.getResourceAsStream("/vegetables.txt").reader().readText()
Java
var stream = MyClass.class.getResource("/vegetables.txt").openStream();
// OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt");
var reader = new BufferedReader(new InputStreamReader(stream));
var vegetables = reader.lines().collect(Collectors.joining("\n"));
If you're used to ant then you could try the same with Gradle too:
task bundlemyjava{
ant.jar(destfile: "build/cookmyjar.jar"){
fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
}
}

Resources