Description
Problem
It is a common pattern in build scripts to run a rustc 'build probe': compile some code with rustc, see if that works, and then enable some cfg
flags depending on the result. Crates use this to automatically detect if some API they optionally use is available on the current compiler. Examples of this include anhow, thiserror, eyre, and the autocfg crate which abstracts that pattern into a library.
However, invoking rustc in the right way is non-trivial. build scripts have to remember to check RUSTC and RUSTC_WRAPPER, to pass on the RUSTFLAGS (using CARGO_ENCODED_RUSTFLAGS where possible so spaces are preserved properly), and to set --target
based on the TARGET env var. And even then the final rustc invocation is not always the same as what cargo itself uses, causing issues like the one described here: it is impossible to tell, from the information a build script has, whether cargo will pass --target
to rustc or not, and there are some situations (rustc bootstrap and Miri, for instance) where this really matters. As a consequence, the aforementioned crates sometimes break in rustc bootstrap and Miri when they are used as dependency of a proc macro.
Proposed Solution
Cargo should just tell the build script how it will invoke rustc. I'm imagining an env var like CARGO_ENCODED_RUSTC
(name to be bikeshed), which build scripts could use like this:
use std::path::Path;
use std::env;
use std::fs;
use std::process::Command;
fn probe(probe: &str) -> bool {
let out_dir = env::var_os("OUT_DIR").unwrap();
let probefile = Path::new(&out_dir).join("probe.rs");
fs::write(&probefile, probe).expect("failed to write in OUT_DIR");
let rustc = env::var("CARGO_ENCODED_RUSTC").unwrap();
let mut rustc = rustc.split('\x1f');
let mut cmd = Command::new(rustc.next().unwrap());
cmd
.args(rustc)
.arg("--crate-name=build_probe")
.arg("--crate-type=lib")
.arg("--emit=metadata")
.arg("--out-dir")
.arg(out_dir)
.arg(probefile);
cmd.status().expect("failed to invoke rustc").success()
}
Notes
Does this need an RFC?