Gradle

Gradle SourceSets Example

Gradle SourceSets are a key concept for the Gradle Java Plugin which define the structure of Java Source Files. In this example will see how to use this concept, customize them through gradle properties, create a new sourceset, get documentation and assembling them in a JAR.

1. Introduction to Gradle SourceSets

1.1 What is a Gradle SourceSet ?

A SourceSet is a collection of java source files and additional resource files that are compiled and assembled together to be executed. The main idea of sourcesets is to group files with a common meaning for the project, with no need of separate them in another project.

2. What do We Need?

  1. As IDE: Eclipse Luna 4.4
  2. Eclipse Gradle Plugin
  3. JDK 1.7_75 or higher
  4. Gradle 2.3

But the main idea is to edit a build.gradle script and you can do this with only a plain text editor, also should have a java project ready to work on it.

3.Environment Configuration

Please set your Gradle environment variables and install the Gradle plugin on your IDE. To avoid to be boilerplate visit this previous posts that show how to configure your Gradle Environment. Gradle Hello World Tutorial & Gradle GWT Integration Example

4.Creating a Gradle Project

Go to the Eclipse Wizard and then use Gradle Project Wizard.

Gradle SourceSet Project Wizard
Gradle SourceSet Project Wizard

Then, please choose in sample project Java API and Implementation because is most useful for this example, but you can use any other.

Gradle Sourceset Sample Project
Gradle Sourceset Sample Project

5.Generated Build.Gradle (New SourceSet Definition)

If we go to the build.gradle file in project’s root, we find a script like this:

apply plugin: "java"
apply plugin: "maven"

group = "myorg"
version = 1.0

repositories {
    mavenCentral()
}

dependencies {
    apiCompile 'commons-codec:commons-codec:1.5'

    implCompile sourceSets.api.output
    implCompile 'commons-lang:commons-lang:2.6'

    testCompile 'junit:junit:4.9'
    testCompile sourceSets.api.output
    testCompile sourceSets.impl.output
    runtime configurations.apiRuntime
    runtime configurations.implRuntime
}

sourceSets.all { set ->
    def jarTask = task("${set.name}Jar", type: Jar) {
        baseName = baseName + "-$set.name"
        from set.output
    }

    artifacts {
        archives jarTask
    }
}

sourceSets {
    api
    impl
}

jar {
    from sourceSets.api.output
    from sourceSets.impl.output
}

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri("${buildDir}/repo"))

            addFilter("main") { artifact, file -> artifact.name == project.name }
            ["api", "impl"].each { type ->
                addFilter(type) { artifact, file -> artifact.name.endsWith("-$type") }
                
                // We now have to map our configurations to the correct maven scope for each pom
                ["compile", "runtime"].each { scope ->
                    configuration = configurations[type + scope.capitalize()]
                    ["main", type].each { pomName ->
                        pom(pomName).scopeMappings.addMapping 1, configuration, scope
                    }
                }
            }

        }
    }
}

So, this script has a some tasks configurations, take a look to each:

  • First we apply java and maven plugins to use the tasks defined in them. See apply plugin reference.
  • The maven repository is referenced to download the libraries with which there are dependencies to compile and run. See repositories and dependencies reference.
  • Then, dynamically it’s defined for each SourceSet a jar assembly task. In the 25 line, we define 2 tasks, called apiJar and implJar that both are Jar type and what they do is to assemble the jar with the classes contained for the SourceSets.
  • In the line 35 we define the new SourceSets, api and impl, that are contained in the src folder, in next steps we see how to set a custom location.
  • The Jar method in line 40 set the sources that are assembled in the Jar, in this case get all sources from api and impl SourceSets. If we take a look at line 27, for apiJar and implJar tasks, those only has api sources or impl sources but no both, so if them have dependency will occurs compilation or runtime errors using the Jar.
  • Last method uploadArchives will deploy this jar file to a remote Maven repository. For this example we can delete this.

6. SourceSet’s Properties

Foremost we will add a task to see all the properties of SourceSets, please add it to the end of the script.

