Gradle Whirlwind Introduction
Thomas J Kennedy
For the remainder of this semester we will use Gradle for build and configuration management. We will also add a few quality of life tweaks (e.g., use Gradle to create Eclipse projects).
For now we will discuss the basics of Gradle, focusing on a few selected plugins.
1 Getting Started
The first step to using Gradle is setting up a specific directory structure. Let us pick one of my Java CS 350 Review Examples. Let us use JUnit 3 - Example 6.
Example 1: JUnit 3 Example 6 Directory Structure├── build.gradle ├── config │ └── checkstyle │ └── checkstyle.xml ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── makefile └── src ├── main │ └── java │ └── edu │ └── odu │ └── cs │ └── cs350 │ └── examples │ └── numbers │ ├── package-info.java │ ├── PrimeGenerator.java │ └── RunPrimeGenerator.java └── test └── java └── edu └── odu └── cs └── cs350 └── examples └── numbers └── TestPrimeGenerator.java
1.1 Top Level Directories & Files
Let us focus on just the top level.
Example 2: Directory Structure - Top Level├── build.gradle ├── config ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── makefile └── src
Let us examine each item, one at a time.
-
build.gradle
- This is the Gradle build file. It specifies which tasks to run, which plugins to use, and a few settings. -
config
- This is an auxiliary directory I use to store configuration files (e.g., checkstyle and other code linting rules). -
gradle
- This is a directory containing Gradle Wrapper files. These files allow us to run Gradle without needing to intall it first. -
gradle.properties
- This is an optional file that can be used to specify additional settings. -
gradlew
- This is a bootstrap/wrapper script for Linux and macOS. -
gradlew.bat
- This is a bootstrap/wrapper script for Windows. -
makefile
- Ignore this makefile. It is little more than a wrapper around Gradle. -
src
- This directory contains allmain
andtest
code.
1.2 The src Directory
Eclipse, by default, places test classes and main classes in the same directory.
Example 3: Eclipse src Directory└── src └── edu └── odu └── cs └── cs350 └── examples └── numbers ├── package-info.java ├── PrimeGenerator.java ├── RunPrimeGenerator.java └── TestPrimeGenerator.java
Gradle seperates test and main (production) code.
Example 4: Gradle src Directory└── src ├── main │ └── java │ └── edu │ └── odu │ └── cs │ └── cs350 │ └── examples │ └── numbers │ ├── package-info.java │ ├── PrimeGenerator.java │ └── RunPrimeGenerator.java └── test └── java └── edu └── odu └── cs └── cs350 └── examples └── numbers └── TestPrimeGenerator.java
Take note of how the standard Java package directory structure exists for both the test code and main code.
You probably noticed a java
subdirectory for both main and test:
└── src
├── main
│ └── java
└── test
└── java
While most of our work will be with Java code, Gradle supports composite projects (i.e., projects that use generated code or multiple languages) and non-code resource files.
2 An Initial build.gradle
Gradle syntax has changed in recent versions. For the remainder of this discussion we will focus on Gradle 5.6.1.
Let us start with a quick look at the full build.gradle
from JUnit3 Example 6.
This is a complete build.gradle
. It includes not only what we need to compile code, but also settings for code analysis tools, test coverage, and fancy test reports. Do not try to understand everything just yet.
2.1 Starting build.gradle
Let us take a few steps back. A simple initial build.gradle
needs to:
- Include the
java
andeclipse
plugins. - Specify the target version of Java (e.g., Java 8).
Example 5: Initial build.gradleplugins { id "java" id "eclipse" } sourceCompatibility = 1.8 targetCompatibility = 1.8
This would be fine… if we did not also need to use JUnit and Hamcrest for our tests. We need to add:
-
A
repositories
block that specifies the location from which dependencies can be downloaded. -
A
dependencies
block listing which dependencies (i.e., libraries) are needed.
repositories {
jcenter()
}
dependencies {
testCompile "junit:junit:4.12"
testCompile "org.hamcrest:hamcrest-library:1.3"
}
Take note of the testCompile
before both dependencies. JUnit and Hamcrest are only needed for test code.
2.2 Tweaking Test Settings
There are a few tweaks we can make to test settings:
- Continue running after a failure.
- Do not mark the entire build as failed if a test fails.
- Generate a quick JUnit Report in the terminal.
- Generate a detailed HTML test report.
These tweaks result in the following test
block:
test {
reports {
html.enabled = true
}
ignoreFailures = true
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
}
}
2.3 Making an Executable Jar
The JUnit Example included a driver class, RunPrimeGenerator
, with a main function. We should probably:
- Give the
jar
file a name. - Specify which class contains the
main
function.
jar {
baseName = "GeneratePrimes"
manifest {
attributes(
"Main-Class": "edu.odu.cs.cs350.examples.numbers.RunPrimeGenerator"
)
}
}
Take note of edu.odu.cs.cs350.examples.numbers.RunPrimeGenerator
. When specifying a Main-Class
, a fully qualified class name (package and class) must be used.
2.4 Tweaking Javadoc Settings
You have seen Javadoc documentation a few times this semester (e.g., in the Unit Testing assignments). The java
plugin automatically adds the javadoc
task. Let us make one small tweak.
javadoc {
failOnError false
}
This tweak tells Gradle to continue even if the codebase has incomplete Javadoc documentation.
2.5 Our build.gradle So Far
Currently, build.gradle
includes:
- Basic Java settings
- Dependencies
- Test configuration
- Jar configuration
Let us take a look at our current build.gradle
.
Example 6: Updated build.gradleplugins { id "java" id "eclipse" } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { jcenter() } dependencies { testCompile "junit:junit:4.12" testCompile "org.hamcrest:hamcrest-library:1.3" } jar { baseName = "GeneratePrimes" manifest { attributes( "Main-Class": "edu.odu.cs.cs350.examples.numbers.RunPrimeGenerator" ) } } test { reports { html.enabled = true } ignoreFailures = true testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" } jacoco { append = false } } javadoc { failOnError false }
2.6 Deprecated Options
In newer versions of Gradle (6.1+) one of the test options has been deprecated:
test {
reports {
html.enabled = true
}
ignoreFailures = true
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
}
jacoco {
append = false
}
}
You should remove the append = false
.
3 Importing Everything into Eclipse
You are probably using Eclipse. You have two choices:
- Set up a new project manually.
- Let Gradle do the heavy lifting.
Gradle is a build and configuration manager. With the right plugins Gradle can set up IDE projects. Do you remember that eclipse plugin from earlier?
plugins {
id "java"
id "eclipse"
}
Open a terminal, PowerShell, or command prompt window in the directory containing build.gradle
.
If you are on macOS, Linux, or WSL, run
chmod a+rx gradlew
./gradlew eclipse
If you are on Windows, run
gradlew.bat eclipse
Once the command sequence completes, open Eclipse and import the .project file, using the import an existing project wizard.
4 The Fun Stuff
Technically the rest of our example build.gradle
discussion requires the Configuration Management and Analysis Tools modules. Feel free to stop here for now and come back to these notes later.
It is never too early to introduce code analysis, code linting, and test coverage tools.
4.1 Preparing for More Gradle Plugins
Earlier we added a dependencies
block to handle third party libraries for our code. Now we need a similar block for additional Gradle Plugins. The following lines need to be added to the very top of build.gradle
.
buildscript {
repositories {
jcenter()
}
}
4.2 Jacoco for Test Coverage
Let us start with jacoco. Jacoco is a test coverage tool. It tells us which lines of code are run by at least one of our tests. Jacoco is not a substitute for proper mutator-accessor test construction.
Jacoco requires an additional plugin line.
plugins {
id "java"
id "eclipse"
id "jacoco"
}
Jacoco requires three tweaks to settings. First the test
block needs to be updated.
test {
reports {
html.enabled = true
}
ignoreFailures = true
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
}
jacoco {
append = false
}
}
The second jacoco settings block specifies the tool version.
jacoco {
toolVersion = "0.8.4"
}
The final jacoco settings block specifies in which formats coverage reports should be generated.
jacocoTestReport {
reports {
html.enabled true
xml.enabled true
csv.enabled true
}
}
The html
format is for human readability. The xml
and csv
formats will be used when setting up continuous integration (CI).
4.3 build.gradle with Jacoco
Let us take a look at the full build.gradle
after adding jacoco.
Example 7: build.gradle with Jacocobuildscript { repositories { jcenter() } } plugins { id "java" id "eclipse" id "jacoco" } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { jcenter() } dependencies { testCompile "junit:junit:4.12" testCompile "org.hamcrest:hamcrest-library:1.3" } jar { baseName = "GeneratePrimes" manifest { attributes( "Main-Class": "edu.odu.cs.cs350.examples.numbers.RunPrimeGenerator" ) } } test { reports { html.enabled = true } ignoreFailures = true testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" } jacoco { append = false } } javadoc { failOnError false } jacoco { toolVersion = "0.8.4" } jacocoTestReport { reports { html.enabled true xml.enabled true csv.enabled true } }
4.4 Project Reports
The Gradle project report plugin generates a few different reports. I generally focus on two reports in particular:
- Dependency report - lists all libraries on which the project is dependent, including bot code and Gradle plugins.
- Task report - lists all the tasks available for a Gradle Project
This plugin is not immediately useful. However, it adding it requires one small (one line) addition to the plugin block:
plugins {
id "java"
id "eclipse"
id "project-report"
id "jacoco"
}
Why not add the one line now?
4.5 SpotBugs & Findbugs
Spotbugs is the successor to Findbugs. Findbugs has been abandoned since 2015. You may see a few mentions to Findbugs in the lecture notes and a few older examples.
Use SpotBugs in Java/Gradle projects.
Spotbugs checks code for potential bugs, including:
- Repeated conditional tests
- Constructor does not initialize are data members (fields)
- Self assignment
A full list of bugs can be found in the Spotbugs documentation.
4.5.1 Adding SpotBugs
The first step is to add another line to our plugins block.
plugins { id “java” id “eclipse”
id "com.github.spotbugs" version "2.0.0"
id "project-report"
id "jacoco"
}
SpotBugs requires a little configuration. Just like with Junit and Javadoc, we want to continue the build and analysis if a tool detects failures. Let us also set up an HTML
report.
spotbugsMain {
ignoreFailures = true
effort = "max"
reportLevel = "medium"
reports {
xml.enabled = false
html.enabled = true
}
}
I usually disable SpotBugs for test code.
spotbugsTest.enabled = false
4.6 PMD
PMD is similar to SpotBugs. PMD performs analysis of code, looking for mistakes such as:
- Unused variables
- D.R.Y violations (duplicated code)
- Unnecessary parentheses
- Empty
if
orelse
blocks
A full listing of PMD rules can be found in the PMD documentation.
4.6.1 Adding PMD
PMD requires another plugin line.
plugins {
id "java"
id "eclipse"
id "com.github.spotbugs" version "2.0.0"
id "project-report"
id "jacoco"
id "pmd"
}
Just like with SpotBugs, we want the build process to continue after failures (i.e., detected issues).
pmd {
ignoreFailures = true
ruleSets = [
"category/java/bestpractices.xml",
"category/java/codestyle.xml",
"category/java/design.xml",
"category/java/errorprone.xml",
"category/java/performance.xml"
]
}
Setting up ruleSets
requires selecting specific sets of rules. I usually stick with the five rulesets listed in the example.
5 Back to build.gradle
After adding Jacoco, Javadoc, SpotBugs, and PMD we end up with a fairly complete build.gradle
. We can now
- Run tests.
- Generate test coverage reports.
- Generate documentation.
- Run code analysis tools.
Putting everything together we end up with…
Example 8: build.gradle with Tools Addedbuildscript { repositories { jcenter() } } plugins { id "java" id "eclipse" id "com.github.spotbugs" version "2.0.0" id "project-report" id "jacoco" id "pmd" } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { jcenter() } dependencies { testCompile "junit:junit:4.12" testCompile "org.hamcrest:hamcrest-library:1.3" } jar { baseName = "GeneratePrimes" manifest { attributes( "Main-Class": "edu.odu.cs.cs350.examples.numbers.RunPrimeGenerator" ) } } test { reports { html.enabled = true } ignoreFailures = true testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" } jacoco { append = false } } javadoc { failOnError false } jacoco { toolVersion = "0.8.4" } jacocoTestReport { reports { html.enabled true xml.enabled true csv.enabled true } } // SpotBugs spotbugsMain { ignoreFailures = true effort = "max" reportLevel = "medium" reports { xml.enabled = false html.enabled = true } } spotbugsTest.enabled = false // End SpotBugs config pmd { ignoreFailures = true ruleSets = [ "category/java/bestpractices.xml", "category/java/codestyle.xml", "category/java/design.xml", "category/java/errorprone.xml", "category/java/performance.xml" ] }
6 Checkstyle
What about Checkstyle? Checkstyle performs code style analysis, including checking:
- Variable names
- Function names
- Class names
- Tabs vs spaces
- Missing spaces around operators
Configuring Checkstyle is a little more involved than the other tools we have discussed so far. Let us leave a discussion of Checkstyle configuration and use for the Analysis Tools module.