Bu is designed to be the absolute easiest build system to use ever. So this tutorial will just jump in.
We are going to write a hello world build script. So first, create a file called build.bu which will be our build script. This is the default name for a build script.
Our build script will have a single target, which will be a shell script. We know that the shell script would be something like:
echo Hello World
In bu, we need to give this target a name, in our case hello, followed by a ‘:’ and a new line, and so we write in our script:
hello:
echo Hello World
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:hello) ======================================
!sh {}
1 echo Hello World
> 0
1 Hello World
] hello
======================================= [bu 0.1] Ends (<string>:hello) =======================================
|
We can now execute this using:
bu hello
And we get some output:
=== build starts [hello]
--- !sh
> 1 echo Hello World
--- 0
< 1 Hello World
=== build ends [hello]
If you didn’t bother to create the file, you can execute a similar example in the source code with:
bu -f examples/hello.bu hello
Here the -f parameter specifies a script file name that overrides the default build.bu.
In our Hello World example, we execute a single action, which is of unspecified type, and so bu treats it as a shell action. Bu has two types of action, shell, and python. To use multiple or different kinds of action for a target, each must have its type explicitly specified.
If we want to have a target that prints Hello World twice, we would do:
hello_twice:
echo Hello World
echo Hello World
output:
1 2 3 4 5 6 7 8 9 10 11 | =================================== [bu 0.1] Starts (<string>:hello_twice) ===================================
!sh {}
1 echo Hello World
2 echo Hello World
> 0
1 Hello World
2 Hello World
] hello_twice
==================================== [bu 0.1] Ends (<string>:hello_twice) ====================================
|
Here, the entire command is executed in the shell as a single action. If we were to have two separate actions performing this, we would use the explicit action notation, ! followed by the action type and any options:
hello:
!sh
echo Hello World
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:hello) ======================================
!sh {}
1 echo Hello World
> 0
1 Hello World
] hello
======================================= [bu 0.1] Ends (<string>:hello) =======================================
|
This is identical to our original Hello World example, except the action type is specified as !sh.
Multiple actions can exist in a single target like so:
hello_split:
!sh
echo Hello
!sh
echo World
output:
1 2 3 4 5 6 7 8 9 10 11 12 13 | =================================== [bu 0.1] Starts (<string>:hello_split) ===================================
!sh {}
1 echo Hello
> 0
1 Hello
!sh {}
1 echo World
> 0
1 World
] hello_split
==================================== [bu 0.1] Ends (<string>:hello_split) ====================================
|
Actions may be python scripts instead of shell scripts, by using the !py action type:
hello_python:
!py
print 'Hello World'
output:
1 2 3 4 5 6 7 8 9 | ================================== [bu 0.1] Starts (<string>:hello_python) ===================================
!py {}
1 print 'Hello World'
> 0
1 Hello World
] hello_python
=================================== [bu 0.1] Ends (<string>:hello_python) ====================================
|
An action may be a link to another target in the same build script, which will cause a dependency. Reference actions are defined by using !! operator with the target name, for example:
hello:
echo Hello
world:
echo World
hello_world:
@hello
@world
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:hello) ======================================
!sh {}
1 echo Hello
> 0
1 Hello
] hello
======================================= [bu 0.1] Ends (<string>:hello) =======================================
|
This script creates a hello_world target that is dependent on first the hello target, and second the world target:
=== build starts [hello_world]
--- !sh
> 1 echo Hello
--- 0
< 1 Hello
--- !sh
> 1 echo World
--- 0
< 1 World
=== build ends [hello_world]
All targets and actions can have options. The namespaces for each action are therefore a combination of the parent namespaces of the target and the script. The script itself can have global options which are also passed down.
All options in the namespaces are pushed into 2 places:
- The pre-processor namespace
- The action executor
The action executors handling of the namespace is different for each different type. Python scripts have every option available in the namespace, shell scripts have every option available as an environment variable.
In addition options may be specified to modify the behaviour of the actions. In all cases the namespace is inherited down through each level.
The script can have global options (ones which will appear in the namespace of every action), by using the !opt reference before defining any targets:
!opt build_dir=build
build:
mkdir $build_dir
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:build) ======================================
!sh {}
1 mkdir $build_dir
> 1
1 mkdir: cannot create directory `build': File exists
] build
======================================= [bu 0.1] Ends (<string>:build) =======================================
|
As you can see, the variable is available to the shell command as an environment variable.
Similarly an option can be used by the preprocessor, like so:
!opt build_dir=build
build:
mkdir {{ build_dir }}
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:build) ======================================
!sh {}
1 mkdir build
> 1
1 mkdir: cannot create directory `build': File exists
] build
======================================= [bu 0.1] Ends (<string>:build) =======================================
|
The mkdir action will be pre-processed before being passed to the shell and the option interpolated.
Target options are specified as key value pares after the target name. Thes options are inherited by all the actions in the target:
build build_dir=build:
mkdir $build_dir
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:build) ======================================
!sh {}
1 mkdir $build_dir
> 1
1 mkdir: cannot create directory `build': File exists
] build
======================================= [bu 0.1] Ends (<string>:build) =======================================
|
Because targets can be called from other targets, the options can be overridden, like so:
build build_dir=build:
mkdir $build_dir
build_different:
!!build build_dir=build2
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:build) ======================================
!sh {}
1 mkdir $build_dir
> 1
1 mkdir: cannot create directory `build': File exists
] build
======================================= [bu 0.1] Ends (<string>:build) =======================================
|
Here, the build_dir passed in in the build_different target overrides the build_dir passed in the original build target for options when included.
Action options are usually passed to modify the behaviour of an action. An example is the ssh option to cause any action to be performed remotely:
hello:
!sh ssh=pseudoscience.co.uk
echo hello
output:
1 2 3 4 5 6 7 8 9 | ====================================== [bu 0.1] Starts (<string>:hello) ======================================
!sh {ssh=pseudoscience.co.uk}
1 echo hello
> 0
1 hello
] hello
======================================= [bu 0.1] Ends (<string>:hello) =======================================
|
This will perform the action on the remote machine at ssh pida.co.uk.
Of course, since action options inherit from their parents, if you have multiple actions to perform over ssh, this might be easier:
hello ssh=pseudoscience.co.uk:
!sh
echo hello
!py
print 'Hello'
output:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ====================================== [bu 0.1] Starts (<string>:hello) ======================================
!sh {}
1 echo hello
> 0
1 hello
!py {}
1 print 'Hello'
> 0
1 Hello
] hello
======================================= [bu 0.1] Ends (<string>:hello) =======================================
|
Where all actions will be performed remotely over ssh.
All commands are jinja2 templates, and so can be generated during a pre-processor stage, to use for loops, tests etc. in the context of the actual command creation, where the DOM is available:
hello_many:
{% for i in range(5) %}
echo Hello World
{% endfor %}
output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | =================================== [bu 0.1] Starts (<string>:hello_many) ====================================
!sh {}
1
2 echo Hello World
3
4 echo Hello World
5
6 echo Hello World
7
8 echo Hello World
9
10 echo Hello World
> 0
1 Hello World
2 Hello World
3 Hello World
4 Hello World
5 Hello World
] hello_many
==================================== [bu 0.1] Ends (<string>:hello_many) =====================================
|
Which would generate a shell script as the output, and execute like so:
=== build starts [hello_many]
--- !sh
> 1 {% for i in range(5) %}
> 2 echo Hello World
> 3 {% endfor %}
--- 0
< 1 Hello World
< 2 Hello World
< 3 Hello World
< 4 Hello World
< 5 Hello World
=== build ends [hello_many]
Option values are also subject to the preprocessor, but in their parent’s namespace. Thus, global options can be used to assign child options:
!opt build_dir=build
build build_dir={{build}}:
mkdir $build_dir
output:
1 2 3 4 5 6 7 8 9 10 | ====================================== [bu 0.1] Starts (<string>:build) ======================================
!sh {}
1 mkdir $build_dir
> 1
1 mkdir: missing operand
2 Try `mkdir --help' for more information.
] build
======================================= [bu 0.1] Ends (<string>:build) =======================================
|