0. Prerrequisites
1. Set the JAVA_HOME to a Java installation. Java 17 does NOT work well with gradle
1.a In the ~/.bashrc file for a single user
export JAVA_HOME=<path to your version of java> export PATH=${PATH}:${JAVA_HOME}/bin
1.b in the /etc/profile file for all users
JAVA_HOME=<path to your version of java>
PATH=$PATH:$HOME/bin:$JAVA_HOME/bin export JAVA_HOME export PATH
1.c in the /etc/environment file
JAVA_HOME=<path to your version of java>
export JAVA_HOME
1.c in the /etc/environment file
JAVA_HOME=<path to your version of java>
export JAVA_HOME
1.d Execute export JAVA_HOME=<path to your version of java>
2. Download gradle from download page
3. Set the PATH variable to gradle, using any of the above methods
4. Create a folder (e.g. MyProject), with a terminal go into this folder and type gradle init
5. Import the gradle project in Eclipse (File->Import->Gradle-> Existing Gradle project)
1. settings.gradle
rootProjectName=myProject println 'Initialization Phase'
2. build.gradle
println 'Printing...' def a = 5 ; def b=6 ; println a +b + ' Hola world ---' println 'Configuration Phase'
description 'Just my project'
group 'com.ximodante'
version '1.0-SNAPSHOT'
3. gradlew
./gradlew tasks
./gradlew clean
./gradlew copyMessage
./gradlew cM
./gradlew --version
./gradlew wrapper --gradle-version=7.5
4. Tasks
import org.apache.tools.ant.filters.ReplaceTokens tasks.register('copyMessage', Copy) { //Task with a class (Copy) group 'My Own Tasks' // show tasks grouped when using "gradle tasks" description 'Copy and replace template files with substitution text' from 'myFile.txt' into "$BuildDir/myFiles" filter (ReplaceTokens, tokens: [TO_REPLACE: "String replacement"]) } tasks.register('zipDescriptions', Zip) { group 'My Own Tasks' description 'zip files' from "$buildDir/myFiles" //Approach 1
//Indicates that the output of task copyMessabe is the input (recommended)
// and does NOT need the dependsOnfrom copyMessage //Approach 2destinationDirectory = buildDir archiveFileName = 'descriptions.zip'
//Indicates that this task (copyMessage) must be executed before.
//DO NOT USE IF input points to the previous task!!!!
dependsOn task.named('copyMessage') //Approach 1
dependsOn copyMessage //Approach 2dependsOn copyMessage, otherTask, ... //Several tasks dependences
//Indivcates that this tasks shouild be executed after
finalizedBy anotherTask
//To throw an exceptio!!!
doLast {
throw new GradleException ('My exception is thrown!!!!')
}
}
//Not recommended task definition
task('zipDescriptions2', type: Zip) { //Lower performace group 'My Own Tasks' description 'zip files' from "$buildDir/myFiles" destinationDirectory = buildDir archiveFileName = 'descriptions.zip' }
//Adhoc task that is not an instance of another class
tasks.register('adhocTask') { //Task without a classdoFirst { //closure
println 'hello!'}doLast { //closure
println 'Bye!'}
enabled false // or trueonlyif { // similar to enabled
5 == 3 +2} }
//Task that uses the same name as the first task but copies to another directory
tasks.named('copyMessage') {into "$BuildDir/myFiles-bis" }
//Task that uses the same name as the first task but copies to another directory
// NOT recommended!!!
tasks.getByName('copyMessage') {into "$BuildDir/myFiles-bis-bis"
}
//Call a task by its name (usually tasks added by plugin
tasks.clean {doLast {println "Squeaky clean" }
}
//Call a task by its name (usually tasks added by plugin but simpler
clean {doLast {println "Squeaky clean" }
}
4.1 Tasks documentation
https://docs.gradle.org/current/dsl/index.html, look for tasks and can see several defined tasks (Delete, Copy, jar..), For task Delete:
tasks.register('myDelete', Delete) { //Task with a class (Copy) group 'My Own Tasks' // show tasks grouped when using "gradle tasks" description 'Delete some stuff' //followSymlinks = true
}
4.2 Tasks graph
The taskinfo plugin shows us the task graph
plugins { id 'base'
id "org.barfuin.gradle.taskinfo" version "1.3.0"
}
To see the tasks -> ./gradlew build tiTree
5. Plugins
plugins { id 'base'
id "org.barfuin.gradle.taskinfo" version "1.3.0" //Other plugins
}
//Alternative to "plugins" NOT RCOMMENDED
apply plugin: 'base'
// archiveFileName is defined by default in plugin id to 'gradle'
// archiveFileName is a variable that can be used in any task
// there are also other defined defined by default variables taht can be changed
base {
archivesName = 'stuff'
}
5.1 Plugins documentation
6. Gradle
gradle init
1: Basic, 2: Application, 3: library, 4:Gradle plugin
1: Groovy, 2: Kotlin
gradle wrapper
7. Git
git status
git init
git add .
git commit -m "First commit of the project"
.gitignore
8. Groovy
JVM language
Can use any standard java library
dynamic typed
less verbose (optional semicolons, run code as script, optional parenthesis)
support closures (block of code passed as a variable)
pass closures outside brackets.
9. Kotlin
JVM language
statically typed
string double quotes
has functions but NO methods
need parenthesis
closures
pass closures outside brackets
IntelliJ suggestions of code
9.1 Example of build.gradle.kts
import org.apache.tools.ant.filters.ReplaceTokens plugins { base } tasks.register<Copy> ("generateDescriptions") { from ("descriptions") into ("$buildDir/descriptions") filters(ReplaceTokens::class, "tokens" to mapOf("TO_REPLACE" to "String replacement")) }
10. Gradle api & documentation
11. Repositories and dependencies
Maven
Ivy
Flat directory
Built-in repositories: (Maven Central , Google Maven)
repoitories { mavenCentral() google() maven { // Custom repository url 'https://tomgregory-2994844798587.d.codeartifact.eu-west-1amazonaws.com/maven/demo/' } }
dependencies {
implementation ('commons-beanutls:commons-beanutls:1.9.4) {
// Exclude transitive dependency !!!
exclude( group = "commons-collections", module = "commons-collections"
}
Multiples stages to build an application (Compiling, Testing, Running)
12. Java plugin
plugins { id 'java' }
Included Tasks:
1. compileJava: generates .class files in the build directory. ./gradlew compileJava .
2. processResources: copies resources into build directory. ./gradlew processResources .
3. jar: adds compiled classes and resources to .jar archive. The name of the file is
<project-name>-<version.jar. ./gradlew jar .
4. test: compiles, processes test resources and run tests. Test report in buid directory. ./gradlew processResources .
Type of tasks:
action task: performs an action
aggregate task: groups other tasks
Tasks to execute with ./gradlew:
clean: removes the build directory.
assemble & jar: Compile, assemble resources and build jar. (build/(classes,libs,resources/nain)
check & test: assemble task+ assemble test classes and resources and run tests (resources/test, report)
build: assemble task+ test task
Define dependencies:
Defines the configuration used to generate classpath.
Classpaths: compile and runtime
Keywords:
compileOnly -> compile path (e.g. "servlet-api" the implementation is supplied by the server)
runtimeOnly -> runtime path (e.g. to connect to a DB, you don't need to know DB internals )
implementation -> both compile and runtime
testCompileOnly, test implementation, testRuntime: similar for tests
Annotation processor: like Lombok or MapStruct
Extra properties for dependencies:
For not repeating versions use "ext" for defining a property and double quotes for using it
ext { jujVersion = '5.7.2' //Define version } dependencies { //Use double quotes!! testImplementation "org.junit.jupiter:junit-jupiter-api:$jujVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$jujVersion" testRuntimeOnly "org.junit.jupiter:junit-engine-api:$jujVersion" }
Project default layout:
src/main/java >>> build/classes/java/main
src/main/resources >>> build/resources/main
src/test/java >>> build/classes/java/test
src/test/resources >>> build/resources/test
The jar file is saved in build/libs
Execute the application:
java -jar <jar-file-locatiom> parameter
Selecting the main class in the jar task:
tasks.name ('jar') {
manifest {attributes('Main-Class': 'org.ximo.MyMainClass')
} }
tasks.named('jar') {
manifest {
attributes('Main-Class': 'com.gradlehero.themepark.RideStatusService')
}
}
Class types for tasks:
- compileJava and compileTestJava are of type JavaCompile class
- processResources and processTestResources are of type Copy class
- jar is of type Jar class
Making changes in tasks (by name or by class):
It can be made using 2 options:
- tasks.named('taskName') That affects only to the task with "taskName"
- tasks.withType(taskType).configureEach Affecting all the tasks of "taskType"
//The tasks compileJava and compileTestJava use JavaCompile Class
tasks.named('compileJava') { options.verbose = true //For verbose results
}
tasks.named('compileTestJava') { options.verbose = true //For verbose results
}//The above can be simplified as
tasks.withType('JavaCompile') { options.verbose = true }
//==========================================================
//The tasks processResources and processTestesources use Copy Class
tasks.named('processResources') {logflume
include '**/*.txt' //Only copies resources that ends with ".txt"
}
//==========================================================
//The task jar usee Jar Class
tasks.named('jar') {
archiveFileName = 'myJar.jar'' //Sets the jar name
}
Make sure that you have enabled tests:
tasks.withType(Test).configureEach {
useJUnitPlatform()
}
Example of java test class:
public class RideStatusServiceTest { //For displaying results: @ParameterizedTest(name = "{index} gets {0} ride status") //For executing the test with these parameter each time @ValueSource(strings = {"rollercoaster", "logflume", "teacups"}) //And applies to this test public void getsRideStatus(String ride) { RideStatusService rideStatusService = new RideStatusService(); String rideStatus = rideStatusService.getRideStatus(ride); assertNotNull(rideStatus); } @Test public void unknownRideCausesFailure() { RideStatusService rideStatusService = new RideStatusService(); //An exception must be raised if the parameter is not good! assertThrows(IllegalArgumentException.class, () -> { rideStatusService.getRideStatus("dodgems"); }); } }
Saving project to git:
git init Initialises
git add . If you don't execute it, Git does not save anything!!!
git status
git commit -m "Create Java Project for all of you."
13. Application plugin (run java program)
plugins { id 'application' }
Included Tasks:
1. run: executes the java application. ./gradlew run .
Defining the main class:
It has been defined previously, in the "jar" task how to set the "main" class, but in the application plugin, it is defined ALSO in the "application" section
aplication { mainClass 'org.ximo.MyMainClass' }
tasks.name ('jar') {manifest {attributes('Main-Class': 'org.ximo.MyMainClass')
} }
Running the application with parameters creating a new task:
Execute the "run" task and don't forget to pass parameters! Use --args
In Eclipse you should run the application in a terminal using ./gradlew run --args myParameter .
I have not been able to add arguments to gradle run task in the gradle task window in Eclipse!!!.
Tom Gregory suggests adding arguments by updating the "javaExec" task in the build.gradle file, redefining the jar of the application and the runtime jars and also the main class!!!
tasks.register('runJar', JavaExec) {// Many redundances !!!!!
group 'My Own Tasks' // show tasks grouped when using "gradle tasks"
description 'execute with arguments'
// Uses the ouput from jar task (only the outputs
classpath tasks.named('jar').map { it.outputs } // collects main jar
classpath configurations.runtimeClasspath // collects runtime jar dependencies
// Add arguments to the run
args 'myParameter'
mainClass = 'org.ximo.MyMainClass' // reenter the main class !!!}
Debugging the application using 2 ways:
1. executing ./gradlew runJar --debug-jvm and open IDE on port 5005 (this can be tricky as the java compiler of gradlew and project must be the same. Maybe intelliJ can be better for this.
2. Debug as an application in Eclipse (but it can be tricky)
The process of debugging. Using Jupiter, JUnit, and Testng
1. Jupiter
dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.2' }
tasks.withType(Test).configureEach { useJUnitPlatform() }
2. JUnit
dependencies { testImplementation 'junit:junit:4.3.12' }
tasks.withType(Test).configureEach { // Empty }
3. TestNG
dependencies { testImplementation 'org.testng.testng:7.7.0' }
tasks.withType(Test).configureEach {useTestNG() }
Including and excluding only some classes for testing
tasks.withType(Test).configureEach {
useJUnitPlatform()include '**/*MyTest*'
exclude '**/*OtherTest*'
}
Executing a single test
./gradlew test --test MyTest executes a class for testing
./gradlew test --test MyTest.someMethod executes a method of a class for testing
./gradlew test --test MyTest.*someMethod* executes matching method of a class for testing
./gradlew test --test org.tests.MyTest.someMethod fully qualified name
Executing a test from zero (cleanTest option)
Only a test is executed if there have been changes, or the clean task has been previously used. If you want to execute it without making changes
./gradlew cleanTest test
Eclipse IDE executing a test method in the editor window:
Right click on the method name of the source editor window and select "Run as -> JUnit Test"
13.1 JVM Test Suite plugin (Gradle>=7.3)
Applied automatically with java plugin
Each test suite has source directory and task
"test" is the default test suite for unit tests.
Define the required tests in the "testing" section, and can supply specific dependencies
testing {// Define the required integration tests
suites {integrationTest(JvmTestSuite) {//Define internal dependencies
dependencies {// Depends on the production code
implementation project}}}}
And is available in the tasks (verification-integrationTest)
1. Create the folder src/integrationTest/java
2. Add the package com.gradlehero.themepark
3. Add a "source folder" and select the created folder "src/integrationTest/java"
Task check
In the the Gradle task window, in the verification folder, there is the check task that can be set to execute the integration test, used before committing code or continuous integration pipeline.
tasks.named('check') { dependsOn testing.suites.integrationTest }
13.2 Java Version
Take care to have the same Java version for
1. Eclipse workspace settings
2. ./gradlew
3. build.gradle
In Eclipse, right click on the project and select "properties->Gradle" and select the convenient Java version
In gradlew, execute ./gradlew --version and we get
to change the java version, in the terminal type
export JAVA_HOME=<your new java path>
In the build.gradle we can set the java version as java-toolchain
java { toolchain {// Forces gradlew to use version 17 independenly of the JAVA_PATH!!!!
// If not found this java version, gradlew DONWLOADS it
languageVersion = JavaLanguageVersion.of(17)
} }
Select another java version for executing the application:
Although we have the previous java-toolchain, we can additionally select another java version for executing
java { toolchain {// Forces gradlew to use version 17 independenly of the JAVA_PATH!!!!
// If not found this java version, gradlew DONWLOADS it
languageVersion = JavaLanguageVersion.of(17)
} }
// Forces gradle to execute the application with Java 18 although we have// compiled the application with version 17!!!!!tasks.withType(JavaExec).configureEach {
javaLauncher = javaToolchains.launcherFor {languageVersion = JavaLanguageVersion.of(18)
}}
Select different java versions for different tasks:
// Compile the application with version 17!!!!!tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {languageVersion = JavaLanguageVersion.of(17)
}}
// Forces this test task to be executed with java 18tasks('myTest', Test) {javaLauncher = javaToolchains.launcherFor {languageVersion = JavaLanguageVersion.of(18)
}}
14. maven-publish plugin
plugins { id 'application'id 'maven-publish'
}
Identifying artifacts to publish and the destination:
Specify: group, version, publications and destination repository (if repository is remote)!
// You must supply a group and a version for publishing
group 'com.gradlehero'
version '0.1.0-SNAPSHOT'
// Publishing part
publishing { // Select from publications{ maven(MavenPublication) { // select components to publish from components.java //java file created by the java task } } //destination repositories { maven { url 'https://<your remote repository>' credentials { username "myUser" // password set by an environment variable password System.env.CODEARTIFACT_AUTH_TOKEN } } } }
Publishing : execute ./gradlew publish
14.1 Local maven repository
There is a local repository, so try to execute in the terminal
ls -l ~/.m2/repository
Publish to maven local using ./gradlew publishToMavenLocal
and verify that the project has been added
ls -l ~/.m2/repository/com/gradlehero
15. Spring Boot applications
15.1 With java plugin
plugins { id 'java' } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:2.5.3' } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
//===================================== // ThemeParkApplication //===================================== package com.gradlehero.themepark; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThemeParkApplication { public static void main(String[] args) { SpringApplication.run(ThemeParkApplication.class); } } //===================================== // ThemeParkRide //===================================== package com.gradlehero.themepark; public record ThemeParkRide(String name, String description) { } //===================================== // ThemeParkRideController //===================================== package com.gradlehero.themepark; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.Iterator; @RestController public class ThemeParkRideController { @GetMapping(path = "/rides") public Iterator<ThemeParkRide> getRides() { return Arrays.asList( new ThemeParkRide("Rollercoaster", "Train ride that speeds you along."), new ThemeParkRide("Log flume", "Boat ride with plenty of splashes."), new ThemeParkRide("Teacups", "Spinning ride in a giant tea-cup.") ).iterator(); } }
Run the application by right-clicking the ThemeParkApplication class and "run -run as java application"
If we change the java plugin with the application plugin we need to add this code to the build.gradle
application {
mainClass = 'com.gradlehero.themepark.ThemeParkApplication'
}
15.2 With springframework plugin
plugins { id 'java'
id 'org.springframework.boot' version '2.5.3'
// takes the version from springframework plugin id 'io.spring.dependency-management' version '1.0.11.RELEASE'
} repositories { mavenCentral() } dependencies {// takes the version from springframework plugin implementation 'org.springframework.boot:spring-boot-starter-web' } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
Run the application with ./gradlew bootRun
16. java-library plugin
plugins { id 'java-library'}
Concepts:
- direct dependency (used explicitly by the application)
- transitive dependency (used by a dependency that is used by the application)
Dependency resolution:
- Only one version per group and name in dependencies.
- Gradle chooses the highest version of the dependency.
api vs implementation:
When creating a library, if the interface of any class or method in the created library contains any reference to a used library, then when declaring the dependency use api.
If our created library does not use any reference to the internally used library, then use implentation.
But this differentiation can be tricky in certain circumstances.
16.1 Special scenarios for dependency conflicts.
1. Simple exclude (transitive) of an unneeded dependency: We are sure that a transitive dependency of a direct dependency is not used. We can exclude it.
dependencies { implementation 'com.google.guava:guava:31.0.1-jre' {// unneeded dependency
exclude group: 'com.google.code.findbugs', module; 'jsr305'}
2. Version upgrade (transitive): A transitive dependency (of a direct dependency) is buggy and a new version is available.
dependencies { implementation 'com.google.guava:guava:31.0.1-jre'// change to a newer version not buggy. Does not work for version DOWNGRADE!!!constrains {
implementation ('org.checkerframework:checker-qual:3.13.0') {because ('previous versions have a security vulnerability')}}
3. Version conflict (of a transitive dependency with direct dependency): A transitive dependency (of a direct dependency) conflicts with a direct dependency that has a different group and name.
There are 2 solutions for this:
a. Using configuration-level exclude.
b. Module replacement.
dependencies { // uses "starter.logging"
implementation 'org.springframework.boot:spring-boot-starter-web:2.6.2'// Conflicts with "starter.logging"
implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.5.2:2.6.2'// Option a. Exclude starter-logging. Never will be used!!!!
configuration.implementation {exclude group: 'org.springframework.boot', module:'spring-boot-starter-logging'}// Option b. Module replacement
modules {module( 'org.springframework.boot:spring-boot-starter-logging')replacedBy ''org.springframework.boot:spring-boot-starter-log4j2'. 'Use Log42 instead of logback'}}
16.2 Dependencies task
Run ./gradlew dependencies to show all transitive dependencies tree of all direct dependencies
The omitted dependencies are marked as (*).
The not resolved dependencies are marked as (n).
Run ./gradlew dependencies --configuration compileClasspath to show all transitive dependencies tree of all direct dependencies during the compilation process
Run ./gradlew dependencies --dependency org.slf4j:slf4j-api --configuration compileClasspath to show all versions of a dependency used transitively or directly during the compilation process, without informing the library that uses it
When a dependency cannot be resolved, it is marked as FAILED.
17. Logging instead of println
17.1 logger:
There are 6 log levels: (1) Error, (2) Quiet (--quiet), (3) warn (--warn), (4) lifecicle (default), (5) info (--info), (6) debug (--debug)
first, create a task and use for instance
logger.info 'Some data {}', System.currentTimerMillis()
tasks.register('logTest') { doLast {
logger.info 'Some data {}', System.currenttimeMillis()}}
and now run it indicating the log level of "info" or a down level (e.g. debug) in order to see the messages.
./gradlew logTest --debug
17.2 Detecting errors:
1. Clear (syntax) error detected by task build:
If you write "implementatino" instead of implementation, gradle complains "Could not find method implementatino()", but does not say at what line it is, you need to search it whit an editor.
2. Error in the code of a custom tag being executed:
You can add --stacktrace option to show more info ./gradlew mytask --stacktrace and displays the line number of the error
3. Debug out compiler options:
You can see whow your code is being compiled
./gradlew compileJava --debug | grep "Compiler arguments"
3. Log out dependency resolutions:
You can force gradle to refresh the dependencies from repository and show the latest version
./gradlew compileJava --refresh-dependencies --info
18. Password protection in build.gradle
18.1 defining and passing parameters to execute a task:
Place parameters mavenUserName and mavenPassword parameters instead of 'myUser' and 'myPassword'
publishing {publications{ maven(MavenPublication) { from components.java //java file created by the java task } } repositories { maven { url 'https://<your remote repository>' credentials {// username and password ara parametersusername mavenUserName //'myUser' password mavenPassword //'myPassword' } } } }
call the task as follows
./gradlew publish -PmavenUserName=myUser -PmavenPassword=myPassword
19. ProjectProperties
19.1 Passing properties for executing a task
1. On the command line using -P
./gradlew <task-name> -PmyPropName=myProvValue
2. As java system property using -D
./gradlew <task-name> -Dorg.gradle.project.myPropName=myProvValue
3. As environment variables
ORG_GRADLE_PROJECT_myPropName=myProvValue ./gradlew <task-name>
4. In the file gradle.properties
myPropName=myProvValue
19.1 Accesing properties in the definition of tasks (build.maven)
1. Directly as a variable
println myPropName
2. Using property method
println project.property('myPropName')
3. Using findProperty method
println project.findProperty('myPropName')
4. Using Elvis operator to return a default value
println project.findProperty('myPropName') ?: 'default value'
5. Check if exist a property with hasProperty method
if (hasProperty('myPropName') {...