Skip to content
Mats Wichmann edited this page Oct 16, 2020 · 10 revisions

ParseDepends

ParseDepends allows to parse compiler-generated dependency files and let SCons establish the listed dependencies.

What's that in aid of?

As a rule of thumb, never use it unless you have to. SCons has scanners to extract implicit dependencies. However, sometimes the built-in scanners choke on pre-processor statements. Consider the following example: hello.c:

#define FOO_HEADER "foo.h"

#include FOO_HEADER

int main() {
        return FOO;
}

foo.h:

#define FOO 42

SConstruct:

Program('hello.c')
scons --tree=prune

As the dependency tree reveals, SCons does not know about foo.h and does not rebuild hello.o when foo.h changes. If the compiler is able to extract implicit dependencies and output those as Make rules, SCons can parse these files and properly set up the dependencies.

Generate dependency files as a side effect

Here is an example of how to let gcc generate a dependency file while compiling the object file:

Program("hello.c", CCFLAGS="-MD -MF hello.d")
ParseDepends("hello.d")
SideEffect("hello.d", "hello.o")

GCC generates a dependency file that looks like the following:

hello.o: hello.c foo.h

There is one problem with this approach: Read the full story here DEAD LINK. TL;DR ParseDepends does not read the file in the first pass, leading to unnecessary rebuilds in the second pass. The reason is, that the signature changes as new dependencies are added (foo.h in the example above).

Generate dependency files in advance

If you want to extract the dependencies (and call ParseDepends) before building the object files, the only viable solution is to use a multi-stage builder with a source scanner:

def parsedep(node, env, path):
    print "ParseDepends(%s)" % str(node)
    ParseDepends(str(node))
    return []


def parsecheck(node, env):
    return node.exists()


depscan = Scanner(function=parsedep, skeys=[".d"], scan_check=parsecheck)

depbuild = Builder(
    action="$CC -M -MF $TARGET $CCFLAGS -c $SOURCE", suffix=".d", src_suffix=".c"
)

depparse = Builder(
    action=Copy("$TARGET", "$SOURCE"),
    suffix=".dep",
    src_builder=depbuild,
    source_scanner=depscan,
)

env = Environment(BUILDERS={"ExtractDependencies": depparse})

dep = env.ExtractDependencies("hello.c")

obj = env.Object("hello.c")
env.Requires(obj, dep)
env.Program(obj)

Some notes:

  • The scanner needs to be called when the file exists
    • scan_check ensures the node exists before calling the scanner
    • A target_scanner in depbuild does not work (it's called before the .d file exists)
  • The Copy action is useless, the depparse builder exists only to execute a source_scanner
  • The dependency file must be generated before the object, therefore the order-only prerequisite

Links

Clone this wiki locally