How do I generate the class path for a Gradle project? - java

I have a gradle project with multiple packages. After the build, each package generates its jar files in build/libs. The external jar dependencies are pulled into ~/.gradle. I would now like to run the service locally from the commandline with the appropriate classpath. For this purpose, I am writing a script that constructs the classpath. The problem is that the script does not understand all the external dependencies and hence cannot construct the classpath. Is there a way for gradle to help with this? Ideally, I would like to dump all the dependencies into a folder at the end of the build.

Firstly, i would suggest using the application plugin if you can, since it takes care of this already.
If you want to dump the classpath to a file yourself, the simplest way is something like:
task writeClasspath << {
buildDir.mkdirs()
new File(buildDir, "classpath.txt").text = configurations.runtime.asPath + "\n"
}
If you want to actually copy all the libraries on the classpath into a directory, you can do:
task copyDependencies(type: Copy) {
from configurations.runtime
into new File(buildDir, "dependencies")
}

You could try something like this in your build script:
// add an action to the build task that creates a startup shell script
build << {
File script = file('start.sh')
script.withPrintWriter {
it.println '#!/bin/sh'
it.println "java -cp ${getRuntimeClasspath()} com.example.Main \"\$#\""
}
// make it executable
ant.chmod(file: script.absolutePath, perm: 'u+x')
}
String getRuntimeClasspath() {
sourceSets.main.runtimeClasspath.collect { it.absolutePath }.join(':')
}

Related

`gradlew jar` is not producing a jar

I'm building a Java command line application using gradle and have it running when I use gradlew run, however I would like to generate a jar -- which I would assume I would then have users download to invoke the CLI.
However, when I run gradlew jar, nothing is produced (build/lib dir doesn't even exist) even though the build runs with no errors and finishes with BUILD_SUCCESSFUL.
Two questions:
Why is no jar being produced?
Is having users download a jar the best way to ship a CLI for Java?
Below is my full build.gradle.kts
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
id("com.diffplug.spotless") version "6.12.0"
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit test framework.
testImplementation("junit:junit:4.13.2")
// This dependency is used by the application.
implementation("com.google.guava:guava:30.1-jre")
implementation("info.picocli:picocli:4.7.0")
annotationProcessor("info.picocli:picocli-codegen:4.7.0")
implementation("io.vavr:vavr:0.10.4")
}
application {
// Define the main class for the application.
mainClass.set("testlauncher.command.Runner")
}
subprojects {
apply {
plugin("com.diffplug.spotless")
}
}
spotless {
java {
importOrder()
removeUnusedImports()
googleJavaFormat()
}
}
project.tasks.findByName("build")?.dependsOn(project.tasks.findByName("spotlessApply"))
I'm dumb.
I thought the jar would be in ./build/libs but it's actually in ./app/build/libs.

Izpack custom actions JAR cannot be found

I need to create an installer using gradle and izpack through gradle-izpack-plugin, but every time I run gradle build, it points out that the custom action .jar that are defined in izpack-install.xml are not being found , even though I put them in the correct folder. I have the same project written in Ant, and it works normally, but even trying to run the Ant project through gradle, it shows the same error, the same way it happens when I use the gradle-izpack-plugin. Am I doing something wrong or is there any additional configuration to be done in gradle?
build.gradle
(I can only post the tasks because its from my work)
izPackCreateInstaller.dependsOn('customActions')
izpack{
baseDir = file("target")
installFile = file('IzPack-install.xml')
outputFile = file(project.jarName)
compression = 'deflate'
compressionLevel = 9
}
task customActions(type: Copy, dependsOn: ['jar']){
duplicatesStrategy=DuplicatesStrategy.EXCLUDE
into 'bin'
into('/customActions'){
from 'build/libs'
rename('installer.jar', 'MyTestInstallerListener.jar')
}
into('/customActions'){
from 'build/libs'
rename('installer.jar', 'MyTestUninstallerListener.jar')
}
}
The error :
[org.gradle.api.internal.project.ant.AntLoggingAdapter] Couldn't load Resource bin/customActions/MyTestInstallerListener.jar
[org.gradle.api.internal.project.ant.AntLoggingAdapter] Couldn't load Resource bin/customActions/MyTestUninstallerListener.jar
directory structure
I already check and the customActions task is running before izPackCreateInstaller task

Package tests for execution in gradle

I have a gradle project that contains a suit of tests, that can be launched with a command:
gradlew clean :mymodule:test
I need to build a jar, that can be executed in the same way and launch tests. I found instruction for creating a jar of test binaries here Creating a Jar of test binaries - Gradle.
task packageTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
However the resulting jar contains only test classes and resources, no dependent libraries or main application source code.
How can I create an executable test jar?
If I were you, I wouldn't use classifier='tests' as that is usually reserved for a jar containing test classes without dependencies. Possibly uber-tests or fat-tests is better as uber-jar and fat-jar are common names given to this type of jar. You could do something like
task uberTestJar(type: Jar) {
dependsOn testClasses
classifier = 'uber-tests'
sourceSets.main.output.each {
from it
}
sourceSets.test.output.each {
from it
}
configurations.testRuntime.each { File f ->
if (f.name.endsWith('.jar')) {
from zipTree(f)
} else {
from f
}
}
}
There's also the shadow jar plugin

How to execute a class with main method which is present in fatJar created by gradle

