Integrating Closure Tools with Maven

Apache Maven is a popular build management tool that we use at Scott Logic to handle our internal projects. When I began using the Closure Tools, integrating the Compiler into the Maven build process became an important task.

Having the compilation of code in the build process assures that the code will be built correctly both at development and release time, as well as ensuring repeatability in the build. We also chose to integrate Closure Linter into the build process to ensure adherence to style. This blog article will aim to show you one technique for including Closure compilation and style checking into your Maven project.

Closure Compiler

Let's imagine we want to include the following builder configuration into Maven:

python closurebuilder.py --root=js --namespace=foo.bar --output_mode=compiled /
--compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" /
--compiler_jar=compiler.jar --output_file=compiled.js

Maven Integration

Integrating an executable with Maven is simple using the Maven Exec Plugin. However, the tricky part comes with requiring all the source files in one location to compile.

It's to be assumed that you've been using the war plugin to package your web project. The war:war goal will explode all the required files in a target directory, and then package them into a WAR file. The uncompressed files remain in the target directory after the WAR has been created, and we will use these exploded files to compile the code.

The goal is to have all the JavaScript source files in the Maven directory before creating the final WAR file, so we can compile them. We do this by adding a war:war goal to the prepare-package phase. This goal explodes all the required source into the target directory, and allows us to compile, before the actual packaging is done in the package phase.

In its simplest form, the phases are as follows:

  • [prepare-package(war:war)] Explode the WAR file into the target directory, then create an empty WAR file from the files just exploded.
  • [prepare-package(exec:exec)] Compile the exploded files that were left behind by the war goal of the prepare-package phase.
  • [package(war:war)] Create a WAR with the compiled source in it, and no other JavaScript sources.

This can get a little complicated, so I've put together a small table showing the phases:

Phase Goal Configuration Description
prepare-package war:war Creating a WAR archive will explode all files into the target directory. This is how we get all the files we want to compile.
only include JavaScript source files We only want JavaScript source files - don't include any other files
exclude everything from archive Maven will automatically create an archive from the exploded directory. Although this is the goal, we aren't interested in the war file (we only want the contents of the exploded directory). Don't include anything in the war to save processing.
exec:exec The exec goal is what will execute the compiler python script.
output to target folder Ensure that the compiled file is output to the target folder, so when we package, we can include it.
package war:war Now that we have the compiled file, we can create the WAR archive.
exclude all JavaScript sources except the compiled file Only include the compiled file (no other JS sources) as they are contained within the compiled file.

Here's the XML that reflects this:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.1-beta-1</version>
  <executions>
    <execution>
      <id>write-deps</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>war</goal>
      </goals>
      <configuration>
        <!-- Make sure to include only the JavaScript source files -->
        <warSourceIncludes>**/*.js</warSourceIncludes>
        <dependencyWarIncludes>**/*.js</dependencyWarIncludes>
        <!-- Exclude everything from the final WAR file, as we won't use it -->
        <packagingExcludes>**/*</packagingExcludes>
      </configuration>
    </execution>
    <execution>
      <id>default-war</id>
      <phase>package</phase>
      <goals>
        <goal>war</goal>
      </goals>
      <configuration>
        <!-- This is the final WAR package.
          Exclude all the JavaScript sources except the compiled file -->
        <packagingExcludes>js/*/**/*.js</packagingExcludes>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.1.1</version>
  <executions>
    <execution>
      <id>resolve-and-compile-js</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>exec</goal>
      </goals>
      <configuration>
        <!-- We're running a python script, so the executable is python. -->
        <executable>python</executable>
        <workingDirectory>${project.build.directory}/${project.build.finalName}</workingDirectory>
        <!-- Here we define the arguments to the Python execution.
          This includes the location of closure-builder,
          and any other arguments to the Python script -->
        <arguments>
          <!-- Remember that you're in the BUILD directory.
            Make sure this points to the correct place. -->
          <argument>../closure-builder.py</argument>
          <!-- Ensure this points to the root of all your JS files,
            including any dependencies that may have been pulled in.
            Everything in this directory will be compiled. -->
          <argument>--root=js</argument>
          <argument>--namespace="foo.bar"</argument>
          <argument>--output_mode=compiled</argument>
          <argument>--compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS"</argument>
          <!-- As with closure-builder, ensure that this points to
            the Compiler .jar file from the build directory. -->
          <argument>--compiler_jar=../compiler.jar</argument>
          <argument>--output_file=compiled.js</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

war:exploded

Note that we could have used war:exploded in the prepare-package phase as the packaged war file isn't required (war:exploded does the job of war:war but without the last step, where the exploded files are added to a war archive).

However, war:exploded doesn't have the same optimization of ignoring dependency changes as war:war, so while it would work the build would be much slower.

Dependencies

It's better practice in Maven to keep individual modules separate, so that you can build against specific versions and therefore improve the repeatability and reliability of your builds. For this reason, you probably won't want to have your application and the Closure Library in the same project, which can be achieved by using a War overlay.

As long as both projects are war packaged, the war goal will explode all files into the same directory. For example, your structure may be similar to the example below:

As you can see, the target directory in myproject has combined the js folders from both projects, so giving the Compiler access to the js folder will include all JavaScript source.

Closure Linter

As another step, you may wish to include a Closure Linter pass in your build process. This is done a very similar way, albeit with fewer complications. Using the same Exec Plugin (remember your dependency!) we can add another execution to the prepare-package phase as such:

<execution>
  <id>check-style</id>
  <phase>prepare-package</phase>
  <goals>
    <goal>exec</goal>
  </goals>
  <configuration>
    <!-- We aren't using Python anymore; if you've followed the instructions from Google,
      gjslint should be runnable from the command line. -->
    <executable>gjslint</executable>
    <workingDirectory>${project.build.directory}/${project.build.finalName}</workingDirectory>
    <arguments>
      <!-- Set your arguments here -->
      <argument>--strict</argument>
      <argument>-r</argument>
      <!-- Each source file in this directory will be checked. -->
      <argument>js/scottlogic</argument>
    </arguments>
  </configuration>
</execution>

The build will then output the results of the style check to the standard output. Of course, if you wanted to use fixjsstyle too, this would only require changing the executable tag.

Conclusion

Hopefully this article has explained the method of integrating the Closure Compiler into your build process. Whilst in theory it doesn't sound like a complicated task, the management of files between different projects can be quite complex.

The main goal of this article was to introduce the method of exploding the required files into one directory, manipulating the files, and then creating the final WAR.

Hopefully I've provided a framework for working with the Closure Tools and Maven and that, in theory, different executables and Maven goals can be applied to the source files (jsdoc-toolkit and JSLint as examples) before they are packaged into the final WAR.

blog comments powered by Disqus