8774: feat: Honor `.cargo/config.toml` r=matklad a=Veykril

![f1Gup1aiAn](https://user-images.githubusercontent.com/3757771/117545448-1dcaae00-b026-11eb-977a-0f35a5e3f2e0.gif)

Implements `cargo/.config` build target and cfg access by using unstable cargo options:

- `cargo config get` to read the target triple out of the config to pass to `cargo metadata` --filter-platform
- `cargo rustc --print` to read out the `rustc_cfgs`, this causes us to honor `rustflags` and the like.

If those commands fail, due to not having a nightly toolchain present for example, they will fall back to invoking rustc directly as we currently do.

I personally think it should be fine to use these unstable options as they are unlikely to change(even if they did it shouldn't be a problem due to the fallback) and don't burden the user if they do not have a nightly toolchain at hand since we fall back to the previous behaviour.

cc #8741
Closes #6604, Closes #5904, Closes #8430, Closes #8480

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-05-09 10:33:31 +00:00 committed by GitHub
commit 6c0cdc5f55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 37 deletions

View File

@ -201,31 +201,12 @@ impl CargoWorkspace {
if let Some(parent) = cargo_toml.parent() {
meta.current_dir(parent.to_path_buf());
}
let target = if let Some(target) = config.target.as_ref() {
let target = if let Some(target) = &config.target {
Some(target.clone())
} else if let stdout @ Some(_) = cargo_config_build_target(cargo_toml) {
stdout
} else {
// cargo metadata defaults to giving information for _all_ targets.
// In the absence of a preference from the user, we use the host platform.
let mut rustc = Command::new(toolchain::rustc());
rustc.current_dir(cargo_toml.parent().unwrap()).arg("-vV");
log::debug!("Discovering host platform by {:?}", rustc);
match utf8_stdout(rustc) {
Ok(stdout) => {
let field = "host: ";
let target = stdout.lines().find_map(|l| l.strip_prefix(field));
if let Some(target) = target {
Some(target.to_string())
} else {
// If we fail to resolve the host platform, it's not the end of the world.
log::info!("rustc -vV did not report host platform, got:\n{}", stdout);
None
}
}
Err(e) => {
log::warn!("Failed to discover host platform: {}", e);
None
}
}
rustc_discover_host_triple(cargo_toml)
};
if let Some(target) = target {
meta.other_options(vec![String::from("--filter-platform"), target]);
@ -368,3 +349,43 @@ impl CargoWorkspace {
self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
}
}
fn rustc_discover_host_triple(cargo_toml: &AbsPath) -> Option<String> {
let mut rustc = Command::new(toolchain::rustc());
rustc.current_dir(cargo_toml.parent().unwrap()).arg("-vV");
log::debug!("Discovering host platform by {:?}", rustc);
match utf8_stdout(rustc) {
Ok(stdout) => {
let field = "host: ";
let target = stdout.lines().find_map(|l| l.strip_prefix(field));
if let Some(target) = target {
Some(target.to_string())
} else {
// If we fail to resolve the host platform, it's not the end of the world.
log::info!("rustc -vV did not report host platform, got:\n{}", stdout);
None
}
}
Err(e) => {
log::warn!("Failed to discover host platform: {}", e);
None
}
}
}
fn cargo_config_build_target(cargo_toml: &AbsPath) -> Option<String> {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config
.current_dir(cargo_toml.parent().unwrap())
.args(&["-Z", "unstable-options", "config", "get", "build.target"])
.env("RUSTC_BOOTSTRAP", "1");
// if successful we receive `build.target = "target-triple"`
log::debug!("Discovering cargo config target by {:?}", cargo_config);
match utf8_stdout(cargo_config) {
Ok(stdout) => stdout
.strip_prefix("build.target = \"")
.and_then(|stdout| stdout.strip_suffix('"'))
.map(ToOwned::to_owned),
Err(_) => None,
}
}

View File

@ -2,9 +2,12 @@
use std::process::Command;
use anyhow::Result;
use paths::AbsPath;
use crate::{cfg_flag::CfgFlag, utf8_stdout};
pub(crate) fn get(target: Option<&str>) -> Vec<CfgFlag> {
pub(crate) fn get(cargo_toml: Option<&AbsPath>, target: Option<&str>) -> Vec<CfgFlag> {
let _p = profile::span("rustc_cfg::get");
let mut res = Vec::with_capacity(6 * 2 + 1);
@ -16,19 +19,39 @@ pub(crate) fn get(target: Option<&str>) -> Vec<CfgFlag> {
}
}
let rustc_cfgs = {
let mut cmd = Command::new(toolchain::rustc());
cmd.args(&["--print", "cfg", "-O"]);
if let Some(target) = target {
cmd.args(&["--target", target]);
}
utf8_stdout(cmd)
};
match rustc_cfgs {
match get_rust_cfgs(cargo_toml, target) {
Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
}
res
}
fn get_rust_cfgs(cargo_toml: Option<&AbsPath>, target: Option<&str>) -> Result<String> {
let cargo_rust_cfgs = match cargo_toml {
Some(cargo_toml) => {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config
.current_dir(cargo_toml.parent().unwrap())
.args(&["-Z", "unstable-options", "rustc", "--print", "cfg"])
.env("RUSTC_BOOTSTRAP", "1");
if let Some(target) = target {
cargo_config.args(&["--target", target]);
}
utf8_stdout(cargo_config).ok()
}
None => None,
};
match cargo_rust_cfgs {
Some(stdout) => Ok(stdout),
None => {
// using unstable cargo features failed, fall back to using plain rustc
let mut cmd = Command::new(toolchain::rustc());
cmd.args(&["--print", "cfg", "-O"]);
if let Some(target) = target {
cmd.args(&["--target", target]);
}
utf8_stdout(cmd)
}
}
}

View File

@ -143,7 +143,8 @@ impl ProjectWorkspace {
} else {
None
};
let rustc_cfg = rustc_cfg::get(config.target.as_deref());
let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }
}
};
@ -159,7 +160,7 @@ impl ProjectWorkspace {
Some(path) => Some(Sysroot::load(path)?),
None => None,
};
let rustc_cfg = rustc_cfg::get(target);
let rustc_cfg = rustc_cfg::get(None, target);
Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
}
@ -310,7 +311,7 @@ fn project_json_to_crate_graph(
let target_cfgs = match krate.target.as_deref() {
Some(target) => {
cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(Some(target)))
cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
}
None => &rustc_cfg,
};