11use super :: Remapping ;
22use foundry_compilers_core:: utils;
3+ use rayon:: prelude:: * ;
34use std:: {
45 collections:: { btree_map:: Entry , BTreeMap , HashSet } ,
56 path:: { Path , PathBuf } ,
7+ sync:: Mutex ,
68} ;
79
810const DAPPTOOLS_CONTRACTS_DIR : & str = "src" ;
@@ -52,6 +54,7 @@ impl Remapping {
5254 /// which would be multiple rededications according to our rules ("governance", "protocol-v2"),
5355 /// are unified into `@aave` by looking at their common ancestor, the root of this subdirectory
5456 /// (`@aave`)
57+ #[ instrument( level = "trace" , name = "Remapping::find_many" ) ]
5558 pub fn find_many ( dir : & Path ) -> Vec < Self > {
5659 /// prioritize
5760 /// - ("a", "1/2") over ("a", "1/2/3")
@@ -76,41 +79,31 @@ impl Remapping {
7679 }
7780 }
7881
79- // all combined remappings from all subdirs
80- let mut all_remappings = BTreeMap :: new ( ) ;
81-
8282 let is_inside_node_modules = dir. ends_with ( "node_modules" ) ;
83+ let visited_symlink_dirs = Mutex :: new ( HashSet :: new ( ) ) ;
8384
84- let mut visited_symlink_dirs = HashSet :: new ( ) ;
8585 // iterate over all dirs that are children of the root
86- for dir in walkdir:: WalkDir :: new ( dir)
87- . sort_by_file_name ( )
88- . follow_links ( true )
89- . min_depth ( 1 )
90- . max_depth ( 1 )
91- . into_iter ( )
92- . filter_entry ( |e| !is_hidden ( e) )
93- . filter_map ( Result :: ok)
86+ let candidates = read_dir ( dir)
9487 . filter ( |e| e. file_type ( ) . is_dir ( ) )
95- {
96- let depth1_dir = dir . path ( ) ;
97- // check all remappings in this depth 1 folder
98- let candidates = find_remapping_candidates (
99- depth1_dir ,
100- depth1_dir ,
101- 0 ,
102- is_inside_node_modules ,
103- & mut visited_symlink_dirs ,
104- ) ;
105-
106- for candidate in candidates {
107- if let Some ( name ) = candidate . window_start . file_name ( ) . and_then ( |s| s . to_str ( ) ) {
108- insert_prioritized (
109- & mut all_remappings ,
110- format ! ( "{name}/" ) ,
111- candidate . source_dir ,
112- ) ;
113- }
88+ . collect :: < Vec < _ > > ( )
89+ . par_iter ( )
90+ . flat_map_iter ( |entry| {
91+ let dir = entry . path ( ) ;
92+ find_remapping_candidates (
93+ dir ,
94+ dir ,
95+ 0 ,
96+ is_inside_node_modules ,
97+ & visited_symlink_dirs ,
98+ )
99+ } )
100+ . collect :: < Vec < _ > > ( ) ;
101+
102+ // all combined remappings from all subdirs
103+ let mut all_remappings = BTreeMap :: new ( ) ;
104+ for candidate in candidates {
105+ if let Some ( name ) = candidate . window_start . file_name ( ) . and_then ( |s| s . to_str ( ) ) {
106+ insert_prioritized ( & mut all_remappings , format ! ( "{name}/" ) , candidate . source_dir ) ;
114107 }
115108 }
116109
@@ -298,32 +291,23 @@ fn is_hidden(entry: &walkdir::DirEntry) -> bool {
298291
299292/// Finds all remappings in the directory recursively
300293///
301- /// Note: this supports symlinks and will short-circuit if a symlink dir has already been visited, this can occur in pnpm setups: <https://github.com/foundry-rs/foundry/issues/7820>
294+ /// Note: this supports symlinks and will short-circuit if a symlink dir has already been visited,
295+ /// this can occur in pnpm setups: <https://github.com/foundry-rs/foundry/issues/7820>
302296fn find_remapping_candidates (
303297 current_dir : & Path ,
304298 open : & Path ,
305299 current_level : usize ,
306300 is_inside_node_modules : bool ,
307- visited_symlink_dirs : & mut HashSet < PathBuf > ,
301+ visited_symlink_dirs : & Mutex < HashSet < PathBuf > > ,
308302) -> Vec < Candidate > {
303+ trace ! ( "find_remapping_candidates({})" , current_dir. display( ) ) ;
304+
309305 // this is a marker if the current root is a candidate for a remapping
310306 let mut is_candidate = false ;
311307
312- // all found candidates
313- let mut candidates = Vec :: new ( ) ;
314-
315308 // scan all entries in the current dir
316- for entry in walkdir:: WalkDir :: new ( current_dir)
317- . sort_by_file_name ( )
318- . follow_links ( true )
319- . min_depth ( 1 )
320- . max_depth ( 1 )
321- . into_iter ( )
322- . filter_entry ( |e| !is_hidden ( e) )
323- . filter_map ( Result :: ok)
324- {
325- let entry: walkdir:: DirEntry = entry;
326-
309+ let mut search = Vec :: new ( ) ;
310+ for entry in read_dir ( current_dir) {
327311 // found a solidity file directly the current dir
328312 if !is_candidate
329313 && entry. file_type ( ) . is_file ( )
@@ -341,7 +325,7 @@ fn find_remapping_candidates(
341325 // ```
342326 if entry. path_is_symlink ( ) {
343327 if let Ok ( target) = utils:: canonicalize ( entry. path ( ) ) {
344- if !visited_symlink_dirs. insert ( target. clone ( ) ) {
328+ if !visited_symlink_dirs. lock ( ) . unwrap ( ) . insert ( target. clone ( ) ) {
345329 // short-circuiting if we've already visited the symlink
346330 return Vec :: new ( ) ;
347331 }
@@ -357,38 +341,45 @@ fn find_remapping_candidates(
357341
358342 let subdir = entry. path ( ) ;
359343 // we skip commonly used subdirs that should not be searched for recursively
360- if !( subdir. ends_with ( "tests" ) || subdir. ends_with ( "test" ) || subdir. ends_with ( "demo" ) )
361- {
362- // scan the subdirectory for remappings, but we need a way to identify nested
363- // dependencies like `ds-token/lib/ds-stop/lib/ds-note/src/contract.sol`, or
364- // `oz/{tokens,auth}/{contracts, interfaces}/contract.sol` to assign
365- // the remappings to their root, we use a window that lies between two barriers. If
366- // we find a solidity file within a window, it belongs to the dir that opened the
367- // window.
368-
369- // check if the subdir is a lib barrier, in which case we open a new window
370- if is_lib_dir ( subdir) {
371- candidates. extend ( find_remapping_candidates (
372- subdir,
373- subdir,
374- current_level + 1 ,
375- is_inside_node_modules,
376- visited_symlink_dirs,
377- ) ) ;
378- } else {
379- // continue scanning with the current window
380- candidates. extend ( find_remapping_candidates (
381- subdir,
382- open,
383- current_level,
384- is_inside_node_modules,
385- visited_symlink_dirs,
386- ) ) ;
387- }
344+ if !no_recurse ( subdir) {
345+ search. push ( subdir. to_path_buf ( ) ) ;
388346 }
389347 }
390348 }
391349
350+ // all found candidates
351+ let mut candidates = search
352+ . par_iter ( )
353+ . flat_map_iter ( |subdir| {
354+ // scan the subdirectory for remappings, but we need a way to identify nested
355+ // dependencies like `ds-token/lib/ds-stop/lib/ds-note/src/contract.sol`, or
356+ // `oz/{tokens,auth}/{contracts, interfaces}/contract.sol` to assign
357+ // the remappings to their root, we use a window that lies between two barriers. If
358+ // we find a solidity file within a window, it belongs to the dir that opened the
359+ // window.
360+
361+ // check if the subdir is a lib barrier, in which case we open a new window
362+ if is_lib_dir ( subdir) {
363+ find_remapping_candidates (
364+ subdir,
365+ subdir,
366+ current_level + 1 ,
367+ is_inside_node_modules,
368+ visited_symlink_dirs,
369+ )
370+ } else {
371+ // continue scanning with the current window
372+ find_remapping_candidates (
373+ subdir,
374+ open,
375+ current_level,
376+ is_inside_node_modules,
377+ visited_symlink_dirs,
378+ )
379+ }
380+ } )
381+ . collect :: < Vec < _ > > ( ) ;
382+
392383 // need to find the actual next window in the event `open` is a lib dir
393384 let window_start = next_nested_window ( open, current_dir) ;
394385 // finally, we need to merge, adjust candidates from the same level and open window
@@ -425,6 +416,21 @@ fn find_remapping_candidates(
425416 candidates
426417}
427418
419+ fn read_dir ( dir : & Path ) -> impl Iterator < Item = walkdir:: DirEntry > {
420+ walkdir:: WalkDir :: new ( dir)
421+ . sort_by_file_name ( )
422+ . follow_links ( true )
423+ . min_depth ( 1 )
424+ . max_depth ( 1 )
425+ . into_iter ( )
426+ . filter_entry ( |e| !is_hidden ( e) )
427+ . filter_map ( Result :: ok)
428+ }
429+
430+ fn no_recurse ( dir : & Path ) -> bool {
431+ dir. ends_with ( "tests" ) || dir. ends_with ( "test" ) || dir. ends_with ( "demo" )
432+ }
433+
428434/// Counts the number of components between `root` and `current`
429435/// `dir_distance("root/a", "root/a/b/c") == 2`
430436fn dir_distance ( root : & Path , current : & Path ) -> usize {
0 commit comments