diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl index 813f2a2d73..bc12306a82 100644 --- a/rust/private/rust_analyzer.bzl +++ b/rust/private/rust_analyzer.bzl @@ -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__/" @@ -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": []} @@ -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: @@ -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 @@ -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("/") @@ -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, diff --git a/test/rust_analyzer/generated_srcs_test/BUILD.bazel b/test/rust_analyzer/generated_srcs_test/BUILD.bazel index 96045899d8..884169ab31 100644 --- a/test/rust_analyzer/generated_srcs_test/BUILD.bazel +++ b/test/rust_analyzer/generated_srcs_test/BUILD.bazel @@ -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", + ], ) diff --git a/test/rust_analyzer/generated_srcs_test/rust_project_json_test.rs b/test/rust_analyzer/generated_srcs_test/rust_project_json_test.rs index 8ba6bb4cd7..81fe369554 100644 --- a/test/rust_analyzer/generated_srcs_test/rust_project_json_test.rs +++ b/test/rust_analyzer/generated_srcs_test/rust_project_json_test.rs @@ -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, + } + + #[derive(Deserialize)] + struct Crate { + display_name: String, + root_module: String, + source: Option, + } + + #[derive(Deserialize)] + struct Source { + include_dirs: Vec, + } + #[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)); } } diff --git a/test/rust_analyzer/merging_crates_test/BUILD.bazel b/test/rust_analyzer/merging_crates_test/BUILD.bazel index 5023e0a336..1e2a8caab1 100644 --- a/test/rust_analyzer/merging_crates_test/BUILD.bazel +++ b/test/rust_analyzer/merging_crates_test/BUILD.bazel @@ -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", + ], ) diff --git a/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs b/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs index 6e476b9651..16d42d43a6 100644 --- a/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs +++ b/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs @@ -1,33 +1,41 @@ #[cfg(test)] mod tests { + use serde::Deserialize; use std::env; use std::path::PathBuf; + #[derive(Deserialize)] + struct Project { + crates: Vec, + } + + #[derive(Deserialize)] + struct Crate { + display_name: String, + deps: Vec, + } + + #[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::>(); + deps.sort(); + assert!(deps == vec!["extra_test_dep", "lib_dep"]); } } diff --git a/test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel b/test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel index 7efd036406..da4a7ebd32 100644 --- a/test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel +++ b/test/rust_analyzer/static_and_shared_lib_test/BUILD.bazel @@ -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", + ], ) diff --git a/test/rust_analyzer/static_and_shared_lib_test/rust_project_json_test.rs b/test/rust_analyzer/static_and_shared_lib_test/rust_project_json_test.rs index 6bff4fcd36..88b0a3da3d 100644 --- a/test/rust_analyzer/static_and_shared_lib_test/rust_project_json_test.rs +++ b/test/rust_analyzer/static_and_shared_lib_test/rust_project_json_test.rs @@ -1,31 +1,41 @@ #[cfg(test)] mod tests { + use serde::Deserialize; use std::env; use std::path::PathBuf; + #[derive(Deserialize)] + struct Project { + crates: Vec, + } + + #[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")); } } diff --git a/tools/rust_analyzer/lib.rs b/tools/rust_analyzer/lib.rs index 55f1946071..7a6eb28d64 100644 --- a/tools/rust_analyzer/lib.rs +++ b/tools/rust_analyzer/lib.rs @@ -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, diff --git a/tools/rust_analyzer/rust_project.rs b/tools/rust_analyzer/rust_project.rs index 248a196fd6..f694267379 100644 --- a/tools/rust_analyzer/rust_project.rs +++ b/tools/rust_analyzer/rust_project.rs @@ -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"))?; @@ -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)?;