Skip to content

Commit ec1312e

Browse files
authored
Merge pull request #3582 from kiljacken/out-dir-from-check
Update OUT_DIR based on `cargo check` output
2 parents 2720e23 + 2dd887d commit ec1312e

File tree

18 files changed

+315
-176
lines changed

18 files changed

+315
-176
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.

crates/ra_cargo_watch/src/lib.rs

Lines changed: 96 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use lsp_types::{
99
};
1010
use std::{
1111
io::{BufRead, BufReader},
12-
path::PathBuf,
13-
process::{Command, Stdio},
12+
path::{Path, PathBuf},
13+
process::{Child, Command, Stdio},
1414
thread::JoinHandle,
1515
time::Instant,
1616
};
@@ -246,102 +246,119 @@ enum CheckEvent {
246246
End,
247247
}
248248

249+
pub fn run_cargo(
250+
args: &[String],
251+
current_dir: Option<&Path>,
252+
on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
253+
) -> Child {
254+
let mut command = Command::new("cargo");
255+
if let Some(current_dir) = current_dir {
256+
command.current_dir(current_dir);
257+
}
258+
259+
let mut child = command
260+
.args(args)
261+
.stdout(Stdio::piped())
262+
.stderr(Stdio::null())
263+
.stdin(Stdio::null())
264+
.spawn()
265+
.expect("couldn't launch cargo");
266+
267+
// We manually read a line at a time, instead of using serde's
268+
// stream deserializers, because the deserializer cannot recover
269+
// from an error, resulting in it getting stuck, because we try to
270+
// be resillient against failures.
271+
//
272+
// Because cargo only outputs one JSON object per line, we can
273+
// simply skip a line if it doesn't parse, which just ignores any
274+
// erroneus output.
275+
let stdout = BufReader::new(child.stdout.take().unwrap());
276+
for line in stdout.lines() {
277+
let line = match line {
278+
Ok(line) => line,
279+
Err(err) => {
280+
log::error!("Couldn't read line from cargo: {}", err);
281+
continue;
282+
}
283+
};
284+
285+
let message = serde_json::from_str::<cargo_metadata::Message>(&line);
286+
let message = match message {
287+
Ok(message) => message,
288+
Err(err) => {
289+
log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
290+
continue;
291+
}
292+
};
293+
294+
if !on_message(message) {
295+
break;
296+
}
297+
}
298+
299+
child
300+
}
301+
249302
impl WatchThread {
250303
fn dummy() -> WatchThread {
251304
WatchThread { handle: None, message_recv: never() }
252305
}
253306

254-
fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
307+
fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
255308
let mut args: Vec<String> = vec![
256309
options.command.clone(),
257310
"--workspace".to_string(),
258311
"--message-format=json".to_string(),
259312
"--manifest-path".to_string(),
260-
format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
313+
format!("{}/Cargo.toml", workspace_root.display()),
261314
];
262315
if options.all_targets {
263316
args.push("--all-targets".to_string());
264317
}
265318
args.extend(options.args.iter().cloned());
266319

267320
let (message_send, message_recv) = unbounded();
268-
let enabled = options.enable;
269-
let handle = std::thread::spawn(move || {
270-
if !enabled {
271-
return;
272-
}
273-
274-
let mut command = Command::new("cargo")
275-
.args(&args)
276-
.stdout(Stdio::piped())
277-
.stderr(Stdio::null())
278-
.stdin(Stdio::null())
279-
.spawn()
280-
.expect("couldn't launch cargo");
281-
282-
// If we trigger an error here, we will do so in the loop instead,
283-
// which will break out of the loop, and continue the shutdown
284-
let _ = message_send.send(CheckEvent::Begin);
285-
286-
// We manually read a line at a time, instead of using serde's
287-
// stream deserializers, because the deserializer cannot recover
288-
// from an error, resulting in it getting stuck, because we try to
289-
// be resillient against failures.
290-
//
291-
// Because cargo only outputs one JSON object per line, we can
292-
// simply skip a line if it doesn't parse, which just ignores any
293-
// erroneus output.
294-
let stdout = BufReader::new(command.stdout.take().unwrap());
295-
for line in stdout.lines() {
296-
let line = match line {
297-
Ok(line) => line,
298-
Err(err) => {
299-
log::error!("Couldn't read line from cargo: {}", err);
300-
continue;
301-
}
302-
};
303-
304-
let message = serde_json::from_str::<cargo_metadata::Message>(&line);
305-
let message = match message {
306-
Ok(message) => message,
307-
Err(err) => {
308-
log::error!(
309-
"Invalid json from cargo check, ignoring ({}): {:?} ",
310-
err,
311-
line
312-
);
313-
continue;
321+
let workspace_root = workspace_root.to_owned();
322+
let handle = if options.enable {
323+
Some(std::thread::spawn(move || {
324+
// If we trigger an error here, we will do so in the loop instead,
325+
// which will break out of the loop, and continue the shutdown
326+
let _ = message_send.send(CheckEvent::Begin);
327+
328+
let mut child = run_cargo(&args, Some(&workspace_root), &mut |message| {
329+
// Skip certain kinds of messages to only spend time on what's useful
330+
match &message {
331+
Message::CompilerArtifact(artifact) if artifact.fresh => return true,
332+
Message::BuildScriptExecuted(_) => return true,
333+
Message::Unknown => return true,
334+
_ => {}
314335
}
315-
};
316-
317-
// Skip certain kinds of messages to only spend time on what's useful
318-
match &message {
319-
Message::CompilerArtifact(artifact) if artifact.fresh => continue,
320-
Message::BuildScriptExecuted(_) => continue,
321-
Message::Unknown => continue,
322-
_ => {}
323-
}
324336

325-
match message_send.send(CheckEvent::Msg(message)) {
326-
Ok(()) => {}
327-
Err(_err) => {
328-
// The send channel was closed, so we want to shutdown
329-
break;
330-
}
331-
}
332-
}
333-
334-
// We can ignore any error here, as we are already in the progress
335-
// of shutting down.
336-
let _ = message_send.send(CheckEvent::End);
337-
338-
// It is okay to ignore the result, as it only errors if the process is already dead
339-
let _ = command.kill();
340-
341-
// Again, we don't care about the exit status so just ignore the result
342-
let _ = command.wait();
343-
});
344-
WatchThread { handle: Some(handle), message_recv }
337+
match message_send.send(CheckEvent::Msg(message)) {
338+
Ok(()) => {}
339+
Err(_err) => {
340+
// The send channel was closed, so we want to shutdown
341+
return false;
342+
}
343+
};
344+
345+
true
346+
});
347+
348+
// We can ignore any error here, as we are already in the progress
349+
// of shutting down.
350+
let _ = message_send.send(CheckEvent::End);
351+
352+
// It is okay to ignore the result, as it only errors if the process is already dead
353+
let _ = child.kill();
354+
355+
// Again, we don't care about the exit status so just ignore the result
356+
let _ = child.wait();
357+
}))
358+
} else {
359+
None
360+
};
361+
WatchThread { handle, message_recv }
345362
}
346363
}
347364

