29.3. What Dependencies Does SCons Know About? the --tree Option

Sometimes the best way to try to figure out what SCons is doing is simply to take a look at the dependency graph that it constructs based on your SConscript files. The --tree option will display all or part of the SCons dependency graph in an "ASCII art" graphical format that shows the dependency hierarchy.

For example, given the following input SConstruct file:

env = Environment(CPPPATH = ['.'])
env.Program('prog', ['f1.c', 'f2.c', 'f3.c'])
      

Running SCons with the --tree=all option yields:

% scons -Q --tree=all
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
+-.
  +-SConstruct
  +-f1.c
  +-f1.o
  | +-f1.c
  | +-inc.h
  +-f2.c
  +-f2.o
  | +-f2.c
  | +-inc.h
  +-f3.c
  +-f3.o
  | +-f3.c
  | +-inc.h
  +-inc.h
  +-prog
    +-f1.o
    | +-f1.c
    | +-inc.h
    +-f2.o
    | +-f2.c
    | +-inc.h
    +-f3.o
      +-f3.c
      +-inc.h

The tree will also be printed when the -n (no execute) option is used, which allows you to examine the dependency graph for a configuration without actually rebuilding anything in the tree.

By default SCons uses "ASCII art" to draw the tree. It is possible to use line-drawing characters (Unicode calls these Box Drawing) to make a nicer display. To do this, add the linedraw qualifier:

% scons -Q --tree=all,linedraw
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
└─┬.
  ├─SConstruct
  ├─f1.c
  ├─┬f1.o
  │ ├─f1.c
  │ └─inc.h
  ├─f2.c
  ├─┬f2.o
  │ ├─f2.c
  │ └─inc.h
  ├─f3.c
  ├─┬f3.o
  │ ├─f3.c
  │ └─inc.h
  ├─inc.h
  └─┬prog
    ├─┬f1.o
    │ ├─f1.c
    │ └─inc.h
    ├─┬f2.o
    │ ├─f2.c
    │ └─inc.h
    └─┬f3.o
      ├─f3.c
      └─inc.h

The --tree option only prints the dependency graph for the specified targets (or the default target(s) if none are specified on the command line). So if you specify a target like f2.o on the command line, the --tree option will only print the dependency graph for that file:

% scons -Q --tree=all f2.o
cc -o f2.o -c -I. f2.c
+-f2.o
  +-f2.c
  +-inc.h

This is, of course, useful for restricting the output from a very large build configuration to just a portion in which you're interested. Multiple targets are fine, in which case a tree will be printed for each specified target:

% scons -Q --tree=all f1.o f3.o
cc -o f1.o -c -I. f1.c
+-f1.o
  +-f1.c
  +-inc.h
cc -o f3.o -c -I. f3.c
+-f3.o
  +-f3.c
  +-inc.h

The status argument may be used to tell SCons to print status information about each file in the dependency graph:

% scons -Q --tree=status
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
 E         = exists
  R        = exists in repository only
   b       = implicit builder
   B       = explicit builder
    S      = side effect
     P     = precious
      A    = always build
       C   = current
        N  = no clean
         H = no cache

[E b      ]+-.
[E     C  ]  +-SConstruct
[E     C  ]  +-f1.c
[E B   C  ]  +-f1.o
[E     C  ]  | +-f1.c
[E     C  ]  | +-inc.h
[E     C  ]  +-f2.c
[E B   C  ]  +-f2.o
[E     C  ]  | +-f2.c
[E     C  ]  | +-inc.h
[E     C  ]  +-f3.c
[E B   C  ]  +-f3.o
[E     C  ]  | +-f3.c
[E     C  ]  | +-inc.h
[E     C  ]  +-inc.h
[E B   C  ]  +-prog
[E B   C  ]    +-f1.o
[E     C  ]    | +-f1.c
[E     C  ]    | +-inc.h
[E B   C  ]    +-f2.o
[E     C  ]    | +-f2.c
[E     C  ]    | +-inc.h
[E B   C  ]    +-f3.o
[E     C  ]      +-f3.c
[E     C  ]      +-inc.h

Note that --tree=all,status is equivalent; the all is assumed if only status is present. As an alternative to all, you can specify --tree=derived to have SCons only print derived targets in the tree output, skipping source files (like .c and .h files):

% scons -Q --tree=derived
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
+-.
  +-f1.o
  +-f2.o
  +-f3.o
  +-prog
    +-f1.o
    +-f2.o
    +-f3.o

