Skip to content

Commit 651983d

Browse files
committed
feature: move linked_projects discovery to the rust-analyzer server
1 parent 4af21ff commit 651983d

File tree

23 files changed

+506
-135
lines changed

23 files changed

+506
-135
lines changed

Cargo.lock

Lines changed: 87 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/flycheck/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ process-wrap.workspace = true
2424
paths.workspace = true
2525
stdx.workspace = true
2626
toolchain.workspace = true
27+
project-model.workspace = true
2728

2829
[lints]
2930
workspace = true

crates/flycheck/src/json_workspace.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//! A `cargo-metadata`-equivalent for non-Cargo build systems.
2+
use std::{io, process::Command};
3+
4+
use crossbeam_channel::Sender;
5+
use paths::Utf8PathBuf;
6+
use project_model::ProjectJsonData;
7+
use serde::{Deserialize, Serialize};
8+
9+
use crate::command::{CommandHandle, ParseFromLine};
10+
11+
/// A command wrapper for getting a `rust-project.json`.
12+
///
13+
/// This is analogous to `cargo-metadata`, but for non-Cargo build systems.
14+
pub struct JsonWorkspace {
15+
command: Vec<String>,
16+
sender: Sender<DiscoverProjectMessage>,
17+
}
18+
19+
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
20+
#[serde(rename_all = "camelCase")]
21+
pub enum JsonArguments {
22+
Path(Utf8PathBuf),
23+
Label(String),
24+
}
25+
26+
impl JsonWorkspace {
27+
/// Create a new [JsonWorkspace].
28+
pub fn new(sender: Sender<DiscoverProjectMessage>, command: Vec<String>) -> Self {
29+
Self { sender, command }
30+
}
31+
32+
/// Spawn the command inside [JsonWorkspace] and report progress, if any.
33+
pub fn spawn(&self, arg: JsonArguments) -> io::Result<JsonWorkspaceHandle> {
34+
let command = &self.command[0];
35+
let args = &self.command[1..];
36+
37+
let mut cmd = Command::new(command);
38+
cmd.args(args);
39+
40+
let arg = serde_json::to_string(&arg)?;
41+
cmd.arg(arg);
42+
43+
Ok(JsonWorkspaceHandle { _handle: CommandHandle::spawn(cmd, self.sender.clone())? })
44+
}
45+
}
46+
47+
/// A handle to a spawned [JsonWorkspace].
48+
#[derive(Debug)]
49+
pub struct JsonWorkspaceHandle {
50+
_handle: CommandHandle<DiscoverProjectMessage>,
51+
}
52+
53+
/// An enum containing either progress messages or the materialized rust-project.
54+
#[derive(Debug, Clone, Deserialize, Serialize)]
55+
#[serde(tag = "type")]
56+
pub enum DiscoverProjectMessage {
57+
Error { message: String, context: Option<String> },
58+
Progress { message: String },
59+
Finished(FinishedOutput),
60+
}
61+
62+
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
63+
pub struct FinishedOutput {
64+
pub project: ProjectJsonData,
65+
pub buildfile: Utf8PathBuf,
66+
}
67+
68+
impl ParseFromLine for DiscoverProjectMessage {
69+
fn from_line(line: &str, _error: &mut String) -> Option<Self> {
70+
let Ok(value) = serde_json::from_str::<serde_json::Value>(line) else {
71+
return Some(DiscoverProjectMessage::Error { message: line.to_owned(), context: None });
72+
};
73+
74+
if let Ok(project) = serde_json::from_value::<FinishedOutput>(value.clone()) {
75+
return Some(DiscoverProjectMessage::Finished(project));
76+
}
77+
78+
if let Some(message) = value.pointer("/fields/message") {
79+
return Some(DiscoverProjectMessage::Progress {
80+
message: message.as_str().unwrap().to_owned(),
81+
});
82+
}
83+
84+
if let Some(error) = value.pointer("/fields/error") {
85+
if let Some(source) = value.pointer("/fields/source") {
86+
return Some(DiscoverProjectMessage::Error {
87+
message: error.as_str().unwrap().to_owned(),
88+
context: Some(source.as_str().unwrap().to_owned()),
89+
});
90+
}
91+
}
92+
93+
None
94+
}
95+
96+
fn from_eof() -> Option<Self> {
97+
None
98+
}
99+
}

