Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.

Commit 3b62d15

Browse files
authored
Merge pull request #731 from rust-lang-nursery/packages
Autodetect the package currently being edited
2 parents 4c8e250 + b218ee1 commit 3b62d15

File tree

7 files changed

+160
-25
lines changed

7 files changed

+160
-25
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ build = "build.rs"
1212
[dependencies]
1313
# cargo hash delivered with nightly-2018-02-01
1414
cargo = { git = "https://github.com/rust-lang/cargo", rev = "1d6dfea44f97199d5d5c177c7dadcde393eaff9a" }
15+
cargo_metadata = "0.4"
1516
env_logger = "0.5"
1617
failure = "0.1.1"
1718
jsonrpc-core = "8.0.1"

src/build/cargo.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use cargo::ops::{compile_with_exec, CompileFilter, CompileMode, CompileOptions,
1313
Packages, Unit};
1414
use cargo::util::{homedir, important_paths, CargoResult, Config as CargoConfig, ConfigValue,
1515
ProcessBuilder};
16-
use serde_json;
1716
use failure;
17+
use serde_json;
1818

1919
use data::Analysis;
20-
use build::{BufWriter, BuildResult, CompilationContext, Internals};
20+
use build::{BufWriter, BuildResult, CompilationContext, Internals, PackageArg};
2121
use build::environment::{self, Environment, EnvironmentLock};
2222
use config::Config;
2323
use vfs::Vfs;
@@ -32,7 +32,7 @@ use std::sync::{Arc, Mutex};
3232
use std::thread;
3333

