The source code for large software projects
rarely stays in a single directory,
but is nearly always divided into a
hierarchy of directories.
Organizing a large software build using &SCons;
involves creating a hierarchy of build scripts
using the &SConscript; function.
&SConscript; Files
As we've already seen,
the build script at the top of the tree is called &SConstruct;.
The top-level &SConstruct; file can
use the &SConscript; function to
include other subsidiary scripts in the build.
These subsidiary scripts can, in turn,
use the &SConscript; function
to include still other scripts in the build.
By convention, these subsidiary scripts are usually
named &SConscript;.
For example, a top-level &SConstruct; file might
arrange for four subsidiary scripts to be included
in the build as follows:
SConscript(['drivers/display/SConscript',
'drivers/mouse/SConscript',
'parser/SConscript',
'utilities/SConscript'])
In this case, the &SConstruct; file
lists all of the &SConscript; files in the build explicitly.
(Note, however, that not every directory in the tree
necessarily has an &SConscript; file.)
Alternatively, the drivers
subdirectory might contain an intermediate
&SConscript; file,
in which case the &SConscript; call in
the top-level &SConstruct; file
would look like:
SConscript(['drivers/SConscript',
'parser/SConscript',
'utilities/SConscript'])
And the subsidiary &SConscript; file in the
drivers subdirectory
would look like:
SConscript(['display/SConscript',
'mouse/SConscript'])
Whether you list all of the &SConscript; files in the
top-level &SConstruct; file,
or place a subsidiary &SConscript; file in
intervening directories,
or use some mix of the two schemes,
is up to you and the needs of your software.
Path Names Are Relative to the &SConscript; Directory
Subsidiary &SConscript; files make it easy to create a build
hierarchy because all of the file and directory names
in a subsidiary &SConscript; files are interpreted
relative to the directory in which the &SConscript; file lives.
Typically, this allows the &SConscript; file containing the
instructions to build a target file
to live in the same directory as the source files
from which the target will be built,
making it easy to update how the software is built
whenever files are added or deleted
(or other changes are made).
For example, suppose we want to build two programs
&prog1; and &prog2; in two separate directories
with the same names as the programs.
One typical way to do this would be
with a top-level &SConstruct; file like this:
SConscript(['prog1/SConscript',
'prog2/SConscript'])
And subsidiary &SConscript; files that look like this:
env = Environment()
env.Program('prog1', ['main.c', 'foo1.c', 'foo2.c'])
And this:
env = Environment()
env.Program('prog2', ['main.c', 'bar1.c', 'bar2.c'])
Then, when we run &SCons; in the top-level directory,
our build looks like:
% scons -Q
cc -o prog1/foo1.o -c prog1/foo1.c
cc -o prog1/foo2.o -c prog1/foo2.c
cc -o prog1/main.o -c prog1/main.c
cc -o prog1/prog1 prog1/main.o prog1/foo1.o prog1/foo2.o
cc -o prog2/bar1.o -c prog2/bar1.c
cc -o prog2/bar2.o -c prog2/bar2.c
cc -o prog2/main.o -c prog2/main.c
cc -o prog2/prog2 prog2/main.o prog2/bar1.o prog2/bar2.o
Notice the following:
First, you can have files with the same names
in multiple directories, like main.c in the above example.
Second, unlike standard recursive use of &Make;,
&SCons; stays in the top-level directory
(where the &SConstruct; file lives)
and issues commands that use the path names
from the top-level directory to the
target and source files within the hierarchy.
Top-Level Path Names in Subsidiary &SConscript; Files
If you need to use a file from another directory,
it's sometimes more convenient to specify
the path to a file in another directory
from the top-level &SConstruct; directory,
even when you're using that file in
a subsidiary &SConscript; file in a subdirectory.
You can tell &SCons; to interpret a path name
as relative to the top-level &SConstruct; directory,
not the local directory of the &SConscript; file,
by appending a &hash; (hash mark)
to the beginning of the path name:
env = Environment()
env.Program('prog', ['main.c', '#lib/foo1.c', 'foo2.c'])
In this example,
the lib directory is
directly underneath the top-level &SConstruct; directory.
If the above &SConscript; file is in a subdirectory
named src/prog,
the output would look like:
% scons -Q
cc -o lib/foo1.o -c lib/foo1.c
cc -o src/prog/foo2.o -c src/prog/foo2.c
cc -o src/prog/main.o -c src/prog/main.c
cc -o src/prog/prog src/prog/main.o lib/foo1.o src/prog/foo2.o
(Notice that the lib/foo1.o object file
is built in the same directory as its source file.
See , below,
for information about
how to build the object file in a different subdirectory.)
Absolute Path Names
Of course, you can always specify
an absolute path name for a file--for example:
env = Environment()
env.Program('prog', ['main.c', '/usr/joe/lib/foo1.c', 'foo2.c'])
Which, when executed, would yield:
% scons -Q
cc -o src/prog/foo2.o -c src/prog/foo2.c
cc -o src/prog/main.o -c src/prog/main.c
cc -o /usr/joe/lib/foo1.o -c /usr/joe/lib/foo1.c
cc -o src/prog/prog src/prog/main.o /usr/joe/lib/foo1.o src/prog/foo2.o
(As was the case with top-relative path names,
notice that the /usr/joe/lib/foo1.o object file
is built in the same directory as its source file.
See , below,
for information about
how to build the object file in a different subdirectory.)
Sharing Environments (and Other Variables) Between &SConscript; Files
In the previous example,
each of the subsidiary &SConscript; files
created its own construction environment
by calling &Environment; separately.
This obviously works fine,
but if each program must be built
with the same construction variables,
it's cumbersome and error-prone to initialize
separate construction environments
in the same way over and over in each subsidiary
&SConscript; file.
&SCons; supports the ability to export variables
from a parent &SConscript; file
to its subsidiary &SConscript; files,
which allows you to share common initialized
values throughout your build hierarchy.
Exporting Variables
There are two ways to export a variable,
such as a construction environment,
from an &SConscript; file,
so that it may be used by other &SConscript; files.
First, you can call the &Export;
function with a list of variables,
or a string of white-space separated variable names.
Each call to &Export; adds one
or more variables to a global list
of variables that are available for import
by other &SConscript; files.
env = Environment()
Export('env')
You may export more than one variable name at a time:
env = Environment()
debug = ARGUMENTS['debug']
Export('env', 'debug')
Because white space is not legal in Python variable names,
the &Export; function will even automatically split
a string into separate names for you:
Export('env debug')
Second, you can specify a list of
variables to export as a second argument
to the &SConscript; function call:
SConscript('src/SConscript', 'env')
Or as the &exports; keyword argument:
SConscript('src/SConscript', exports='env')
These calls export the specified variables
to only the listed &SConscript; files.
You may, however, specify more than one
&SConscript; file in a list:
SConscript(['src1/SConscript',
'src2/SConscript'], exports='env')
This is functionally equivalent to
calling the &SConscript; function
multiple times with the same &exports; argument,
one per &SConscript; file.
Importing Variables
Once a variable has been exported from a calling
&SConscript; file,
it may be used in other &SConscript; files
by calling the &Import; function:
Import('env')
env.Program('prog', ['prog.c'])
The &Import; call makes the env construction
environment available to the &SConscript; file,
after which the variable can be used to build
programs, libraries, etc.
Like the &Export; function,
the &Import; function can be used
with multiple variable names:
Import('env', 'debug')
env = env.Clone(DEBUG = debug)
env.Program('prog', ['prog.c'])
And the &Import; function will similarly
split a string along white-space
into separate variable names:
Import('env debug')
env = env.Clone(DEBUG = debug)
env.Program('prog', ['prog.c'])
Lastly, as a special case,
you may import all of the variables that
have been exported by supplying an asterisk
to the &Import; function:
Import('*')
env = env.Clone(DEBUG = debug)
env.Program('prog', ['prog.c'])
If you're dealing with a lot of &SConscript; files,
this can be a lot simpler than keeping
arbitrary lists of imported variables in each file.
Returning Values From an &SConscript; File
Sometimes, you would like to be able to
use information from a subsidiary
&SConscript; file in some way.
For example,
suppose that you want to create one
library from source files
scattered throughout a number
of subsidiary &SConscript; files.
You can do this by using the &Return;
function to return values
from the subsidiary &SConscript; files
to the calling file.
If, for example, we have two subdirectories
&foo; and &bar;
that should each contribute a source
file to a Library,
what we'd like to be able to do is
collect the object files
from the subsidiary &SConscript; calls
like this:
env = Environment()
Export('env')
objs = []
for subdir in ['foo', 'bar']:
o = SConscript('%s/SConscript' % subdir)
objs.append(o)
env.Library('prog', objs)
We can do this by using the &Return;
function in the
foo/SConscript file like this:
Import('env')
obj = env.Object('foo.c')
Return('obj')
(The corresponding
bar/SConscript
file should be pretty obvious.)
Then when we run &SCons;,
the object files from the subsidiary subdirectories
are all correctly archived in the desired library:
% scons -Q
cc -o bar/bar.o -c bar/bar.c
cc -o foo/foo.o -c foo/foo.c
ar rc libprog.a foo/foo.o bar/bar.o
ranlib libprog.a