4726: Groundwork for specifying the set of projects via config r=matklad a=matklad



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-06-03 10:26:38 +00:00 committed by GitHub
commit 9b3d4be421
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 65 deletions

View File

@ -14,7 +14,7 @@ use std::{
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use ra_cfg::CfgOptions; use ra_cfg::CfgOptions;
use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
use rustc_hash::FxHashMap; use rustc_hash::{FxHashMap, FxHashSet};
use serde_json::from_reader; use serde_json::from_reader;
pub use crate::{ pub use crate::{
@ -57,25 +57,25 @@ impl PackageRoot {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ProjectRoot { pub enum ProjectManifest {
ProjectJson(PathBuf), ProjectJson(PathBuf),
CargoToml(PathBuf), CargoToml(PathBuf),
} }
impl ProjectRoot { impl ProjectManifest {
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> { pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> {
if path.ends_with("rust-project.json") { if path.ends_with("rust-project.json") {
return Ok(ProjectRoot::ProjectJson(path)); return Ok(ProjectManifest::ProjectJson(path));
} }
if path.ends_with("Cargo.toml") { if path.ends_with("Cargo.toml") {
return Ok(ProjectRoot::CargoToml(path)); return Ok(ProjectManifest::CargoToml(path));
} }
bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
} }
pub fn discover_single(path: &Path) -> Result<ProjectRoot> { pub fn discover_single(path: &Path) -> Result<ProjectManifest> {
let mut candidates = ProjectRoot::discover(path)?; let mut candidates = ProjectManifest::discover(path)?;
let res = match candidates.pop() { let res = match candidates.pop() {
None => bail!("no projects"), None => bail!("no projects"),
Some(it) => it, Some(it) => it,
@ -87,12 +87,12 @@ impl ProjectRoot {
Ok(res) Ok(res)
} }
pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> {
if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
return Ok(vec![ProjectRoot::ProjectJson(project_json)]); return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
} }
return find_cargo_toml(path) return find_cargo_toml(path)
.map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
match find_in_parent_dirs(path, "Cargo.toml") { match find_in_parent_dirs(path, "Cargo.toml") {
@ -128,16 +128,28 @@ impl ProjectRoot {
.collect() .collect()
} }
} }
pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> {
let mut res = paths
.iter()
.filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
.flatten()
.collect::<FxHashSet<_>>()
.into_iter()
.collect::<Vec<_>>();
res.sort();
res
}
} }
impl ProjectWorkspace { impl ProjectWorkspace {
pub fn load( pub fn load(
root: ProjectRoot, root: ProjectManifest,
cargo_features: &CargoConfig, cargo_features: &CargoConfig,
with_sysroot: bool, with_sysroot: bool,
) -> Result<ProjectWorkspace> { ) -> Result<ProjectWorkspace> {
let res = match root { let res = match root {
ProjectRoot::ProjectJson(project_json) => { ProjectManifest::ProjectJson(project_json) => {
let file = File::open(&project_json).with_context(|| { let file = File::open(&project_json).with_context(|| {
format!("Failed to open json file {}", project_json.display()) format!("Failed to open json file {}", project_json.display())
})?; })?;
@ -148,7 +160,7 @@ impl ProjectWorkspace {
})?, })?,
} }
} }
ProjectRoot::CargoToml(cargo_toml) => { ProjectManifest::CargoToml(cargo_toml) => {
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
.with_context(|| { .with_context(|| {
format!( format!(

View File

@ -4,9 +4,14 @@
mod args; mod args;
use lsp_server::Connection; use lsp_server::Connection;
use rust_analyzer::{cli, config::Config, from_json, Result}; use rust_analyzer::{
cli,
config::{Config, LinkedProject},
from_json, Result,
};
use crate::args::HelpPrinted; use crate::args::HelpPrinted;
use ra_project_model::ProjectManifest;
fn main() -> Result<()> { fn main() -> Result<()> {
setup_logging()?; setup_logging()?;
@ -97,17 +102,6 @@ fn run_server() -> Result<()> {
log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
} }
let cwd = std::env::current_dir()?;
let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
let workspace_roots = initialize_params
.workspace_folders
.map(|workspaces| {
workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::<Vec<_>>()
})
.filter(|workspaces| !workspaces.is_empty())
.unwrap_or_else(|| vec![root]);
let config = { let config = {
let mut config = Config::default(); let mut config = Config::default();
if let Some(value) = &initialize_params.initialization_options { if let Some(value) = &initialize_params.initialization_options {
@ -115,10 +109,31 @@ fn run_server() -> Result<()> {
} }
config.update_caps(&initialize_params.capabilities); config.update_caps(&initialize_params.capabilities);
if config.linked_projects.is_empty() {
let cwd = std::env::current_dir()?;
let root =
initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
let workspace_roots = initialize_params
.workspace_folders
.map(|workspaces| {
workspaces
.into_iter()
.filter_map(|it| it.uri.to_file_path().ok())
.collect::<Vec<_>>()
})
.filter(|workspaces| !workspaces.is_empty())
.unwrap_or_else(|| vec![root]);
config.linked_projects = ProjectManifest::discover_all(&workspace_roots)
.into_iter()
.map(LinkedProject::from)
.collect();
}
config config
}; };
rust_analyzer::main_loop(workspace_roots, config, connection)?; rust_analyzer::main_loop(config, connection)?;
log::info!("shutting down IO..."); log::info!("shutting down IO...");
io_threads.join()?; io_threads.join()?;

View File

@ -8,7 +8,8 @@ use crossbeam_channel::{unbounded, Receiver};
use ra_db::{ExternSourceId, FileId, SourceRootId}; use ra_db::{ExternSourceId, FileId, SourceRootId};
use ra_ide::{AnalysisChange, AnalysisHost}; use ra_ide::{AnalysisChange, AnalysisHost};
use ra_project_model::{ use ra_project_model::{
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest,
ProjectWorkspace,
}; };
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@ -28,7 +29,7 @@ pub fn load_cargo(
with_proc_macro: bool, with_proc_macro: bool,
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { ) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
let root = std::env::current_dir()?.join(root); let root = std::env::current_dir()?.join(root);
let root = ProjectRoot::discover_single(&root)?; let root = ProjectManifest::discover_single(&root)?;
let ws = ProjectWorkspace::load( let ws = ProjectWorkspace::load(
root, root,
&CargoConfig { load_out_dirs_from_check, ..Default::default() }, &CargoConfig { load_out_dirs_from_check, ..Default::default() },

View File

@ -12,14 +12,13 @@ use std::{ffi::OsString, path::PathBuf};
use lsp_types::ClientCapabilities; use lsp_types::ClientCapabilities;
use ra_flycheck::FlycheckConfig; use ra_flycheck::FlycheckConfig;
use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
use ra_project_model::CargoConfig; use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
pub client_caps: ClientCapsConfig, pub client_caps: ClientCapsConfig,
pub with_sysroot: bool,
pub publish_diagnostics: bool, pub publish_diagnostics: bool,
pub lru_capacity: Option<usize>, pub lru_capacity: Option<usize>,
pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>, pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
@ -35,6 +34,27 @@ pub struct Config {
pub assist: AssistConfig, pub assist: AssistConfig,
pub call_info_full: bool, pub call_info_full: bool,
pub lens: LensConfig, pub lens: LensConfig,
pub with_sysroot: bool,
pub linked_projects: Vec<LinkedProject>,
}
#[derive(Debug, Clone)]
pub enum LinkedProject {
ProjectManifest(ProjectManifest),
JsonProject(JsonProject),
}
impl From<ProjectManifest> for LinkedProject {
fn from(v: ProjectManifest) -> Self {
LinkedProject::ProjectManifest(v)
}
}
impl From<JsonProject> for LinkedProject {
fn from(v: JsonProject) -> Self {
LinkedProject::JsonProject(v)
}
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -141,6 +161,7 @@ impl Default for Config {
assist: AssistConfig::default(), assist: AssistConfig::default(),
call_info_full: true, call_info_full: true,
lens: LensConfig::default(), lens: LensConfig::default(),
linked_projects: Vec::new(),
} }
} }
} }

View File

@ -12,13 +12,11 @@ use std::{
fmt, fmt,
ops::Range, ops::Range,
panic, panic,
path::PathBuf,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
use itertools::Itertools;
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
use lsp_types::{ use lsp_types::{
DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
@ -36,7 +34,7 @@ use serde::{de::DeserializeOwned, Serialize};
use threadpool::ThreadPool; use threadpool::ThreadPool;
use crate::{ use crate::{
config::{Config, FilesWatcher}, config::{Config, FilesWatcher, LinkedProject},
diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
from_proto, from_proto,
global_state::{GlobalState, GlobalStateSnapshot}, global_state::{GlobalState, GlobalStateSnapshot},
@ -70,7 +68,7 @@ impl fmt::Display for LspError {
impl Error for LspError {} impl Error for LspError {}
pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> { pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
log::info!("initial config: {:#?}", config); log::info!("initial config: {:#?}", config);
// Windows scheduler implements priority boosts: if thread waits for an // Windows scheduler implements priority boosts: if thread waits for an
@ -95,29 +93,24 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
let mut loop_state = LoopState::default(); let mut loop_state = LoopState::default();
let mut global_state = { let mut global_state = {
let workspaces = { let workspaces = {
// FIXME: support dynamic workspace loading. if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
let project_roots: FxHashSet<_> = ws_roots
.iter()
.filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
.flatten()
.collect();
if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
show_message( show_message(
lsp_types::MessageType::Error, lsp_types::MessageType::Error,
format!( "rust-analyzer failed to discover workspace".to_string(),
"rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
),
&connection.sender, &connection.sender,
); );
}; };
project_roots config
.into_iter() .linked_projects
.iter()
.filter_map(|project| match project {
LinkedProject::ProjectManifest(it) => Some(it),
LinkedProject::JsonProject(_) => None,
})
.filter_map(|root| { .filter_map(|root| {
ra_project_model::ProjectWorkspace::load( ra_project_model::ProjectWorkspace::load(
root, root.clone(),
&config.cargo, &config.cargo,
config.with_sysroot, config.with_sysroot,
) )

View File

@ -19,8 +19,9 @@ use serde_json::{to_string_pretty, Value};
use tempfile::TempDir; use tempfile::TempDir;
use test_utils::{find_mismatch, parse_fixture}; use test_utils::{find_mismatch, parse_fixture};
use ra_project_model::ProjectManifest;
use rust_analyzer::{ use rust_analyzer::{
config::{ClientCapsConfig, Config}, config::{ClientCapsConfig, Config, LinkedProject},
main_loop, main_loop,
}; };
@ -42,7 +43,7 @@ impl<'a> Project<'a> {
self self
} }
pub fn root(mut self, path: &str) -> Project<'a> { pub(crate) fn root(mut self, path: &str) -> Project<'a> {
self.roots.push(path.into()); self.roots.push(path.into());
self self
} }
@ -74,7 +75,16 @@ impl<'a> Project<'a> {
paths.push((path, entry.text)); paths.push((path, entry.text));
} }
let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); let mut roots =
self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::<Vec<_>>();
if roots.is_empty() {
roots.push(tmp_dir.path().to_path_buf());
}
let linked_projects = roots
.into_iter()
.map(|it| ProjectManifest::discover_single(&it).unwrap())
.map(LinkedProject::from)
.collect::<Vec<_>>();
let mut config = Config { let mut config = Config {
client_caps: ClientCapsConfig { client_caps: ClientCapsConfig {
@ -84,6 +94,7 @@ impl<'a> Project<'a> {
..Default::default() ..Default::default()
}, },
with_sysroot: self.with_sysroot, with_sysroot: self.with_sysroot,
linked_projects,
..Config::default() ..Config::default()
}; };
@ -91,7 +102,7 @@ impl<'a> Project<'a> {
f(&mut config) f(&mut config)
} }
Server::new(tmp_dir, config, roots, paths) Server::new(tmp_dir, config, paths)
} }
} }
@ -109,20 +120,12 @@ pub struct Server {
} }
impl Server { impl Server {
fn new( fn new(dir: TempDir, config: Config, files: Vec<(PathBuf, String)>) -> Server {
dir: TempDir,
config: Config,
roots: Vec<PathBuf>,
files: Vec<(PathBuf, String)>,
) -> Server {
let path = dir.path().to_path_buf();
let roots = if roots.is_empty() { vec![path] } else { roots };
let (connection, client) = Connection::memory(); let (connection, client) = Connection::memory();
let _thread = jod_thread::Builder::new() let _thread = jod_thread::Builder::new()
.name("test server".to_string()) .name("test server".to_string())
.spawn(move || main_loop(roots, config, connection).unwrap()) .spawn(move || main_loop(config, connection).unwrap())
.expect("failed to spawn a thread"); .expect("failed to spawn a thread");
let res = let res =