Skip to content

rust_analyzer: make all paths in rust-project.json absolute #3033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions rust/private/rust_analyzer.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ rust_analyzer_aspect = aspect(
doc = "Annotates rust rules with RustAnalyzerInfo later used to build a rust-project.json",
)

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

Expand Down Expand Up @@ -222,7 +225,7 @@ def _create_single_crate(ctx, attrs, info):
# TODO: Some folks may want to override this for vendored dependencies.
is_external = info.crate.root.path.startswith("external/")
is_generated = not info.crate.root.is_source
path_prefix = _EXEC_ROOT_TEMPLATE if is_external or is_generated else ""
path_prefix = _EXEC_ROOT_TEMPLATE if is_external or is_generated else _WORKSPACE_TEMPLATE
crate["is_workspace_member"] = not is_external
crate["root_module"] = path_prefix + info.crate.root.path
crate["source"] = {"exclude_dirs": [], "include_dirs": []}
Expand All @@ -231,7 +234,7 @@ def _create_single_crate(ctx, attrs, info):
srcs = getattr(ctx.rule.files, "srcs", [])
src_map = {src.short_path: src for src in srcs if src.is_source}
if info.crate.root.short_path in src_map:
crate["root_module"] = src_map[info.crate.root.short_path].path
crate["root_module"] = _WORKSPACE_TEMPLATE + src_map[info.crate.root.short_path].path
crate["source"]["include_dirs"].append(path_prefix + info.crate.root.dirname)

if info.build_info != None and info.build_info.out_dir != None:
Expand Down Expand Up @@ -263,7 +266,8 @@ def _create_single_crate(ctx, attrs, info):
crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
crate["aliases"] = {_crate_id(alias_target.crate): alias_name for alias_target, alias_name in info.aliases.items()}
crate["cfg"] = info.cfgs
crate["target"] = find_toolchain(ctx).target_flag_value
toolchain = find_toolchain(ctx)
crate["target"] = (_EXEC_ROOT_TEMPLATE + toolchain.target_json.path) if toolchain.target_json else toolchain.target_flag_value
if info.proc_macro_dylib_path != None:
crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib_path
return crate
Expand Down Expand Up @@ -315,6 +319,8 @@ def _rust_analyzer_detect_sysroot_impl(ctx):
sysroot_src = rustc_srcs.label.package + "/library"
if rustc_srcs.label.workspace_root:
sysroot_src = _OUTPUT_BASE_TEMPLATE + rustc_srcs.label.workspace_root + "/" + sysroot_src
else:
sysroot_src = _WORKSPACE_TEMPLATE + sysroot_src

rustc = rust_analyzer_toolchain.rustc
sysroot_dir, _, bin_dir = rustc.dirname.rpartition("/")
Expand All @@ -323,10 +329,7 @@ def _rust_analyzer_detect_sysroot_impl(ctx):
rustc.path,
))

sysroot = "{}/{}".format(
_OUTPUT_BASE_TEMPLATE,
sysroot_dir,
)
sysroot = _OUTPUT_BASE_TEMPLATE + sysroot_dir

toolchain_info = {
"sysroot": sysroot,
Expand Down
4 changes: 4 additions & 0 deletions test/rust_analyzer/generated_srcs_test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ rust_test(
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
# that target to execute this test.
tags = ["manual"],
deps = [
"@rules_rust//test/3rdparty/crates:serde",
"@rules_rust//test/3rdparty/crates:serde_json",
],
)
72 changes: 43 additions & 29 deletions test/rust_analyzer/generated_srcs_test/rust_project_json_test.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
#[cfg(test)]
mod tests {
use serde::Deserialize;
use std::env;
use std::path::PathBuf;

#[derive(Deserialize)]
struct Project {
sysroot_src: String,
crates: Vec<Crate>,
}

#[derive(Deserialize)]
struct Crate {
display_name: String,
root_module: String,
source: Option<Source>,
}

#[derive(Deserialize)]
struct Source {
include_dirs: Vec<String>,
}

#[test]
fn test_deps_of_crate_and_its_test_are_merged() {
fn test_generated_srcs() {
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());

let content = std::fs::read_to_string(&rust_project_path)
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));
println!("{}", content);
let project: Project =
serde_json::from_str(&content).expect("Failed to deserialize project JSON");

let output_base = content
.lines()
.find(|text| text.trim_start().starts_with("\"sysroot_src\":"))
.map(|text| {
let mut split = text.splitn(2, "\"sysroot_src\": ");
let mut with_hash = split.nth(1).unwrap().trim().splitn(2, "/external/");
let mut output = with_hash.next().unwrap().rsplitn(2, '/');
output.nth(1).unwrap()
})
.expect("Failed to find sysroot entry.");
// /tmp/_bazel/12345678/external/tools/rustlib/library => /tmp/_bazel
let output_base = project
.sysroot_src
.rsplitn(2, "/external/")
.last()
.unwrap()
.rsplitn(2, '/')
.last()
.unwrap();
println!("output_base: {output_base}");

let expected = r#"{
"display_name": "generated_srcs",
"root_module": "lib.rs",
"edition": "2021",
"deps": [],
"is_workspace_member": true,
"source": {
"include_dirs": [
"#
.to_owned()
+ output_base;
let gen = project
.crates
.iter()
.find(|c| &c.display_name == "generated_srcs")
.unwrap();
assert!(gen.root_module.starts_with("/"));
assert!(gen.root_module.ends_with("/lib.rs"));

println!("{}", content);
assert!(
content.contains(&expected),
"expected rust-project.json to contain the following block:\n{}",
expected
);
let include_dirs = &gen.source.as_ref().unwrap().include_dirs;
assert!(include_dirs.len() == 1);
assert!(include_dirs[0].starts_with(output_base));
}
}
4 changes: 4 additions & 0 deletions test/rust_analyzer/merging_crates_test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ rust_test(
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
# that target to execute this test.
tags = ["manual"],
deps = [
"@rules_rust//test/3rdparty/crates:serde",
"@rules_rust//test/3rdparty/crates:serde_json",
],
)
48 changes: 28 additions & 20 deletions test/rust_analyzer/merging_crates_test/rust_project_json_test.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
#[cfg(test)]
mod tests {
use serde::Deserialize;
use std::env;
use std::path::PathBuf;

#[derive(Deserialize)]
struct Project {
crates: Vec<Crate>,
}

#[derive(Deserialize)]
struct Crate {
display_name: String,
deps: Vec<Dep>,
}

#[derive(Deserialize)]
struct Dep {
name: String,
}

#[test]
fn test_deps_of_crate_and_its_test_are_merged() {
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());

let content = std::fs::read_to_string(&rust_project_path)
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));