task sourceSetProperties << {
	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}"
		}
	}
}

In this tasks we point to the main SourceSet that’s set by default. So, then run the task with the command gradle sSP (brief invocation) and will see this output:

C:\Users\Andres\workspaceLuna\GradleSourceSetProject>gradle sSP
:sourceSetProperties
java.srcDirs = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\main\ja
va]
resources.srcDirs = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\ma
in\resources]
java.files = []
allJava.files = []
resources.files = []
allSource.files = []
output.classesDir = C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\c
lasses\main
output.resourcesDir = C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build
\resources\main
output.files = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\class
es\main, C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\resources\ma
in]

BUILD SUCCESSFUL

Total time: 1.169 secs

The result is, all ‘files properties’ are empty because the directories point to the default values src/main/java. So to make this properties works we can set this task to impl or api SourceSets, or also we can set any as the main SourceSet. If we change main for impl in 3rd line and run again the task we will see this output.

C:\Users\Andres\workspaceLuna\GradleSourceSetProject>gradle sSP
:sourceSetProperties
java.srcDirs = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\impl\ja
va]
resources.srcDirs = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\im
pl\resources]
java.files = [DoublerImpl.java]
allJava.files = [DoublerImpl.java]
resources.files = []
allSource.files = [DoublerImpl.java]
output.classesDir = C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\c
lasses\impl
output.resourcesDir = C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build
\resources\impl
output.files = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\class
es\impl, C:\Users\Andres\workspaceLuna\GradleSourceSetProject\build\resources\im
pl]

BUILD SUCCESSFUL

Total time: 1.265 secs

Gradle this time whether found java files.

Another strategy is to set the main SourceSet point to src’s root. adding this configuration to SourceSets task. So if we run again the gradle sSP command will see all Java sources without test clasess. This is how we customize the SourceSet directory.

sourceSets {
    api
    impl
	main{
		java {
			srcDir 'src/api/java'
			srcDir 'src/impl/java'
		}
	}
	test {
		java {
			srcDir 'src/test/java'
		}
	}
}
java.srcDirs = [C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\main\ja
va, C:\Users\Andres\workspaceLuna\GradleSourceSetProject\src\api\java, C:\Users\
Andres\workspaceLuna\GradleSourceSetProject\src\impl\java]
java.files = [Doubler.java, DoublerImpl.java]
allJava.files = [Doubler.java, DoublerImpl.java]
resources.files = []
allSource.files = [Doubler.java, DoublerImpl.java]

We have 3 directories for the main SourceSet, and both classes are contained.

7. Assembling SourceSets in JAR Files

So if we want to package the output classes in a new JAR file, it’s simple we must define a task of Jar type and set a key sentence from sourceSet.output. In previous steps we already define them, look at 25 line or Jar configuration in line 40. So if we run the inital dinamic tasks gradle apiJar and gradle implJar, Gradle will generate a JAR with the output sources in the directories defined for the task.

C:\Users\Andres\workspaceLuna\GradleSourceSetProject>gradle apiJar
:compileApiJava UP-TO-DATE
:processApiResources UP-TO-DATE
:apiClasses UP-TO-DATE
:apiJar UP-TO-DATE

BUILD SUCCESSFUL

Total time: 0.997 secs

Gradle automatically will create 3 new tasks based on any new SourceSet added to the project, apiClasses, compileApiJava, and processApiResources for this case, but in other case change api for sourceset’s name. These tasks have a dependency between them, so what they do in 3 steps is to compile the java files, process resources and assemble the jar copying all the files to it, keeping the project structure.

Then, we assemble the three possible Jar files, so:

  • gradle apiJar , only contains api output sources
  • gradle impJar, only contains api output sources.
  • gradle Jar, contains all output project sources.

So refresh the project and take a look in the build/libs directory.

Gradle SourceSet Jars
Gradle SourceSet Jars

 

8. Creating SourceSet Documentation

So if we want to generate the Javadoc documentation, we must use the javadoc task. This tasks seeks by default the main SourceSet, but with the property sourceSets.<sourceSet>.allJava we can add another custom SourceSet. Next, add this task to the build script and we can run the command gradle javadoc; the generated documentation is allocated into build/docs/javadoc.

