20.3. Using scanners with Builders

One approach for introducing a Scanner into the build is in conjunction with a Builder. There are two relvant optional parameters we can use when creating a Builder: source_scanner and target_scanner. source_scanner is used for scanning source files, and target_scanner is used for scanning the target once it is generated.

import os, re

include_re = re.compile(r"^include\s+(\S+)$", re.M)

def kfile_scan(node, env, path, arg=None):
    includes = include_re.findall(node.get_text_contents())
    print(f"DEBUG: scan of {str(node)!r} found {includes}")
    deps = []
    for inc in includes:
        for dir in path:
            file = str(dir) + os.sep + inc
            if os.path.exists(file):
    print(f"DEBUG: scanned dependencies found: {deps}")
    return env.File(deps)

kscan = Scanner(

def build_function(target, source, env):
    # Code to build "target" from "source"
    return None

bld = Builder(

env = Environment(BUILDERS={"KFile": bld}, KPATH="inc")

Running this example would only show that the stub build_function is getting called, so some debug prints were added to the scaner function, just to show the scanner is being invoked.

% scons -Q
DEBUG: scan of 'file.input' found ['other_file']
DEBUG: scanned dependencies found: ['inc/other_file']
build_function(["file.k"], ["file.input"])

The path-search implementation in kfile_scan works, but is quite simple-minded - a production scanner will probably do something more sophisticated.

An emitter function can modify the list of sources or targets passed to the action function when the Builder is triggered.

A scanner function will not affect the list of sources or targets seen by the Builder during the build action. The scanner function will, however, affect if the Builder should rebuild (if any of the files sourced by the Scanner have changed for example).