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.
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.
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.
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.
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.