Lua Examples

In these examples, we will present the basic Lua Tupfile API and show how it relates to several small projects, then give an introduction to some of the helper API methods. A reference for all Lua API methods can be found here - not all API methods are demonstrated in the examples below.

Compiling a Single Program

In this example, we will write a Lua Tupfile that compiles a small C program. The two key requirements for using Tup are 1. running tup init at the top of the project, and 2. Tupfile.lua.

In a new directory, run:

$ tup init

and create the following files in the same directory:

hello.c
#include <stdio.h>

int main(void)
{
	printf("Hello, world!\n");
	return 0;
}
Tupfile.lua
tup.definerule{ inputs = {'hello.c'}, command = 'gcc hello.c -o hello', outputs = {'hello'} }

Now, everything is prepared to compile. The current directory structure looks like this:

$ ls -a
.  ..  hello.c  .tup  Tupfile.lua

Compile the project by running:

$ tup
[ tup ] [0.134s] Scanning filesystem...
[ tup ] [0.252s] Reading in new environment variables...
[ tup ] [0.369s] Parsing Tupfiles...
 1) [0.008s] .
 [ ] 100%
[ tup ] [0.388s] No files to delete.
[ tup ] [0.388s] Generating .gitignore files...
[ tup ] [0.530s] Executing Commands...
 1) [0.095s] gcc hello.c -o hello
 [ ] 100%
[ tup ] [0.740s] Updated.
$ ls -a
.  ..  hello  hello.c  .tup  Tupfile.lua
$ ./hello
Hello, world!

Compiling Multiple Programs

In this example, we will build off the previous project to build the similar program goodbye in addition to hello, using Lua functions to generalize build rule definition.

Create the following file in the same directory as the previous example:

goodbye.c
#include <stdio.h>

int main(void)
{
	printf("Goodbye, world!\n");
	return 0;
}

Replace the rules file with this new one:

Tupfile.lua
function compile(source, output)
	tup.definerule{ inputs = {source}, command = 'gcc ' .. source .. ' -o ' .. output, outputs = {output} }
end

compile('hello.c', 'hello')
compile('goodbye.c', 'goodbye')

The current directory structure looks like this:

$ ls -a
.  ..  goodbye.c  hello  hello.c  .tup  Tupfile.lua

Build and test the programs:

$ tup
[ tup ] [0.000s] Scanning filesystem...
[ tup ] [0.252s] Reading in new environment variables...
[ tup ] [0.253s] Parsing Tupfiles...
 1) [0.006s] .
 [ ] 100%
[ tup ] [0.264s] No files to delete.
[ tup ] [0.264s] Generating .gitignore files...
[ tup ] [0.509s] Executing Commands...
 1) [0.071s] gcc goodbye.c -o goodbye
 [ ] 100%
[ tup ] [0.788s] Updated.
$ ls -a
.  ..  goodbye  goodbye.c  hello  hello.c  .tup  Tupfile.lua
$ ./hello
Hello, world!
$ ./goodbye
Goodbye, world!

Note that (if you copied everything verbatim) the command to build hello didn't change, nor did any of its dependencies, so Tup didn't rebuild it.

Working With Multiple Directories

In this example, we place and build the program source files in their own directories for organizational purposes. We will use Tuprules.lua to store our common compile method to avoid duplicating it in each build directory.

Create directories and move the source files to get the following project structure:

$ rm hello goodbye # They are in the way of the next steps
$ rm Tupfile.lua
$ mkdir hello
$ mkdir goodbye
$ mv hello.c hello/
$ mv goodbye.c goodbye/
Tuprules.lua
function compile(source, output)
	tup.definerule{ inputs = {source}, command = 'gcc ' .. source .. ' -o ' .. output, outputs = {output} }
end
hello/Tupfile.lua
compile('hello.c', 'hello')
goodbye/Tupfile.lua
compile('goodbye.c', 'goodbye')

The final directory structure should look like this:

$ ls -a
.  ..  goodbye  hello  .tup  Tuprules.lua
$ ls -a hello
.  ..  hello.c  Tupfile.lua
$ ls -a goodbye
.  ..  goodbye.c  Tupfile.lua

Tupfile.lua automatically includes Tuprules.lua in its current directory and parent directories, so we can access compile in hello/Tupfile.lua and goodbye/Tupfile.lua.

Build and test the programs. You can run tup from any location under the root of your project (where the Tupfile.initup init), but for the following listing we run everything in the top directory.

$ tup
[ tup ] [0.000s] Scanning filesystem...
[ tup ] [0.286s] Reading in new environment variables...
[ tup ] [0.287s] Parsing Tupfiles...
 1) [0.001s] .
 2) [0.001s] goodbye
 3) [0.001s] hello
 [   ] 100%
[ tup ] [0.296s] No files to delete.
[ tup ] [0.296s] Deleting 2 commands...
 [  ] 100%
[ tup ] [0.297s] Generating .gitignore files...
[ tup ] [0.532s] Executing Commands...
 1) [0.107s] goodbye: gcc goodbye.c -o goodbye
 2) [0.111s] hello: gcc hello.c -o hello
 [  ] 100%
