Merge #9752
9752: feature: Declare proc-macro dependent crates in `rust-project.json` r=matklad a=tobywf This adds the `is_proc_macro` flag in `rust-project.json`. By default, this is `false` and not required, so existing projects won't break/have the same behavior as before this change. If the flag is true, a dependency to the `proc_macro` sysroot crate is added (if it exists), so that rust-analyzer can resolve those imports. This fixes #9726 . I've also added some tests in the second commit. The first is a smoke test for a basic, minimal `rust-project.json` file. The second is a more targeted test for the flag. Both tests depend on the fake sysroot (a bunch of directories in the correct layout with empty `lib.rs` files), and also on `env!("CARGO_MANIFEST_DIR")` being an absolute path. I'm not sure if the later assumption is valid on all platforms. I wanted to at least try and add tests, but I'm happy to rework them or remove them if you don't think that's the way to go. (You can license/relicense my contribution in any way you wish without contacting me.) Co-authored-by: Toby Fleming <sourcecode@tobywf.com>
This commit is contained in:
commit
314e2e75c0
@ -37,6 +37,7 @@ pub struct Crate {
|
||||
pub(crate) is_workspace_member: bool,
|
||||
pub(crate) include: Vec<AbsPathBuf>,
|
||||
pub(crate) exclude: Vec<AbsPathBuf>,
|
||||
pub(crate) is_proc_macro: bool,
|
||||
}
|
||||
|
||||
impl ProjectJson {
|
||||
@ -96,6 +97,7 @@ pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
|
||||
is_workspace_member,
|
||||
include,
|
||||
exclude,
|
||||
is_proc_macro: crate_data.is_proc_macro,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
@ -135,6 +137,8 @@ struct CrateData {
|
||||
proc_macro_dylib_path: Option<PathBuf>,
|
||||
is_workspace_member: Option<bool>,
|
||||
source: Option<CrateSource>,
|
||||
#[serde(default)]
|
||||
is_proc_macro: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
|
@ -1,12 +1,20 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use base_db::FileId;
|
||||
use base_db::{CrateGraph, FileId};
|
||||
use expect_test::{expect, Expect};
|
||||
use paths::AbsPath;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::{CargoWorkspace, CfgOverrides, ProjectWorkspace, Sysroot, WorkspaceBuildScripts};
|
||||
use crate::{
|
||||
CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot,
|
||||
WorkspaceBuildScripts,
|
||||
};
|
||||
|
||||
fn check(file: &str, expect: Expect) {
|
||||
let meta = get_test_metadata(file);
|
||||
fn load_cargo(file: &str) -> CrateGraph {
|
||||
let meta = get_test_json_file(file);
|
||||
let cargo_workspace = CargoWorkspace::new(meta);
|
||||
let project_workspace = ProjectWorkspace::Cargo {
|
||||
cargo: cargo_workspace,
|
||||
@ -16,23 +24,21 @@ fn check(file: &str, expect: Expect) {
|
||||
rustc_cfg: Vec::new(),
|
||||
cfg_overrides: CfgOverrides::default(),
|
||||
};
|
||||
|
||||
let crate_graph = project_workspace.to_crate_graph(None, {
|
||||
let mut counter = 0;
|
||||
&mut move |_path| {
|
||||
counter += 1;
|
||||
Some(FileId(counter))
|
||||
}
|
||||
});
|
||||
|
||||
let mut crate_graph = format!("{:#?}", crate_graph);
|
||||
replace_root(&mut crate_graph, false);
|
||||
|
||||
expect.assert_eq(&crate_graph);
|
||||
to_crate_graph(project_workspace)
|
||||
}
|
||||
|
||||
fn get_test_metadata(file: &str) -> cargo_metadata::Metadata {
|
||||
let mut json = get_test_data(file).parse::<serde_json::Value>().unwrap();
|
||||
fn load_rust_project(file: &str) -> CrateGraph {
|
||||
let data = get_test_json_file(file);
|
||||
let project = rooted_project_json(data);
|
||||
let sysroot = Some(get_fake_sysroot());
|
||||
let project_workspace = ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new() };
|
||||
to_crate_graph(project_workspace)
|
||||
}
|
||||
|
||||
fn get_test_json_file<T: DeserializeOwned>(file: &str) -> T {
|
||||
let file = get_test_path(file);
|
||||
let data = std::fs::read_to_string(file).unwrap();
|
||||
let mut json = data.parse::<serde_json::Value>().unwrap();
|
||||
fixup_paths(&mut json);
|
||||
return serde_json::from_value(json).unwrap();
|
||||
|
||||
@ -58,16 +64,46 @@ fn replace_root(s: &mut String, direction: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_test_data(file: &str) -> String {
|
||||
fn get_test_path(file: &str) -> PathBuf {
|
||||
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let file = base.join("test_data").join(file);
|
||||
std::fs::read_to_string(file).unwrap()
|
||||
base.join("test_data").join(file)
|
||||
}
|
||||
|
||||
fn get_fake_sysroot() -> Sysroot {
|
||||
let sysroot_path = get_test_path("fake-sysroot");
|
||||
let sysroot_src_dir = AbsPath::assert(&sysroot_path);
|
||||
Sysroot::load(&sysroot_src_dir).unwrap()
|
||||
}
|
||||
|
||||
fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
|
||||
let mut root = "$ROOT$".to_string();
|
||||
replace_root(&mut root, true);
|
||||
let path = Path::new(&root);
|
||||
let base = AbsPath::assert(path);
|
||||
ProjectJson::new(base, data)
|
||||
}
|
||||
|
||||
fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
|
||||
project_workspace.to_crate_graph(None, {
|
||||
let mut counter = 0;
|
||||
&mut move |_path| {
|
||||
counter += 1;
|
||||
Some(FileId(counter))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn check_crate_graph(crate_graph: CrateGraph, expect: Expect) {
|
||||
let mut crate_graph = format!("{:#?}", crate_graph);
|
||||
replace_root(&mut crate_graph, false);
|
||||
expect.assert_eq(&crate_graph);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hello_world_project_model() {
|
||||
check(
|
||||
"hello-world-metadata.json",
|
||||
fn cargo_hello_world_project_model() {
|
||||
let crate_graph = load_cargo("hello-world-metadata.json");
|
||||
check_crate_graph(
|
||||
crate_graph,
|
||||
expect![[r#"
|
||||
CrateGraph {
|
||||
arena: {
|
||||
@ -514,3 +550,468 @@ fn hello_world_project_model() {
|
||||
}"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_project_hello_world_project_model() {
|
||||
let crate_graph = load_rust_project("hello-world-project.json");
|
||||
check_crate_graph(
|
||||
crate_graph,
|
||||
expect![[r#"
|
||||
CrateGraph {
|
||||
arena: {
|
||||
CrateId(
|
||||
0,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
1,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"alloc",
|
||||
),
|
||||
canonical_name: "alloc",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
1,
|
||||
),
|
||||
name: CrateName(
|
||||
"core",
|
||||
),
|
||||
},
|
||||
],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
10,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
11,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"unwind",
|
||||
),
|
||||
canonical_name: "unwind",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
7,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
8,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"std_detect",
|
||||
),
|
||||
canonical_name: "std_detect",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
4,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
5,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"proc_macro",
|
||||
),
|
||||
canonical_name: "proc_macro",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
6,
|
||||
),
|
||||
name: CrateName(
|
||||
"std",
|
||||
),
|
||||
},
|
||||
],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
1,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
2,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"core",
|
||||
),
|
||||
canonical_name: "core",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
11,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
12,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"hello_world",
|
||||
),
|
||||
canonical_name: "hello_world",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
1,
|
||||
),
|
||||
name: CrateName(
|
||||
"core",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
0,
|
||||
),
|
||||
name: CrateName(
|
||||
"alloc",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
6,
|
||||
),
|
||||
name: CrateName(
|
||||
"std",
|
||||
),
|
||||
},
|
||||
],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
8,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
9,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"term",
|
||||
),
|
||||
canonical_name: "term",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
5,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
6,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"profiler_builtins",
|
||||
),
|
||||
canonical_name: "profiler_builtins",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
2,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
3,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"panic_abort",
|
||||
),
|
||||
canonical_name: "panic_abort",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
9,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
10,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"test",
|
||||
),
|
||||
canonical_name: "test",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
6,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
7,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"std",
|
||||
),
|
||||
canonical_name: "std",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
0,
|
||||
),
|
||||
name: CrateName(
|
||||
"alloc",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
1,
|
||||
),
|
||||
name: CrateName(
|
||||
"core",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
2,
|
||||
),
|
||||
name: CrateName(
|
||||
"panic_abort",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
3,
|
||||
),
|
||||
name: CrateName(
|
||||
"panic_unwind",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
5,
|
||||
),
|
||||
name: CrateName(
|
||||
"profiler_builtins",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
7,
|
||||
),
|
||||
name: CrateName(
|
||||
"std_detect",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
8,
|
||||
),
|
||||
name: CrateName(
|
||||
"term",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
9,
|
||||
),
|
||||
name: CrateName(
|
||||
"test",
|
||||
),
|
||||
},
|
||||
Dependency {
|
||||
crate_id: CrateId(
|
||||
10,
|
||||
),
|
||||
name: CrateName(
|
||||
"unwind",
|
||||
),
|
||||
},
|
||||
],
|
||||
proc_macro: [],
|
||||
},
|
||||
CrateId(
|
||||
3,
|
||||
): CrateData {
|
||||
root_file_id: FileId(
|
||||
4,
|
||||
),
|
||||
edition: Edition2018,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"panic_unwind",
|
||||
),
|
||||
canonical_name: "panic_unwind",
|
||||
},
|
||||
),
|
||||
cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
potential_cfg_options: CfgOptions(
|
||||
[],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
dependencies: [],
|
||||
proc_macro: [],
|
||||
},
|
||||
},
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_project_is_proc_macro_has_proc_macro_dep() {
|
||||
let crate_graph = load_rust_project("is-proc-macro-project.json");
|
||||
// Since the project only defines one crate (outside the sysroot crates),
|
||||
// it should be the one with the biggest Id.
|
||||
let crate_id = crate_graph.iter().max().unwrap();
|
||||
let crate_data = &crate_graph[crate_id];
|
||||
// Assert that the project crate with `is_proc_macro` has a dependency
|
||||
// on the proc_macro sysroot crate.
|
||||
crate_data.dependencies.iter().find(|&dep| dep.name.deref() == "proc_macro").unwrap();
|
||||
}
|
||||
|
@ -446,10 +446,20 @@ fn project_json_to_crate_graph(
|
||||
|
||||
for (from, krate) in project.crates() {
|
||||
if let Some(&from) = crates.get(&from) {
|
||||
if let Some((public_deps, _proc_macro)) = &sysroot_deps {
|
||||
if let Some((public_deps, libproc_macro)) = &sysroot_deps {
|
||||
for (name, to) in public_deps.iter() {
|
||||
add_dep(&mut crate_graph, from, name.clone(), *to)
|
||||
}
|
||||
if krate.is_proc_macro {
|
||||
if let Some(proc_macro) = libproc_macro {
|
||||
add_dep(
|
||||
&mut crate_graph,
|
||||
from,
|
||||
CrateName::new("proc_macro").unwrap(),
|
||||
*proc_macro,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for dep in &krate.deps {
|
||||
|
12
crates/project_model/test_data/hello-world-project.json
Normal file
12
crates/project_model/test_data/hello-world-project.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"sysroot_src": null,
|
||||
"crates": [
|
||||
{
|
||||
"display_name": "hello_world",
|
||||
"root_module": "$ROOT$src/lib.rs",
|
||||
"edition": "2018",
|
||||
"deps": [],
|
||||
"is_workspace_member": true
|
||||
}
|
||||
]
|
||||
}
|
13
crates/project_model/test_data/is-proc-macro-project.json
Normal file
13
crates/project_model/test_data/is-proc-macro-project.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"sysroot_src": null,
|
||||
"crates": [
|
||||
{
|
||||
"display_name": "is_proc_macro",
|
||||
"root_module": "$ROOT$src/lib.rs",
|
||||
"edition": "2018",
|
||||
"deps": [],
|
||||
"is_workspace_member": true,
|
||||
"is_proc_macro": true
|
||||
}
|
||||
]
|
||||
}
|
@ -578,6 +578,8 @@ interface Crate {
|
||||
/// the `env!` macro
|
||||
env: : { [key: string]: string; },
|
||||
|
||||
/// Whether the crate is a proc-macro crate.
|
||||
is_proc_macro: bool;
|
||||
/// For proc-macro crates, path to compiled
|
||||
/// proc-macro (.so file).
|
||||
proc_macro_dylib_path?: string;
|
||||
@ -597,7 +599,7 @@ Specifically, the `roots` setup will be different eventually.
|
||||
|
||||
There are three ways to feed `rust-project.json` to rust-analyzer:
|
||||
|
||||
* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it.
|
||||
* Place `rust-project.json` file at the root of the project, and rust-analyzer will discover it.
|
||||
* Specify `"rust-analyzer.linkedProjects": [ "path/to/rust-project.json" ]` in the settings (and make sure that your LSP client sends settings as a part of initialize request).
|
||||
* Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user