]> git.lizzy.rs Git - rust.git/blob - crates/project_model/src/sysroot.rs
4e39d6dd3c64f85c5ee2fa413c4a2b26a99c6a38
[rust.git] / crates / project_model / src / sysroot.rs
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::{convert::TryFrom, env, ops, path::PathBuf, process::Command};
8
9 use anyhow::{format_err, Result};
10 use la_arena::{Arena, Idx};
11 use paths::{AbsPath, AbsPathBuf};
12
13 use crate::utf8_stdout;
14
15 #[derive(Default, Debug, Clone, Eq, PartialEq)]
16 pub struct Sysroot {
17     crates: Arena<SysrootCrateData>,
18 }
19
20 pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
21
22 #[derive(Debug, Clone, Eq, PartialEq)]
23 pub struct SysrootCrateData {
24     pub name: String,
25     pub root: AbsPathBuf,
26     pub deps: Vec<SysrootCrate>,
27 }
28
29 impl ops::Index<SysrootCrate> for Sysroot {
30     type Output = SysrootCrateData;
31     fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
32         &self.crates[index]
33     }
34 }
35
36 impl Sysroot {
37     pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate)> + '_ {
38         // core is added as a dependency before std in order to
39         // mimic rustcs dependency order
40         ["core", "alloc", "std"].iter().filter_map(move |&it| Some((it, self.by_name(it)?)))
41     }
42
43     pub fn proc_macro(&self) -> Option<SysrootCrate> {
44         self.by_name("proc_macro")
45     }
46
47     pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + 'a {
48         self.crates.iter().map(|(id, _data)| id)
49     }
50
51     pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52         log::debug!("Discovering sysroot for {}", cargo_toml.display());
53         let current_dir = cargo_toml.parent().ok_or_else(|| {
54             format_err!("Failed to find the parent directory for {}", cargo_toml.display())
55         })?;
56         let sysroot_dir = discover_sysroot_dir(current_dir)?;
57         let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?;
58         let res = Sysroot::load(&sysroot_src_dir)?;
59         Ok(res)
60     }
61
62     pub fn discover_rustc(cargo_toml: &AbsPath) -> Option<AbsPathBuf> {
63         log::debug!("Discovering rustc source for {}", cargo_toml.display());
64         let current_dir = cargo_toml.parent().unwrap();
65         discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
66     }
67
68     pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
69         let mut sysroot = Sysroot { crates: Arena::default() };
70
71         for name in SYSROOT_CRATES.trim().lines() {
72             let root = [format!("{}/src/lib.rs", name), format!("lib{}/lib.rs", name)]
73                 .iter()
74                 .map(|it| sysroot_src_dir.join(it))
75                 .find(|it| it.exists());
76
77             if let Some(root) = root {
78                 sysroot.crates.alloc(SysrootCrateData {
79                     name: name.into(),
80                     root,
81                     deps: Vec::new(),
82                 });
83             }
84         }
85
86         if let Some(std) = sysroot.by_name("std") {
87             for dep in STD_DEPS.trim().lines() {
88                 if let Some(dep) = sysroot.by_name(dep) {
89                     sysroot.crates[std].deps.push(dep)
90                 }
91             }
92         }
93
94         if let Some(alloc) = sysroot.by_name("alloc") {
95             if let Some(core) = sysroot.by_name("core") {
96                 sysroot.crates[alloc].deps.push(core);
97             }
98         }
99
100         if sysroot.by_name("core").is_none() {
101             let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
102                 " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
103             } else {
104                 ""
105             };
106             anyhow::bail!(
107                 "could not find libcore in sysroot path `{}`{}",
108                 sysroot_src_dir.as_ref().display(),
109                 var_note,
110             );
111         }
112
113         Ok(sysroot)
114     }
115
116     fn by_name(&self, name: &str) -> Option<SysrootCrate> {
117         let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
118         Some(id)
119     }
120 }
121
122 fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
123     let mut rustc = Command::new(toolchain::rustc());
124     rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
125     log::debug!("Discovering sysroot by {:?}", rustc);
126     let stdout = utf8_stdout(rustc)?;
127     Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
128 }
129
130 fn discover_sysroot_src_dir(
131     sysroot_path: &AbsPathBuf,
132     current_dir: &AbsPath,
133 ) -> Result<AbsPathBuf> {
134     if let Ok(path) = env::var("RUST_SRC_PATH") {
135         let path = AbsPathBuf::try_from(path.as_str())
136             .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
137         let core = path.join("core");
138         if core.exists() {
139             log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
140             return Ok(path);
141         }
142         log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
143     }
144
145     get_rust_src(&sysroot_path)
146         .or_else(|| {
147             let mut rustup = Command::new(toolchain::rustup());
148             rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
149             utf8_stdout(rustup).ok()?;
150             get_rust_src(&sysroot_path)
151         })
152         .ok_or_else(|| {
153             format_err!(
154                 "\
155 can't load standard library from sysroot
156 {}
157 (discovered via `rustc --print sysroot`)
158 try installing the Rust source the same way you installed rustc",
159                 sysroot_path.display(),
160             )
161         })
162 }
163
164 fn get_rustc_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
165     let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
166     log::debug!("Checking for rustc source code: {}", rustc_src.display());
167     if rustc_src.exists() {
168         Some(rustc_src)
169     } else {
170         None
171     }
172 }
173
174 fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
175     // Try the new path first since the old one still exists.
176     let rust_src = sysroot_path.join("lib/rustlib/src/rust");
177     log::debug!("Checking sysroot (looking for `library` and `src` dirs): {}", rust_src.display());
178     ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists())
179 }
180
181 impl SysrootCrateData {
182     pub fn root_dir(&self) -> &AbsPath {
183         self.root.parent().unwrap()
184     }
185 }
186
187 const SYSROOT_CRATES: &str = "
188 alloc
189 core
190 panic_abort
191 panic_unwind
192 proc_macro
193 profiler_builtins
194 rtstartup
195 std
196 stdarch
197 term
198 test
199 unwind";
200
201 const STD_DEPS: &str = "
202 alloc
203 core
204 panic_abort
205 panic_unwind
206 profiler_builtins
207 rtstartup
208 proc_macro
209 stdarch
210 term
211 test
212 unwind";