Build with SCons
SCons is touted as an easier, more reliable and faster way to build software with in-built support for multiple languages and an easily extensible user-defined builder system.
Let’s talk briefly about build tools so as to understand the role SCons plays in the build process.
Build tools are described as programs that automate the creation of executable applications from source files.
Build tools help ease this process by tracking the following processes and items: build requirements, build sequence, and project depencencies hence making the build process consistent.
Currently build tools are split into:
- Build automation utilities
- such as CMake, Ant, sbt
- Build automation servers
- which are essentially web-based and run builds on trigger or schedule basis. Example TravisCI
The above can be further subdivided into other broader sub-categories.
Next, let’s take a brief look at a typical build process. In this case we’ll look at a simple C program build process.
The application source code is as below:
In this initial stage the pre-processor joins continued lines and removes comments. Preprocessor commands such as
are interpreted to form a macro language. Symbolic constant are then evaluated to their defined values example
Let’s inspect the result of this process by executing the following command
gcc -E <filename.c> -o <filename.i>
For such a simple program, you’ll notice a very massive expanded file.
However, of interest, you should notice the following section in the intermediate
.i expanded source code file.
int main(int argc, char const *argv)
You’ll notice that our macro
UNIVERSAL_CONSTANT has been substituted with
42 and all comments in the code have been stripped off.
In this this stage, the expanded source code file is taken as input and translated into assembly instructions for the target processor architecture by the compiler .
Let’s see this in action by running the following command
gcc -S <filename.c> -o <filename.s>
In our example application the output will be as shown :
In this stage the assembler takes the intermediate assembly code as input and translates it into an object file that can be run by the target processor in a format known as relocatable object program .
Note that most of the functions will not be resolved and will only be resolved at a later stage.
Let’s have a look at this stage by running the following commands:
gcc <filename.c> -c <filename.o>
The output will be as shown (using
hedxump utility in GNU/Linux Ubuntu)
When we try to read the file contents via
cat we get the following output
From the above output, not much can be derived other than our expected output
Well the answer is %i.
In this stage, the object code will be re-arranged by the linker to facilitate function calls. The linker will also resolve various function calls from libraries and plug(merge) them into the program.
The linker will handle instructions for setting up the running environment like parsing command line arguments, environment variables and return values. The object code is finally converted into an executable by the linker.
Let’s inpect this process by running the following command
You should now get an executable
a.out file, which you can run by executing the following command
Understanding of machine-level code and compiler translation processes enables one to make good decisions on how to write efficient code.
In building large applications, understanding the linker process can be of great use in solving some of the most complex link-time errors.
Understanding how data is read and in which format helps in avoiding gaping security holes commonly present in most applications.
You could have generated all intermediate files by running
gcc -save-temps <filename.c>
You can also name the final artifact by passing the
-o flag to
gcc -o <executablename> <filename.c>
The above processes can be visualized as below
sequenceDiagram Preprocessor->>Compiler: Expanded source code Compiler->>Assembler: Assembler file Assembler->>Linker: Object file Linker->>Executable: Executable file
Now that we have an idea of the build process, lets dive into SCons
As of the time of writing this article the latest version is
The procedure is pretty simple : Create a directory to install SCons and download the latest version and install
sudo mkdir -p /opt/src &&\
sudo apt-get install scons -y
sudo pacman -S scons
SCons will search for a SConstruct or Sconstruct or sconstruct file to fetch build configurations.
Because SConstruct files are treated as normal Python files, one can leverage Python’s scripting capabilities to handle complex build processes.
Let’s start by developing a simple application that generates fractal images in PPM format.
The application structure is a shown as below:
We’ll begin with a barebones SConstruct configuration.
In this instance we’re instructing SCons to build a
mandelbrot library and also passing the library source files
lib/mandelbrot.cc. Then we build our application whose source files are located inside
src/app.c by linking it to our library.
Then inside the project directory we run SCons
Inside your project directory you should now have a tree similar to this
You’ll notice that we now have an executable
app inside our
src/ directory which can be launched from the commandline by executing
Now let’s clean up the the output and try something different. Run the following command:
In this configuration we’ll define the output directory for our executable: in this case the output will be inside the
scons command results in to the creation of an executable inside the
Now we can clean the result by running the
scons -c command.
But you’ll notice that the
dist folder will remain after the clean command is run. We can add a custom
Clean() method that will remove specified folders and directories. The
Clean() expects :
- Target as the first argument which is the return value of the command which creates the file
- File/Directory which is the file or directory you want removed
Now, the SConstruct will look like this
This can be useful if the build command generates log files or certain artifacts that you might later want to discard.
Suppose now you’d like to allow your users to supply custom build options.
In the above instance you can see we practically invoked Python to read OS Information. This can be useful if you want to call native platform build or disable builds for certain platforms.
We’re also letting the user know that they can pass
scons -Q IMAGETYPE=PNG to build the app with PNG support. Also note that
IMAGETYPE is set to
PPM by default. See the
Now when a user types
scons -h from the project directory they should see the following (GNU/Linux example):
Now, we have a
MACROin our sample application that will generate a PNG image when
IMAGETYPE=PNGis passed in the compilation flags.
SCons offers a provision to check for the existence specific header files in the the system, as well as programs, functions and datatypes.
This is facilitated by the
Configure(arg) function :
conf = Configure(env)
if not conf.CheckCHeader('stdlib.h'):
For C++ the above check can be modified as follows
if not conf.CheckCXXHeader('math.h'):
if not conf.CheckLib('png'):
if not conf.CheckProg('xterm'):
One can also write custom checks. Suppose we’d like to check for
assert.h without relying on SCons’ inbuilt checks:
test_assert = """
Note that the
TryLink(arg1, arg2) generates an
arg2 filetype - eg.
.c - to run the check.
You’ll notice that a
.sconf_temp directory is created during these tests and in one of the files the following lines can be found
When run, the resulting output is like shown below
The complete SConstruct file for this blog is as given below; change to suite your application build requirements
Get up and runing with SCons without installing it in your PC. Follow through with this Katacoda scenario to get started.
Happy Building! :)
Build with SCons