• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Loads "sysroot" crate.
2 //!
3 //! One confusing point here is that normally sysroot is a bunch of `.rlib`s,
4 //! but we can't process `.rlib` and need source code instead. The source code
5 //! is typically installed with `rustup component add rust-src` command.
6 
7 use std::{env, fs, iter, ops, path::PathBuf, process::Command};
8 
9 use anyhow::{format_err, Result};
10 use base_db::CrateName;
11 use la_arena::{Arena, Idx};
12 use paths::{AbsPath, AbsPathBuf};
13 use rustc_hash::FxHashMap;
14 
15 use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath};
16 
17 #[derive(Debug, Clone, Eq, PartialEq)]
18 pub struct Sysroot {
19     root: AbsPathBuf,
20     src_root: AbsPathBuf,
21     crates: Arena<SysrootCrateData>,
22     /// Stores the result of `cargo metadata` of the `RA_UNSTABLE_SYSROOT_HACK` workspace.
23     pub hack_cargo_workspace: Option<CargoWorkspace>,
24 }
25 
26 pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
27 
28 #[derive(Debug, Clone, Eq, PartialEq)]
29 pub struct SysrootCrateData {
30     pub name: String,
31     pub root: ManifestPath,
32     pub deps: Vec<SysrootCrate>,
33 }
34 
35 impl ops::Index<SysrootCrate> for Sysroot {
36     type Output = SysrootCrateData;
index(&self, index: SysrootCrate) -> &SysrootCrateData37     fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
38         &self.crates[index]
39     }
40 }
41 
42 impl Sysroot {
43     /// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
44     /// subfolder live, like:
45     /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu`
root(&self) -> &AbsPath46     pub fn root(&self) -> &AbsPath {
47         &self.root
48     }
49 
50     /// Returns the sysroot "source" directory, where stdlib sources are located, like:
51     /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
src_root(&self) -> &AbsPath52     pub fn src_root(&self) -> &AbsPath {
53         &self.src_root
54     }
55 
public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_56     pub fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
57         // core is added as a dependency before std in order to
58         // mimic rustcs dependency order
59         ["core", "alloc", "std"]
60             .into_iter()
61             .zip(iter::repeat(true))
62             .chain(iter::once(("test", false)))
63             .filter_map(move |(name, prelude)| {
64                 Some((CrateName::new(name).unwrap(), self.by_name(name)?, prelude))
65             })
66     }
67 
proc_macro(&self) -> Option<SysrootCrate>68     pub fn proc_macro(&self) -> Option<SysrootCrate> {
69         self.by_name("proc_macro")
70     }
71 
crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_72     pub fn crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_ {
73         self.crates.iter().map(|(id, _data)| id)
74     }
75 
is_empty(&self) -> bool76     pub fn is_empty(&self) -> bool {
77         self.crates.is_empty()
78     }
79 
loading_warning(&self) -> Option<String>80     pub fn loading_warning(&self) -> Option<String> {
81         if self.by_name("core").is_none() {
82             let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
83                 " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
84             } else {
85                 " try running `rustup component add rust-src` to possible fix this"
86             };
87             Some(format!(
88                 "could not find libcore in loaded sysroot at `{}`{}",
89                 self.src_root.as_path().display(),
90                 var_note,
91             ))
92         } else {
93             None
94         }
95     }
96 }
97 
98 // FIXME: Expose a builder api as loading the sysroot got way too modular and complicated.
99 impl Sysroot {
100     /// Attempts to discover the toolchain's sysroot from the given `dir`.
discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot>101     pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
102         tracing::debug!("discovering sysroot for {}", dir.display());
103         let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
104         let sysroot_src_dir =
105             discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
106         Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
107     }
108 
discover_with_src_override( current_dir: &AbsPath, extra_env: &FxHashMap<String, String>, src: AbsPathBuf, ) -> Result<Sysroot>109     pub fn discover_with_src_override(
110         current_dir: &AbsPath,
111         extra_env: &FxHashMap<String, String>,
112         src: AbsPathBuf,
113     ) -> Result<Sysroot> {
114         tracing::debug!("discovering sysroot for {}", current_dir.display());
115         let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?;
116         Ok(Sysroot::load(sysroot_dir, src))
117     }
118 
discover_rustc(&self) -> Option<ManifestPath>119     pub fn discover_rustc(&self) -> Option<ManifestPath> {
120         get_rustc_src(&self.root)
121     }
122 
with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot>123     pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
124         let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
125             format_err!("can't load standard library from sysroot path {}", sysroot_dir.display())
126         })?;
127         Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
128     }
129 
load(sysroot_dir: AbsPathBuf, mut sysroot_src_dir: AbsPathBuf) -> Sysroot130     pub fn load(sysroot_dir: AbsPathBuf, mut sysroot_src_dir: AbsPathBuf) -> Sysroot {
131         // FIXME: Remove this `hack_cargo_workspace` field completely once we support sysroot dependencies
132         let hack_cargo_workspace = if let Ok(path) = std::env::var("RA_UNSTABLE_SYSROOT_HACK") {
133             let cargo_toml = ManifestPath::try_from(
134                 AbsPathBuf::try_from(&*format!("{path}/Cargo.toml")).unwrap(),
135             )
136             .unwrap();
137             sysroot_src_dir = AbsPathBuf::try_from(&*path).unwrap().join("library");
138             CargoWorkspace::fetch_metadata(
139                 &cargo_toml,
140                 &AbsPathBuf::try_from("/").unwrap(),
141                 &CargoConfig::default(),
142                 &|_| (),
143             )
144             .map(CargoWorkspace::new)
145             .ok()
146         } else {
147             None
148         };
149         let mut sysroot = Sysroot {
150             root: sysroot_dir,
151             src_root: sysroot_src_dir,
152             crates: Arena::default(),
153             hack_cargo_workspace,
154         };
155 
156         for path in SYSROOT_CRATES.trim().lines() {
157             let name = path.split('/').last().unwrap();
158             let root = [format!("{path}/src/lib.rs"), format!("lib{path}/lib.rs")]
159                 .into_iter()
160                 .map(|it| sysroot.src_root.join(it))
161                 .filter_map(|it| ManifestPath::try_from(it).ok())
162                 .find(|it| fs::metadata(it).is_ok());
163 
164             if let Some(root) = root {
165                 sysroot.crates.alloc(SysrootCrateData {
166                     name: name.into(),
167                     root,
168                     deps: Vec::new(),
169                 });
170             }
171         }
172 
173         if let Some(std) = sysroot.by_name("std") {
174             for dep in STD_DEPS.trim().lines() {
175                 if let Some(dep) = sysroot.by_name(dep) {
176                     sysroot.crates[std].deps.push(dep)
177                 }
178             }
179         }
180 
181         if let Some(alloc) = sysroot.by_name("alloc") {
182             for dep in ALLOC_DEPS.trim().lines() {
183                 if let Some(dep) = sysroot.by_name(dep) {
184                     sysroot.crates[alloc].deps.push(dep)
185                 }
186             }
187         }
188 
189         if let Some(proc_macro) = sysroot.by_name("proc_macro") {
190             for dep in PROC_MACRO_DEPS.trim().lines() {
191                 if let Some(dep) = sysroot.by_name(dep) {
192                     sysroot.crates[proc_macro].deps.push(dep)
193                 }
194             }
195         }
196 
197         sysroot
198     }
199 
by_name(&self, name: &str) -> Option<SysrootCrate>200     fn by_name(&self, name: &str) -> Option<SysrootCrate> {
201         let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
202         Some(id)
203     }
204 }
205 
discover_sysroot_dir( current_dir: &AbsPath, extra_env: &FxHashMap<String, String>, ) -> Result<AbsPathBuf>206 fn discover_sysroot_dir(
207     current_dir: &AbsPath,
208     extra_env: &FxHashMap<String, String>,
209 ) -> Result<AbsPathBuf> {
210     let mut rustc = Command::new(toolchain::rustc());
211     rustc.envs(extra_env);
212     rustc.current_dir(current_dir).args(["--print", "sysroot"]);
213     tracing::debug!("Discovering sysroot by {:?}", rustc);
214     let stdout = utf8_stdout(rustc)?;
215     Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
216 }
217 
discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf>218 fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
219     if let Ok(path) = env::var("RUST_SRC_PATH") {
220         if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
221             let core = path.join("core");
222             if fs::metadata(&core).is_ok() {
223                 tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
224                 return Some(path);
225             }
226             tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
227         } else {
228             tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
229         }
230     }
231 
232     get_rust_src(sysroot_path)
233 }
234 
discover_sysroot_src_dir_or_add_component( sysroot_path: &AbsPathBuf, current_dir: &AbsPath, extra_env: &FxHashMap<String, String>, ) -> Result<AbsPathBuf>235 fn discover_sysroot_src_dir_or_add_component(
236     sysroot_path: &AbsPathBuf,
237     current_dir: &AbsPath,
238     extra_env: &FxHashMap<String, String>,
239 ) -> Result<AbsPathBuf> {
240     discover_sysroot_src_dir(sysroot_path)
241         .or_else(|| {
242             let mut rustup = Command::new(toolchain::rustup());
243             rustup.envs(extra_env);
244             rustup.current_dir(current_dir).args(["component", "add", "rust-src"]);
245             tracing::info!("adding rust-src component by {:?}", rustup);
246             utf8_stdout(rustup).ok()?;
247             get_rust_src(sysroot_path)
248         })
249         .ok_or_else(|| {
250             format_err!(
251                 "\
252 can't load standard library from sysroot
253 {}
254 (discovered via `rustc --print sysroot`)
255 try installing the Rust source the same way you installed rustc",
256                 sysroot_path.display(),
257             )
258         })
259 }
260 
get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath>261 fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
262     let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
263     let rustc_src = ManifestPath::try_from(rustc_src).ok()?;
264     tracing::debug!("checking for rustc source code: {}", rustc_src.display());
265     if fs::metadata(&rustc_src).is_ok() {
266         Some(rustc_src)
267     } else {
268         None
269     }
270 }
271 
get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf>272 fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
273     let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
274     tracing::debug!("checking sysroot library: {}", rust_src.display());
275     if fs::metadata(&rust_src).is_ok() {
276         Some(rust_src)
277     } else {
278         None
279     }
280 }
281 
282 const SYSROOT_CRATES: &str = "
283 alloc
284 backtrace
285 core
286 panic_abort
287 panic_unwind
288 proc_macro
289 profiler_builtins
290 std
291 stdarch/crates/std_detect
292 test
293 unwind";
294 
295 const ALLOC_DEPS: &str = "core";
296 
297 const STD_DEPS: &str = "
298 alloc
299 panic_unwind
300 panic_abort
301 core
302 profiler_builtins
303 unwind
304 std_detect
305 test";
306 
307 // core is required for our builtin derives to work in the proc_macro lib currently
308 const PROC_MACRO_DEPS: &str = "
309 std
310 core";
311