@@ -10,8 +10,8 @@ use ruff_linter::{warn_user, warn_user_once};
1010use ruff_python_ast:: { PySourceType , SourceType } ;
1111use ruff_workspace:: resolver:: { python_files_in_path, ResolvedFile } ;
1212use 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.
1717pub ( 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+ }
0 commit comments