Skip to content

Commit 022dc7e

Browse files
committed
perf: parallelize Remapping::get_many
1 parent 0a1c495 commit 022dc7e

File tree

1 file changed

+82
-76
lines changed
  • crates/artifacts/solc/src/remappings

1 file changed

+82
-76
lines changed

crates/artifacts/solc/src/remappings/find.rs

Lines changed: 82 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use super::Remapping;
22
use foundry_compilers_core::utils;
3+
use rayon::prelude::*;
34
use std::{
45
collections::{btree_map::Entry, BTreeMap, HashSet},
56
path::{Path, PathBuf},
7+
sync::Mutex,
68
};
79

810
const 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>
302296
fn 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`
430436
fn dir_distance(root: &Path, current: &Path) -> usize {

0 commit comments

Comments
 (0)