Skip to content

Commit f6de921

Browse files
committed
Auto merge of #11114 - matklad:build-falgs, r=ehuss
Iteratively construct target cfg When setting target features via rustflags via `[build]` config key, cargo correctly propagates them to rustc (via -C flag) and to build.rs (via CARGO_CFG_TARGET_FEATURE env var). However, if `[target.cfg]` is used instead, build.rs doesn't get the flags (rustc still gets them though). This changes it so that cargo will call `rustc` up to two times to collect the `cfg` values, updating which flags to use on the second call based on the cfg discovered in the first call. Closes #6858.
2 parents abf6b4e + c333b0a commit f6de921

File tree

2 files changed

+190
-98
lines changed

2 files changed

+190
-98
lines changed

src/cargo/core/compiler/build_context/target_info.rs

+124-98
Original file line numberDiff line numberDiff line change
@@ -138,122 +138,148 @@ impl TargetInfo {
138138
rustc: &Rustc,
139139
kind: CompileKind,
140140
) -> CargoResult<TargetInfo> {
141-
let rustflags = env_args(
141+
let mut rustflags = env_args(
142142
config,
143143
requested_kinds,
144144
&rustc.host,
145145
None,
146146
kind,
147147
Flags::Rust,
148148
)?;
149-
let extra_fingerprint = kind.fingerprint_hash();
150-
let mut process = rustc.workspace_process();
151-
process
152-
.arg("-")
153-
.arg("--crate-name")
154-
.arg("___")
155-
.arg("--print=file-names")
156-
.args(&rustflags)
157-
.env_remove("RUSTC_LOG");
158-
159-
if let CompileKind::Target(target) = kind {
160-
process.arg("--target").arg(target.rustc_target());
161-
}
162-
163-
let crate_type_process = process.clone();
164-
const KNOWN_CRATE_TYPES: &[CrateType] = &[
165-
CrateType::Bin,
166-
CrateType::Rlib,
167-
CrateType::Dylib,
168-
CrateType::Cdylib,
169-
CrateType::Staticlib,
170-
CrateType::ProcMacro,
171-
];
172-
for crate_type in KNOWN_CRATE_TYPES.iter() {
173-
process.arg("--crate-type").arg(crate_type.as_str());
174-
}
175-
let supports_split_debuginfo = rustc
176-
.cached_output(
177-
process.clone().arg("-Csplit-debuginfo=packed"),
178-
extra_fingerprint,
179-
)
180-
.is_ok();
181-
182-
process.arg("--print=sysroot");
183-
process.arg("--print=cfg");
184-
185-
let (output, error) = rustc
186-
.cached_output(&process, extra_fingerprint)
187-
.with_context(|| "failed to run `rustc` to learn about target-specific information")?;
149+
let mut turn = 0;
150+
loop {
151+
let extra_fingerprint = kind.fingerprint_hash();
152+
let mut process = rustc.workspace_process();
153+
process
154+
.arg("-")
155+
.arg("--crate-name")
156+
.arg("___")
157+
.arg("--print=file-names")
158+
.args(&rustflags)
159+
.env_remove("RUSTC_LOG");
160+
161+
if let CompileKind::Target(target) = kind {
162+
process.arg("--target").arg(target.rustc_target());
163+
}
188164

189-
let mut lines = output.lines();
190-
let mut map = HashMap::new();
191-
for crate_type in KNOWN_CRATE_TYPES {
192-
let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?;
193-
map.insert(crate_type.clone(), out);
194-
}
165+
let crate_type_process = process.clone();
166+
const KNOWN_CRATE_TYPES: &[CrateType] = &[
167+
CrateType::Bin,
168+
CrateType::Rlib,
169+
CrateType::Dylib,
170+
CrateType::Cdylib,
171+
CrateType::Staticlib,
172+
CrateType::ProcMacro,
173+
];
174+
for crate_type in KNOWN_CRATE_TYPES.iter() {
175+
process.arg("--crate-type").arg(crate_type.as_str());
176+
}
177+
let supports_split_debuginfo = rustc
178+
.cached_output(
179+
process.clone().arg("-Csplit-debuginfo=packed"),
180+
extra_fingerprint,
181+
)
182+
.is_ok();
183+
184+
process.arg("--print=sysroot");
185+
process.arg("--print=cfg");
186+
187+
let (output, error) = rustc
188+
.cached_output(&process, extra_fingerprint)
189+
.with_context(|| {
190+
"failed to run `rustc` to learn about target-specific information"
191+
})?;
192+
193+
let mut lines = output.lines();
194+
let mut map = HashMap::new();
195+
for crate_type in KNOWN_CRATE_TYPES {
196+
let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?;
197+
map.insert(crate_type.clone(), out);
198+
}
195199

196-
let line = match lines.next() {
197-
Some(line) => line,
198-
None => anyhow::bail!(
199-
"output of --print=sysroot missing when learning about \
200+
let line = match lines.next() {
201+
Some(line) => line,
202+
None => anyhow::bail!(
203+
"output of --print=sysroot missing when learning about \
200204
target-specific information from rustc\n{}",
201-
output_err_info(&process, &output, &error)
202-
),
203-
};
204-
let sysroot = PathBuf::from(line);
205-
let sysroot_host_libdir = if cfg!(windows) {
206-
sysroot.join("bin")
207-
} else {
208-
sysroot.join("lib")
209-
};
210-
let mut sysroot_target_libdir = sysroot.clone();
211-
sysroot_target_libdir.push("lib");
212-
sysroot_target_libdir.push("rustlib");
213-
sysroot_target_libdir.push(match &kind {
214-
CompileKind::Host => rustc.host.as_str(),
215-
CompileKind::Target(target) => target.short_name(),
216-
});
217-
sysroot_target_libdir.push("lib");
218-
219-
let cfg = lines
220-
.map(|line| Ok(Cfg::from_str(line)?))
221-
.filter(TargetInfo::not_user_specific_cfg)
222-
.collect::<CargoResult<Vec<_>>>()
223-
.with_context(|| {
224-
format!(
225-
"failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
226-
output
227-
)
228-
})?;
229-
230-
Ok(TargetInfo {
231-
crate_type_process,
232-
crate_types: RefCell::new(map),
233-
sysroot,
234-
sysroot_host_libdir,
235-
sysroot_target_libdir,
205+
output_err_info(&process, &output, &error)
206+
),
207+
};
208+
let sysroot = PathBuf::from(line);
209+
let sysroot_host_libdir = if cfg!(windows) {
210+
sysroot.join("bin")
211+
} else {
212+
sysroot.join("lib")
213+
};
214+
let mut sysroot_target_libdir = sysroot.clone();
215+
sysroot_target_libdir.push("lib");
216+
sysroot_target_libdir.push("rustlib");
217+
sysroot_target_libdir.push(match &kind {
218+
CompileKind::Host => rustc.host.as_str(),
219+
CompileKind::Target(target) => target.short_name(),
220+
});
221+
sysroot_target_libdir.push("lib");
222+
223+
let cfg = lines
224+
.map(|line| Ok(Cfg::from_str(line)?))
225+
.filter(TargetInfo::not_user_specific_cfg)
226+
.collect::<CargoResult<Vec<_>>>()
227+
.with_context(|| {
228+
format!(
229+
"failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
230+
output
231+
)
232+
})?;
233+
236234
// recalculate `rustflags` from above now that we have `cfg`
237235
// information
238-
rustflags: env_args(
236+
let new_flags = env_args(
239237
config,
240238
requested_kinds,
241239
&rustc.host,
242240
Some(&cfg),
243241
kind,
244242
Flags::Rust,
245-
)?,
246-
rustdocflags: env_args(
247-
config,
248-
requested_kinds,
249-
&rustc.host,
250-
Some(&cfg),
251-
kind,
252-
Flags::Rustdoc,
253-
)?,
254-
cfg,
255-
supports_split_debuginfo,
256-
})
243+
)?;
244+
245+
// Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active
246+
// `cfg` flags define which `.cargo/config` sections apply, and they
247+
// in turn can affect `RUSTFLAGS`! This is a bona fide mutual
248+
// dependency, and it can even diverge (see `cfg_paradox` test).
249+
//
250+
// So what we do here is running at most *two* iterations of
251+
// fixed-point iteration, which should be enough to cover
252+
// practically useful cases, and warn if that's not enough for
253+
// convergence.
254+
let reached_fixed_point = new_flags == rustflags;
255+
if !reached_fixed_point && turn == 0 {
256+
turn += 1;
257+
rustflags = new_flags;
258+
continue;
259+
}
260+
if !reached_fixed_point {
261+
config.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?;
262+
}
263+
264+
return Ok(TargetInfo {
265+
crate_type_process,
266+
crate_types: RefCell::new(map),
267+
sysroot,
268+
sysroot_host_libdir,
269+
sysroot_target_libdir,
270+
rustflags,
271+
rustdocflags: env_args(
272+
config,
273+
requested_kinds,
274+
&rustc.host,
275+
Some(&cfg),
276+
kind,
277+
Flags::Rustdoc,
278+
)?,
279+
cfg,
280+
supports_split_debuginfo,
281+
});
282+
}
257283
}
258284

259285
fn not_user_specific_cfg(cfg: &CargoResult<Cfg>) -> bool {

tests/testsuite/build_script_env.rs

+66
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,69 @@ fn rustc_bootstrap() {
173173
.with_status(101)
174174
.run();
175175
}
176+
177+
#[cargo_test]
178+
#[cfg(target_arch = "x86_64")]
179+
fn build_script_sees_cfg_target_feature() {
180+
let build_rs = r#"
181+
fn main() {
182+
let cfg = std::env::var("CARGO_CFG_TARGET_FEATURE").unwrap();
183+
eprintln!("CARGO_CFG_TARGET_FEATURE={cfg}");
184+
}
185+
"#;
186+
187+
let configs = [
188+
r#"
189+
[build]
190+
rustflags = ["-Ctarget-feature=+sse4.1,+sse4.2"]
191+
"#,
192+
r#"
193+
[target.'cfg(target_arch = "x86_64")']
194+
rustflags = ["-Ctarget-feature=+sse4.1,+sse4.2"]
195+
"#,
196+
];
197+
198+
for config in configs {
199+
let p = project()
200+
.file(".cargo/config.toml", config)
201+
.file("src/lib.rs", r#""#)
202+
.file("build.rs", build_rs)
203+
.build();
204+
205+
p.cargo("build -vv")
206+
.with_stderr_contains("[foo 0.0.1] CARGO_CFG_TARGET_FEATURE=[..]sse4.2[..]")
207+
.with_stderr_contains("[..]-Ctarget-feature=[..]+sse4.2[..]")
208+
.run();
209+
}
210+
}
211+
212+
/// In this test, the cfg is self-contradictory. There's no *right* answer as to
213+
/// what the value of `RUSTFLAGS` should be in this case. We chose to give a
214+
/// warning. However, no matter what we do, it's important that build scripts
215+
/// and rustc see a consistent picture
216+
#[cargo_test]
217+
fn cfg_paradox() {
218+
let build_rs = r#"
219+
fn main() {
220+
let cfg = std::env::var("CARGO_CFG_BERTRAND").is_ok();
221+
eprintln!("cfg!(bertrand)={cfg}");
222+
}
223+
"#;
224+
225+
let config = r#"
226+
[target.'cfg(not(bertrand))']
227+
rustflags = ["--cfg=bertrand"]
228+
"#;
229+
230+
let p = project()
231+
.file(".cargo/config.toml", config)
232+
.file("src/lib.rs", r#""#)
233+
.file("build.rs", build_rs)
234+
.build();
235+
236+
p.cargo("build -vv")
237+
.with_stderr_contains("[WARNING] non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")
238+
.with_stderr_contains("[foo 0.0.1] cfg!(bertrand)=true")
239+
.with_stderr_contains("[..]--cfg=bertrand[..]")
240+
.run();
241+
}

0 commit comments

Comments
 (0)