blob: d9c25a4461920d864764845afb3733f1d25f5fbc [file] [log] [blame]
//! Defines an `AnalysisLoader` trait, which allows to specify directories
//! from which save-analysis JSON files can be read. Also supplies a
//! default implementation `CargoAnalysisLoader` for Cargo-emitted save-analysis
//! files.
use std::env;
use std::ffi::OsStr;
use std::fmt;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::AnalysisHost;
#[derive(Debug)]
pub struct CargoAnalysisLoader {
pub path_prefix: Option<PathBuf>,
pub target: Target,
}
#[derive(Debug, new)]
pub struct SearchDirectory {
pub path: PathBuf,
// The directory searched must have spans re-written to be based on a new
// path prefix. This happens for example when the std lib crates are compiled
// on the Rust CI, but live in the user's sysroot directory, this adjustment
// (which happens in `lower_span`) means we have the new source location.
pub prefix_rewrite: Option<PathBuf>,
}
impl CargoAnalysisLoader {
pub fn new(target: Target) -> CargoAnalysisLoader {
CargoAnalysisLoader { path_prefix: None, target }
}
}
/// Allows to specify from where and which analysis files will be considered
/// when reloading data to lower.
pub trait AnalysisLoader: Sized {
fn needs_hard_reload(&self, path_prefix: &Path) -> bool;
fn fresh_host(&self) -> AnalysisHost<Self>;
fn set_path_prefix(&mut self, path_prefix: &Path);
fn abs_path_prefix(&self) -> Option<PathBuf>;
/// Returns every directory in which analysis files are to be considered.
fn search_directories(&self) -> Vec<SearchDirectory>;
}
impl AnalysisLoader for CargoAnalysisLoader {
fn needs_hard_reload(&self, path_prefix: &Path) -> bool {
self.path_prefix.as_ref().map_or(true, |p| p != path_prefix)
}
fn fresh_host(&self) -> AnalysisHost<Self> {
AnalysisHost::new_with_loader(CargoAnalysisLoader {
path_prefix: self.path_prefix.clone(),
..CargoAnalysisLoader::new(self.target)
})
}
fn set_path_prefix(&mut self, path_prefix: &Path) {
self.path_prefix = Some(path_prefix.to_owned());
}
fn abs_path_prefix(&self) -> Option<PathBuf> {
self.path_prefix.as_ref().map(|s| Path::new(s).canonicalize().unwrap().to_owned())
}
fn search_directories(&self) -> Vec<SearchDirectory> {
let path_prefix = self.path_prefix.as_ref().unwrap();
let target = self.target.to_string();
let deps_path =
path_prefix.join("target").join("rls").join(&target).join("deps").join("save-analysis");
// FIXME sys_root_path allows to break out of 'sandbox' - is that Ok?
// FIXME libs_path and src_path both assume the default `libdir = "lib"`.
let sys_root_path = sys_root_path();
let target_triple = extract_target_triple(sys_root_path.as_path());
let libs_path =
sys_root_path.join("lib").join("rustlib").join(&target_triple).join("analysis");
let src_path = sys_root_path.join("lib").join("rustlib").join("src").join("rust");
vec![SearchDirectory::new(libs_path, Some(src_path)), SearchDirectory::new(deps_path, None)]
}
}
fn extract_target_triple(sys_root_path: &Path) -> String {
// First try to get the triple from the rustc version output,
// otherwise fall back on the rustup-style toolchain path.
// FIXME: Both methods assume that the target is the host triple,
// which isn't the case for cross-compilation (rust-lang/rls#309).
extract_rustc_host_triple().unwrap_or_else(|| extract_rustup_target_triple(sys_root_path))
}
fn extract_rustc_host_triple() -> Option<String> {
let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc"));
let verbose_version = Command::new(rustc)
.arg("--verbose")
.arg("--version")
.output()
.ok()
.and_then(|out| String::from_utf8(out.stdout).ok())?;
// Extracts the triple from a line like `host: x86_64-unknown-linux-gnu`
verbose_version
.lines()
.find(|line| line.starts_with("host: "))
.and_then(|host| host.split_whitespace().nth(1))
.map(String::from)
}
// FIXME: This can fail when using a custom toolchain in rustup (often linked to
// `/$rust_repo/build/$target/stage2`)
fn extract_rustup_target_triple(sys_root_path: &Path) -> String {
// Extracts nightly-x86_64-pc-windows-msvc from
// $HOME/.rustup/toolchains/nightly-x86_64-pc-windows-msvc
let toolchain =
sys_root_path.iter().last().and_then(OsStr::to_str).expect("extracting toolchain failed");
// Extracts x86_64-pc-windows-msvc from nightly-x86_64-pc-windows-pc
toolchain.splitn(2, '-').last().map(String::from).expect("extracting triple failed")
}
fn sys_root_path() -> PathBuf {
env::var("SYSROOT")
.ok()
.map(PathBuf::from)
.or_else(|| {
Command::new(env::var("RUSTC").unwrap_or_else(|_| String::from("rustc")))
.arg("--print")
.arg("sysroot")
.output()
.ok()
.and_then(|out| String::from_utf8(out.stdout).ok())
.map(|s| PathBuf::from(s.trim()))
})
.expect("need to specify SYSROOT or RUSTC env vars, or rustc must be in PATH")
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Target {
Release,
Debug,
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Target::Release => write!(f, "release"),
Target::Debug => write!(f, "debug"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn windows_path() {
let path = Path::new(r#"C:\Users\user\.rustup\toolchains\nightly-x86_64-pc-windows-msvc"#);
assert_eq!(extract_rustup_target_triple(path), String::from("x86_64-pc-windows-msvc"));
}
#[test]
fn unix_path() {
let path = Path::new("/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu");
assert_eq!(extract_rustup_target_triple(path), String::from("x86_64-unknown-linux-gnu"));
}
#[test]
fn target_triple() {
let sys_root_path = sys_root_path();
let target_triple = extract_target_triple(&sys_root_path);
let target_path = sys_root_path.join("lib").join("rustlib").join(&target_triple);
assert!(target_path.is_dir(), "{:?} is not a directory!", target_path);
}
}