This is a follow up question for how to execute a built fat JAR as a gradle task?
I don't have enough points to ask my question as a comment. So I have to ask again. This question has been asked in other formats and scenarios multiple times but none of those responses helped me.
My problem:
scenario 1 : create a single jar with dependencies using gradle fatJar
scenario 2 : create a single jar with dependencies using maven assembly
Execute
java -cp sample.jar com.example.ClassA
on jar files generated in both processes.
Issue:
jar from Process 1 gives me
Error : Could not find or load main class com.example.ClassA
jar from Process 2 executes correctly.
I have extracted both jar files and both of them have the same folder structure and same files - meaning the compiled class files are present in both jar files.
I haven't specified any manifest entries in either process because I have multiple main classes and I am not trying to generate an executable jar file.
My build.gradle file looks like below:
apply plugin: 'java'
apply plugin: 'maven'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
}
sourceSets {
main{
java{
srcDirs 'src/main/java'
}
}
}
processResources {
from 'src/main/resources'
}
task compile (type: JavaCompile) {
source = sourceSets.main.java.srcDirs
include '**/*.java'
classpath = sourceSets.main.compileClasspath
destinationDir = sourceSets.main.output.classesDir
}
compile.options.compilerArgs = ["-sourcepath", "$projectDir/src/main/java"]
dependencies {
.
.
.
}
task fatJar(type:Jar) {
baseName = 'xxxxxxx'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
defaultTasks 'clean', 'compile', 'fatJar'
EDIT - 1:
I have tried relative path to the jar, absolute path to the jar and even browsing to the folder which contains the jar. No luck whatsoever.
I have tried using '/' instead of '.' in the package name. No luck there either.
I have tried using java VM arguments providing huge enough heap space. Nada.
Tried executing on powershell. Got the below error:
+ CategoryInfo : NotSpecified: (Error: Could not lo...ClassA:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
I wanted to see the contents directly so used jar -tvf path_to_jar\sample.jar . Interestingly jar command didn't execute and complained that command was not found on the classpath. I had to browse to the java installation directory and execute the command and it showed the file contents.
I am running out of ideas and options here.
Any pointers?
For some reason, no matter which config I use for fatJar, the final jar gives errors when trying to execute the main class.
Using ShadowJar task I was able to build the necessary jar and execute the main class as specified in the question.
I know this defeats the entire purpose of the question about using fatJar and executing the main class. But due to time-constraints, I had to look for alternatives.
For those who are looking for config for Shadow Jar tasks, I did it using below:
buildscript {
repositories {
mavenLocal()
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.3'
}
}
}
apply plugin: 'com.github.johnrengelman.shadow'
and finally to execute the task, I used
gradle shadowJar
It looks like your execution command is not valid. After building your project with:
./gradlew clean fatJar
you should use following command to execute your program:
java -cp build/libs/xxxxxxx.jar com.example.ClassA
Parameter -cp build/libs/xxxxxxx.jar sets the classpath for JVM that is about to start and it has to point to the existing JAR file (you can use relative or absolute path to this file).
Take a look at following screencast I've recorded a few minutes ago:
You can see that if I change execution command to:
java -cp xxxxxxx.jar com.example.ClassA
I got exactly the same error as yours:
Error: Could not find or load main class com.example.ClassA
Here you can find a simple Gradle project I used to play around with your build.gradle file example. I've added a single dependency to prove that fatJar builds a JAR with dependencies and I've added to classes with public static void main(String[] args) to prove that you can pick any main class from command line. I hope it helps.

Git based automatic versioning in Gradle

I'd like to implement automatic versioning for java-projects in Gradle via Git tags. Currently I'm using the git describe command to generate a version string. The version string has to be used in a manifest-file and also be programmatically retrievable. This is why I implemented a method to save it in a properties file that get's included in the build and can be read by Java code.
What I currently have is this small Gradle plugin (I'm really new to gradle):
apply plugin: 'java'
apply plugin: VersioningPlugin
class VersioningPlugin implements Plugin<Project> {
private Project project;
private String getVersion() {
def stdout = new ByteArrayOutputStream()
project.exec {
commandLine 'git', 'describe', '--tags'
standardOutput = stdout
}
stdout.toString().trim()
}
#Override
void apply(Project project) {
this.project = project
project.extensions.create('versioning', VersioningPluginExtension)
project.versioning.version = getVersion()
project.task('writeVersionFile') {
doLast {
def versionPropsFile = project.versioning.versionFile
def versionProps = new Properties()
versionProps.put('version', getVersion())
versionProps.store(versionPropsFile.newWriter(), null)
}
}
}
}
class VersioningPluginExtension {
File versionFile
String version
}
compileJava.dependsOn writeVersionFile
I use it in my build scrips like this:
apply from: 'versioning.gradle'
versioning {
versionFile = file('my/ressource/folder/version.properties')
}
println(versioning.version)
Everything works OK. The only problem I have is the following: I use gradle to build and test my code, then commit the code and create a git tag. If I now ask gradle to package a jar file without cleaning first, it doesn't copy the new version.properties into the jar file because it didn't notice that the file changed. So my application shows the old version.
Is there anything I can do about this? How can I get gradle to notice the version change and create a jar with the current version.properties file?
What you need to do is to configure a TaskOutputs (or from) for Jar task so that it can recognize if something has changed and be run without calling clean explicitly. This can be done in the following way:
jar {
from versioning.versionFile
}
Secondly. You can configure writeVersionFile in a similar way. If you configure both inputs and outputs the task will be cacheable. How it has changed can been seen here, a demo can be found here.
Also, mind that your task is created now in afterEvaluate closure. Why is that? Because this is after versioning {} extension is executed and versionFile property is set and can be configured as an output.
You can also configure jar input in versioning.gradle file.

Categories