Skip to content

Commit fc181eb

Browse files
committed
build: Inherit flags from rustc
Where applicable, detect which RUSTFLAGS were set for rustc and convert them into their corresponding cc flags in order to ensure consistent codegen across Rust and non-Rust modules.
1 parent 290a629 commit fc181eb

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed

src/lib.rs

+319
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ pub struct Build {
312312
emit_rerun_if_env_changed: bool,
313313
cached_compiler_family: Arc<RwLock<HashMap<Box<Path>, ToolFamily>>>,
314314
shell_escaped_flags: Option<bool>,
315+
inherit_rustflags: bool,
315316
}
316317

317318
/// Represents the types of errors that may occur while using cc-rs.
@@ -437,6 +438,7 @@ impl Build {
437438
emit_rerun_if_env_changed: true,
438439
cached_compiler_family: Arc::default(),
439440
shell_escaped_flags: None,
441+
inherit_rustflags: true,
440442
}
441443
}
442444

@@ -664,6 +666,7 @@ impl Build {
664666
.debug(false)
665667
.cpp(self.cpp)
666668
.cuda(self.cuda)
669+
.inherit_rustflags(false)
667670
.emit_rerun_if_env_changed(self.emit_rerun_if_env_changed);
668671
if let Some(target) = &self.target {
669672
cfg.target(target);
@@ -1313,6 +1316,15 @@ impl Build {
13131316
self
13141317
}
13151318

1319+
/// Configure whether cc should automatically inherit compatible flags passed to rustc
1320+
/// from `CARGO_ENCODED_RUSTFLAGS`.
1321+
///
1322+
/// This option defaults to `true`.
1323+
pub fn inherit_rustflags(&mut self, inherit_rustflags: bool) -> &mut Build {
1324+
self.inherit_rustflags = inherit_rustflags;
1325+
self
1326+
}
1327+
13161328
#[doc(hidden)]
13171329
pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build
13181330
where
@@ -1904,6 +1916,11 @@ impl Build {
19041916
cmd.args.push((**flag).into());
19051917
}
19061918

1919+
// Add cc flags inherited from matching rustc flags
1920+
if self.inherit_rustflags {
1921+
self.add_inherited_rustflags(&mut cmd, &target)?;
1922+
}
1923+
19071924
for flag in self.flags_supported.iter() {
19081925
if self
19091926
.is_flag_supported_inner(flag, &cmd.path, &target)
@@ -2439,6 +2456,23 @@ impl Build {
24392456
Ok(())
24402457
}
24412458

2459+
fn add_inherited_rustflags(&self, cmd: &mut Tool, target: &Target) -> Result<(), Error> {
2460+
let env_os = match self.getenv("CARGO_ENCODED_RUSTFLAGS") {
2461+
Some(env) => env,
2462+
// No encoded RUSTFLAGS -> nothing to do
2463+
None => return Ok(()),
2464+
};
2465+
2466+
let Tool {
2467+
family, path, args, ..
2468+
} = cmd;
2469+
2470+
let env = env_os.to_string_lossy();
2471+
let codegen_flags = RustcCodegenFlags::parse(&env);
2472+
args.extend(codegen_flags.cc_flags(&self, path, family, target));
2473+
Ok(())
2474+
}
2475+
24422476
fn has_flags(&self) -> bool {
24432477
let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" };
24442478
let flags_env_var_value = self.getenv_with_target_prefixes(flags_env_var_name);
@@ -4221,6 +4255,291 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &Target) -> &str
42214255
}
42224256
}
42234257

4258+
#[derive(Debug)]
4259+
struct RustcCodegenFlags {
4260+
branch_protection: Option<String>,
4261+
code_model: Option<String>,
4262+
no_vectorize_loops: bool,
4263+
no_vectorize_slp: bool,
4264+
profile_generate: Option<String>,
4265+
profile_use: Option<String>,
4266+
control_flow_guard: Option<String>,
4267+
lto: Option<String>,
4268+
relocation_model: Option<String>,
4269+
embed_bitcode: Option<bool>,
4270+
force_frame_pointers: Option<bool>,
4271+
link_dead_code: Option<bool>,
4272+
no_redzone: Option<bool>,
4273+
soft_float: Option<bool>,
4274+
}
4275+
4276+
impl RustcCodegenFlags {
4277+
// Parse flags obtained from CARGO_ENCODED_RUSTFLAGS
4278+
fn parse(rustflags_env: &str) -> RustcCodegenFlags {
4279+
fn is_flag_prefix(flag: &str) -> bool {
4280+
match flag {
4281+
"-Z" | "-C" | "--codegen" => true,
4282+
_ => false,
4283+
}
4284+
}
4285+
4286+
fn join_flag_prefix<'a>(prev: &'a str, curr: &'a str) -> Cow<'a, str> {
4287+
match prev {
4288+
"--codegen" | "-C" => Cow::from(format!("-C{}", curr)),
4289+
"-Z" => Cow::from(format!("-Z{}", curr)),
4290+
_ => Cow::from(curr),
4291+
}
4292+
}
4293+
4294+
let mut codegen_flags = RustcCodegenFlags {
4295+
branch_protection: None,
4296+
code_model: None,
4297+
no_vectorize_loops: false,
4298+
no_vectorize_slp: false,
4299+
profile_generate: None,
4300+
profile_use: None,
4301+
control_flow_guard: None,
4302+
lto: None,
4303+
relocation_model: None,
4304+
embed_bitcode: None,
4305+
force_frame_pointers: None,
4306+
link_dead_code: None,
4307+
no_redzone: None,
4308+
soft_float: None,
4309+
};
4310+
4311+
let env_flags: Vec<&str> = rustflags_env.split("\u{1f}").collect();
4312+
4313+
if !is_flag_prefix(env_flags[0]) {
4314+
codegen_flags.set_rustc_flag(env_flags[0]);
4315+
}
4316+
4317+
for i in 1..env_flags.len() {
4318+
let curr = env_flags[i];
4319+
let prev = env_flags[i - 1];
4320+
4321+
// Do not process prefixes on their own
4322+
if !is_flag_prefix(curr) {
4323+
// Concat flags preceded by a prefix
4324+
let rustc_flag = join_flag_prefix(prev, curr);
4325+
codegen_flags.set_rustc_flag(&rustc_flag);
4326+
}
4327+
}
4328+
4329+
codegen_flags
4330+
}
4331+
4332+
fn set_rustc_flag(&mut self, flag: &str) {
4333+
if flag.starts_with("-Z") {
4334+
self.set_unstable_rustc_flag(flag);
4335+
} else {
4336+
self.set_stable_rustc_flag(flag);
4337+
}
4338+
}
4339+
4340+
fn set_stable_rustc_flag(&mut self, flag: &str) {
4341+
let (flag, value) = if let Some((flag, value)) = flag.split_once('=') {
4342+
(flag, Some(value.to_owned()))
4343+
} else {
4344+
(flag, None)
4345+
};
4346+
4347+
match flag {
4348+
"-Ccode-model" => self.code_model = value,
4349+
"-Cno-vectorize-loops" => self.no_vectorize_loops = true,
4350+
"-Cno-vectorize-slp" => self.no_vectorize_slp = true,
4351+
"-Cprofile-generate" => self.profile_generate = value,
4352+
"-Cprofile-use" => self.profile_use = value,
4353+
"-Ccontrol-flow-guard" => self.control_flow_guard = value.or(Some("true".into())),
4354+
"-Clto" => self.lto = value.or(Some("true".into())),
4355+
"-Crelocation-model" => self.relocation_model = value,
4356+
"-Cembed-bitcode" => {
4357+
self.embed_bitcode = value.map_or(Some(true), |val| match val.as_str() {
4358+
"y" | "yes" | "on" | "true" => Some(true),
4359+
"n" | "no" | "off" | "false" => Some(false),
4360+
_ => None,
4361+
});
4362+
}
4363+
"-Cforce-frame-pointers" => {
4364+
self.force_frame_pointers = value.map_or(Some(true), |val| match val.as_str() {
4365+
"y" | "yes" | "on" | "true" => Some(true),
4366+
"n" | "no" | "off" | "false" => Some(false),
4367+
_ => None,
4368+
});
4369+
}
4370+
"-Clink-dead-code" => {
4371+
self.link_dead_code = value.map_or(Some(true), |val| match val.as_str() {
4372+
"y" | "yes" | "on" | "true" => Some(true),
4373+
"n" | "no" | "off" | "false" => Some(false),
4374+
_ => None,
4375+
})
4376+
}
4377+
"-Cno-redzone" => {
4378+
self.no_redzone = value.map_or(Some(true), |val| match val.as_str() {
4379+
"y" | "yes" | "on" | "true" => Some(true),
4380+
"n" | "no" | "off" | "false" => Some(false),
4381+
_ => None,
4382+
});
4383+
}
4384+
"-Csoft-float" => {
4385+
self.soft_float = value.map_or(Some(true), |val| match val.as_str() {
4386+
"y" | "yes" | "on" | "true" => Some(true),
4387+
"n" | "no" | "off" | "false" => Some(false),
4388+
_ => None,
4389+
});
4390+
}
4391+
_ => {}
4392+
}
4393+
}
4394+
4395+
fn set_unstable_rustc_flag(&mut self, flag: &str) {
4396+
let (flag, value) = if let Some((flag, value)) = flag.split_once('=') {
4397+
(flag, Some(value.to_owned()))
4398+
} else {
4399+
(flag, None)
4400+
};
4401+
4402+
match flag {
4403+
"-Zbranch-protection" => self.branch_protection = value,
4404+
_ => {}
4405+
}
4406+
}
4407+
4408+
// Rust and clang/cc don't agree on what equivalent flags should look like either.
4409+
fn cc_flags(
4410+
&self,
4411+
build: &Build,
4412+
path: &PathBuf,
4413+
family: &ToolFamily,
4414+
target: &Target,
4415+
) -> Vec<OsString> {
4416+
let push_if_supported = |flags: &mut Vec<OsString>, flag: OsString| {
4417+
if build
4418+
.is_flag_supported_inner(&flag, path, target)
4419+
.unwrap_or(false)
4420+
{
4421+
flags.push(flag);
4422+
} else {
4423+
build.cargo_output.print_warning(&format!(
4424+
"Inherited flag {:?} is not supported by the currently used CC",
4425+
flag
4426+
));
4427+
}
4428+
};
4429+
4430+
let mut flags: Vec<OsString> = vec![];
4431+
4432+
match family {
4433+
ToolFamily::Clang { .. } | ToolFamily::Gnu => {
4434+
if let Some(value) = &self.branch_protection {
4435+
push_if_supported(
4436+
&mut flags,
4437+
format!("-mbranch-protection={}", value.replace(",", "+")).into(),
4438+
);
4439+
}
4440+
if let Some(value) = &self.code_model {
4441+
push_if_supported(&mut flags, format!("-mcmodel={value}").into());
4442+
}
4443+
if self.no_vectorize_loops {
4444+
push_if_supported(&mut flags, "-fno-vectorize".into());
4445+
}
4446+
if self.no_vectorize_slp {
4447+
push_if_supported(&mut flags, "-fno-slp-vectorize".into());
4448+
}
4449+
if let Some(value) = &self.profile_generate {
4450+
push_if_supported(&mut flags, format!("-fprofile-generate={value}").into());
4451+
}
4452+
if let Some(value) = &self.profile_use {
4453+
push_if_supported(&mut flags, format!("-fprofile-use={value}").into());
4454+
}
4455+
if let Some(value) = &self.control_flow_guard {
4456+
let cc_val = match value.as_str() {
4457+
"y" | "yes" | "on" | "true" | "checks" => Some("cf"),
4458+
"nochecks" => Some("cf-nochecks"),
4459+
"n" | "no" | "off" | "false" => Some("none"),
4460+
_ => None,
4461+
};
4462+
if let Some(cc_val) = cc_val {
4463+
push_if_supported(&mut flags, format!("-mguard={cc_val}").into());
4464+
}
4465+
}
4466+
if let Some(value) = &self.lto {
4467+
let cc_val = match value.as_str() {
4468+
"y" | "yes" | "on" | "true" | "fat" => Some("full"),
4469+
"thin" => Some("thin"),
4470+
_ => None,
4471+
};
4472+
if let Some(cc_val) = cc_val {
4473+
push_if_supported(&mut flags, format!("-flto={cc_val}").into());
4474+
}
4475+
}
4476+
if let Some(value) = &self.relocation_model {
4477+
let cc_flag = match value.as_str() {
4478+
"pic" => Some("-fPIC"),
4479+
"pie" => Some("-fPIE"),
4480+
"dynamic-no-pic" => Some("-mdynamic-no-pic"),
4481+
_ => None,
4482+
};
4483+
if let Some(cc_flag) = cc_flag {
4484+
push_if_supported(&mut flags, cc_flag.into());
4485+
}
4486+
}
4487+
if let Some(value) = &self.embed_bitcode {
4488+
let cc_val = if *value { "all" } else { "off" };
4489+
push_if_supported(&mut flags, format!("-fembed-bitcode={cc_val}").into());
4490+
}
4491+
if let Some(value) = &self.force_frame_pointers {
4492+
let cc_flag = if *value {
4493+
"-fno-omit-frame-pointer"
4494+
} else {
4495+
"-fomit-frame-pointer"
4496+
};
4497+
push_if_supported(&mut flags, cc_flag.into());
4498+
}
4499+
if let Some(value) = &self.link_dead_code {
4500+
if !value {
4501+
push_if_supported(&mut flags, "-dead_strip".into());
4502+
}
4503+
}
4504+
if let Some(value) = &self.no_redzone {
4505+
let cc_flag = if *value {
4506+
"-mno-red-zone"
4507+
} else {
4508+
"-mred-zone"
4509+
};
4510+
push_if_supported(&mut flags, cc_flag.into());
4511+
}
4512+
if let Some(value) = &self.soft_float {
4513+
let cc_flag = if *value {
4514+
"-msoft-float"
4515+
} else {
4516+
"-mno-soft-float"
4517+
};
4518+
push_if_supported(&mut flags, cc_flag.into());
4519+
}
4520+
}
4521+
ToolFamily::Msvc { .. } => {
4522+
if let Some(value) = &self.control_flow_guard {
4523+
let cc_val = match value.as_str() {
4524+
"y" | "yes" | "on" | "true" | "checks" => Some("cf"),
4525+
"n" | "no" | "off" | "false" => Some("cf-"),
4526+
_ => None,
4527+
};
4528+
if let Some(cc_val) = cc_val {
4529+
push_if_supported(&mut flags, format!("/guard:{cc_val}").into());
4530+
}
4531+
}
4532+
if let Some(value) = &self.force_frame_pointers {
4533+
let cc_flag = if *value { "/Oy-" } else { "/Oy" };
4534+
push_if_supported(&mut flags, cc_flag.into());
4535+
}
4536+
}
4537+
}
4538+
4539+
flags
4540+
}
4541+
}
4542+
42244543
#[derive(Clone, Copy, PartialEq)]
42254544
enum AsmFileExt {
42264545
/// `.asm` files. On MSVC targets, we assume these should be passed to MASM

tests/rustflags.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::support::Test;
2+
mod support;
3+
4+
/// This test is in its own module because it modifies the environment and would affect other tests
5+
/// when run in parallel with them.
6+
#[test]
7+
#[cfg(not(windows))]
8+
fn inherits_rustflags() {
9+
// Sanity check - no flags
10+
std::env::set_var("CARGO_ENCODED_RUSTFLAGS", "");
11+
let test = Test::gnu();
12+
test.gcc().file("foo.c").compile("foo");
13+
test.cmd(0)
14+
.must_not_have("-fno-omit-frame-pointer")
15+
.must_not_have("-mcmodel=small")
16+
.must_not_have("-msoft-float");
17+
18+
// Correctly inherits flags from rustc
19+
std::env::set_var(
20+
"CARGO_ENCODED_RUSTFLAGS",
21+
"-Cforce-frame-pointers=true\u{1f}-Ccode-model=small\u{1f}-Csoft-float",
22+
);
23+
let test = Test::gnu();
24+
test.gcc().file("foo.c").compile("foo");
25+
test.cmd(0)
26+
.must_have("-fno-omit-frame-pointer")
27+
.must_have("-mcmodel=small")
28+
.must_have("-msoft-float");
29+
}

0 commit comments

Comments
 (0)