Enterprise Java

Identifying Gradle Conventions

Configuration by convention has many advantages, especially in terms of conciseness because developers do not need to explicitly configure things that are implicitly configured through convention. When taking advantage of configuration by convention, however, one needs to be aware of the conventions. These conventions might be documented, but I always like it when I can programmatically determine the conventions because documentation can become outdated (same principle behind code always being correct and comments only sometimes being correct). I begin this post by looking at how to identify the specific conventions associated with the Gradle Java Plugin. I then generalize this approach to identify all properties associated with all tasks associated with the root project of a Gradle build.

The Gradle documentation on Gradle Plugins states the following regarding the importance of Gradle Plugins and what they add to a Gradle build:


Gradle at its core intentionally provides little useful functionality for real world automation. All of the useful features, such as the ability to compile Java code for example, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. main Java source is located at src/main/java) as well as extending core objects and objects from other plugins.

This post looks at some of the tasks, domain objects, and conventions that the Java Plugin brings to a Gradle build. To start, I need a very simple Gradle build file. It consists solely of a single line that applies the Java plugin. It is shown next in the Gradle build file build-java-plugin.gradle.

build-java-plugin.gradle

1
apply plugin: 'java'

With that single-line Gradle build file in place, it’s easy to see which Gradle tasks the plugin-provides by running the command gradle -b build-java-plugin.gradle tasks. The next two screen snapshots show the output of running an empty Gradle build file followed by the output of running the Gradle build file with only the application of the Java plugin.

emptyGradleBuildTasksOutput

gradleJavaPluginTasksOutput

By comparing the output from running Gradle “tasks” against an empty build file to the output from running Gradle “tasks” against the build file with the Java plugin applied, we can see that the Gradle has the same set of “Build Setup tasks” and “Help tasks” whether the plugin is applied or not. More significantly, we see that the Java plugin adds many new tasks categorized as “Build tasks” (assemble, build, buildDependents, buildNeeded, classes, clean, jar, testClasses), “Documentation tasks” (javadoc), “Verification tasks” (check, test), and “Rules”.

One feature I enjoy in Gradle 1.10 that Gradle 1.8 (the previous version I used) did not have is the ability on the command line to ask for details on a specific Gradle task. This is demonstrated in the next screen snapshot for Java Plugin tasks compileJava, jar, and javadoc. All three tasks have details written to standard output by using the help --task <task_name> command on the command line. These details on the Java Plugin tasks can also be found in the Gradle User Guide.

gradle_1_10_help_task_details

Because Gradle is built on Groovy, it’s fairly easy to determine characteristics of the Java Plugin using “brute force.” The next code listing, for build-java-plugin-properties.gradle, demonstrates using Groovy to determine the Gradle properties (those which can be specified with -P as opposed to system properties specified with -D) available to the build script before and after applying the Java plugin and then uses Groovy’s highly convenient overridden subtraction operator to find the differences. The names and values of all of the properties added to the Gradle script by the Java Plugin (except the property “properties”) are presented in alphabetical order.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// build-java-plugin-properties.gradle
//
// Displays properties that Gradle Java Plugin adds beyond properties already
// specified for any Gradle build.
 
def propertiesBefore = this.properties
 
apply plugin: 'java'
 
def propertiesAfter = this.properties
 
def extraProperties = propertiesAfter - propertiesBefore
 
def extraPropertiesKeys = new TreeSet<String>()
extraProperties.each
{ property ->
   if (property.key != "properties")
   {
      extraPropertiesKeys.add(property.key)
   }
}
 
extraPropertiesKeys.each
{ key ->
   println "${key} : ${extraProperties.get(key)}"
}

The next image shows a screen snapshot with the output from running this script. The screen snapshot does not show the full output, but a larger piece of the output (all the properties) is shown in text after the image.

propertiesJavaPluginAddsToGradleBuild

