BENJAMINWINTERBERG

Writing Nashorn Command-Line Scripts with Nake

Nake is a simplified version of Make (Cake, Jake, Rake) for Java 8 based on the Nashorn Javascript Engine. I wrote Nake to automate common tasks in our daily project business here at Pondus. You define tasks in a project-specific Nakefile and call those tasks from the command line. Tasks are written in Javascript empowered by the JDK 8 API and your preferred external java libraries. You don't have to mess with native shell scripts any more.

Backed by a lot of code examples this article demonstrates how to write command-line scripts with the Nashorn Javascript Engine and how to use Nake on top of Nashorn to automate common tasks in your Java projects.

Hello Nake

In order to use Nake on your local machine just follow the installation instructions. When everything's up and running, create a Nakefile in your favorite projects root directory with the following content:

task("hello", "Say Hello", function() {
  print("Hello, World!");
});

Open your terminal of choice, cd into the project directory and type:

nake -- hello

As you see writing Nake tasks is quite easy and straight forward. Nake is a small javascript file which runs in Nashorns scripting mode, thus enabling you to directly use the native terminal commands of your local machine. Let's see what the scripting mode is all about.

Nashorn Scripting

The Nashorn command-line tool jjs can be used in a special scripting mode via jjs -scripting. Scripting mode extends the Nashorn javascript dialect by a handful of variables and functions to work with the native terminal commands of your shell.

You can execute native commands from your terminal with the function $EXEC. Results from the last command written to stdout can be accessed via $OUT.

$EXEC("ls -l");
print($OUT);

Alternatively you can use the literal syntax to execute commands:

`ls -l`

The $EXEC function accepts a second string parameter to be used as standard input (stdin) for the command:

$EXEC("cat", "Hello World!");
print($OUT);     // Hello World!

In order to execute commands in a specific directory, first set the environment variable PWD to the desired path:

$ENV("PWD") = "/path/to/my/dir";
print(`ls -l`);

The variable $ENV can also be used to access environment variables of your system:

print($ENV.USER);  // prints my username

Similar to $OUT the variable $ERR contains everything written to stderr from the last executed command:

$EXEC("ls -l");
if ($ERR) print($ERR);

String interpolation via ${...} can be used for every string defined with double quotes:

var name = 'Peter';
print("Hello, ${name}!");  // Hello, Peter!

You can pass arguments when invoking your script via jjs -scripting script.js -- arg1 arg2. Those arguments can be accessed via $ARG:

print($ARG);     // arg1, arg2
print($ARG[0]);  // arg1

The function readLine reads user input from the command-line:

var name = readLine("Who's there? ");
print("Hello, ${name}!");

Finally, you can exit your script by calling the exit function with an optional exit code:

print("over and out");
exit(0);

Read here for more information about Nashorn shell scripting.

Nake Examples

Nake is applicable for every JVM based project. All you need is Java 8 pre-installed on your machine. Here's a few example tasks:

Run your Java application with Nake by invoking the main method of your program:

task("run", "Run my java app", function() {
  $ENV['PWD'] = "out";  // root dir of .class files
  print(`java com/mycompany/somepackage/Main`);
  if ($ERR) print($ERR);
});

Execute the task from your terminal:

nake -- run

The next example task shows how to compile all Java files from any sub-directory of your project. A more complex example of how to watch and recompile Java files automatically can be found here.

task("compile", "Compile all java files", function() {
  var src = "src";  // root dir of .java files
  var out = "out";  // root dir of .class files

  var found = $EXEC("find ${src} -name *.java");
  var paths = found.trim().split("\n");
  var join = java.lang.String.join(" ", paths);

  print("compiling ${paths.length} java files to dir: ${out}");

  $EXEC("mkdir -p ${out}");    // create dir if not exists
  $EXEC("javac -d ${out} ${join}");

  if ($ERR) print("[ERROR] ${$ERR}");
});

Execute the task from your terminal:

nake -- compile

Nake is not meant to replace existing build tools. Instead use any of your existing command-line tools in conjunction with Nake. The next example demonstrates how to use Nake in a Maven + Git project. The task first performs a maven deploy, then creates a git tag and pushes the tag to the remote repository:

task("deploy", "Deploy and tag project", function(options) {
  var tagName = options[0]
  if (!tagName) {
    print("usage error: provide a tag name as first arg");
    exit(1);
  }

  print("deploying project");

  `mvn deploy`

  if ($ERR) {
    print($ERR);
    exit(1);
  }

  print("tagging project: ${tagName}");

  `git tag -a "${tagName}" -m "${tagName}"`
  `git push origin --tags`
});

Execute the task from your terminal:

nake -- deploy

A more sophisticated task would parse the actual Maven project version from the pom.xml and use this as tag name instead of the argument.

Keep in mind that you can execute Nake tasks from any sub-directory of your project. Typing nake prints all available tasks defined in your Nakefile:

$ nake
Tasks defined in /path/to/project/Nakefile

   run       Run my java app
   compile   Compile all java files
   deploy    Deploy and tag project

use 'nake -- taskName' to run a specific task

Conclusion

Using Nashorn Javascript for automating common tasks in Java projects is very comfortable and straight forward. You don't have to mess around with shell scripts. Instead you use the same language, APIs and libraries as your project. You find many examples of how to use the Nashorn javascript dialect in my Java 8 Nashorn Tutorial.

Nake is a lightweight utility - a convention of how to write project-specific tasks with Java 8 Nashorn. Nake is completely open source and hosted on GitHub. Feel free to fork the repository or send me your Feedback via Twitter.

Happy Easter!