Using Maven and Jenkins to perform modular Java builds

This is a third post in a series, describing the problems my team has faced during implementation of Jenkins pipelines in Kubernetes.

  1. Jenkins Java Centric Pipelines in Kubernetes
  2. Building Maven Projects in Jenkins Docker workers
  3. Using Maven and Jenkins to perform modular Java builds (This article)
  4. Building Docker Images in Jenkins on Kubernetes

Modular Maven builds in stateless Jenkins workers

Maven is a pretty smart piece of software. Among its many capabilities it knows to build only the modified code since the last build (unless you run the “clean” directive.
When building in a stateless Jenkins workers for the reasons stated in the previous post, we lose these abilities, since every builds starts with an empty workspace.

To overcome these issues and save time on cumulative builds, we need to reliably detect changes and cache previous builds artifacts.

Detecting changes

To detect changes, we use Jenkins and Git. We can kindly ask Jenkins to provide us with the last successful build commit hash.

def lastSuccessfulBuild() {
  def build = currentBuild.previousBuild
  while (build != null) {
    if(build.result == 'SUCCESS') {
      return build.changeSets.get(0).getCommitId()
    }
  }
  return ""
}

Using the two hashes (the last successful build and the current build), we use this command to get the list of changed files:
git diff --name-only <last_successful_build> HEAD

Detecting Maven modules

We will find all of the Maven modules by searching the workspace for the pom.xml files.
findFiles(glob: '**/pom.xml')

Mapping changes to modules

Once we have a list of changed files, we map them to the closest pom.xml file, this way we detect modules that we need to rebuild.
For example, file webapps/appA/src/main/java/org/foo/Bar.java will get mapped to webapps/appA/pom.xml, but not to webapps/pom.xml.

Running the build

Armed with a list of modules to build, we tell Maven to build only these modules.
mvn install -also-make-dependents -P profile_name -pl webapps/appA,webapps/appB