Skip to content

Commit b794b44

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 4c27c96 commit b794b44

File tree

4 files changed

+254
-20
lines changed

4 files changed

+254
-20
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
@@ -695,6 +695,16 @@ impl Config {
695695
false
696696
}
697697

698+
fn has_env_key(&self, key: &ConfigKey) -> bool {
699+
if self.env.contains_key(key.as_env_key()) {
700+
return true;
701+
}
702+
703+
self.check_environment_key_case_mismatch(key);
704+
705+
false
706+
}
707+
698708
fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
699709
if cfg!(windows) {
700710
// In the case of windows the check for case mismatch in keys can be skipped
@@ -843,6 +853,14 @@ impl Config {
843853
Ok(())
844854
}
845855

856+
fn is_table(&self, key: &ConfigKey) -> CargoResult<Option<bool>> {
857+
match self.get_cv(key)? {
858+
Some(CV::Table(_, _def)) => Ok(Some(true)),
859+
Some(_) => Ok(Some(false)),
860+
None => Ok(None),
861+
}
862+
}
863+
846864
/// Low-level method for getting a config value as a `OptValue<HashMap<String, CV>>`.
847865
///
848866
/// NOTE: This does not read from env. The caller is responsible for that.
@@ -1539,6 +1557,15 @@ impl Config {
15391557
T::deserialize(d).map_err(|e| e.into())
15401558
}
15411559

1560+
pub fn get_env_only<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1561+
let d = Deserializer {
1562+
config: self,
1563+
key: ConfigKey::from_str(key),
1564+
env_prefix_ok: false,
1565+
};
1566+
T::deserialize(d).map_err(|e| e.into())
1567+
}
1568+
15421569
pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path {
15431570
let ret = f.as_path_unlocked();
15441571
assert!(
@@ -1843,6 +1870,13 @@ impl ConfigValue {
18431870
}
18441871
}
18451872

1873+
pub fn is_string(&self) -> CargoResult<bool> {
1874+
match self {
1875+
CV::String(_, _def) => Ok(true),
1876+
_ => Ok(false),
1877+
}
1878+
}
1879+
18461880
pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
18471881
match self {
18481882
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
@@ -86,13 +86,7 @@ pub(super) fn get_target_applies_to_host(config: &Config) -> CargoResult<bool> {
8686
/// Loads a single `[host]` table for the given triple.
8787
pub(super) fn load_host_triple(config: &Config, triple: &str) -> CargoResult<TargetConfig> {
8888
if config.cli_unstable().host_config {
89-
let host_triple_prefix = format!("host.{}", triple);
90-
let host_triple_key = ConfigKey::from_str(&host_triple_prefix);
91-
let host_prefix = match config.get_cv(&host_triple_key)? {
92-
Some(_) => host_triple_prefix,
93-
None => "host".to_string(),
94-
};
95-
load_config_table(config, &host_prefix)
89+
load_config_table(config, "host", triple)
9690
} else {
9791
Ok(TargetConfig {
9892
runner: None,
@@ -105,21 +99,56 @@ pub(super) fn load_host_triple(config: &Config, triple: &str) -> CargoResult<Tar
10599

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

111131
/// Loads a single table for the given prefix.
112-
fn load_config_table(config: &Config, prefix: &str) -> CargoResult<TargetConfig> {
132+
fn load_config_table(config: &Config, prefix: &str, triple: &str) -> CargoResult<TargetConfig> {
113133
// This needs to get each field individually because it cannot fetch the
114134
// struct all at once due to `links_overrides`. Can't use `serde(flatten)`
115135
// because it causes serde to use `deserialize_map` which means the config
116136
// deserializer does not know which keys to deserialize, which means
117137
// environment variables would not work.
118-
let runner: OptValue<PathAndArgs> = config.get(&format!("{}.runner", prefix))?;
119-
let rustflags: OptValue<StringList> = config.get(&format!("{}.rustflags", prefix))?;
120-
let linker: OptValue<ConfigRelativePath> = config.get(&format!("{}.linker", prefix))?;
138+
let runner: OptValue<PathAndArgs> = load_config_val(config, "runner", prefix, triple)?;
139+
let rustflags: OptValue<StringList> = load_config_val(config, "rustflags", prefix, triple)?;
140+
let linker: OptValue<ConfigRelativePath> = load_config_val(config, "linker", prefix, triple)?;
121141
// Links do not support environment variables.
122-
let target_key = ConfigKey::from_str(prefix);
142+
let generic_key = ConfigKey::from_str(&format!("{}", prefix));
143+
let triple_key = ConfigKey::from_str(&format!("{}.{}", prefix, triple));
144+
let generic_table = config.is_table(&generic_key)?;
145+
let triple_table = config.is_table(&triple_key)?;
146+
let target_key = if !triple_table.is_some() && generic_table.is_some() && generic_table.unwrap()
147+
{
148+
generic_key
149+
} else {
150+
triple_key
151+
};
123152
let links_overrides = match config.get_table(&target_key)? {
124153
Some(links) => parse_links_overrides(&target_key, links.val, config)?,
125154
None => BTreeMap::new(),
@@ -148,7 +177,11 @@ fn parse_links_overrides(
148177
_ => {}
149178
}
150179
let mut output = BuildOutput::default();
151-
let table = value.table(&format!("{}.{}", target_key, lib_name))?.0;
180+
let table_key = &format!("{}.{}", target_key, lib_name);
181+
let table = value
182+
.table(table_key)
183+
.map_err(|e| anyhow::anyhow!("invalid configuration for key `{}`\n{}", table_key, e))?
184+
.0;
152185
// We require deterministic order of evaluation, so we must sort the pairs by key first.
153186
let mut pairs = Vec::new();
154187
for (k, value) in table {
@@ -219,8 +252,10 @@ fn parse_links_overrides(
219252
anyhow::bail!("`{}` is not supported in build script overrides", key);
220253
}
221254
_ => {
222-
let val = value.string(key)?.0;
223-
output.metadata.push((key.clone(), val.to_string()));
255+
if value.is_string()? {
256+
let val = value.string(key)?.0;
257+
output.metadata.push((key.clone(), val.to_string()));
258+
}
224259
}
225260
}
226261
}

tests/testsuite/build_script.rs

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

168+
#[cargo_test]
169+
fn custom_build_env_var_rustc_generic_linker() {
170+
let target = rustc_host();
171+
let p = project()
172+
.file(
173+
".cargo/config",
174+
r#"
175+
[target]
176+
linker = "/path/to/linker"
177+
"#,
178+
)
179+
.file(
180+
"build.rs",
181+
r#"
182+
use std::env;
183+
184+
fn main() {
185+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
186+
}
187+
"#,
188+
)
189+
.file("src/lib.rs", "")
190+
.build();
191+
192+
// no crate type set => linker never called => build succeeds if and
193+
// only if build.rs succeeds, despite linker binary not existing.
194+
if cargo_test_support::is_nightly() {
195+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
196+
.arg(&target)
197+
.masquerade_as_nightly_cargo()
198+
.run();
199+
}
200+
}
201+
202+
#[cargo_test]
203+
fn custom_build_env_var_rustc_triple_overrides_generic_linker() {
204+
let target = rustc_host();
205+
let p = project()
206+
.file(
207+
".cargo/config",
208+
&format!(
209+
r#"
210+
[target]
211+
linker = "/path/to/generic/linker"
212+
[target.{}]
213+
linker = "/path/to/linker"
214+
"#,
215+
target
216+
),
217+
)
218+
.file(
219+
"build.rs",
220+
r#"
221+
use std::env;
222+
223+
fn main() {
224+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
225+
}
226+
"#,
227+
)
228+
.file("src/lib.rs", "")
229+
.build();
230+
231+
// no crate type set => linker never called => build succeeds if and
232+
// only if build.rs succeeds, despite linker binary not existing.
233+
if cargo_test_support::is_nightly() {
234+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
235+
.arg(&target)
236+
.masquerade_as_nightly_cargo()
237+
.run();
238+
}
239+
}
240+
241+
#[cargo_test]
242+
fn custom_build_env_var_rustc_generic_env_overrides_tables() {
243+
let target = rustc_host();
244+
let p = project()
245+
.file(
246+
".cargo/config",
247+
&format!(
248+
r#"
249+
[target]
250+
linker = "/path/to/generic/table/linker"
251+
[target.{}]
252+
linker = "/path/to/triple/table/linker"
253+
"#,
254+
target
255+
),
256+
)
257+
.file(
258+
"build.rs",
259+
r#"
260+
use std::env;
261+
262+
fn main() {
263+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
264+
}
265+
"#,
266+
)
267+
.file("src/lib.rs", "")
268+
.build();
269+
270+
// no crate type set => linker never called => build succeeds if and
271+
// only if build.rs succeeds, despite linker binary not existing.
272+
if cargo_test_support::is_nightly() {
273+
p.cargo("build -Z target-applies-to-host -Z host-config --target")
274+
.env("CARGO_TARGET_LINKER", "/path/to/linker")
275+
.arg(&target)
276+
.masquerade_as_nightly_cargo()
277+
.run();
278+
}
279+
}
280+
168281
#[cargo_test]
169282
fn custom_build_env_var_rustc_linker_bad_host_target() {
170283
let target = rustc_host();
@@ -464,6 +577,55 @@ fn custom_build_env_var_rustc_linker_bad_host_with_arch() {
464577
}
465578
}
466579

580+
#[cargo_test]
581+
fn custom_build_env_var_rustc_linker_bad_host_with_arch_env_override() {
582+
let target = rustc_host();
583+
let p = project()
584+
.file(
585+
".cargo/config",
586+
&format!(
587+
r#"
588+
[host]
589+
linker = "/path/to/host/linker"
590+
[host.{}]
591+
linker = "/path/to/host/arch/linker"
592+
[target.{}]
593+
linker = "/path/to/target/linker"
594+
"#,
595+
target, target
596+
),
597+
)
598+
.file(
599+
"build.rs",
600+
r#"
601+
use std::env;
602+
603+
fn main() {
604+
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker"));
605+
}
606+
"#,
607+
)
608+
.file("src/lib.rs", "")
609+
.build();
610+
611+
// build.rs should fail due to bad host linker being set
612+
if cargo_test_support::is_nightly() {
613+
p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
614+
.env("CARGO_HOST_LINKER", "/path/to/env/host/linker")
615+
.arg(&target)
616+
.masquerade_as_nightly_cargo()
617+
.with_status(101)
618+
.with_stderr_contains(
619+
"\
620+
[COMPILING] foo v0.0.1 ([CWD])
621+
[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/env/host/linker [..]`
622+
[ERROR] linker `[..]/path/to/env/host/linker` not found
623+
"
624+
)
625+
.run();
626+
}
627+
}
628+
467629
#[cargo_test]
468630
fn custom_build_env_var_rustc_linker_cross_arch_host() {
469631
let target = rustc_host();

0 commit comments

Comments
 (0)