Merge pull request #3582 from kiljacken/out-dir-from-check

Update OUT_DIR based on `cargo check` output
This commit is contained in:
Aleksey Kladov 2020-03-17 15:42:02 +01:00 committed by GitHub
commit ec1312ef38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 309 additions and 170 deletions

1
Cargo.lock generated
View file

@ -1092,6 +1092,7 @@ dependencies = [
"cargo_metadata", "cargo_metadata",
"log", "log",
"ra_arena", "ra_arena",
"ra_cargo_watch",
"ra_cfg", "ra_cfg",
"ra_db", "ra_db",
"rustc-hash", "rustc-hash",

View file

@ -9,8 +9,8 @@ use lsp_types::{
}; };
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
path::PathBuf, path::{Path, PathBuf},
process::{Command, Stdio}, process::{Child, Command, Stdio},
thread::JoinHandle, thread::JoinHandle,
time::Instant, time::Instant,
}; };
@ -246,43 +246,24 @@ enum CheckEvent {
End, End,
} }
impl WatchThread { pub fn run_cargo(
fn dummy() -> WatchThread { args: &[String],
WatchThread { handle: None, message_recv: never() } current_dir: Option<&Path>,
on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
) -> Child {
let mut command = Command::new("cargo");
if let Some(current_dir) = current_dir {
command.current_dir(current_dir);
} }
fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread { let mut child = command
let mut args: Vec<String> = vec![ .args(args)
options.command.clone(),
"--workspace".to_string(),
"--message-format=json".to_string(),
"--manifest-path".to_string(),
format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
];
if options.all_targets {
args.push("--all-targets".to_string());
}
args.extend(options.args.iter().cloned());
let (message_send, message_recv) = unbounded();
let enabled = options.enable;
let handle = std::thread::spawn(move || {
if !enabled {
return;
}
let mut command = Command::new("cargo")
.args(&args)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::null()) .stderr(Stdio::null())
.stdin(Stdio::null()) .stdin(Stdio::null())
.spawn() .spawn()
.expect("couldn't launch cargo"); .expect("couldn't launch cargo");
// If we trigger an error here, we will do so in the loop instead,
// which will break out of the loop, and continue the shutdown
let _ = message_send.send(CheckEvent::Begin);
// We manually read a line at a time, instead of using serde's // We manually read a line at a time, instead of using serde's
// stream deserializers, because the deserializer cannot recover // stream deserializers, because the deserializer cannot recover
// from an error, resulting in it getting stuck, because we try to // from an error, resulting in it getting stuck, because we try to
@ -291,7 +272,7 @@ impl WatchThread {
// Because cargo only outputs one JSON object per line, we can // Because cargo only outputs one JSON object per line, we can
// simply skip a line if it doesn't parse, which just ignores any // simply skip a line if it doesn't parse, which just ignores any
// erroneus output. // erroneus output.
let stdout = BufReader::new(command.stdout.take().unwrap()); let stdout = BufReader::new(child.stdout.take().unwrap());
for line in stdout.lines() { for line in stdout.lines() {
let line = match line { let line = match line {
Ok(line) => line, Ok(line) => line,
@ -305,20 +286,51 @@ impl WatchThread {
let message = match message { let message = match message {
Ok(message) => message, Ok(message) => message,
Err(err) => { Err(err) => {
log::error!( log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
"Invalid json from cargo check, ignoring ({}): {:?} ",
err,
line
);
continue; continue;
} }
}; };
if !on_message(message) {
break;
}
}
child
}
impl WatchThread {
fn dummy() -> WatchThread {
WatchThread { handle: None, message_recv: never() }
}
fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
let mut args: Vec<String> = vec![
options.command.clone(),
"--workspace".to_string(),
"--message-format=json".to_string(),
"--manifest-path".to_string(),
format!("{}/Cargo.toml", workspace_root.display()),
];
if options.all_targets {
args.push("--all-targets".to_string());
}
args.extend(options.args.iter().cloned());
let (message_send, message_recv) = unbounded();
let workspace_root = workspace_root.to_owned();
let handle = if options.enable {
Some(std::thread::spawn(move || {
// If we trigger an error here, we will do so in the loop instead,
// which will break out of the loop, and continue the shutdown
let _ = message_send.send(CheckEvent::Begin);
let mut child = run_cargo(&args, Some(&workspace_root), &mut |message| {
// Skip certain kinds of messages to only spend time on what's useful // Skip certain kinds of messages to only spend time on what's useful
match &message { match &message {
Message::CompilerArtifact(artifact) if artifact.fresh => continue, Message::CompilerArtifact(artifact) if artifact.fresh => return true,
Message::BuildScriptExecuted(_) => continue, Message::BuildScriptExecuted(_) => return true,
Message::Unknown => continue, Message::Unknown => return true,
_ => {} _ => {}
} }
@ -326,22 +338,27 @@ impl WatchThread {
Ok(()) => {} Ok(()) => {}
Err(_err) => { Err(_err) => {
// The send channel was closed, so we want to shutdown // The send channel was closed, so we want to shutdown
break; return false;
}
}
} }
};
true
});
// We can ignore any error here, as we are already in the progress // We can ignore any error here, as we are already in the progress
// of shutting down. // of shutting down.
let _ = message_send.send(CheckEvent::End); let _ = message_send.send(CheckEvent::End);
// It is okay to ignore the result, as it only errors if the process is already dead // It is okay to ignore the result, as it only errors if the process is already dead
let _ = command.kill(); let _ = child.kill();
// Again, we don't care about the exit status so just ignore the result // Again, we don't care about the exit status so just ignore the result
let _ = command.wait(); let _ = child.wait();
}); }))
WatchThread { handle: Some(handle), message_recv } } else {
None
};
WatchThread { handle, message_recv }
} }
} }

View file

@ -6,7 +6,11 @@
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
//! actual IO is done and lowered to input. //! actual IO is done and lowered to input.
use std::{fmt, ops, str::FromStr}; use std::{
fmt, ops,
path::{Path, PathBuf},
str::FromStr,
};
use ra_cfg::CfgOptions; use ra_cfg::CfgOptions;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
@ -144,7 +148,7 @@ pub struct Env {
// crate. We store a map to allow remap it to ExternSourceId // crate. We store a map to allow remap it to ExternSourceId
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct ExternSource { pub struct ExternSource {
extern_paths: FxHashMap<String, ExternSourceId>, extern_paths: FxHashMap<PathBuf, ExternSourceId>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -294,13 +298,10 @@ impl Env {
} }
impl ExternSource { impl ExternSource {
pub fn extern_path(&self, path: &str) -> Option<(ExternSourceId, RelativePathBuf)> { pub fn extern_path(&self, path: impl AsRef<Path>) -> Option<(ExternSourceId, RelativePathBuf)> {
let path = path.as_ref();
self.extern_paths.iter().find_map(|(root_path, id)| { self.extern_paths.iter().find_map(|(root_path, id)| {
if path.starts_with(root_path) { if let Ok(rel_path) = path.strip_prefix(root_path) {
let mut rel_path = &path[root_path.len()..];
if rel_path.starts_with("/") {
rel_path = &rel_path[1..];
}
let rel_path = RelativePathBuf::from_path(rel_path).ok()?; let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
Some((id.clone(), rel_path)) Some((id.clone(), rel_path))
} else { } else {
@ -309,8 +310,8 @@ impl ExternSource {
}) })
} }
pub fn set_extern_path(&mut self, root_path: &str, root: ExternSourceId) { pub fn set_extern_path(&mut self, root_path: &Path, root: ExternSourceId) {
self.extern_paths.insert(root_path.to_owned(), root); self.extern_paths.insert(root_path.to_path_buf(), root);
} }
} }

View file

@ -16,6 +16,7 @@ cargo_metadata = "0.9.1"
ra_arena = { path = "../ra_arena" } ra_arena = { path = "../ra_arena" }
ra_db = { path = "../ra_db" } ra_db = { path = "../ra_db" }
ra_cfg = { path = "../ra_cfg" } ra_cfg = { path = "../ra_cfg" }
ra_cargo_watch = { path = "../ra_cargo_watch" }
serde = { version = "1.0.104", features = ["derive"] } serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48" serde_json = "1.0.48"

View file

@ -3,8 +3,9 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use cargo_metadata::{CargoOpt, MetadataCommand}; use cargo_metadata::{CargoOpt, Message, MetadataCommand, PackageId};
use ra_arena::{impl_arena_id, Arena, RawId}; use ra_arena::{impl_arena_id, Arena, RawId};
use ra_cargo_watch::run_cargo;
use ra_db::Edition; use ra_db::Edition;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize; use serde::Deserialize;
@ -35,11 +36,19 @@ pub struct CargoFeatures {
/// List of features to activate. /// List of features to activate.
/// This will be ignored if `cargo_all_features` is true. /// This will be ignored if `cargo_all_features` is true.
pub features: Vec<String>, pub features: Vec<String>,
/// Runs cargo check on launch to figure out the correct values of OUT_DIR
pub load_out_dirs_from_check: bool,
} }
impl Default for CargoFeatures { impl Default for CargoFeatures {
fn default() -> Self { fn default() -> Self {
CargoFeatures { no_default_features: false, all_features: true, features: Vec::new() } CargoFeatures {
no_default_features: false,
all_features: true,
features: Vec::new(),
load_out_dirs_from_check: false,
}
} }
} }
@ -60,6 +69,7 @@ struct PackageData {
dependencies: Vec<PackageDependency>, dependencies: Vec<PackageDependency>,
edition: Edition, edition: Edition,
features: Vec<String>, features: Vec<String>,
out_dir: Option<PathBuf>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -131,6 +141,9 @@ impl Package {
) -> impl Iterator<Item = &'a PackageDependency> + 'a { ) -> impl Iterator<Item = &'a PackageDependency> + 'a {
ws.packages[self].dependencies.iter() ws.packages[self].dependencies.iter()
} }
pub fn out_dir(self, ws: &CargoWorkspace) -> Option<&Path> {
ws.packages[self].out_dir.as_ref().map(PathBuf::as_path)
}
} }
impl Target { impl Target {
@ -173,6 +186,12 @@ impl CargoWorkspace {
let meta = meta.exec().with_context(|| { let meta = meta.exec().with_context(|| {
format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display()) format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
})?; })?;
let mut out_dir_by_id = FxHashMap::default();
if cargo_features.load_out_dirs_from_check {
out_dir_by_id = load_out_dirs(cargo_toml, cargo_features);
}
let mut pkg_by_id = FxHashMap::default(); let mut pkg_by_id = FxHashMap::default();
let mut packages = Arena::default(); let mut packages = Arena::default();
let mut targets = Arena::default(); let mut targets = Arena::default();
@ -193,6 +212,7 @@ impl CargoWorkspace {
edition, edition,
dependencies: Vec::new(), dependencies: Vec::new(),
features: Vec::new(), features: Vec::new(),
out_dir: out_dir_by_id.get(&id).cloned(),
}); });
let pkg_data = &mut packages[pkg]; let pkg_data = &mut packages[pkg];
pkg_by_id.insert(id, pkg); pkg_by_id.insert(id, pkg);
@ -252,3 +272,46 @@ impl CargoWorkspace {
&self.workspace_root &self.workspace_root
} }
} }
pub fn load_out_dirs(
cargo_toml: &Path,
cargo_features: &CargoFeatures,
) -> FxHashMap<PackageId, PathBuf> {
let mut args: Vec<String> = vec![
"check".to_string(),
"--message-format=json".to_string(),
"--manifest-path".to_string(),
format!("{}", cargo_toml.display()),
];
if cargo_features.all_features {
args.push("--all-features".to_string());
} else if cargo_features.no_default_features {
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
// https://github.com/oli-obk/cargo_metadata/issues/79
args.push("--no-default-features".to_string());
} else if !cargo_features.features.is_empty() {
for feature in &cargo_features.features {
args.push(feature.clone());
}
}
let mut res = FxHashMap::default();
let mut child = run_cargo(&args, cargo_toml.parent(), &mut |message| {
match message {
Message::BuildScriptExecuted(message) => {
let package_id = message.package_id;
let out_dir = message.out_dir;
res.insert(package_id, out_dir);
}
Message::CompilerArtifact(_) => (),
Message::CompilerMessage(_) => (),
Message::Unknown => (),
}
true
});
let _ = child.wait();
res
}

