Skip to content

Commit c405509

Browse files
committed
Auto merge of rust-lang#15560 - davidbarsky:davidbarsky/use-sysroot-rustc-to-determine-cfgs, r=Veykril
project-model: when using `rust-project.json`, prefer the sysroot-defined rustc over discovery in `$PATH` At the moment, rust-analyzer discovers `rustc` via the `$PATH` even if the `sysroot` field is defined in a `rust-project.json`. However, this does not work for users who do not have rustup installed, resulting in any `cfg`-based inference in rust-analzyer not working correctly. In my (decently naive!) opinion, it makes more sense to rely on the `sysroot` field in the `rust-project.json`. One might ask "why not add `rustc` to the `$PATH`?" That is a reasonable question, but that doesn't work for my use case: - The path to the sysroot in my employer's monorepo changes depending on which platform a user is on. For example, if they're on Linux, they'd want to use the sysroot defined at path `a`, whereas if they're on macOS, they'd want to use the sysroot at path `b` (I wrote the sysroot resolution functionality [here](https://github.com/facebook/buck2/blob/765da4ca1e0b97a0de1e82b2fb3af52766fd06f4/integrations/rust-project/src/sysroot.rs#L39), if you're curious). - The location of the sysroot can (and does!) change, especially as people figure out how to make Rust run successfully on non-Linux platforms (e.g., iOS, Android, etc.) in a monorepo. Updating people's `$PATH` company-wide is hard while updating a config inside a CLI is pretty easy. ## Testing I've created a `rust-project.json` using [rust-project](https://github.com/facebook/buck2/tree/main/integrations/rust-project) and was able to successfully load a project with and without the `sysroot`/`sysroot_src` fields—without those fields, rust-analyzer fell back to the `$PATH` based approach, as evidenced by `[DEBUG project_model::rustc_cfg] using rustc from env rustc="rustc"` showing up in the logs.
2 parents c31d418 + 0bf6ffa commit c405509

File tree

3 files changed

+112
-47
lines changed

3 files changed

+112
-47
lines changed

crates/project-model/src/rustc_cfg.rs

+62-29
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@
22
33
use std::process::Command;
44

5+
use anyhow::Context;
56
use rustc_hash::FxHashMap;
67

7-
use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath};
8+
use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot};
9+
10+
/// Determines how `rustc --print cfg` is discovered and invoked.
11+
///
12+
/// There options are supported:
13+
/// - [`RustcCfgConfig::Cargo`], which relies on `cargo rustc --print cfg`
14+
/// and `RUSTC_BOOTSTRAP`.
15+
/// - [`RustcCfgConfig::Explicit`], which uses an explicit path to the `rustc`
16+
/// binary in the sysroot.
17+
/// - [`RustcCfgConfig::Discover`], which uses [`toolchain::rustc`].
18+
pub(crate) enum RustcCfgConfig<'a> {
19+
Cargo(&'a ManifestPath),
20+
Explicit(&'a Sysroot),
21+
Discover,
22+
}
823

924
pub(crate) fn get(
10-
cargo_toml: Option<&ManifestPath>,
1125
target: Option<&str>,
1226
extra_env: &FxHashMap<String, String>,
27+
config: RustcCfgConfig<'_>,
1328
) -> Vec<CfgFlag> {
1429
let _p = profile::span("rustc_cfg::get");
1530
let mut res = Vec::with_capacity(6 * 2 + 1);
@@ -25,49 +40,67 @@ pub(crate) fn get(
2540
// Add miri cfg, which is useful for mir eval in stdlib
2641
res.push(CfgFlag::Atom("miri".into()));
2742

28-
match get_rust_cfgs(cargo_toml, target, extra_env) {
43+
let rustc_cfgs = get_rust_cfgs(target, extra_env, config);
44+
45+
let rustc_cfgs = match rustc_cfgs {
46+
Ok(cfgs) => cfgs,
47+
Err(e) => {
48+
tracing::error!(?e, "failed to get rustc cfgs");
49+
return res;
50+
}
51+
};
52+
53+
let rustc_cfgs =
54+
rustc_cfgs.lines().map(|it| it.parse::<CfgFlag>()).collect::<Result<Vec<_>, _>>();
55+
56+
match rustc_cfgs {
2957
Ok(rustc_cfgs) => {
30-
tracing::debug!(
31-
"rustc cfgs found: {:?}",
32-
rustc_cfgs
33-
.lines()
34-
.map(|it| it.parse::<CfgFlag>().map(|it| it.to_string()))
35-
.collect::<Vec<_>>()
36-
);
37-
res.extend(rustc_cfgs.lines().filter_map(|it| it.parse().ok()));
58+
tracing::debug!(?rustc_cfgs, "rustc cfgs found");
59+
res.extend(rustc_cfgs);
60+
}
61+
Err(e) => {
62+
tracing::error!(?e, "failed to get rustc cfgs")
3863
}
39-
Err(e) => tracing::error!("failed to get rustc cfgs: {e:?}"),
4064
}
4165

4266
res
4367
}
4468

4569
fn get_rust_cfgs(
46-
cargo_toml: Option<&ManifestPath>,
4770
target: Option<&str>,
4871
extra_env: &FxHashMap<String, String>,
72+
config: RustcCfgConfig<'_>,
4973
) -> anyhow::Result<String> {
50-
if let Some(cargo_toml) = cargo_toml {
51-
let mut cargo_config = Command::new(toolchain::cargo());
52-
cargo_config.envs(extra_env);
53-
cargo_config
54-
.current_dir(cargo_toml.parent())
55-
.args(["rustc", "-Z", "unstable-options", "--print", "cfg"])
56-
.env("RUSTC_BOOTSTRAP", "1");
57-
if let Some(target) = target {
58-
cargo_config.args(["--target", target]);
74+
let mut cmd = match config {
75+
RustcCfgConfig::Cargo(cargo_toml) => {
76+
let mut cmd = Command::new(toolchain::cargo());
77+
cmd.envs(extra_env);
78+
cmd.current_dir(cargo_toml.parent())
79+
.args(["rustc", "-Z", "unstable-options", "--print", "cfg"])
80+
.env("RUSTC_BOOTSTRAP", "1");
81+
if let Some(target) = target {
82+
cmd.args(["--target", target]);
83+
}
84+
85+
return utf8_stdout(cmd).context("Unable to run `cargo rustc`");
5986
}
60-
match utf8_stdout(cargo_config) {
61-
Ok(it) => return Ok(it),
62-
Err(e) => tracing::debug!("{e:?}: falling back to querying rustc for cfgs"),
87+
RustcCfgConfig::Explicit(sysroot) => {
88+
let rustc: std::path::PathBuf = sysroot.discover_rustc()?.into();
89+
tracing::debug!(?rustc, "using explicit rustc from sysroot");
90+
Command::new(rustc)
6391
}
64-
}
65-
// using unstable cargo features failed, fall back to using plain rustc
66-
let mut cmd = Command::new(toolchain::rustc());
92+
RustcCfgConfig::Discover => {
93+
let rustc = toolchain::rustc();
94+
tracing::debug!(?rustc, "using rustc from env");
95+
Command::new(rustc)
96+
}
97+
};
98+
6799
cmd.envs(extra_env);
68100
cmd.args(["--print", "cfg", "-O"]);
69101
if let Some(target) = target {
70102
cmd.args(["--target", target]);
71103
}
72-
utf8_stdout(cmd)
104+
105+
utf8_stdout(cmd).context("Unable to run `rustc`")
73106
}

crates/project-model/src/sysroot.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,19 @@ impl Sysroot {
115115
Ok(Sysroot::load(sysroot_dir, src))
116116
}
117117

118-
pub fn discover_rustc(&self) -> Option<ManifestPath> {
118+
pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
119119
get_rustc_src(&self.root)
120120
}
121121

122+
pub fn discover_rustc(&self) -> Result<AbsPathBuf, std::io::Error> {
123+
let rustc = self.root.join("bin/rustc");
124+
tracing::debug!(?rustc, "checking for rustc binary at location");
125+
match fs::metadata(&rustc) {
126+
Ok(_) => Ok(rustc),
127+
Err(e) => Err(e),
128+
}
129+
}
130+
122131
pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
123132
let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
124133
format_err!("can't load standard library from sysroot path {sysroot_dir}")

crates/project-model/src/workspace.rs

+40-17
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::{
2121
cargo_workspace::{DepKind, PackageData, RustLibSource},
2222
cfg_flag::CfgFlag,
2323
project_json::Crate,
24-
rustc_cfg,
24+
rustc_cfg::{self, RustcCfgConfig},
2525
sysroot::SysrootCrate,
2626
target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
2727
Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
@@ -240,9 +240,9 @@ impl ProjectWorkspace {
240240
Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
241241
.map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
242242
Some(RustLibSource::Discover) => {
243-
sysroot.as_ref().ok().and_then(Sysroot::discover_rustc).ok_or_else(|| {
244-
Some(format!("Failed to discover rustc source for sysroot."))
245-
})
243+
sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else(
244+
|| Some(format!("Failed to discover rustc source for sysroot.")),
245+
)
246246
}
247247
None => Err(None),
248248
};
@@ -279,8 +279,11 @@ impl ProjectWorkspace {
279279
}
280280
});
281281

282-
let rustc_cfg =
283-
rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env);
282+
let rustc_cfg = rustc_cfg::get(
283+
config.target.as_deref(),
284+
&config.extra_env,
285+
RustcCfgConfig::Cargo(cargo_toml),
286+
);
284287

285288
let cfg_overrides = config.cfg_overrides.clone();
286289
let data_layout = target_data_layout::get(
@@ -331,11 +334,18 @@ impl ProjectWorkspace {
331334
}
332335
(None, None) => Err(None),
333336
};
334-
if let Ok(sysroot) = &sysroot {
335-
tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
336-
}
337+
let config = match &sysroot {
338+
Ok(sysroot) => {
339+
tracing::debug!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
340+
RustcCfgConfig::Explicit(sysroot)
341+
}
342+
Err(_) => {
343+
tracing::debug!("discovering sysroot");
344+
RustcCfgConfig::Discover
345+
}
346+
};
337347

338-
let rustc_cfg = rustc_cfg::get(None, target, extra_env);
348+
let rustc_cfg = rustc_cfg::get(target, extra_env, config);
339349
ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain }
340350
}
341351

@@ -357,10 +367,18 @@ impl ProjectWorkspace {
357367
}
358368
None => Err(None),
359369
};
360-
if let Ok(sysroot) = &sysroot {
361-
tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
362-
}
363-
let rustc_cfg = rustc_cfg::get(None, None, &Default::default());
370+
let rustc_config = match &sysroot {
371+
Ok(sysroot) => {
372+
tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
373+
RustcCfgConfig::Explicit(sysroot)
374+
}
375+
Err(_) => {
376+
tracing::info!("discovering sysroot");
377+
RustcCfgConfig::Discover
378+
}
379+
};
380+
381+
let rustc_cfg = rustc_cfg::get(None, &FxHashMap::default(), rustc_config);
364382
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
365383
}
366384

@@ -755,9 +773,14 @@ fn project_json_to_crate_graph(
755773
let env = env.clone().into_iter().collect();
756774

757775
let target_cfgs = match target.as_deref() {
758-
Some(target) => cfg_cache
759-
.entry(target)
760-
.or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)),
776+
Some(target) => cfg_cache.entry(target).or_insert_with(|| {
777+
let rustc_cfg = match sysroot {
778+
Some(sysroot) => RustcCfgConfig::Explicit(sysroot),
779+
None => RustcCfgConfig::Discover,
780+
};
781+
782+
rustc_cfg::get(Some(target), extra_env, rustc_cfg)
783+
}),
761784
None => &rustc_cfg,
762785
};
763786

0 commit comments

Comments
 (0)