Thứ Bảy, 25 tháng 1, 2014

Book Review: Mastering HTML5 Forms

When I accepted Packt Publishing's offer to review the recently released book Mastering HTML5 Forms, I was curious about what an entire book would cover related to HTML5 forms. Although they are relatively basic when compared to some of HTML5's other features, I've always felt that HTML5 forms have much to offer consumers of HTML5 applications.

The subtitle of Mastering HTML5 Forms is "Create dynamic and responsive web forms with this in-depth, hands-on guide." Mastering HTML5 Forms is written by Gaurav Gupta, has five chapters, and is a bit over 125 pages long.

As is the case with most of the Packt Publishing books I have read, Mastering HTML5 Forms's Preface introduce's the book's objectives, describes the book's overall approach ("step-by-step, example driven, and visual-based approach to learning"), summarizes the book's chapters, explains what readers should have when reading the book (text editor, Wamp, and phpMyAdmin), and describes the book's intended audience ("those who are interested in learning how HTML5, CSS3, and PHP can be used to build responsive, beautiful, and dynamic web forms").

Chapter 1: Forms and Their Significance

The first three chapters of Mastering HTML5 Forms's five chapters cover traditional HTML5 forms topics. The initial chapter introduces HTML5 forms, briefly discusses the history and appeal of forms in web pages, outlines benefits of web forms in general, contrasts HTML5 web forms with traditional HTML web forms, and an introduction to common <input> elements and the browsers that support them. Chapter 1 also introduces HTML5 form elements (<datalist>, <keygen>, and <output>) and HTML5 form attributes (including autocomplete, autofocus, placeholder, min and max, list, formnovalidate, required, and pattern).

After introducing HTML5 form elements and attributes, the initial chapter looks at complete web page code listings using some of these with CSS and shows some screen snapshots of the web pages generated by the HTML5/CSS code. Before closing with a Summary, the first chapter provides a list of bullets highlighting guidelines for obtaining "better results" when using HTML5 web forms.

Chapter 2: Validation of Forms

The second chapter of Mastering HTML5 Forms is focused on form validation. After contrasting client-side validation and server-side validation including discussion of the advantages and disadvantages of each, the second chapter moves to more detailed coverage of HTML5 form validation. This is done by first introducing client-side validation with a traditional pre-HTML5 JavaScript approach that helps to demonstrate the advantage of the HTML5 form-based approach that follows.

A good portion of Chapter 2 covers HTML5 constraint validation. This coverage includes descriptions and examples of constraint validation objects, properties, attributes, and methods such as checkValidity() and customError. These are appealing because they require minimal JavaScript.

Chapter 3: Styling the Forms

Chapter 3 of Mastering HTML5 Forms focuses on styling HTML5 forms with CSS. The chapter provides an overview of using CSS with HTML5, discusses vendor-specific prefixes for CSS3 properties, and begins introducing specifics of CSS3 such as selectors and pseudo-selectors and the substring matching attribute selectors (begins with, ends with, contains). The chapter also provides a table listing "new pseudo-classes."

Other CSS3 coverage in the third chapter of Mastering HTML5 Forms covers background, borders, text effects, fonts, and gradients. With the CSS3 introduction made, the chapter then covers applying CSS3 to HTML5 forms. The chapter concludes with bullets spelling out "CSS3 guidelines for effective styling of the forms."

Chapter 4: Connection with Database

The fourth chapter of Mastering HTML5 Forms leaves core HTML5 forms concepts behind and shifts focus to storing the information provided via web forms. Specifically, Chapter 4 covers "how to store the user's input data into a database using PHP and MySQL." The chapter briefly introduces both PHP and MySQL before demonstrating how to apply this language and database to storing data provides via forms.

Much of the fourth chapter is highly specific to PHP, MySQL, and the use of PHP with MySQL and most of Chapter 4 is not generally applicable to HTML5 forms. However, there are some general principles of HTML5 forms that can be glenaed from the PHP/MySQLs-specific descriptions and examples. Specifically, the concepts of handling potentially spoofed forms submissions and linking forms to the server side are illustrated with these PHP/MySQL examples.

Chapter 5: Responsive Web Forms

