JAR built by Gradle can't find Gson - java

The problem is solved, read the last section.
The "background"
I'm making a ToDoApp just for a side project, as an excuse to learn Gradle. The initial build worked fine and the JAR worked as expected.[a]. But that version had no external dependencies; it was self-contained.
I was encouraged to add persistence to the project and I decided to use the GSON library (version 2.6.2) to work with JSON. I use IntelliJ IDEA (2016.1.2) to write code.
Now, I added the gson-2.6.2 artifact via the Maven Repository option in IntelliJ (in Project Structure). While writing the code, I added the library to the classpath of the project when prompted by IntelliJ. The code compiles and works fine when I run it in my IDE. Here's a sample run:
========ToDo App========
The following commands are recognised:
► help
Display this help message.
► add <name>
Add a new Todo task with the given name and also displays its corresponding ID.
► get <id>
Displays the task with the given id.
► mark <id>
Toggles the given task as completed or incomplete.
► print
Displays all tasks in order of their creation.
► update <id> <new text>
Updates the item with the given id to store the new text.
► del <id>
Deletes the task with the given id.
► exit
Exit the program.
The tasks are :
>> add Get the Gradle build to work.
New item added with id = 1
>> add Push the stable system to GitHub and wait for Travis to give the green signal.
New item added with id = 2
>> add Proceed
New item added with id = 3
>> print
The tasks are :
• Get the Gradle build to work. [ID: 1 Completed: false]
• Push the stable system to GitHub and wait for Travis to give the green signal. [ID: 2 Completed: false]
• Proceed [ID: 3 Completed: false]
>> exit
It works fine. The JSON data gets saved as I expect:
{"currentId":3,"toDos":{"1":{"id":1,"name":" Get the Gradle build to work. ","completed":false},"2":{"id":2,"name":" Push the stable system to GitHub and wait for Travis to give the green signal.","completed":false},"3":{"id":3,"name":" Proceed","completed":false}}}
When I rerun the code, the data is read back properly. I'm very happy with my code.
The "situation"
Here's my build.gradle:
group 'ml.cristatus'
version = '0.2'
apply plugin: 'java'
sourceCompatibility = 1.7
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.code.gson:gson:2.6.2'
}
jar {
manifest {
attributes 'Main-Class': 'ml.cristatus.todo.ToDoApp'
}
}
When I run gradle build on my Ubuntu 16.04 terminal, the JAR file is built with the following output:
:compileJava
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
BUILD SUCCESSFUL
Total time: 7.036 secs
This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.13/userguide/gradle_daemon.html
So I somehow managed to make the thing compile. But when I try to run it, using java -jar build/libs/ToDoApp-0.2.jar, the code doesn't work:
========ToDo App========
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gson/Gson
at ml.cristatus.todo.repository.ToDoRepositoryWithJSON.<init>(ToDoRepositoryWithJSON.java:25)
at ml.cristatus.todo.ToDoApp.REPL(ToDoApp.java:38)
at ml.cristatus.todo.ToDoApp.main(ToDoApp.java:17)
Caused by: java.lang.ClassNotFoundException: com.google.gson.Gson
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 3 more
I'm probably making some newbie mistake, but I can't seem to be able to put my finger on it. What am I doing wrong? It is also interesting to note that the code compiles yet it can't find the gson artifact. I'm guessing it has something to do with the classpath? I don't know. I'm not sure.
Please help me in this regard.
Solution
I just added this line to the jar {} block:
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
Courtesy of this tutorial.
[a]Here's the version 0.1 binary.

You have to add jar file with gson jar to classpath. When you are starting application ("java -jar build/libs/ToDoApp-0.2.jar"). There is multiple ways it can be achiveved.
One possible way is:
Add task into your gradle, which copy dependencies (gson.jar in your case) into "lib" directory. And when you are starting your application, add it to classpath. For example in this way "java -cp build/lib/gson.jar -jar build/libs/ToDoApp-0.2.jar"
Maybe better for you will be:
Add dependencies into your manifest. It is discussed in this answer how to copy the dependencies libraries JARs in gradle
It should work for you.
Next Option is:
Create an "uberjar" it means add all dependencies into one big jar file. Personally I don't like it. But it will work in your case. How to create uberjar in gradle is here discussed here: Building a uberjar with Gradle

Related

