Building Software With Lua via Tup, For Fun

Tup is my favorite program for building software, which I talked about years ago. Tup comes with its own language for writing the “rules” to build your projects, but recent versions also allow you to write your build scripts in Lua. I’ve found this to be nice when using Tup on a Lua-based project, as there is a pleasant feeling of consistency to use the same language for building your software and for that software itself. So today I felt like showing you what it’s like to use Tup in conjunction with Lua, using a real-world example.

A Simple Introduction

When you run Tup it will look for “Tupfiles”, files which contain the “rules” that tell Tup how to build your software. As I said, you can use Tup’s own domain-specific language for these files. Or you can use Lua by creating a Tupfile.lua script in the top-level directory of your project.

Tup provides access to some of Lua’s standard library, such as the string and io functions. But it also provides a tup table which contains most of what you’ll use to create your build rules. The most commonly used function in that table, in my experience, is the appropriately named tup.rule(). The function’s signature is tup.rule(inputs, command, outputs), although the command parameter is the only one required. So here is a simple example:

tup.rule({"main.lua", "./src/", "./libs/"}, "luacheck %f")

This will cause Tup to run the following whenever any of those inputs change:

luacheck main.lua ./src/ ./libs/

Often you’ll write rules which take input and create output files. You must list all output files in a table, even if the rule only creates a single file. For example, I use rules like this to run Exuberant Ctags in order to create a TAGS file for use with Emacs:

local sources = {
    [[^ Creating TAGS^ ctags-exuberant -e --languages=lua %f]],

Since Ctags creates the TAGS file Tup requires that we list in the output table. But this example demonstrates how you can create tables of inputs, which you can then use for multiple rules. This is the equivalent to defining variables in Tup’s own language.

Speaking of tables, Tup provides the function tup_append_assignment(a, b) for combining the contents of two tables, e.g.

sources = tup_append_assignment(sources, {"./libs/"})

Alternatively you can use the new += operator that Tup implements, which behaves much like += in Tup’s domain-specific language, allowing you to rewrite the above like so:

sources += {"./libs/"}

In Tup you can define macros. For example, here is one I use for this very blog, which runs Pandoc to convert my articles from Markdown to HTML:

!pandoc = |> ^ Writing %o^ pandoc %f -f markdown -t html5 -o %o |> %B.html

There is no special syntax for writing Tup macros when writing your build rules in Lua. Instead you simply define the macro like a normal Lua function. So I could rewrite the above like so:

function run_pandoc(inputs)
    tup.frule {
        inputs = inputs,
        command = "^ Writing %o^ pandoc %f -f markdown -t html5 -o %o",
        outputs = {"%B.html"}

-- An example use:
run_pandoc {"drafts/Tools/Emacs/*.md"}

This example shows the low-level tup.frule() function which is the foundation for others like tup.rule() and tup.foreach_rule().


If you’re interested in writing build files in Lua with Tup then you should check out the full details on Tup’s site. And if you want to see a complete real-world example then you can see the Tup file I use to build LNVL. As I said at the start, personally I find it fun to use Lua for building software which I’m already writing in Lua. If you’re also a fan of the language and/or Tup then you may enjoy it too.

Add Your Thoughts

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

You are commenting using your 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 )

Google+ photo

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

Connecting to %s