Mastering HTML5 Forms's fifth and final chapter focuses on one of the trends that I recently highlighted in my post Significant Software Development Developments of 2013 (#6): responsive design. Gupta begins the chapter by referencing Ethan Marcotte's Responsive Web Design and defining responsive web design as, "An approach by which a website or a particular page dynamically adjusts itself according to particular screen resolution to give the best user experience." I also like the author's overall description of the approach, "Using fluid, proportion-based grids, flexible images, and CSS3 media queries, a site designed with responsive web design automatically adapts the layout to the particular device resolution."

Mastering HTML5 Forms's chapter on responsive design delineates advantages of responsive design before moving onto discussion on "how responsive design works" using HTML, CSS, and JavaScript. The chapter introduces use of the <meta> tag, media queries, media features, fluid grids, and fluid grid frameworks (960 Grid System and Bootstrap), and adaptive images. As with most of the other chapters, Chapter 5 includes a "Guidelines" section that includes "guidelines for responsive design so as to make our forms more effective."

General Observations
  • The first three chapters are exactly what one would expect from a book on HTML5 forms with deep focus specifically on HTML5 forms. These are the most important chapters for those wanting a comprehensive introduction to HTML5 forms.
  • Chapter 4 and Chapter 5 are less about HTML5 forms directly and more about database connectivity and responsive web design and how those two concepts affect form design.
    • Chapter 4 will be mostly of interest to PHP developers.
    • Chapter 5 is generally interesting in terms of what responsive design is; responsive designs can make forms more effective but responsive design is interesting from a broader perspective than forms. Although Chapter 5 was likely not necessary in a book on HTML5 forms, I like its inclusion in the book for completeness.
  • Most of the chapters of Mastering HTML5 Forms include "guidelines" and "best practices" related to the material covered in the chapter. Most of these guidelines/best practices were only tangentially covered or not covered at all in the chapter, but provided a starting point for someone wanting to take the next step in that area. There were a few instances in which I would have liked a particular guideline or best practice to come with additional explanation regarding why it is recommended.
  • Mastering HTML5 Forms is generally well-written. There were one or two sentences that I had difficulty deciphering, but the majority of the book is clearly written. There were some minor editing issues where lines of ext ended after only a couple of words and then the same sentence continued on the next line, but there edit issues are minor and don't take much away from the substance of the book.
  • The electronic (PDF) version of Mastering HTML5 Forms includes color screen snapshots. I wish that more screen snapshots were included. There were some screen snapshots shown of some of the HTML5 forms features, but it would have been nice to have small screen snapshots of each feature rather than of a subset of the features.
  • My only reservation in recommending purchase of Mastering HTML5 Forms has nothing to do with book itself. Rather, the one caveat I would offer to those considering this book is that its content is largely covered online in resources such as The Current State of HTML5 Forms, Dive Into HTML5 Forms, Making Forms Fabulous with HTML5, Rethinking Forms in HTML5, Bring Your Forms Up to Date With CSS3 and HTML5 Validation, and A List Apart. However, there are advantages to having all of these items covered in a single book with consistent coverage.
Conclusion

Mastering HTML5 Forms provides a comprehensive introduction and overview of HTML5 forms in its first three chapters. It adds coverage of using HTML5 forms with PHP and MySQL in Chapter 4 for those who are interested in those technologies. Its final chapter introduces responsive design and demonstrates how responsive design techniques can make forms more useful and efficient for users. Although most of the topics covered in Mastering HTML5 Forms are readily available online, the book does a nice job of providing all of this information in a single place and demonstrating how the various concepts it covers can be used together.

Thứ Hai, 20 tháng 1, 2014

Something to Consider as Java Tops the Programming Charts

The following is a contributed article from Dennis Chu of Coverity:


Something to Consider as Java Tops the Programming Charts

By Dennis Chu, Senior Product Manager, Coverity

For development teams, it may be obvious: Java is one of the top programming languages today. Approximately 9 million developers are currently working in Java; it’s said to be running on three billion devices and the language continues to evolve almost as quickly as the changing technology landscape. But, as the story goes, the rise to the top isn’t always easy.

While Java continues to grow in popularity, it has also been linked to a number of vulnerabilities over the years - due in large part to hackers capitalizing on its widespread use. So much so that Apple moved to pull Java entirely from its Mac OS X and its products at the end of 2012.

Further, during the summer of 2013, flaws in Java were linked to growing security threats for some Android device users who owned the much-hyped digital currency Bitcoin. The vulnerability enabled hackers to tap into the digital wallets of these Bitcoin owners, exposing a serious risk for both the new monetary system and the Android operating system.

