Gradle Wrapper for cross-platform builds with Node.js

Like every other JavaScript project on Earth, our stuff uses node.js and npm modules to lint, standardizeunit test, document, and compress the source.  These tasks are executed by Grunt.  This type of workflow should be familiar to any experienced JavaScript developer. When developing on a team, the ideal project should require only 2 steps:

  1. check out the source
  2. run a single build command

Unfortunately most node-depdendent projects using grunt work like this:

  1. check out the source
  2. install node.js globally
  3. install grunt-cli globally
  4. run ‘npm install’ in your project directory
  5. run grunt (or whatever the flavor of the month is) in your project

This complexity is an issue for development teams, but it becomes a real problem when running continuous integration servers. Ideally build servers should compile projects with very few, if any, system dependencies.
Enter Gradle. Gradle is a build tool that runs in the JVM. Using Gradle and the Gradle wrapper, you can achieve that 2-step build ideal on any platform that has Java installed (which effectively translates to any developer’s machine on your team, and certainly any box running Jenkins CI server). You don’t need Gradle installed, either. The wrapper takes care of everything. What’s more, Jenkins (my build server of choice) has a Gradle plugin to make life even easier. With the Gradle wrapper, you run a single command to build a project:

$ ./gradlew

Now, the Gradle wrapper must be generated at some point and committed to the project’s repository. Please read the documentation. In short, you create a build file then run the ‘wrapper’ task once from the command line, and commit the results to the repository. To get this working for a JS project using Grunt, you need a file that configures the Grunt plugin and node.js options. Here you go:

plugins {
  id "com.moowork.grunt" version "0.10"

defaultTasks 'gruntBuildWithOpts'

node {
  // Version of node to use.
  version = '0.10.35'
  // Version of npm to use.
  npmVersion = '1.4.28'
  // Base URL for fetching node distributions
  distBaseUrl = ''
  // If true, it will download node using above parameters.
  // If false, it will try to use globally installed node.
  download = true
  // Set the work directory for unpacking node
  workDir = file("${project.projectDir}/nodejs")

grunt {
  // Set the directory where Gruntfile.js should be found
  workDir = file("${project.projectDir}")
  // Whether colors should output on the terminal
  colors = true
  // Whether output from Grunt should be buffered
  bufferOutput = false

//this task lets us pass command line arguments to Grunt. 
//the debug option is helpful with continuous integration issues.
//'npmInstall' is exposed through the grunt plugin.
task gruntBuildWithOpts(type: GruntTask, dependsOn:'npmInstall') {
  args = ["default", "--debug"]

task wrapper(type: Wrapper) {
  gradleVersion = '2.2.1'

Putting aside the wrapper subject for moment, you can run this build with the Gradle executable.

$ gradle

This will run the ‘default’ task in the build file. The build will check your environment, install the necessary node executable using the Gradle node plugin (that is a dependency of the Grunt plugin), run ‘npm install’ using the node plugin, then run the Grunt file using the Grunt plugin.

Back the wrapper. You must generate the wrapper and commit it to the repository for others to be able to build the project without a Gradle installation. You do this by runnng the ‘wrapper’ task, like so:

$ gradle wrapper

This will generate all the wrapper .jar files and scripts. Commit them to your repo then grab a beer, you earned it!
If you want to check this all out in action, I provide a demo project that you can run the wrapper in. Clone the following repo:
Once you have it on your system, cd to the project directory and run

$ ./gradlew

If you are on Windows, run

$ gradlew.bat

This post was triggered by my experience maintaining our Jenkins build servers, and our recent move from OS X to Windows hosting for the Jenkins instances. Gradle’s capabilities are far beyond the scope of this post, and it has a pretty steep learning curve. However, once you get it configured for a project it pays back in operational simplicity. The investment can save everyone gobs of time down the road.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s