Skip to content

Commit 70ab006

Browse files
committed
Added support for --incompatible_compact_repo_mapping_manifest
1 parent 76a0b8f commit 70ab006

File tree

1 file changed

+182
-12
lines changed

1 file changed

+182
-12
lines changed

rust/runfiles/runfiles.rs

Lines changed: 182 additions & 12 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.
@@ -140,8 +140,64 @@ 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+
enum RepoMapping {
150+
/// Implements `--incompatible_compact_repo_mapping_manifest`.
151+
/// <https://github.com/bazelbuild/bazel/issues/26262>
152+
///
153+
/// Uses a HashMap for fast exact lookups, and a Vec for prefix-based fallback matching.
154+
Compact {
155+
exact: HashMap<RepoMappingKey, String>,
156+
prefixes: Vec<(RepoMappingKey, String)>,
157+
},
158+
159+
/// The canonical repo mapping file.
160+
Verbose(HashMap<RepoMappingKey, String>),
161+
}
162+
163+
impl RepoMapping {
164+
pub fn new() -> Self {
165+
RepoMapping::Compact {
166+
exact: HashMap::new(),
167+
prefixes: Vec::new(),
168+
}
169+
}
170+
171+
pub fn get(&self, key: &RepoMappingKey) -> Option<&String> {
172+
match self {
173+
RepoMapping::Compact { exact, prefixes } => {
174+
// First try exact match with O(1) hash lookup
175+
if let Some(value) = exact.get(key) {
176+
return Some(value);
177+
}
178+
179+
// Then try prefix match with O(n) iteration
180+
// In compact mode, entries with wildcards are stored with just the prefix.
181+
// We need to check if the lookup key's source_repo starts with any stored prefix.
182+
let (source_repo, apparent_name) = key;
183+
for ((stored_source, stored_apparent), value) in prefixes {
184+
if source_repo.starts_with(stored_source) && apparent_name == stored_apparent {
185+
return Some(value);
186+
}
187+
}
188+
189+
None
190+
}
191+
RepoMapping::Verbose(hash_map) => hash_map.get(key),
192+
}
193+
}
194+
}
195+
196+
impl Default for RepoMapping {
197+
fn default() -> Self {
198+
Self::new()
199+
}
200+
}
145201

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

253309
fn parse_repo_mapping(path: PathBuf) -> Result<RepoMapping> {
254-
let mut repo_mapping = RepoMapping::new();
310+
let mut exact = HashMap::new();
311+
let mut prefixes = Vec::new();
255312

256313
for line in std::fs::read_to_string(path)
257314
.map_err(RunfilesError::RepoMappingIoError)?
@@ -261,10 +318,31 @@ fn parse_repo_mapping(path: PathBuf) -> Result<RepoMapping> {
261318
if parts.len() < 3 {
262319
return Err(RunfilesError::RepoMappingInvalidFormat);
263320
}
264-
repo_mapping.insert((parts[0].into(), parts[1].into()), parts[2].into());
321+
322+
let source_repo = parts[0];
323+
let apparent_name = parts[1];
324+
let target_repo = parts[2];
325+
326+
// Check if this is a prefix entry (ends with '*')
327+
// The '*' character is terminal and marks a prefix match entry
328+
if let Some(prefix) = source_repo.strip_suffix('*') {
329+
prefixes.push((
330+
(prefix.to_owned(), apparent_name.to_owned()),
331+
target_repo.to_owned(),
332+
));
333+
} else {
334+
exact.insert(
335+
(source_repo.to_owned(), apparent_name.to_owned()),
336+
target_repo.to_owned(),
337+
);
338+
}
265339
}
266340

267-
Ok(repo_mapping)
341+
Ok(if !prefixes.is_empty() {
342+
RepoMapping::Compact { exact, prefixes }
343+
} else {
344+
RepoMapping::Verbose(exact)
345+
})
268346
}
269347

270348
/// Returns the .runfiles directory for the currently executing binary.
@@ -625,7 +703,7 @@ mod test {
625703

626704
assert_eq!(
627705
parse_repo_mapping(valid),
628-
Ok(RepoMapping::from([
706+
Ok(RepoMapping::Verbose(HashMap::from([
629707
(
630708
("local_config_xcode".to_owned(), "rules_rust".to_owned()),
631709
"rules_rust".to_owned()
@@ -661,7 +739,7 @@ mod test {
661739
("".to_owned(), "rules_rust".to_owned()),
662740
"rules_rust".to_owned()
663741
)
664-
]))
742+
])))
665743
);
666744
}
667745

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

0 commit comments

Comments
 (0)