Skip to content

Maintain your build

B. K. Oxley (binkley) edited this page Apr 4, 2024 · 22 revisions

Maintain build

Maintain your build

Treat your build as you would your codebase: Maintain it, refactor as needed, run performance testing, et al.

Know what your build does

What does your build do exactly, and in what order? You can ask Gradle or Maven to find out:

Each of these have many options and features, and are worth exploring.

Keep your build clean

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).

Gradle

The Kordamp plugin used for Gradle does not fail the build when jdeps errors, and only generates a report text file. See this issue.

Maven

Try Maven with dependency:tree -Dverbose. This will show conflicting versions of dependencies.

Keep local builds quiet

It is frustrating for local devs when something horrible happened during the build (say a production with "ERROR" output during a test), but:

  1. The build is GREEN, and developers should trust that
  2. 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.

Keep CI builds noisy

In CI, this is different, and there you want as much output as possible to diagnose the unexpected.

Keep your build current

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").

Keep plugins and dependencies up-to-date

  • 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.

Tips

  • 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

Automated dependency upgrade PRs

NBDependabot 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.

More on Gradle version numbers

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"

Note on toolVersion property

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 all toolVersion properties, and update gradle.properties as accordingly

NB — Maven handles this differently, and does not have this concern.

Keep your build fast

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:

See an example build scan from May 1, 2023.

NBBuild Scan supports Maven as well when using the paid enterprise version.

Keep your developers fast

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

Tips

Clone this wiki locally