gradle: Skipping task ':test' as it has no source files and no previous output files

The problem seems to have to do with the fact that the project structure is wrong. As I read on another SO thread, the java project structure should follow /src/main/java/com and /src/test/java/com for the tests. I triple checked and the structure is correct.
The build task do work though. The build folder is populated and the jar file is created.
I can run the tests in vs-code without issues.
There are other projects in the same workspace which runs the test task just fine.
Here's the debug log on the problematic project:
Task :modules:advisors:test NO-SOURCE
[...]
Skipping task ':modules:advisors:test' as it has no source files and no previous output files.
[...]
Task :modules:advisors:jacocoTestReport SKIPPED
Here's my setup:
gradle version: 4.10.2
vs-code version: 1.47.3
vs-code java version: v0.57.0
Update:
I found what I did wrong when migrating from Maven to Gradle. Turns out the tests class name must end with "Test" to get picked up since I'm no longer using maven-surefire-plugin. I was instead naming them starting with "Test".
Here's the documentation for the maven-surefire-plugin: http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html
There is two ways to fix this:
Edit the names of all classes so they end with "Test"
Add this snippet of code in your workspace build.gradle
test {
include "**/Test*.class"
include "**/*Test.class"
include "**/*Test.class"
include "**/*TestCase.class"
}

Multi project gradle build - java.io.IOException: Unable to delete file

I have a multi project build
main-module
-> api
-> pets
-> gateway
Configured settings.gradle in root project to include all the subprojects.
Included api in pets and gateway -> compile project(":api").
Start pets with gradlew run -> starts successfully
Start gateway with gradlew run -> I get below error
Execution failed for task ':api:jar'.
java.io.IOException: Unable to delete file: \main-module\api\build\libs\api.jar
How do I resolve this? Thanks.
It seems there's something wrong with your build. The api:jar task should be considered as UP-TO-DATE at step 4 (since step 3 has already built it and nothing has changed) but it seems that it's trying to build the jar again. Most likely cause is a task input (a file) has changed
I'd guess that some of your task inputs/outputs are incorrect. Or maybe you generate a file with the current date/time in it as part of your build process?. You can try running gradle with --info to see why api:jar task is not UP-TO-DATE for step 4
See up to date checks

Gradle builds, then ignores sub-project

I have a gradle project in big-project/ and a sub-project in big-project/lib/.
In big-project/'s big.project.Main class I import big.project.lib.Utils, which is defined in the big-project/lib/ sub-project.
When I try to run gradle build, this is what happens:
:lib:
compileJava UP-TO-DATE
:lib:processResources UP-TO-DATE
:lib:classes UP-TO-DATE
:lib:jar
:compileJava
big-project/src/main/java/big/project/Main.java:3: error: package big.project.lib not exist
import big.project.lib.Utils;
^
1 error
:compileJava FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 6.243 secs
For some reason, gradle compiles the code of the subproject, and then decides to ignore it. How do I fix that?
These are my gradle files:
big-project/settings.gradle:
include 'lib'
big-project/build.gradle:
evaluationDependsOnChildren()
allprojects {
apply plugin: 'java'
sourceCompatibility = '1.8'
version = '1.0'
group = 'big.project'
}
apply plugin: 'war'
dependencies {
compile project(':lib')
}
big-project/lib/build.gradle is empty
I did study the gradle manual: Chapter 56. Multi-project Builds, but nothing useful came from this.
TL;DR: My big-project/lib/src folder wasn't set up correctly (source code was in the wrong sub directory). As this answer states, Java sources need to be in src/main/java.
Thanks to comments on the question I was able to look in the right place for a solution. As suggested, I looked at a working sample project. As I was using Eclipse, I generated a new Gradle project based on the flat-java-multiproject sample. This enabled me to eliminate my build.gradle files as the source of the error.
Next, I decided to inspect the build/ artifacts of the lib/ project and discovered that the .jar file did not contain any .class files. That explains the compiler error I reported in the question. This caused my to see if all .java sources were in the right place, and as mentioned above, they weren't. Fixing this, fixed the compiler error.
The whole crux was that while I was writing my code in eclipse, all references to the lib project's classes seemed to be found.

Understanding gradle multiproject building