View file

@ -22,6 +22,7 @@ pub struct Crate {
pub(crate) deps: Vec<Dep>, pub(crate) deps: Vec<Dep>,
pub(crate) atom_cfgs: FxHashSet<String>, pub(crate) atom_cfgs: FxHashSet<String>,
pub(crate) key_value_cfgs: FxHashMap<String, String>, pub(crate) key_value_cfgs: FxHashMap<String, String>,
pub(crate) out_dir: Option<PathBuf>,
} }
#[derive(Clone, Copy, Debug, Deserialize)] #[derive(Clone, Copy, Debug, Deserialize)]

View file

@ -150,6 +150,29 @@ impl ProjectWorkspace {
} }
} }
pub fn out_dirs(&self) -> Vec<PathBuf> {
match self {
ProjectWorkspace::Json { project } => {
let mut out_dirs = Vec::with_capacity(project.crates.len());
for krate in &project.crates {
if let Some(out_dir) = &krate.out_dir {
out_dirs.push(out_dir.to_path_buf());
}
}
out_dirs
}
ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => {
let mut out_dirs = Vec::with_capacity(cargo.packages().len());
for pkg in cargo.packages() {
if let Some(out_dir) = pkg.out_dir(&cargo) {
out_dirs.push(out_dir.to_path_buf());
}
}
out_dirs
}
}
}
pub fn n_packages(&self) -> usize { pub fn n_packages(&self) -> usize {
match self { match self {
ProjectWorkspace::Json { project } => project.crates.len(), ProjectWorkspace::Json { project } => project.crates.len(),
@ -162,7 +185,7 @@ impl ProjectWorkspace {
pub fn to_crate_graph( pub fn to_crate_graph(
&self, &self,
default_cfg_options: &CfgOptions, default_cfg_options: &CfgOptions,
outdirs: &FxHashMap<String, (ExternSourceId, String)>, extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>,
load: &mut dyn FnMut(&Path) -> Option<FileId>, load: &mut dyn FnMut(&Path) -> Option<FileId>,
) -> CrateGraph { ) -> CrateGraph {
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
@ -187,6 +210,16 @@ impl ProjectWorkspace {
opts opts
}; };
let mut env = Env::default();
let mut extern_source = ExternSource::default();
if let Some(out_dir) = &krate.out_dir {
// FIXME: We probably mangle non UTF-8 paths here, figure out a better solution
env.set("OUT_DIR", out_dir.to_string_lossy().to_string());
if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
extern_source.set_extern_path(&out_dir, extern_source_id);
}
}
// FIXME: No crate name in json definition such that we cannot add OUT_DIR to env // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
crates.insert( crates.insert(
crate_id, crate_id,
@ -196,8 +229,8 @@ impl ProjectWorkspace {
// FIXME json definitions can store the crate name // FIXME json definitions can store the crate name
None, None,
cfg_options, cfg_options,
Env::default(), env,
Default::default(), extern_source,
), ),
); );
} }
@ -235,13 +268,8 @@ impl ProjectWorkspace {
opts opts
}; };
let mut env = Env::default(); let env = Env::default();
let mut extern_source = ExternSource::default(); let extern_source = ExternSource::default();
if let Some((id, path)) = outdirs.get(krate.name(&sysroot)) {
env.set("OUT_DIR", path.clone());
extern_source.set_extern_path(&path, *id);
}
let crate_id = crate_graph.add_crate_root( let crate_id = crate_graph.add_crate_root(
file_id, file_id,
Edition::Edition2018, Edition::Edition2018,
@ -292,9 +320,12 @@ impl ProjectWorkspace {
}; };
let mut env = Env::default(); let mut env = Env::default();
let mut extern_source = ExternSource::default(); let mut extern_source = ExternSource::default();
if let Some((id, path)) = outdirs.get(pkg.name(&cargo)) { if let Some(out_dir) = pkg.out_dir(cargo) {
env.set("OUT_DIR", path.clone()); // FIXME: We probably mangle non UTF-8 paths here, figure out a better solution
extern_source.set_extern_path(&path, *id); env.set("OUT_DIR", out_dir.to_string_lossy().to_string());
if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
extern_source.set_extern_path(&out_dir, extern_source_id);
}
} }
let crate_id = crate_graph.add_crate_root( let crate_id = crate_graph.add_crate_root(
file_id, file_id,

View file

@ -28,10 +28,12 @@ pub(crate) enum Command {
only: Option<String>, only: Option<String>,
with_deps: bool, with_deps: bool,
path: PathBuf, path: PathBuf,
load_output_dirs: bool,
}, },
Bench { Bench {
path: PathBuf, path: PathBuf,
what: BenchWhat, what: BenchWhat,
load_output_dirs: bool,
}, },
RunServer, RunServer,
Version, Version,
@ -138,6 +140,7 @@ USAGE:
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
--memory-usage --memory-usage
--load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
-v, --verbose -v, --verbose
-q, --quiet -q, --quiet
@ -154,6 +157,7 @@ ARGS:
let memory_usage = matches.contains("--memory-usage"); let memory_usage = matches.contains("--memory-usage");
let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?; let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
let with_deps: bool = matches.contains("--with-deps"); let with_deps: bool = matches.contains("--with-deps");
let load_output_dirs = matches.contains("--load-output-dirs");
let path = { let path = {
let mut trailing = matches.free()?; let mut trailing = matches.free()?;
if trailing.len() != 1 { if trailing.len() != 1 {
@ -162,7 +166,7 @@ ARGS:
trailing.pop().unwrap().into() trailing.pop().unwrap().into()
}; };
Command::Stats { randomize, memory_usage, only, with_deps, path } Command::Stats { randomize, memory_usage, only, with_deps, path, load_output_dirs }
} }
"analysis-bench" => { "analysis-bench" => {
if matches.contains(["-h", "--help"]) { if matches.contains(["-h", "--help"]) {
@ -175,6 +179,7 @@ USAGE:
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
--load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
-v, --verbose -v, --verbose
OPTIONS: OPTIONS:
@ -201,7 +206,8 @@ ARGS:
"exactly one of `--highlight`, `--complete` or `--goto-def` must be set" "exactly one of `--highlight`, `--complete` or `--goto-def` must be set"
), ),
}; };
Command::Bench { path, what } let load_output_dirs = matches.contains("--load-output-dirs");
Command::Bench { path, what, load_output_dirs }
} }
_ => { _ => {
eprintln!( eprintln!(

View file

@ -19,19 +19,25 @@ fn main() -> Result<()> {
args::Command::Parse { no_dump } => cli::parse(no_dump)?, args::Command::Parse { no_dump } => cli::parse(no_dump)?,
args::Command::Symbols => cli::symbols()?, args::Command::Symbols => cli::symbols()?,
args::Command::Highlight { rainbow } => cli::highlight(rainbow)?, args::Command::Highlight { rainbow } => cli::highlight(rainbow)?,
args::Command::Stats { randomize, memory_usage, only, with_deps, path } => { args::Command::Stats {
cli::analysis_stats( randomize,
memory_usage,
only,
with_deps,
path,
load_output_dirs,
} => cli::analysis_stats(
args.verbosity, args.verbosity,
memory_usage, memory_usage,
path.as_ref(), path.as_ref(),
only.as_ref().map(String::as_ref), only.as_ref().map(String::as_ref),
with_deps, with_deps,
randomize, randomize,
)? load_output_dirs,
} )?,
args::Command::Bench { path, what } => { args::Command::Bench { path, what, load_output_dirs } => {
cli::analysis_bench(args.verbosity, path.as_ref(), what)? cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)?
} }
args::Command::RunServer => run_server()?, args::Command::RunServer => run_server()?,

View file

@ -42,12 +42,17 @@ fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
Ok((&s[..idx], &s[idx + 1..])) Ok((&s[..idx], &s[idx + 1..]))
} }
pub fn analysis_bench(verbosity: Verbosity, path: &Path, what: BenchWhat) -> Result<()> { pub fn analysis_bench(
verbosity: Verbosity,
path: &Path,
what: BenchWhat,
load_output_dirs: bool,
) -> Result<()> {
ra_prof::init(); ra_prof::init();
let start = Instant::now(); let start = Instant::now();
eprint!("loading: "); eprint!("loading: ");
let (mut host, roots) = load_cargo(path)?; let (mut host, roots) = load_cargo(path, load_output_dirs)?;
let db = host.raw_database(); let db = host.raw_database();
eprintln!("{:?}\n", start.elapsed()); eprintln!("{:?}\n", start.elapsed());

View file

@ -23,9 +23,10 @@ pub fn analysis_stats(
only: Option<&str>, only: Option<&str>,
with_deps: bool, with_deps: bool,
randomize: bool, randomize: bool,
load_output_dirs: bool,
) -> Result<()> { ) -> Result<()> {
let db_load_time = Instant::now(); let db_load_time = Instant::now();
let (mut host, roots) = load_cargo(path)?; let (mut host, roots) = load_cargo(path, load_output_dirs)?;
let db = host.raw_database(); let db = host.raw_database();
println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
let analysis_time = Instant::now(); let analysis_time = Instant::now();

View file

@ -1,13 +1,13 @@
//! Loads a Cargo project into a static instance of analysis, without support //! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes. //! for incorporating changes.
use std::path::Path; use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
use crossbeam_channel::{unbounded, Receiver}; use crossbeam_channel::{unbounded, Receiver};
use ra_db::{CrateGraph, FileId, SourceRootId}; use ra_db::{ExternSourceId, FileId, SourceRootId};
use ra_ide::{AnalysisChange, AnalysisHost}; use ra_ide::{AnalysisChange, AnalysisHost};
use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace}; use ra_project_model::{get_rustc_cfg_options, CargoFeatures, PackageRoot, 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};
@ -22,10 +22,21 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
pub(crate) fn load_cargo( pub(crate) fn load_cargo(
root: &Path, root: &Path,
load_out_dirs_from_check: 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 ws = ProjectWorkspace::discover(root.as_ref(), &Default::default())?; let ws = ProjectWorkspace::discover(
let project_roots = ws.to_roots(); root.as_ref(),
&CargoFeatures { load_out_dirs_from_check, ..Default::default() },
)?;
let mut extern_dirs = FxHashSet::default();
extern_dirs.extend(ws.out_dirs());
let mut project_roots = ws.to_roots();
project_roots
.extend(extern_dirs.iter().map(|path| PackageRoot::new(path.to_path_buf(), false)));
let (sender, receiver) = unbounded(); let (sender, receiver) = unbounded();
let sender = Box::new(move |t| sender.send(t).unwrap()); let sender = Box::new(move |t| sender.send(t).unwrap());
let (mut vfs, roots) = Vfs::new( let (mut vfs, roots) = Vfs::new(
@ -44,24 +55,6 @@ pub(crate) fn load_cargo(
Watch(false), Watch(false),
); );
// FIXME: cfg options?
let default_cfg_options = {
let mut opts = get_rustc_cfg_options();
opts.insert_atom("test".into());
opts.insert_atom("debug_assertion".into());
opts
};
// FIXME: outdirs?
let outdirs = FxHashMap::default();
let crate_graph = ws.to_crate_graph(&default_cfg_options, &outdirs, &mut |path: &Path| {
let vfs_file = vfs.load(path);
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
vfs_file.map(vfs_file_to_id)
});
log::debug!("crate graph: {:?}", crate_graph);
let source_roots = roots let source_roots = roots
.iter() .iter()
.map(|&vfs_root| { .map(|&vfs_root| {
@ -74,23 +67,24 @@ pub(crate) fn load_cargo(
(source_root_id, project_root) (source_root_id, project_root)
}) })
.collect::<FxHashMap<_, _>>(); .collect::<FxHashMap<_, _>>();
let host = load(&source_roots, crate_graph, &mut vfs, receiver); let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs);
Ok((host, source_roots)) Ok((host, source_roots))
} }
pub(crate) fn load( pub(crate) fn load(
source_roots: &FxHashMap<SourceRootId, PackageRoot>, source_roots: &FxHashMap<SourceRootId, PackageRoot>,
crate_graph: CrateGraph, ws: ProjectWorkspace,
vfs: &mut Vfs, vfs: &mut Vfs,
receiver: Receiver<VfsTask>, receiver: Receiver<VfsTask>,
extern_dirs: FxHashSet<PathBuf>,
) -> AnalysisHost { ) -> AnalysisHost {
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok()); let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
let mut host = AnalysisHost::new(lru_cap); let mut host = AnalysisHost::new(lru_cap);
let mut analysis_change = AnalysisChange::new(); let mut analysis_change = AnalysisChange::new();
analysis_change.set_crate_graph(crate_graph);
// wait until Vfs has loaded all roots // wait until Vfs has loaded all roots
let mut roots_loaded = FxHashSet::default(); let mut roots_loaded = FxHashSet::default();
let mut extern_source_roots = FxHashMap::default();
for task in receiver { for task in receiver {
vfs.handle_task(task); vfs.handle_task(task);
let mut done = false; let mut done = false;
@ -110,6 +104,11 @@ pub(crate) fn load(
source_roots[&source_root_id].path().display().to_string(), source_roots[&source_root_id].path().display().to_string(),
); );
let vfs_root_path = vfs.root2path(root);
if extern_dirs.contains(&vfs_root_path) {
extern_source_roots.insert(vfs_root_path, ExternSourceId(root.0));
}
let mut file_map = FxHashMap::default(); let mut file_map = FxHashMap::default();
for (vfs_file, path, text) in files { for (vfs_file, path, text) in files {
let file_id = vfs_file_to_id(vfs_file); let file_id = vfs_file_to_id(vfs_file);
@ -136,6 +135,23 @@ pub(crate) fn load(
} }
} }
// FIXME: cfg options?
let default_cfg_options = {
let mut opts = get_rustc_cfg_options();
opts.insert_atom("test".into());
opts.insert_atom("debug_assertion".into());
opts
};
let crate_graph =
ws.to_crate_graph(&default_cfg_options, &extern_source_roots, &mut |path: &Path| {
let vfs_file = vfs.load(path);
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
vfs_file.map(vfs_file_to_id)
});
log::debug!("crate graph: {:?}", crate_graph);
analysis_change.set_crate_graph(crate_graph);
host.apply_change(analysis_change); host.apply_change(analysis_change);
host host
} }
@ -149,7 +165,7 @@ mod tests {
#[test] #[test]
fn test_loading_rust_analyzer() { fn test_loading_rust_analyzer() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
let (host, _roots) = load_cargo(path).unwrap(); let (host, _roots) = load_cargo(path, false).unwrap();
let n_crates = Crate::all(host.raw_database()).len(); let n_crates = Crate::all(host.raw_database()).len();
// RA has quite a few crates, but the exact count doesn't matter // RA has quite a few crates, but the exact count doesn't matter
assert!(n_crates > 20); assert!(n_crates > 20);

View file

@ -48,9 +48,6 @@ pub struct ServerConfig {
/// Fine grained feature flags to disable specific features. /// Fine grained feature flags to disable specific features.
pub feature_flags: FxHashMap<String, bool>, pub feature_flags: FxHashMap<String, bool>,
/// Fine grained controls for additional `OUT_DIR` env variables
pub additional_out_dirs: FxHashMap<String, String>,
pub rustfmt_args: Vec<String>, pub rustfmt_args: Vec<String>,
/// Cargo feature configurations. /// Cargo feature configurations.
@ -76,7 +73,6 @@ impl Default for ServerConfig {
cargo_watch_all_targets: true, cargo_watch_all_targets: true,
with_sysroot: true, with_sysroot: true,
feature_flags: FxHashMap::default(), feature_flags: FxHashMap::default(),
additional_out_dirs: FxHashMap::default(),
cargo_features: Default::default(), cargo_features: Default::default(),
rustfmt_args: Vec::new(), rustfmt_args: Vec::new(),
vscode_lldb: false, vscode_lldb: false,

View file

@ -204,7 +204,6 @@ pub fn main_loop(
Watch(!config.use_client_watching), Watch(!config.use_client_watching),
options, options,
feature_flags, feature_flags,
config.additional_out_dirs,
) )
}; };

View file

@ -82,7 +82,6 @@ impl WorldState {
watch: Watch, watch: Watch,
options: Options, options: Options,
feature_flags: FeatureFlags, feature_flags: FeatureFlags,
additional_out_dirs: FxHashMap<String, String>,
) -> WorldState { ) -> WorldState {
let mut change = AnalysisChange::new(); let mut change = AnalysisChange::new();
@ -105,11 +104,14 @@ impl WorldState {
})); }));
} }
let extern_dirs: FxHashSet<_> = let mut extern_dirs = FxHashSet::default();
additional_out_dirs.iter().map(|(_, path)| (PathBuf::from(path))).collect(); for ws in workspaces.iter() {
extern_dirs.extend(ws.out_dirs());
}
let mut extern_source_roots = FxHashMap::default(); let mut extern_source_roots = FxHashMap::default();
roots.extend(additional_out_dirs.iter().map(|(_, path)| { roots.extend(extern_dirs.iter().map(|path| {
let mut filter = RustPackageFilterBuilder::default().set_member(false); let mut filter = RustPackageFilterBuilder::default().set_member(false);
for glob in exclude_globs.iter() { for glob in exclude_globs.iter() {
filter = filter.exclude(glob.clone()); filter = filter.exclude(glob.clone());
@ -148,17 +150,9 @@ impl WorldState {
vfs_file.map(|f| FileId(f.0)) vfs_file.map(|f| FileId(f.0))
}; };
let mut outdirs = FxHashMap::default();
for (name, path) in additional_out_dirs {
let path = PathBuf::from(&path);
if let Some(id) = extern_source_roots.get(&path) {
outdirs.insert(name, (id.clone(), path.to_string_lossy().replace("\\", "/")));
}
}
workspaces workspaces
.iter() .iter()
.map(|ws| ws.to_crate_graph(&default_cfg_options, &outdirs, &mut load)) .map(|ws| ws.to_crate_graph(&default_cfg_options, &extern_source_roots, &mut load))
.for_each(|graph| { .for_each(|graph| {
crate_graph.extend(graph); crate_graph.extend(graph);
}); });

View file

@ -237,11 +237,6 @@
"default": true, "default": true,
"description": "Whether to ask for permission before downloading any files from the Internet" "description": "Whether to ask for permission before downloading any files from the Internet"
}, },
"rust-analyzer.additionalOutDirs": {
"type": "object",
"default": {},
"markdownDescription": "Fine grained controls for OUT_DIR `env!(\"OUT_DIR\")` variable. e.g. `{\"foo\":\"/path/to/foo\"}`, "
},
"rust-analyzer.serverPath": { "rust-analyzer.serverPath": {
"type": [ "type": [
"null", "null",
@ -362,6 +357,11 @@
}, },
"default": [], "default": [],
"description": "List of features to activate" "description": "List of features to activate"
},
"rust-analyzer.cargoFeatures.loadOutDirsFromCheck": {
"type": "boolean",
"default": false,
"markdownDescription": "Run `cargo check` on startup to get the correct value for package OUT_DIRs"
} }
} }
}, },

View file

@ -42,7 +42,6 @@ export async function createClient(config: Config, serverPath: string): Promise<
excludeGlobs: config.excludeGlobs, excludeGlobs: config.excludeGlobs,
useClientWatching: config.useClientWatching, useClientWatching: config.useClientWatching,
featureFlags: config.featureFlags, featureFlags: config.featureFlags,
additionalOutDirs: config.additionalOutDirs,
withSysroot: config.withSysroot, withSysroot: config.withSysroot,
cargoFeatures: config.cargoFeatures, cargoFeatures: config.cargoFeatures,
rustfmtArgs: config.rustfmtArgs, rustfmtArgs: config.rustfmtArgs,

View file

@ -22,6 +22,7 @@ export interface CargoFeatures {
noDefaultFeatures: boolean; noDefaultFeatures: boolean;
allFeatures: boolean; allFeatures: boolean;
features: string[]; features: string[];
loadOutDirsFromCheck: boolean;
} }
export const enum UpdatesChannel { export const enum UpdatesChannel {
@ -202,8 +203,8 @@ export class Config {
get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; }
get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; }
get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; } get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; }
get additionalOutDirs() { return this.cfg.get("additionalOutDirs") as Record<string, string>; }
get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; } get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; }
get loadOutDirsFromCheck() { return this.cfg.get("loadOutDirsFromCheck") as boolean; }
get cargoWatchOptions(): CargoWatchOptions { get cargoWatchOptions(): CargoWatchOptions {
return { return {
@ -219,6 +220,7 @@ export class Config {
noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean,
allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean,
features: this.cfg.get("cargoFeatures.features") as string[], features: this.cfg.get("cargoFeatures.features") as string[],
loadOutDirsFromCheck: this.cfg.get("cargoFeatures.loadOutDirsFromCheck") as boolean,
}; };
} }