]> git.lizzy.rs Git - rust.git/blob - crates/project_model/src/sysroot.rs
3b0ff506d49a1914cdf13fd97c4bd1ce01baccbc
[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().unwrap();
54         let sysroot_dir = discover_sysroot_dir(current_dir)?;
55         let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?;
56         let res = Sysroot::load(&sysroot_src_dir)?;
57         Ok(res)
58     }
59
60     pub fn discover_rustc(cargo_toml: &AbsPath) -> Option<AbsPathBuf> {
61         log::debug!("Discovering rustc source for {}", cargo_toml.display());
62         let current_dir = cargo_toml.parent().unwrap();
63         discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
64     }
65
66     pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
67         let mut sysroot = Sysroot { crates: Arena::default() };
68
69         for name in SYSROOT_CRATES.trim().lines() {
70             let root = [format!("{}/src/lib.rs", name), format!("lib{}/lib.rs", name)]
71                 .iter()
72                 .map(|it| sysroot_src_dir.join(it))
73                 .find(|it| it.exists());
74
75             if let Some(root) = root {
76                 sysroot.crates.alloc(SysrootCrateData {
77                     name: name.into(),
78                     root,
79                     deps: Vec::new(),
80                 });
81             }
82         }
83
84         if let Some(std) = sysroot.by_name("std") {
85             for dep in STD_DEPS.trim().lines() {
86                 if let Some(dep) = sysroot.by_name(dep) {
87                     sysroot.crates[std].deps.push(dep)
88                 }
89             }
90         }
91
92         if let Some(alloc) = sysroot.by_name("alloc") {
93             if let Some(core) = sysroot.by_name("core") {
94                 sysroot.crates[alloc].deps.push(core);
95             }
96         }
97
98         if sysroot.by_name("core").is_none() {
99             let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
100                 " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
101             } else {
102                 ""
103             };
104             anyhow::bail!(
105                 "could not find libcore in sysroot path `{}`{}",
106                 sysroot_src_dir.as_ref().display(),
107                 var_note,
108             );
109         }
110
111         Ok(sysroot)
112     }
113
114     fn by_name(&self, name: &str) -> Option<SysrootCrate> {
115         let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
116         Some(id)
117     }
118 }
119
120 fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
121     let mut rustc = Command::new(toolchain::rustc());
122     rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
123     log::debug!("Discovering sysroot by {:?}", rustc);
124     let stdout = utf8_stdout(rustc)?;
125     Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
126 }
127
128 fn discover_sysroot_src_dir(
129     sysroot_path: &AbsPathBuf,
130     current_dir: &AbsPath,
131 ) -> Result<AbsPathBuf> {
132     if let Ok(path) = env::var("RUST_SRC_PATH") {
133         let path = AbsPathBuf::try_from(path.as_str())
134             .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
135         let core = path.join("core");
136         if core.exists() {
137             log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
138             return Ok(path);
139         }
140         log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
141     }
142
143     get_rust_src(&sysroot_path)
144         .or_else(|| {
145             let mut rustup = Command::new(toolchain::rustup());
146             rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
147             utf8_stdout(rustup).ok()?;
148             get_rust_src(&sysroot_path)
149         })
150         .ok_or_else(|| {
151             format_err!(
152                 "\
153 can't load standard library from sysroot
154 {}
155 (discovered via `rustc --print sysroot`)
156 try installing the Rust source the same way you installed rustc",
157                 sysroot_path.display(),
158             )
159         })
160 }
161
162 fn get_rustc_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
163     let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
164     log::debug!("Checking for rustc source code: {}", rustc_src.display());
165     if rustc_src.exists() {
166         Some(rustc_src)
167     } else {
168         None
169     }
170 }
171
172 fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
173     // Try the new path first since the old one still exists.
174     let rust_src = sysroot_path.join("lib/rustlib/src/rust");
175     log::debug!("Checking sysroot (looking for `library` and `src` dirs): {}", rust_src.display());
176     ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists())
177 }
178
179 impl SysrootCrateData {
180     pub fn root_dir(&self) -> &AbsPath {
181         self.root.parent().unwrap()
182     }
183 }
184
185 const SYSROOT_CRATES: &str = "
186 alloc
187 core
188 panic_abort
189 panic_unwind
190 proc_macro
191 profiler_builtins
192 rtstartup
193 std
194 stdarch
195 term
196 test
197 unwind";
198
199 const STD_DEPS: &str = "
200 alloc
201 core
202 panic_abort
203 panic_unwind
204 profiler_builtins
205 rtstartup
206 proc_macro
207 stdarch
208 term
209 test
210 unwind";