[ tup ] [0.877s] Updated.
$ ls -a
.  ..  goodbye  hello  .tup  Tuprules.lua
$ ls -a hello/
.  ..  hello  hello.c  Tupfile.lua
$ ls -a goodbye/
.  ..  goodbye  goodbye.c  Tupfile.lua
$ ./hello/hello
Hello, world!
$ ./goodbye/goodbye
Goodbye, world!

Configuration And Selectively Sharing Code

You can split Lua Tupfiles as you see fit and selectively combine them using tup.include. In this example, we read the tup.config file to determine which modules to build, and only include the module's build definitions if enabled.

In a new directory, run:

$ tup init

and create the following files in the same directory:

module1.c
#include <stdio.h>

int main(void)
{
	printf("Module 1 run.\n");
	return 0;
}
module2.c
#include <stdio.h>

int main(void)
{
	printf("Module 2 run.\n");
	return 0;
}
module1.lua
compile('module1.c', 'module1')
module2.lua
compile('module2.c', 'module2')
Tupfile.lua
function compile(source, output)
	tup.definerule{ inputs = {source}, command = 'gcc ' .. source .. ' -o ' .. output, outputs = {output} }
end

if tup.getconfig('MODULE1') == 'y' then
	tup.include('module1.lua')
end

if tup.getconfig('MODULE2') == 'y' then
	tup.include('module2.lua')
end

tup.config
CONFIG_MODULE1=n
CONFIG_MODULE2=y

This will cause the module 2 build rule to be included and built, but not module 1. Note that tup.getconfig returns an empty string if the specified config value is missing. The way Tupfile.lua is set up, all modules are disabled by default.

The directory structure should look like this:

$ ls -a
.  ..  module1.c  module1.lua  module2.c  module2.lua  .tup  tup.config  Tupfile.lua

As usual, build and test with the following:

$ tup
[ tup ] [0.298s] Scanning filesystem...
[ tup ] [0.917s] Reading in new configuration/environment variables...
 1) new variant: tup.config
 [ ] 100%
[ tup ] [2.435s] Parsing Tupfiles...
 1) [0.007s] .
 [ ] 100%
[ tup ] [2.442s] No files to delete.
[ tup ] [2.443s] Generating .gitignore files...
[ tup ] [3.755s] Executing Commands...
 1) [0.071s] gcc module2.c -o module2
 [ ] 100%
[ tup ] [4.283s] Updated.
$ ./module1
bash: ./module1: No such file or directory
$ ./module2
Module 2 run.

Helper API Methods

The following are not complete examples, but snippets and descriptions of their results when run.


out = tup.frule{inputs = {'hello.c'}, command = 'gcc %f -o %o', outputs = {'%B'}}
out = tup.frule{input = 'hello.c', command = 'gcc %f -o %o', output = '%B'}
out = tup.rule('hello.c', 'gcc %f -o %o', '%B')

All of these are equivalent to tup.definerule{inputs = {'hello.c'}, command = 'gcc hello.c -o hello', outputs = {'hello'}}. out is {'hello'}.


tup.frule{inputs = {'file1.c', 'file2.c'}, command = 'gcc %f -o %o', output = 'app'}

This is equivalent to tup.definerule{inputs = {'file1.c', 'file2.c'}, command = 'gcc file1.c file2.c -o app', outputs = {'app'}}.


header = tup.rule('./generateheader.sh', {'generatedheader.h'})
objects = tup.foreach_rule(
	{
		'file1.c',
		'file2.c',
		extra_inputs = header
	},
	'gcc %f -c -o %o',
	{'%B.o'}
)
tup.rule(objects, 'gcc %f -o %o', {'app'})

This is equivalent to:

tup.definerule{
	inputs = {'file1.c', 'generatedheader.h'},
	command = 'gcc file1.c -c -o file1.o',
	outputs = {'file1.o'}
}
tup.definerule{
	inputs = {'file2.c', 'generatedheader.h'},
	command = 'gcc file2.c -c -o file2.o',
	outputs = {'file2.o'}
}
tup.definerule{
	inputs = {'file1.o', 'file2.o'},
	command = 'gcc file1.o file2.o -c -o app',
	outputs = {'app'}
}

Environment Variables And Globbing

Like the previous section, the following are not complete examples, but snippets and descriptions of their results when run.

tup.export('SDK_PREFIX')
tup.rule(tup.glob('*.c'), 'gcc %f -o %o -L$SDK_PREFIX/lib -lsdklib', {'app'})

In a directory with the files file1.c and file2.c, this is equivalent to:

tup.definerule{inputs = {'file1.c', 'file2.c'}, command = 'gcc file1.c file2.c -o app -L$SDK_PREFIX -lsdklib', outputs = {'app'}}

Note that $SDK_PREFIX is expanded by the shell and is treated the same as any other command substring by Tup. tup.export simply passes the value in the shell that invokes Tup to the shell that Tup invokes.