Bzlmod-aware runfiles library (#2566)

This implements the repo_mapping capabilities and provides a new
runfiles API. The new API can be accessed explicitly as
`runfiles::Runfiles::rlocation_from` or with the `runfiles::rlocation!`
macro, which adds compile-time support for correctly embedding the
external repo. This is a purely new API, existing usage continues to
work, although we mark it deprecated because it's not fully correct. We
can remove it at some point in the future.

This PR also transitions in-repo examples/tests to using it, in case
anyone copies them.

---------

Co-authored-by: scentini <rosica@google.com>
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index ccea830..932bf8c 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -586,6 +586,12 @@
     test_flags:
       - "--@rules_rust//rust/toolchain/channel=nightly"
       - "--@rules_rust//:no_std=alloc"
+  bzlmod_repo_mapping_runfiles:
+    name: bzlmod repo mapping test
+    platform: ubuntu2004
+    working_directory: test/bzlmod_repo_mapping/module_a
+    test_targets:
+      - "//..."
   android_examples_ubuntu2004:
     name: Android Examples
     platform: ubuntu2004
diff --git a/.bazelignore b/.bazelignore
index 058f980..0b25ffe 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -2,5 +2,6 @@
 docs
 examples
 crate_universe/private/bootstrap
+test/bzlmod_repo_mapping
 test/cc_common_link
 test/no_std
diff --git a/crate_universe/src/api/lockfile.rs b/crate_universe/src/api/lockfile.rs
index 0c7a1df..36261aa 100644
--- a/crate_universe/src/api/lockfile.rs
+++ b/crate_universe/src/api/lockfile.rs
@@ -143,8 +143,8 @@
         };
 
         let runfiles = runfiles::Runfiles::create().unwrap();
-        let path = runfiles
-            .rlocation("rules_rust/crate_universe/test_data/cargo_bazel_lockfile/multi_package-cargo-bazel-lock.json");
+        let path = runfiles::rlocation!(
+            runfiles, "rules_rust/crate_universe/test_data/cargo_bazel_lockfile/multi_package-cargo-bazel-lock.json");
 
         let parsed = parse(&path).unwrap();
         assert_eq!(parsed.workspace_members(), want_workspace_member_names);
diff --git a/crate_universe/src/config.rs b/crate_universe/src/config.rs
index 0e0ab53..9b8354e 100644
--- a/crate_universe/src/config.rs
+++ b/crate_universe/src/config.rs
@@ -927,8 +927,10 @@
     #[test]
     fn deserialize_config() {
         let runfiles = runfiles::Runfiles::create().unwrap();
-        let path = runfiles
-            .rlocation("rules_rust/crate_universe/test_data/serialized_configs/config.json");
+        let path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/serialized_configs/config.json"
+        );
 
         let content = std::fs::read_to_string(path).unwrap();
 