One of the most recent blows for Java came from its link to HealthCare.gov, the website that continues to make headlines as developers work to fix the programming errors that caused the site to come to a crawl - only about 5 percent of the expected 500,000 health insurance plan enrollments were able to occur in the first month of the site’s launch. HealthCare.gov was developed with Java on top of Tomcat, and while the causes of its errors are many and complex, coding and architecture design flaws were no doubt part of the problem.

Despite the shortcomings exposed over the years, Java has a large number of effective testing and development tools. But even so, given the persistence of issues, it’s become clear that these tools are not being leveraged properly. This is presumably due to poor development testing discipline or weak processes in place within organizations.

After reviewing a number of open source Java projects via our Coverity Scan service – which helps the open source development community evaluate and improve the quality and security of their software – we found similar levels of quality and security issues for Java relative to other languages, such as C and C++. So it turns out that just because Java is one of the most widely used computer languages, it doesn’t guarantee higher quality software.

Some advice for developers coding in Java, or any other computer programming language for that matter: be vigilant. Make an emphasis to select the right tools that will provide the right framework and process to allow your organization to test early and often. This will enable your organization to avoid potential nightmares down the road – for example after it’s been released to customers, when it’s too late.

Using the right technologies and best practices are still the best safeguards to ensure high-quality software. Fixing a flaw during the development process will cost only a small fraction of what it will cost to fix a defect after the product has been released – and that’s not including the damage to your brand and reputation.

On the road ahead, no matter what language tops the charts, it’s important to view testing as a critical investment rather than an unintended expense.


The article above was contributed by Dennis Chu of Coverity. I have published this contributed article because I think it brings up some interesting points of discussion. No payment or remuneration was received for publishing this article.

Thứ Bảy, 18 tháng 1, 2014

Specifying Gradle Build Properties

Properties are a valuable tool for easily customizing Gradle builds and the Gradle environment. I demonstrate some of these approaches for specifying properties used in a Gradle build in this post.

Gradle supports both project properties and system properties. The main difference between the two that is of interest in this post is how each is accessed. Project properties are more conducive to direct access by name while system properties are accessed via normal Java/Groovy system properties access methods.

Passing Project Properties from Command-Line with -P

One of the easiest approaches for passing properties to a Gradle build is to specify project properties with -P from the command line. Properties passed into the build with -P are readily accessible in the build as project properties and, if their naming structure allows, can be accessed directly like variables.

Passing System Properties from Command-Line with -D

As is the case with other Java applications, one can pass system properties to Gradle builds with -D. Although these system properties provided to the Gradle build via the -D option are always available to the Gradle build via the normal Java mechanism for obtaining system properties, Gradle makes it possible to specify Project Properties as system properties. This is done by placing the prefix org.gradle.project. before the name of the property desired in the build. For example, if one wanted to specify a system property named name.first with -D that would be available to the Gradle build as if it was provided with -P, the person could provide it to the Gradle build on the command line as org.gradle.project.name.first and the Gradle build would see it as a project property named name.first.

Passing System Properties via Environmental Variable

Any Java or Groovy application (including a Gradle build) can access environmental variables via System.getenv(String). However, Gradle allows environment variables to be accessed within the build like other project properties if the environmental variable is prefixed with ORG_GRADLE_PROJECT_. For example, if one wanted a project property in the Gradle build to be called name.last and wanted to supply it to the build via environment variable, that person could declare the environment variable ORG_GRADLE_PROJECT_name.last and its value would be available to the Gradle build as a project property with name name.last.

gradle.properties

Properties can also be provided to a Gradle build via a properties file named gradle.properties. Any properties specified with systemProp. at the beginning of their property name are seen as system properties in the Gradle build and other properties (without their names beginning with "systemProp.") are seen as Gradle project properties. For example, if my gradle.properties file had a property name.last=Marx and a property systemPropr.name.first=Dustin, the name.last property would be seen and accessed in the Gradle build like any project property while the name.first property would be seen and accessed in the Gradle build like any system property.

Demonstration / Example

Each of these types of properties-specifying mechanisms can be demonstrated with a simple example. The Gradle build shown next attempts to print out various properties that are specified in different ways.

build-properties.gradle

task displayProperties << {
displaySystemProperties()
displayGradleProjectProperties()
}

