Skip to content

Commit 770b276

Browse files
Cache glob resolutions in import graph (#13413)
## Summary These are often repeated; caching the resolutions can have a huge impact.
1 parent 4e935f7 commit 770b276

File tree

4 files changed

+88
-37
lines changed

4 files changed

+88
-37
lines changed

crates/ruff/src/commands/analyze_graph.rs

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use ruff_linter::{warn_user, warn_user_once};
1010
use ruff_python_ast::{PySourceType, SourceType};
1111
use ruff_workspace::resolver::{python_files_in_path, ResolvedFile};
1212
use rustc_hash::FxHashMap;
13-
use std::path::Path;
14-
use std::sync::Arc;
13+
use std::path::{Path, PathBuf};
14+
use std::sync::{Arc, Mutex};
1515

1616
/// Generate an import map.
1717
pub(crate) fn analyze_graph(
@@ -51,7 +51,7 @@ pub(crate) fn analyze_graph(
5151
.map(|(path, package)| (path.to_path_buf(), package.map(Path::to_path_buf)))
5252
.collect::<FxHashMap<_, _>>();
5353

54-
// Create a database for each source root.
54+
// Create a database from the source roots.
5555
let db = ModuleDb::from_src_roots(
5656
package_roots
5757
.values()
@@ -61,8 +61,11 @@ pub(crate) fn analyze_graph(
6161
.filter_map(|path| SystemPathBuf::from_path_buf(path).ok()),
6262
)?;
6363

64+
// Create a cache for resolved globs.
65+
let glob_resolver = Arc::new(Mutex::new(GlobResolver::default()));
66+
6467
// Collect and resolve the imports for each file.
65-
let result = Arc::new(std::sync::Mutex::new(Vec::new()));
68+
let result = Arc::new(Mutex::new(Vec::new()));
6669
let inner_result = Arc::clone(&result);
6770

6871
rayon::scope(move |scope| {
@@ -111,6 +114,7 @@ pub(crate) fn analyze_graph(
111114
let db = db.snapshot();
112115
let root = root.clone();
113116
let result = inner_result.clone();
117+
let glob_resolver = glob_resolver.clone();
114118
scope.spawn(move |_| {
115119
// Identify any imports via static analysis.
116120
let mut imports =
@@ -120,38 +124,12 @@ pub(crate) fn analyze_graph(
120124
ModuleImports::default()
121125
});
122126

127+
debug!("Discovered {} imports for {}", imports.len(), path);
128+
123129
// Append any imports that were statically defined in the configuration.
124130
if let Some((root, globs)) = include_dependencies {
125-
match globwalk::GlobWalkerBuilder::from_patterns(root, &globs)
126-
.file_type(globwalk::FileType::FILE)
127-
.build()
128-
{
129-
Ok(walker) => {
130-
for entry in walker {
131-
let entry = match entry {
132-
Ok(entry) => entry,
133-
Err(err) => {
134-
warn!("Failed to read glob entry: {err}");
135-
continue;
136-
}
137-
};
138-
let path = match SystemPathBuf::from_path_buf(entry.into_path()) {
139-
Ok(path) => path,
140-
Err(err) => {
141-
warn!(
142-
"Failed to convert path to system path: {}",
143-
err.display()
144-
);
145-
continue;
146-
}
147-
};
148-
imports.insert(path);
149-
}
150-
}
151-
Err(err) => {
152-
warn!("Failed to read glob walker: {err}");
153-
}
154-
}
131+
let mut glob_resolver = glob_resolver.lock().unwrap();
132+
imports.extend(glob_resolver.resolve(root, globs));
155133
}
156134

157135
// Convert the path (and imports) to be relative to the working directory.
@@ -180,3 +158,67 @@ pub(crate) fn analyze_graph(
180158

181159
Ok(ExitStatus::Success)
182160
}
161+
162+
/// A resolver for glob sets.
163+
#[derive(Default, Debug)]
164+
struct GlobResolver {
165+
cache: GlobCache,
166+
}
167+
168+
impl GlobResolver {
169+
/// Resolve a set of globs, anchored at a given root.
170+
fn resolve(&mut self, root: PathBuf, globs: Vec<String>) -> Vec<SystemPathBuf> {
171+
if let Some(cached) = self.cache.get(&root, &globs) {
172+
return cached.clone();
173+
}
174+
175+
let walker = match globwalk::GlobWalkerBuilder::from_patterns(&root, &globs)
176+
.file_type(globwalk::FileType::FILE)
177+
.build()
178+
{
179+
Ok(walker) => walker,
180+
Err(err) => {
181+
warn!("Failed to read glob walker: {err}");
182+
return Vec::new();
183+
}
184+
};
185+
186+
let mut paths = Vec::new();
187+
for entry in walker {
188+
let entry = match entry {
189+
Ok(entry) => entry,
190+
Err(err) => {
191+
warn!("Failed to read glob entry: {err}");
192+
continue;
193+
}
194+
};
195+
let path = match SystemPathBuf::from_path_buf(entry.into_path()) {
196+
Ok(path) => path,
197+
Err(err) => {
198+
warn!("Failed to convert path to system path: {}", err.display());
199+
continue;
200+
}
201+
};
202+
paths.push(path);
203+
}
204+
205+
self.cache.insert(root, globs, paths.clone());
206+
paths
207+
}
208+
}
209+
210+
/// A cache for resolved globs.
211+
#[derive(Default, Debug)]
212+
struct GlobCache(FxHashMap<PathBuf, FxHashMap<Vec<String>, Vec<SystemPathBuf>>>);
213+
214+
impl GlobCache {
215+
/// Insert a resolved glob.
216+
fn insert(&mut self, root: PathBuf, globs: Vec<String>, paths: Vec<SystemPathBuf>) {
217+
self.0.entry(root).or_default().insert(globs, paths);
218+
}
219+
220+
/// Get a resolved glob.
221+
fn get(&self, root: &Path, globs: &[String]) -> Option<&Vec<SystemPathBuf>> {
222+
self.0.get(root).and_then(|map| map.get(globs))
223+
}
224+
}

crates/ruff/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ pub fn run(
188188
Command::Check(args) => check(args, global_options),
189189
Command::Format(args) => format(args, global_options),
190190
Command::Server(args) => server(args),
191-
Command::Analyze(AnalyzeCommand::Graph(args)) => graph_build(args, global_options),
191+
Command::Analyze(AnalyzeCommand::Graph(args)) => analyze_graph(args, global_options),
192192
}
193193
}
194194

@@ -202,7 +202,10 @@ fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitS
202202
}
203203
}
204204

205-
fn graph_build(args: AnalyzeGraphCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
205+
fn analyze_graph(
206+
args: AnalyzeGraphCommand,
207+
global_options: GlobalConfigArgs,
208+
) -> Result<ExitStatus> {
206209
let (cli, config_arguments) = args.partition(global_options)?;
207210

208211
commands::analyze_graph::analyze_graph(cli, &config_arguments)

crates/ruff/tests/analyze_graph.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Tests the interaction of the `analyze graph` command.
22
3-
#![cfg(not(target_family = "wasm"))]
3+
#![cfg(not(target_arch = "wasm32"))]
4+
#![cfg(not(windows))]
45

56
use assert_fs::prelude::*;
67
use std::process::Command;

crates/ruff_graph/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ impl ModuleImports {
2626
self.0.insert(path);
2727
}
2828

29+
/// Extend the module imports with additional file paths.
30+
pub fn extend(&mut self, paths: impl IntoIterator<Item = SystemPathBuf>) {
31+
self.0.extend(paths);
32+
}
33+
2934
/// Returns `true` if the module imports are empty.
3035
pub fn is_empty(&self) -> bool {
3136
self.0.is_empty()

0 commit comments

Comments
 (0)