Skip to content

Commit 74f164b

Browse files
authored
rust_analyzer: make all paths in rust-project.json absolute (#3033)
This is a step towards supporting automatic project discovery (#2755). Relative paths are resolved against the location of rust-project.json, but in auto-discovery this is the location of the BUILD file being discovered, not the workspace root. We already use absolute paths for generated files, so rust-project.json is not workspace-independent today. The alternatives seem more complex for little gain: - Only use absolute paths for auto-discovery mode, relative otherwise. - Use ../../ to express all workspace paths relative to the BUILD. See #2755 (comment)
1 parent ba6af15 commit 74f164b

File tree

9 files changed

+128
-74
lines changed

9 files changed

+128
-74
lines changed

rust/private/rust_analyzer.bzl

+10-7
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ rust_analyzer_aspect = aspect(
187187
doc = "Annotates rust rules with RustAnalyzerInfo later used to build a rust-project.json",
188188
)
189189

190+
# Paths in the generated JSON file begin with one of these placeholders.
191+
# The gen_rust_project driver will replace them with absolute paths.
192+
_WORKSPACE_TEMPLATE = "__WORKSPACE__/"
190193
_EXEC_ROOT_TEMPLATE = "__EXEC_ROOT__/"
191194
_OUTPUT_BASE_TEMPLATE = "__OUTPUT_BASE__/"
192195

@@ -222,7 +225,7 @@ def _create_single_crate(ctx, attrs, info):
222225
# TODO: Some folks may want to override this for vendored dependencies.
223226
is_external = info.crate.root.path.startswith("external/")
224227
is_generated = not info.crate.root.is_source
225-
path_prefix = _EXEC_ROOT_TEMPLATE if is_external or is_generated else ""
228+
path_prefix = _EXEC_ROOT_TEMPLATE if is_external or is_generated else _WORKSPACE_TEMPLATE
226229
crate["is_workspace_member"] = not is_external
227230
crate["root_module"] = path_prefix + info.crate.root.path
228231
crate["source"] = {"exclude_dirs": [], "include_dirs": []}
@@ -231,7 +234,7 @@ def _create_single_crate(ctx, attrs, info):
231234
srcs = getattr(ctx.rule.files, "srcs", [])
232235
src_map = {src.short_path: src for src in srcs if src.is_source}
233236
if info.crate.root.short_path in src_map:
234-
crate["root_module"] = src_map[info.crate.root.short_path].path
237+
crate["root_module"] = _WORKSPACE_TEMPLATE + src_map[info.crate.root.short_path].path
235238
crate["source"]["include_dirs"].append(path_prefix + info.crate.root.dirname)
236239

237240
if info.build_info != None and info.build_info.out_dir != None:
@@ -263,7 +266,8 @@ def _create_single_crate(ctx, attrs, info):
263266
crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
264267
crate["aliases"] = {_crate_id(alias_target.crate): alias_name for alias_target, alias_name in info.aliases.items()}
265268
crate["cfg"] = info.cfgs
266-
crate["target"] = find_toolchain(ctx).target_flag_value
269+
toolchain = find_toolchain(ctx)
270+
crate["target"] = (_EXEC_ROOT_TEMPLATE + toolchain.target_json.path) if toolchain.target_json else toolchain.target_flag_value
267271
if info.proc_macro_dylib_path != None:
268272
crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib_path
269273
return crate
@@ -315,6 +319,8 @@ def _rust_analyzer_detect_sysroot_impl(ctx):
315319
sysroot_src = rustc_srcs.label.package + "/library"
316320
if rustc_srcs.label.workspace_root:
317321
sysroot_src = _OUTPUT_BASE_TEMPLATE + rustc_srcs.label.workspace_root + "/" + sysroot_src
322+
else:
323+
sysroot_src = _WORKSPACE_TEMPLATE + sysroot_src
318324

319325
rustc = rust_analyzer_toolchain.rustc
320326
sysroot_dir, _, bin_dir = rustc.dirname.rpartition("/")
@@ -323,10 +329,7 @@ def _rust_analyzer_detect_sysroot_impl(ctx):
323329
rustc.path,
324330
))
325331

326-
sysroot = "{}/{}".format(
327-
_OUTPUT_BASE_TEMPLATE,
328-
sysroot_dir,
329-
)
332+
sysroot = _OUTPUT_BASE_TEMPLATE + sysroot_dir
330333