def displaySystemProperties()
{
println "\n=== System Properties ==="
println "Favorite Movie (1994): ${System.properties['movie.favorite.1994']}"
println "Favorite Movie (1996): ${System.properties['movie.favorite.1996']}"
println "Favorite Movie (1997): ${System.properties['movie.favorite.1997']}"
println "Favorite Movie (1981): ${System.properties['movie.favorite.1981']}"
println "Favorite Movie (2012): ${System.properties['movie.favorite.2012']}"
println "Favorite Movie (2013): ${System.properties['movie.favorite.2013']}"
}

def displayGradleProjectProperties()
{
println "\n=== Gradle Project Properties ==="
println "Favorite Movie (1994): ${getProjectProperty('movie.favorite.1994')}"
println "Favorite Movie (1996): ${getProjectProperty('movie.favorite.1996')}"
println "Favorite Movie (1997): ${getProjectProperty('movie.favorite.1997')}"
println "Favorite Movie (1981): ${getProjectProperty('movie.favorite.1981')}"
println "Favorite Movie (2012): ${getProjectProperty('movie.favorite.2012')}"
println "Favorite Movie (2013): ${getProjectProperty('movie.favorite.2013')}"
}

def String getProjectProperty(String propertyName)
{
String movieTitle = "null"
if (hasProperty(propertyName))
{
movieTitle = this.properties[propertyName]
}
return movieTitle
}

Some of the properties to be passed to this script will be provided on the command-line with -P, some will be provided on the command line with -D, one will be provided via environment variable, and two will be provided via gradle.properties file in the same directory as the build. That gradle.properties file is shown next.

gradle.properties

movie.favorite.2013=Star Trek into Darkness
systemProp.movie.favorite.2012=Skyfall

With the gradle.properties file in place, the other two interesting parts of the example are the setting of the environment variable. The example here is in DOS, but the same thing could be done with slightly different syntax in Linux environments. The DOS/Windows command is: set ORG_GRADLE_PROJECT.movie.favorite.1981="Raiders of the Lost Ark"

For this demonstration, I'll run the Gradle build script with -D and -P parameters: gradle -b build-properties.gradle displayProperties -Pmovie.favorite.1996="Independence Day" -Dmovie.favorite.1997=Gattaca -Dorg.gradle.project.movie.favorite.1994="Shawshank Redemption"

When running the above-listed Gradle build script with the indicated gradle.properties file in place, with the indicated environment variable specified, and with the command just shown, the output looks that shown in the next screen snapshot.

The screen snapshot indicates how properties are seen/accessed in the Gradle build depending on their source and naming convention. In short, the output demonstrates the following "rules" of property availability in a Gradle build:

  • Command-line -P properties are "project properties"
  • Command-line -D properties are, with one exception, "system properties"
  • Command-line -D properties that begin with org.gradle.project. are "project properties"
  • Properties specified in gradle.properties are, with one exception, "project properties"
  • Properties specified in gradle.properties that begin with systemProp. are "system properties"
  • Properties specified via environment variable are, with one exception, "system properties"
  • Properties specified via environment variables that begin with ORG_GRADLE_PROJECT_ are "project properties"
Conclusion

Gradle provides numerous approaches for specifying properties that can be used to customize the Gradle build.

Thứ Năm, 16 tháng 1, 2014

The Gradle Interface: Gradle Build Metadata

As I've shown in previous posts such as "Identifying Gradle Conventions" and "Evolving Gradle Build from Ant Build: Importing Ant Build File", significant information about a Gradle build can be gleaned by accessing Gradle's APIs via Groovy. In this post, I look demonstrate accessing basic Gradle build details via the Gradle interface.

The org.gradle.api.invocation.Gradle interface is accessible in the Gradle build file via simply "gradle" [which implicitly corresponds to getGradle() in Groovy parlance]. The next Gradle build script listing shows a subset of the metadata information available via the Gradle interface.

build-gradle-interface.gradle

// build-gradle-interface.gradle
apply plugin: 'java'

println "Class: ${this.getClass().canonicalName}"
println "Gradle: ${gradle.getClass().canonicalName}"
println "Ant: ${ant.getClass().canonicalName}"
println "Root Project: ${rootProject.getClass().canonicalName}"