crates/flycheck/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ pub use cargo_metadata::diagnostic::{
2222
use toolchain::Tool;
2323

2424
mod command;
25+
mod json_workspace;
2526
mod test_runner;
2627

2728
use command::{CommandHandle, ParseFromLine};
29+
pub use json_workspace::{
30+
DiscoverProjectMessage, JsonArguments, JsonWorkspace, JsonWorkspaceHandle,
31+
};
2832
pub use test_runner::{CargoTestHandle, CargoTestMessage, TestState};
2933

3034
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
@@ -242,7 +246,7 @@ enum FlycheckStatus {
242246
Finished,
243247
}
244248

245-
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
249+
pub const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
246250

247251
impl FlycheckActor {
248252
fn new(

crates/load-cargo/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use itertools::Itertools;
1717
use proc_macro_api::{MacroDylib, ProcMacroServer};
1818
use project_model::{CargoConfig, ManifestPath, PackageRoot, ProjectManifest, ProjectWorkspace};
1919
use span::Span;
20-
use tracing::instrument;
2120
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
2221

2322
pub struct LoadCargoConfig {
@@ -51,7 +50,6 @@ pub fn load_workspace_at(
5150
load_workspace(workspace, &cargo_config.extra_env, load_config)
5251
}
5352

54-
#[instrument(skip_all)]
5553
pub fn load_workspace(
5654
ws: ProjectWorkspace,
5755
extra_env: &FxHashMap<String, String>,

crates/project-model/src/project_json.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ impl ProjectJson {
275275
self.manifest.as_ref()
276276
}
277277

278+
pub fn crate_by_buildfile(&self, path: &AbsPath) -> Option<Build> {
279+
let path: &std::path::Path = path.as_ref();
280+
self.crates
281+
.iter()
282+
.filter(|krate| krate.is_workspace_member)
283+
.filter_map(|krate| krate.build.clone())
284+
.find(|build| build.build_file.as_std_path() == path)
285+
}
286+
278287
/// Returns the path to the project's manifest or root folder, if no manifest exists.
279288
pub fn manifest_or_root(&self) -> &AbsPath {
280289
self.manifest.as_ref().map_or(&self.project_root, |manifest| manifest.as_ref())
@@ -285,16 +294,17 @@ impl ProjectJson {
285294
}
286295
}
287296

288-
#[derive(Serialize, Deserialize, Debug, Clone)]
297+
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
289298
pub struct ProjectJsonData {
290299
sysroot: Option<Utf8PathBuf>,
291300
sysroot_src: Option<Utf8PathBuf>,
292301
crates: Vec<CrateData>,
293302
#[serde(default)]
294303
runnables: Vec<RunnableData>,
304+
pub buildfile: Option<Utf8PathBuf>,
295305
}
296306

297-
#[derive(Serialize, Deserialize, Debug, Clone)]
307+
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
298308
struct CrateData {
299309
display_name: Option<String>,
300310
root_module: Utf8PathBuf,
@@ -318,7 +328,7 @@ struct CrateData {
318328
build: Option<BuildData>,
319329
}
320330

321-
#[derive(Serialize, Deserialize, Debug, Clone)]
331+
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
322332
#[serde(rename = "edition")]
323333
enum EditionData {
324334
#[serde(rename = "2015")]
@@ -331,7 +341,7 @@ enum EditionData {
331341
Edition2024,
332342
}
333343

334-
#[derive(Debug, Clone, Serialize, Deserialize)]
344+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
335345
pub struct BuildData {
336346
label: String,
337347
build_file: Utf8PathBuf,
@@ -418,7 +428,7 @@ pub(crate) struct Dep {
418428
pub(crate) name: CrateName,
419429
}
420430

421-
#[derive(Serialize, Deserialize, Debug, Clone)]
431+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
422432
struct CrateSource {
423433
include_dirs: Vec<Utf8PathBuf>,
424434
exclude_dirs: Vec<Utf8PathBuf>,

crates/project-model/src/workspace.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::{
3030
utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
3131
ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
3232
};
33+
use tracing::{debug, error, info};
3334

3435
pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>;
3536

@@ -249,7 +250,7 @@ impl ProjectWorkspace {
249250
};
250251

251252
let rustc = rustc_dir.and_then(|rustc_dir| {
252-
tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
253+
info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
253254
match CargoWorkspace::fetch_metadata(
254255
&rustc_dir,
255256
cargo_toml.parent(),
@@ -772,9 +773,9 @@ impl ProjectWorkspace {
772773
};
773774

774775
if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() {
775-
tracing::debug!("Patched std to depend on cfg-if")
776+
debug!("Patched std to depend on cfg-if")
776777
} else {
777-
tracing::debug!("Did not patch std to depend on cfg-if")
778+
debug!("Did not patch std to depend on cfg-if")
778779
}
779780
(crate_graph, proc_macros)
780781
}
@@ -919,6 +920,11 @@ fn project_json_to_crate_graph(
919920
CrateOrigin::Local { repo: None, name: None }
920921
},
921922
);
923+
debug!(
924+
?crate_graph_crate_id,
925+
crate = display_name.as_ref().map(|name| name.canonical_name()),
926+
"added root to crate graph"
927+
);
922928
if *is_proc_macro {
923929
if let Some(path) = proc_macro_dylib_path.clone() {
924930
let node = Ok((
@@ -933,6 +939,7 @@ fn project_json_to_crate_graph(
933939
)
934940
.collect();
935941

942+
debug!(map = ?idx_to_crate_id);
936943
for (from_idx, krate) in project.crates() {
937944
if let Some(&from) = idx_to_crate_id.get(&from_idx) {
938945
public_deps.add_to_crate_graph(crate_graph, from);
@@ -1158,7 +1165,7 @@ fn detached_file_to_crate_graph(
11581165
let file_id = match load(detached_file) {
11591166
Some(file_id) => file_id,
11601167
None => {
1161-
tracing::error!("Failed to load detached file {:?}", detached_file);
1168+
error!("Failed to load detached file {:?}", detached_file);
11621169
return (crate_graph, FxHashMap::default());
11631170
}
11641171
};
@@ -1355,7 +1362,7 @@ fn add_target_crate_root(
13551362
crate_id
13561363
}
13571364

1358-
#[derive(Default)]
1365+
#[derive(Default, Debug)]
13591366
struct SysrootPublicDeps {
13601367
deps: Vec<(CrateName, CrateId, bool)>,
13611368
}

crates/rust-analyzer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mimalloc = { version = "0.1.30", default-features = false, optional = true }
3939
lsp-server.workspace = true
4040
tracing.workspace = true
4141
tracing-subscriber.workspace = true
42+
pretty_assertions = "1.4"
4243
tracing-tree.workspace = true
4344
triomphe.workspace = true
4445
toml = "0.8.8"

crates/rust-analyzer/src/bin/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ fn run_server() -> anyhow::Result<()> {
176176
return Err(e.into());
177177
}
178178
};
179+
179180
tracing::info!("InitializeParams: {}", initialize_params);
180181
let lsp_types::InitializeParams {
181182
root_uri,
@@ -265,7 +266,10 @@ fn run_server() -> anyhow::Result<()> {
265266
return Err(e.into());
266267
}
267268

268-
if !config.has_linked_projects() && config.detached_files().is_empty() {
269+
if config.discover_command().is_none()
270+
&& !config.has_linked_projects()
271+
&& config.detached_files().is_empty()
272+
{
269273
config.rediscover_workspaces();
270274
}
271275

0 commit comments

Comments
 (0)