3434
// Runs an in-process instance of Cargo.
35-
pub(super) fn cargo(internals: &Internals) -> BuildResult {
35+
pub(super) fn cargo(internals: &Internals, package_arg: PackageArg) -> BuildResult {
3636
let workspace_mode = internals.config.lock().unwrap().workspace_mode;
3737

3838
let compilation_cx = internals.compilation_cx.clone();
@@ -54,6 +54,7 @@ pub(super) fn cargo(internals: &Internals) -> BuildResult {
5454
let handle = thread::spawn(move || {
5555
run_cargo(
5656
compilation_cx,
57+
package_arg,
5758
config,
5859
vfs,
5960
env_lock,
@@ -90,6 +91,7 @@ pub(super) fn cargo(internals: &Internals) -> BuildResult {
9091

9192
fn run_cargo(
9293
compilation_cx: Arc<Mutex<CompilationContext>>,
94+
package_arg: PackageArg,
9395
rls_config: Arc<Mutex<Config>>,
9496
vfs: Arc<Vfs>,
9597
env_lock: Arc<EnvironmentLock>,
@@ -113,6 +115,7 @@ fn run_cargo(
113115

114116
compilation_cx.build_dir.as_ref().unwrap().clone()
115117
};
118+
116119
// Note that this may not be equal build_dir when inside a workspace member
117120
let manifest_path = important_paths::find_root_manifest_for_wd(None, &build_dir)?;
118121
trace!("root manifest_path: {:?}", &manifest_path);
@@ -131,6 +134,11 @@ fn run_cargo(
131134

132135
let ws = Workspace::new(&manifest_path, &config)?;
133136

137+
let packages = match package_arg {
138+
PackageArg::Unknown | PackageArg::All => vec![],
139+
PackageArg::Package(s) => vec![s]
140+
};
141+
134142
// TODO: It might be feasible to keep this CargoOptions structure cached and regenerate
135143
// it on every relevant configuration change
136144
let (opts, rustflags, clear_env_rust_log) =
@@ -142,7 +150,14 @@ fn run_cargo(
142150
trace!("Cargo compilation options:\n{:?}", opts);
143151
let rustflags = prepare_cargo_rustflags(&rls_config);
144152

145-
if !rls_config.workspace_mode {
153+
154+
if rls_config.workspace_mode {
155+
for package in &packages {
156+
if let None = ws.members().find(|x| x.name() == package) {
157+
warn!("cargo - couldn't find member package `{}` specified in `analyze_package` configuration", package);
158+
}
159+
}
160+
} else {
146161
// Warn about invalid specified bin target or package depending on current mode
147162
// TODO: Return client notifications along with diagnostics to inform the user
148163
let cur_pkg_targets = ws.current()?.targets();
@@ -158,7 +173,7 @@ fn run_cargo(
158173
(opts, rustflags, rls_config.clear_env_rust_log)
159174
};
160175

161-
let spec = Packages::from_flags(false, &[], &[])?;
176+
let spec = Packages::from_flags(false, &[], &packages)?;
162177

163178
let compile_opts = CompileOptions {
164179
target: opts.target.as_ref().map(|t| &t[..]),
@@ -630,6 +645,7 @@ pub fn make_cargo_config(build_dir: &Path,
630645
let target_dir = target_dir
631646
.map(|d| d.to_str().unwrap().to_owned())
632647
.unwrap_or_else(|| {
648+
// FIXME(#730) should be using the workspace root here, not build_dir
633649
build_dir
634650
.join("target")
635651
.join("rls")

src/build/mod.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
pub use self::cargo::make_cargo_config;
1414

1515
use actions::post_build::PostBuildHandler;
16+
use cargo::util::important_paths;
17+
use config::Config;
1618
use data::Analysis;
1719
use vfs::Vfs;
18-
use config::Config;
1920

2021
use self::environment::EnvironmentLock;
2122

@@ -119,6 +120,15 @@ pub enum BuildPriority {
119120
Normal,
120121
}
121122

123+
impl BuildPriority {
124+
fn is_cargo(&self) -> bool {
125+
match *self {
126+
BuildPriority::Cargo => true,
127+
_ => false,
128+
}
129+
}
130+
}
131+
122132
/// Information passed to Cargo/rustc to build.
123133
#[derive(Debug)]
124134
struct CompilationContext {
@@ -138,13 +148,22 @@ impl CompilationContext {
138148
CompilationContext {
139149
args: vec![],
140150
envs: HashMap::new(),
151+
cwd: None,
141152
build_dir: None,
142153
build_plan: BuildPlan::new(),
143-
cwd: None,
144154
}
145155
}
146156
}
147157

158+
#[derive(Debug, Clone, Eq, PartialEq)]
159+
pub enum PackageArg {
160+
Unknown,
161+
// --all
162+
All,
163+
// -p ...
164+
Package(String),
165+
}
166+
148167
/// Status of the build queue.
149168
///
150169
/// Pending should only be replaced if it is built or squashed. `InProgress` can be
@@ -429,11 +448,11 @@ impl Internals {
429448
.map_or(true, |dir| dir != new_build_dir)
430449
{
431450
// We'll need to re-run cargo in this case.
432-
assert!(priority == BuildPriority::Cargo);
451+
assert!(priority.is_cargo());
433452
(*compilation_cx).build_dir = Some(new_build_dir.to_owned());
434453
}
435454

436-
if priority == BuildPriority::Cargo {
455+
if priority.is_cargo() {
437456
// Killing these args indicates we'll do a full Cargo build.
438457
compilation_cx.args = vec![];
439458
compilation_cx.envs = HashMap::new();
@@ -485,19 +504,27 @@ impl Internals {
485504
// has to be specifically rerun (e.g. when build scripts changed)
486505
let work = {
487506
let modified: Vec<_> = self.dirty_files.lock().unwrap().keys().cloned().collect();
488-
let cx = self.compilation_cx.lock().unwrap();
489-
cx.build_plan.prepare_work(&modified)
507+
let mut cx = self.compilation_cx.lock().unwrap();
508+
let manifest_path = important_paths::find_root_manifest_for_wd(None, cx.build_dir.as_ref().unwrap());
509+
let manifest_path = match manifest_path {
510+
Ok(mp) => mp,
511+
Err(e) => {
512+
let msg = format!("Error reading manifest path: {:?}", e);
513+
return BuildResult::Err(msg, None);
514+
}
515+
};
516+
cx.build_plan.prepare_work(&manifest_path, &modified, needs_to_run_cargo)
490517
};
491518
return match work {
492519
// In workspace_mode, cargo performs the full build and returns
493520
// appropriate diagnostics/analysis data
494-
WorkStatus::NeedsCargo => cargo::cargo(self),
521+
WorkStatus::NeedsCargo(package_arg) => cargo::cargo(self, package_arg),
495522
WorkStatus::Execute(job_queue) => job_queue.execute(self),
496523
};
497524
// In single package mode Cargo needs to be run to cache args/envs for
498525
// future rustc calls
499526
} else if needs_to_run_cargo {
500-
if let e @ BuildResult::Err(..) = cargo::cargo(self) {
527+
if let e @ BuildResult::Err(..) = cargo::cargo(self, PackageArg::Unknown) {
501528
return e;
502529
}
503530
}

src/build/plan.rs

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@
2727
use std::collections::{HashMap, HashSet};
2828
use std::fmt;
2929
use std::path::{Path, PathBuf};
30-
use std::sync::Arc;
30+
use std::sync::{Arc, Mutex};
3131

32+
use build::PackageArg;
3233
use cargo::core::{PackageId, Profile, Target, TargetKind};
3334
use cargo::ops::{Context, Kind, Unit};
3435
use cargo::util::{CargoResult, ProcessBuilder};
36+
use cargo_metadata;
37+
use lsp_data::parse_file_path;
38+
use url::Url;
3539

3640
use super::{BuildResult, Internals};
3741

@@ -49,6 +53,11 @@ pub struct Plan {
4953
pub rev_dep_graph: HashMap<UnitKey, HashSet<UnitKey>>,
5054
/// Cached compiler calls used when creating a compiler call queue.
5155
pub compiler_jobs: HashMap<UnitKey, ProcessBuilder>,
56+
// An object for finding the package which a file belongs to and this inferring
57+
// a package argument.
58+
package_map: Option<PackageMap>,
59+
// The package argument used in the last Cargo build.
60+
prev_package_arg: Option<PackageArg>,
5261
}
5362

5463
impl Plan {
@@ -58,11 +67,16 @@ impl Plan {
5867
dep_graph: HashMap::new(),
5968
rev_dep_graph: HashMap::new(),
6069
compiler_jobs: HashMap::new(),
70+
package_map: None,
71+
prev_package_arg: None,
6172
}
6273
}
6374

6475
pub fn clear(&mut self) {
65-
*self = Plan::new();
76+
self.units = HashMap::new();
77+
self.dep_graph = HashMap::new();
78+
self.rev_dep_graph = HashMap::new();
79+
self.compiler_jobs = HashMap::new();
6680
}
6781

6882
/// Returns whether a build plan has cached compiler invocations and dep
@@ -273,23 +287,34 @@ impl Plan {
273287
}
274288
}
275289

276-
pub fn prepare_work<T: AsRef<Path> + fmt::Debug>(&self, modified: &[T]) -> WorkStatus {
277-
if !self.is_ready() {
278-
return WorkStatus::NeedsCargo;
290+
pub fn prepare_work<T: AsRef<Path> + fmt::Debug>(&mut self, manifest_path: &Path, modified: &[T], requested_cargo: bool) -> WorkStatus {
291+
if self.package_map.is_none() || requested_cargo {
292+
self.package_map = Some(PackageMap::new(manifest_path));
293+
}
294+
295+
let package_arg = self.package_map.as_ref().unwrap().compute_package_arg(modified);
296+
let package_arg_changed = match self.prev_package_arg {
297+
Some(ref ppa) => ppa != &package_arg,
298+
None => true,
299+
};
300+
self.prev_package_arg = Some(package_arg.clone());
301+
302+
if !self.is_ready() || requested_cargo || package_arg_changed {
303+
return WorkStatus::NeedsCargo(package_arg);
279304
}
280305

281306
let dirties = self.fetch_dirty_units(modified);
282307
trace!(
283308
"fetch_dirty_units: for files {:?}, these units are dirty: {:?}",
284309
modified,
285-
dirties
310+
dirties,
286311
);
287312

288313
if dirties
289314
.iter()
290315
.any(|&(_, ref kind)| *kind == TargetKind::CustomBuild)
291316
{
292-
WorkStatus::NeedsCargo
317+
WorkStatus::NeedsCargo(package_arg)
293318
} else {
294319
let graph = self.dirty_rev_dep_graph(&dirties);
295320
trace!("Constructed dirty rev dep graph: {:?}", graph);
@@ -302,7 +327,7 @@ impl Plan {
302327
.collect();
303328

304329
if jobs.is_empty() {
305-
WorkStatus::NeedsCargo
330+
WorkStatus::NeedsCargo(package_arg)
306331
} else {
307332
WorkStatus::Execute(JobQueue(jobs))
308333
}
@@ -311,10 +336,74 @@ impl Plan {
311336
}
312337

313338
pub enum WorkStatus {
314-
NeedsCargo,
339+
NeedsCargo(PackageArg),
315340
Execute(JobQueue),
316341
}
317342

343+
// Maps paths to packages.
344+
#[derive(Debug)]
345+
struct PackageMap {
346+
// A map from a manifest directory to the package name.
347+
package_paths: HashMap<PathBuf, String>,
348+
// A map from a file's path, to the package it belongs to.
349+
map_cache: Mutex<HashMap<PathBuf, String>>,
350+
}
351+
352+
impl PackageMap {
353+
fn new(manifest_path: &Path) -> PackageMap {
354+
PackageMap {
355+
package_paths: Self::discover_package_paths(manifest_path),
356+
map_cache: Mutex::new(HashMap::new()),
357+
}
358+
}
359+
360+
fn discover_package_paths(manifest_path: &Path) -> HashMap<PathBuf, String> {
361+
trace!("read metadata {:?}", manifest_path);
362+
let metadata = cargo_metadata::metadata(Some(manifest_path)).expect("Can't read crate metadata");
363+
metadata
364+
.workspace_members
365+
.into_iter()
366+
.map(|wm| {
367+
assert!(wm.url.starts_with("path+"));
368+
let url = Url::parse(&wm.url[5..]).expect("Bad URL");
369+
let path = parse_file_path(&url).expect("URL not a path");
370+
(path, wm.name)
371+
})
372+
.collect()
373+
}
374+
375+
fn compute_package_arg<T: AsRef<Path> + fmt::Debug>(&self, modified_files: &[T]) -> PackageArg {
376+
let mut packages: Option<HashSet<String>> = modified_files.iter().map(|p| self.map(p.as_ref())).collect();
377+
match packages {
378+
Some(ref mut packages) if packages.len() == 1 => {
379+
PackageArg::Package(packages.drain().next().unwrap())
380+
}
381+
_ => PackageArg::All,
382+
}
383+
}
384+
385+
fn map(&self, path: &Path) -> Option<String> {
386+
let mut map_cache = self.map_cache.lock().unwrap();
387+
if map_cache.contains_key(path) {
388+
return Some(map_cache[path].clone());
389+
}
390+
391+
let result = Self::map_uncached(path, &self.package_paths)?;
392+
393+
map_cache.insert(path.to_owned(), result.clone());
394+
Some(result)
395+
}
396+
397+
fn map_uncached(path: &Path, package_paths: &HashMap<PathBuf, String>) -> Option<String> {
398+
match package_paths.get(path) {
399+
Some(package) => Some(package.clone()),
400+
None => {
401+
Self::map_uncached(path.parent()?, package_paths)
402+
}
403+
}
404+
}
405+
}
406+
318407
pub struct JobQueue(Vec<ProcessBuilder>);
319408

320409
impl JobQueue {

src/lsp_data.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ where
5454

5555
/// Parse the given URI into a `PathBuf`.
5656
pub fn parse_file_path(uri: &Url) -> Result<PathBuf, UrlFileParseError> {
57-
if uri.scheme() != "file" {
58-
Err(UrlFileParseError::InvalidScheme)
59-
} else {
57+
if uri.scheme() == "file" {
6058
uri.to_file_path()
6159
.map_err(|_err| UrlFileParseError::InvalidFilePath)
60+
} else {
61+
Err(UrlFileParseError::InvalidScheme)
6262
}
6363
}
6464

0 commit comments

Comments
 (0)