Working with source sets

The Java plugin also adds a new concept to our project—source sets. A source set is a collection of source files that are compiled and executed together. The files can be Java source files or resource files. Source sets can be used to group together files with a certain meaning in our project, without having to create a separate project. For example, we can separate the location of source files that describe the API of our Java project in a source set, and run tasks that only apply to the files in this source set.

Without any configuration, we already have the main and test source sets, which are added by the Java plugin. For each source set, the plugin also adds these three tasks: compile<SourceSet>Java, process<SourceSet>Resources, and <SourceSet>Classes. When the source set is named main, we don't have to provide the source set name when we execute a task. For example, compileJava applies to the main source test, but compileTestJava applies to the test source set.

Each source set also has some properties to access the directories and files that make up the source set. The following table shows the properties we can access in a source set:

Source set property

Type

Description

java

org.gradle.api.file.SourceDirectorySet

The Java source files for this project. Only files with the extension .java are in this collection.

allJava

SourceDirectorySet

By default, it is the same as the java property, so it contains all the Java source files. Other plugins can add extra source files to this collection.

resources

SourceDirectorySet

All the resource files for this source set. This contains all the files in the resources source directory, excluding any files with the extension .java.

allSource

SourceDirectorySet

By default, this is the combination of the resources and Java properties. It includes all the source files of this source set, both resource and Java source files.

output

SourceDirectorySet

The output files for the source files in the source set. It contains the compiled classes and processed resources.

java.srcDirs

Set<File>

Directories with Java source files.

resources.srcDirs

Set<File>

Directories with the resource files for this source set.

output.classesDir

File

The output directory with the compiled class files for the Java source files in this source set.

output.resourcesDir

File

The output directory with the processed resource files from the resources in this source set.

name

String

Read-only value with the name of the source set.

We can access these properties via the sourceSets property of our project. In the following example, we will create a new task to display values for several properties:

apply plugin: 'java'

task sourceSetJavaProperties << {
    sourceSets {
        main {
            println "java.srcDirs = ${java.srcDirs}"
            println "resources.srcDirs = ${resources.srcDirs}"
            println "java.files = ${java.files.name}"
            println "allJava.files = ${allJava.files.name}"
            println "resources.files = ${resources.files.name}"
            println "allSource.files = ${allSource.files.name}"
            println "output.classesDir = ${output.classesDir}"
            println "output.resourcesDir = ${output.resourcesDir}"
            println "output.files = ${output.files}"
        }
    }
}

When we run the task sourceSetJavaProperties, we get the following output:

$ gradle sourceSetJavaProperties
:sourceSetJavaProperties
java.srcDirs = [/chapter4/sample/src/main/java]
resources.srcDirs = [/chapter4/sample/src/main/resources]
java.files = [Sample.java, SampleApp.java]
allJava.files = [Sample.java, SampleApp.java]
resources.files = [messages.properties]
allSource.files = [messages.properties, Sample.java, SampleApp.java]
output.classesDir = /chapter4/sample/build/classes/main
output.resourcesDir = /chapter4/sample/build/resources/main
output.files = [/chapter4/sample/build/classes/main, /chapter4/sample/build/resources/main]

BUILD SUCCESSFUL

Total time: 2.82 secs

Creating a new source set

We can create our own source set in a project. A source set contains all the source files that are related to each other. In our example, we will add a new source set to include a Java interface. Our Sample class will then implement the interface, but because we use a separate source set, we can later use this to create a separate JAR file with only the compiled interface class. We will name the source set api, because the interface is actually the API of our example project that we can share with other projects.

To define this source set, we only have to put the name in the sourceSets property of the project:

apply plugin: 'java'

sourceSets {
    api
}

Gradle will create three new tasks based on this source set—apiClasses, compileApiJava, and processApiResources. We can see these tasks after we execute the tasks command:

$ gradle tasks --all
...
apiClasses - Assembles the api classes.
    compileApiJava - Compiles the api Java source.
    processApiResources - Processes the api resources.
...

We have created our Java interface in the directory src/api/java, which is the source directory for the Java source files for the api source set. The following code allows us to see the Java interface:

// File: src/api/java/gradle/sample/ReadWelcomeMessage.java
package gradle.sample;

/**
 * Read welcome message from source and return value.
 */
public interface ReadWelcomeMessage {

    /**
     * @return Welcome message
     */
    String getWelcomeMessage();
}

To compile the source file, we can execute the task compileApiJava or apiClasses:

$ gradle apiClasses
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses

BUILD SUCCESSFUL

Total time: 3.507 secs

The source file is compiled into the build/classes/api directory.

We will now change the source code of our Sample class and implement the ReadWelcomeMessage interface:

// File: src/main/java/gradle/sample/Sample.java
package gradle.sample;

import java.util.ResourceBundle;

/**
 * Read welcome message from external properties file
 * <code>messages.properties</code>.
 */
public class Sample implements ReadWelcomeMessage {

    public Sample() {
    }

    /**
     * Get <code>messages.properties</code> file and read
     * value for <em>welcome</em> key.
     *
     * @return Value for <em>welcome</em> key from <code>messages.properties</code>
     */
    public String getWelcomeMessage() {
        final ResourceBundle resourceBundle = ResourceBundle.getBundle("messages");
        final String message = resourceBundle.getString("welcome");
        return message;
    }
}

Next, we run the classes task to recompile our changed Java source file:

$ gradle classes
:compileJava
/chapter4/sample/src/main/java/gradle/sample/Sample.java:10: cannot find symbol
symbol: class ReadWelcomeMessage
public class Sample implements ReadWelcomeMessage {
                               ^
1 error

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileJava'.
> Compile failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 3.325 secs

We get a compilation error! The Java compiler cannot find the ReadWelcomeMessage interface. But we just ran the apiClasses task and compiled the interface without errors. To fix this, we must define a dependency between the classes and apiClasses tasks. The classes task is dependent on the apiClasses tasks. First, the interface must be compiled, and then the class that implements the interface.

Next, we must add the output directory with the compiled interface class file, to the compileClasspath property of the main source set. Once we have done that, we know for sure that the Java compiler for compiling the Sample class picks up the compiled class file.

To do this, we will change the build file and add the task dependency between the two tasks and the main source set configuration:

apply plugin: 'java'

sourceSets {
    api
    main {
        compileClasspath = compileClasspath + files(api.output.classesDir)
    }
}

classes.dependsOn apiClasses

Now we can run the classes task again, without errors:

$ gradle classes
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
:compileJava
:processResources
:classes

BUILD SUCCESSFUL

Total time: 3.703 secs

Custom configuration

If we use Gradle for an existing project, we might have a different directory structure than the default structure defined by Gradle, or it may be that we want to have a different structure for another reason. We can account for this by configuring the source sets and using different values for the source directories.

Suppose that we have a project with the following source directory structure:

+ resources
|  |
|  + java
|  |
|  + test
|
+ src
|  |
|  + java
|
+ test
   |
   + unit
   |  |
   |  + java
   |
   + integration
      |
      + java

We will need to reconfigure the main and test source sets, but we must also add a new integration-test source set. The following code reflects the directory structure for the source sets:

apply plugin: 'java'

sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
        resources {
            srcDir 'resources/java'
        }
    }
    test {
        java {
            srcDir 'test/unit/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }
    'integration-test' {
        java {
            srcDir 'test/integration/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }
}

Notice how we must put the name of the integration-test source set in quotes; this is because we use a hyphen in the name. Gradle then converts the name of the source set into integrationTest (without the hyphen and with a capital T). To compile, for example, the source files of the integration test source set, we use the compileIntegrationTestJava task.

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

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