In build.rs
file, it's generally a mistake to do cfg!(target_foo = "thing")
, ad it will test the value on the host instead of the target. This means code that does this is broken when cross compiling, which is unfortunate (but extremely common).
(While this is confusing at first, it's also hard to argue it is not the correct way for this to work. If when run from a build script it worked some other way, then, say, #[cfg(target_arch = "x86_64)]
might be insufficient to soundly guard use of core::arch::asm!
for x86_64 code).
Instead, I believe we're we're supposed to use the values provided by cargo for build scripts, such as CARGO_CFG_<cfg>
. That is:
let target_foo_is_thing = std::env::var("CARGO_CFG_TARGET_FOO")
.map_or(false, |v| v == "thing");
This properly checks the target's value for target_foo
rather than the host's. All good? Well, no. this is still possibly not quite right.
This may work now, and it may stay that way, or it may break under different compilation settings. Consider the case of cfg
s which may be used with multiple values, like target_feature
. cfg!(target_feature = "abc")
and cfg!(target_feature = "cde")
both evaluate to true, so what does CARGO_CFG_TARGET_FEATURE
use? It uses all of the values, separated by commas. So, for cfg
keys which may hold multiple values, you must account for this and write your test in a slightly different manner, for example:
let have_static_crt = std::env::var("CARGO_CFG_TARGET_FEATURE")
.map_or(false, |v| v.split(",").any(|v| v == "static-crt"))
In fact, the statement "for cfg
keys which may hold multiple values" may be too conservative. Somewhat recently, the target_family
cfg changed from being single-valued to multi-valued, as wasm
has been added as a target_family
, and now is returned on certain targets which are also unix
; for example, wasm32-unknown-emscripten
comes through in CARGO_CFG_TARGET_FAMILY
as "unix,wasm"
. This was done without much discussion, and was celebrated by many, and several people have expressed desires to add even more target_families, for cases like x86
(x86/x86_64), darwin
(macOS/iOS/tvOS), linux
(linux/android), and so on.
I think we should be careful here. I believe the change from unix
to unix,wasm
on those targets was probably a breaking change, probably an accidental one (I looked and I couldn't find this being mentioned at all in the discussion about the new target family, but perhaps I missed it). In practice, we almost certainly "got away" with it for a few reasons:
- Most build scripts use
cfg!()
or#[cfg()]
for this, which would keep working in this case, despite being incorrect for cross compilation. - The targets which changed are the
emscripten
targets, which are rare. - Use of
CARGO_CFG_UNIX
is slightly more common from what I can tell thantCARGO_CFG_TARGET_FAMILY
(but the latter does seem to get used)
Alternatively, I suppose we could take the stance that actually all of the cfg values may gain multiple values in the future. If we do this, we should document this fact more clearly than we do. (I also think it will lead to annoying breakage when running some build scripts which suddenly do the wrong thing). I don't know if I think this is useful, since many of these clearly cannot take multiple values, but it's unclear how we would clarify this fact.
Anyway, I don't really have a point with this, I just saw someone mention more target_family
values as a desirable goal recently, and wanted to bring this up for discussion. I personally am concerned it would cause subtle breakage, and that it should be done carefully at the very least.