331334
toolchain_info = {
332335
"sysroot": sysroot,

test/rust_analyzer/generated_srcs_test/BUILD.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ rust_test(
3434
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
3535
# that target to execute this test.
3636
tags = ["manual"],
37+
deps = [
38+
"@rules_rust//test/3rdparty/crates:serde",
39+
"@rules_rust//test/3rdparty/crates:serde_json",
40+
],
3741
)
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,57 @@
11
#[cfg(test)]
22
mod tests {
3+
use serde::Deserialize;
34
use std::env;
45
use std::path::PathBuf;
56

7+
#[derive(Deserialize)]
8+
struct Project {
9+
sysroot_src: String,
10+
crates: Vec<Crate>,
11+
}
12+
13+
#[derive(Deserialize)]
14+
struct Crate {
15+
display_name: String,
16+
root_module: String,
17+
source: Option<Source>,
18+
}
19+
20+
#[derive(Deserialize)]
21+
struct Source {
22+
include_dirs: Vec<String>,
23+
}
24+
625
#[test]
7-
fn test_deps_of_crate_and_its_test_are_merged() {
26+
fn test_generated_srcs() {
827
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());
9-
1028
let content = std::fs::read_to_string(&rust_project_path)
1129
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));
30+
println!("{}", content);
31+
let project: Project =
32+
serde_json::from_str(&content).expect("Failed to deserialize project JSON");
1233

13-
let output_base = content
14-
.lines()
15-
.find(|text| text.trim_start().starts_with("\"sysroot_src\":"))
16-
.map(|text| {
17-
let mut split = text.splitn(2, "\"sysroot_src\": ");
18-
let mut with_hash = split.nth(1).unwrap().trim().splitn(2, "/external/");
19-
let mut output = with_hash.next().unwrap().rsplitn(2, '/');
20-
output.nth(1).unwrap()
21-
})
22-
.expect("Failed to find sysroot entry.");
34+
// /tmp/_bazel/12345678/external/tools/rustlib/library => /tmp/_bazel
35+
let output_base = project
36+
.sysroot_src
37+
.rsplitn(2, "/external/")
38+
.last()
39+
.unwrap()
40+
.rsplitn(2, '/')
41+
.last()
42+
.unwrap();
43+
println!("output_base: {output_base}");
2344

24-
let expected = r#"{
25-
"display_name": "generated_srcs",
26-
"root_module": "lib.rs",
27-
"edition": "2021",
28-
"deps": [],
29-
"is_workspace_member": true,
30-
"source": {
31-
"include_dirs": [
32-
"#
33-
.to_owned()
34-
+ output_base;
45+
let gen = project
46+
.crates
47+
.iter()
48+
.find(|c| &c.display_name == "generated_srcs")
49+
.unwrap();
50+
assert!(gen.root_module.starts_with("/"));
51+
assert!(gen.root_module.ends_with("/lib.rs"));
3552

36-
println!("{}", content);
37-
assert!(
38-
content.contains(&expected),
39-
"expected rust-project.json to contain the following block:\n{}",
40-
expected
41-
);
53+
let include_dirs = &gen.source.as_ref().unwrap().include_dirs;
54+
assert!(include_dirs.len() == 1);
55+
assert!(include_dirs[0].starts_with(output_base));
4256
}
4357
}

test/rust_analyzer/merging_crates_test/BUILD.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ rust_test(
3636
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
3737
# that target to execute this test.
3838
tags = ["manual"],
39+
deps = [
40+
"@rules_rust//test/3rdparty/crates:serde",
41+
"@rules_rust//test/3rdparty/crates:serde_json",
42+
],
3943
)
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
#[cfg(test)]
22
mod tests {
3+
use serde::Deserialize;
34
use std::env;
45
use std::path::PathBuf;
56

7+
#[derive(Deserialize)]
8+
struct Project {
9+
crates: Vec<Crate>,
10+
}
11+
12+
#[derive(Deserialize)]
13+
struct Crate {
14+
display_name: String,
15+
deps: Vec<Dep>,
16+
}
17+
18+
#[derive(Deserialize)]
19+
struct Dep {
20+
name: String,
21+
}
22+
623
#[test]
724
fn test_deps_of_crate_and_its_test_are_merged() {
825
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());
9-
1026
let content = std::fs::read_to_string(&rust_project_path)
1127
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));
12-
13-
let expected = r#"{
14-
"display_name": "mylib",
15-
"root_module": "mylib.rs",
16-
"edition": "2018",
17-
"deps": [
18-
{
19-
"crate": 0,
20-
"name": "extra_test_dep"
21-
},
22-
{
23-
"crate": 1,
24-
"name": "lib_dep"
25-
}
26-
],"#;
27-
2828
println!("{}", content);
29-
assert!(
30-
content.contains(expected),
31-
"expected rust-project.json to contain both lib_dep and extra_test_dep in deps of mylib.rs.");
29+
let project: Project =
30+
serde_json::from_str(&content).expect("Failed to deserialize project JSON");
31+
32+
let lib = project
33+
.crates
34+
.iter()
35+
.find(|c| &c.display_name == "mylib")
36+
.unwrap();
37+
let mut deps = lib.deps.iter().map(|d| &d.name).collect::<Vec<_>>();
38+
deps.sort();
39+
assert!(deps == vec!["extra_test_dep", "lib_dep"]);
3240
}
3341
}

