Gradle offers a neat feature called “Composite Builds” which allows one project to consume artifacts from other builds as if they were part of a single coherent unit. This feature is pretty easy to setup, however you might not know that it’s possible to do the same with Maven. Here’s how.
Let’s say you have a producer and consumer projects, where producer is set as a dependency on consumer. If these two projects are to have their own release lifecycles their file structure may look like this
The contents of the Gradle build files look like so
And in the case of Maven they look like this
For now, invoking a build for consumer requires pushing a matching version (1.0.0 in this case) of producer to a repository. Let’s enable the composite build feature for both Maven and Gradle. In the case of Gradle we need a new file named settings.gradle in the consumer project; for Maven we need an extra pom.xml file for convenience placed one level up from producer and consumer, like so
There’s only one single change that we have to make in the Gradle build, that is, tell consumer that it should include producer‘s build; this is done by editing settings.gradle like this
Notice the relative path, this is quite important as composite builds rely on source paths. Now, when we run a build on consumer notice that build steps on producer are invoked first as if both projects belonged to multi-project setup
Great! This link between builds can be severed at any time by simply editing settings.gradle once more and removing the includeBuild directive. Now for Maven the additional pom.xml groups both consumer and producer into a single reactor project while still letting those projects keep their individuality, just like we did with Gradle. Here’s how the updated Maven build files look like
Take note that there are no modifications made on consumer and producer, only the new pom.xml file has to be added to the build. With this setup in place we can now invoke targets on consumer from the reactor’s root, like so
Et voilà! Composite builds are a nice feature that enables some use cases like:
- Fixing a bug in producer that’s affecting consumer. An iterative approach may be needed to make the final fix. In a traditional approach you may need to make multiple intermediate releases for producer until the fix can be verified in consumer. Composite builds shortens the time it takes to iterate and also reduces the risk of outdated snapshot dependencies in a repository.
- You may want to give monorepo a try but don’t want to change commit history for now. You can “fake” a monorepo by composing all required builds.
- You want to split a monorepo into multiple repositories but some projects may need to be co-located as if they were still part of the monorepo.
- Some other use case where having direct access to sources for all interesting dependencies is a must.
There you go, the way I see it composite builds are a great alternative to publishing intermediate snapshot releases for many cases.
Keep on coding!