diff --git a/crate_universe/src/splicing.rs b/crate_universe/src/splicing.rs
index 5ff21f9..2cd5eb1 100644
--- a/crate_universe/src/splicing.rs
+++ b/crate_universe/src/splicing.rs
@@ -483,8 +483,9 @@
     #[test]
     fn deserialize_splicing_manifest() {
         let runfiles = runfiles::Runfiles::create().unwrap();
-        let path = runfiles.rlocation(
-            "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json",
+        let path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json"
         );
 
         let content = std::fs::read_to_string(path).unwrap();
@@ -566,8 +567,9 @@
     #[test]
     fn splicing_manifest_resolve() {
         let runfiles = runfiles::Runfiles::create().unwrap();
-        let path = runfiles.rlocation(
-            "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json",
+        let path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json"
         );
 
         let content = std::fs::read_to_string(path).unwrap();
@@ -610,14 +612,18 @@
     #[test]
     fn splicing_metadata_workspace_path() {
         let runfiles = runfiles::Runfiles::create().unwrap();
-        let workspace_manifest_path = runfiles
-            .rlocation("rules_rust/crate_universe/test_data/metadata/workspace_path/Cargo.toml");
-        let workspace_path = workspace_manifest_path.parent().unwrap().to_path_buf();
-        let child_a_manifest_path = runfiles.rlocation(
-            "rules_rust/crate_universe/test_data/metadata/workspace_path/child_a/Cargo.toml",
+        let workspace_manifest_path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/metadata/workspace_path/Cargo.toml"
         );
-        let child_b_manifest_path = runfiles.rlocation(
-            "rules_rust/crate_universe/test_data/metadata/workspace_path/child_b/Cargo.toml",
+        let workspace_path = workspace_manifest_path.parent().unwrap().to_path_buf();
+        let child_a_manifest_path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/metadata/workspace_path/child_a/Cargo.toml"
+        );
+        let child_b_manifest_path = runfiles::rlocation!(
+            runfiles,
+            "rules_rust/crate_universe/test_data/metadata/workspace_path/child_b/Cargo.toml"
         );
         let manifest = SplicingManifest {
             direct_packages: BTreeMap::new(),
diff --git a/crate_universe/src/splicing/crate_index_lookup.rs b/crate_universe/src/splicing/crate_index_lookup.rs
index 2cc5134..72f404b 100644
--- a/crate_universe/src/splicing/crate_index_lookup.rs
+++ b/crate_universe/src/splicing/crate_index_lookup.rs
@@ -66,8 +66,9 @@
         {
             let _e = EnvVarResetter::set(
                 "CARGO_HOME",
-                runfiles.rlocation(
-                    "rules_rust/crate_universe/test_data/crate_indexes/lazy_static/cargo_home",
+                runfiles::rlocation!(
+                    runfiles,
+                    "rules_rust/crate_universe/test_data/crate_indexes/lazy_static/cargo_home"
                 ),
             );
 
@@ -95,7 +96,8 @@
             );
         }
         {
-            let _e = EnvVarResetter::set("CARGO_HOME", runfiles.rlocation("rules_rust/crate_universe/test_data/crate_indexes/rewritten_lazy_static/cargo_home"));
+            let _e = EnvVarResetter::set("CARGO_HOME", 
+                runfiles::rlocation!(runfiles, "rules_rust/crate_universe/test_data/crate_indexes/rewritten_lazy_static/cargo_home"));
 
             let index = CrateIndexLookup::Http(
                 crates_index::SparseIndex::from_url("sparse+https://index.crates.io/").unwrap(),
diff --git a/crate_universe/src/splicing/splicer.rs b/crate_universe/src/splicing/splicer.rs
index 0d75dd1..7cb5949 100644
--- a/crate_universe/src/splicing/splicer.rs
+++ b/crate_universe/src/splicing/splicer.rs
@@ -769,9 +769,9 @@
     /// Get cargo and rustc binaries the Bazel way
     #[cfg(not(feature = "cargo"))]
     fn get_cargo_and_rustc_paths() -> (PathBuf, PathBuf) {
-        let runfiles = runfiles::Runfiles::create().unwrap();
-        let cargo_path = runfiles.rlocation(concat!("rules_rust/", env!("CARGO")));
-        let rustc_path = runfiles.rlocation(concat!("rules_rust/", env!("RUSTC")));
+        let r = runfiles::Runfiles::create().unwrap();
+        let cargo_path = runfiles::rlocation!(r, concat!("rules_rust/", env!("CARGO")));
+        let rustc_path = runfiles::rlocation!(r, concat!("rules_rust/", env!("RUSTC")));
 
         (cargo_path, rustc_path)
     }
diff --git a/crate_universe/tests/cargo_integration_test.rs b/crate_universe/tests/cargo_integration_test.rs
index 956f451..8cd3516 100644
--- a/crate_universe/tests/cargo_integration_test.rs
+++ b/crate_universe/tests/cargo_integration_test.rs
@@ -54,7 +54,7 @@
     /*
     let manifest_path = scratch.path().join("Cargo.toml");
     fs::copy(
-        runfiles.rlocation(manifest),
+        runfiles::rlocation!(runfiles, manifest),
         manifest_path,
     )
     .unwrap();
@@ -94,7 +94,7 @@
 
     splice(SpliceOptions {
         splicing_manifest,
-        cargo_lockfile: Some(runfiles.rlocation(lockfile)),
+        cargo_lockfile: Some(runfiles::rlocation!(runfiles, lockfile)),
         repin: None,
         workspace_dir: None,
         output_dir: scratch.path().join("out"),
@@ -127,16 +127,16 @@
         return;
     }
 
-    let runfiles = runfiles::Runfiles::create().unwrap();
+    let r = runfiles::Runfiles::create().unwrap();
     let metadata = run(
         "target_feature_test",
         HashMap::from([(
-            runfiles
-                .rlocation(
-                    "rules_rust/crate_universe/test_data/metadata/target_features/Cargo.toml",
-                )
-                .to_string_lossy()
-                .to_string(),
+            runfiles::rlocation!(
+                r,
+                "rules_rust/crate_universe/test_data/metadata/target_features/Cargo.toml"
+            )
+            .to_string_lossy()
+            .to_string(),
             "//:test_input".to_string(),
         )]),
         "rules_rust/crate_universe/test_data/metadata/target_features/Cargo.lock",
@@ -180,16 +180,16 @@
         return;
     }
 
-    let runfiles = runfiles::Runfiles::create().unwrap();
+    let r = runfiles::Runfiles::create().unwrap();
     let metadata = run(
         "target_cfg_features_test",
         HashMap::from([(
-            runfiles
-                .rlocation(
-                    "rules_rust/crate_universe/test_data/metadata/target_cfg_features/Cargo.toml",
-                )
-                .to_string_lossy()
-                .to_string(),
+            runfiles::rlocation!(
+                r,
+                "rules_rust/crate_universe/test_data/metadata/target_cfg_features/Cargo.toml"
+            )
+            .to_string_lossy()
+            .to_string(),
             "//:test_input".to_string(),
         )]),
         "rules_rust/crate_universe/test_data/metadata/target_cfg_features/Cargo.lock",
@@ -235,24 +235,26 @@
         return;
     }
 
-    let runfiles = runfiles::Runfiles::create().unwrap();
+    let r = runfiles::Runfiles::create().unwrap();
     let metadata = run(
         "workspace_test",
         HashMap::from([
             (
-                runfiles
-                    .rlocation("rules_rust/crate_universe/test_data/metadata/workspace/Cargo.toml")
-                    .to_string_lossy()
-                    .to_string(),
+                runfiles::rlocation!(
+                    r,
+                    "rules_rust/crate_universe/test_data/metadata/workspace/Cargo.toml"
+                )
+                .to_string_lossy()
+                .to_string(),
                 "//:test_input".to_string(),
             ),
             (
-                runfiles
-                    .rlocation(
-                        "rules_rust/crate_universe/test_data/metadata/workspace/child/Cargo.toml",
-                    )
-                    .to_string_lossy()
-                    .to_string(),
+                runfiles::rlocation!(
+                    r,
+                    "rules_rust/crate_universe/test_data/metadata/workspace/child/Cargo.toml"
+                )
+                .to_string_lossy()
+                .to_string(),
                 "//crate_universe:test_data/metadata/workspace/child/Cargo.toml".to_string(),
             ),
         ]),
@@ -274,18 +276,18 @@
         return;
     }
 
-    let runfiles = runfiles::Runfiles::create().unwrap();
+    let r = runfiles::Runfiles::create().unwrap();
     let metadata = run(
         "crate_combined_features",
-        HashMap::from([
-            (
-                runfiles
-                    .rlocation("rules_rust/crate_universe/test_data/metadata/crate_combined_features/Cargo.toml")
-                    .to_string_lossy()
-                    .to_string(),
-                "//:test_input".to_string(),
+        HashMap::from([(
+            runfiles::rlocation!(
+                r,
+                "rules_rust/crate_universe/test_data/metadata/crate_combined_features/Cargo.toml"
             )
-        ]),
+            .to_string_lossy()
+            .to_string(),
+            "//:test_input".to_string(),
+        )]),
         "rules_rust/crate_universe/test_data/metadata/crate_combined_features/Cargo.lock",
     );
 
diff --git a/examples/hello_runfiles/hello_runfiles.rs b/examples/hello_runfiles/hello_runfiles.rs
index b8a5e45..93604a3 100644
--- a/examples/hello_runfiles/hello_runfiles.rs
+++ b/examples/hello_runfiles/hello_runfiles.rs
@@ -6,7 +6,11 @@
 fn main() {
     let r = Runfiles::create().unwrap();
 
-    let mut f = File::open(r.rlocation("examples/hello_runfiles/hello_runfiles.rs")).unwrap();
+    let mut f = File::open(runfiles::rlocation!(
+        r,
+        "examples/hello_runfiles/hello_runfiles.rs"
+    ))
+    .unwrap();
 
     let mut buffer = String::new();
     f.read_to_string(&mut buffer).unwrap();
diff --git a/examples/proto/helloworld/helloworld_test.rs b/examples/proto/helloworld/helloworld_test.rs
index 912f039..827a0a9 100644
--- a/examples/proto/helloworld/helloworld_test.rs
+++ b/examples/proto/helloworld/helloworld_test.rs
@@ -36,12 +36,14 @@
 impl ServerInfo {
     fn new() -> ServerInfo {
         let r = Runfiles::create().unwrap();
-        let mut c =
-            Command::new(r.rlocation("examples/proto/helloworld/greeter_server/greeter_server"))
-                .arg("0")
-                .stdout(Stdio::piped())
-                .spawn()
-                .expect("Unable to start server");
+        let mut c = Command::new(runfiles::rlocation!(
+            r,
+            "examples/proto/helloworld/greeter_server/greeter_server"
+        ))
+        .arg("0")
+        .stdout(Stdio::piped())
+        .spawn()
+        .expect("Unable to start server");
         let mut port: u16 = 0;
         {
             let mut stdout = BufReader::new(c.stdout.as_mut().expect("Failed to open stdout"));
@@ -65,8 +67,10 @@
     fn run_client_impl(&self, arg: Option<String>) -> String {
         let r = Runfiles::create().unwrap();
 
-        let mut cmd0 =
-            Command::new(r.rlocation("examples/proto/helloworld/greeter_client/greeter_client"));
+        let mut cmd0 = Command::new(runfiles::rlocation!(
+            r,
+            "examples/proto/helloworld/greeter_client/greeter_client"
+        ));
         let cmd = cmd0.arg(format!("-p={}", self.port));
 
         let output = if let Some(s) = arg { cmd.arg(s) } else { cmd }
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 6fdc7b9..474d1a6 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1099,6 +1099,9 @@
     if _is_no_std(ctx, toolchain, crate_info):
         rustc_flags.add('--cfg=feature="no_std"')
 
+    # Needed for bzlmod-aware runfiles resolution.
+    env["REPOSITORY_NAME"] = ctx.label.workspace_name
+
     # Create a struct which keeps the arguments separate so each may be tuned or
     # replaced where necessary
     args = struct(
diff --git a/rust/private/rustfmt.bzl b/rust/private/rustfmt.bzl
index 6ce77a6..4b747aa 100644
--- a/rust/private/rustfmt.bzl
+++ b/rust/private/rustfmt.bzl
@@ -13,7 +13,7 @@
     """
 
     # Ignore external targets
-    if target.label.workspace_root.startswith("external"):
+    if target.label.workspace_name:
         return None
 
     # Obviously ignore any targets that don't contain `CrateInfo`
@@ -53,16 +53,19 @@
     return srcs
 
 def _generate_manifest(edition, srcs, ctx):
+    workspace = ctx.label.workspace_name or ctx.workspace_name
+
     # Gather the source paths to non-generated files
-    src_paths = [src.path for src in srcs]
+    content = ctx.actions.args()
+    content.set_param_file_format("multiline")
+    content.add_all(srcs, format_each = workspace + "/%s")
+    content.add(edition)
 
     # Write the rustfmt manifest
     manifest = ctx.actions.declare_file(ctx.label.name + ".rustfmt")
     ctx.actions.write(
         output = manifest,
-        content = "\n".join(src_paths + [
-            edition,
-        ]),
+        content = content,
     )
 
     return manifest
@@ -224,7 +227,7 @@
         ctx.attr._runner[DefaultInfo].default_runfiles,
     )
 
-    path_env_sep = ";" if is_windows else ":"
+    workspace = ctx.label.workspace_name or ctx.workspace_name
 
     return [
         DefaultInfo(
@@ -233,8 +236,8 @@
             executable = runner,
         ),
         testing.TestEnvironment({
-            "RUSTFMT_MANIFESTS": path_env_sep.join([
-                manifest.short_path
+            "RUSTFMT_MANIFESTS": ctx.configuration.host_path_separator.join([
+                workspace + "/" + manifest.short_path
                 for manifest in sorted(manifests.to_list())
             ]),
             "RUST_BACKTRACE": "1",
diff --git a/test/bzl_version/bzl_version_test.rs b/test/bzl_version/bzl_version_test.rs
index fb659a7..b551afc 100644
--- a/test/bzl_version/bzl_version_test.rs
+++ b/test/bzl_version/bzl_version_test.rs
@@ -27,7 +27,7 @@
     let version = std::env::var("VERSION").unwrap();
     let module_bazel_text = {
         let r = Runfiles::create().unwrap();
-        let path = r.rlocation(std::env::var("MODULE_BAZEL").unwrap());
+        let path = runfiles::rlocation!(r, std::env::var("MODULE_BAZEL").unwrap());
         std::fs::read_to_string(path).unwrap()
     };
 
diff --git a/test/bzlmod_repo_mapping/README.md b/test/bzlmod_repo_mapping/README.md
new file mode 100644
index 0000000..2ba7ccb
--- /dev/null
+++ b/test/bzlmod_repo_mapping/README.md
@@ -0,0 +1,4 @@
+Bzlmod allow modules to rename their dependencies and use the aliased names in runfiles lookups.
+This test defines the following dependency tree: A -> B -> C. The test if whether a binary built in A
+that calls code in B that has an `rlocation!` call to lookup a runfile from C is able to correctly use
+B's repo_mapping. This is accomplished by having B use a custom `repo_name` for C.
diff --git a/test/bzlmod_repo_mapping/module_a/.gitignore b/test/bzlmod_repo_mapping/module_a/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_a/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/test/bzlmod_repo_mapping/module_a/BUILD.bazel b/test/bzlmod_repo_mapping/module_a/BUILD.bazel
new file mode 100644
index 0000000..0230fc3
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_a/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+    name = "lib",
+    srcs = ["lib.rs"],
+    deps = ["@module_b//:lib_b"],
+)
+
+rust_test(
+    name = "lib_test",
+    crate = ":lib",
+)
diff --git a/test/bzlmod_repo_mapping/module_a/MODULE.bazel b/test/bzlmod_repo_mapping/module_a/MODULE.bazel
new file mode 100644
index 0000000..2a58707
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_a/MODULE.bazel
@@ -0,0 +1,24 @@
+module(name = "module_a")
+
+local_path_override(
+    module_name = "module_b",
+    path = "../module_b",
+)
+
+local_path_override(
+    module_name = "module_c",
+    path = "../module_c",
+)
+
+bazel_dep(name = "module_b", version = "0.0.0")
+bazel_dep(name = "rules_rust", version = "0.0.0")
+local_path_override(
+    module_name = "rules_rust",
+    path = "../../..",
+)
+
+rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
+rust.toolchain(edition = "2021")
+use_repo(rust, "rust_toolchains")
+
+register_toolchains("@rust_toolchains//:all")
diff --git a/test/bzlmod_repo_mapping/module_a/lib.rs b/test/bzlmod_repo_mapping/module_a/lib.rs
new file mode 100644
index 0000000..77deffa
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_a/lib.rs
@@ -0,0 +1,8 @@
+#[cfg(test)]
+mod test {
+    #[test]
+    fn test_repo_remapping() {
+        let data = lib_b::read_file_from_module_c();
+        assert_eq!(data, "module(name = \"module_c\")\n");
+    }
+}
diff --git a/test/bzlmod_repo_mapping/module_b/BUILD.bazel b/test/bzlmod_repo_mapping/module_b/BUILD.bazel
new file mode 100644
index 0000000..00a9d42
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_b/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+rust_library(
+    name = "lib_b",
+    srcs = ["lib.rs"],
+    data = ["@aliased_c//:MODULE.bazel"],
+    visibility = ["//visibility:public"],
+    deps = ["@rules_rust//tools/runfiles"],
+)
diff --git a/test/bzlmod_repo_mapping/module_b/MODULE.bazel b/test/bzlmod_repo_mapping/module_b/MODULE.bazel
new file mode 100644
index 0000000..74b3d11
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_b/MODULE.bazel
@@ -0,0 +1,10 @@
+module(name = "module_b")
+
+bazel_dep(name = "module_c", version = "0.0.0", repo_name = "aliased_c")
+bazel_dep(name = "rules_rust", version = "0.0.0")
+
+rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
+rust.toolchain(edition = "2021")
+use_repo(rust, "rust_toolchains")
+
+register_toolchains("@rust_toolchains//:all")
diff --git a/test/bzlmod_repo_mapping/module_b/lib.rs b/test/bzlmod_repo_mapping/module_b/lib.rs
new file mode 100644
index 0000000..3a55511
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_b/lib.rs
@@ -0,0 +1,6 @@
+use runfiles::Runfiles;
+
+pub fn read_file_from_module_c() -> String {
+    let r = Runfiles::create().unwrap();
+    std::fs::read_to_string(runfiles::rlocation!(r, "aliased_c/MODULE.bazel")).unwrap()
+}
\ No newline at end of file
diff --git a/test/bzlmod_repo_mapping/module_c/BUILD.bazel b/test/bzlmod_repo_mapping/module_c/BUILD.bazel
new file mode 100644
index 0000000..7627798
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_c/BUILD.bazel
@@ -0,0 +1 @@
+exports_files(["MODULE.bazel"])
diff --git a/test/bzlmod_repo_mapping/module_c/MODULE.bazel b/test/bzlmod_repo_mapping/module_c/MODULE.bazel
new file mode 100644
index 0000000..5c9255c
--- /dev/null
+++ b/test/bzlmod_repo_mapping/module_c/MODULE.bazel
@@ -0,0 +1 @@
+module(name = "module_c")
diff --git a/test/no_std/no_std.rs b/test/no_std/no_std.rs
index 3014eea..80e3568 100644
--- a/test/no_std/no_std.rs
+++ b/test/no_std/no_std.rs
@@ -10,7 +10,6 @@
 #[allow(clippy::panic)]
 fn default_handler(layout: core::alloc::Layout) -> ! {
     panic!("memory allocation of {} bytes failed", layout.size())
-
 }
 
 #[lang = "eh_personality"]
diff --git a/test/process_wrapper/rustc_quit_on_rmeta.rs b/test/process_wrapper/rustc_quit_on_rmeta.rs
index 42dd3f7..9d7b38c 100644
--- a/test/process_wrapper/rustc_quit_on_rmeta.rs
+++ b/test/process_wrapper/rustc_quit_on_rmeta.rs
@@ -15,7 +15,8 @@
         should_succeed: bool,
     ) -> String {
         let r = Runfiles::create().unwrap();
-        let fake_rustc = r.rlocation(
+        let fake_rustc = runfiles::rlocation!(
+            r,
             [
                 "rules_rust",
                 "test",
@@ -27,10 +28,11 @@
                 },
             ]
             .iter()
-            .collect::<PathBuf>(),
+            .collect::<PathBuf>()
         );
 
-        let process_wrapper = r.rlocation(
+        let process_wrapper = runfiles::rlocation!(
+            r,
             [
                 "rules_rust",
                 "util",
@@ -42,7 +44,7 @@
                 },
             ]
             .iter()
-            .collect::<PathBuf>(),
+            .collect::<PathBuf>()
         );
 
         let output = Command::new(process_wrapper)
diff --git a/test/rust/src/greeter.rs b/test/rust/src/greeter.rs
index d97207b..1bc99ed 100644
--- a/test/rust/src/greeter.rs
+++ b/test/rust/src/greeter.rs
@@ -44,10 +44,12 @@
     /// let greeter = Greeter::from_txt_file()?;
     /// ```
     pub fn from_txt_file() -> std::io::Result<Greeter> {
+        let r = runfiles::Runfiles::create()?;
         Ok(Greeter {
-            greeting: std::fs::read_to_string(
-                runfiles::Runfiles::create()?.rlocation("rules_rust/test/rust/greeting.txt"),
-            )?,
+            greeting: std::fs::read_to_string(runfiles::rlocation!(
+                r,
+                "rules_rust/test/rust/greeting.txt"
+            ))?,
         })
     }
 
diff --git a/tools/runfiles/BUILD.bazel b/tools/runfiles/BUILD.bazel
index ee72e13..695fa91 100644
--- a/tools/runfiles/BUILD.bazel
+++ b/tools/runfiles/BUILD.bazel
@@ -4,19 +4,11 @@
     "rust_library",
     "rust_test",
 )
-load("//tools/runfiles/private:runfiles_utils.bzl", "workspace_name")
-
-workspace_name(
-    name = "workspace_name.env",
-)
 
 rust_library(
     name = "runfiles",
     srcs = ["runfiles.rs"],
     edition = "2018",
-    rustc_env_files = [
-        ":workspace_name.env",
-    ],
     visibility = ["//visibility:public"],
 )
 
diff --git a/tools/runfiles/private/BUILD.bazel b/tools/runfiles/private/BUILD.bazel
deleted file mode 100644
index f25a4be..0000000
--- a/tools/runfiles/private/BUILD.bazel
+++ /dev/null
@@ -1,10 +0,0 @@
-package(default_visibility = ["//tools/runfiles:__pkg__"])
-
-filegroup(
-    name = "distro",
-    srcs = glob([
-        "**/*.bzl",
-    ]) + [
-        "BUILD.bazel",
-    ],
-)
diff --git a/tools/runfiles/private/runfiles_utils.bzl b/tools/runfiles/private/runfiles_utils.bzl
deleted file mode 100644
index 85f2334..0000000
--- a/tools/runfiles/private/runfiles_utils.bzl
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Utilities for the `@rules_rust//tools/runfiles` library"""
-
-_RULES_RUST_RUNFILES_WORKSPACE_NAME = "RULES_RUST_RUNFILES_WORKSPACE_NAME"
-
-def _workspace_name_impl(ctx):
-    output = ctx.actions.declare_file(ctx.label.name)
-
-    ctx.actions.write(
-        output = output,
-        content = "{}={}\n".format(
-            _RULES_RUST_RUNFILES_WORKSPACE_NAME,
-            ctx.workspace_name,
-        ),
-    )
-
-    return [DefaultInfo(
-        files = depset([output]),
-    )]
-
-workspace_name = rule(
-    implementation = _workspace_name_impl,
-    doc = """\
-A rule for detecting the current workspace name and writing it to a file for
-for use with `rustc_env_files` attributes on `rust_*` rules. The workspace
-name is exposed by the variable `{}`.""".format(_RULES_RUST_RUNFILES_WORKSPACE_NAME),
-)
diff --git a/tools/runfiles/runfiles.rs b/tools/runfiles/runfiles.rs
index f52d720..a485e2b 100644
--- a/tools/runfiles/runfiles.rs
+++ b/tools/runfiles/runfiles.rs
@@ -19,13 +19,13 @@
 //!     use runfiles::Runfiles;
 //!     ```
 //!
-//! 3.  Create a Runfiles object and use rlocation to look up runfile paths:
+//! 3.  Create a Runfiles object and use `rlocation!`` to look up runfile paths:
 //!     ```ignore -- This doesn't work under rust_doc_test because argv[0] is not what we expect.
 //!
-//!     use runfiles::Runfiles;
+//!     use runfiles::{Runfiles, rlocation};
 //!
 //!     let r = Runfiles::create().unwrap();
-//!     let path = r.rlocation("my_workspace/path/to/my/data.txt");
+//!     let path = rlocation!(r, "my_workspace/path/to/my/data.txt");
 //!
 //!     let f = File::open(path).unwrap();
 //!     // ...
@@ -33,7 +33,6 @@
 
 use std::collections::HashMap;
 use std::env;
-use std::ffi::OsString;
 use std::fs;
 use std::io;
 use std::path::Path;
@@ -41,18 +40,28 @@
 
 const RUNFILES_DIR_ENV_VAR: &str = "RUNFILES_DIR";
 const MANIFEST_FILE_ENV_VAR: &str = "RUNFILES_MANIFEST_FILE";
-const MANIFEST_ONLY_ENV_VAR: &str = "RUNFILES_MANIFEST_ONLY";
 const TEST_SRCDIR_ENV_VAR: &str = "TEST_SRCDIR";
 
+#[macro_export]
+macro_rules! rlocation {
+    ($r:ident, $path:expr) => {
+        $r.rlocation_from($path, env!("REPOSITORY_NAME"))
+    };
+}
+
 #[derive(Debug)]
 enum Mode {
     DirectoryBased(PathBuf),
     ManifestBased(HashMap<PathBuf, PathBuf>),
 }
 
+type RepoMappingKey = (String, String);
+type RepoMapping = HashMap<RepoMappingKey, String>;
+
 #[derive(Debug)]
 pub struct Runfiles {
     mode: Mode,
+    repo_mapping: RepoMapping,
 }
 
 impl Runfiles {
@@ -60,21 +69,22 @@
     /// RUNFILES_MANIFEST_ONLY environment variable is present,
     /// or a directory based Runfiles object otherwise.
     pub fn create() -> io::Result<Self> {
-        if is_manifest_only() {
-            Self::create_manifest_based()
+        let mode = if let Some(manifest_file) = std::env::var_os(MANIFEST_FILE_ENV_VAR) {
+            Self::create_manifest_based(Path::new(&manifest_file))?
         } else {
-            Self::create_directory_based()
-        }
+            Mode::DirectoryBased(find_runfiles_dir()?)
+        };
+
+        let repo_mapping = parse_repo_mapping(raw_rlocation(&mode, "_repo_mapping"))
+            .unwrap_or_else(|_| {
+                println!("No repo mapping found!");
+                RepoMapping::new()
+            });
+
+        Ok(Runfiles { mode, repo_mapping })
     }
 
-    fn create_directory_based() -> io::Result<Self> {
-        Ok(Runfiles {
-            mode: Mode::DirectoryBased(find_runfiles_dir()?),
-        })
-    }
-
-    fn create_manifest_based() -> io::Result<Self> {
-        let manifest_path = find_manifest_path()?;
+    fn create_manifest_based(manifest_path: &Path) -> io::Result<Mode> {
         let manifest_content = std::fs::read_to_string(manifest_path)?;
         let path_mapping = manifest_content
             .lines()
@@ -85,9 +95,7 @@
                 (pair.0.into(), pair.1.into())
             })
             .collect::<HashMap<_, _>>();
-        Ok(Runfiles {
-            mode: Mode::ManifestBased(path_mapping),
-        })
+        Ok(Mode::ManifestBased(path_mapping))
     }
 
     /// Returns the runtime path of a runfile.
@@ -95,36 +103,74 @@
     /// Runfiles are data-dependencies of Bazel-built binaries and tests.
     /// The returned path may not be valid. The caller should check the path's
     /// validity and that the path exists.
+    /// @deprecated - this is not bzlmod-aware. Prefer the `rlocation!` macro or `rlocation_from`
     pub fn rlocation(&self, path: impl AsRef<Path>) -> PathBuf {
         let path = path.as_ref();
         if path.is_absolute() {
             return path.to_path_buf();
         }
-        match &self.mode {
-            Mode::DirectoryBased(runfiles_dir) => runfiles_dir.join(path),
-            Mode::ManifestBased(path_mapping) => path_mapping
-                .get(path)
-                .unwrap_or_else(|| {
-                    panic!("Path {} not found among runfiles.", path.to_string_lossy())
-                })
-                .clone(),
-        }
+        raw_rlocation(&self.mode, path)
     }
 
-    /// Returns the canonical name of the caller's Bazel repository.
-    pub fn current_repository(&self) -> &str {
-        // This value must match the value of `_RULES_RUST_RUNFILES_WORKSPACE_NAME`
-        // which can be found in `@rules_rust//tools/runfiles/private:workspace_name.bzl`
-        env!("RULES_RUST_RUNFILES_WORKSPACE_NAME")
+    /// Returns the runtime path of a runfile.
+    ///
+    /// Runfiles are data-dependencies of Bazel-built binaries and tests.
+    /// The returned path may not be valid. The caller should check the path's
+    /// validity and that the path exists.
+    ///
+    /// Typically this should be used via the `rlocation!` macro to properly set source_repo.
+    pub fn rlocation_from(&self, path: impl AsRef<Path>, source_repo: &str) -> PathBuf {
+        let path = path.as_ref();
+        if path.is_absolute() {
+            return path.to_path_buf();
+        }
+
+        let parts: Vec<&str> = path
+            .to_str()
+            .expect("Should be valid UTF8")
+            .splitn(2, '/')
+            .collect();
+        if parts.len() == 2 {
+            let key: (String, String) = (source_repo.into(), parts[0].into());
+            if let Some(target_repo_directory) = self.repo_mapping.get(&key) {
+                return raw_rlocation(
+                    &self.mode,
+                    target_repo_directory.to_owned() + "/" + parts[1],
+                );
+            };
+        }
+        raw_rlocation(&self.mode, path)
     }
 }
 
+fn raw_rlocation(mode: &Mode, path: impl AsRef<Path>) -> PathBuf {
+    let path = path.as_ref();
+    match mode {
+        Mode::DirectoryBased(runfiles_dir) => runfiles_dir.join(path),
+        Mode::ManifestBased(path_mapping) => path_mapping
+            .get(path)
+            .unwrap_or_else(|| panic!("Path {} not found among runfiles.", path.to_string_lossy()))
+            .clone(),
+    }
+}
+
+fn parse_repo_mapping(path: PathBuf) -> io::Result<RepoMapping> {
+    let mut repo_mapping = RepoMapping::new();
+
+    for line in std::fs::read_to_string(path)?.lines() {
+        let parts: Vec<&str> = line.splitn(3, ',').collect();
+        if parts.len() < 3 {
+            return Err(make_io_error("Malformed repo_mapping file"));
+        }
+        repo_mapping.insert((parts[0].into(), parts[1].into()), parts[2].into());
+    }
+
+    Ok(repo_mapping)
+}
+
 /// Returns the .runfiles directory for the currently executing binary.
 pub fn find_runfiles_dir() -> io::Result<PathBuf> {
-    assert_ne!(
-        std::env::var_os(MANIFEST_ONLY_ENV_VAR).unwrap_or_else(|| OsString::from("0")),
-        "1"
-    );
+    assert!(std::env::var_os(MANIFEST_FILE_ENV_VAR).is_none());
 
     // If bazel told us about the runfiles dir, use that without looking further.
     if let Some(runfiles_dir) = std::env::var_os(RUNFILES_DIR_ENV_VAR).map(PathBuf::from) {
@@ -187,26 +233,6 @@
     io::Error::new(io::ErrorKind::Other, msg)
 }
 
-fn is_manifest_only() -> bool {
-    match std::env::var(MANIFEST_ONLY_ENV_VAR) {
-        Ok(val) => val == "1",
-        Err(_) => false,
-    }
-}
-
-fn find_manifest_path() -> io::Result<PathBuf> {
-    assert_eq!(
-        std::env::var_os(MANIFEST_ONLY_ENV_VAR).expect("RUNFILES_MANIFEST_ONLY was not set"),
-        OsString::from("1")
-    );
-    match std::env::var_os(MANIFEST_FILE_ENV_VAR) {
-        Some(path) => Ok(path.into()),
-        None => Err(
-            make_io_error(
-                "RUNFILES_MANIFEST_ONLY was set to '1', but RUNFILES_MANIFEST_FILE was not set. Did Bazel change?"))
-    }
-}
-
 #[cfg(test)]
 mod test {
     use super::*;
@@ -217,7 +243,7 @@
     #[test]
     fn test_can_read_data_from_runfiles() {
         // We want to run multiple test cases with different environment variables set. Since
-        // environment variables are global state, we need to ensure the two test cases do not run
+        // environment variables are global state, we need to ensure the test cases do not run
         // concurrently. Rust runs tests in parallel and does not provide an easy way to synchronise
         // them, so we run all test cases in the same #[test] function.
 
@@ -225,10 +251,12 @@
             env::var_os(TEST_SRCDIR_ENV_VAR).expect("bazel did not provide TEST_SRCDIR");
         let runfiles_dir =
             env::var_os(RUNFILES_DIR_ENV_VAR).expect("bazel did not provide RUNFILES_DIR");
+        let runfiles_manifest_file = env::var_os(MANIFEST_FILE_ENV_VAR).unwrap_or("".into());
 
         // Test case 1: Only $RUNFILES_DIR is set.
         {
             env::remove_var(TEST_SRCDIR_ENV_VAR);
+            env::remove_var(MANIFEST_FILE_ENV_VAR);
             let r = Runfiles::create().unwrap();
 
             let mut f =
@@ -238,11 +266,13 @@
             f.read_to_string(&mut buffer).unwrap();
 
             assert_eq!("Example Text!", buffer);
-            env::set_var(TEST_SRCDIR_ENV_VAR, &test_srcdir)
+            env::set_var(TEST_SRCDIR_ENV_VAR, &test_srcdir);
+            env::set_var(MANIFEST_FILE_ENV_VAR, &runfiles_manifest_file);
         }
         // Test case 2: Only $TEST_SRCDIR is set.
         {
             env::remove_var(RUNFILES_DIR_ENV_VAR);
+            env::remove_var(MANIFEST_FILE_ENV_VAR);
             let r = Runfiles::create().unwrap();
 
             let mut f =
@@ -252,13 +282,15 @@
             f.read_to_string(&mut buffer).unwrap();
 
             assert_eq!("Example Text!", buffer);
-            env::set_var(RUNFILES_DIR_ENV_VAR, &runfiles_dir)
+            env::set_var(RUNFILES_DIR_ENV_VAR, &runfiles_dir);
+            env::set_var(MANIFEST_FILE_ENV_VAR, &runfiles_manifest_file);
         }
 
         // Test case 3: Neither are set
         {
             env::remove_var(RUNFILES_DIR_ENV_VAR);
             env::remove_var(TEST_SRCDIR_ENV_VAR);
+            env::remove_var(MANIFEST_FILE_ENV_VAR);
 
             let r = Runfiles::create().unwrap();
 
@@ -272,6 +304,7 @@
 
             env::set_var(TEST_SRCDIR_ENV_VAR, &test_srcdir);
             env::set_var(RUNFILES_DIR_ENV_VAR, &runfiles_dir);
+            env::set_var(MANIFEST_FILE_ENV_VAR, &runfiles_manifest_file);
         }
     }
 
@@ -281,17 +314,9 @@
         path_mapping.insert("a/b".into(), "c/d".into());
         let r = Runfiles {
             mode: Mode::ManifestBased(path_mapping),
+            repo_mapping: RepoMapping::new(),
         };
 
         assert_eq!(r.rlocation("a/b"), PathBuf::from("c/d"));
     }
-
-    #[test]
-    fn test_current_repository() {
-        let r = Runfiles::create().unwrap();
-
-        // This check is unique to the rules_rust repository. The name
-        // here is expected to be different in consumers of this library
-        assert_eq!(r.current_repository(), "rules_rust")
-    }
 }
diff --git a/tools/rustfmt/BUILD.bazel b/tools/rustfmt/BUILD.bazel
index 88e2792..e7d3961 100644
--- a/tools/rustfmt/BUILD.bazel
+++ b/tools/rustfmt/BUILD.bazel
@@ -24,8 +24,8 @@
     ],
     edition = "2018",
     rustc_env = {
-        "RUSTFMT": "$(rootpath //rust/toolchain:current_rustfmt_toolchain)",
-        "RUSTFMT_CONFIG": "$(rootpath //:rustfmt.toml)",
+        "RUSTFMT": "$(rlocationpath //rust/toolchain:current_rustfmt_toolchain)",
+        "RUSTFMT_CONFIG": "$(rlocationpath //:rustfmt.toml)",
     },
     deps = [
         "//tools/runfiles",
diff --git a/tools/rustfmt/src/lib.rs b/tools/rustfmt/src/lib.rs
index 9658a76..eb51bbd 100644
--- a/tools/rustfmt/src/lib.rs
+++ b/tools/rustfmt/src/lib.rs
@@ -21,20 +21,12 @@
 pub fn parse_rustfmt_config() -> RustfmtConfig {
     let runfiles = runfiles::Runfiles::create().unwrap();
 
-    let rustfmt = runfiles.rlocation(format!(
-        "{}/{}",
-        runfiles.current_repository(),
-        env!("RUSTFMT")
-    ));
+    let rustfmt = runfiles::rlocation!(runfiles, env!("RUSTFMT"));
     if !rustfmt.exists() {
         panic!("rustfmt does not exist at: {}", rustfmt.display());
     }
 
-    let config = runfiles.rlocation(format!(
-        "{}/{}",
-        runfiles.current_repository(),
-        env!("RUSTFMT_CONFIG")
-    ));
+    let config = runfiles::rlocation!(runfiles, env!("RUSTFMT_CONFIG"));
     if !config.exists() {
         panic!(
             "rustfmt config file does not exist at: {}",
@@ -79,7 +71,7 @@
         edition,
         sources: lines
             .into_iter()
-            .map(|src| runfiles.rlocation(format!("{}/{}", runfiles.current_repository(), src)))
+            .map(|src| runfiles::rlocation!(runfiles, src))
             .collect(),
     }
 }
@@ -98,14 +90,7 @@
     std::env::var("RUSTFMT_MANIFESTS")
         .map(|var| {
             var.split(PATH_ENV_SEP)
-                .filter_map(|path| match path.is_empty() {
-                    true => None,
-                    false => Some(runfiles.rlocation(format!(
-                        "{}/{}",
-                        runfiles.current_repository(),
-                        path
-                    ))),
-                })
+                .map(|path| runfiles::rlocation!(runfiles, path))
                 .collect()
         })
         .unwrap_or_default()