Automating GNU/Lilypond with Grunt

One of the very nice things about GNU/LilyPond, is its command line only interface. This offers the possibility to set up highly customized work flows. Below I’m describing mine, using vim as a text editor (but you can use any editor, really), and grunt as a helper that watches file changes and gives specific commands to LilyPond.

You can grab a template, containing all the required configuration files and a sample directory structure, from my Bitbucket repo.

Installing grunt

Grunt will be responsible for the automation stuff. The tool depends on Node, so let’s install that first if you don’t have a copy already.

curl -sL | bash -
apt-get install nodejs

Once Node.js is installed, we have access to npm, its package manager. With npm, we can install grunt, which will be our task runner.

npm i -g grunt-cli

Setting up a directory structure

My setup uses the following structure, allowing for changing specific settings and modules whenever needed.

The main file is, which references/includes files from the parts and config directories. Any output will be created in the out directory.

Configuring grunt

Now for grunt to do its magic, we need to create two files in the root of our project (i.e. on the same level as

First we create package.json, which will hold basic information on our project, as well as a list of grunt modules.

    "name": "lilypond-project",
    "version": "0.1.0"

Next, we use npm again to install two grunt modules. One offers a way to interact with the shell, and a second one will watch any file changes and trigger commands if a file change occurs.

npm i --saveDev grunt-bg-shell
npm i --saveDev grunt-contrib-watch

Note the --saveDev flag. This argument will put the name and version of the installed module in our package.json file.

    "name": "lilypond-project",
    "version": "0.1.0",
    "devDependencies": {
        "grunt-bg-shell": "^2.3.1",
        "grunt-contrib-watch": "^0.6.1"

Now we have the necessary grunt modules in place, let’s make our Gruntfile.js. We configure the two grunt modules: we tell watch which files to watch (in our case: only files ending with *ly), and which action must be triggered on change, and we define those actions in bgShell (in my case, I provide two options for LilyPond output: svg or pdf).

The file is quite self explanatory:

'use strict';

module.exports = function(grunt) {

    var default = ['bgShell:compile', 'watch'];
    var svg = ['bgShell:compileSvg'];


        watch: {
        files: ['**/*ly'],
        tasks: ['bgShell:compile']

        bgShell: {

        _defaults: {
            bg: false,
            stdout: true,
            stderr: true

        compile: {
            cmd: 'lilypond -o out/'

        compileSvg: {
            cmd: 'lilypond -o out/ -dbackend=svg'


    grunt.registerTask('default', default);
    grunt.registerTask('svg', svg);

Putting grunt to work

Now everything is configured, it suffices to fire up grunt in a terminal, and edit some files:

cd my-project-dir/

As you’ll see every time you’re saving changes in the watched directory, grunt will perform the tasks we defined in the Gruntfile. That’s some nice automation!

Using a PDF viewer with auto-refresh will give you continuous feedback on every LilyPond file save. If I would like an SVG file instead of my default PDF output, I can now just type grunt svg in my terminal, and an SVG file will appear in my out directory.