These are some notes I took from the LinkedIn Learning courses Learning Gradle and Gradle for Java-Based Applications and Libraries by Benjamin Mushko.
Gradle is a "build automation" tool. "Build" can include:
- Compiling the source code
- Running tests
- Building binary artifacts.
- Generating technical documentation.
Gradle can be used to automate the software lifecycle apps in different programming languages like: Java Android, Go, Python, Java, Kotlin, etc.
- Gradle runs on the Java virtual machine.
- The build logic is defined as instruction scripts.
- There are multiple gradle plugins that you can use to provide predefined functionality.
- It is primarily a command line tool but there extensive support for it in IDEs.
- Gradle is also widely supported by CI tools like Jenkins, GitHub Actions, among others.
- Software Component: A piece of software that is internally modularized
and structured using gradle projects.
- For example a micro-services would be a Software Component. The microservice can be internally modularized using multiple gradle projects.
- If you don't want to have internal boundaries within your software component, then the software component = gradle project.
- Gradle Project: the core of every gradle build. Marks the capabilities and boundaries of a software project.
- Build file / script: Each project should have its own build file. This
file is typically name
build.gradle
(Groovy) orbuild.gradle.kts
(Kotlin). - Tasks: The step-by-step automation instructions to build a project. You write tasks in the build script. Tasks ara a unit of work.
- This means that the conceptual model is:
- A software component is called a
Project
. - One
Project
has one to manyTasks
. - One
Task
has zero to manyActions
.
- A software component is called a
- Gradle defines a DSL API that can be mixed with regular programming code.
- The DSL comes in two flavours: Groovy DSL and the Kotlin DSL. Both are equivalent, and you can pick whichever you prefer.
- A project typically uses a fixed gradle version.
- Gradle's API can include breaking changes between major versions.
- When you want to upgrade the gradle version for a project, you will need to update the Gradle runtime and potentially update your scripts to reflect the new API.
- The Gradle Wrapper is the solution for the problem of maintaining on the same local machine multiple gradle projects that use different gradle versions.
- The wrapper is a shell script (also available in bat for Windows) that is checked into the project and will automatically download the correct version of gradle for a project (if not installed) or load the correct version of gradle (if multiple are locally installed) before running any commands given to the wrapper.
- The wrapper looks into the
./gradle/*
directory in your project to look for the version the project should use. This folder must also be checked into version control with the wrapper. - To GENERATE the wrapper scripts (
gradlew
andgradlew.bat
) and the./gradle/*
directory, you can rungradle wrapper
in the root of your project directory.- The machine that does this for the first time in a project needs to
have the correct version of gradle locally available. All other
developers can just use
./gradlew
to get everything setup for them. - IntelliJ takes care of this if you start a new project, or a new
module using the
Gradle
option.
- The machine that does this for the first time in a project needs to
have the correct version of gradle locally available. All other
developers can just use
- If using the terminal to invoke gradle, invoke the tasks through the
./gradlew
instead of calling it directly.- For example
./gradlew build
- For example
- The gradle wrapper mode of operation also works within continuous delivery servers. That is, the wrapper will download the correct version of gradle if not installed.
- IDEA relies on the
gradlew
, and the./gradle/
directories to enable IDE support for gradle. - If IDEA finds those files, it will inspect the build files and load all tasks into the gradle tab on the right side.
- When gradle is properly detected by IDEA, we get autocompletion with
the Gradle DSL when editing the
build.gradle
, or thebuild.gradle.kts
files.
- Gradle automatically derives the project name from the directory.
- If you want to manually set this, you can do
rootProject.name = "1-hello-world"
in thesettings.gradle.kts
file. - See
1-hello-world/settings.gradle.kts
.
- If you want to manually set this, you can do
IMPORTANT: Gradle has a very clear distinction on how to handle a single software component which is structured internally with multiple projects (which conceptual map to IntelliJ IDEA modules), from a software product that is made of orchestrating multiple software components. In this section we are talking about the former. See the documentation for more information.
- Gradle prescribes how to structure the source files of a multi-project software component.
- In a multi-project component, each module must contain its
build.gradle.kts
file at the root of the module. - Additionally, the root of directory that holds must contain a
settings.gradle.kts
in which all participating projects in the component get declared.
- Resides in the root directory of your project hierarchy or in the
Gradle user home directory.
- By user home we mean
~/.gradle/gradle.properties
. These properties will be applied to all gradle builds for the local machine.
- By user home we mean
gradle.properties
defines runtime options for your build (from a set of predefined configurable options).- It also allows you to set custom key value pairs that you can later access to customize your build.
- See the
1-hello-world
module for an example on how to use this.
- Good fit for a one-off simple actions by adding code into
doFirst
ordoLast
actions.
- Better fit for more complex task logic.
- Our custom typed tasks make use of other higher level tasks provided
by Gradle (e.g
Copy, Zip, etc
). - We don't necessarily have to code the action details as the higher
level tasks we use provide APIs that probably do much of what we need.
- In our code we typically set property values or call methods from the higher level task.
tasks.register<Copy>("myCopyTask") {
from(layout.projectDirectory.dir("src"))
into(layout.buildDirectory.dir("docs"))
include("**/*md")
includeEmptyDirs = false
}
A dependency can be defined with the dependsOn
method.
tasks.register<Zip>("zipDocs") {
dependsOn(tasks["myCopyTask"])
from(layout.buildDirectory.dir("docs"))
archiveFileName.set("docs.zip")
destinationDirectory.set(layout.buildDirectory.dir("dist"))
}
- Dependent tasks in gradle form a DAG with tasks being the nodes of the graph.
- The DAG CANNOT have cycles. Gradle will fail the build if a cycle is detected.
- In Gradle, the execution order of parallel tasks is not guaranteed
(i.e. it does not follow the declaration order in the task code).
- For example, if
A --> B and A --> C
, there is no guarantee on which taskB
orC
will run first. - To force a run order we can:
- Declare an extra dependency (e.g.
C --> B
) to ensure thatB
runs beforeC
. - There are other methods for finer grained order control like using
mustRunAfter
(see docs more info).
- Declare an extra dependency (e.g.
- For example, if
- By default, Gradle does not provide a way to visualize the resulting
DAG.
- The next best thing to do without installing anything else is to
print the names of all the tasks that will get run without running
them using the
--dry-run
option- e.g.
./gradlew zipDocs --dry-run
- e.g.
- Alternatively, you can install the Gradle task tree plugin which allows you to render the tasks as a tree.
- The next best thing to do without installing anything else is to
print the names of all the tasks that will get run without running
them using the
Everytime we run a task, Gradle will:
- Build the complete DAG reading the build file
- Create and configure the tasks
- Execute them in the correct order
The above formally happens in 3 phases. Understanding what happens when is very important to write correct gradle tasks and debug.
- Evaluates the
settings.gradle
file and sets up the build (configures the build not the tasks).
- Gradle parses and evaluates the build logic in one or many build
scripts (and builds the task DAG).
- In multi-project software components, each project typically defines its own build script (although this is not mandatory).
- In this phase, tasks are configured, NOT run.
- Configuration of tasks means assigning values to properties or calling API methods from higher order tasks.
- Performance is critical in this phase, since at this point the code cannot be parallelized (TODO: is this true?).
- Execute tasks in correct order.
In a typical custom task, this is where configuration code and execution code goes:
// Configuration Code
tasks.create("myTask") {
// More configuration code
doFirst {
// Execution code always inside the doFirst or doLast action blocks
}
doLast {
// Execution code always inside the doFirst or doLast action blocks
}
}
Plugins provide reusable build functionality across projects. Gradle has two types of plugins.
- Script plugins are essentially auxiliary build scripts that can be
included into the main
build.gradle.(kts)
file. - We write these auxiliary scripts using the same syntax as the main one, just in a different file.
- The main use case for Script Plugins is to use them to split up the build logic of a SINGLE software project into multiple files to make it more maintainable.
- See
2-tasks-sample/build.gradle.kts
for an example of how a build script can reference other script plugins.
- Binary plugins are meant for more complex cases than script plugins.
- Their main use case is to make plugins reusable across MULTIPLE self-contained software projects.
- Binary plugins are Implemented as classes that get bundled as JAR files.
- Some binary plugins are available through gradle itself. These are
called core plugins (e.g. the
base
plugin) - There are also some community plugins available through the plugin portal.
- You can also build and distribute your own binary plugins (out of scope of this summary).
- See
2-tasks-sample/archiving.gradle.kts
for an example on how to include a binary plugin.
- Domain Objects are programmatic representations of Gradle concepts like Projects, Tasks, Actions, Plugins among others.
- Domain objects in Gradle can be inspected and modified programmatically from the build script.
- This programmatic access to domain objects is what gives Gradle a lot of its power.
- Domain Objects are also known as "Core types" in the Gradle docs.
- We will explore some of the most important domain objects here.
- Represents an invocation of the build.
- Has knowledge of the project hierarchy in single and multi-project builds.
- Provides pointers to higher order configurations of a build (version, home directory, etc).
- Can register callback logic to react to certain events during the build.
- One "Gradle" domain object has multiple "Project" Domain objects.
- Represents a software component and provides an API to access the
object hierarchy.
- e.g. Register new tasks, modify output directory
- A "Project" holds many "Tasks".
- Represents a unit of work with its potential dependencies
- A "Task" holds 0 to many "Actions"
- "Actions" can be "doFirst", "doLast"
- An "Action" represents the actual work performed during the execution phase.
- "Plugin" domain objects are linked to the "Project" domain objects (one project can hold many plugins).
- Explains the build tool and its functionality at a conceptual level.
- It is recommended to read this guide from cover to cover.
- https://docs.gradle.org/current/userguide/userguide.html
- Hands-on, tutorial-style documentation.
- Explains certain aspects of Gradle by example.
- Read the ones that are relevant for your day-to-day work.
- https://gradle.org/guides/
- The Gradle DSL is how we actually use the conceptual elements of the tool in the build scripts.
- The DSL docs document all the domain objects plus their properties and methods.
- It is a lower level type of documentation than the User Guide or the Gradle Guides.
- Use the DSL docs when looking for specific properties of domain objects.
- https://docs.gradle.org/current/dsl/index.html
- Gradle is built using Java.
- You can read even lower level Gradle documentation by reading the source code's Javadoc.
./gradlew tasks --all
lists all tasks available for the gradle project.