println "\n=== Gradle ==="
println "\tgradleVersion = ${gradle.gradleVersion}"
println "\tgradleHomeDir = ${gradle.gradleHomeDir}"
println "\tgradleUserHomeDir = ${gradle.gradleUserHomeDir}"

println "\n=== Gradle.startParameter ==="
def startParameter = gradle.startParameter
println "\tcurrentDir = ${startParameter.currentDir}"
println "\tprojectDir = ${startParameter.projectDir}"
println "\tgradleUserHomeDir = ${startParameter.gradleUserHomeDir}"
println "\tbuildFile = ${startParameter.buildFile}"
println "\tprojectProperties = ${startParameter.projectProperties}"
println "\tsystemPropertiesArgs = ${startParameter.systemPropertiesArgs}"
println "\ttaskNames = ${startParameter.taskNames}"

When I run the Gradle build above and specify that the "jar" task (a task provided by the Java plugin) should be run, the output appears as follows:

The above build listing and associated screen snapshot indicate that the Gradle interface provides metadata such as version of Gradle, the Gradle installation's home directory, and the Gradle user's directory. The Gradle interface also provides access to an instance of StartParameter that provides further build start-up metadata details such as project directory, current directory, the name of the build file, project properties, system properties, and names of tasks that were specified for execution.

There is significantly more information that can be gleaned about a Gradle build from the Gradle interface and this post only shows a subset of that information.

Thứ Hai, 13 tháng 1, 2014

Differentiating Ant Target-Based Gradle Tasks

In my blog post Evolving Gradle Build from Ant Build: Importing Ant Build File, I demonstrated using Gradle's built-in AntBuilder-based Ant support to import Ant targets in a Gradle build. These Ant targets can then be accessed as Gradle tasks and appear the same as tasks introduced directly by the Gradle build. In this post, I look at using Groovy to differentiate between Gradle tasks based on imported Ant targets and Gradle-defined tasks.

The Ant build file imported by the Gradle builds in my examples of this post was introduced in my previous post. That Ant build file included the targets "-init", "clean", "compile", "jar", "javadoc", "all", and "output". The Gradle build script file that follows imports that build.xml and its Ant targets.

build-ant-targets-and-gradle-tasks.gradle (Version 1)

// build-ant-targets-and-gradle-tasks.gradle
//
// Gradle build script demonstrating Gradle Tasks associated with this project
// that are not provided by an imported Ant build file.

// ant is a DefaultAntBuilder instance
ant.importBuild 'build.xml'

def antTargetsNames = ant.references.get("ant.targets").collect{ it.name }
println "\nAnt Targets: ${antTargetsNames}\n"

def taskNames = rootProject.tasks.collect{ it.name }
println "\nGradle Task Names: ${taskNames}\n"

def tasksThatAreNotAntTargets = taskNames - antTargetsNames
println "\nGradle Tasks that are NOT Ant Targets: ${tasksThatAreNotAntTargets}\n"

The implicitly available "ant" variable (default AntBuilder) is used first to get all Ant-provided targets via the call ant.references.get("ant.targets"). Groovy's handy Collection.collect(Closure) method is invoked upon that collection to return a collection of the "names" of the Ant targets.

The implicitly available "rootProject" can also be used to get the tasks at the root project level using rootProject.tasks. The same Groovy Collection.collect(Closure) method is used on this collection to get the names of the Gradle tasks. Finally, Groovy's subtraction operator is used to easily determine which Gradle Tasks are not Ant Targets. When run as shown above all the Gradle Tasks are Ant Targets and so the subtraction operator returns nothing. This is shown in the next screen snapshot.

To make the example more interesting, I add a couple of Gradle-introduced Tasks to the Gradle build file shown above. The new version with two new Gradle-introduced Tasks is shown next.

build-ant-targets-and-gradle-tasks.gradle (Version 2)

// build-ant-targets-and-gradle-tasks.gradle
//
// Gradle build script demonstrating Gradle Tasks associated with this project
// that are not provided by an imported Ant build file.

// ant is a DefaultAntBuilder instance
ant.importBuild 'build.xml'

task(helloWorld) << {
println "Hello, World!"
}

task(currentDateTime) << {
println new Date()
}

def antTargetsNames = ant.references.get("ant.targets").collect{ it.name }
println "\nAnt Targets: ${antTargetsNames}\n"

def taskNames = rootProject.tasks.collect{ it.name }
println "\nGradle Task Names: ${taskNames}\n"

