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.
3030//! // ...
3131//! ```
3232
33- use std:: collections:: HashMap ;
33+ use std:: collections:: { BTreeMap , HashMap } ;
3434use std:: env;
3535use std:: fs;
3636use 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).
143145type 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
253298fn 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