# Hierarchical Builds

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'])
env = Environment() env.Program('prog1', ['main.c', 'foo1.c', 'foo2.c'])
env = Environment() env.Program('prog2', ['main.c', 'bar1.c', 'bar2.c'])
x
x
x
x
x
x

And subsidiary &SConscript; files that look like this:

And this:

Then, when we run &SCons; in the top-level directory, our build looks like:

scons -Q

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-Relative 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 prepending a &hash; (hash mark) in front of the path name:

SConscript('src/prog/SConscript')
env = Environment() env.Program('prog', ['main.c', '#lib/foo1.c', 'foo2.c'])
x
x
x

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

(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.)

A couple of notes on top-relative paths:

1. &SCons; doesn't care whether you add a slash after the &hash;. Some people consider `'#/lib/foo1.c'` more readable than `'#lib/foo1.c'`, but they're functionally equivalent.

2. The top-relative syntax is only evaluated by &SCons;, the &Python; language itself does not understand about it. This becomes immediately obvious if you like to use `print` for debugging, or write a Python function that wants to evaluate a path. You can force &SCons; to evaluate a top-relative path by creating a Node object from it:

path = "#/include" print("path =", path) print("force-interpreted path =", Entry(path))

Which shows:

scons -Q

## Absolute Path Names

Of course, you can always specify an absolute path name for a file--for example:

SConscript('src/prog/SConscript')
env = Environment() env.Program('prog', ['main.c', '__ROOT__/usr/joe/lib/foo1.c', 'foo2.c'])
x
x
x

Which, when executed, would yield:

scons -Q

(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 &f-link-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 an &SConscript; file so they can be imported by other &SConscript; files, thus allowing you to share common initialized values throughout your build hierarchy.

### Exporting Variables

There are two ways to export a variable from an &SConscript; file. The first way is to call the &f-link-Export; function. &Export; is pretty flexible - in the simplest form, you pass it a string that represents the name of the variable, and &Export; stores that with its value:

env = Environment() Export('env')

You may export more than one variable name at a time:

env = Environment() debug = ARGUMENTS['debug'] Export('env', 'debug')

Because a Python identifier cannot contain spaces, &Export; assumes a string containing spaces is is a shortcut for multiple variable names to export and splits it up for you:

env = Environment() debug = ARGUMENTS['debug'] Export('env debug')

You can also pass &Export; a dictionary of values. This form allows the opportunity to export a variable from the current scope under a different name - in this example, the value of `foo` is exported under the name `"bar"`:

env = Environment() foo = "FOO" args = {"env": env, "bar": foo} Export(args)

&Export; will also accept arguments in keyword style. This form adds the ability to create exported variables that have not actually been set locally in the SConscript file. When used this way, the key is the intended variable name, not a string representation as with the other forms:

Export(MODE="DEBUG", TARGET="arm")

The styles can be mixed, though Python function calling syntax requires all non-keyword arguments to precede any keyword arguments in the call.

The &Export; function adds the variables to a global location from which other &SConscript; files can import. Calls to &Export; are cumulative. When you call &Export; you are actually updating a Python dictionary, so it is fine to export a variable you have already exported, but when doing so, the previous value is lost.

The other way to export is you can specify a list of variables as a second argument to the &f-link-SConscript; function call:

SConscript('src/SConscript', 'env')

Or (preferably, for readability) using the &exports; keyword argument:

SConscript('src/SConscript', exports='env')

These calls export the specified variables to only the listed &SConscript; file(s). You may 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 &f-link-Import; function:

Import('env') env.Program('prog', ['prog.c'])

The &Import; call makes the previously defined `env` variable available to the &SConscript; file. Assuming `env` is a &consenv;, after import it can be used to build programs, libraries, etc. The use case of passing around a &consenv; is extremely common in larger &scons; builds.

Like the &Export; function, the &Import; function can be called with multiple variable names:

Import('env', 'debug') env = env.Clone(DEBUG=debug) env.Program('prog', ['prog.c'])

In this example, we pull in the common &consenv; `env`, and use the value of the `debug` variable to make a modified copy by passing that to a &f-link-Clone; call.

The &Import; function will (like &Export;) split a string containing white-space into separate variable names:

Import('env debug') env = env.Clone(DEBUG=debug) env.Program('prog', ['prog.c'])

&Import; prefers a local definition to a global one, so that if there is a global export of `foo`, and the calling SConscript has exported `foo` to this SConscript, the import will find the `foo` exported to this SConscript.

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 up to date 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 object files built by several subsidiary &SConscript; files. You can do this by using the &f-link-Return; function to return values from the subsidiary &SConscript; files to the calling file. Like &Import; and &Export;, &Return; takes a string representation of the variable name, not the variable name itself.

If, for example, we have two subdirectories &foo; and &bar; that should each contribute an object 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)
Import('env') obj = env.Object('foo.c') Return('obj')
Import('env') obj = env.Object('bar.c') Return('obj')
void foo(void) { printf("foo/foo.c\n"); }
void bar(void) { printf("bar/bar.c\n"); }

We can do this by using the &Return; function in the `foo/SConscript` file like this:

(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