Working with Java multi-project builds

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'
    }
}

Using partial builds

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.163.238