Builders and Pseudo-builders
Last updated on 2025-04-16 | Edit this page
Estimated time: 10 minutes
Overview
Questions
- How can I define common task operations for similar files?
Objectives
- Write SCons builders and pseudo-builders
Our SConstruct file still has repeated content. The task for each
.dat
file are identical apart from the text and data file
names. We can replace these tasks with a single builder which can be used to build any
.dat
file from a .txt
file in
books/
:
After creating the custom builder, we need to add it to the construction environment to make it available for task definitions.
PYTHON
env = Environment(ENV=os.environ.copy())
env.Append(BUILDERS={"CountWords": count_words_builder})
Now we can convert our .dat
tasks from the
Command
to CountWords
builder.
PYTHON
env.CountWords(
target=["isles.dat"],
source=["books/isles.txt", "countwords.py"],
)
env.CountWords(
target=["abyss.dat"],
source=["books/abyss.txt", "countwords.py"],
)
env.CountWords(
target=["last.dat"],
source=["books/last.txt", "countwords.py"],
)
Custom builders like CountWords
allow us to apply the
same action to many tasks.
If we re-run SCons,
then we get:
OUTPUT
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
python countwords.py books/isles.txt isles.dat
python countwords.py books/abyss.txt abyss.dat
python countwords.py books/last.txt last.dat
scons: done building targets.
We can further simplify the task definition by moving the text file
handling inside a pseudo-builder function. Pseudo-builders behave like
builders, but allow flexibility in task construction through
user-defined arguments. We will use the pathlib
module to
help us construct OS-agnostic paths and perform path manipulation.
At the top of your SConstruct
file, update the imports
as below. Then define a new count_words
pseudo-builder
function after the imports to replace the
count_word_builder
and add it to the construction
environment.
PYTHON
import os
import pathlib
def count_words(env, data_file):
"""Pseudo-builder to run the `countwords.py` script and produce `.dat` target
Assumes that the source text file is found in `books/{data_file}.txt`
:param env: SCons construction environment. Do not provide when using this
function with the `env.AddMethod` and `env.CountWords` access style.
:param data_file: String name of the data file to create.
"""
data_path = pathlib.Path(data_file)
text_file = pathlib.Path("books") / data_path.with_suffix(".txt")
target_nodes = env.Command(
target=[data_file],
source=[text_file, "countwords.py"],
action=["python ${SOURCES[-1]} ${SOURCES[0]} ${TARGET}"],
)
return target_nodes
env = Environment(ENV=os.environ.copy())
env.AddMethod(count_words, "CountWords")
This pseudo-builder has further reduced the interface necessary to
define the .dat
tasks, which now can be re-written as
For students familiar with GNU Make, pseudo-builders are similar to Make ‘pattern rules’, but pseudo-builders are both more verbose and more flexible. Pseudo-builders require a full Python function definition syntax, but they can do more than simple file extension pattern matching and anything the user requires.
A psuedo-builder alone will not allow us to match arbitrary files
using the .dat
file extension. If we desire the full Make
‘pattern rule’ behavior, we can accept a target name and match it to our
pseudo-builder with the SCons COMMAND_LINE_TARGETS
variable.
Add the following to the bottom of your SConstruct file
PYTHON
for target in COMMAND_LINE_TARGETS:
if pathlib.Path(target).suffix == ".dat":
env.CountWords(target)
Now we can define tasks for new files not found in our pre-defined tasks as
OUTPUT
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
python countwords.py books/sierra.txt sierra.dat
scons: done building targets.
Where We Are
This SConstruct file contains all of our work so far.
Key Points
- Use the
Builder
function andAppend
the construction environmentBUILDERS
dictionary to define common actions. - Use the
AddMethod
function and Python functions to define pseudo-builders with custom tailored task handling. - Use the special SCons variable
COMMAND_LINE_TARGETS
to perform dynamic handling that depends on command line target requests.