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
.
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.
// 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
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 : [source set 'main', source set 'test'] sources : [[source set 'main:java', source set 'main:resources'], [source set 'test:java', source set 'test:resources']] 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// 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================================================================================ = 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// 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.
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.
2 comments:
Excellent post - it always seem like 'convention over configuration' meant that "we don't have to document it - it's convention", so it's great to be able to figure it out or find it documented somewhere!
Mike,
Thanks for the comment. I definitely have felt the way you describe with more than one product built on the concept of convention over configuration.
Dustin
Post a Comment