You can use the status modifier with derived as well:

% scons -Q --tree=derived,status
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
 E         = exists
  R        = exists in repository only
   b       = implicit builder
   B       = explicit builder
    S      = side effect
     P     = precious
      A    = always build
       C   = current
        N  = no clean
         H = no cache

[E b      ]+-.
[E B   C  ]  +-f1.o
[E B   C  ]  +-f2.o
[E B   C  ]  +-f3.o
[E B   C  ]  +-prog
[E B   C  ]    +-f1.o
[E B   C  ]    +-f2.o
[E B   C  ]    +-f3.o

Note that the order of the --tree= arguments doesn't matter; --tree=status,derived is completely equivalent.

The default behavior of the --tree option is to repeat all of the dependencies each time the library dependency (or any other dependency file) is encountered in the tree. If certain target files share other target files, such as two programs that use the same library:

env = Environment(CPPPATH = ['.'],
                  LIBS = ['foo'],
                  LIBPATH = ['.'])
env.Library('foo', ['f1.c', 'f2.c', 'f3.c'])
env.Program('prog1.c')
env.Program('prog2.c')
      

Then there can be a lot of repetition in the --tree= output:

% scons -Q --tree=all
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog1.o -c -I. prog1.c
cc -o prog1 prog1.o -L. -lfoo
cc -o prog2.o -c -I. prog2.c
cc -o prog2 prog2.o -L. -lfoo
+-.
  +-SConstruct
  +-f1.c
  +-f1.o
  | +-f1.c
  | +-inc.h
  +-f2.c
  +-f2.o
  | +-f2.c
  | +-inc.h
  +-f3.c
  +-f3.o
  | +-f3.c
  | +-inc.h
  +-inc.h
  +-libfoo.a
  | +-f1.o
  | | +-f1.c
  | | +-inc.h
  | +-f2.o
  | | +-f2.c
  | | +-inc.h
  | +-f3.o
  |   +-f3.c
  |   +-inc.h
  +-prog1
  | +-prog1.o
  | | +-prog1.c
  | | +-inc.h
  | +-libfoo.a
  |   +-f1.o
  |   | +-f1.c
  |   | +-inc.h
  |   +-f2.o
  |   | +-f2.c
  |   | +-inc.h
  |   +-f3.o
  |     +-f3.c
  |     +-inc.h
  +-prog1.c
  +-prog1.o
  | +-prog1.c
  | +-inc.h
  +-prog2
  | +-prog2.o
  | | +-prog2.c
  | | +-inc.h
  | +-libfoo.a
  |   +-f1.o
  |   | +-f1.c
  |   | +-inc.h
  |   +-f2.o
  |   | +-f2.c
  |   | +-inc.h
  |   +-f3.o
  |     +-f3.c
  |     +-inc.h
  +-prog2.c
  +-prog2.o
    +-prog2.c
    +-inc.h

In a large configuration with many internal libraries and include files, this can very quickly lead to huge output trees. To help make this more manageable, a prune modifier may be added to the option list, in which case SCons will print the name of a target that has already been visited during the tree-printing in square brackets ([]) as an indication that the dependencies of the target file may be found by looking farther up the tree:

% scons -Q --tree=prune
cc -o f1.o -c -I. f1.c
cc -o f2.o -c -I. f2.c
cc -o f3.o -c -I. f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog1.o -c -I. prog1.c
cc -o prog1 prog1.o -L. -lfoo
cc -o prog2.o -c -I. prog2.c
cc -o prog2 prog2.o -L. -lfoo
+-.
  +-SConstruct
  +-f1.c
  +-f1.o
  | +-f1.c
  | +-inc.h
  +-f2.c
  +-f2.o
  | +-f2.c
  | +-inc.h
  +-f3.c
  +-f3.o
  | +-f3.c
  | +-inc.h
  +-inc.h
  +-libfoo.a
  | +-[f1.o]
  | +-[f2.o]
  | +-[f3.o]
  +-prog1
  | +-prog1.o
  | | +-prog1.c
  | | +-inc.h
  | +-[libfoo.a]
  +-prog1.c
  +-[prog1.o]
  +-prog2
  | +-prog2.o
  | | +-prog2.c
  | | +-inc.h
  | +-[libfoo.a]
  +-prog2.c
  +-[prog2.o]

Like the status keyword, the prune argument by itself is equivalent to --tree=all,prune.