Tutorial 2

Recap

We have created a root build.rabs script, and defined some meta and file targets. We added some build functions and dependencies. We've also seen how rabs builds or rebuilds targets when necessary, and skips targets otherwise. In this tutorial, we will introduce some more target types in rabs.

Nested Directories

So far, we have only used one build.rabs script in a single directory. Larger projects are usually organized in multiple, nested directories. rabs provides a number of built-in functions and features for dealing with complex project structures, some of which we will cover here.

To start, create a new directory called src within the tutorial directory created in the previous tutorial. This is where we will put source code files for our tutorial project. Inside the new src, create another file called build.rabs with the following content:

1print("Hello from a subdirectory!\n")
2
3DEFAULT => fun print("Building DEFAULT in src\n")

Note that this build.rabs file does not need to start with :< ROOT >:.

Note

Nested build.rabs files may also start with :< ROOT >: without problems. In fact, this is useful for nested projects (using a git submodule for example) which can be built both independently or as part of a larger project.

Finally, add one more line to the build.rabs file in the root tutorial directory:

 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
20subdir("src")
21
22DEFAULT[Output]

The final directory structure should look like this:

  • 🖹 build.rabs
  • 📁 src
    • 🖹 build.rabs

Run rabs as before (in the root tutorial directory).

$ rabs -s -c
Looking for library path at /usr/lib/rabs
RootPath = /tutorial
Building in /tutorial
Rabs version = 2.11.0
Build iteration = 33
Hello world!
Hello from a subdirectory!
1 / 6 #0 Updated file:input.txt to iteration 1
2 / 6 #0 Updated meta:::TEST to iteration 1
3 / 6 #0 Updated file:output.txt to iteration 1
4 / 6 #0 Updated file:test.txt to iteration 1
Building DEFAULT in src
5 / 6 #0 Updated meta:/src::DEFAULT to iteration 33
   Updating due to meta:/src::DEFAULT
Building DEFAULT again
6 / 6 #0 Updated meta:::DEFAULT to iteration 33

When rabs runs the subdir("src") function, it loads and runs the file src/build.rabs. It also automatically creates a new meta target called DEFAULT specific to the src directory, and adds it as a dependency of DEFAULT in the root directory.

Contexts

Now we have multiple targets, defined in multiple build.rabs files located in different directories. In most programming languages, definitions in different files are kept seperate except through module imports or similar mechanisms. Since rabs is designed to simplify building large nested projects, definitions in build.rabs files are automatically made available to build.rabs in nested directories.

Similarly, build.rabs files in nested directories can affect (add or update) definitions in their parent directories. As a result, rabs defines the concept of a context when running code. Typically, a context is associated with the directory of the current build.rabs file. Each context has a single parent context, typically the context of the parent directory. It is however possible to create multiple contexts within a single directory, which we will see in a later tutorial.

Symbol Targets

Build instructions are often parameterised:

  1. Various flags or options are passed to external programs such as compilers.

  2. The same project can be built in different configurations by passing options to the build program (in this case rabs).

rabs provides symbol targets to store and retrieve values containing parameters, flags, options, etc. Symbols are created by assigning to an identifier which has not been declared as a variable.

1:< ROOT >:
2
3var Variable := "value"
4
5Symbol := "value"
6
7print('Variable = ', Variable, '\n')
8print('Symbol = ', Symbol, '\n')

Here, Variable is a normal variable and Symbol is a symbol. Both have been assigned the same value, "value" and can be used in code by their identifiers. Although symbols are similar to variables in many ways, they also have a number of extra properties:

  • Symbols are tracked automatically as dependencies when used in a build function. If the value of a symbol is changed during a subsequent build, rabs will rebuild any targets whose build functions used the symbol.

  • Symbols are inherited by contexts, and can be overridden (redefined) in a context without affecting the parent context.

RootPath = /home/raja/Work/Rabs/work/tutorial1
Building in /home/raja/Work/Rabs/work/tutorial1
Rabs version = 2.19.5
Build iteration = 1
Hello world!
Hello from a subdirectory!
1 / 6 #0 Updated file:input.txt to iteration 1
Building TEST again
2 / 6 #0 Updated meta:::TEST to iteration 1
/home/raja/Work/Rabs/work/tutorial1: cp /home/raja/Work/Rabs/work/tutorial1/input.txt /home/raja/Work/Rabs/work/tutorial1/output.txt
   0.000474 seconds.
3 / 6 #0 Updated file:output.txt to iteration 1
4 / 6 #0 Updated file:test.txt to iteration 1
Building DEFAULT in src
5 / 6 #0 Updated meta:/src::DEFAULT to iteration 1
Building DEFAULT again
6 / 6 #0 Updated meta:::DEFAULT to iteration 1