If you work with source code of multiple dependencies at the same time then Composite Builds is the feature you didn't know you needed. Back in October 2016 Gradle 3.1 introduced Composite Builds to the world. The idea behind this feature is to let a project build its external dependencies as needed; think of quick fixing a bug or checking for breakages before publishing a snapshot release.
I've got a real world scenario for this feature: the set of builds for JSR377, the Desktop Application JSR. This specification provides the following artefacts
- jsr377-api: Defines the API for this JSR.
- jsr377-spec: Provides the spec document for this JSR.
- jsr377-tck: Delivers the TCK that every implementation must pass.
- jsr377-converters: Additional type converters based on the Converter API provided by this JSR.
jsr377-api as a dependency. Each of these projects is set as standalone as they have different lifecycles and publication schedules. This means that for the first two to consume the latter we'll need to publish intermediate releases (or snapshots) to figure out if the changes made to the base API are compatible or not. We know this by heart when using Maven or Gradle, simply call
mvn install or
gradle publishToMavenLocal and move on. But dealing with snapshot releases results in pain no matter what; there's always that outdated release that gets in the way because we forgot to call install or publish at the right time, isn't it? To give you more context just look at what fellow Java Champion Anton Arhipov posted on this regard with Maven and CI environments.
Composite Builds on the other hand let you define a relationship between two or more separate builds, in such a way that the dependent builds will be invoked at the right time before their consumers. Take for example the JSR377 build for the converters. If you check out all projects and decide to build the converter project right away you'll end up with something like this
$ pwd ~/dev/github/jsr377-converters $ ./gradlew jar > Task :processResources > Task :jsr377-parent:jsr377-api:compileJava > Task :jsr377-parent:jsr377-api:processResources NO-SOURCE > Task :jsr377-parent:jsr377-api:classes > Task :jsr377-parent:jsr377-api:generateMinPom > Task :jsr377-parent:jsr377-api:jar > Task :compileJava > Task :classes > Task :jar BUILD SUCCESSFUL in 2s 3 actionable tasks: 3 executed
It turns out Gradle built the
jsr377-api project first then
jsr377-converters. Neat! Say goodbye to snapshot releases. With this feature in place now the JSR377 team can make changes to the API and see the changes cascade down to consumer projects. And just like every other task, those provided by
jsr377-api also participate in input/output caching, there's actually no difference from a developer's POV.
As great as this feature is it had one flaw until recently: you cannot have more than one level, that is you can't nest composite builds. This was a painful realisation as the Griffon build (the Reference Implementation for JSR377) requires all of the JSR377 artefacts to be available. This meant going back to intermediate releases (bad) or snapshot releases (even worse). Luckily Gradle 4.10 (released some weeks ago) added this feature. To give you and idea on the setup, this is how the project structure looks like
. ├── griffon │ ├── build.gradle │ └── settings.gradle ├── jsr377-api │ ├── build.gradle │ ├── jsr377-api │ │ └── jsr377-api.gradle │ ├── jsr377-spec │ │ └── jsr377-spec.gradle │ └── settings.gradle ├── jsr377-converters │ ├── build.gradle │ └── settings.gradle └── jsr377-tck ├── build.gradle └── settings.gradle
Prior to this feature we had to keep locally built binaries under source control to make the Griffon build work both locally and on CI servers, as the JSR has not reached a stage of final publication. Now that we have this feature at our disposal it's just a matter of adding the required changes to
settings.gradle and voilà! Here's the file being edited on IntelliJ IDEA, notice that the project navigator shows all dependencies, just like regular submodules, which means we can edit any of the 4 projects in the same window.
This is such a time saver as we no longer have to build intermediate releases; and if there are changes introduced in the TCK or converter projects we can see the updates reflected on the Griffon source immediately. There's one catch though, sources must be available at the right location for this to work. Notice that the settings file expects all other dependencies to reside on sibling directories to the current build. You'll have to follow the same structure in local and CI settings; in the case of the Griffon build we use Travis for our CI needs, thus we only have to configure the
before_script step with the following
before_script: - ./gradlew --no-daemon --version - cd .. - git clone --depth=50 --branch=master https://github.com/jsr377/jsr377-tck.git jsr377-tck - git clone --depth=50 --branch=master https://github.com/jsr377/jsr377-api.git jsr377-api - git clone --depth=50 --branch=master https://github.com/jsr377/jsr377-converters.git jsr377-converters - cd griffon
This snippet makes sure to check out dependencies at the right location before proceeding.
Keep on Coding!