Gradle Quick Start - kaushikdas/TechnicalWritings GitHub Wiki
- 1. Introduction
- 2. Creating Java Project in IntelliJ (Community 2020.1) without using Gradle
- 3. Java Plugin for Gradle
- 4. Creating Gradle Project in IntelliJ (Community 2020.1)
- 5. A Super Crash Course on Groovy
- 6. Gradle Project Object Model
- 6. Gradle Project Object Model
- 7. Dependency Mangement
- Build File
- Construction of a Graph of Tasks
- Execution of Tasks
- Gradle Wrapper
- Dependency Management
- Use of Repositories
- Self Updating
- Human and machine readable instruction file
- Partly declarative and partly programmatic
- Uses DSL (Domain Specific Language) to describe the build
- Uses Groovy (or Kotlin) to specify lower level details of the build
- Default name:
build.gradle
- Tasks: detailed build steps
- Gradle parses the build file and creates a DAG (Directed Acyclic Graph) of tasks
~ $ mkdir hello-gradle
~ $ cd hello-gradle
Create a file build.gradle
:
~/hello-gradle $ vi build.gradle
task helloGradleTask { // Create task named helloGradleTask
doLast { // add a method to the task
println "Hello Gradle!"
}
}
Run the created task
~/hello-gradle $ gradle helloGradleTask
> Task :helloGradleTask
Hello Gradle!
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
If we choose to use Kotlin the gradle file name should be
build.gradle.kts
:tasks.create("helloGradleTask") { doLast { println("Hello Gradle!") } }
- Executes as per the order indicated by the DAG
- Output of task = Input of the (next) task
- Saves output of the each task
- When we rerun the build if the output of a task remains the same then there is no need to run the next task because its output will not change
-
A wrapper over gradle that allows to specify the required Gradle version for the project. If that version is not present it will be automatically installed
-
Create gradle wrapper using command
gradle wrapper
~/hello-gradle $ gradle wrapper
~/hello-gradle $ ls -l
total 13
-rw-r--r-- 1 KausPratMaha 197121 141 Jun 12 20:15 build.gradle
drwxr-xr-x 1 KausPratMaha 197121 0 Jun 12 20:31 gradle/
-rwxr-xr-x 1 KausPratMaha 197121 5770 Jun 12 20:31 gradlew*
-rw-r--r-- 1 KausPratMaha 197121 3058 Jun 12 20:31 gradlew.bat
This command creates the gradle
directory and two executable files
gradlew
and gradlew.bat
required for x-nix type of system and
Windows system, respectively. These executables run the jar
gradle/wrapper/gradle-wrapper
to check which version of the gradle is
installed and which version is required. It knows which version of
gradle is required using the entry distributionUrl
in the file
gradle/wrapper/gradle-wrapper.properties
. If the required gradle
version is not present it downloads the required gradle version.
- We should check-in all these generated by
gradle wrapper
to source control.- We can specify the gradle version we need using
--gradle-version
:gradle wrapper --gardle-version 6.4.1
~/hello-gradle $ cat gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
To use gradle wrapper we can use the gradlew
and gradle task that
we want to run. For example:
~/hello-gradle $ .\gradlew.bat helloGradleTask
> Task :helloGradleTask
Hello Gradle!
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
Benefits of using Gradle Wrapper
- Required gradle version is automatically installed (if not available already)
- Works the same way on continuoys intergration servers like Jenkins
- Dependencies to external Java code and libraries
- May have transitive dependencies
- Ensures that right version of the dependencies
- Repositories: Storehouse of external dependencies
- On the Internet/with the enterprise/on the local machine
- Automatically updates to newer version
- Auto-retrieval of newer versions of dependencies
-
Single project build
root_project_name βββ build.gradle βββ src βββ ...
-
src
is a directory
-
-
Multi-project build
root_project_name βββ build.gradle βββ settings.gradle βββ module_1 β βββ build.gradle β βββ src β ... βββ module_2 β βββ build.gradle β βββ src β ...
-
Settings file
settings.gradle
inside the root directory- Declares the participating projects (in case of multi-project build)
- Can change the default, viz. the project name
- By default the project name is the name the of the root directory
- We can get the the project name (and other project related info.
using the
projects
tasks. For example if we run theprojects
from insidehello-gradle
directory we get the root project name as'hello-gradle'
~/hello-gradle $ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'hello-gradle' No sub-projects To see a list of the tasks of a project, run gradlew <project-path>:tasks For example, try running gradlew :tasks BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
- We can create the file
settings.gradle
and add therootProject.name
property to set the project name (to some other meaningful name)
~/hello-gradle $ cat settings.gradle rootProject.name = "gradle-essentials"
~/hello-gradle $ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'gradle-essentials' No sub-projects To see a list of the tasks of a project, run gradlew <project-path>:tasks For example, try running gradlew :tasks BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
- It also shows that this vanila project has not sub-projects
-
Gradle properties file β
gradle.properties
- Resides in the project root directory or Gradle user home directory
- If exists in the Gradle user home directory, then it's configuration applies to all Gradle projects executed on the machine
- Preconfigures the runtime behaviour
root_project_name βββ build.gradle βββ settings.gradle βββ gradle.properties ...
~/hello-gradle $ cat gradle.properties org.gradle.logging.level = info version = 1.0.0
-
org.gradle.logging.level = info
changes toinfo
logging level -
version = 1.0.0
specifies the version of the project as1.0.0
- These propery values can also be accessed from
build.gradle
task helloGradleTask { // Create task named helloGradleTask doLast { // add a method to the task println "Hello Gradle! version " + version } }
~/hello-gradle $ ./gradlew helloGradleTask Initialized native services in: C:\Users\KausPratMaha\.gradle\native Found daemon DaemonInfo{pid=19928, address=[ec7419e0-52e6-41df-b1ce-c4e9a28273d5 port:55545, addresses:[/127.0.0.1]], state=Idle, lastBusy=1623506306426, context=DefaultDaemonContext[uid=13fd02a0-d0c8-4676-abb2-dd9d8032e19a,javaHome=C:\Program Files\Java\jdk1.8.0_221,daemonRegistryDir=C:\Users\KausPratMaha\.gradle\daemon,pid=19928,idleTimeout=10800000,priority=NORMAL,daemonOpts=-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=windows-1252,-Duser.country=IN,-Duser.language=en,-Duser.variant]} however its context does not match the desired criteria. Java home is different. Wanted: DefaultDaemonContext[uid=null,javaHome=C:\Program Files\AdoptOpenJDK\jdk-11.0.9.101-hotspot,daemonRegistryDir=C:\Users\KausPratMaha\.gradle\daemon,pid=2916,idleTimeout=null,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=windows-1252,-Duser.country=IN,-Duser.language=en,-Duser.variant] Actual: DefaultDaemonContext[uid=13fd02a0-d0c8-4676-abb2-dd9d8032e19a,javaHome=C:\Program Files\Java\jdk1.8.0_221,daemonRegistryDir=C:\Users\KausPratMaha\.gradle\daemon,pid=19928,idleTimeout=10800000,priority=NORMAL,daemonOpts=-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=windows-1252,-Duser.country=IN,-Duser.language=en,-Duser.variant] Looking for a different daemon... The client will now receive all logging from the daemon (pid: 15608). The daemon log file: C:\Users\KausPratMaha\.gradle\daemon\6.4.1\daemon-15608.out.log Starting 15th build in daemon [uptime: 2 hrs 29 mins 16.03 secs, performance: 98%, non-heap usage: 19% of 268.4 MB] Using 4 worker leases. Starting Build Settings evaluated using settings file 'C:\Users\KausPratMaha\hello-gradle\settings.gradle'. Projects loaded. Root project using build file 'C:\Users\KausPratMaha\hello-gradle\build.gradle'. Included projects: [root project 'gradle-essentials'] > Configure project : Evaluating root project 'gradle-essentials' using build file 'C:\Users\KausPratMaha\hello-gradle\build.gradle'. Compiling build file 'C:\Users\KausPratMaha\hello-gradle\build.gradle' using SubsetScriptTransformer. Compiling build file 'C:\Users\KausPratMaha\hello-gradle\build.gradle' using BuildScriptTransformer. All projects evaluated. Selected primary task 'helloGradleTask' from project : Tasks to be executed: [task ':helloGradleTask'] Tasks that were excluded: [] :helloGradleTask (Thread[Daemon worker Thread 9,5,main]) started. > Task :helloGradleTask Caching disabled for task ':helloGradleTask' because: Build cache is disabled Task ':helloGradleTask' is not up-to-date because: Task has not declared any outputs despite executing actions. Hello Gradle! version 1.0.0 :helloGradleTask (Thread[Daemon worker Thread 9,5,main]) completed. Took 0.021 secs. BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
- This time in prints lot of information because of `info` log level and also shows the version as `1.0.0` as set in the `gradle.properties` file
- Resides in the project root directory or Gradle user home directory
- Defines an executable unit of work
- Task = set of actions
- Actions contain logic to be executed at runtime
- Two types of tasks
-
Ad hoc tasks
- Implements one-off simplicstic action implementation by defining
doFirst
ordoLast
- Automatically uses the default implementation of task interface
- Extends
DefaultTask
without explicit declaration - Ex.
task helloGradleTask
explained in the previous section
- Extends
ββββββββββββββββ β β β Default Task β β β ββββββββββββββββ β³ β β extends β βββββββββββββββ β β β Ad hoc Task β β β βββββββββββββββ
- Implements one-off simplicstic action implementation by defining
-
Of explicitly declared type
- Complex class logic can be also abstracted by task implementation β types taks in Gradle terminalogy
- Explicitly declares type (viz.
copy
) and does not define actions as those are provided by the type (task)
ββββββββββββββββ β β β (ex.) Copy β βββ This can copy β β files & dirs ββββββββββββββββ β³ β β extends β ββββββββββββββ β β β Typed Task β β β ββββββββββββββ
-
While using types tasks we need not implement any methods, we can just call the methods defined in the typed task and provide values to required properties
-
Let us add a task
copyDocs
inbuild.gradle
usingCopy
typed tasks that will recursively copy all the.md
files frommd
directory tobuild/docs
directoryTarget (
build/docs
) directory will be created is not present
~/hello-gradle $ tree . βββ build.gradle βββ gradle β βββ wrapper β βββ gradle-wrapper.jar β βββ gradle-wrapper.properties βββ gradle.properties βββ gradlew βββ gradlew.bat βββ md β βββ ReadMe.txt β βββ readme.md β βββ sd β β βββ errors.txt β β βββ read_style.md β βββ sd_bak βββ settings.gradle
task copyDocs(type: Copy) { from "md" // Call the `from` method of Copy type task into "build/docs" // Call the `to` method include "**/*.md" // ANT like include patterns // .. `**` -> scan all sub-dirs includeEmptyDirs = false // because by default empty dirs are // ... also copied }
Execute the
copyDocs
task:~/hello-gradle $ gradle copyDocs BUILD SUCCESSFUL in 2s 1 actionable task: 1 executed
Now the
build/docs
directory have the.md
files frommd
dir:~/hello-gradle $ tree build build βββ docs βββ readme.md βββ sd βββ read_style.md
- Let us now add another typed task
createZip
using the task typeZip
to zip the contents frombuild/docs
tobuild/dist
(again this destination directory may not exists already)
task createZip(type: Zip) { from "build/docs" archiveFileName = "dccs.zip" destinationDirectory = file("build/dist") }
~/hello-gradle $ gradle createZip BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
~/hello-gradle $ tree build build βββ dist β βββ dccs.zip # created zip file βββ docs βββ readme.md βββ sd βββ read_style.md
- We can make the
createZip
task to depend oncopyDocs
task by callingdependsOn
method β then if we invokecreateZip
task that will first execute thecopyDocs
task and thencreateZip
task:
task copyDocs(type: Copy) { from "md" // Call the `from` method of Copy type task into "build/docs" // Call the `into` method include "**/*.md" // ANT like include patterns // .. `**` -> scan all sub-dirs includeEmptyDirs = false // because by default empty dirs are copied } task createZip(type: Zip) { from "build/docs" archiveFileName = "dccs.zip" destinationDirectory = file("build/dist") dependsOn copyDocs }
~/hello-gradle $ gradle createZip BUILD SUCCESSFUL in 1s 2 actionable tasks: 2 up-to-date
-
ββββββββββββββββ
βInitializationβ --> Evaluates the settings.gradle file
β Phase β and sets up the build
ββββββββββββββββ
β
β
β½
βΌ
ββββββ-βββββββββ
βConfiguration β --> Parses and evaluates the build script(s)
β Phase β and runs the configuration logic like
ββββββββββββββββ assigning values to properties or
β calling task methods as exposed by the
β APIs
β½
βΌ
ββββββββββββββββ
β Execution β --> Execute the task actions in correct order
β Phase β
ββββββββββββββββ
// configuration code
task helloGradleTask {
// configuration code
doFirst {
// execution code
}
doLast {
// execution code
}
}
- We can add to a task as many actions we want using
doFirst
ordoLast
task multiActionTask {
// Initial declaration of task containing the first & last actions
doFirst {
println "[2] Initially declared FIRST action"
}
doLast {
println "[3] Initially declared LAST action"
}
}
// Additive doFirst closures are inserted at the BEGINNING of the list
// of actions
multiActionTask.doFirst {
println '[1] another doFirst'
}
// Additive doFirst closures are inserted at the BEGINNING of the list
// of actions
multiActionTask.doFirst {
println '[0] another doFirst'
}
// Additive doLast closures are inserted at the END of the actions list
multiActionTask.doLast {
println '[4] another doLast'
}
// Additive doLast closures are inserted at the END of the actions list
multiActionTask.doLast {
println '[5] another doLast'
}
~/hello-gradle $ ./gradlew multiActionTask
> Task :multiActionTask
[0] another doFirst
[1] another doFirst
[2] Initially declared FIRST action
[3] Initially declared LAST action
[4] another doLast
[5] another doLast
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
Goals of Plugins
- Avoid repetitive code
- Make build logic more maintainable
- Poivide reusable functionality across projects
Types of Plugins
-
Script plugins
- Just another build script that can be included in the main
build.gradle
script
/------ includes ----> publishing.gradle / build.gradle \ \------ includes ----> deployment.gradle
- Useful to split the build logic in multiple scripts for better maintainability
- Just another build script that can be included in the main
-
Binary plugins
- Used for more complex logic
- Implemented as class files; bundled as
JAR
files
/------ includes ----> Gradle core plugins / build.gradle \ \------ includes ----> Community pluginss
- Create a tsk of type
Tar
bundling text files - Rename the file extension from
.text
to.txt
- Should not include empty directories
- Compressed by Gzip and produced in builds or distributions
- The task should print message before start and end of archiving
- Create another task that depends on this task
We will consider below directory for this exercise:
$ cd archive-demo
~/archive-demo $ tree
.
βββ customers.csv
βββ src
βββ bin
β βββ 1.0.0.dat
β βββ versions.text
βββ bugs.text
βββ inputs
βββ readme.text
In the final archive we do not expect the empty inputs
directory.
STEPS
- Let us first create the wrapper:
~/archive-demo $ gradle wrapper
BUILD SUCCESSFUL in 10s
1 actionable task: 1 executed
- Create the
build.gradle
file:
~/archive-demo $ vi build.gradle
// We will use (our) script plugin - archiver.gradle
apply from: 'archiver.gradle' // This applies the script plugin
- Create the script plugin file used by the
build.gradle
file:
~/archive-demo $ vi archiver.gradle
// We will use `base` plugin calling the method apply
// .. This plugin provides some naming conventions, for example,
// .. the output archive's directory is build/distributions
apply plugin: 'base' // eqv. apply(plugin: 'base')
task archiveTextFiles(type: Tar) { // Use Tar type task
from 'src' // Copy the input files from 'src' dir
into 'text' // Output of copy
include '**/*.text' // Copy .text files including sub-dirs
includeEmptyDirs = false // Exclude empty dirs
rename '(.+).text', '$1.txt' // rename .text -> .txt
compression = Compression.GZIP // `Compression.GZIP` is an enum
// We do not need below 2 line because base plugin provides
// these confgurations
// archiveFileName = "dccs.zip"
// destinationDirectory = file("build/dist")
// Render output messages
doFirst {
println 'Starting archiving task'
}
doLast {
println 'Archive complete. Good bye!'
}
}
task createTextArchive() {
// This is just an AGGREGATOR task
dependsOn archiveTextFiles
}
- Run the aggregator task
createTextArchive
:
~/archive-demo $ ./gradlew createTextArchive --console=verbose
> Task :archiveTextFiles
Starting archiving task
Archive complete. Good bye!
> Task :createTextArchive
BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
- Verify the output
~/archive-demo $ tree
.
βββ archiver.gradle
βββ build
β βββ distributions
β βββ archive-demo.tgz # Created output archive
βββ build.gradle
βββ customers.csv
βββ gradle
β βββ wrapper
β βββ gradle-wrapper.jar
β βββ gradle-wrapper.properties
βββ gradlew
βββ gradlew.bat
βββ src
βββ bin
β βββ 1.0.0.dat
β βββ versions.text
βββ bugs.text
βββ inputs
βββ readme.text
7 directories, 12 files
~/archive-demo $ tar -tzvf build/distributions/archive-demo.tgz
drwxr-xr-x 0/0 0 2021-06-20 15:03 text/
drwxr-xr-x 0/0 0 2021-06-20 14:56 text/bin/
-rw-r--r-- 0/0 0 2021-06-20 14:56 text/bin/versions.txt
-rw-r--r-- 0/0 0 2021-06-20 14:06 text/bugs.txt
-rw-r--r-- 0/0 0 2021-06-20 14:05 text/readme.txt
-
Create Java project to emulate a very basic Sensor Event
- File -> New -> Project
- Select Java
- Give a name GradleLab GradleLab_NoGradle
- Create a Package
lab.java.gradle.kaushik
under the created project
Project Structure
GradleLab_NoGradle βββ .idea βββ src βββ lab.java.gradle.kaushik βββ SensorEvent.java
-
This project is a simple Java project that does not use any external dependency - it just prints the
SensorEvent
object using itstoString
method:... @Override public String toString() { return "SensorEvent{" + "id='" + id + '\'' + ", capability='" + capability + '\'' + ", value='" + value + '\'' + '}'; } public static void main(String[] args) { SensorEvent sensorEvent = new SensorEvent(); sensorEvent.setId("100-101-102-103"); sensorEvent.setCapability("motionSensor"); sensorEvent.setValue("active"); System.out.println(sensorEvent.toString()); }
-
Now let us introduce an external dependency (to Google GSON library) to convert this simple sensor event to a JSON string
- Create a directory
libs
under the root folder - Download Google GSON library from Maven repository on the Internet and place the downloaded jar to
libs
directory(Important) Right click the
libs
folder in IntelliJ and select "Add as Library..."
Project Structure
GradleLab_NoGradle βββ .idea βββ libs β βββ gson-2.8.0.jar βββ src βββ lab.java.gradle.kaushik βββ SensorEvent.java
- Create a directory
-
With this done we can now use the GSON library in our Java project
- We need to import some GSON specific code, ex.:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
...
public static void main(String[] args) {
...
System.out.println(sensorEvent.toString());
Gson json = new GsonBuilder().create();
String jsonEventString = json.toJson(sensorEvent);
System.out.println(jsonEventString);
}
[Ref. https://github.com/kaushikdas/InterestingCodes/tree/master/Java/GradleLab_NoGradle]
- Need to manually find and download the jar
- Manage updates if the version of the GSON library changes
- Makes Gradle "Java aware"
- Defines a set of tasks for Java builds (many kinds of plugins available)
- Applying a plugin
- Write
apply plugin: '<plugin name>'
inbuild.gradle
βapply plugin: 'java'
- Write
- Main tasks added
- Clean
- Compile
- Assemble
- Test
-
Create a directory structure as expected the java plugin
$ mkdir -p GradleLab_GsonGradle/src/main/java $ mkdir -p GradleLab_GsonGradle/src/main/resources
-
In IntelliJ open the directory
GradleLab_GsonGradle
using menu File -> Open -
Important Right click the
java
folder ->Mark Directory as
->Sources Root
-
Right click the
java
directory -> New -> Java Class ->lab.java.gradle.kaushik.SeonsorEvent
and add code toSensorEvent.java
-
Add
build.gradle
file at the root of project and add java plugin to that as below:plugins { id 'java' }
Directory structure at this point will be like:
$ tree GradleLab_GsonGradle GradleLab_GsonGradle βββ build.gradle βββ src βββ main βββ java β βββ lab β βββ java β βββ gradle β βββ kaushik β βββ SensorEvent.java βββ resources 8 directories, 2 files
-
Generate gradle wrapper:
$ gradle wrapper BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
We will have new files and directories like below:
GradleLab_GsonGradle βββ build.gradle βββ gradle # new directory β βββ wrapper β βββ gradle-wrapper.jar β βββ gradle-wrapper.properties βββ gradlew # new file βββ gradlew.bat # new file βββ src βββ main βββ java β βββ lab β βββ java β βββ gradle β βββ kaushik β βββ SensorEvent.java βββ resources 10 directories, 6 files
We can now see the tasks that the java plugin brings using
gradlew tasks
:$ ./gradlew tasks--all > Task :tasks ------------------------------------------------------------ Tasks runnable from root project ------------------------------------------------------------ Build tasks ----------- assemble - Assembles the outputs of this project. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles main classes. clean - Deletes the build directory. jar - Assembles a jar archive containing the main classes. testClasses - Assembles test classes. Build Setup tasks ----------------- init - Initializes a new Gradle build. wrapper - Generates Gradle wrapper files. Documentation tasks ------------------- javadoc - Generates Javadoc API documentation for the main source code. Help tasks ---------- buildEnvironment - Displays all buildscript dependencies declared in root project 'GradleLab_GsonGradle'. components - Displays the components produced by root project 'GradleLab_GsonGradle'. [incubating] dependencies - Displays all dependencies declared in root project 'GradleLab_GsonGradle'. dependencyInsight - Displays the insight into a specific dependency in root project 'GradleLab_GsonGradle'. dependentComponents - Displays the dependent components of components in root project 'GradleLab_GsonGradle'. [incubating] help - Displays a help message. model - Displays the configuration model of root project 'GradleLab_GsonGradle'. [incubating] outgoingVariants - Displays the outgoing variants of root project 'GradleLab_GsonGradle'. projects - Displays the sub-projects of root project 'GradleLab_GsonGradle'. properties - Displays the properties of root project 'GradleLab_GsonGradle'. tasks - Displays the tasks runnable from root project 'GradleLab_GsonGradle'. Verification tasks ------------------ check - Runs all checks. test - Runs the unit tests. Other tasks ----------- compileJava - Compiles main Java source. compileTestJava - Compiles test Java source. prepareKotlinBuildScriptModel processResources - Processes main resources. processTestResources - Processes test resources. Rules ----- Pattern: clean<TaskName>: Cleans the output files of a task. Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration. Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration. BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
-
Compiling Java source code. There are 2 tasks,
compileJava
andprocessResources
that we can use.-
compileJava
: This task will compile any.java
file insidesrc/main/java
directory and compiled class files will be generated underbuild/classes
directory.$ ./gradlew compileJava --console=verbose > Task :compileJava BUILD SUCCESSFUL in 3s 1 actionable task: 1 executed
-
processResources
: This task copies the resource files tobuild
directory. For our thos basic project we do not have any resources so far:$ ./gradlew processResources --console=verbose > Task :processResources NO-SOURCE BUILD SUCCESSFUL in 1s
-
Instead of using
compileJava
andprocessResources
individually everytime, we can use the aggregator taskclasses
that perform both the tasks:$ ./gradlew classes --console=verbose > Task :compileJava > Task :processResources NO-SOURCE > Task :classes BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
-
We can set more compiler options inside
build.gradle
. For example we can set source nad target compatibility of our project using thejava
extension created by the java plugin. We can also set the compiler option to stop compilation in case of any warningjava { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } compileJava { options.compilerArgs << '-Werror' }
-
-
Packaging a JAR file using the
jar
task ofjava
plugin. Thejar
task as such does not need any special properties to be set. But we can configire some useful properties of thejar
task, (say) to give a meaningful name to the jar, to make the jar executable.
-
java
plugin'sjar
task generates the jar underbuild/libs
plugins { id 'java' } version = '1.0-SNAPSHOT' java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } compileJava { options.compilerArgs << '-Werror' } jar { // Give a meaningful name to our jar, else it will be the root // project name, by default. In case the version property is // defined, the jar will also have the version included in the // jar name archiveBaseName = 'SensorEvent' // Make the jar executable manifest { attributes "Main-Class": 'lab.java.gradle.kaushik.SensorEvent' } }
Build the
jar
$ ./gradlew clean --console=verbose > Task :clean BUILD SUCCESSFUL in 2s 1 actionable task: 1 executed $ ./gradlew jar --console=verbose > Task :compileJava > Task :processResources NO-SOURCE > Task :classes > Task :jar BUILD SUCCESSFUL in 2s 2 actionable tasks: 2 executed
Run the
jar
$ java -jar .\build\libs\SensorEvent-1.0-SNAPSHOT.jar SensorEvent{id='100-101-102-103', capability='motionSensor', value='active'}
The Java plugin can produce both library and executable application type of artifacts
- The Java Application plugin
-
It helps to run the application directly from the build (using the
run
task that it provides) or to produce an executable distribution. The important tasks that ot provides are:- the
run
task that executes the main class of the program - the
installDist
task that generates operating system specific scripts suitable for starting the program - the
distZip
ordistTar
task that bundle the distribution as zip or tar file
- the
-
Let us apply the application plugin
STEPS
- Change the
build.gradle
file to apply the application plugin
plugins { id 'java' id 'application' // apply application plugin // IMP: It needs mainClass property to be set } version = '1.0-SNAPSHOT' java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } compileJava { options.compilerArgs << '-Werror' } application { // Set mandatory mainClass property for application plugin mainClassName = 'lab.java.gradle.kaushik.SensorEvent' } jar { // Give a meaningful name to our jar, else it will be the root // project name, by default. In case the version property is // defined, the jar will also have the version included in the // jar name archiveBaseName = 'SensorEvent' // Make the jar executable manifest { attributes "Main-Class": 'lab.java.gradle.kaushik.SensorEvent' } }
- Run the
run
task
$ ./gradlew run --console=verbose > Task :compileJava > Task :processResources NO-SOURCE > Task :classes > Task :run SensorEvent{id='100-101-102-103', capability='motionSensor', value='active'} BUILD SUCCESSFUL in 2s 2 actionable tasks: 2 executed
- Run the
installDist
task
$ ./gradlew installDist --console=verbose > Task :compileJava > Task :processResources NO-SOURCE > Task :classes > Task :jar > Task :startScripts > Task :installDist BUILD SUCCESSFUL in 2s 4 actionable tasks: 4 executed
- This will generate OS specific executables to run the project under
build/install/bin
Run the executable$ tree build/install build/install βββ GradleLab_GsonGradle βββ bin β βββ GradleLab_GsonGradle # x-Nix specific executable β βββ GradleLab_GsonGradle.bat # For Windows βββ lib βββ SensorEvent-1.0-SNAPSHOT.jar
$ ./build/install/SensorEvent/bin/GradleLab_GsonGradle SensorEvent{id='100-101-102-103', capability='motionSensor', value='active'}
-
Note
SensorEvent-1.0-SNAPSHOT.jar
is also generated because this task also runs thejar
task as indiciated in the output -
To change the output file and directory names (by default as per the root directory ,i.e.,
GradleLab_GsonGradle
in this case) we need to update therootProject.name
inseetings.gradle
file# Create settings.gradle at the root level $ cat ./settings.gradle rootProject.name = 'SensorEvent'
Now the name will reflect the root project name from
settings.gradle
file:$ tree build/install build/install βββ SensorEvent # from rootProject.name βββ bin β βββ SensorEvent # from rootProject.name β βββ SensorEvent.bat # from rootProject.name βββ lib βββ SensorEvent-1.0-SNAPSHOT.jar
-
- Run
distTar
anddistZip
tasks which will create the distributable packages underbuild/distributions
$ ./gradlew distTar distZip --console=verbose > Task :compileJava UP-TO-DATE > Task :processResources NO-SOURCE > Task :classes UP-TO-DATE > Task :jar UP-TO-DATE > Task :startScripts UP-TO-DATE > Task :distTar > Task :distZip BUILD SUCCESSFUL in 1s 5 actionable tasks: 2 executed, 3 up-to-date
$ tree build/distributions/ build/distributions/ βββ SensorEvent-1.0-SNAPSHOT.tar # Use proerties for jar task βββ SensorEvent-1.0-SNAPSHOT.zip # Use proerties for jar task
- Change the
This approach does NOT follow directory structure convention expected by the java plugin
-
Create the Java as described Section 2
-
Create a new file
build.gradle
under the root directoryProject Structure
GradleLab_GsonGradle βββ .gradle βββ .idea βββ src βββ lab.java.gradle.kaushik βββ SensorEvent.java
-
Apply Java plugin:
apply plugin: 'java'
This steps enables the simple project with gradle. To verify, we can go to Terminal in IntelliJ (or open command prompt and go to this project root path) and run below command and see that it shows various steps under the build task (notice
Task :compileJava NO-SOURCE
indicating there are no sources added to compile yet:β― gradle build --console plain > Task :compileJava NO-SOURCE > Task :processResources NO-SOURCE > Task :classes UP-TO-DATE > Task :jar UP-TO-DATE > Task :assemble UP-TO-DATE > Task :compileTestJava NO-SOURCE > Task :processTestResources NO-SOURCE > Task :testClasses UP-TO-DATE > Task :test NO-SOURCE > Task :check UP-TO-DATE > Task :build UP-TO-DATE BUILD SUCCESSFUL in 1s 1 actionable task: 1 up-to-date β―
In older version of gradle we could just run
gradle build
to get the output like above. But with new gradle version (my version is 6.4.1) it gives very short output:β― gradle build BUILD SUCCESSFUL in 1s 1 actionable task: 1 up-to-date <-------------> 0% WAITING β―
-
Add source code declaration to
build.gradle
sourceSets { // Add source set main { // For main Java code (and not Test code, we don't have any yet) java.srcDirs = ['src'] // Add directory containing source files... // ... add multiple dirs (if any) separated by commas } }
-
Add external library dependency
dependencies { compile 'com.google.code.gson:gson:2.8.6' // compile config // '<co_name.artifact_name.artifact_version> }
This
compile
configuration for dependencies is deprecated now (with my gradle version 5.4.1) and insteadimplementation
is the recommended configuration. Thecompile
configuration adds the dependecies as shown below with the commandgradle dependencies
.β― gradle dependencies > Task :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ annotationProcessor - Annotation processors and their dependencies for source set 'main'. No dependencies apiElements - API elements for main. (n) No dependencies archives - Configuration for archive artifacts. (n) No dependencies compileClasspath - Compile classpath for source set 'main'. \--- com.google.code.gson:gson:2.8.6 compileOnly - Compile only dependencies for source set 'main'. (n) No dependencies default - Configuration for default artifacts. (n) No dependencies implementation - Implementation only dependencies for source set 'main'. (n) No dependencies runtimeClasspath - Runtime classpath of source set 'main'. \--- com.google.code.gson:gson:2.8.6 runtimeElements - Elements of runtime for main. (n) No dependencies runtimeOnly - Runtime only dependencies for source set 'main'. (n) No dependencies testAnnotationProcessor - Annotation processors and their dependencies for source set 'test'. No dependencies testCompileClasspath - Compile classpath for source set 'test'. \--- com.google.code.gson:gson:2.8.6 testCompileOnly - Compile only dependencies for source set 'test'. (n) No dependencies testImplementation - Implementation only dependencies for source set 'test'. (n) No dependencies testRuntimeClasspath - Runtime classpath of source set 'test'. \--- com.google.code.gson:gson:2.8.6 testRuntimeOnly - Runtime only dependencies for source set 'test'. (n) No dependencies A web-based, searchable dependency report is available by adding the --scan option. Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/6.4.1/userguide/command_line_interface.html#sec:command_line_warnings BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed β―
-
Add repository declaration
repositories { mavenCentral() }
With this we can run the command
gradle build
to build our project (and createbuild\libs\GradleLab_GsonGradle.jar
) but when we try to do "Run SensorEvent.Main()" by right clicking on theSensorEvent.java
file inside IntelliJ it fails to execute. We need to follow below steps to run the project (from terminal usinggradle run
). -
Add
java
plugin in thebuild.gradle
(which applies thejava
anddistribution
plugins implicitly so we may even retain that - no problem)apply plugin: 'java' apply plugin: 'application' // This provides 'run' task
-
Specify the main class name for the jar in the
build.gradle
mainClassName = 'lab.java.gradle.kaushik.SensorEvent'
-
Build and run the project from Terminal:
β― gradle clean Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/6.4.1/userguide/command_line_interface.html#sec:command_line_warnings BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed β― gradle build ... BUILD SUCCESSFUL in 1s 5 actionable tasks: 5 executed β― gradle run > Task :run SensorEvent{id='100-101-102-103', capability='motionSensor', value='active'} {"id":"100-101-102-103","capability":"motionSensor","value":"active"} ... BUILD SUCCESSFUL in 1s 2 actionable tasks: 1 executed, 1 up-to-date β―
- File -> New -> Project
- Select Gradle and Java (under "Additional Libraries and Frameworks:")
- (Optional Select Project SDK (1.8 - java version)
- Next
- Give Name and Location
- (Optional) Change Artifact Coordinates
- Click Finish
Sample build.gradle
for a Gradle project created using above steps:
plugins {
id 'java'
}
group 'kaushikd.gradleexps'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
- Add imports:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
- Add Gradle dependency (without this the project will show error):
dependencies {
...
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
...
}
and do "Load Gradle Changes" to apply the dependency change (this will add Gradle:org.slf4j-api:1.7.30
to External Libraries).
3. This dependency will just allow to use slf4j
...
Logger logger = LoggerFactory.getLogger(GaussianRandom.class);
...
logger.info("GaussianRandom");
but no logs will come because and we will get below warnings:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J's plug-in mechanism looks for the StaticLoggerBinder
class and ends up with a no operation implementation that discards all log messages. Just to avoid the above warnings we can add below dependency to build.gradle
to bind slf4j to slf4j-nop [http://www.slf4j.org/manual.html] (note that we will still not get any logs)
compile group: 'org.slf4j', name: 'slf4j-nop', version: '1.7.30'
- To get logs using
logback-classic
as underlying logger we can use below dependency tobuild.gradle
:
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
Using log4j2
- (Remove any previously added dependency for
logback-classic
orslf4j-nop
and) Add below dependency tobuild.gradle
to bind slf4j to log4j2:
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.13.3'
- Add log4j2 configuration properties file under
src/main/resources
. A samplelog4j2.properties
file is shown as below:
status = error
name = PropertiesConfig
filters = threshold
filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appenders = console
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
rootLogger.level = debug
rootLogger.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = STDOUT
With these changes we should see the logs to appear.
Add below code to build.gradle
to modify the jar
task to create an executable JAR to add Main-Class
attribute to the manifest file:
jar {
manifest {
attributes "Main-Class": "kaushikd.java.exps.GaussianRandom"
}
}
But this will generate an executable JAR without an dependencies. Therefore, if we run the executable jar we shall get error. For example, if we are using slf4j we shall get below error:
β― java -jar D:\GitHub\InterestingCodes\Java\Gradle\build\libs\GradleExps-1.0-SNAPSHOT.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
at kaushikd.java.exps.GaussianRandom.main(GaussianRandom.java:8)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
To solve this problem we need to create a Uber JAR that will also include its dependencies.
Modify jar
task in build.gradle
to include below code that will ensure that dependencies are also included in the created JAR:
jar {
manifest {
attributes "Main-Class": "kaushikd.java.exps.GaussianRandom"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Now if we see the JAR content, we shall see that the JAR now includes all required dependencies:
β― jar tf D:\GitHub\InterestingCodes\Java\Gradle\build\libs\GradleExps-1.0-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
kaushikd/
kaushikd/java/
kaushikd/java/exps/
kaushikd/java/exps/GaussianRandom.class
log4j2.properties
META-INF/DEPENDENCIES
META-INF/LICENSE
META-INF/NOTICE
META-INF/maven/
META-INF/maven/org.apache.logging.log4j/
META-INF/maven/org.apache.logging.log4j/log4j-slf4j-impl/
META-INF/maven/org.apache.logging.log4j/log4j-slf4j-impl/pom.properties
META-INF/maven/org.apache.logging.log4j/log4j-slf4j-impl/pom.xml
org/
org/apache/
org/apache/logging/
org/apache/logging/slf4j/
...
and we should be able to run the JAR.
- Groovy is also a JVM language that gets compiled to byte code
- Go to Tools -> Groovy Console
// We can also write...
// ... System.out.println("Hello world");
// ... but that is not required
// ... 1. System.out is automatically available
// ... 2. No need of ; at the end no
// ... 3. Even () is not needed for a method call with 1 arg
println "Hello world!"
- Using classes
Code
class DemoClass {
void doSomework(Closure closure) {
/*
closure can be considered as piece of code being passed
(similar to Java lambda) we we can invoke that code as below
*/
closure.call()
}
}
obj = new DemoClass() // again no need of any ending ;
/* Now we invoke doSomework using obj.doSomework method by passing
some piece of code inside {} */
obj.doSomework {
println new Date() // no need to import anything to use Date
}
Output
Fri Jul 24 20:52:05 IST 202
- Project Object Model
- A Java object built by Gradle
- Represents different things in our project
- Used by Gradle to build projects
- Not a static model: Accessible to developers for modfication
- Project object
- 1-to-1 mapped to a
build.gradle
-
preject
: corresponding object reference
- 1-to-1 mapped to a
- Task object
- Project is a collection of tasks (compile, create jar file etc.)
- Task is a collection of (smaller) actions performed by gradle
- Usually 1-to-1 mapping between task and action
- We can list the tasks for a project
- Project Object Model
- A Java object built by Gradle
- Represents different things in our project
- Used by Gradle to build projects
- Not a static model: Accessible to developers for modfication
- Project object
- 1-to-1 mapped to a
build.gradle
-
preject
: corresponding object reference
- 1-to-1 mapped to a
- Task object
- Project is a collection of tasks (compile, create jar file etc.)
- Task is a collection of (smaller) actions performed by gradle
- Usually 1-to-1 mapping between task and action
- We can list the tasks for a project
project.depedencies { // depedencies β‘ project.depedencies
}
project.buildDir = "..." // this will change the build directory...
// ...from the default value of build folder
// ...under the project root
List of attaributes under project
object can be found by typing the command gradle properties
in the terminal. More details can be found at Gradle official documentation website.
-
gradle tasks
- Shows all the tasks available
- Tasks are grouped logically into different categories
-
gradle dependencies
: Shows all dependencies of a project-
gradle dependencies --configuration compile
: Shows dependencies for a requested configuration (compile
in this case) - Shows trasitive dependencies
\--- org.apache.logging.log4j:log4j-slf4j-impl:2.13.3 +--- org.slf4j:slf4j-api:1.7.25 +--- org.apache.logging.log4j:log4j-api:2.13.3 \--- org.apache.logging.log4j:log4j-core:2.13.3 \--- org.apache.logging.log4j:log4j-api:2.13.3
- Gradle automatically resolves conflicting depedencies w.r.t. versions of libraries requied by different dependencies (say, by upgrading dependency of one to a higher version as included by another depedency)
-
- HTML report for dependency
- Requires another plugin
project-report
inbuild.gradle
apply plugin: 'project-report'
- Run gradle command
gradle htmlDependencyReport
- This is generate an HTML report under a specific path as indicated in its execution output
- Requires another plugin
- WIP