Output from Running Above Gradle Script to See Java Plugin Properties

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiDocTitle : gradleExample API
archivesBaseName : gradleExample
assemble : task ':assemble'
binaries : [classes 'main', classes 'test']
build : task ':build'
buildDependents : task ':buildDependents'
buildNeeded : task ':buildNeeded'
buildTasks : [build]
check : task ':check'
classes : task ':classes'
clean : task ':clean'
compileJava : task ':compileJava'
compileTestJava : task ':compileTestJava'
defaultArtifacts : org.gradle.api.internal.plugins.DefaultArtifactPublicationSet_Decorated@bc80d8
dependencyCacheDir : C:\java\examples\groovyExamples\gradleExample\build\dependency-cache
dependencyCacheDirName : dependency-cache
distsDir : C:\java\examples\groovyExamples\gradleExample\build\distributions
distsDirName : distributions
docsDir : C:\java\examples\groovyExamples\gradleExample\build\docs
docsDirName : docs
inheritedScope : org.gradle.api.internal.ExtensibleDynamicObject$InheritedDynamicObject@c10304
jar : task ':jar'
javadoc : task ':javadoc'
libsDir : C:\java\examples\groovyExamples\gradleExample\build\libs
libsDirName : libs
manifest : org.gradle.api.java.archives.internal.DefaultManifest@1ad3677
metaInf : []
module : org.gradle.api.internal.artifacts.ProjectBackedModule@d2eead
processResources : task ':processResources'
processTestResources : task ':processTestResources'
rebuildTasks : [clean, build]
reporting : org.gradle.api.reporting.ReportingExtension_Decorated@33ab8f
reportsDir : C:\java\examples\groovyExamples\gradleExample\build\reports
reportsDirName : reports
runtimeClasspath : file collection
sourceCompatibility : 1.7
sourceSets :
sources : [, ]
status : integration
targetCompatibility : 1.7
test : task ':test'
testClasses : task ':testClasses'
testReportDir : C:\java\examples\groovyExamples\gradleExample\build\reports\tests
testReportDirName : tests
testResultsDir : C:\java\examples\groovyExamples\gradleExample\build\test-results
testResultsDirName : test-results

Gradle makes it easy to see all the Gradle properties using the command “gradle properties“, but this command line action shows all properties regardless of their source (Gradle itself or a plugin).

Each Gradle task that the Java Plugin adds to the build has its own set of properties. These properties can be identified in the Gradle Build Language Reference. The Task Types section of that document has links to each task type. The linked-to pages on each task type have details on the properties supported by that task type. For example, the Task Type JavaCompile is listed on its page as having properties such as classpath, destinationDir, and source.

The following rather extensive script displays the settings for the properties of the compileJava, jar, and javadoc Gradle Java Plugin tasks. This script demonstrates how powerful it can be to apply Groovy to identifying Gradle build settings. The script could be shorter if more reflection was used, but calling the tasks’ properties out explicitly does have advantages in terms of readability and as a reference for what properties are available on each task.

build-java-plugin-metadata.gradle

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// build-java-plugin-metadata.gradle
//
// Displays the properties associated with the Gradle Java Plugin tasks
// of "compileJava", "jar", and "javadoc".
 
import groovy.transform.Field
 
apply plugin: 'java'
 
@Field int MAX_COLUMNS = 80
@Field String headerSeparator = "=".multiply(MAX_COLUMNS)
 
printCompileJavaProperties()
printJarProperties()
printJavadocProperties()
 
def printCompileJavaProperties()
{
   printHeader("compileJava Task")
   println "compileJava.classpath:\n${extractStringRepresentation(compileJava.classpath)}"
   println "compileJava.destinationDir:\n${extractStringRepresentation(compileJava.destinationDir)}"
   println "compileJava.source:\n${extractStringRepresentation(compileJava.source)}"
   println "compileJava.options:\n${extractStringRepresentation(compileJava.options)}"
   println "compileJava.includes:\n${extractStringRepresentation(compileJava.includes)}"
   println "compileJava.excludes:\n${extractStringRepresentation(compileJava.excludes)}"
   println "compileJava.sourceCompatibility:\n${extractStringRepresentation(compileJava.sourceCompatibility)}"
   println "compileJava.targetCompatibility:\n${extractStringRepresentation(compileJava.targetCompatibility)}"
}
 
def printJarProperties()
{
   printHeader("jar Task")
   println "jar.appendix:\n${extractStringRepresentation(jar.appendix)}"
   println "jar.archiveName:\n${extractStringRepresentation(jar.archiveName)}"
   println "jar.archivePath:\n${extractStringRepresentation(jar.archivePath)}"
   println "jar.baseName:\n${extractStringRepresentation(jar.baseName)}"
   println "jar.caseSensitive:\n${extractStringRepresentation(jar.caseSensitive)}"
   println "jar.classifier:\n${extractStringRepresentation(jar.classifier)}"
   println "jar.destinationDir:\n${extractStringRepresentation(jar.destinationDir)}"
   println "jar.dirMode:\n${extractStringRepresentation(jar.dirMode)}"
   println "jar.duplicatesStrategy:\n${extractStringRepresentation(jar.duplicatesStrategy)}"
   println "jar.entryCompression:\n${extractStringRepresentation(jar.entryCompression)}"
   println "jar.excludes:\n${extractStringRepresentation(jar.excludes)}"
   println "jar.extension:\n${extractStringRepresentation(jar.extension)}"
   println "jar.fileMode:\n${extractStringRepresentation(jar.fileMode)}"
   println "jar.includeEmptyDirs:\n${extractStringRepresentation(jar.includeEmptyDirs)}"
   println "jar.includes:\n${extractStringRepresentation(jar.includes)}"
   println "jar.manifest:\n${extractStringRepresentation(jar.manifest)}"
   println "jar.source:\n${extractStringRepresentation(jar.source)}"
   println "jar.version:\n${extractStringRepresentation(jar.version)}"
}
 