test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ rust_test(
3535
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
3636
# that target to execute this test.
3737
tags = ["manual"],
38+
deps = [
39+
"@rules_rust//test/3rdparty/crates:serde",
40+
"@rules_rust//test/3rdparty/crates:serde_json",
41+
],
3842
)
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
11
#[cfg(test)]
22
mod tests {
3+
use serde::Deserialize;
34
use std::env;
45
use std::path::PathBuf;
56

7+
#[derive(Deserialize)]
8+
struct Project {
9+
crates: Vec<Crate>,
10+
}
11+
12+
#[derive(Deserialize)]
13+
struct Crate {
14+
display_name: String,
15+
root_module: String,
16+
}
17+
618
#[test]
7-
fn test_deps_of_crate_and_its_test_are_merged() {
19+
fn test_static_and_shared_lib() {
820
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());
9-
1021
let content = std::fs::read_to_string(&rust_project_path)
1122
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));
12-
1323
println!("{}", content);
24+
let project: Project =
25+
serde_json::from_str(&content).expect("Failed to deserialize project JSON");
1426

15-
let expected_cdylib = r#"{
16-
"display_name": "greeter_cdylib",
17-
"root_module": "shared_lib.rs","#;
18-
assert!(
19-
content.contains(expected_cdylib),
20-
"expected rust-project.json to contain a rust_shared_library target."
21-
);
27+
let cdylib = project
28+
.crates
29+
.iter()
30+
.find(|c| &c.display_name == "greeter_cdylib")
31+
.unwrap();
32+
assert!(cdylib.root_module.ends_with("/shared_lib.rs"));
2233

23-
let expected_staticlib = r#"{
24-
"display_name": "greeter_staticlib",
25-
"root_module": "static_lib.rs","#;
26-
assert!(
27-
content.contains(expected_staticlib),
28-
"expected rust-project.json to contain a rust_static_library target."
29-
);
34+
let staticlib = project
35+
.crates
36+
.iter()
37+
.find(|c| &c.display_name == "greeter_staticlib")
38+
.unwrap();
39+
assert!(staticlib.root_module.ends_with("/static_lib.rs"));
3040
}
3141
}

tools/rust_analyzer/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub fn write_rust_project(
7474

7575
rust_project::write_rust_project(
7676
rust_project_path.as_ref(),
77+
workspace.as_ref(),
7778
execution_root.as_ref(),
7879
output_base.as_ref(),
7980
&rust_project,

tools/rust_analyzer/rust_project.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,15 @@ fn detect_cycle<'a>(
243243

244244
pub fn write_rust_project(
245245
rust_project_path: &Path,
246+
workspace: &Path,
246247
execution_root: &Path,
247248
output_base: &Path,
248249
rust_project: &RustProject,
249250
) -> anyhow::Result<()> {
251+
let workspace = workspace
252+
.to_str()
253+
.ok_or_else(|| anyhow!("workspace is not valid UTF-8"))?;
254+
250255
let execution_root = execution_root
251256
.to_str()
252257
.ok_or_else(|| anyhow!("execution_root is not valid UTF-8"))?;
@@ -272,7 +277,8 @@ pub fn write_rust_project(
272277
let rust_project_content = serde_json::to_string_pretty(rust_project)?
273278
.replace("${pwd}", execution_root)
274279
.replace("__EXEC_ROOT__", execution_root)
275-
.replace("__OUTPUT_BASE__", output_base);
280+
.replace("__OUTPUT_BASE__", output_base)
281+
.replace("__WORKSPACE__", workspace);
276282

277283
// Write the new rust-project.json file.
278284
std::fs::write(rust_project_path, rust_project_content)?;

0 commit comments

Comments
 (0)