crates/ra_db/src/input.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
77
//! actual IO is done and lowered to input.
88
9-
use std::{fmt, ops, str::FromStr};
9+
use std::{
10+
fmt, ops,
11+
path::{Path, PathBuf},
12+
str::FromStr,
13+
};
1014

1115
use ra_cfg::CfgOptions;
1216
use ra_syntax::SmolStr;
@@ -144,7 +148,7 @@ pub struct Env {
144148
// crate. We store a map to allow remap it to ExternSourceId
145149
#[derive(Default, Debug, Clone, PartialEq, Eq)]
146150
pub struct ExternSource {
147-
extern_paths: FxHashMap<String, ExternSourceId>,
151+
extern_paths: FxHashMap<PathBuf, ExternSourceId>,
148152
}
149153

150154
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -294,13 +298,10 @@ impl Env {
294298
}
295299

296300
impl ExternSource {
297-
pub fn extern_path(&self, path: &str) -> Option<(ExternSourceId, RelativePathBuf)> {
301+
pub fn extern_path(&self, path: impl AsRef<Path>) -> Option<(ExternSourceId, RelativePathBuf)> {
302+
let path = path.as_ref();
298303
self.extern_paths.iter().find_map(|(root_path, id)| {
299-
if path.starts_with(root_path) {
300-
let mut rel_path = &path[root_path.len()..];
301-
if rel_path.starts_with("/") {
302-
rel_path = &rel_path[1..];
303-
}
304+
if let Ok(rel_path) = path.strip_prefix(root_path) {
304305
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
305306
Some((id.clone(), rel_path))
306307
} else {
@@ -309,8 +310,8 @@ impl ExternSource {
309310
})
310311
}
311312

312-
pub fn set_extern_path(&mut self, root_path: &str, root: ExternSourceId) {
313-
self.extern_paths.insert(root_path.to_owned(), root);
313+
pub fn set_extern_path(&mut self, root_path: &Path, root: ExternSourceId) {
314+
self.extern_paths.insert(root_path.to_path_buf(), root);
314315
}
315316
}
316317

crates/ra_project_model/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ cargo_metadata = "0.9.1"
1616
ra_arena = { path = "../ra_arena" }
1717
ra_db = { path = "../ra_db" }
1818
ra_cfg = { path = "../ra_cfg" }
19+
ra_cargo_watch = { path = "../ra_cargo_watch" }
1920

2021
serde = { version = "1.0.104", features = ["derive"] }
2122
serde_json = "1.0.48"

crates/ra_project_model/src/cargo_workspace.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
use std::path::{Path, PathBuf};
44

55
use anyhow::{Context, Result};
6-
use cargo_metadata::{CargoOpt, MetadataCommand};
6+
use cargo_metadata::{CargoOpt, Message, MetadataCommand, PackageId};
77
use ra_arena::{impl_arena_id, Arena, RawId};
8+
use ra_cargo_watch::run_cargo;
89
use ra_db::Edition;
910
use rustc_hash::FxHashMap;
1011
use serde::Deserialize;
@@ -35,11 +36,19 @@ pub struct CargoFeatures {
3536
/// List of features to activate.
3637
/// This will be ignored if `cargo_all_features` is true.
3738
pub features: Vec<String>,
39+
40+
/// Runs cargo check on launch to figure out the correct values of OUT_DIR
41+
pub load_out_dirs_from_check: bool,
3842
}
3943

4044
impl Default for CargoFeatures {
4145
fn default() -> Self {
42-
CargoFeatures { no_default_features: false, all_features: true, features: Vec::new() }
46+
CargoFeatures {
47+
no_default_features: false,
48+
all_features: true,
49+
features: Vec::new(),
50+
load_out_dirs_from_check: false,
51+
}
4352
}
4453
}
4554

@@ -60,6 +69,7 @@ struct PackageData {
6069
dependencies: Vec<PackageDependency>,
6170
edition: Edition,
6271
features: Vec<String>,
72+
out_dir: Option<PathBuf>,
6373
}
6474

6575
#[derive(Debug, Clone)]
@@ -131,6 +141,9 @@ impl Package {
131141
) -> impl Iterator<Item = &'a PackageDependency> + 'a {
132142
ws.packages[self].dependencies.iter()
133143
}
144+
pub fn out_dir(self, ws: &CargoWorkspace) -> Option<&Path> {
145+
ws.packages[self].out_dir.as_ref().map(PathBuf::as_path)
146+
}
134147
}
135148

136149
impl Target {
@@ -173,6 +186,12 @@ impl CargoWorkspace {
173186
let meta = meta.exec().with_context(|| {
174187
format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
175188
})?;
189+
190+
let mut out_dir_by_id = FxHashMap::default();
191+
if cargo_features.load_out_dirs_from_check {
192+
out_dir_by_id = load_out_dirs(cargo_toml, cargo_features);
193+
}
194+
176195
let mut pkg_by_id = FxHashMap::default();
177196
let mut packages = Arena::default();
178197
let mut targets = Arena::default();
@@ -193,6 +212,7 @@ impl CargoWorkspace {
193212
edition,
194213
dependencies: Vec::new(),
195214
features: Vec::new(),
215+
out_dir: out_dir_by_id.get(&id).cloned(),
196216
});
197217
let pkg_data = &mut packages[pkg];
198218
pkg_by_id.insert(id, pkg);
@@ -252,3 +272,46 @@ impl CargoWorkspace {
252272
&self.workspace_root
253273
}
254274
}
275+
276+
pub fn load_out_dirs(
277+
cargo_toml: &Path,
278+
cargo_features: &CargoFeatures,
279+
) -> FxHashMap<PackageId, PathBuf> {
280+
let mut args: Vec<String> = vec![
281+
"check".to_string(),
282+
"--message-format=json".to_string(),
283+
"--manifest-path".to_string(),
284+
format!("{}", cargo_toml.display()),
285+
];
286+
287+
if cargo_features.all_features {
288+
args.push("--all-features".to_string());
289+
} else if cargo_features.no_default_features {
290+
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
291+
// https://github.com/oli-obk/cargo_metadata/issues/79
292+
args.push("--no-default-features".to_string());
293+
} else if !cargo_features.features.is_empty() {
294+
for feature in &cargo_features.features {
295+
args.push(feature.clone());
296+
}
297+
}
298+
299+
let mut res = FxHashMap::default();
300+
let mut child = run_cargo(&args, cargo_toml.parent(), &mut |message| {
301+
match message {
302+
Message::BuildScriptExecuted(message) => {
303+
let package_id = message.package_id;
304+
let out_dir = message.out_dir;
305+
res.insert(package_id, out_dir);
306+
}
307+
308+
Message::CompilerArtifact(_) => (),
309+
Message::CompilerMessage(_) => (),
310+
Message::Unknown => (),
311+
}
312+
true
313+
});
314+
315+
let _ = child.wait();
316+
res
317+
}

crates/ra_project_model/src/json_project.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct Crate {
2222
pub(crate) deps: Vec<Dep>,
2323
pub(crate) atom_cfgs: FxHashSet<String>,
2424
pub(crate) key_value_cfgs: FxHashMap<String, String>,
25+
pub(crate) out_dir: Option<PathBuf>,
2526
}
2627

2728
#[derive(Clone, Copy, Debug, Deserialize)]

0 commit comments

Comments
 (0)