def tasksThatAreNotAntTargets = taskNames - antTargetsNames
println "\nGradle Tasks that are NOT Ant Targets: ${tasksThatAreNotAntTargets}\n"

This revised version of the Gradle build script introduces two Tasks of its own ("helloWorld" and "currentDateTime"). The output from running this script includes these two new Gradle tasks as Gradle tasks that are not Ant-introduced, Target-based Gradle tasks. This output is shown next.

The examples in this post provide additional examples of the advantages of being able to use Groovy code to better understand Gradle builds. It is straightforward to access the default Ant Builder instance ("ant") and the "rootProject " to get the names of all Ant-based Gradle tasks as well as all tasks (Ant-based or Gradle-introduced).

Thứ Tư, 8 tháng 1, 2014

Evolving Gradle Build from Ant Build: Importing Ant Build File

Changing the build system on a large project can be difficult and a lot of work. Fortunately for those migrating Ant builds to Gradle builds, Gradle provides particularly convenient mechanisms to facilitate this migration. Because Gradle is built on Groovy and Groovy includes built-in Ant support via AntBuilder, Gradle builds can use AntBuilder to call Ant tasks and run Ant targets. However, Gradle provides an even easier mechanism for referencing existing Ant targets from a Gradle build with Gradle's support for importing an Ant build via DefaultAntBuilder and that is the subject of this post.

Being able to call existing Ant targets from a new Gradle build is advantageous because it allows the migration to take place over time. One can start using Gradle almost immediately with all the real work delegated to the existing Ant build. Then, as time and priorities allow, different Ant tasks can be replaced with Gradle tasks.

To demonstrate how easy it is to import an Ant build in a Gradle build, I first provide the code listing for a simplified Ant build.

Ant Build File: build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="JavaArrays" default="all" basedir=".">
<description>Java Array Utility Functions</description>

<property name="javac.debug" value="true" />
<property name="src.dir" value="src" />
<property name="dist.dir" value="dist" />
<property name="classes.dir" value="classes" />
<property name="javadoc.dir" value="${dist.dir}/javadoc" />

<property name="jar.name" value="javaArrays.jar" />
<property name="jar.filesonly" value="true" />

<path id="classpath">
</path>

<target name="-init">
<mkdir dir="${classes.dir}" />
<mkdir dir="${dist.dir}" />
</target>

<target name="compile"
description="Compile the Java code."
depends="-init">
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
classpathref="classpath"
debug="${javac.debug}"
includeantruntime="false" />
</target>

<target name="jar"
description="Package compiled classes into JAR file"
depends="compile">
<jar destfile="${dist.dir}/${jar.name}"
basedir="${classes.dir}"
filesonly="${jar.filesonly}">
</jar>
</target>

<target name="all"
description="Compile Java source, assemble JAR, and generate documentation"
depends="jar, javadoc" />

<target name="javadoc" description="Generate Javadoc-based documentation">
<mkdir dir="${javadoc.dir}" />
<javadoc doctitle="Examples of Java Array Utility Functions"
destdir="${javadoc.dir}"
sourcepath="${src.dir}"
classpathref="classpath"
private="true"
author="Dustin" />
</target>

<target name="clean" description="Remove generated artifacts.">
<delete dir="${classes.dir}" />
<delete dir="${dist.dir}" />
</target>

</project>

The above Ant build file has some fairly typical targets with names like "compile", "jar", "javadoc", and "clean". All of this functionality can be imported into a Gradle build file. The next code listing is the complete Gradle build file that does this.

Gradle build.gradle that imports Ant build.xml

ant.importBuild 'build.xml'

The one-line Gradle build file shown above imports the Ant build file shown earlier. The effects of this can be easily seen in the following screen snapshots. The initial screen snapshot shows that the single line Gradle build file makes the "arrays" project available to the Gradle build as well as "other tasks" of "all" and "clean" with the descriptions associated with those Ant targets.

One can use gradle tasks --all to see all Ant targets, including the dependent targets such as "compile", "jar", and "javadoc". This is demonstrated in the next screen snapshot.

The next screen snapshot demonstrates running the default "all" target in the Ant build from the Gradle build.

As the build listings and images have demonstrated, importing an existing Ant build in a Gradle build is a straightforward process.

Thứ Hai, 6 tháng 1, 2014

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

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.

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.