Ora

What is a Composite Build?

Published in Build System Features 4 mins read

A composite build is a powerful build organization strategy where a build includes other independent builds, treating them as source dependencies rather than binary ones. Unlike a traditional multi-project build that includes subprojects, a composite build integrates entire, standalone builds. This allows for seamless local development and testing across projects that would typically be published and consumed separately.

Understanding the Core Concept

At its heart, a composite build is a build that includes other builds. Imagine you have a main application and several libraries or plugins that your application depends on. Normally, you'd build these libraries, publish them to a repository, and then your main application would download and use the pre-compiled binary versions.

A composite build changes this dynamic. Instead of consuming the pre-compiled binaries, you tell your main application's build system to include the source code of those dependent libraries' entire builds directly. This means any changes you make to the included library's source code are immediately reflected and compiled into your main application when you build it, without needing to publish new versions.

Why Use Composite Builds?

Composite builds offer significant advantages, particularly for developers working on interdependent components:

  • Streamlined Local Development: Easily develop and test changes across multiple related projects. For example, if you're developing a new feature in a library and need to test its integration with an application that uses it, you can make changes in both projects locally and build them together.
  • Faster Iteration Cycles: Avoid the overhead of publishing intermediate versions of dependencies to a repository just to test changes.
  • Enhanced Debugging: Debug issues that span across multiple projects more effectively, as all components are available as source code within a single development environment.
  • Improved Collaboration: Teams can work on different parts of a larger system, each within their own independent build, while still having a mechanism to integrate and test locally.

How Composite Builds Work

When a build system (like Gradle) encounters a declaration to include another build, it essentially overlays the included build's components into the consuming build's dependency resolution process. If the consuming build declares a dependency on a component that can be provided by an included build, the build system uses the source code and build logic from the included build instead of fetching a published binary from a repository.

This mechanism ensures that:

  • The included build's output (e.g., compiled classes, JARs) is generated directly from its source code within the composite context.
  • Any changes to the included build's source files trigger recompilation as needed, just as if it were a subproject.

Key Characteristics and Benefits

Feature Multi-Project Build Composite Build
Inclusion Type Subprojects (modules within a single build definition) Entire, independent builds
Dependency Source Internal project outputs (source-based) Overrides external binary dependencies with source-based
Development Flow All projects typically developed together Independent projects developed separately, integrated locally
Build Structure Single root build file (settings.gradle or similar) Each included build has its own settings.gradle and build logic
Use Case Focus Monorepos, tightly coupled components Loosely coupled components, library/plugin development, inter-team collaboration

Practical Scenarios and Examples

Consider these common situations where composite builds are highly beneficial:

  • Developing a Shared Library: You maintain a utility library used by several applications. When adding a new feature to the library, you can include its build into one of your applications to test the feature end-to-end before publishing the new library version.
  • Working on a Plugin: If you're developing a plugin for a larger platform, you can include your plugin's build into the platform's build for development, allowing you to quickly test changes without constant redeployments.
  • Refactoring a Monolith: As you decompose a large monolithic application into microservices or independent modules, composite builds can help manage the transition by allowing services to temporarily consume each other's source code during refactoring phases.

Example (Conceptual):

Imagine you have two separate Git repositories:

  1. my-app (A web application)
  2. my-library (A shared utility library)

my-app depends on my-library:1.0. With a composite build, instead of fetching my-library:1.0 from Maven Central, you can configure my-app's build to directly use the my-library source code on your local machine.

  • Setup:
    1. Clone both my-app and my-library repositories to your local machine.
    2. In my-app's build configuration, specify that my-library should be included as a composite build.
  • Development:
    • Make changes in my-library's source code.
    • Run my-app's build. The build system will automatically compile my-library from its source and use the updated version within my-app.

This seamless integration drastically simplifies the development workflow for interdependent projects.