def printJavadocProperties()
{
   printHeader("javadoc Task")
   println "javadoc.classpath:\n${extractStringRepresentation(javadoc.classpath)}"
   println "javadoc.destinationDir:\n${extractStringRepresentation(javadoc.destinationDir)}"
   println "javadoc.excludes:\n${extractStringRepresentation(javadoc.excludes)}"
   println "javadoc.executable:\n${extractStringRepresentation(javadoc.executable)}"
   println "javadoc.failOnError:\n${extractStringRepresentation(javadoc.failOnError)}"
   println "javadoc.includes:\n${extractStringRepresentation(javadoc.includes)}"
   println "javadoc.maxMemory:\n${extractStringRepresentation(javadoc.maxMemory)}"
   println "javadoc.options:\n${extractStringRepresentation(javadoc.options)}"
   println "javadoc.source:\n${extractStringRepresentation(javadoc.source)}"
   println "javadoc.title:\n${extractStringRepresentation(javadoc.title)}"
}
 
def String extractStringRepresentation(Object object)
{
   String returnString
   if (object in String)
   {
      returnString = "\t${object}\n"
   }
   else if (object in File)
   {
      returnString = "\t${object.canonicalPath}\n"
   }
   else if (object in FileCollection)  // FileTree is a FileCollection
   {
      StringBuilder filesStr = new StringBuilder()
      def files = object.files
      files.each
      { file ->
         filesStr << "\t" << file.canonicalPath << "\n"
      }
      returnString = filesStr.toString()
   }
   else if (object in CompileOptions)
   {
      StringBuilder compileOptionsStr = new StringBuilder()
      def compileProperties = object.properties
      compileProperties.each
      { compileProperty ->
         if (compileProperty.value in DebugOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in DependOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in ForkOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.key != "class")
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << compileProperty.value << "\n"
         }
      }
      returnString = compileOptionsStr.toString()
   }
   else if (object in DebugOptions)
   {
      returnString = "\t${object.debugLevel}"
   }
   else if (object in DependOptions)
   {
      returnString = "\t${object.classpath}"
   }
   else if (object in ForkOptions)
   {
      returnString = "\t${object.executable} executable with ${object.tempDir} temp directory"
   }
   else if (object in Set || object in Boolean || object in Number || object in Enum)
   {
      returnString = "\t${object.toString()}\n"
   }
   else if (object in Manifest)
   {
      StringBuilder manifestStr = new StringBuilder()
      def manifestAttributes = object.getAttributes()
      manifestAttributes.each
      { manifestAttribute ->
         manifestStr << "\t" << manifestAttribute.key << ": " << manifestAttribute.value << "\n"
      }
      returnString = manifestStr.toString()
   }
   else if (object in MinimalJavadocOptions)
   {
      returnString = extractJavadocOptionsAsString(object)
   }
   else if (object == null)
   {
      returnString = "\tnull\n"
   }
   else
   {
      returnString = "\t${object?.class} was unexpected type.\n"
   }
   return returnString
}
 