let expected = r#"{
"display_name": "mylib",
"root_module": "mylib.rs",
"edition": "2018",
"deps": [
{
"crate": 0,
"name": "extra_test_dep"
},
{
"crate": 1,
"name": "lib_dep"
}
],"#;

println!("{}", content);
assert!(
content.contains(expected),
"expected rust-project.json to contain both lib_dep and extra_test_dep in deps of mylib.rs.");
let project: Project =
serde_json::from_str(&content).expect("Failed to deserialize project JSON");

let lib = project
.crates
.iter()
.find(|c| &c.display_name == "mylib")
.unwrap();
let mut deps = lib.deps.iter().map(|d| &d.name).collect::<Vec<_>>();
deps.sort();
assert!(deps == vec!["extra_test_dep", "lib_dep"]);
}
}
4 changes: 4 additions & 0 deletions test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ rust_test(
# contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run
# that target to execute this test.
tags = ["manual"],
deps = [
"@rules_rust//test/3rdparty/crates:serde",
"@rules_rust//test/3rdparty/crates:serde_json",
],
)
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
#[cfg(test)]
mod tests {
use serde::Deserialize;
use std::env;
use std::path::PathBuf;

#[derive(Deserialize)]
struct Project {
crates: Vec<Crate>,
}

#[derive(Deserialize)]
struct Crate {
display_name: String,
root_module: String,
}

#[test]
fn test_deps_of_crate_and_its_test_are_merged() {
fn test_static_and_shared_lib() {
let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap());

let content = std::fs::read_to_string(&rust_project_path)
.unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path));

println!("{}", content);
let project: Project =
serde_json::from_str(&content).expect("Failed to deserialize project JSON");

let expected_cdylib = r#"{
"display_name": "greeter_cdylib",
"root_module": "shared_lib.rs","#;
assert!(
content.contains(expected_cdylib),
"expected rust-project.json to contain a rust_shared_library target."
);
let cdylib = project
.crates
.iter()
.find(|c| &c.display_name == "greeter_cdylib")
.unwrap();
assert!(cdylib.root_module.ends_with("/shared_lib.rs"));

let expected_staticlib = r#"{
"display_name": "greeter_staticlib",
"root_module": "static_lib.rs","#;
assert!(
content.contains(expected_staticlib),
"expected rust-project.json to contain a rust_static_library target."
);
let staticlib = project
.crates
.iter()
.find(|c| &c.display_name == "greeter_staticlib")
.unwrap();
assert!(staticlib.root_module.ends_with("/static_lib.rs"));
}
}
1 change: 1 addition & 0 deletions tools/rust_analyzer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub fn write_rust_project(

rust_project::write_rust_project(
rust_project_path.as_ref(),
workspace.as_ref(),
execution_root.as_ref(),
output_base.as_ref(),
&rust_project,
Expand Down
8 changes: 7 additions & 1 deletion tools/rust_analyzer/rust_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,15 @@ fn detect_cycle<'a>(

pub fn write_rust_project(
rust_project_path: &Path,
workspace: &Path,
execution_root: &Path,
output_base: &Path,
rust_project: &RustProject,
) -> anyhow::Result<()> {
let workspace = workspace
.to_str()
.ok_or_else(|| anyhow!("workspace is not valid UTF-8"))?;

let execution_root = execution_root
.to_str()
.ok_or_else(|| anyhow!("execution_root is not valid UTF-8"))?;
Expand All @@ -272,7 +277,8 @@ pub fn write_rust_project(
let rust_project_content = serde_json::to_string_pretty(rust_project)?
.replace("${pwd}", execution_root)
.replace("__EXEC_ROOT__", execution_root)
.replace("__OUTPUT_BASE__", output_base);
.replace("__OUTPUT_BASE__", output_base)
.replace("__WORKSPACE__", workspace);

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