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 %f -o %o', 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.luafunction compile(source, output) tup.definerule{ inputs = {source}, command = 'gcc %f -o %o', 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 %f -o %o', outputs = {output} } endhello/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.ini lives, or where you ran tup 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 %f -o %o', outputs = {output} } end if tup.getconfig('MODULE1') == 'y' then tup.include('module1.lua') end if tup.getconfig('MODULE2') == 'y' then tup.include('module2.lua') endtup.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'}. Note that using %f/%o is preferred if you intend to use variants.
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 %f -o %o -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.