Skip to content

Commit c6b9399

Browse files
authored
Added support for --incompatible_compact_repo_mapping_manifest (#3713)
Implements support for bazelbuild/bazel#26262
1 parent f3ccfa4 commit c6b9399

File tree

1 file changed

+200
-45
lines changed

1 file changed

+200
-45
lines changed

rust/runfiles/runfiles.rs

Lines changed: 200 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
//!
55
//! 1. Depend on this runfiles library from your build rule:
66
//! ```python
7-
//! rust_binary(
8-
//! name = "my_binary",
9-
//! ...
10-
//! data = ["//path/to/my/data.txt"],
11-
//! deps = ["@rules_rust//rust/runfiles"],
12-
//! )
7+
//! rust_binary(
8+
//! name = "my_binary",
9+
//! ...
10+
//! data = ["//path/to/my/data.txt"],
11+
//! deps = ["@rules_rust//rust/runfiles"],
12+
//! )
1313
//! ```
1414
//!
1515
//! 2. Import the runfiles library.
@@ -30,7 +30,7 @@
3030
//! // ...
3131
//! ```
3232
33-
use std::collections::HashMap;
33+
use std::collections::{BTreeMap, HashMap};
3434
use std::env;
3535
use std::fs;
3636
use std::io;
@@ -140,8 +140,53 @@ enum Mode {
140140
ManifestBased(HashMap<PathBuf, PathBuf>),
141141
}
142142

143+
/// A pair of "source" (the workspace the mapping affects) and "target apparent name" (the
144+
/// non-bzlmod-generated/pretty name of a dependent workspace).
143145
type RepoMappingKey = (String, String);
144-
type RepoMapping = HashMap<RepoMappingKey, String>;
146+
147+
/// The mapping of keys to "target canonical directory" (the bzlmod-generated workspace name).
148+
#[derive(Debug, PartialEq, Eq)]
149+
struct RepoMapping {
150+
exact: HashMap<RepoMappingKey, String>,
151+
152+
/// Used for `--incompatible_compact_repo_mapping_manifest`.
153+
/// <https://github.com/bazelbuild/bazel/issues/26262>
154+
prefixes: BTreeMap<RepoMappingKey, String>,
155+
}
156+
157+
impl RepoMapping {
158+
pub fn new() -> Self {
159+
RepoMapping {
160+
exact: HashMap::new(),
161+
prefixes: BTreeMap::new(),
162+
}
163+
}
164+
165+
pub fn get(&self, key: &RepoMappingKey) -> Option<&String> {
166+
// First try exact match with O(1) hash lookup
167+
if let Some(value) = self.exact.get(key) {
168+
return Some(value);
169+
}
170+
171+
// Then try prefix match with O(n) iteration
172+
// In compact mode, entries with wildcards are stored with just the prefix.
173+
// We need to check if the lookup key's source_repo starts with any stored prefix.
174+
let (source_repo, apparent_name) = key;
175+
for ((stored_source, stored_apparent), value) in self.prefixes.iter() {
176+
if source_repo.starts_with(stored_source) && apparent_name == stored_apparent {
177+
return Some(value);
178+
}
179+
}
180+
181+
None
182+
}
183+
}
184+
185+
impl Default for RepoMapping {
186+
fn default() -> Self {
187+
Self::new()
188+
}
189+
}
145190

146191
/// An interface for accessing to [Bazel runfiles](https://bazel.build/extending/rules#runfiles).
147192
#[derive(Debug)]
@@ -251,7 +296,8 @@ fn raw_rlocation(mode: &Mode, path: impl AsRef<Path>) -> Option<PathBuf> {
251296
}
252297

253298
fn parse_repo_mapping(path: PathBuf) -> Result<RepoMapping> {
254-
let mut repo_mapping = RepoMapping::new();
299+
let mut exact = HashMap::new();
300+
let mut prefixes = BTreeMap::new();
255301

256302
for line in std::fs::read_to_string(path)
257303
.map_err(RunfilesError::RepoMappingIoError)?
@@ -261,10 +307,27 @@ fn parse_repo_mapping(path: PathBuf) -> Result<RepoMapping> {
261307
if parts.len() < 3 {
262308
return Err(RunfilesError::RepoMappingInvalidFormat);
263309
}
264-
repo_mapping.insert((parts[0].into(), parts[1].into()), parts[2].into());
310+
311+
let source_repo = parts[0];
312+
let apparent_name = parts[1];
313+
let target_repo = parts[2];
314+
315+
// Check if this is a prefix entry (ends with '*')
316+
// The '*' character is terminal and marks a prefix match entry
317+
if let Some(prefix) = source_repo.strip_suffix('*') {
318+
prefixes.insert(
319+
(prefix.to_owned(), apparent_name.to_owned()),
320+
target_repo.to_owned(),
321+
);
322+
} else {
323+
exact.insert(
324+
(source_repo.to_owned(), apparent_name.to_owned()),
325+
target_repo.to_owned(),
326+
);
327+
}
265328
}
266329

267-
Ok(repo_mapping)
330+
Ok(RepoMapping { exact, prefixes })
268331
}
269332

270333
/// Returns the .runfiles directory for the currently executing binary.
@@ -625,43 +688,46 @@ mod test {
625688

626689
assert_eq!(
627690
parse_repo_mapping(valid),
628-
Ok(RepoMapping::from([
629-
(
630-
("local_config_xcode".to_owned(), "rules_rust".to_owned()),
631-
"rules_rust".to_owned()
632-
),
633-
(
634-
("platforms".to_owned(), "rules_rust".to_owned()),
635-
"rules_rust".to_owned()
636-
),
637-
(
691+
Ok(RepoMapping {
692+
prefixes: BTreeMap::new(),
693+
exact: HashMap::from([
638694
(
639-
"rust_darwin_aarch64__aarch64-apple-darwin__stable_tools".to_owned(),
695+
("local_config_xcode".to_owned(), "rules_rust".to_owned()),
640696
"rules_rust".to_owned()
641697
),
642-
"rules_rust".to_owned()
643-
),
644-
(
645-
("rules_rust_tinyjson".to_owned(), "rules_rust".to_owned()),
646-
"rules_rust".to_owned()
647-
),
648-
(
649-
("local_config_sh".to_owned(), "rules_rust".to_owned()),
650-
"rules_rust".to_owned()
651-
),
652-
(
653-
("bazel_tools".to_owned(), "__main__".to_owned()),
654-
"rules_rust".to_owned()
655-
),
656-
(
657-
("local_config_cc".to_owned(), "rules_rust".to_owned()),
658-
"rules_rust".to_owned()
659-
),
660-
(
661-
("".to_owned(), "rules_rust".to_owned()),
662-
"rules_rust".to_owned()
663-
)
664-
]))
698+
(
699+
("platforms".to_owned(), "rules_rust".to_owned()),
700+
"rules_rust".to_owned()
701+
),
702+
(
703+
(
704+
"rust_darwin_aarch64__aarch64-apple-darwin__stable_tools".to_owned(),
705+
"rules_rust".to_owned()
706+
),
707+
"rules_rust".to_owned()
708+
),
709+
(
710+
("rules_rust_tinyjson".to_owned(), "rules_rust".to_owned()),
711+
"rules_rust".to_owned()
712+
),
713+
(
714+
("local_config_sh".to_owned(), "rules_rust".to_owned()),
715+
"rules_rust".to_owned()
716+
),
717+
(
718+
("bazel_tools".to_owned(), "__main__".to_owned()),
719+
"rules_rust".to_owned()
720+
),
721+
(
722+
("local_config_cc".to_owned(), "rules_rust".to_owned()),
723+
"rules_rust".to_owned()
724+
),
725+
(
726+
("".to_owned(), "rules_rust".to_owned()),
727+
"rules_rust".to_owned()
728+
)
729+
])
730+
})
665731
);
666732
}
667733

@@ -684,4 +750,93 @@ mod test {
684750
Err(RunfilesError::RepoMappingInvalidFormat),
685751
);
686752
}
753+
754+
#[test]
755+
fn test_parse_repo_mapping_with_wildcard() {
756+
let temp_dir = PathBuf::from(std::env::var("TEST_TMPDIR").unwrap());
757+
std::fs::create_dir_all(&temp_dir).unwrap();
758+
759+
let mapping_file = temp_dir.join("test_parse_repo_mapping_with_wildcard.txt");
760+
std::fs::write(
761+
&mapping_file,
762+
dedent(
763+
r#"+deps+*,aaa,_main
764+
+deps+*,dep,+deps+dep1
765+
+deps+*,dep1,+deps+dep1
766+
+deps+*,dep2,+deps+dep2
767+
+deps+*,dep3,+deps+dep3
768+
+other+exact,foo,bar
769+
"#,
770+
),
771+
)
772+
.unwrap();
773+
774+
let repo_mapping = parse_repo_mapping(mapping_file).unwrap();
775+
776+
// Check exact match for non-wildcard entry
777+
assert_eq!(
778+
repo_mapping.get(&("+other+exact".to_owned(), "foo".to_owned())),
779+
Some(&"bar".to_owned())
780+
);
781+
782+
// Check prefix matches work correctly
783+
// When looking up with +deps+dep1 as source_repo, it should match entries with +deps+ prefix
784+
assert_eq!(
785+
repo_mapping.get(&("+deps+dep1".to_owned(), "aaa".to_owned())),
786+
Some(&"_main".to_owned())
787+
);
788+
assert_eq!(
789+
repo_mapping.get(&("+deps+dep1".to_owned(), "dep".to_owned())),
790+
Some(&"+deps+dep1".to_owned())
791+
);
792+
assert_eq!(
793+
repo_mapping.get(&("+deps+dep2".to_owned(), "dep2".to_owned())),
794+
Some(&"+deps+dep2".to_owned())
795+
);
796+
assert_eq!(
797+
repo_mapping.get(&("+deps+dep3".to_owned(), "dep3".to_owned())),
798+
Some(&"+deps+dep3".to_owned())
799+
);
800+
}
801+
802+
#[test]
803+
fn test_rlocation_from_with_wildcard() {
804+
let temp_dir = PathBuf::from(std::env::var("TEST_TMPDIR").unwrap());
805+
std::fs::create_dir_all(&temp_dir).unwrap();
806+
807+
// Create a mock runfiles directory
808+
let runfiles_dir = temp_dir.join("test_rlocation_from_with_wildcard.runfiles");
809+
std::fs::create_dir_all(&runfiles_dir).unwrap();
810+
811+
let r = Runfiles {
812+
mode: Mode::DirectoryBased(runfiles_dir.clone()),
813+
repo_mapping: RepoMapping {
814+
exact: HashMap::new(),
815+
prefixes: BTreeMap::from([
816+
(("+deps+".to_owned(), "aaa".to_owned()), "_main".to_owned()),
817+
(
818+
("+deps+".to_owned(), "dep".to_owned()),
819+
"+deps+dep1".to_owned(),
820+
),
821+
]),
822+
},
823+
};
824+
825+
// Test prefix matching for +deps+dep1
826+
let result = r.rlocation_from("aaa/some/path", "+deps+dep1");
827+
assert_eq!(result, Some(runfiles_dir.join("_main/some/path")));
828+
829+
// Test prefix matching for +deps+dep2
830+
let result = r.rlocation_from("aaa/other/path", "+deps+dep2");
831+
assert_eq!(result, Some(runfiles_dir.join("_main/other/path")));
832+
833+
// Test prefix matching with different apparent name
834+
let result = r.rlocation_from("dep/foo/bar", "+deps+dep3");
835+
assert_eq!(result, Some(runfiles_dir.join("+deps+dep1/foo/bar")));
836+
837+
// Test non-matching source repo (doesn't start with +deps+)
838+
let result = r.rlocation_from("aaa/path", "+other+repo");
839+
// Should fall back to the path as-is
840+
assert_eq!(result, Some(runfiles_dir.join("aaa/path")));
841+
}
687842
}

0 commit comments

Comments
 (0)