-
Notifications
You must be signed in to change notification settings - Fork 71
Maintain your build

Treat your build as you would your codebase: Maintain it, refactor as needed, run performance checks, et al.
The key mindset for maintaining your build is measuring and metrics. This means you take advantage of tools that:
- Show you what your build does at every step
- Show you how long steps take
- Give you data relevant to each step
- Automate most busy work, and only want attention when needed
What does your build do exactly, and in what order? You can ask Gradle or Maven to find out:
-
Gradle Task Tree plugin
with
./gradlew some...tasks taskTree
-
Maven Buildplan plugin
with
./mvnw buildplan:list
(see plugin documentation for other goals and output format)
Each of these have many options and features, and are worth exploring.
Let tools tell you when you have dodgy dependencies, or an inconsistent setup.
For example, leverage jdeps
which
comes with the JDK.
Jdeps spots, for example, if you have a multi-version jar as a dependency that
does not include your JDK version (an example of this may be is JUnit), or if
your code depends on internal (non-public) classes of the JDK
(important especially when using the JDK module system).
The Kordamp plugin used for Gradle does not fail the build when jdeps errors, and only generates a report text file. See this issue.
Try Maven with dependency:tree -Dverbose
.
This will show conflicting versions of dependencies.
It is frustrating for local devs when something horrible happened during the build (say a production with "ERROR" output during a test), but:
- The build is GREEN, and developers should trust that.
- There is too much output in the local build, so developers don't spot telltale signs of trouble.
There are many approaches to this problem. This project uses JDK logging as an
example,
and keeps the build quiet in
config/logging.properties
.
In CI, this is different, and there you want as much output as possible to diagnose the unexpected.
An important part of build hygiene is keeping your build system, plugins, and dependencies up to date. This might be simply to address bug fixes (including bugs you weren't aware of), or might be critical security fixes. The best policy is: Stay current. Others will have found—reported problems—, and 3rd-parties may have addressed them. Leverage the power of Linus' Law ("given enough eyeballs, all bugs are shallow").
- Gradle — Benjamin Manes is kind enough in his plugin project to list alternatives. If you are moving towards Gradle version catalogs, you might consider refreshVersions.
- Maven
- Team agreement on release updates only, or if non-release plugins and dependencies make sense for your situation.
- Each of these plugins for Gradle or Maven have their quirks. Do not treat them as sources of truth but as recommendations. Use your judgment. In parallel, take advantage of CI tooling such as Dependabot (Github) or Dependabot (GitLab).
An example use which shows most outdated plugins and dependencies (note that one
Maven example modifies your pom.xml
, a fact you can choose or avoid):
$ ./gradlew dependencyUpdates
# output ommitted
$ ./mvnw versions:update-properties # Updates pom.xml in place
$ ./mvnw versions:display-property-updates # Just lists proposed updates
# output ommitted
This project keeps Gradle version numbers in
gradle.properties
,
and for Maven in
the
POM,
and you should do the same.
Since your pom.xml
is in Git, versions:update-properties
is safe as you
can always revert changes, but some folks want to look before doing.
NB —
Dependabot
may prove speedier for you than updating dependency versions locally, and runs
in CI (GitHub) on a schedule you pick. It submits PRs to your repository when it
finds out of date dependencies. See
dependabot.yml
for an example using a daily schedule.
A similar choice is Renovate.
Your simplest approach to Gradle is to keep everything in build.gradle
. Even
this unfortunately still requires a settings.gradle
to define a project
artifact name, and leaves duplicate version numbers for related dependencies
scattered through build.gradle
.
Another approach is to rely on a Gradle plugin such as that from Spring Boot to manage dependencies for you. This unfortunately does not help with plugins at all, nor with dependencies that Spring Boot does not know about.
This project uses a 3-file solution for Gradle versioning, and you should consider doing the same:
-
gradle.properties
is the sole source of truth for version numbers, both plugins and dependencies. -
settings.gradle
configures plugin versions using the properties. -
build.gradle
uses plugins without needing version numbers, and dependencies refer to their property versions.
The benefits of this approach grow for Gradle multi-project projects, where you
may have plugin and dependency versions scattered across each build.gradle
file for you project and subprojects.
So to adjust a version, edit gradle.properties
. To see this approach in action
for dependencies, try:
$ grep junitVersion gradle.properties setttings.gradle build.gradle
gradle.properties:junitVersion=5.7.0
build.gradle: testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
build.gradle: testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
If you use the toolVersion
property for a plugin to update the called tool
separately from the plugin itself, this is a convention, not something the
Gradle API provides to plugins. As a consequence, the Versions plugin is unable
to know if your tool version is out of date. An example is the JaCoCo plugin
distributed with Gradle.
Two options:
- Do not use the
toolVersion
property unless needed to address a discovered build issue, and remove it once the plugin catches up to provide the tool version you need - Continue using the
toolVersion
property, and as part of running./gradlew dependencyUpdates
, manually check alltoolVersion
properties, and updategradle.properties
as accordingly
NB — Maven handles this differently, and does not have this concern.
A fast local build is one of the best things you can do for your team. There are variants of profiling your build for Gradle and Maven:
-
Gradle build scan with the
--scan
flag -
Maven profiler extension with
the
-Dprofile
flag
See an example build scan from May 1, 2023.
NB — Build Scan supports Maven as well when using the paid enterprise version.
Some shortcuts to speed up the red-green-refactor cycle:
- Just validate code coverage; do not run other parts of the build:
- Gradle —
./gradlew clean jacocoTestReport jacocoTestCoverageVerification
- Maven —
./mvnw clean test jacoco:report jacoco:check
.
- Gradle —
- Gradle and Maven provide default versions of bundled plugins. In both built tools, the version update plugins need you to be explicit in stating versions for bundled plugins, so those versions are visible for update.
- Enable HTML reports for local use; enable XML reports for CI use in integrating with report tooling.
- To open the report for Jdeps, build locally and use the
<project root>/build/reports/jdeps/
(Gradle) path. The path shown in a Docker build is relative to the interior of the container. - Both dependency vulnerability
checks and mutation
testing can take a while, depending on your project.
If you find they slow your team local build too much, these are good
candidates for moving to
CI-only steps, such as a
-PCI
flag for Maven (see "Tips" section of Use Gradle or Maven for Gradle for an equivalent). This project keeps them as part of the local build, as the demonstration code is short. - See the bottom of
build.gradle
for an example of customizing "new" versions reported by the GradledependencyUpdates
task. - The equivalent Maven approach for controlling the definition of "new" is to use Version number rules.
- With the Gradle plugin, you can program your build to fail if dependencies are outdated. Read at Configuration option to fail build if stuff is out of date for details.
See the code repo for working examples.
This work is dedicated/deeded to the public following laws in jurisdictions
for such purpose.
The principal authors are:
You can always use the "Pages" UI control above this sidebar ☝ to navigate around all pages alphabetically (even pages not in this sidebar), to navigate the outline for each page, or for a search box.
Here is the suggested reading order: