Skip to content

Commit 1d07c5b

Browse files
committed
Auto merge of #14218 - Veykril:root-dedup, r=Veykril
Deduplicate source roots that have overlapping include paths Fixes flycheck not working for the rustc workspace when using `linkedProjects`
2 parents c386316 + 47a567b commit 1d07c5b

File tree

4 files changed

+135
-54
lines changed

4 files changed

+135
-54
lines changed

crates/flycheck/src/lib.rs

+26-15
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl fmt::Display for FlycheckConfig {
7676
#[derive(Debug)]
7777
pub struct FlycheckHandle {
7878
// XXX: drop order is significant
79-
sender: Sender<Restart>,
79+
sender: Sender<StateChange>,
8080
_thread: jod_thread::JoinHandle,
8181
id: usize,
8282
}
@@ -89,7 +89,7 @@ impl FlycheckHandle {
8989
workspace_root: AbsPathBuf,
9090
) -> FlycheckHandle {
9191
let actor = FlycheckActor::new(id, sender, config, workspace_root);
92-
let (sender, receiver) = unbounded::<Restart>();
92+
let (sender, receiver) = unbounded::<StateChange>();
9393
let thread = jod_thread::Builder::new()
9494
.name("Flycheck".to_owned())
9595
.spawn(move || actor.run(receiver))
@@ -99,12 +99,12 @@ impl FlycheckHandle {
9999

100100
/// Schedule a re-start of the cargo check worker.
101101
pub fn restart(&self) {
102-
self.sender.send(Restart::Yes).unwrap();
102+
self.sender.send(StateChange::Restart).unwrap();
103103
}
104104

105105
/// Stop this cargo check worker.
106106
pub fn cancel(&self) {
107-
self.sender.send(Restart::No).unwrap();
107+
self.sender.send(StateChange::Cancel).unwrap();
108108
}
109109

110110
pub fn id(&self) -> usize {
@@ -149,9 +149,9 @@ pub enum Progress {
149149
DidFailToRestart(String),
150150
}
151151

152-
enum Restart {
153-
Yes,
154-
No,
152+
enum StateChange {
153+
Restart,
154+
Cancel,
155155
}
156156

157157
/// A [`FlycheckActor`] is a single check instance of a workspace.
@@ -172,7 +172,7 @@ struct FlycheckActor {
172172
}
173173

174174
enum Event {
175-
Restart(Restart),
175+
RequestStateChange(StateChange),
176176
CheckEvent(Option<CargoMessage>),
177177
}
178178

@@ -191,30 +191,31 @@ impl FlycheckActor {
191191
self.send(Message::Progress { id: self.id, progress });
192192
}
193193

194-
fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
194+
fn next_event(&self, inbox: &Receiver<StateChange>) -> Option<Event> {
195195
let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
196196
if let Ok(msg) = inbox.try_recv() {
197197
// give restarts a preference so check outputs don't block a restart or stop
198-
return Some(Event::Restart(msg));
198+
return Some(Event::RequestStateChange(msg));
199199
}
200200
select! {
201-
recv(inbox) -> msg => msg.ok().map(Event::Restart),
201+
recv(inbox) -> msg => msg.ok().map(Event::RequestStateChange),
202202
recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
203203
}
204204
}
205205

206-
fn run(mut self, inbox: Receiver<Restart>) {
206+
fn run(mut self, inbox: Receiver<StateChange>) {
207207
'event: while let Some(event) = self.next_event(&inbox) {
208208
match event {
209-
Event::Restart(Restart::No) => {
209+
Event::RequestStateChange(StateChange::Cancel) => {
210+
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
210211
self.cancel_check_process();
211212
}
212-
Event::Restart(Restart::Yes) => {
213+
Event::RequestStateChange(StateChange::Restart) => {
213214
// Cancel the previously spawned process
214215
self.cancel_check_process();
215216
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
216217
// restart chained with a stop, so just cancel
217-
if let Restart::No = restart {
218+
if let StateChange::Cancel = restart {
218219
continue 'event;
219220
}
220221
}
@@ -255,10 +256,20 @@ impl FlycheckActor {
255256
}
256257
Event::CheckEvent(Some(message)) => match message {
257258
CargoMessage::CompilerArtifact(msg) => {
259+
tracing::trace!(
260+
flycheck_id = self.id,
261+
artifact = msg.target.name,
262+
"artifact received"
263+
);
258264
self.report_progress(Progress::DidCheckCrate(msg.target.name));
259265
}
260266

261267
CargoMessage::Diagnostic(msg) => {
268+
tracing::trace!(
269+
flycheck_id = self.id,
270+
message = msg.message,
271+
"diagnostic received"
272+
);
262273
self.send(Message::AddDiagnostic {
263274
id: self.id,
264275
workspace_root: self.root.clone(),

crates/project-model/src/workspace.rs

+23-19
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl ProjectWorkspace {
237237
};
238238

239239
if let Some(sysroot) = &sysroot {
240-
tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
240+
tracing::info!(workspace = %cargo_toml.display(), src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
241241
}
242242

243243
let rustc_dir = match &config.rustc_source {
@@ -247,27 +247,31 @@ impl ProjectWorkspace {
247247
}
248248
None => None,
249249
};
250-
if let Some(rustc_dir) = &rustc_dir {
251-
tracing::info!(rustc_dir = %rustc_dir.display(), "Using rustc source");
252-
}
253250

254251
let rustc = match rustc_dir {
255-
Some(rustc_dir) => match CargoWorkspace::fetch_metadata(
256-
&rustc_dir,
257-
cargo_toml.parent(),
258-
config,
259-
progress,
260-
) {
261-
Ok(meta) => Some(CargoWorkspace::new(meta)),
262-
Err(e) => {
263-
tracing::error!(
264-
%e,
265-
"Failed to read Cargo metadata from rustc source at {}",
266-
rustc_dir.display()
267-
);
268-
None
252+
Some(rustc_dir) if rustc_dir == cargo_toml => {
253+
tracing::info!(rustc_dir = %rustc_dir.display(), "Workspace is the rustc workspace itself, not adding the rustc workspace separately");
254+
None
255+
}
256+
Some(rustc_dir) => {
257+
tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source");
258+
match CargoWorkspace::fetch_metadata(
259+
&rustc_dir,
260+
cargo_toml.parent(),
261+
config,
262+
progress,
263+
) {
264+
Ok(meta) => Some(CargoWorkspace::new(meta)),
265+
Err(e) => {
266+
tracing::error!(
267+
%e,
268+
"Failed to read Cargo metadata from rustc source at {}",
269+
rustc_dir.display()
270+
);
271+
None
272+
}
269273
}
270-
},
274+
}
271275
None => None,
272276
};
273277

crates/rust-analyzer/src/config.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -854,27 +854,27 @@ impl Config {
854854
}
855855
pub fn linked_projects(&self) -> Vec<LinkedProject> {
856856
match self.data.linkedProjects.as_slice() {
857-
[] => match self.discovered_projects.as_ref() {
858-
Some(discovered_projects) => {
859-
let exclude_dirs: Vec<_> = self
860-
.data
861-
.files_excludeDirs
857+
[] => {
858+
match self.discovered_projects.as_ref() {
859+
Some(discovered_projects) => {
860+
let exclude_dirs: Vec<_> = self
861+
.data
862+
.files_excludeDirs
863+
.iter()
864+
.map(|p| self.root_path.join(p))
865+
.collect();
866+
discovered_projects
862867
.iter()
863-
.map(|p| self.root_path.join(p))
864-
.collect();
865-
discovered_projects
866-
.iter()
867-
.filter(|p| {
868-
let (ProjectManifest::ProjectJson(path)
869-
| ProjectManifest::CargoToml(path)) = p;
868+
.filter(|(ProjectManifest::ProjectJson(path) | ProjectManifest::CargoToml(path))| {
870869
!exclude_dirs.iter().any(|p| path.starts_with(p))
871870
})
872871
.cloned()
873872
.map(LinkedProject::from)
874873
.collect()
874+
}
875+
None => Vec::new(),
875876
}
876-
None => Vec::new(),
877-
},
877+
}
878878
linked_projects => linked_projects
879879
.iter()
880880
.filter_map(|linked_project| match linked_project {

crates/rust-analyzer/src/reload.rs

+72-6
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,21 @@
1212
//! correct. Instead, we try to provide a best-effort service. Even if the
1313
//! project is currently loading and we don't have a full project model, we
1414
//! still want to respond to various requests.
15-
use std::{mem, sync::Arc};
15+
use std::{collections::hash_map::Entry, mem, sync::Arc};
1616

1717
use flycheck::{FlycheckConfig, FlycheckHandle};
1818
use hir::db::DefDatabase;
1919
use ide::Change;
20-
use ide_db::base_db::{
21-
CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
22-
ProcMacroLoadResult, SourceRoot, VfsPath,
20+
use ide_db::{
21+
base_db::{
22+
CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
23+
ProcMacroLoadResult, SourceRoot, VfsPath,
24+
},
25+
FxHashMap,
2326
};
27+
use itertools::Itertools;
2428
use proc_macro_api::{MacroDylib, ProcMacroServer};
25-
use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
29+
use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts};
2630
use syntax::SmolStr;
2731
use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
2832

@@ -494,7 +498,69 @@ impl ProjectFolders {
494498
let mut fsc = FileSetConfig::builder();
495499
let mut local_filesets = vec![];
496500

497-
for root in workspaces.iter().flat_map(|ws| ws.to_roots()) {
501+
// Dedup source roots
502+
// Depending on the project setup, we can have duplicated source roots, or for example in
503+
// the case of the rustc workspace, we can end up with two source roots that are almost the
504+
// same but not quite, like:
505+
// PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
506+
// PackageRoot {
507+
// is_local: true,
508+
// include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
509+
// exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
510+
// }
511+
//
512+
// The first one comes from the explicit rustc workspace which points to the rustc workspace itself
513+
// The second comes from the rustc workspace that we load as the actual project workspace
514+
// These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
515+
// So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
516+
// so we need to also coalesce the includes if they overlap.
517+
518+
let mut roots: Vec<_> = workspaces
519+
.iter()
520+
.flat_map(|ws| ws.to_roots())
521+
.update(|root| root.include.sort())
522+
.sorted_by(|a, b| a.include.cmp(&b.include))
523+
.collect();
524+
525+
// map that tracks indices of overlapping roots
526+
let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
527+
let mut done = false;
528+
529+
while !mem::replace(&mut done, true) {
530+
// maps include paths to indices of the corresponding root
531+
let mut include_to_idx = FxHashMap::default();
532+
// Find and note down the indices of overlapping roots
533+
for (idx, root) in roots.iter().filter(|it| !it.include.is_empty()).enumerate() {
534+
for include in &root.include {
535+
match include_to_idx.entry(include) {
536+
Entry::Occupied(e) => {
537+
overlap_map.entry(*e.get()).or_default().push(idx);
538+
}
539+
Entry::Vacant(e) => {
540+
e.insert(idx);
541+
}
542+
}
543+
}
544+
}
545+
for (k, v) in overlap_map.drain() {
546+
done = false;
547+
for v in v {
548+
let r = mem::replace(
549+
&mut roots[v],
550+
PackageRoot { is_local: false, include: vec![], exclude: vec![] },
551+
);
552+
roots[k].is_local |= r.is_local;
553+
roots[k].include.extend(r.include);
554+
roots[k].exclude.extend(r.exclude);
555+
}
556+
roots[k].include.sort();
557+
roots[k].exclude.sort();
558+
roots[k].include.dedup();
559+
roots[k].exclude.dedup();
560+
}
561+
}
562+
563+
for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
498564
let file_set_roots: Vec<VfsPath> =
499565
root.include.iter().cloned().map(VfsPath::from).collect();
500566

0 commit comments

Comments
 (0)