javadoc {
	// but the main sourceset contains both api & impl sourceset, javadoc will generate all documentation
	source sourceSets.api.allJava
	}
C:\Users\Andres\workspaceLuna\GradleSourceSetProject>gradle javadoc
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:javadoc UP-TO-DATE

BUILD SUCCESSFUL

Total time: 1.321 secs

 

9. Testing

So for run the Unit test classes contained in the test SourceSet, we need to add this configuration to the test Task to view the result of execution.

test {
	// Print in console the result of test
	afterTest { test, result ->
		println "Executing test ${test.name} [${test.className}] with result: ${result.resultType}"
	}
}

We run the task, gradle test and get this output:

C:\Users\Andres\workspaceLuna\GradleSourceSetProject>gradle test
:compileApiJava UP-TO-DATE
:processApiResources UP-TO-DATE
:apiClasses UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileImplJava UP-TO-DATE
:processImplResources UP-TO-DATE
:implClasses UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test
Executing test testIt [doubler.impl.DoublerImplTest] with result: SUCCESS

BUILD SUCCESSFUL

Total time: 1.596 secs

10. Final Build.Gradle SourceSet Script

This is the final version of the script.

/*
 * Author: Andres Cespedes
 * Date: 01 May 2015
 * Example: Gradle SourceSets Example
 * Site: www.javacodegeeks.com
 * */
apply plugin: "java"

//Script Version
version = 1.0
//Java version compatibility to use when compiling Java source.
sourceCompatibility = 1.7
//Java version to generate classes for.
targetCompatibility = 1.7

//repository where to fetch third party libraries and dependencies
repositories { mavenCentral() }

//Set a Jar task to all of SourceSets.
sourceSets.all { set ->
	def jarTask = task("${set.name}Jar", type: Jar) {
		baseName = baseName + "-$set.name"
		from set.output
	}

	artifacts { archives jarTask }
}

//Print Main Sourceset Properties
task sourceSetProperties << {
	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}"
		}
	}
}

// SourceSet's Configuration
sourceSets {
	api
	impl
	main{
		java {
			srcDir 'src/api/java'
			srcDir 'src/impl/java'
		}
	}
	test {
		java { srcDir 'src/test/java' }
	}
}

// Compile, Test and Run dependencies
dependencies {
	apiCompile 'commons-codec:commons-codec:1.5'

	implCompile sourceSets.api.output
	implCompile 'commons-lang:commons-lang:2.6'

	testCompile 'junit:junit:4.9'
	testCompile sourceSets.api.output
	testCompile sourceSets.impl.output
	runtime configurations.apiRuntime
	runtime configurations.implRuntime
}

// JAR Task Configuration, define output the directories that make up the file.
jar {
	from sourceSets.api.output
	from sourceSets.impl.output
}

javadoc {
	// but the main sourceset contains both api & impl sourceset, javadoc will generate all documentation
	source sourceSets.api.allJava
	}

test {
	// Print in console the result of test
	afterTest { test, result ->
		println "Executing test ${test.name} [${test.className}] with result: ${result.resultType}"
	}
}

11.Key Points

Tips

  • The Gradle SourceSet’s Properties are made to access to the directories and files that make up the SourceSet.
  • Java plugin give us a lot of basic funcionalities to improves the development process.
  • Gradle have a lot of default values, you need to set the custom values that adjust to your project
  • Every task have so many properties, that can be useful to your needs but to mantain the article more readable we don’t mention it here
  • Gradle SourceSet concept, is an excellent tool to build a clean structure into your project and make software’s components, atomic pieces that you can manage and assemble.

12. Download the Eclipse Project

Download
You can download the full source code of this example here Gradle SourceSet Project

Andres Cespedes

Andres is a Java Software Craftsman from Medellin Colombia, who strongly develops on DevOps practices, RESTful Web Services, Continuous integration and delivery. Andres is working to improve software process and modernizing software culture on Colombia.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button