You can open this sample inside an IDE using the IntelliJ native importer or Eclipse Buildship.

Using composite builds in a hierarchical multirepo project

This sample demonstrates how composite builds can be used to develop a hierarchical project that is composed from different Git repositories. Instead of Gradle subprojects in a multiproject build, this example uses separate Gradle builds included into a composite.

In addition to the benefits of a multirepo architecture, using a Gradle composite build in this way allows a developer to easily choose which modules to develop as source dependencies and which to integrate via a binary repository.

Running multirepo-app with dependencies from included builds

In the first instance, all of the required dependencies are present as builds in the modules directory. In a real-world example, these could well be clones of different Git repositories.

In order to avoid hard-coding the included builds to load, the settings.gradle file in multirepo-app loads each of these builds dynamically:

settings.gradle.kts
file("modules").listFiles().forEach { moduleBuild: File ->
    includeBuild(moduleBuild)
}
settings.gradle
file('modules').listFiles().each { File moduleBuild ->
    includeBuild moduleBuild
}

When the multirepo-app build is executed, these module builds are used to generate the dependent artifacts:

gradle run

And the 'dependencies' report shows the dependency substitution in action:

gradle app:dependencies --configuration runtimeClasspath
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.sample:number-utils:1.0 -> project :number-utils
\--- org.sample:string-utils:1.0 -> project :string-utils
     \--- org.apache.commons:commons-lang3:3.12.0

Switching to use binary dependency

As long as the modules are available in a binary repository, the multirepo-app build will continue to work even if you don’t have some modules available locally. In this case Gradle will use a binary dependency downloaded from a repository instead.

Preparing the binary repository

To demonstrate this functionality, we first need to publish each module to a binary repository. In this case we use a local file repository for this purpose:

gradle :publishDeps

The publishDeps creates and uploads the artifacts for each included build. It is defined in multirepo-app as follows:

build.gradle.kts
tasks.register("publishDeps") {
    dependsOn(gradle.includedBuilds.map { it.task(":publishMavenPublicationToMavenRepository") })
}
build.gradle
tasks.register('publishDeps') {
    dependsOn gradle.includedBuilds*.task(':publishMavenPublicationToMavenRepository')
}

Removing the local module source

With module artifacts available in a repository, we can now remove the module sources from the build. Since the composite is configured to automatically load available modules, this is as easy as deleting one or more module directories.

rm -r modules/string-utils
gradle run

Note that the number-utils dependency is still satisfied by the included build, while the string-utils dependency is now resolved from the repository.

The 'dependencies' report shows the dependency substitution in action:

gradle app:dependencies --configuration runtimeClasspath
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.sample:number-utils:1.0 -> project :number-utils
\--- org.sample:string-utils:1.0
     \--- org.apache.commons:commons-lang3:3.12.0

Including an external library as a submodule

The power of this configuration can be demonstrated by adding the external 'commons-lang' build directly to the composite.

git clone http://gitbox.apache.org/repos/asf/commons-lang.git modules/commons-lang --branch rel/commons-lang-3.12.0 --depth 1
gradle --project-dir modules/commons-lang init
gradle run

You can see the external transitive dependency commons-lang being replaced with the local project dependency by running:

gradle app:dependencies --configuration runtimeClasspath