I have the following project tree:
root
|
|--MP
| |
| |---build.gradle
|
|--API
|
|---build.gradle
|
|---settings.gradle
MP::buiild.gradle:
dependencies {
compile project(':API')
}
root:build.gradle:
subprojects{
apply plugin : 'java'
repositories{
mavenCentral()
}
version = '1.0'
jar{
manifest{
attributes 'Gradle': 'Multiproject'
}
}
}
root::settings.gradle:
include 'API', 'MP'
The thing is if we delete one of these three files gradle build task will fail. So it's not clear to me how java plugin builds the project. I run gradle build for MP::build.gradle, the following output was produced:
:API:compileJava
:API:processResources UP-TO-DATE
:API:classes
:API:jar
:MP:compileJava
:MP:processResources UP-TO-DATE
:MP:classes
:MP:jar
:MP:assemble
:MP:compileTestJava UP-TO-DATE
:MP:processTestResources UP-TO-DATE
:MP:testClasses UP-TO-DATE
:MP:test UP-TO-DATE
:MP:check UP-TO-DATE
:MP:build
So, the first what we need to do when we run gradle build for MP::build.gradle is to resolve all dependecies. As far as I understand it means to load jars from an external repositories and, if need, to compile jar-files from a separate projects. In my case it's just to get API project jar-file.
So my question is what is the subsequnce of actions to compile that jar. What will happens when gradle came across the compie project(':API'). It's looking for the gradle.settings file and report an error if there isn't or it's looking for build.gradle in the root directory first?
To have quick look of what is going on in a java multiproject:
http://www.gradle.org/docs/current/userguide/tutorial_java_projects.html
"For the subsequence of actions to compile that jar." Look at the diagram
http://www.gradle.org/docs/current/userguide/tutorial_java_projects.html
And for crossproject dependencies
http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:cross_project_configuration
Quote: "By default, the configuration of all projects happens before any task is executed"
I hope you have already figured it out .
So the gradle build life cycle explains it ... the life cycle is as below
1) initialisation
2) configuation
3) execuion .
for your particular case of a multi project build , what happens is
1) initialisation ::
here the settings.gradle is searched for no matter from which project you run it (it always tries to find settings.gradle file when you run a task and includes those projects defined in its include directive.)
2) configures
it creates the task tree based on the task you have tried to run and its dependencies.
3)execution ::
runs the task tree.
Please let me know if this is helpful.
You can read this page for more clarity.
http://www.gradle.org/docs/current/userguide/build_lifecycle.html

Incremental Gradle Build not saying UP-TO-DATE

Gradle, being an incremental build system, is supposed to detect when no changes to source or outputs have been made, and skip tasks when appropriate to save on build time.
However, in my build, subsequent executions of a gradle task, with no changes in between, this incremental feature is not working. compileJava, jar, etc are executing with every build rather than only when changes have been made.
Our build is pretty complex (switching to gradle from a very old, very messy ant build), so I'm just going to show a small snippet:
buildDir = 'build-server'
jar {
sourceSets.main.java.srcDirs = ['src',
'../otherProject/src']
sourceSets.main.java {
include 'com/ourCompany/pkgone/allocation/**'
include 'com/ourCompany/pkgone/authenticationmngr/**'
...
//Excludes from all VOBs
exclude 'com/ourCompany/pkgtwo/polling/**'
}
sourceSets.main.resources.srcDirs = ['cotsConfig/ejbconfig']
sourceSets.main.resources {
include 'META-INF/**'
}
}
dependencies {
compile project(':common')
}
Running gradle jar on this project twice in a row results in the following output:
P:\Project>gradlew jar
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:clnt:compileJava
:clnt:processResources UP-TO-DATE
:clnt:classes
:clnt:jar
BUILD SUCCESSFUL
Total time: 1 mins 46.802 secs
P:\Project>gradlew jar
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:clnt:compileJava
:clnt:processResources UP-TO-DATE
:clnt:classes
:clnt:jar
BUILD SUCCESSFUL
My question is, what might I be doing that would prevent the up-to-date detection to not work properly? Could it be related to my complicated build path?
When you run with --info, Gradle will tell you why the task isn't up-to-date.
Two things you need to do
Check if your gradle version is above 2.1
There is a way setting incremental build:
tasks.withType(JavaCompile) {
options.incremental = true // one flag, and things will get MUCH faster
}
Reference is here: https://blog.gradle.org/incremental-compiler-avoidance

Categories

Resources