1use core::fmt;
8use std::{env, fs, ops::Not, path::Path, process::Command};
9
10use anyhow::{Result, format_err};
11use itertools::Itertools;
12use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
13use rustc_hash::FxHashMap;
14use stdx::format_to;
15use toolchain::{Tool, probe_for_binary};
16
17use crate::{
18 CargoWorkspace, ManifestPath, ProjectJson, RustSourceWorkspaceConfig,
19 cargo_workspace::{CargoMetadataConfig, FetchMetadata},
20 utf8_stdout,
21};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Sysroot {
25 root: Option<AbsPathBuf>,
26 rust_lib_src_root: Option<AbsPathBuf>,
27 workspace: RustLibSrcWorkspace,
28 error: Option<String>,
29}
30
31#[derive(Debug, Clone, Eq, PartialEq)]
32pub enum RustLibSrcWorkspace {
33 Workspace(CargoWorkspace),
34 Json(ProjectJson),
35 Stitched(stitched::Stitched),
36 Empty,
37}
38
39impl fmt::Display for RustLibSrcWorkspace {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 RustLibSrcWorkspace::Workspace(ws) => write!(f, "workspace {}", ws.workspace_root()),
43 RustLibSrcWorkspace::Json(json) => write!(f, "json {}", json.manifest_or_root()),
44 RustLibSrcWorkspace::Stitched(stitched) => {
45 write!(f, "stitched with {} crates", stitched.crates.len())
46 }
47 RustLibSrcWorkspace::Empty => write!(f, "empty"),
48 }
49 }
50}
51
52impl Sysroot {
53 pub const fn empty() -> Sysroot {
54 Sysroot {
55 root: None,
56 rust_lib_src_root: None,
57 workspace: RustLibSrcWorkspace::Empty,
58 error: None,
59 }
60 }
61
62 pub fn root(&self) -> Option<&AbsPath> {
66 self.root.as_deref()
67 }
68
69 pub fn rust_lib_src_root(&self) -> Option<&AbsPath> {
72 self.rust_lib_src_root.as_deref()
73 }
74
75 pub fn is_rust_lib_src_empty(&self) -> bool {
76 match &self.workspace {
77 RustLibSrcWorkspace::Workspace(ws) => ws.packages().next().is_none(),
78 RustLibSrcWorkspace::Json(project_json) => project_json.n_crates() == 0,
79 RustLibSrcWorkspace::Stitched(stitched) => stitched.crates.is_empty(),
80 RustLibSrcWorkspace::Empty => true,
81 }
82 }
83
84 pub fn error(&self) -> Option<&str> {
85 self.error.as_deref()
86 }
87
88 pub fn num_packages(&self) -> usize {
89 match &self.workspace {
90 RustLibSrcWorkspace::Workspace(ws) => ws.packages().count(),
91 RustLibSrcWorkspace::Json(project_json) => project_json.n_crates(),
92 RustLibSrcWorkspace::Stitched(stitched) => stitched.crates.len(),
93 RustLibSrcWorkspace::Empty => 0,
94 }
95 }
96
97 pub(crate) fn workspace(&self) -> &RustLibSrcWorkspace {
98 &self.workspace
99 }
100}
101
102impl Sysroot {
103 pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, Option<String>>) -> Sysroot {
105 let sysroot_dir = discover_sysroot_dir(dir, extra_env);
106 let rust_lib_src_dir = sysroot_dir.as_ref().ok().map(|sysroot_dir| {
107 discover_rust_lib_src_dir_or_add_component(sysroot_dir, dir, extra_env)
108 });
109 Sysroot::assemble(Some(sysroot_dir), rust_lib_src_dir)
110 }
111
112 pub fn discover_with_src_override(
113 current_dir: &AbsPath,
114 extra_env: &FxHashMap<String, Option<String>>,
115 rust_lib_src_dir: AbsPathBuf,
116 ) -> Sysroot {
117 let sysroot_dir = discover_sysroot_dir(current_dir, extra_env);
118 Sysroot::assemble(Some(sysroot_dir), Some(Ok(rust_lib_src_dir)))
119 }
120
121 pub fn discover_rust_lib_src_dir(sysroot_dir: AbsPathBuf) -> Sysroot {
122 let rust_lib_src_dir = discover_rust_lib_src_dir(&sysroot_dir)
123 .ok_or_else(|| format_err!("can't find standard library sources in {sysroot_dir}"));
124 Sysroot::assemble(Some(Ok(sysroot_dir)), Some(rust_lib_src_dir))
125 }
126
127 pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
128 get_rustc_src(self.root()?)
129 }
130
131 pub fn new(sysroot_dir: Option<AbsPathBuf>, rust_lib_src_dir: Option<AbsPathBuf>) -> Sysroot {
132 Self::assemble(sysroot_dir.map(Ok), rust_lib_src_dir.map(Ok))
133 }
134
135 pub fn tool(
137 &self,
138 tool: Tool,
139 current_dir: impl AsRef<Path>,
140 envs: &FxHashMap<String, Option<String>>,
141 ) -> Command {
142 match self.root() {
143 Some(root) => {
144 if let Tool::Rustc = tool
147 && let Some(path) =
148 probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into())
149 {
150 return toolchain::command(path, current_dir, envs);
151 }
152
153 let mut cmd = toolchain::command(tool.prefer_proxy(), current_dir, envs);
154 if !envs.contains_key("RUSTUP_TOOLCHAIN")
155 && std::env::var_os("RUSTUP_TOOLCHAIN").is_none()
156 {
157 cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(root));
158 }
159
160 cmd
161 }
162 _ => toolchain::command(tool.path(), current_dir, envs),
163 }
164 }
165
166 pub fn discover_proc_macro_srv(&self) -> Option<anyhow::Result<AbsPathBuf>> {
167 let root = self.root()?;
168 Some(
169 ["libexec", "lib"]
170 .into_iter()
171 .map(|segment| root.join(segment).join("rust-analyzer-proc-macro-srv"))
172 .find_map(|server_path| probe_for_binary(server_path.into()))
173 .map(AbsPathBuf::assert)
174 .ok_or_else(|| {
175 anyhow::format_err!("cannot find proc-macro server in sysroot `{}`", root)
176 }),
177 )
178 }
179
180 fn assemble(
181 sysroot_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
182 rust_lib_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
183 ) -> Sysroot {
184 let mut errors = String::new();
185 let root = match sysroot_dir {
186 Some(Ok(sysroot_dir)) => Some(sysroot_dir),
187 Some(Err(e)) => {
188 format_to!(errors, "{e}\n");
189 None
190 }
191 None => None,
192 };
193 let rust_lib_src_root = match rust_lib_src_dir {
194 Some(Ok(rust_lib_src_dir)) => Some(rust_lib_src_dir),
195 Some(Err(e)) => {
196 format_to!(errors, "{e}\n");
197 None
198 }
199 None => None,
200 };
201 Sysroot {
202 root,
203 rust_lib_src_root,
204 workspace: RustLibSrcWorkspace::Empty,
205 error: errors.is_empty().not().then_some(errors),
206 }
207 }
208
209 pub fn load_workspace(
210 &self,
211 sysroot_source_config: &RustSourceWorkspaceConfig,
212 no_deps: bool,
213 current_dir: &AbsPath,
214 target_dir: &Utf8Path,
215 progress: &dyn Fn(String),
216 ) -> Option<RustLibSrcWorkspace> {
217 assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded");
218 let Self { root: _, rust_lib_src_root: Some(src_root), workspace: _, error: _ } = self
219 else {
220 return None;
221 };
222 if let RustSourceWorkspaceConfig::CargoMetadata(cargo_config) = sysroot_source_config {
223 let library_manifest = ManifestPath::try_from(src_root.join("Cargo.toml")).unwrap();
224 if fs::metadata(&library_manifest).is_ok() {
225 match self.load_library_via_cargo(
226 &library_manifest,
227 current_dir,
228 target_dir,
229 cargo_config,
230 no_deps,
231 progress,
232 ) {
233 Ok(loaded) => return Some(loaded),
234 Err(e) => {
235 tracing::error!("`cargo metadata` failed on `{library_manifest}` : {e}")
236 }
237 }
238 }
239 tracing::debug!("Stitching sysroot library: {src_root}");
240
241 let mut stitched = stitched::Stitched { crates: Default::default() };
242
243 for path in stitched::SYSROOT_CRATES.trim().lines() {
244 let name = path.split('/').next_back().unwrap();
245 let root = [format!("{path}/src/lib.rs"), format!("lib{path}/lib.rs")]
246 .into_iter()
247 .map(|it| src_root.join(it))
248 .filter_map(|it| ManifestPath::try_from(it).ok())
249 .find(|it| fs::metadata(it).is_ok());
250
251 if let Some(root) = root {
252 stitched.crates.alloc(stitched::RustLibSrcCrateData {
253 name: name.into(),
254 root,
255 deps: Vec::new(),
256 });
257 }
258 }
259
260 if let Some(std) = stitched.by_name("std") {
261 for dep in stitched::STD_DEPS.trim().lines() {
262 if let Some(dep) = stitched.by_name(dep) {
263 stitched.crates[std].deps.push(dep)
264 }
265 }
266 }
267
268 if let Some(alloc) = stitched.by_name("alloc") {
269 for dep in stitched::ALLOC_DEPS.trim().lines() {
270 if let Some(dep) = stitched.by_name(dep) {
271 stitched.crates[alloc].deps.push(dep)
272 }
273 }
274 }
275
276 if let Some(proc_macro) = stitched.by_name("proc_macro") {
277 for dep in stitched::PROC_MACRO_DEPS.trim().lines() {
278 if let Some(dep) = stitched.by_name(dep) {
279 stitched.crates[proc_macro].deps.push(dep)
280 }
281 }
282 }
283 return Some(RustLibSrcWorkspace::Stitched(stitched));
284 } else if let RustSourceWorkspaceConfig::Json(project_json) = sysroot_source_config {
285 return Some(RustLibSrcWorkspace::Json(project_json.clone()));
286 }
287
288 None
289 }
290
291 pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) {
292 self.workspace = workspace;
293 if self.error.is_none()
294 && let Some(src_root) = &self.rust_lib_src_root
295 {
296 let has_core = match &self.workspace {
297 RustLibSrcWorkspace::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
298 RustLibSrcWorkspace::Json(project_json) => project_json
299 .crates()
300 .filter_map(|(_, krate)| krate.display_name.clone())
301 .any(|name| name.canonical_name().as_str() == "core"),
302 RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
303 RustLibSrcWorkspace::Empty => true,
304 };
305 if !has_core {
306 let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
307 " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
308 } else {
309 ", try running `rustup component add rust-src` to possibly fix this"
310 };
311 self.error =
312 Some(format!("sysroot at `{src_root}` is missing a `core` library{var_note}",));
313 }
314 }
315 }
316
317 fn load_library_via_cargo(
318 &self,
319 library_manifest: &ManifestPath,
320 current_dir: &AbsPath,
321 target_dir: &Utf8Path,
322 cargo_config: &CargoMetadataConfig,
323 no_deps: bool,
324 progress: &dyn Fn(String),
325 ) -> Result<RustLibSrcWorkspace> {
326 tracing::debug!("Loading library metadata: {library_manifest}");
327 let mut cargo_config = cargo_config.clone();
328 cargo_config.extra_env.insert(
330 "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS".to_owned(),
331 Some("nightly".to_owned()),
332 );
333
334 let locked = true;
336 let (mut res, _) =
337 FetchMetadata::new(library_manifest, current_dir, &cargo_config, self, no_deps)
338 .exec(target_dir, locked, progress)?;
339
340 let patches = {
343 let mut fake_core = None;
344 let mut fake_alloc = None;
345 let mut fake_std = None;
346 let mut real_core = None;
347 let mut real_alloc = None;
348 let mut real_std = None;
349 res.packages.iter().enumerate().for_each(|(idx, package)| {
350 match package.name.strip_prefix("rustc-std-workspace-") {
351 Some("core") => fake_core = Some((idx, package.id.clone())),
352 Some("alloc") => fake_alloc = Some((idx, package.id.clone())),
353 Some("std") => fake_std = Some((idx, package.id.clone())),
354 Some(_) => {
355 tracing::warn!("unknown rustc-std-workspace-* crate: {}", package.name)
356 }
357 None => match &**package.name {
358 "core" => real_core = Some(package.id.clone()),
359 "alloc" => real_alloc = Some(package.id.clone()),
360 "std" => real_std = Some(package.id.clone()),
361 _ => (),
362 },
363 }
364 });
365
366 [fake_core.zip(real_core), fake_alloc.zip(real_alloc), fake_std.zip(real_std)]
367 .into_iter()
368 .flatten()
369 };
370
371 if let Some(resolve) = res.resolve.as_mut() {
372 resolve.nodes.retain_mut(|node| {
373 node.deps.iter_mut().for_each(|dep| {
375 let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg);
376 if let Some((_, real)) = real_pkg {
377 dep.pkg = real;
378 }
379 });
380 !patches.clone().any(|((_, fake), _)| fake == node.id)
382 });
383 }
384 patches.map(|((idx, _), _)| idx).sorted().rev().for_each(|idx| {
386 res.packages.remove(idx);
387 });
388
389 let cargo_workspace =
390 CargoWorkspace::new(res, library_manifest.clone(), Default::default(), true);
391 Ok(RustLibSrcWorkspace::Workspace(cargo_workspace))
392 }
393}
394
395fn discover_sysroot_dir(
396 current_dir: &AbsPath,
397 extra_env: &FxHashMap<String, Option<String>>,
398) -> Result<AbsPathBuf> {
399 let mut rustc = toolchain::command(Tool::Rustc.path(), current_dir, extra_env);
400 rustc.current_dir(current_dir).args(["--print", "sysroot"]);
401 tracing::debug!("Discovering sysroot by {:?}", rustc);
402 let stdout = utf8_stdout(&mut rustc)?;
403 Ok(AbsPathBuf::assert(Utf8PathBuf::from(stdout)))
404}
405
406fn discover_rust_lib_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
407 if let Ok(path) = env::var("RUST_SRC_PATH") {
408 if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
409 let core = path.join("core");
410 if fs::metadata(&core).is_ok() {
411 tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {path}");
412 return Some(path);
413 }
414 tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {core:?}), ignoring");
415 } else {
416 tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
417 }
418 }
419
420 get_rust_lib_src(sysroot_path)
421}
422
423fn discover_rust_lib_src_dir_or_add_component(
424 sysroot_path: &AbsPathBuf,
425 current_dir: &AbsPath,
426 extra_env: &FxHashMap<String, Option<String>>,
427) -> Result<AbsPathBuf> {
428 discover_rust_lib_src_dir(sysroot_path)
429 .or_else(|| {
430 let mut rustup = toolchain::command(Tool::Rustup.prefer_proxy(), current_dir, extra_env);
431 rustup.args(["component", "add", "rust-src"]);
432 tracing::info!("adding rust-src component by {:?}", rustup);
433 utf8_stdout(&mut rustup).ok()?;
434 get_rust_lib_src(sysroot_path)
435 })
436 .ok_or_else(|| {
437 tracing::error!(%sysroot_path, "can't load standard library, try installing `rust-src`");
438 format_err!(
439 "\
440can't load standard library from sysroot
441{sysroot_path}
442(discovered via `rustc --print sysroot`)
443try installing `rust-src` the same way you installed `rustc`"
444 )
445 })
446}
447
448fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
449 let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
450 let rustc_src = ManifestPath::try_from(rustc_src).ok()?;
451 tracing::debug!("checking for rustc source code: {rustc_src}");
452 if fs::metadata(&rustc_src).is_ok() { Some(rustc_src) } else { None }
453}
454
455fn get_rust_lib_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
456 let rust_lib_src = sysroot_path.join("lib/rustlib/src/rust/library");
457 tracing::debug!("checking sysroot library: {rust_lib_src}");
458 if fs::metadata(&rust_lib_src).is_ok() { Some(rust_lib_src) } else { None }
459}
460
461pub(crate) mod stitched {
463 use std::ops;
464
465 use base_db::CrateName;
466 use la_arena::{Arena, Idx};
467
468 use crate::ManifestPath;
469
470 #[derive(Debug, Clone, Eq, PartialEq)]
471 pub struct Stitched {
472 pub(super) crates: Arena<RustLibSrcCrateData>,
473 }
474
475 impl ops::Index<RustLibSrcCrate> for Stitched {
476 type Output = RustLibSrcCrateData;
477 fn index(&self, index: RustLibSrcCrate) -> &RustLibSrcCrateData {
478 &self.crates[index]
479 }
480 }
481
482 impl Stitched {
483 pub(crate) fn public_deps(
484 &self,
485 ) -> impl Iterator<Item = (CrateName, RustLibSrcCrate, bool)> + '_ {
486 [("core", true), ("alloc", false), ("std", true), ("test", false)]
489 .into_iter()
490 .filter_map(move |(name, prelude)| {
491 Some((CrateName::new(name).unwrap(), self.by_name(name)?, prelude))
492 })
493 }
494
495 pub(crate) fn proc_macro(&self) -> Option<RustLibSrcCrate> {
496 self.by_name("proc_macro")
497 }
498
499 pub(crate) fn crates(&self) -> impl ExactSizeIterator<Item = RustLibSrcCrate> + '_ {
500 self.crates.iter().map(|(id, _data)| id)
501 }
502
503 pub(super) fn by_name(&self, name: &str) -> Option<RustLibSrcCrate> {
504 let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
505 Some(id)
506 }
507 }
508
509 pub(crate) type RustLibSrcCrate = Idx<RustLibSrcCrateData>;
510
511 #[derive(Debug, Clone, Eq, PartialEq)]
512 pub(crate) struct RustLibSrcCrateData {
513 pub(crate) name: String,
514 pub(crate) root: ManifestPath,
515 pub(crate) deps: Vec<RustLibSrcCrate>,
516 }
517
518 pub(super) const SYSROOT_CRATES: &str = "
519alloc
520backtrace
521core
522panic_abort
523panic_unwind
524proc_macro
525profiler_builtins
526std
527stdarch/crates/std_detect
528test
529unwind";
530
531 pub(super) const ALLOC_DEPS: &str = "core";
532
533 pub(super) const STD_DEPS: &str = "
534alloc
535panic_unwind
536panic_abort
537core
538profiler_builtins
539unwind
540std_detect
541test";
542
543 pub(super) const PROC_MACRO_DEPS: &str = "
545std
546core";
547}