如何在SCons中运行Make构建,兼顾并行效率与资源控制?
Great question—mixing SCons and Make in a multi-repo build system is super common, but their parallelization models clash hard. Let’s walk through a clean, maintainable fix that lets you:
- Run SCons’ own tasks in parallel without issues
- When invoking Make, lock up all available build slots so SCons doesn’t spin up other tasks (preventing CPU overload)
- Safely pass full parallelism to Make using either SCons’
-jparameter or your system’s core count
Core Idea: SCons Resource Locking
SCons has a built-in resource management system that lets you define shared resources (like CPU cores) and control how tasks consume them. We’ll use this to:
- Treat CPU cores as a shared resource with a count equal to your desired parallelism
- Have regular SCons tasks consume 1 core each (enabling normal parallelism)
- Have Make tasks consume all cores (so no other tasks run while Make is active)
Step-by-Step Implementation
1. Initialize Environment and Capture Parallelism Settings
First, in your root SConstruct, grab the number of jobs from the SCons command line (via -j) or fall back to your system’s core count:
import os from SCons.Resources import Resource # Initialize base environment env = Environment() # Get parallelism count: use SCons' -j value, or system CPU count if not specified num_jobs = env.GetOption('num_jobs') or os.cpu_count()
2. Define the CPU Shared Resource
Create a global resource representing your CPU cores, with a total count matching your parallelism value:
# Create a CPU resource pool with exactly num_jobs slots CPUResource = Resource('CPU', num_jobs) # Attach this resource to all regular SCons tasks (each task uses 1 core) env.Append(RESOURCES = {CPUResource: 1})
3. Custom Make Builder for Exclusive Resource Use
Build a custom builder that invokes Make, and configure it to consume all CPU resources when running. This ensures SCons won’t start any other parallel tasks while Make is active:
def make_build_action(target, source, env): # Get the path to your subrepo (source[0] is the directory containing the Makefile) subrepo_dir = str(source[0]) # Run Make with full parallelism (matches SCons' -j or core count) return env.Execute(f"cd {subrepo_dir} && make -j{env['NUM_JOBS']}") # Create a dedicated builder for Make tasks MakeBuilder = Builder( action=make_build_action, # Clone the base env and override resources: this task uses ALL CPU slots env=env.Clone( NUM_JOBS=num_jobs, RESOURCES={CPUResource: num_jobs} ) ) # Register the builder with your environment env['BUILDERS']['MakeBuilder'] = MakeBuilder
4. Use the Builder for Subrepo Make Targets
Now you can define your subrepo Make targets just like any other SCons target:
# Example: Build a subrepo located at ./my_submodule env.MakeBuilder('my_submodule_build', './my_submodule') # Add your regular SCons targets (these will run in parallel normally) env.Program('my_app', ['src/main.cpp', 'src/utils.cpp'])
How This Works
- Regular SCons Tasks: Each task takes 1 slot from the CPU resource pool, so SCons can run up to
num_jobstasks in parallel (exactly what you want for normal builds). - Make Tasks: When a Make task runs, it grabs every slot in the CPU resource pool. SCons’ scheduler won’t start any other tasks until the Make task finishes, because there are no free resources left. We pass the full
num_jobsto Make’s-jflag, so it uses all available cores without clashing with SCons.
Key Notes
- If you have multiple Make subrepos, they’ll run sequentially (since each needs all CPU slots). This is intentional—running multiple full-parallel Make processes would overload your CPU anyway.
- Always test with
scons -j<num>to verify: you’ll see SCons run parallel compilation tasks until it hits a Make target, at which point it pauses other tasks until Make completes. - Avoid using SCons’ default
Makebuilder—it doesn’t handle resource locking, so it’ll let SCons run parallel tasks alongside Make, leading to resource exhaustion.
内容的提问来源于stack exchange,提问作者Rupert Swarbrick

