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 files together 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 the following 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 that we can access in a source set:
Source set property |
Type |
Description |
|
|
These are the Java source files for this project. Only files with the |
|
|
By default, this 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. |
|
|
These are all the resource files for this source set. This contains all the files in the resources source directory, excluding any files with the |
|
|
By default, this is the combination of the resources and Java properties. This includes all the source files of this source set, both resource and Java source files. |
|
|
These are the output files for the source files in the source set. This contains the compiled classes and processed resources. |
|
|
These are the directories with Java source files. |
|
|
These are the directories with the resource files for this source set. |
|
|
This is the output directory with the compiled class files for the Java source files in this source set. |
|
|
This is the output directory with the processed resource files from the resources in this source set. |
|
|
This is the 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 sourceSetJavaproperties
task, we get the following output:
$ gradle sourceSetJavaproperties :sourceSetJavaProperties java.srcDirs = [/gradle-book/Chapter4/Code_Files/sourcesets/src/main/java] resources.srcDirs = [/gradle-book/Chapter4/Code_Files/sourcesets/src/main/resources] java.files = [Sample.java] allJava.files = [Sample.java] resources.files = [messages.properties] allSource.files = [messages.properties, Sample.java] output.classesDir = /gradle-book/Chapter4/Code_Files/sourcesets/build/classes/main output.resourcesDir = /gradle-book/Chapter4/Code_Files/sourcesets/build/resources/main output.files = [/gradle-book/Chapter4/Code_Files/sourcesets/build/classes/main, /gradle-book/Chapter4/Code_Files/sourcesets/build/resources/main] BUILD SUCCESSFUL Total time: 0.594 secs
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; however, as we use a separate source set, we can use this later to create a separate JAR file with only the compiled interface class. We will name the source set api
as the interface is actually the API of our example project, which 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, as follows:
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 ... Build tasks ----------- apiClasses - Assembles api classes. compileApiJava - Compiles api Java source. processApiResources - Processes api resources. ...
We have created our Java interface in the src/api/java
directory, 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 compileApiJava
or apiClasses
task:
$ gradle apiClasses :compileApiJava :processApiResources UP-TO-DATE :apiClasses BUILD SUCCESSFUL Total time: 0.595 secs
The source file is compiled in the build/classes/api
directory.
We will now change the source code of our Sample
class and implement the ReadWelcomeMessage
interface, as shown in the following code:
// 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 the 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 /gradle-book/Chapter4/src/main/java/gradle/sample/Sample.java:10: error: cannot find symbol public class Sample implements ReadWelcomeMessage { ^ symbol: class ReadWelcomeMessage 1 error :compileJava FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':compileJava'. > Compilation 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: 0.608 secs
We get a compilation error! The Java compiler cannot find the ReadWelcomeMessage
interface. However, 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 must be compiled .
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 this, we know for sure that the Java compiler picks up the compiled class file to compile the Sample
class.
To do this, we will change the build file and add the task dependency between the two tasks and the main source set configuration, as follows:
apply plugin: 'java' n sourceSets { api main { 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: 0.648 secs
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.
Consider that we have a project with the following source directory structure:
. ├── resources │ ├── java │ └── test ├── src │ └── java ├── test │ ├── integration │ │ └── java │ └── unit │ └── java └── tree.txt
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' } } 'integeration-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.
18.118.208.97