Tutorial 1¶
In this tutorial, we will create a Rabs script that updates a generated file whenever a corresponding input file is modified.
Hello world¶
Create a new directory called tutorial
and inside it create a file called build.rabs
with the following content:
1:< ROOT >:
2
3print("Hello world!\n")
Run rabs
in the directory, which will give the following output (with /tutorial replaced by the full path of your directory):
$ rabs
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 1
Hello world!
Note that rabs
automatically loads and runs the build.rabs
file. The first line is a comment since it starts with :<
. Although comments are generally ignored by rabs
, a :< ROOT >:
comment on the first line of a build.rabs
serves as a special marker.
Note
The :< ROOT >:
line is needed to tell rabs
that this is the root directory of our project. Without it, rabs
will search the parent directories until it finds a build.rabs
file that does start with :< ROOT >:
. If we omit this line and do not have a suitable build.rabs
in any parent directory, we get an error:
$ rabs
Looking for library path at /usr/lib/rabs
Error: could not find project root
After this, the build.rabs
contains one other line: print("Hello world!n")
which predictably causes rabs
to print "Hello world!"
to the console.
Build Iterations¶
If we run rabs
again, we get almost the same output:
$ rabs
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 2
Hello world!
The only difference is the build iteration has increased from 1 to 2. This means that rabs
knows that this is the second time it has been run in this directory. rabs
does this by storing information each time it is run in a directory called build.rabs.db
, creating this directory if it does not yet exist. If we list the files in the directory, we see this new directory.
$ ls
build.rabs build.rabs.db/
The Default Target¶
So far, each time we run rabs
, the same output is produced with the exception of the build iteration. However, the main purpose of rabs
is as an incremental build system, i.e. it should run code as needed to update a project, and not run unnecessary code. To do this, we define targets that rabs
will update when necessary. There are many types of target, but the simplest is a meta target, which is defined only by its name. rabs
automatically defines a default target, accessible as DEFAULT
, which is updated whenever rabs
is run.
If we run rabs
with the additional argument -s, we can see how it checks and updates the DEFAULT
target.
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 3
Hello world!
1 / 1 #0 Updated meta:::DEFAULT to iteration 1
Note that even though the build iteration is now 3 (or possibly higher if you ran rabs
a few more times), the DEFAULT
target has only been updated to iteration 1. This is because nothing has changed since the first time rabs
was run.
Build Functions¶
Update the build.rabs
file to look as follows:
1:< ROOT >:
2
3print("Hello world!\n")
4
5DEFAULT => fun() print("Building DEFAULT\n")
This change sets a build function for the DEFAULT
target to a function that prints out a single string "Building DEFAULT"
. Since DEFAULT
is a meta target, its build function doesn't need to do anything specific such as creating a file or returning a value.
Running rabs -s
again produces the following output:
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 4
Hello world!
Building DEFAULT
1 / 1 #0 Updated meta:::DEFAULT to iteration 4
We see the message Building DEFAULT and the DEFAULT
target has been updated to match the build iteration.
If we try running rabs -s
again, we'll get different output:
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 5
Hello world!
1 / 1 #0 Updated meta:::DEFAULT to iteration 4
This time, the build function for DEFAULT
is not executed, and the target is not updated again. Like earlier, since no change was made, rabs
does not need to run the build function for DEFAULT
again.
Try changing the build function:
1:< ROOT >:
2
3print("Hello world!\n")
4
5DEFAULT => fun() print("Building DEFAULT again\n")
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 6
Hello world!
Building DEFAULT again
1 / 1 #0 Updated meta:::DEFAULT to iteration 6
rabs
detects that the build function has changed and runs it again, as expected. Running rabs
again after this will be similar to earlier.
Note
rabs
detects any functional changes in a build function such as added or removed code or different constant values. Comments and formatting do not count as changes.
Targets and Dependencies¶
rabs
predefines the DEFAULT
target, but other targets can be created in the build.rabs
script. Change build.rabs
to contain the following:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST")
6Test => fun() print("Building TEST\n")
7
8DEFAULT => fun() print("Building DEFAULT again\n")
This defines a new meta target, called TEST
. However running rabs -s
will not display Building TEST and TEST
will not be updated (or even displayed).
In order for TEST
to be built, we need to make one more change:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST")
6Test => fun() print("Building TEST\n")
7
8DEFAULT[Test]
9DEFAULT => fun() print("Building DEFAULT again\n")
The line DEFAULT[TEST]
adds TEST
as a dependency of DEFAULT
. This causes 2 things:
TEST
must be built beforeDEFAULT
andwhenever
TEST
changes,DEFAULT
will be rebuilt.
Running rabs -s
shows us this in action:
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 11
Hello world!
Building TEST
1 / 2 #0 Updated meta:::TEST to iteration 11
Updating due to meta:::TEST
Building DEFAULT again
2 / 2 #0 Updated meta:::DEFAULT to iteration 11
Not only is the build function for TEST
executed, the build function for DEFAULT
is also executed again. If we change the build function for TEST
, both it and DEFAULT
will be rebuilt.
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST")
6Test => fun() print("Building TEST again\n")
7
8DEFAULT[Test]
9DEFAULT => fun() print("Building DEFAULT again\n")
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 12
Hello world!
Building TEST again
1 / 2 #0 Updated meta:::TEST to iteration 12
Updating due to meta:::TEST
Building DEFAULT again
2 / 2 #0 Updated meta:::DEFAULT to iteration 12
Note
Some targets (e.g. file targets), are considered unchanged even if their build functions was run in an iteration. This happens if the contents / value of a target has not changed despite changes to its build function or dependencies. Since meta targets have no contents or value, they are always considered changed if their build function or any of their dependencies change.
Shorter Syntax¶
Our current script describes build functions (using Target => Function
) and dependencies (using Target[Dependency]
). Both of these operations return the target itself, so we can combine them on one line:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST") => fun() print("Building TEST again\n")
6
7DEFAULT[Test] => fun() print("Building DEFAULT again\n")
File Targets¶
Now that we can create and update meta targets, it's time to move on to the most useful type of target in rabs
, file targets. These correspond to files (or directories) on disk. As such, they have contents, which are read when checking if a file has changed.
Add a few more lines to build.rabs
:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST") => fun() print("Building TEST again\n")
6
7DEFAULT[Test] => fun() print("Building DEFAULT again\n")
8
9var Test2 := file("test.txt") => fun(Target) do
10 var File := Target:open("w")
11 File:write("Hello world!\n")
12 File:close
13end
14
15DEFAULT[Test2]
Running rabs -s
again creates the file test.txt
with the expected content:
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 13
Hello world!
1 / 3 #0 Updated file:test.txt to iteration 13
2 / 3 #0 Updated meta:::TEST to iteration 12
Updating due to file:test.txt
Building DEFAULT again
3 / 3 #0 Updated meta:::DEFAULT to iteration 13
$ ls
build.rabs build.rabs.db/ test.txt
$ cat test.txt
Hello world!
Notice that in this example, we added Test2
as a dependency of DEFAULT
on its own line. We could include in the same line as the Test
dependency as below:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST") => fun() print("Building TEST again\n")
6
7var Test2 := file("test.txt") => fun(Target) do
8 var File := Target:open("w")
9 File:write("Hello world!\n")
10 File:close
11end
12
13DEFAULT[Test, Test2] => fun() print("Building DEFAULT again\n")
In this example, the test.txt
target has no dependencies so it will only be rebuilt if we change its build function, or if the file itself is deleted fromt the disk:
$ rm test.txt
$ cat test.txt
cat: test.txt: No such file or directory
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 14
Hello world!
1 / 3 #0 Updated file:test.txt to iteration 13
2 / 3 #0 Updated meta:::TEST to iteration 12
3 / 3 #0 Updated meta:::DEFAULT to iteration 13
$ cat test.txt
Hello world!
Here we get to see how file targets are checked for changes. Despite rebuilding test.txt
, its updated iteration was not increased since its contents did not change since that last build. Like the last build iteration and information about build functions, this information is stored in the build.rabs.db
directory. Finally, rabs
does not rebuild DEFAULT
despite its dependency on test.txt
.
Changing Dependencies¶
Lets add two more targets to the build.rabs
script:
1:< ROOT >:
2
3print("Hello world!\n")
4
5var Test := meta("TEST") => fun() print("Building TEST again\n")
6
7var Test2 := file("test.txt") => fun(Target) do
8 var File := Target:open("w")
9 File:write("Hello world!\n")
10 File:close
11end
12
13DEFAULT[Test, Test2] => fun() print("Building DEFAULT again\n")
14
15var Input := file("input.txt")
16var Output := file("output.txt")[Input] => fun(Target) do
17 execute('cp {Input} {Output}')
18end
19
20DEFAULT[Output]
We are introducing two new features here, using an external file target and calling a shell command. Running rabs
gives us an error:
$ rabs -s
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 24
Hello world!
Error: rule failed to build: /tutorial/input.txt
rabs
needs to check the file input.txt
before it can build output.txt
, but this file does not exist and it has no build function. So rabs
complains that the (nil
) build function for input.txt
failed to build the file. This error will also occur for any file target which has a build function if that build function fails to build the expected file.
Create input.txt
with some text, and run rabs. This time add the extra option -c
when running rabs.
$ echo 1 > input.txt
$ rabs -s -c
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 25
Hello world!
1 / 5 #0 Updated file:input.txt to iteration 25
2 / 5 #0 Updated file:test.txt to iteration 13
Updating due to file:input.txt
/tutorial: cp /tutorial/input.txt /tutorial/output.txt
0.000101 seconds.
3 / 5 #0 Updated file:output.txt to iteration 25
4 / 5 #0 Updated meta:::TEST to iteration 12
Updating due to file:output.txt
Building DEFAULT again
5 / 5 #0 Updated meta:::DEFAULT to iteration 25
$ cat output.txt
1
The extra -c
option shows us any shell commands that rabs
runs, along with the time taken. In this case rabs
uses cp to copy input.txt
to output.txt
(rabs
has its own built in functions for copying files, we used cp here as an example).
If we change the contents of input.txt
, rabs
will do its job and rebuild output.txt
:
$ echo 2 > input.txt
$ rabs -s -c
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.5.4
Build iteration = 28
Hello world!
1 / 5 #0 Updated file:input.txt to iteration 28
2 / 5 #0 Updated file:test.txt to iteration 13
Updating due to file:input.txt
/tutorial: cp /tutorial/input.txt /tutorial/output.txt
0.000101 seconds.
3 / 5 #0 Updated file:output.txt to iteration 28
4 / 5 #0 Updated meta:::TEST to iteration 12
Updating due to file:output.txt
Building DEFAULT again
5 / 5 #0 Updated meta:::DEFAULT to iteration 28
$ cat output.txt
2
Now we know how to create and use meta targets, file targets, build functions and shell commands, we can move on to more advanced functionality in Tutorial 2.