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