def String extractJavadocOptionsAsString(MinimalJavadocOptions javadocOptions)
{
   StringBuilder javadocOptionsStr = new StringBuilder()
 
   javadocOptionsStr << "\tjavadoc.bootClasspath:"
   def bootClasspathFiles = javadocOptions.bootClasspath
   bootClasspathFiles.each
   { bootClasspathFile ->
      javadocOptionsStr << "\t\t" << bootClasspathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.classpath:"
   def classpathFiles = javadocOptions.classpath
   classpathFiles.each
   { classpathFile ->
      javadocOptionsStr << "\t\t" << classpathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.destinationDirectory: " << javadocOptions.destinationDirectory?.canonicalName << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.doclet: " << javadocOptions.doclet << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.docletpath:"
   def docletpath = javadocOptions.docletpath
   docletpath.each
   { docletEntry ->
      javadocOptionsStr << "\t\t" << docletEntry.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.encoding: " << javadocOptions.encoding << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.extDirs:"
   def extDirs = javadocOptions.extDirs
   extDirs.each
   { extDir ->
      javadocOptionsStr << "\t\t" << extDir.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.header: " << javadocOptions.header << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.JFlags:"
   def jflags = javadocOptions.JFlags
   jflags.each
   { jflag ->
      javadocOptionsStr << "\t\t" << jflag << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.locale: " << javadocOptions.locale << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.memberLevel: " << javadocOptions.memberLevel << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.optionFiles:"
   def optionFiles = javadocOptions.optionFiles
   optionFiles.each
   { optionFile ->
      javadocOptionsStr << "\t\t" << optionFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.outputLevel: " << javadocOptions.outputLevel << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.overview: " << javadocOptions.overview << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.source: " << javadocOptions.source << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.sourceNames:"
   def sourceNames = javadocOptions.sourceNames
   sourceNames.each
   { sourceName ->
      javadocOptionsStr << "\t\t" << sourceName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.windowTitle: " << javadocOptions.windowTitle << "\n"
 
   return javadocOptionsStr.toString()
}
 
def printHeader(String headerText)
{
   println headerSeparator
   println "= ${headerText.center(MAX_COLUMNS-4)} ="
   println headerSeparator
}

I used the Groovy @Field annotation in this build file to make the variable to which it was applied available to methods in the build file. The @Field annotation was not available until Groovy 1.8 and this reminded me of something else significant to point out about Gradle and Groovy here: Gradle uses its own prepackaged Groovy rather than any other version of Groovy that might be installed on one’s machine. You can determine which version of Groovy that is with the gradle --version command. The next screen snapshot demonstrates that my version of Groovy (2.1.6) is different than the version of Groovy (1.8.6) used by my installation of Gradle (Gradle 1.10). Because Gradle 1.10 comes with Groovy 1.8.6, I had the @Field annotation at my disposal.

gradleGroovyVersionDiffFromOwnGroovyVersion

Because the output from the last script is so lengthy, I show it here as text rather than in an image.

Output of Running Gradle on build-java-plugin-metadata.gradle

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
================================================================================
=                               compileJava Task                               =
================================================================================
compileJava.classpath:
 
compileJava.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\classes\main
 
compileJava.source:
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main3.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main4.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Temperature.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureScale.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit3.java
 
compileJava.options:
 bootClasspath: null
 fork: false
 encoding: null
 deprecation: false
 warnings: true
 forkOptions:  null executable with null temp directory
 failOnError: true
 useDepend: false
 includeJavaRuntime: false
 useAnt: false
 compilerArgs: []
 debug: true
 extensionDirs: null
 compiler: null
 debugOptions:  null
 verbose: false
 optimize: false
 dependOptions: 
 listFiles: false
 
compileJava.includes:
 []
 
compileJava.excludes:
 []
 
compileJava.sourceCompatibility:
 1.7
 
compileJava.targetCompatibility:
 1.7
 
================================================================================
=                                   jar Task                                   =
================================================================================
jar.appendix:
 null
 
jar.archiveName:
 gradleExample.jar
 
jar.archivePath:
 C:\java\examples\groovyExamples\gradleExample\build\libs\gradleExample.jar
 
jar.baseName:
 gradleExample
 
jar.caseSensitive:
 true
 
jar.classifier:
 
jar.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\libs
 
jar.dirMode:
 null
 
jar.duplicatesStrategy:
 INCLUDE
 
jar.entryCompression:
 DEFLATED
 
jar.excludes:
 []
 
jar.extension:
 jar
 
jar.fileMode:
 null
 
jar.includeEmptyDirs:
 true
 
jar.includes:
 []
 
jar.manifest:
 Manifest-Version: 1.0
 
jar.source:
 C:\java\examples\groovyExamples\gradleExample\build\tmp\jar\MANIFEST.MF
 
jar.version:
 null
 
================================================================================
=                                 javadoc Task                                 =
================================================================================
javadoc.classpath:
 C:\java\examples\groovyExamples\gradleExample\build\classes\main
 C:\java\examples\groovyExamples\gradleExample\build\resources\main
 
javadoc.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\docs\javadoc
 
javadoc.excludes:
 []
 
javadoc.executable:
 null
 
javadoc.failOnError:
 true
 
javadoc.includes:
 []
 
javadoc.maxMemory:
 null
 
javadoc.options:
 javadoc.bootClasspath:
 javadocOptions.classpath:
 javadocOptions.destinationDirectory: null
 javadocOptions.doclet: null
 javadocOptions.docletpath:
 javadocOptions.encoding: null
 javadocOptions.extDirs:
 javadocOptions.header: null
 javadocOptions.JFlags:
 javadocOptions.locale: null
 javadocOptions.memberLevel: null
 javadocOptions.optionFiles:
 javadocOptions.outputLevel: QUIET
 javadocOptions.overview: null
 javadocOptions.source: null
 javadocOptions.sourceNames:
 javadocOptions.windowTitle: null
 
javadoc.source:
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main3.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main4.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Temperature.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureScale.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit3.java
 
javadoc.title:
 gradleExample API
 
:help
 
Welcome to Gradle 1.10.
 
To run a build, run gradle  ...
 
To see a list of available tasks, run gradle tasks
 
To see a list of command-line options, run gradle --help
 
BUILD SUCCESSFUL
 
Total time: 14.041 secs

The example shown above works well for identifying specific properties associated with the Java Gradle plugin. This works fine, but its limitations include the need to write explicit code for each property whose value is desired. This implies further limitations of not necessarily knowing all the properties that are available (I used the documentation to explicitly print out values in the example above). A further implied limitation is that the script above will not display any properties values that are added to those tasks in the future. The next Gradle build example is based on the previous example, but this example does not explicitly state the tasks and properties to display. Instead, it finds all Tasks associated with the root project and then prints all properties associated with each of those Tasks.

build-java-plugin-metadata-reflection.gradle

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// build-java-plugin-metadata-reflection.gradle
//
// Displays the properties associated with the tasks associated with the Gradle
// root project.
//
 
import groovy.transform.Field
 
apply plugin: 'java'
 
@Field int MAX_COLUMNS = 80
@Field String headerSeparator = "=".multiply(MAX_COLUMNS)
 
def rootProject = getRootProject()
def tasks = rootProject.tasks
tasks.each
{ task ->
   printTaskProperties(task)
}
 
def printTaskProperties(Task task)
{
   printHeader("Task " + task.name)
   def taskProperties = task.properties
   taskProperties.each
   { taskProperty ->
      println "${task.name}.${taskProperty.key}=${extractStringRepresentation(taskProperty.value)}"
   }
}
 
def String extractStringRepresentation(Object object)
{
   String returnString
   if (object in String)
   {
      returnString = "\t${object}\n"
   }
   else if (object in File)
   {
      returnString = "\t${object.canonicalPath}\n"
   }
   else if (object in FileCollection)  // FileTree is a FileCollection
   {
      StringBuilder filesStr = new StringBuilder()
      def files = object.files
      files.each
      { file ->
         filesStr << "\t" << file.canonicalPath << "\n"
      }
      returnString = filesStr.toString()
   }
   else if (object in CompileOptions)
   {
      StringBuilder compileOptionsStr = new StringBuilder()
      def compileProperties = object.properties
      compileProperties.each
      { compileProperty ->
         if (compileProperty.value in DebugOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in DependOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in ForkOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.key != "class")
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << compileProperty.value << "\n"
         }
      }
      returnString = compileOptionsStr.toString()
   }
   else if (object in DebugOptions)
   {
      returnString = "\t${object.debugLevel}"
   }
   else if (object in DependOptions)
   {
      returnString = "\t${object.classpath}"
   }
   else if (object in ForkOptions)
   {
      returnString = "\t${object.executable} executable with ${object.tempDir} temp directory"
   }
   else if (object in Set || object in List || object in Boolean || object in Number || object in Enum || object in Class)
   {
      returnString = "\t${object.toString()}\n"
   }
   else if (object in Manifest)
   {
      StringBuilder manifestStr = new StringBuilder()
      def manifestAttributes = object.getAttributes()
      manifestAttributes.each
      { manifestAttribute ->
         manifestStr << "\t" << manifestAttribute.key << ": " << manifestAttribute.value << "\n"
      }
      returnString = manifestStr.toString()
   }
   else if (object in MinimalJavadocOptions)
   {
      returnString = extractJavadocOptionsAsString(object)
   }
   else if (object in Convention)
   {
      StringBuilder conventionStr = new StringBuilder()
      object.plugins.each?.keyset
      { plugin ->
         conventionStr << "\t" << plugin << "\n"
      }
      returnString = conventionStr.toString()
   }
   else if (object in LoggingManager)
   {
      returnString = "\n\tCurrent Log Level: ${object.level}\n\tStandard Error: ${object.standardErrorCaptureLevel}\n\tStandard Output: ${object.standardOutputCaptureLevel}\n"
   }
   else if (object == null)
   {
      returnString = "\tnull\n"
   }
   else
   {
      returnString = "\t${object?.class} was unexpected type with value of ${object}.\n"
   }
   return returnString
}
 
def String extractJavadocOptionsAsString(MinimalJavadocOptions javadocOptions)
{
   StringBuilder javadocOptionsStr = new StringBuilder()
 
   javadocOptionsStr << "\tjavadoc.bootClasspath:"
   def bootClasspathFiles = javadocOptions.bootClasspath
   bootClasspathFiles.each
   { bootClasspathFile ->
      javadocOptionsStr << "\t\t" << bootClasspathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.classpath:"
   def classpathFiles = javadocOptions.classpath
   classpathFiles.each
   { classpathFile ->
      javadocOptionsStr << "\t\t" << classpathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.destinationDirectory: " << javadocOptions.destinationDirectory?.canonicalName << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.doclet: " << javadocOptions.doclet << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.docletpath:"
   def docletpath = javadocOptions.docletpath
   docletpath.each
   { docletEntry ->
      javadocOptionsStr << "\t\t" << docletEntry.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.encoding: " << javadocOptions.encoding << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.extDirs:"
   def extDirs = javadocOptions.extDirs
   extDirs.each
   { extDir ->
      javadocOptionsStr << "\t\t" << extDir.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.header: " << javadocOptions.header << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.JFlags:"
   def jflags = javadocOptions.JFlags
   jflags.each
   { jflag ->
      javadocOptionsStr << "\t\t" << jflag << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.locale: " << javadocOptions.locale << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.memberLevel: " << javadocOptions.memberLevel << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.optionFiles:"
   def optionFiles = javadocOptions.optionFiles
   optionFiles.each
   { optionFile ->
      javadocOptionsStr << "\t\t" << optionFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.outputLevel: " << javadocOptions.outputLevel << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.overview: " << javadocOptions.overview << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.source: " << javadocOptions.source << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.sourceNames:"
   def sourceNames = javadocOptions.sourceNames
   sourceNames.each
   { sourceName ->
      javadocOptionsStr << "\t\t" << sourceName << "\n"
   }
   javadocOptionsStr << "\n"
 
   javadocOptionsStr << "\tjavadocOptions.windowTitle: " << javadocOptions.windowTitle << "\n"
 
   return javadocOptionsStr.toString()
}
 
def printHeader(String headerText)
{
   println headerSeparator
   println "= ${headerText.center(MAX_COLUMNS-4)} ="
   println headerSeparator
}

Because this output is for all properties associated with all Tasks associated with the Gradle build’s root project, the output is too lengthy to include here. Not all of the property value instances have classes that the extractStringRepresentation(Object object) method is prepared to handle, but those cases could be added to the if-else if structure of that method to handle them. This version of the Gradle build is more generic than the earlier one and prints out properties associated with Tasks that are grouped by Task.

Because a Gradle build is tightly coupled to Groovy, Groovy syntax and features can be used to learn more about the Gradle build. The examples in this post took advantage of numerous Groovy niceties. The reason that the Gradle build code above is so verbose is because most of the Gradle classes used for property values do NOT have overridden toString() methods and so no really useful output is shown without special code to call specific methods to get useful representations. I didn’t do it in this post’s examples, but another option to deal with lack of overridden toString() methods would be to use Groovy’s interception capabilities (metaClass.invokeMethod) to intercept calls to toString() and provide an overridden version. That would be essentially the same code as used above, but would be encapsulated in the intercepting objects rather than contained in the script code.

Conclusion

Gradle has really nice documentation (especially the Gradle User Guide and the Gradle Build Language Reference) and most of the tasks and properties associated with the Java Plugin for Gradle (and other plugins) are easily accessible from that documentation. However, I like to know how to programmatically identify important conventions in case the documentation is ever mistaken or I use a version different than the documentation supports. Another objective of this post has been to demonstrate how useful it can be to know Groovy when working with Gradle. It is for this reason that I believe that the rising prominence of Gradle cannot help but increase interest in Groovy.
 

Reference: Identifying Gradle Conventions from our JCG partner Dustin Marx at the Inspired by Actual Events blog.
Subscribe
Notify of
guest


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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button