In a Java project, we usually have compile or runtime dependencies between projects. The output of one project is a compile dependency for another project, for example. This is very common in Java projects. Let's create a Java project with a common project that contains a Java class used by other projects. We will add a services project that references the class in the common project. Finally, we will add a web project with a Java servlet class that uses classes from the services project.
We have the following directory structure for our project:
root/ build.gradle settings.gradle common/ src/main/java/sample/gradle/util/ Logger.java services/sample src/main/java/sample/gradle/ api/ SampleService.java impl/ SampleImpl.java src/test/java/sample/gradle/impl/ SampleTest.java web/ src/main/java/sample/gradle/web/ SampleServlet.java src/main/webapp/WEB-INF/ web.xml
In the root directory, we create a settings.gradle
file. We will use the include()
method to add the common
, web
, and services/sample
projects to the build:
include 'common', 'services:sample', 'web'
Next, we create a build.gradle
file in the root directory. We apply the Java plugin to each subproject and add a testCompile
dependency on the JUnit libraries. This configuration is applied to each subproject in our build. Our :services:sample
project has a dependency on the common
project. We will configure this dependency in the project configuration of :services:sample
. We will use the project()
method to define this inter-project dependency. Our web project uses classes from both the :common
and the :services:sample
projects. We only have to define the dependency on the :services:sample
project. Gradle will automatically add the dependencies for that project to the :web
project. In our project, this means the :common
project is also added as a transitive project dependency, and we can use the Logger
class from that project in our SampleServlet
class. We will add another external dependency for the Servlet API to our :web
project and also apply the War plugin to our :web
project:
subprojects { apply plugin: 'java' repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4.8.2' } } project(':services:sample') { dependencies { compile project(':common') } } project(':web') { apply plugin: 'war' dependencies { compile project(':services:sample') compile 'javax.servlet:servlet-api:2.5' } }
The project dependencies are also called lib dependencies. These dependencies are used to evaluate the execution order of the projects. Gradle will analyze the dependencies and then decide which project needs to be built first, so the resulting classes can be used by dependent projects.
Let's build our project with the following command from the root directory:
root $ gradle build :common:compileJava :common:processResources UP-TO-DATE :common:classes :common:jar :common:assemble :common:compileTestJava UP-TO-DATE :common:processTestResources UP-TO-DATE :common:testClasses UP-TO-DATE :common:test :common:check :common:build :services:compileJava UP-TO-DATE :services:processResources UP-TO-DATE :services:classes UP-TO-DATE :services:jar :services:assemble :services:compileTestJava UP-TO-DATE :services:processTestResources UP-TO-DATE :services:testClasses UP-TO-DATE :services:test :services:check :services:build :services:sample:compileJava :services:sample:processResources UP-TO-DATE :services:sample:classes :services:sample:jar :web:compileJava :web:processResources UP-TO-DATE :web:classes :web:war :web:assemble :web:compileTestJava UP-TO-DATE :web:processTestResources UP-TO-DATE :web:testClasses UP-TO-DATE :web:test :web:check :web:build :services:sample:assemble :services:sample:compileTestJava :services:sample:processTestResources UP-TO-DATE :services:sample:testClasses :services:sample:test :services:sample:check :services:sample:build BUILD SUCCESSFUL Total time: 5.19 secs
A lot of tasks are executed, but we don't have to worry about their dependencies. Gradle will make sure the correct order of tasks is executed.
We can also have project dependencies based on a configuration in a project. Suppose we define a separate JAR artifact with only the
SampleService
class in the :services:sample
project. We can add this as a separate dependency to our :web
project. In the following example build file, we create a new JAR file with the SampleService
class and then use that as a lib dependency in the :web
project:
subprojects { apply plugin: 'java' repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4.8.2' } } project(':services:sample') { configurations { api } task apiJar(type: Jar) { baseName = 'api' dependsOn classes from sourceSets.main.output include 'sample/gradle/api/SampleService.class' } artifacts { // Add output of apiJar task to api configuration. // so we can reference it from the :web project. api apiJar } dependencies { compile project(':common') } } project(':web') { apply plugin: 'war' dependencies { compile project(path: ':services:sample', configuration: 'api') compile project(':services:sample') compile 'javax.servlet:servlet-api:2.5' } }
Because of the lib dependencies between the projects, we can execute partial builds in Gradle. This means we don't have to be in the root directory of our project to build the necessary projects. We can change to a project directory and invoke the build task from there, and Gradle will build all necessary projects first and then the current project.
Let's change to the services/sample
directory and invoke the build
task from there and look at the output:
root $ cd services/sample sample $ gradle build :common:compileJava UP-TO-DATE :common:processResources UP-TO-DATE :common:classes UP-TO-DATE :common:jar UP-TO-DATE :services:sample:compileJava UP-TO-DATE :services:sample:processResources UP-TO-DATE :services:sample:classes UP-TO-DATE :services:sample:jar UP-TO-DATE :services:sample:assemble UP-TO-DATE :services:sample:compileTestJava UP-TO-DATE :services:sample:processTestResources UP-TO-DATE :services:sample:testClasses UP-TO-DATE :services:sample:test UP-TO-DATE :services:sample:check UP-TO-DATE :services:sample:build UP-TO-DATE BUILD SUCCESSFUL Total time: 3.201 secs
The :common
project is built before our :services:sample
project. If we don't want the projects we are dependent on to be built, we must use the --no-rebuild
(or -a
) command-line argument. Gradle will now skip the building of projects that our project depends on and will use cached versions of the dependencies.
When we use the -a
argument, while invoking the build
task, we get the following output:
sample $ gradle -a build :services:sample:compileJava UP-TO-DATE :services:sample:processResources UP-TO-DATE :services:sample:classes UP-TO-DATE :services:sample:jar UP-TO-DATE :services:sample:assemble UP-TO-DATE :services:sample:compileTestJava UP-TO-DATE :services:sample:processTestResources UP-TO-DATE :services:sample:testClasses UP-TO-DATE :services:sample:test UP-TO-DATE :services:sample:check UP-TO-DATE :services:sample:build UP-TO-DATE BUILD SUCCESSFUL Total time: 3.237 secs
If we invoke the build
task on our :services:sample
project, the :common
project is also built. But there is a catch, as only the jar
task of the :common
project is executed. Normally, the build
task also runs tests and executes the check
task. Gradle will skip those tasks only if the project is built as a lib dependency.
If we want to execute the tests and checks for the dependency projects, we must execute the buildNeeded
task. Gradle will then do a complete build of all dependent projects. Let's execute the buildNeeded
task from the services/sample
directory and look at the output:
sample $ gradle buildNeeded :common:compileJava UP-TO-DATE :common:processResources UP-TO-DATE :common:classes UP-TO-DATE :common:jar UP-TO-DATE :common:assemble UP-TO-DATE :common:compileTestJava UP-TO-DATE :common:processTestResources UP-TO-DATE :common:testClasses UP-TO-DATE :common:test UP-TO-DATE :common:check UP-TO-DATE :common:build UP-TO-DATE :common:buildNeeded UP-TO-DATE :services:sample:compileJava UP-TO-DATE :services:sample:processResources UP-TO-DATE :services:sample:classes UP-TO-DATE :services:sample:jar UP-TO-DATE :services:sample:assemble UP-TO-DATE :services:sample:compileTestJava UP-TO-DATE :services:sample:processTestResources UP-TO-DATE :services:sample:testClasses UP-TO-DATE :services:sample:test UP-TO-DATE :services:sample:check UP-TO-DATE :services:sample:build UP-TO-DATE :services:sample:buildNeeded UP-TO-DATE BUILD SUCCESSFUL Total time: 3.335 secs
If we have made changes to our :services:sample
project, we might also want projects that are dependent on the sample project to be built. We can use this to make sure we have not broken any code that depends on our project. Gradle has a buildDependents
task to do this. For example, let's execute this task from our :services:sample
project; our :web
project is also built because it has a dependency on the :services:sample
project. We get the following output when we execute the buildDependents
task:
sample $ gradle buildDependents :common:compileJava UP-TO-DATE :common:processResources UP-TO-DATE :common:classes UP-TO-DATE :common:jar UP-TO-DATE :services:sample:compileJava UP-TO-DATE :services:sample:processResources UP-TO-DATE :services:sample:classes UP-TO-DATE :services:sample:apiJar :services:sample:jar UP-TO-DATE :web:compileJava :web:processResources UP-TO-DATE :web:classes :web:war :web:assemble :web:compileTestJava UP-TO-DATE :web:processTestResources UP-TO-DATE :web:testClasses UP-TO-DATE :web:test :web:check :web:build :web:buildDependents :services:sample:assemble UP-TO-DATE :services:sample:compileTestJava UP-TO-DATE :services:sample:processTestResources UP-TO-DATE :services:sample:testClasses UP-TO-DATE :services:sample:test UP-TO-DATE :services:sample:check UP-TO-DATE :services:sample:build UP-TO-DATE :services:sample:buildDependents BUILD SUCCESSFUL Total time: 3.896 secs
18.119.163.238