Problem
Cargo has rustc-wrapper for wrapping rustc,
but there is no way to wrap other host executables like build scripts and proc macros.
This makes several use cases awkward or impossible:
-
Distributed builds:
Tools like distcc/sccache work as compiler wrappers that intercept invocations,
decide whether to run locally or remotely, and handle the handoff.
For build scripts, we'd want the same pattern to avoid wrapping the entire cargo
or modifying cargo source code.
-
Build system integration:
Build system like Nix keeps dependencies in a read-only store for reproducibility.
Some build scripts try to write to their own source directory not just OUT_DIR,
which breaks in build sandbox (granted, they shouldn't do that in the first place).
Existing workarounds are like copying all vendored sources to a writable temp directory
before every build, or patch the build scripts directly.
This is usually expensive and not recommended (see Crane docs).
With a wrapper,
Build system could intercept the build script, redirect writes,
or fail fast with a clear error instead of subtle breakage.
-
Debugging:
Build scripts buffer their output and only show it at the end.
If a build script crashes or hangs, you don't see what it was doing.
A wrapper could stream output earlier, add timestamps,
catch crashes and provide stack traces.
This gives a good playground for experimenting diferent build script output for Cargo itself.
-
Environment control:
Currently you'd set env vars globally for the whole Cargo process,
which affects rustc too.
While rustc has an unstable --env-set to control what env!() sees,
build scripts don't have equivalent mechanisms yet.
-
Process isolation (i.e., sandboxing:
Tools like bubblewrap or seccomp filters can restrict filesystem/network access.
Without a wrapper, you'd need to wrap the entire cargo process which affects rustc too.
A wrapper lets you apply restrictions per build script.
Context
The Cargo team has discussed full sandboxing of build scripts and proc-macros multiple times.
The general consensus has been that built-in sandboxing is likely out of scope for Cargo at this moment,
due to its complexity and hard to satisfy different needs with a single solution.
The focus right now be on reducing reliance on build scripts and making them easier to audit.
In a recent Cargo team discussion https://github.com/rust-lang/cargo-team/blob/0cf1414e1012445262a335a6b1c2b11983d31639/meetings/sync-meeting/2026-01-20.md#build-script-wrapper,
the team agreed with a host runner unstable feature to offer a low-level hook for experimentation.
External tools can try different approaches without Cargo committing to any particular design.
Proposed Solution
Add host.runner config that wraps host build targets like build scripts during the build
This follows the existing pattern of [host] config,
which currently supports linker and rustflags.
It is similar to how target.<triple>.runner works for cargo test/cargo run executables
but for host build targets.
Config example:
[host]
runner = ["wrapper", "--custom-flag"]
Invocation:
wrapper --custom-flag <actual-build-script-executable> <args...>
The goal is to enable experimentation,
not to commit to stabilizing the current design.
Different use cases may need different APIs,
and we should gather real-world feedback before deciding what to stabilize,
or different features for different use cases
For example, we might want first-class support for distributed builds to avoid process-forking cost.
Implementation plan
This can reuse the existing -Zhost-config unstable feature.
The infrastructure is already in place:
TargetConfig struct has a runner field
load_host_triple() already loads [host] config including runner but unused
[host] already applies to build scripts and proc-macros
Main work needed:
Limitations
- Proc-macro wrapping:
Proc-macros can't be wrapped by Cargo, as they're loaded and executed by rustc directly.
This can still establish patterns for future rustc changes.
Related issues
Problem
Cargo has
rustc-wrapperfor wrapping rustc,but there is no way to wrap other host executables like build scripts and proc macros.
This makes several use cases awkward or impossible:
Distributed builds:
Tools like distcc/sccache work as compiler wrappers that intercept invocations,
decide whether to run locally or remotely, and handle the handoff.
For build scripts, we'd want the same pattern to avoid wrapping the entire
cargoor modifying cargo source code.
Build system integration:
Build system like Nix keeps dependencies in a read-only store for reproducibility.
Some build scripts try to write to their own source directory not just OUT_DIR,
which breaks in build sandbox (granted, they shouldn't do that in the first place).
Existing workarounds are like copying all vendored sources to a writable temp directory
before every build, or patch the build scripts directly.
This is usually expensive and not recommended (see Crane docs).
With a wrapper,
Build system could intercept the build script, redirect writes,
or fail fast with a clear error instead of subtle breakage.
Debugging:
Build scripts buffer their output and only show it at the end.
If a build script crashes or hangs, you don't see what it was doing.
A wrapper could stream output earlier, add timestamps,
catch crashes and provide stack traces.
This gives a good playground for experimenting diferent build script output for Cargo itself.
Environment control:
Currently you'd set env vars globally for the whole Cargo process,
which affects rustc too.
While rustc has an unstable
--env-setto control whatenv!()sees,build scripts don't have equivalent mechanisms yet.
Process isolation (i.e., sandboxing:
Tools like bubblewrap or seccomp filters can restrict filesystem/network access.
Without a wrapper, you'd need to wrap the entire
cargoprocess which affects rustc too.A wrapper lets you apply restrictions per build script.
Context
The Cargo team has discussed full sandboxing of build scripts and proc-macros multiple times.
The general consensus has been that built-in sandboxing is likely out of scope for Cargo at this moment,
due to its complexity and hard to satisfy different needs with a single solution.
The focus right now be on reducing reliance on build scripts and making them easier to audit.
In a recent Cargo team discussion https://github.com/rust-lang/cargo-team/blob/0cf1414e1012445262a335a6b1c2b11983d31639/meetings/sync-meeting/2026-01-20.md#build-script-wrapper,
the team agreed with a host runner unstable feature to offer a low-level hook for experimentation.
External tools can try different approaches without Cargo committing to any particular design.
Proposed Solution
Add
host.runnerconfig that wraps host build targets like build scripts during the buildThis follows the existing pattern of
[host]config,which currently supports
linkerandrustflags.It is similar to how
target.<triple>.runnerworks forcargo test/cargo runexecutablesbut for host build targets.
Config example:
Invocation:
The goal is to enable experimentation,
not to commit to stabilizing the current design.
Different use cases may need different APIs,
and we should gather real-world feedback before deciding what to stabilize,
or different features for different use cases
For example, we might want first-class support for distributed builds to avoid process-forking cost.
Implementation plan
This can reuse the existing
-Zhost-configunstable feature.The infrastructure is already in place:
TargetConfigstruct has arunnerfieldload_host_triple()already loads[host]config includingrunnerbut unused[host]already applies to build scripts and proc-macrosMain work needed:
custom_build.rs, usehost.runnerwhen executing build scripts, and update test snapshots.-Zhost-configLimitations
Proc-macros can't be wrapped by Cargo, as they're loaded and executed by rustc directly.
This can still establish patterns for future rustc changes.
Related issues
rustcwrappers which envs to pass through to allow env sandboxing #14444TMPDIRfor build scripts andrustc#16427