Declaring Dependencies between Subprojects
Project dependencies
What if one project needs the jar produced by another project on its compile classpath? What if it also requires the transitive dependencies of the other project? Obviously this is a very common use case for Java multi-project builds. As mentioned in Project dependencies, Gradle offers project dependencies for this.
.
├── buildSrc
│ ...
├── api
│ ├── src
│ │ └──...
│ └── build.gradle
├── services
│ └── person-service
│ ├── src
│ │ └──...
│ └── build.gradle
├── shared
│ ├── src
│ │ └──...
│ └── build.gradle
└── settings.gradle
.
├── buildSrc
│ ...
├── api
│ ├── src
│ │ └──...
│ └── build.gradle.kts
├── services
│ └── person-service
│ ├── src
│ │ └──...
│ └── build.gradle.kts
├── shared
│ ├── src
│ │ └──...
│ └── build.gradle.kts
└── settings.gradle.kts
We have the projects shared
, api
and person-service
.
The person-service
project has a dependency on the other two projects.
The api
project has a dependency on the shared
project.
It has no build script and gets nothing injected by another build script.
We use the :
separator to define a project path.
Consult the DSL documentation of Settings.include(java.lang.String[]) for more information about defining project paths.
rootProject.name = 'dependencies-java'
include 'api', 'shared', 'services:person-service'
plugins {
id 'java'
}
group = 'com.example'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation "junit:junit:4.13"
}
plugins {
id 'myproject.java-conventions'
}
dependencies {
implementation project(':shared')
}
plugins {
id 'myproject.java-conventions'
}
plugins {
id 'myproject.java-conventions'
}
dependencies {
implementation project(':shared')
implementation project(':api')
}
rootProject.name = "dependencies-java"
include("api", "shared", "services:person-service")
plugins {
id("java")
}
group = "com.example"
version = "1.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("junit:junit:4.13")
}
plugins {
id("myproject.java-conventions")
}
dependencies {
implementation(project(":shared"))
}
plugins {
id("myproject.java-conventions")
}
plugins {
id("myproject.java-conventions")
}
dependencies {
implementation(project(":shared"))
implementation(project(":api"))
}
Shared build logic is extracted into a convention plugin that is applied in the subprojects' build scripts that also define project dependencies.
A project dependency is a special form of an execution dependency.
It causes the other project to be built first and adds the jar with the classes of the other project to the classpath.
It also adds the dependencies of the other project to the classpath.
You can trigger a gradle :api:compile
. First the shared
project is built and then the api
project is built.
Project dependencies enable partial multi-project builds.
Depending on artifacts produced by another project
Project dependencies model dependencies between modules. Effectively, you are saying that you depend on the main output of another project. In a Java-based project that’s usually a JAR file.
Sometimes you may want to depend on an output produced by another task.
In turn, you’ll want to make sure that the task is executed beforehand to produce that very output.
Declaring a task dependency from one project to another is a poor way to model this kind of relationship and introduces unnecessary coupling.
The recommended way to model such a dependency is to produce the output, mark it as an "outgoing" artifact or add it to the output of the main
source set which you can depend on in the consuming project.
Let’s say you are working in a multi-project build with the two subprojects producer
and consumer
.
The subproject producer
defines a task named buildInfo
that generates a properties file containing build information e.g. the project version.
You can then map the task provider to its output file and Gradle will automatically establish a task dependency.
plugins {
id 'java-library'
}
version = '1.0'
def buildInfo = tasks.register("buildInfo", BuildInfo) {
version = project.version
outputFile = file("$buildDir/generated-resources/build-info.properties")
}
sourceSets {
main {
output.dir(buildInfo.map { it.outputFile.asFile.get().parentFile })
}
}
plugins {
id("java-library")
}
version = "1.0"
val buildInfo by tasks.registering(BuildInfo::class) {
version.set(project.version.toString())
outputFile.set(file("$buildDir/generated-resources/build-info.properties"))
}
sourceSets {
main {
output.dir(buildInfo.map { it.outputFile.asFile.get().parentFile })
}
}
public abstract class BuildInfo extends DefaultTask {
@Input
public abstract Property<String> getVersion();
@OutputFile
public abstract RegularFileProperty getOutputFile();
@TaskAction
public void create() throws IOException {
Properties prop = new Properties();
prop.setProperty("version", getVersion().get());
try (OutputStream output = new FileOutputStream(getOutputFile().getAsFile().get())) {
prop.store(output, null);
}
}
}
The consuming project is supposed to be able to read the properties file at runtime. Declaring a project dependency on the producing project takes care of creating the properties beforehand and making it available to the runtime classpath.
dependencies {
runtimeOnly project(':producer')
}
dependencies {
runtimeOnly(project(":producer"))
}
In the example above, the consumer now declares a dependency on the outputs of the producer
project.
Depending on the main output artifact from another project is only one example. Gradle has one of the most powerful dependency management engines that allows you to share arbitrary artifacts between projects and let Gradle build them on demand. For more details see the section on sharing outputs between projects.