rls_analysis/
loader.rs

1//! Defines an `AnalysisLoader` trait, which allows to specify directories
2//! from which save-analysis JSON files can be read. Also supplies a
3//! default implementation `CargoAnalysisLoader` for Cargo-emitted save-analysis
4//! files.
5
6use std::env;
7use std::ffi::OsStr;
8use std::fmt;
9use std::path::{Path, PathBuf};
10use std::process::Command;
11
12use crate::AnalysisHost;
13
14#[derive(Debug)]
15pub struct CargoAnalysisLoader {
16    pub path_prefix: Option<PathBuf>,
17    pub target: Target,
18}
19
20#[derive(Debug, new)]
21pub struct SearchDirectory {
22    pub path: PathBuf,
23    // The directory searched must have spans re-written to be based on a new
24    // path prefix. This happens for example when the std lib crates are compiled
25    // on the Rust CI, but live in the user's sysroot directory, this adjustment
26    // (which happens in `lower_span`) means we have the new source location.
27    pub prefix_rewrite: Option<PathBuf>,
28}
29
30impl CargoAnalysisLoader {
31    pub fn new(target: Target) -> CargoAnalysisLoader {
32        CargoAnalysisLoader { path_prefix: None, target }
33    }
34}
35
36/// Allows to specify from where and which analysis files will be considered
37/// when reloading data to lower.
38pub trait AnalysisLoader: Sized {
39    fn needs_hard_reload(&self, path_prefix: &Path) -> bool;
40    fn fresh_host(&self) -> AnalysisHost<Self>;
41    fn set_path_prefix(&mut self, path_prefix: &Path);
42    fn abs_path_prefix(&self) -> Option<PathBuf>;
43    /// Returns every directory in which analysis files are to be considered.
44    fn search_directories(&self) -> Vec<SearchDirectory>;
45}
46
47impl AnalysisLoader for CargoAnalysisLoader {
48    fn needs_hard_reload(&self, path_prefix: &Path) -> bool {
49        self.path_prefix.as_ref().map_or(true, |p| p != path_prefix)
50    }
51
52    fn fresh_host(&self) -> AnalysisHost<Self> {
53        AnalysisHost::new_with_loader(CargoAnalysisLoader {
54            path_prefix: self.path_prefix.clone(),
55            ..CargoAnalysisLoader::new(self.target)
56        })
57    }
58
59    fn set_path_prefix(&mut self, path_prefix: &Path) {
60        self.path_prefix = Some(path_prefix.to_owned());
61    }
62
63    fn abs_path_prefix(&self) -> Option<PathBuf> {
64        self.path_prefix.as_ref().map(|s| Path::new(s).canonicalize().unwrap().to_owned())
65    }
66
67    fn search_directories(&self) -> Vec<SearchDirectory> {
68        let path_prefix = self.path_prefix.as_ref().unwrap();
69        let target = self.target.to_string();
70
71        let deps_path =
72            path_prefix.join("target").join("rls").join(&target).join("deps").join("save-analysis");
73        // FIXME sys_root_path allows to break out of 'sandbox' - is that Ok?
74        // FIXME libs_path and src_path both assume the default `libdir = "lib"`.
75        let sys_root_path = sys_root_path();
76        let target_triple = extract_target_triple(sys_root_path.as_path());
77        let libs_path =
78            sys_root_path.join("lib").join("rustlib").join(&target_triple).join("analysis");
79
80        let src_path = sys_root_path.join("lib").join("rustlib").join("src").join("rust");
81
82        vec![SearchDirectory::new(libs_path, Some(src_path)), SearchDirectory::new(deps_path, None)]
83    }
84}
85
86fn extract_target_triple(sys_root_path: &Path) -> String {
87    // First try to get the triple from the rustc version output,
88    // otherwise fall back on the rustup-style toolchain path.
89    // FIXME: Both methods assume that the target is the host triple,
90    // which isn't the case for cross-compilation (rust-lang/rls#309).
91    extract_rustc_host_triple().unwrap_or_else(|| extract_rustup_target_triple(sys_root_path))
92}
93
94fn extract_rustc_host_triple() -> Option<String> {
95    let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc"));
96    let verbose_version = Command::new(rustc)
97        .arg("--verbose")
98        .arg("--version")
99        .output()
100        .ok()
101        .and_then(|out| String::from_utf8(out.stdout).ok())?;
102
103    // Extracts the triple from a line like `host: x86_64-unknown-linux-gnu`
104    verbose_version
105        .lines()
106        .find(|line| line.starts_with("host: "))
107        .and_then(|host| host.split_whitespace().nth(1))
108        .map(String::from)
109}
110
111// FIXME: This can fail when using a custom toolchain in rustup (often linked to
112// `/$rust_repo/build/$target/stage2`)
113fn extract_rustup_target_triple(sys_root_path: &Path) -> String {
114    // Extracts nightly-x86_64-pc-windows-msvc from
115    // $HOME/.rustup/toolchains/nightly-x86_64-pc-windows-msvc
116    let toolchain =
117        sys_root_path.iter().last().and_then(OsStr::to_str).expect("extracting toolchain failed");
118    // Extracts x86_64-pc-windows-msvc from nightly-x86_64-pc-windows-pc
119    toolchain.splitn(2, '-').last().map(String::from).expect("extracting triple failed")
120}
121
122fn sys_root_path() -> PathBuf {
123    env::var("SYSROOT")
124        .ok()
125        .map(PathBuf::from)
126        .or_else(|| {
127            Command::new(env::var("RUSTC").unwrap_or_else(|_| String::from("rustc")))
128                .arg("--print")
129                .arg("sysroot")
130                .output()
131                .ok()
132                .and_then(|out| String::from_utf8(out.stdout).ok())
133                .map(|s| PathBuf::from(s.trim()))
134        })
135        .expect("need to specify SYSROOT or RUSTC env vars, or rustc must be in PATH")
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139pub enum Target {
140    Release,
141    Debug,
142}
143
144impl fmt::Display for Target {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match *self {
147            Target::Release => write!(f, "release"),
148            Target::Debug => write!(f, "debug"),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use std::path::Path;
157
158    #[test]
159    fn windows_path() {
160        let path = Path::new(r#"C:\Users\user\.rustup\toolchains\nightly-x86_64-pc-windows-msvc"#);
161        assert_eq!(extract_rustup_target_triple(path), String::from("x86_64-pc-windows-msvc"));
162    }
163
164    #[test]
165    fn unix_path() {
166        let path = Path::new("/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu");
167        assert_eq!(extract_rustup_target_triple(path), String::from("x86_64-unknown-linux-gnu"));
168    }
169
170    #[test]
171    fn target_triple() {
172        let sys_root_path = sys_root_path();
173        let target_triple = extract_target_triple(&sys_root_path);
174        let target_path = sys_root_path.join("lib").join("rustlib").join(&target_triple);
175        assert!(target_path.is_dir(), "{:?} is not a directory!", target_path);
176    }
177}