This chapter covers
In recent years, the interest in polyglot programming and its application in real-world, mission-critical software stacks has skyrocketed. The term polyglot programming was coined to describe projects that incorporate more than one language. That’s particularly true for the Java world. For web developers, the world can no longer do without a potpourri of JavaScript, corresponding frameworks, libraries, and CSS. Backend code or desktop applications are no longer built with Java alone. Other JVM-based languages like Groovy or Scala are added to the mix. It’s all about picking the right tool for the job. In this chapter, we’ll discuss how Gradle faces the challenge of organizing and building polyglot projects by using your To Do application as an example.
As Java developers, we’re spoiled by extensive tooling and build automation support. Concepts like build-time dependency management of external libraries and convention over configuration for project layouts have been around for years and are part of our day-to-day vocabulary. In JavaScript land, commonly accepted patterns and techniques for build automation are in their early stages of development. Gradle doesn’t provide first-class support for JavaScript and CSS, but we’ll discuss how to use its API to implement typical usage scenarios, like combining and minifying JavaScript files.
In past chapters, we mainly concentrated on building Java projects. Software stacks that incorporate a wide range of languages are a reality for many organizations. JVM-based languages are well supported by Gradle. We’ll drill into the details of using the core language plugins for building Java, Groovy, and Scala code. Along the way, we’ll touch on topics like compiler daemons and joint compilation.
In addition, many enterprises have to integrate with software written in non-JVM languages like C or C++. This chapter will give an overview of how to incorporate heterogeneous software infrastructure into a build. Let’s start by looking at build automation for modern web architectures.
A rich user experience has become the key to success for any application. This holds true for desktop, mobile, and web applications. Today, users demand the same look and feel for web applications as they do for native applications. Long gone are the days when customers were happy with static HTML pages and synchronous server roundtrips.
JavaScript has become the most popular programming language for the web that can meet this demand. Developers no longer have to depend on plain JavaScript to implement their features. Many application development frameworks and libraries (like jQuery, Prototype, and Backbone, to name just a few) are available to simplify the use of JavaScript development and to smooth the rough edges this language introduces.
JavaScript has moved far beyond client-side runtime environments. Platforms like Node.js (http://nodejs.org/) use JavaScript for implementing server-side applications as well. As a result, JavaScript is ubiquitous and grows in relevance.
When dealing with JavaScript, various tasks come to mind that may be part of your workflow. In fact, some of them should sound familiar, because we discussed them in the context of your Java web application:
If you try to incorporate all of these tasks in your To Do project without proper automation, there are many manual and repetitive steps that need to be performed before you can assemble the WAR file. For the same reasons laid out in chapter 1, you’ll want to avoid this situation under all circumstances. In this chapter, we won’t cover solutions to all tasks presented, but you’ll learn how to generally automate them with Gradle.
Let’s assume you start to modernize your To Do application. Instead of submitting the data to the server for each operation and rerendering the page, you change the functionality to exchange data via asynchronous JavaScript calls (AJAX) in the background. If the HTML user interface needs to be updated, you directly modify the document object model (DOM). As a result, the To Do application will behave similar to a desktop application with no page reload required.
To simplify the use of JavaScript, your application will use an external JavaScript library. Out of the many available libraries, pick JQuery (http://jquery.com/), an established, feature-rich, easy-to-use API for handling AJAX calls and manipulating the HTML DOM.
With the help of JQuery, you’ll implement updating the name of an existing To Do item in your list. This functionality requires two actions. Double-clicking the item’s name will bring the item name into edit mode by offering an input field to change its value. Pressing the Enter key while in editing mode will send the modified item name to the server, update the value in the data store, and exit the edit mode. These actions are implemented in the JavaScript files edit-action.js and update-action.js. We won’t discuss the content of these files here. Feel free to check out their implementation in the book’s source code examples. The following directory structure shows both JavaScript files, as well as the minified JQuery library in your web project:
Now, you could just download the JQuery library manually and put it into the directory src/main/webapp/js/ext. Many projects do that. Remember when we talked about dependency management for external Java libraries in chapter 5? You can apply the same concept for automatically resolving and downloading JavaScript files. Let’s see how this can be done.
Historically, JavaScript developers didn’t bother with the concept of dependency management. We used to throw in external libraries with our application sources. As our code base grew, we had no way of knowing what JavaScript dependencies the project required and with what version. If we were lucky, the filename of the JavaScript library would indicate that.
If you exclusively work with JavaScript, this problem can be tackled by using the node package manager (NPM). Dependencies between JavaScript files can be modeled by using RequireJS (http://requirejs.org/). But what if you want to use JavaScript dependency management from Gradle? No problem; you can just use the standard Gradle dependency declaration and resolution mechanism.
There are some challenges we face with this approach, though. As JVM developers, we’re used to having access to all possible libraries we’d ever want to use. We pointed our build script to Maven Central and that was that. For JavaScript libraries, there’s no established hosting infrastructure. One of the hosting providers with a reasonable collection of popular, open source JavaScript libraries is Google Hosted Libraries (https://developers.google.com/speed/libraries/). Alternatively, you can directly use the hosted versions of a specific library if the project provides them.
On top of an insufficient JavaScript hosting infrastructure, you can’t depend on a unified descriptor format for libraries, similar to the pom.xml file for Maven repositories. This means that transitive JavaScript libraries, CSS, and even required images need to be declared explicitly.
Let’s look at how to retrieve the minified version of the library from JQuery’s download page. You’ll see a familiar pattern here. First, you define a custom configuration, and then you declare the library with the specific version in the dependencies configuration block. For downloading the library to your source code directory, create a new task of type Copy that uses your custom configuration. The following listing demonstrates how to wire this task up with other important tasks of your project to ensure that you fetch the JavaScript before running the application in Jetty or assembling the WAR file.
Give the task a shot. You’ll run it from the root directory of your Gradle project:
$ gradle :web:fetchExternalJs :web:fetchExternalJs Download http://code.jquery.com/jquery-1.10.0.min.js
As expected, the JavaScript library is downloaded and ends up in the specified directory src/main/webapp/js/ext. You can now directly link to this library in your JSPs and HTML files. After its initial download, the task fetchExternalJs automatically knows that it doesn’t have to run again:
$ gradle :web:fetchExternalJs :web:fetchExternalJs UP-TO-DATE
Your application source directory now contains all required JavaScript files. Feel free to check out the working application by running gradle :web:jettyRun. You’ll see that the application is fully functional.
You may ask yourself why the downloaded JavaScript file was put directly in your project’s source tree. Good point! Usually, you don’t want to mix versioned files with downloaded dependencies. This was done to support in-place web application deployment with the Jetty plugin using the jettyRun task. Unfortunately, the plugin doesn’t allow pointing to a secondary web application directory. Therefore, the jQuery JavaScript file needs to sit in the target web application location. There are ways to deal with this shortcoming. For example, you could exclude the file from versioning (please consult your VCS documentation).
You laid the groundwork for implementing additional automation tasks for your JavaScript code. In the next two sections, we’ll talk about how to build your own JavaScript automation with the help of standard Gradle tooling. In particular, we’ll look at how to implement minification and how to generate code quality metrics.
There are two essential optimizations every large JavaScript project needs to embrace: merging multiple JavaScript files into one and minifying its content. A tool that provides support for both of these tasks is the Google Closure Compiler (https://developers.google.com/closure/compiler/). One of its distributions is a Java library that can be directly included in a Gradle build, as shown in the following listing.
Apart from the plain Java API classes, this library includes an Ant task wrapping the provided functionality. It’s a matter of personal taste which of these approaches you use in your Gradle build—both of them work. For further examples, you’ll use the Ant task because it conveniently wraps the API. The next listing demonstrates a custom task written in Groovy that you’ll put into the directory buildSrc/src/main/groovy of your project. If you need a quick refresher on how to use an Ant task from Gradle, cross-reference chapter 9.
For the custom task to function, an enhanced task of type GoogleClosureMinifier needs to set a list of input JavaScript files and the target output file, which will contain the combined and minified JavaScript code. The following listing uses the project’s fileTree method to derive the list of your application’s JavaScript files as input. The output file is defined as all-min.js and is created in the directory build/js.
Executing the task minifyJs will first compile your minification custom task under buildSrc, and then produce a single, optimized JavaScript file:
$ gradle :web:minifyJs :buildSrc:compileJava UP-TO-DATE :buildSrc:compileGroovy :buildSrc:processResources UP-TO-DATE :buildSrc:classes :buildSrc:jar :buildSrc:assemble :buildSrc:compileTestJava UP-TO-DATE :buildSrc:compileTestGroovy UP-TO-DATE :buildSrc:processTestResources UP-TO-DATE :buildSrc:testClasses UP-TO-DATE :buildSrc:test :buildSrc:check :buildSrc:build :web:minifyJs
Being able to create an optimized JavaScript file is great, but now you have to change the references in your dynamic and static web page files. Of course, this process should be fully automated and integrated in your development process. In the next section, we’ll discuss one way to implement this.
In production environments performance is key. That’s why many organizations choose to deploy optimized JavaScript files. The downside is that single-line, optimized JavaScript files are hard to read and not very useful for debugging or even diagnosing an issue. Therefore, you don’t want to package and run your application with only minified files. During development or in testing environments, you still want to use plain old JavaScript files. Obviously, at build time you need to be able to control whether your application JavaScript files need to be optimized and used.
There are many approaches to tackle this problem. Here, we’ll only discuss one of them. For your project, we’ll discuss a new project property named jsOptimized. If it’s provided on the command line, you indicate that optimizations should happen. If it’s not provided, use the original JavaScript files. Here’s an example of how to use it: gradle :web:war –PjsOptimize.
Gradle is flexible enough to support this requirement. Based on this optimization flag, you can configure your war task to trigger the minification, exclude the original JavaScript files, include the produced minified file, and change the JSP file to reference it. The following listing shows how to make this happen for your To Do application.
The trickiest part of this configuration is the replacement of the JavaScript import in the JSP file todo-list.jsp. To make life easier, you created two new JSP files that reference either the original JavaScript files or the minified file. At build time, you only need to replace the JSTL import statement if you decide to go with the optimized version. Keep in mind that the original source file isn’t touched. The file is changed on the fly when building up the WAR file.
This example covers one of the most common use cases for JavaScript developers. Let’s also look at introspecting JavaScript source files for code quality purposes.
Detecting issues and potential problems before the code gets deployed to production should be of concern to every JavaScript developer. JSHint is a popular tool written in JavaScript for detecting errors and potential issues in JavaScript code. To run JSHint, you’ll need to be able to execute it from Gradle. So how do you execute JavaScript from Gradle?
Rhino is an open source implementation of JavaScript written in Java. With the help of this library, you can run JavaScript files and therefore use JSHint in your build. In the next listing, you’ll retrieve Rhino from Maven Central and the JSHint JavaScript file from a hosted download URL.
You declared Rhino as a dependency—now let’s see it in action. Instead of using an Ant task, invoke the main method by assigning it to an enhanced Gradle task of type JavaExec. JSHint requires you to define a list of JavaScript files as arguments that it should examine. Under the hood, a Java invocation of Rhino for your application looks as follows:
java -jar rhino-1.7RC4.jar jshint-rhino-2.1.4.js edit-action.js update- action.js
The following listing demonstrates how to build this argument list and write the enhanced task for invoking the Rhino main class.
Executing the task for your fully functional application doesn’t report any issues:
$ gradle jsHint :web:jsHint
However, try emulating a bug in your JavaScript by removing a semicolon at the end of a line in the file edit-action.js. You can see that JSHint rightfully complains about it and fails the build:
$ gradle :web:jsHint :web:jsHint Missing semicolon. (/Users/Ben/gradle-in-action/code/chapter11/todo- js-code-quality/web/src/main/webapp/js/app/edit-action.js:2:46) > $("#toDoItem_" + row).addClass("editing") :web:jsHint FAILED
We saw that it’s really easy to incorporate third-party libraries to create sufficient tooling for JavaScript in your Gradle build. This can be accomplished by reusing Ant tasks, by executing Java applications with the help of the task type JavaExec, or even by calling external shell scripts. All it takes is a little bit of research and the eagerness to find the best tool for the job.
We all know that good developers are lazy—lazy because they hate doing monotonous and repetitive tasks. Most developers prefer to reuse existing functionality. There’s a community Gradle plugin that provides most of the functionality you’ve implemented and even more (for example, generating API documentation with JSDoc). Enter the Gradle JavaScript plugin (https://github.com/eriwen/gradle-js-plugin). The plugin is a good starting point for every project that incorporates JavaScript and doesn’t require you to implement your own tasks.
In this section, we’ll discuss how to use the plugin and its exposed DSL to optimize your JavaScript—that is, by combining and minifying it. First, pull in the plugin from Maven Central and apply it to your web project, as shown in the next listing.
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.eriwen:gradle-js-plugin:1.5.1' } } apply plugin: com.eriwen.gradle.js.JsPlugin
The plugin describes JavaScript source directories with a dedicated name. This named JavaScript source set can then be used as input for one of the tasks the plugin defines. The following listing shows the JavaScript DSL in more detail. This code should look similar to what you’ve built so far, but is more readable and easier to understand.
The task usage is very similar to what you’ve experienced so far. To run minification on your files, execute the task minifyJs. This task first calls the combineJs task to merge your application JavaScript files before shrinking their size.
Requirements for optimizing CSS are in many cases no different from JavaScript. Similar tasks can be implemented to combine and minify CSS, except with other tools. For a quick start, check out the Gradle CSS plugin: https://github.com/eriwen/gradle-css-plugin.
Great, there’s third-party plugin support for JavaScript in Gradle. But what if your frontend team already implemented build automation that doesn’t use Gradle? Do you need to rewrite all their existing logic? Thankfully, you don’t. You can directly invoke other build automation tools from Gradle. Let’s examine this by the example of Grunt.
If you’re deeply involved in the JavaScript community, you may know the build tool Grunt (http://gruntjs.com/). Grunt is targeted toward implementing common tasks like minifying, merging, and testing of JavaScript code in an automated fashion. With its plugin system and rapidly growing community, you’ll need to take into consideration existing JavaScript automation workflows.
To get started with Grunt, you first need to install the executable via NPM, the Node.js package manager. Please check the Grunt documentation for detailed information on how to install it. Based on your operating system, you’ll either end up with a batch script named grunt.cmd (Windows) or a shell script named grunt (*nix). This executable is used to evaluate and run a Grunt build. A Grunt build requires two mandatory files: the file Gruntfile.js defines the tasks and their configuration, and the file package.json defines the project metadata and required external dependencies (for example, plugins).
In this section, we’ll discuss how to call Grunt tasks from Gradle. This is a valuable approach for two reasons: either you already have an automation process in place and don’t want to rewrite this code in Gradle, or you want to drive build automation from a single tool that allows you to cover the full application stack. Figure 11.1 illustrates how executing the Grunt executable looks in practice.
Let’s see this setup in action. Grunt doesn’t automatically install dependencies defined in package.json. If a user forgets about this step, the Grunt build will fail. To install these dependencies, navigate to the directory that contains the file and run the NPM with the command npm install. The NPM installs dependencies to the directory node_modules.
Of course, we, as automation specialists, want to make this happen automatically. Before running a Grunt task from Gradle, run the Node install command. The following listing demonstrates how to wrap this call with a Gradle task.
If you take a closer look at figure 11.1, you’ll notice a task of type Grunt. Listing 11.11 shows the Groovy task Grunt, a class defined under buildSrc/src/main/groovy, which acts as a simple wrapper for invoking the Grunt executable. All you need to provide to the enhanced task are the commands (the Grunt tasks) defined in the Grunt build file.
Let’s assume you have an existing Grunt build that applies the JSHint plugin. The JSHint task is named jshint. To invoke this Grunt task, you create a new task of type Grunt and provide the Grunt task name as the command, as shown in the next listing.
Run the task jsHint on your To Do application. The following command-line output shows the output from Grunt discovering an erroneous JavaScript file that’s missing a semicolon:
$ gradle :web:jsHint :buildSrc:compileJava UP-TO-DATE :buildSrc:compileGroovy ... :buildSrc:build :web:installGruntDependencies npm WARN package.json [email protected] No README.md file found! npm http GET https://registry.npmjs.org/grunt ... [email protected] node_modules/grunt ├── [email protected] ├── ... ... [email protected] node_modules/grunt-contrib-jshint └── [email protected] ([email protected], [email protected], [email protected], [email protected], [email protected]) :web:jsHint Running "jshint:all" (jshint) task Linting src/main/webapp/js/app/edit-action.js...ERROR [L2:C46] W033: Missing semicolon. $("#toDoItem_" + row).addClass("editing") Warning: Task "jshint:all" failed. Use --force to continue. Aborted due to warnings. :web:jsHint FAILED
After this extensive discussion about JavaScript integration with Gradle, we’ll look at Gradle support for JVM languages other than Java.
The days when Java was the one and only language used for writing applications are over. Today, when we think about Java, we also refer to it as a mature development platform: the Java Virtual Machine (JVM). There’s a wide range of languages running on the JVM that are suitable for enterprise or server-side development. Among them are popular languages likes Groovy, Scala, Clojure, and JRuby. Why would you even want to use a different language than Java or mix them within a single project? Again, it’s all about the right tool for the job. You may prefer Java for its statically typed nature and library support to implement your business logic. However, Java’s syntax isn’t a natural fit for producing DSLs. This is where other, more suitable languages like Groovy come into play.
So far, we’ve discussed how to build Java applications, but there’s more to Gradle. It has first-class support for other JVM languages. In this section, we’ll look at how to organize and build Groovy and Scala projects and how to bring them all under one umbrella within a single project. Before we dive into the details, let’s take a step back and review some of the inner workings of the Java plugin, which to an extent builds the foundation for other language plugins.
In chapter 8, we talked about a guiding principle for separating concerns in plugins: capabilities versus conventions. Let’s quickly recap this concept. A plugin that provides a capability oftentimes introduces new concepts or tasks, and a plugin that provides conventions imposes opinionated defaults for these concepts. What might sound very abstract becomes strikingly clear by dissecting the inner workings of the Java plugin.
What you see as a Gradle user when you apply the Java plugin to one of your projects is the full gamut of functionality. Your project is automatically equipped with the concept of source sets and Java-specific configurations, and exposes tasks as well as properties. This baseline Java support builds the foundation for every JVM language-based project. These capabilities are
Gradle also preconfigured your project with sensible default configurations, also called conventions—for example, production source code sits in the directory src/main/java, and test source code in src/test/java. These conventions aren’t set in stone and can be reconfigured, but they paint an initial opinioned view of your project. Some of the tasks in a Java project are based on the preconfigured source sets. The following list describes the opinionated bits of the Java plugin:
The Java plugin separates capabilities and conventions. How does it do it? Capabilities are extracted into another plugin: the Java base plugin. Internally, the Java plugin applies the Java base plugin and adds the conventions on top, as shown in figure 11.2.
This separation of concerns has various implications. First of all, if your Java project doesn’t fit the default conventions introduced by the Java plugin, you can instead apply the Java base plugin and define your own conventions. Second, the Java base plugin can be used as the basis for building other JVM language plugins because they require similar concepts. This is a perfect example of code reuse and is applied to the core plugins for building Groovy and Scala projects. Before we look at how these base capabilities are applied, let’s explore another useful optimization you can use when compiling Java sources.
By default, Gradle will fork a new JVM for every Java compilation task. Especially in large Java-based multiproject builds, this causes unnecessary overhead. You can speed up the compilation process by reusing an already-forked instance of a Java compilation process. In the following listing, you configure every task of type JavaCompile to run in fork mode, while at the same time using Gradle’s own Java compiler integration.
When executing the multiproject build on the root level with gradle clean build, notice that the total time of your build is faster than without this modification. Internally, Gradle reuses the compiler daemon process across tasks even if they belong to a different subproject. After the build is finished, the compiler daemon process(es) are stopped. This means that they don’t outlast a single build. Next, we’ll focus on building and configuring Groovy projects.
Groovy works nicely in conjunction with its older brother Java. It builds on top of Java, but has a more concise syntax and adds dynamic language features to the mix. Introducing Groovy to a Java project is as simple as adding the Groovy JAR file to its classpath.
In this book, you already used Groovy to write project infrastructure code. You used the Spock framework to write unit tests for your production application code. We also looked at how Groovy can be used to implement custom Gradle tasks. For conservative organizations, these are suitable use cases for incorporating Groovy, because the code isn’t deployed to a production environment. There are many good reasons why you may also want to use Groovy instead of Java for implementing production code. Groovy is a great match for writing DSLs with the help of its meta-programming features. This is something Gradle does itself. It uses Java to implement the core logic, but wraps it with Groovy to expose the powerful DSL you’re now accustomed to. Groovy support in Gradle is provided by the Groovy plugin. Let’s look at its features and see where the Java base plugin comes into play.
The Groovy plugin ecosystem imposes an analogous concept to separate capabilities and conventions as the Java plugin. To get a quick overview on how they play together, look at figure 11.3.
Central to the figure are the Groovy base plugin and the Groovy plugin. The Groovy base plugin inherits all the capabilities of the Java base plugin and imposes its own conventions on top of it. Check out its feature list:
The Groovy plugin builds upon the Groovy base plugin and the Java plugin. The additional benefit that the plugin adds to the equation is a task of type Groovydoc. Executing this task generates the HTML API documentation for the Groovy source code. Next, you’ll convert one of the projects in your To Do application from a Java project into a Groovy project.
Turning a self-contained Java project into a Groovy project is easy. We’ll examine the conversion process by the example of your model project. Currently, this project only defines a single Java class file: ToDoItem.java. Before you turn this class into a Groovy class, you’ll add the build infrastructure code to the script model/build.gradle, as shown in the following listing.
By applying the Groovy plugin, you equipped your project with the capabilities to compile Groovy source code and the default source sets that define where the compiler looks for the source code files.
Remember that the Groovy plugin automatically applies the Java plugin as well. This means that your project now contains a source set for Java sources and one for Groovy sources. Figure 11.4 shows the default project layout with the applied Groovy plugin.
You may be aware that there are multiple distributions of the Groovy library. The file groovy-all contains all external dependencies (for example, Antlr and Apache Commons CLI) in a single JAR file archive to ensure compatibility between them. The file with the name groovy doesn’t contain these dependencies. You’re responsible for declaring these dependencies in your build script. No matter which distribution you choose, both will enable your project to compile Groovy source code.
Keep in mind that this doesn’t mean that you’re required to have Java source files in your project. However, it enables you to mix Java and Groovy files in a single project. For now, just rename the existing Java class to ToDoItem.groovy and move the file under src/main/groovy with the package directory com.manning.gia.todo.model. It’s time to convert the bulky POJO class implementation into Groovy. The next listing demonstrates to what extreme the code could be condensed.
Try out the build of your Groovy project. You can see that the task compileJava is marked UP-TO-DATE because you don’t have any Java source files under src/main/java. The task compileGroovy finds the file ToDoItem.groovy and compiles it. The following command-line output demonstrates this behavior:
$ gradle :model:build :model:compileJava UP-TO-DATE :model:compileGroovy Download http://repo1.maven.org/maven2/org/codehaus/groovy/groovy- all/2.1.5/groovy-all-2.1.5.pom Download http://repo1.maven.org/maven2/org/codehaus/groovy/groovy- all/2.1.5/groovy-all-2.1.5.jar :model:processResources UP-TO-DATE :model:classes :model:jar :model:assemble :model:compileTestJava UP-TO-DATE :model:compileTestGroovy UP-TO-DATE :model:processTestResources UP-TO-DATE :model:testClasses UP-TO-DATE :model:test :model:check :model:build
If the default conventions introduced by the Groovy base plugin don’t fit your needs, you can redefine them in the same way as you can for Java projects. The underlying concept that enables you to reconfigure the default source directories is a SourceSet. In the following listing, you’ll say that the Groovy production source files should sit under src/groovy, and Groovy test source files should reside under test/groovy.
sourceSets { main { groovy { srcDirs = ['src/groovy'] } } test { groovy { srcDirs = ['test/groovy'] } } }
I mentioned before that a single Gradle project can contain Java and Groovy files. To a developer, it feels natural to use Java classes from Groovy classes and vice versa. In the end, shouldn’t both source file types be compiled to bytecode? There’s a catch. Groovy can depend on Java, but Java can’t depend on Groovy.
To demonstrate this behavior, you’ll apply the Groovy plugin to the repository project. You’ll also turn the class ToDoRepository from a Java interface into a Groovy interface, as shown in the next listing.
package com.manning.gia.todo.repository import com.manning.gia.todo.model.ToDoItem interface ToDoRepository { List<ToDoItem> findAll() List<ToDoItem> findAllActive() List<ToDoItem> findAllCompleted() ToDoItem findById(Long id) Long insert(ToDoItem toDoItem) void update(ToDoItem toDoItem) void delete(ToDoItem toDoItem) }
This interface has two implementations, both of which are still Java classes: InMemory-ToDoRepository.java and H2ToDoRepository.java. Now, if you compile the repository project, you’ll end up with a compilation error:
$ gradle :repository:classes :model:compileJava UP-TO-DATE :model:processResources UP-TO-DATE :model:classes UP-TO-DATE :model:jar UP-TO-DATE :repository:compileJava /Users/Ben/gradle-in-action/chapter11/todo-mixed-java-groovy/ repository/src/main/java/com/manning/gia/todo/repository/ H2ToDoRepository.java:9: cannot find symbol symbol: class ToDoRepository public class H2ToDoRepository implements ToDoRepository { ^ /Users/Ben/gradle-in-action/code/chapter11/todo-mixed-java-groovy/ repository/src/main/java/com/manning/gia/todo/repository/ InMemoryToDoRepository.java:12: cannot find symbol symbol: class ToDoRepository public class InMemoryToDoRepository implements ToDoRepository { ^ ... 16 errors :repository:compileJava FAILED
Does this mean you can never depend on Groovy classes from Java classes? No. The key to making this work is called joint compilation, which allows you to freely mix Java and Groovy source code with bidirectional dependencies on each other. One way to address this issue is to put the Java source code together with the Groovy source code under the directory src/main/groovy. Alternatively, you can configure the Groovy compiler to enable joint compilation. The following listing shows what needs to be configured in your build to use this feature.
After you add this code snippet to the build.gradle file of the repository project, joint compilation works as expected:
$ gradle :repository:classes :model:compileJava UP-TO-DATE :model:processResources UP-TO-DATE :model:classes UP-TO-DATE :model:jar UP-TO-DATE :repository:compileJava UP-TO-DATE :repository:compileGroovy :repository:processResources UP-TO-DATE :repository:classes
For the compiler nerds among us, let’s look at what happens under the hood:
1. The compiler parses the Groovy source files and generates stubs for them.
2. The Groovy compiler invokes the Java compiler and generates the stubs for Java source files.
3. With the Java stubs in the Groovy source path, the Groovy compiler can compile both.
With the techniques presented in this chapter, you should be able to set up your own project to incorporate Groovy, either as a standalone Groovy project or as a mixed Java and Groovy project. Next, we’ll explore Gradle’s support for Scala projects.
Scala is another language running on the JVM that has become increasingly popular in the past few years. The language is statically typed, combines object-oriented programming with support for functional programming, and is designed to express logic in an elegant, concise way. As with Groovy, Scala can use all Java libraries that a Java developer already knows. Scala has seen quite a bit of real-world adoption. Twitter reimplemented their backend services with Scala, and Foursquare moved on to Scala as well.
Scala support in Gradle is as sophisticated as the support for Groovy. In this section, we’ll explore the corresponding Scala language plugins. You’ll take an approach similar to what you did for Groovy to transform parts of your To Do application to Scala and build them with Gradle.
The Scala plugins are designed with the same concepts in mind as the Groovy plugins. The basic idea is to separate capabilities from conventions. In figure 11.5, you can see that Gradle provides two Scala plugins.
The Scala base plugin automatically applies the Java base plugin. You learned before that the Java plugin provides the capabilities for building Java-based projects. The Scala base plugin uses these capabilities and preconfigures default conventions for Scala projects, as shown in the following list:
The Scala plugin internally applies not only the Scala base plugin, but also the Java plugin. This allows you to build Java and Scala source code within a single project. The feature the Scala plugin provides on top of the Scala base plugin is the ability to generate HTML API documentation for Scala source code. If you’re building a full-fledged Scala project, applying the Scala plugin is your best choice. Let’s see Scala in action.
To demonstrate the use of Scala within your To Do application, you’ll convert a Java class into a Scala class. The project in your build with the least complex source code is the model project. At the moment, this project’s build.gradle file doesn’t define any logic. The following listing applies the Scala plugin and declares the Scala library to get a hold on the Scala compiler.
After applying the Scala plugin, your project can compile any Scala source code found in the directories src/main/scala and src/test/scala. Figure 11.6 shows the groovy source set directories alongside the source directory conventions introduced by the Java plugin.
Next, you’ll transform the existing Java file ToDoItem.java into a Scala file. Every Scala source code file has the extension .scala. Go ahead and rename the file and move it under the directory src/main/scala. It’s time to change the source code to Scala, as shown in the next listing.
package com.manning.gia.todo.model class ToDoItem extends Ordered[ToDoItem] { var id: Long = _ var name: String = _ var completed: Boolean = _ def getId: Long = { id } def setId(id: Long) = { this.id = id } ... override def compare(that: ToDoItem) = this.id compare that.id override def equals(that: Any) = { that match { case t: ToDoItem => t.id == id && t.name == name && t.completed == completed case _ => false } } }
Even though you may not be well versed in Scala, the language syntax is relatively easy to decipher. In fact, the class doesn’t introduce any additional logic and works exactly like its Java counterpart. Executing the build for the model project produces the following output:
$ gradle :model:build :model:compileJava UP-TO-DATE :model:compileScala Download http://repo1.maven.org/maven2/org/scala-lang/scala-library/ 2.10.1/scala-library-2.10.1.pom Download http://repo1.maven.org/maven2/org/scala-lang/scala-library/ 2.10.1/scala-library-2.10.1.jar Download http://repo1.maven.org/maven2/org/scala-lang/scala-compiler/ 2.10.1/scala-compiler-2.10.1.pom Download http://repo1.maven.org/maven2/org/scala-lang/scala-reflect/ 2.10.1/scala-reflect-2.10.1.pom Download http://repo1.maven.org/maven2/org/scala-lang/scala-compiler/ 2.10.1/scala-compiler-2.10.1.jar Download http://repo1.maven.org/maven2/org/scala-lang/scala-reflect/ 2.10.1/scala-reflect-2.10.1.jar :model:processResources UP-TO-DATE :model:classes :model:jar :model:assemble :model:compileTestJava UP-TO-DATE :model:compileTestScala UP-TO-DATE :model:processTestResources UP-TO-DATE :model:testClasses UP-TO-DATE :model:test :model:check :model:build
We can see that the compileScala task initiates the download of the Scala library to be able to invoke the Scala compiler. As a result, your project compiles the Scala code, which can be used as a dependency for other projects in your build.
If you’re unhappy with the default Scala source directories, feel free to redefine them. By now, you’ve used this concept various times, so the code in the next listing should come as no surprise. In this example, you’ll point the production source directory to src/scala and the test source directory to test/scala.
sourceSets { main { scala { srcDirs = ['src/scala'] } } test { scala { srcDirs = ['test/scala'] } } }
Mixing Java and Scala source code within one Gradle project follows the same rules as for mixing Java and Groovy. What it boils down to is that Scala can depend on Java, but Java can’t depend on Scala. Bidirectional dependencies between these source code file types can only be achieved by joint compilation. You can either put the Java code into the Scala source directory (by default, src/main/scala) or reconfigure the source directory sets, as shown in the following listing.
What happens when you configure Scala joint compilation? Joint compilation for Scala code works slightly differently than for Groovy code. The Scala compiler doesn’t directly invoke the Java compiler. The following steps should make this clear:
1. The Scala compiler parses and analyzes the Scala source code to figure out the dependencies on Java classes. The Scala compiler understands Java syntax, but doesn’t invoke the Java compiler.
2. The Scala sources are compiled. The compiler also produces a class file for each Java source file. However, these files don’t contain bytecode. They’re used for type checking between Java and Scala sources.
3. The Java compiler compiles the Java source code.
We covered the most basic skills required to build Scala projects. Optimizations like incremental compilation can reduce the compilation time even more. Please see the online documentation for more details. In the next section, we’ll go over the Gradle compilation and packaging support for some other languages, JVM-based and native.
There are far more languages out there than we could possibly discuss in this book. Some of these languages are directly supported by a core plugin; others can be integrated with your build through a plugin developed by the Gradle community. Table 11.1 lists some of the more popular language plugins.
Language Homepage |
Gradle Plugin |
|
---|---|---|
C++ | http://www.cplusplus.com | Gradle core plugin |
Clojure | http://clojure.org | https://bitbucket.org/clojuresque/clojuresque |
Golo | http://golo-lang.org | https://github.com/golo-lang/gradle-golo-plugin |
Kotlin | http://kotlin.jetbrains.org | http://repository.jetbrains.com/kotlin/org/jetbrains/kotlin/kotlin-gradle-plugin |
R | http://www.programmingr.com | https://github.com/jamiefolson/gradle-plugin-r |
Even if you don’t find an appropriate plugin for the language you’re trying to incorporate into your build, you’re not out of luck. You learned that Ant tasks and Java APIs can be wrapped with a Gradle task. The same technique is used by the Groovy plugin, for example, which internally invokes the groovyc Ant task. Alternatively, you can always execute a compiler from the command line by creating an enhanced task of type Exec.
Today’s software projects embrace a wide range of programming languages, technologies, and libraries. It’s all about the right tool for the job. Whatever works best, increases productivity, or solves a problem in a more elegant way should be preferred. This maxim became increasingly important for projects that incorporate more than a single programming language, so-called polyglot projects. In this chapter, we discussed how to configure, manage, and build three different languages with Gradle: JavaScript and the two JVM-based languages, Groovy and Scala.
JavaScript has been the dominant language for creating dynamic web experiences for over a decade. Obviously, page-loading times influenced by the size of the JavaScript files play a significant role for end users. JavaScript files can be merged and minified to improve the page rendering performance. You learned that Gradle can help automate otherwise manual steps to perform these actions and integrate them with the development lifecycle of your To Do application. We didn’t stop there. We also explored how to simplify the required configuration of your JavaScript build code by using the community JavaScript plugin. Later, you wrapped existing Grunt build code with Gradle to provide a single, unified control unit for automating all components of your application.
The JVM-based languages Groovy and Scala are directly supported within a Gradle build. Gradle ships with first-class plugin support for both languages. Each language plugin builds on top of the Java base plugin to enforce a separation of capabilities from conventions. You transformed some of the existing To Do application Java classes into Groovy and Scala equivalents to demonstrate the use of these plugins. Source set conventions aren’t set in stone. You learned how to reconfigure them in case your project needs to adapt to a legacy project layout. Groovy and Scala source code can coexist with Java source code in a single project. Bidirectional dependencies between Java and Groovy or Scala require the use of joint compilation. We discussed how to prepare the compiler for handling such a scenario. In the last part of this chapter, we touched on other programming languages supported by Gradle.
The next chapter will focus on measuring the code quality of your project by integrating external tooling into the build process.
3.144.17.45