1use 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 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
36pub 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 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 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 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 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
111fn extract_rustup_target_triple(sys_root_path: &Path) -> String {
114 let toolchain =
117 sys_root_path.iter().last().and_then(OsStr::to_str).expect("extracting toolchain failed");
118 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}