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): deps.append(file) break print(f"DEBUG: scanned dependencies found: {deps}") return env.File(deps) kscan = Scanner( function=kfile_scan, skeys=[".k"], path_function=FindPathDirs("KPATH"), ) def build_function(target, source, env): # Code to build "target" from "source" return None bld = Builder( action=build_function, suffix=".k", source_scanner=kscan, src_suffix=".input", ) env = Environment(BUILDERS={"KFile": bld}, KPATH="inc") env.KFile("file")
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).