Skip to content

Commit 3b4f9d8

Browse files
committed
support generic target tables and env variables
When building for targets from a meta build system like buildroot it is preferable to be able to unconditionally set target config/env variables without having to care about the target triple as we use target specific toolchains that will only support a single target architecture typically.
1 parent 5a574d3 commit 3b4f9d8

File tree

5 files changed

+257
-22
lines changed

5 files changed

+257
-22
lines changed

src/cargo/util/config/de.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,13 @@ enum KeyKind {
206206
impl<'config> ConfigMapAccess<'config> {
207207
fn new_map(de: Deserializer<'config>) -> Result<ConfigMapAccess<'config>, ConfigError> {
208208
let mut fields = Vec::new();
209-
if let Some(mut v) = de.config.get_table(&de.key)? {
210-
// `v: Value<HashMap<String, CV>>`
211-
for (key, _value) in v.val.drain() {
212-
fields.push(KeyKind::CaseSensitive(key));
209+
let key_is_table = de.config.is_table(&de.key)?;
210+
if key_is_table.is_some() && key_is_table.unwrap() {
211+
if let Some(mut v) = de.config.get_table(&de.key)? {
212+
// `v: Value<HashMap<String, CV>>`
213+
for (key, _value) in v.val.drain() {
214+
fields.push(KeyKind::CaseSensitive(key));
215+
}
213216
}
214217
}
215218
if de.config.cli_unstable().advanced_env {

src/cargo/util/config/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,16 @@ impl Config {
759759
Ok(false)
760760
}
761761

762+
fn has_env_key(&self, key: &ConfigKey) -> bool {
763+
if self.env.contains_key(key.as_env_key()) {
764+
return true;
765+
}
766+
767+
self.check_environment_key_case_mismatch(key);
768+
769+
false
770+
}
771+
762772
fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
763773
if let Some(env_key) = self.upper_case_env.get(key.as_env_key()) {
764774
let _ = self.shell().warn(format!(
@@ -901,6 +911,14 @@ impl Config {
901911
Ok(())
902912
}
903913

914+
fn is_table(&self, key: &ConfigKey) -> CargoResult<Option<bool>> {
915+
match self.get_cv(key)? {
916+
Some(CV::Table(_, _def)) => Ok(Some(true)),
917+
Some(_) => Ok(Some(false)),
918+
None => Ok(None),
919+
}
920+
}
921+
904922
/// Low-level method for getting a config value as an `OptValue<HashMap<String, CV>>`.
905923
///
906924
/// NOTE: This does not read from env. The caller is responsible for that.
@@ -1719,6 +1737,15 @@ impl Config {
17191737
T::deserialize(d).map_err(|e| e.into())
17201738
}
17211739

1740+
pub fn get_env_only<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1741+
let d = Deserializer {
1742+
config: self,
1743+
key: ConfigKey::from_str(key),
1744+
env_prefix_ok: false,
1745+
};
1746+
T::deserialize(d).map_err(|e| e.into())
1747+
}
1748+
17221749
pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path {
17231750
let ret = f.as_path_unlocked();
17241751
assert!(
@@ -2023,6 +2050,13 @@ impl ConfigValue {
20232050
}
20242051
}
20252052

2053+
pub fn is_string(&self) -> CargoResult<bool> {
2054+
match self {
2055+
CV::String(_, _def) => Ok(true),
2056+
_ => Ok(false),
2057+
}
2058+
}
2059+
20262060
pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
20272061
match self {
20282062
CV::Table(table, def) => Ok((table, def)),

src/cargo/util/config/target.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,7 @@ pub(super) fn get_target_applies_to_host(config: &Config) -> CargoResult<bool> {
8585
/// Loads a single `[host]` table for the given triple.
8686
pub(super) fn load_host_triple(config: &Config, triple: &str) -> CargoResult<TargetConfig> {
8787
if config.cli_unstable().host_config {
88-
let host_triple_prefix = format!("host.{}", triple);
89-
let host_triple_key = ConfigKey::from_str(&host_triple_prefix);
90-
let host_prefix = match config.get_cv(&host_triple_key)? {
91-
Some(_) => host_triple_prefix,
92-
None => "host".to_string(),
93-
};
94-
load_config_table(config, &host_prefix)
88+
load_config_table(config, "host", triple)
9589
} else {
9690
Ok(TargetConfig {
9791
runner: None,
@@ -104,21 +98,56 @@ pub(super) fn load_host_triple(config: &Config, triple: &str) -> CargoResult<Tar
10498

10599
/// Loads a single `[target]` table for the given triple.
106100
pub(super) fn load_target_triple(config: &Config, triple: &str) -> CargoResult<TargetConfig> {
107-
load_config_table(config, &format!("target.{}", triple))
101+
load_config_table(config, "target", triple)
102+
}
103+
104+
fn load_config_val<'de, T: serde::de::Deserialize<'de>>(
105+
config: &Config,
106+
key: &str,
107+
prefix: &str,
108+
triple: &str,
109+
) -> CargoResult<T> {
110+
let triple_str = &format!("{}.{}.{}", prefix, triple, key);
111+
let triple_key = &ConfigKey::from_str(triple_str);
112+
if config.has_env_key(triple_key) {
113+
return config.get_env_only(triple_str);
114+
}
115+
116+
let generic_str = &format!("{}.{}", prefix, key);
117+
let generic_key = &ConfigKey::from_str(generic_str);
118+
if config.has_env_key(generic_key) {
119+
return config.get_env_only(generic_str);
120+
}
121+
122+
let generic_table = config.is_table(&ConfigKey::from_str(&format!("{}", prefix)))?;
123+
let triple_table = config.is_table(&ConfigKey::from_str(&format!("{}.{}", prefix, triple)))?;
124+
if !triple_table.is_some() && generic_table.is_some() && generic_table.unwrap() {
125+
return config.get(generic_str);
126+
}
127+
config.get(triple_str)
108128
}
109129

110130
/// Loads a single table for the given prefix.
111-
fn load_config_table(config: &Config, prefix: &str) -> CargoResult<TargetConfig> {
131+
fn load_config_table(config: &Config, prefix: &str, triple: &str) -> CargoResult<TargetConfig> {
112132
// This needs to get each field individually because it cannot fetch the
113133
// struct all at once due to `links_overrides`. Can't use `serde(flatten)`
114134
// because it causes serde to use `deserialize_map` which means the config
115135
// deserializer does not know which keys to deserialize, which means
116136
// environment variables would not work.
117-
let runner: OptValue<PathAndArgs> = config.get(&format!("{}.runner", prefix))?;
118-
let rustflags: OptValue<StringList> = config.get(&format!("{}.rustflags", prefix))?;
119-
let linker: OptValue<ConfigRelativePath> = config.get(&format!("{}.linker", prefix))?;
137+
let runner: OptValue<PathAndArgs> = load_config_val(config, "runner", prefix, triple)?;
138+
let rustflags: OptValue<StringList> = load_config_val(config, "rustflags", prefix, triple)?;
139+
let linker: OptValue<ConfigRelativePath> = load_config_val(config, "linker", prefix, triple)?;
120140
// Links do not support environment variables.
121-
let target_key = ConfigKey::from_str(prefix);
141+
let generic_key = ConfigKey::from_str(&format!("{}", prefix));
142+
let triple_key = ConfigKey::from_str(&format!("{}.{}", prefix, triple));
143+
let generic_table = config.is_table(&generic_key)?;
144+
let triple_table = config.is_table(&triple_key)?;
145+
let target_key = if !triple_table.is_some() && generic_table.is_some() && generic_table.unwrap()
146+
{
147+
generic_key
148+
} else {
149+
triple_key
150+
};
122151
let links_overrides = match config.get_table(&target_key)? {
123152
Some(links) => parse_links_overrides(&target_key, links.val, config)?,
124153
None => BTreeMap::new(),
@@ -150,7 +179,11 @@ fn parse_links_overrides(
150179
_ => {}
151180
}
152181
let mut output = BuildOutput::default();
153-
let table = value.table(&format!("{}.{}", target_key, lib_name))?.0;
182+
let table_key = &format!("{}.{}", target_key, lib_name);
183+
let table = value
184+
.table(table_key)
185+
.map_err(|e| anyhow::anyhow!("invalid configuration for key `{}`\n{}", table_key, e))?
186+
.0;
154187
// We require deterministic order of evaluation, so we must sort the pairs by key first.
155188
let mut pairs = Vec::new();
156189
for (k, value) in table {
@@ -227,8 +260,10 @@ fn parse_links_overrides(
227260
anyhow::bail!("`{}` is not supported in build script overrides", key);
228261
}
229262
_ => {
230-
let val = value.string(key)?.0;
231-
output.metadata.push((key.clone(), val.to_string()));
263+
if value.is_string()? {
264+
let val = value.string(key)?.0;
265+
output.metadata.push((key.clone(), val.to_string()));
266+
}
232267
}
233268
}
234269
}

tests/testsuite/bad_config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ fn bad1() {
1919
.with_status(101)
2020
.with_stderr(
2121
"\
22-
[ERROR] expected table for configuration key `target.nonexistent-target`, \
23-
but found string in [..]config
22+
[ERROR] invalid configuration for key `target.nonexistent-target`
23+
expected a table, but found a string for `target.nonexistent-target` \
24+
in [..]config
2425
",
2526
)
2627
.run();

tests/testsuite/build_script.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,119 @@ fn custom_build_env_var_rustc_linker() {
360360
p.cargo("build --target").arg(&target).run();
361361
}
362362

363+
#[cargo_test]
364+
fn custom_build_env_var_rustc_generic_linker() {
365+
let target = rustc_host();
366+
let p = project()
367+
.file(
368+
".cargo/config",
369+
r#"
370+
[target]
371+
linker = "/path/to/linker"
372+
"#,
373+
)
374+
.file(
375+
"build.rs",
376+
r#"
377+
use std::env;
378+
379+
fn main() {
380+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
381+
}
382+
"#,
383+
)
384+
.file("src/lib.rs", "")
385+
.build();
386+
387+
// no crate type set => linker never called => build succeeds if and
388+
// only if build.rs succeeds, despite linker binary not existing.
389+
if cargo_test_support::is_nightly() {
390+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
391+
.arg(&target)
392+
.masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
393+
.run();
394+
}
395+
}
396+
397+
#[cargo_test]
398+
fn custom_build_env_var_rustc_triple_overrides_generic_linker() {
399+
let target = rustc_host();
400+
let p = project()
401+
.file(
402+
".cargo/config",
403+
&format!(
404+
r#"
405+
[target]
406+
linker = "/path/to/generic/linker"
407+
[target.{}]
408+
linker = "/path/to/linker"
409+
"#,
410+
target
411+
),
412+
)
413+
.file(
414+
"build.rs",
415+
r#"
416+
use std::env;
417+
418+
fn main() {
419+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
420+
}
421+
"#,
422+
)
423+
.file("src/lib.rs", "")
424+
.build();
425+
426+
// no crate type set => linker never called => build succeeds if and
427+
// only if build.rs succeeds, despite linker binary not existing.
428+
if cargo_test_support::is_nightly() {
429+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
430+
.arg(&target)
431+
.masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
432+
.run();
433+
}
434+
}
435+
436+
#[cargo_test]
437+
fn custom_build_env_var_rustc_generic_env_overrides_tables() {
438+
let target = rustc_host();
439+
let p = project()
440+
.file(
441+
".cargo/config",
442+
&format!(
443+
r#"
444+
[target]
445+
linker = "/path/to/generic/table/linker"
446+
[target.{}]
447+
linker = "/path/to/triple/table/linker"
448+
"#,
449+
target
450+
),
451+
)
452+
.file(
453+
"build.rs",
454+
r#"
455+
use std::env;
456+
457+
fn main() {
458+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
459+
}
460+
"#,
461+
)
462+
.file("src/lib.rs", "")
463+
.build();
464+
465+
// no crate type set => linker never called => build succeeds if and
466+
// only if build.rs succeeds, despite linker binary not existing.
467+
if cargo_test_support::is_nightly() {
468+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
469+
.env("CARGO_TARGET_LINKER", "/path/to/linker")
470+
.arg(&target)
471+
.masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
472+
.run();
473+
}
474+
}
475+
363476
#[cargo_test]
364477
fn custom_build_env_var_rustc_linker_bad_host_target() {
365478
let target = rustc_host();
@@ -601,6 +714,55 @@ fn custom_build_linker_bad_host_with_arch() {
601714
.run();
602715
}
603716

717+
#[cargo_test]
718+
fn custom_build_env_var_rustc_linker_bad_host_with_arch_env_override() {
719+
let target = rustc_host();
720+
let p = project()
721+
.file(
722+
".cargo/config",
723+
&format!(
724+
r#"
725+
[host]
726+
linker = "/path/to/host/linker"
727+
[host.{}]
728+
linker = "/path/to/host/arch/linker"
729+
[target.{}]
730+
linker = "/path/to/target/linker"
731+
"#,
732+
target, target
733+
),
734+
)
735+
.file(
736+
"build.rs",
737+
r#"
738+
use std::env;
739+
740+
fn main() {
741+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker"));
742+
}
743+
"#,
744+
)
745+
.file("src/lib.rs", "")
746+
.build();
747+
748+
// build.rs should fail due to bad host linker being set
749+
if cargo_test_support::is_nightly() {
750+
p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
751+
.env("CARGO_HOST_LINKER", "/path/to/env/host/linker")
752+
.arg(&target)
753+
.masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
754+
.with_status(101)
755+
.with_stderr_contains(
756+
"\
757+
[COMPILING] foo v0.0.1 ([CWD])
758+
[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/env/host/linker [..]`
759+
[ERROR] linker `[..]/path/to/env/host/linker` not found
760+
"
761+
)
762+
.run();
763+
}
764+
}
765+
604766
#[cargo_test]
605767
fn custom_build_env_var_rustc_linker_cross_arch_host() {
606768
let target = rustc_host();

0 commit comments

Comments
 (0)