@@ -8,6 +8,7 @@ use crossbeam::channel;
88use derive_new:: new;
99use ignore:: WalkBuilder ;
1010
11+ use crate :: errors:: { GrimpError , GrimpResult } ;
1112use crate :: graph:: Graph ;
1213use crate :: import_parsing:: { ImportedObject , parse_imports_from_code} ;
1314
@@ -31,22 +32,31 @@ pub fn build_graph(
3132 include_external_packages : bool ,
3233 exclude_type_checking_imports : bool ,
3334 cache_dir : Option < & PathBuf > ,
34- ) -> Graph {
35+ ) -> GrimpResult < Graph > {
36+ // Check if package directory exists
37+ if !package. directory . exists ( ) {
38+ return Err ( GrimpError :: PackageDirectoryNotFound (
39+ package. directory . display ( ) . to_string ( ) ,
40+ ) ) ;
41+ }
42+
3543 // Load cache if available
3644 let mut cache = cache_dir
3745 . map ( |dir| load_cache ( dir, & package. name ) )
3846 . unwrap_or_default ( ) ;
3947
4048 // Create channels for communication
41- let ( module_discovery_sender, module_discovery_receiver) = channel:: bounded ( 10000 ) ;
42- let ( import_parser_sender, import_parser_receiver) = channel:: bounded ( 10000 ) ;
49+ // This way we can start parsing moduels while we're still discovering them.
50+ let ( found_module_sender, found_module_receiver) = channel:: bounded ( 10000 ) ;
51+ let ( parsed_module_sender, parser_module_receiver) = channel:: bounded ( 10000 ) ;
52+ let ( error_sender, error_receiver) = channel:: bounded ( 1 ) ;
4353
4454 let mut thread_handles = Vec :: new ( ) ;
4555
4656 // Thread 1: Discover modules
4757 let package_clone = package. clone ( ) ;
4858 let handle = thread:: spawn ( move || {
49- discover_python_modules ( & package_clone, module_discovery_sender ) ;
59+ discover_python_modules ( & package_clone, found_module_sender ) ;
5060 } ) ;
5161 thread_handles. push ( handle) ;
5262
@@ -55,32 +65,47 @@ pub fn build_graph(
5565 . map ( |n| max ( n. get ( ) / 2 , 1 ) )
5666 . unwrap_or ( 4 ) ;
5767 for _ in 0 ..num_workers {
58- let receiver = module_discovery_receiver. clone ( ) ;
59- let sender = import_parser_sender. clone ( ) ;
68+ let receiver = found_module_receiver. clone ( ) ;
69+ let sender = parsed_module_sender. clone ( ) ;
70+ let error_sender = error_sender. clone ( ) ;
6071 let cache = cache. clone ( ) ;
6172 let handle = thread:: spawn ( move || {
6273 while let Ok ( module) = receiver. recv ( ) {
63- if let Some ( parsed) = parse_module_imports ( & module, & cache) {
64- sender. send ( parsed) . unwrap ( ) ;
74+ match parse_module_imports ( & module, & cache) {
75+ Ok ( parsed) => {
76+ sender. send ( parsed) . unwrap ( ) ;
77+ }
78+ Err ( e) => {
79+ // Channel has capacity of 1, since we only care to catch one error.
80+ // Drop further errors.
81+ let _ = error_sender. try_send ( e) ;
82+ }
6583 }
6684 }
6785 } ) ;
6886 thread_handles. push ( handle) ;
6987 }
70- drop ( module_discovery_receiver) ; // Close original receiver
71- drop ( import_parser_sender) ; // Close original sender
72-
73- // Collect parsed modules
74- let mut parsed_modules = Vec :: new ( ) ;
75- while let Ok ( parsed) = import_parser_receiver. recv ( ) {
76- parsed_modules. push ( parsed) ;
77- }
7888
89+ // Close original found_module_receiver, so that the parser threads can exit.
90+ drop ( found_module_receiver) ;
7991 // Wait for all threads to complete
8092 for handle in thread_handles {
8193 handle. join ( ) . unwrap ( ) ;
8294 }
8395
96+ // Check if any errors occurred
97+ if let Ok ( error) = error_receiver. try_recv ( ) {
98+ return Err ( error) ;
99+ }
100+
101+ // Collect parsed modules
102+ let mut parsed_modules = Vec :: new ( ) ;
103+ // Close original parsed_module_sender, so that parser_module_receiver iteration terminates.
104+ drop ( parsed_module_sender) ;
105+ while let Ok ( parsed) = parser_module_receiver. recv ( ) {
106+ parsed_modules. push ( parsed) ;
107+ }
108+
84109 // Update and save cache if cache_dir is set
85110 if let Some ( cache_dir) = cache_dir {
86111 for parsed in & parsed_modules {
@@ -89,17 +114,17 @@ pub fn build_graph(
89114 CachedImports :: new ( parsed. module . mtime_secs , parsed. imported_objects . clone ( ) ) ,
90115 ) ;
91116 }
92- save_cache ( & cache, cache_dir, & package. name ) ;
117+ save_cache ( & cache, cache_dir, & package. name ) ? ;
93118 }
94119
95- // Resolve imports and assemble graph (sequential)
120+ // Resolve imports and assemble graph
96121 let imports_by_module = resolve_imports (
97122 & parsed_modules,
98123 include_external_packages,
99124 exclude_type_checking_imports,
100125 ) ;
101126
102- assemble_graph ( & imports_by_module, & package. name )
127+ Ok ( assemble_graph ( & imports_by_module, & package. name ) )
103128}
104129
105130#[ derive( Debug , Clone ) ]
@@ -154,6 +179,12 @@ fn discover_python_modules(package: &PackageSpec, sender: channel::Sender<FoundM
154179 } ;
155180
156181 let path = entry. path ( ) ;
182+
183+ // Only process files, not directories
184+ if !path. is_file ( ) {
185+ return WalkState :: Continue ;
186+ }
187+
157188 if let Some ( module_name) = path_to_module_name ( path, & package) {
158189 let is_package = is_package ( path) ;
159190
@@ -181,23 +212,27 @@ fn discover_python_modules(package: &PackageSpec, sender: channel::Sender<FoundM
181212 } ) ;
182213}
183214
184- fn parse_module_imports ( module : & FoundModule , cache : & ImportCache ) -> Option < ParsedModule > {
215+ fn parse_module_imports ( module : & FoundModule , cache : & ImportCache ) -> GrimpResult < ParsedModule > {
185216 // Check if we have a cached version with matching mtime
186217 if let Some ( cached) = cache. get ( & module. path )
187218 && module. mtime_secs == cached. mtime_secs ( )
188219 {
189220 // Cache hit - use cached imports
190- return Some ( ParsedModule {
221+ return Ok ( ParsedModule {
191222 module : module. clone ( ) ,
192223 imported_objects : cached. imported_objects ( ) . to_vec ( ) ,
193224 } ) ;
194225 }
195226
196227 // Cache miss or file modified - parse the file
197- let code = fs:: read_to_string ( & module. path ) . ok ( ) ?;
198- let imported_objects =
199- parse_imports_from_code ( & code, module. path . to_str ( ) . unwrap_or ( "" ) ) . ok ( ) ?;
200- Some ( ParsedModule {
228+ let code = fs:: read_to_string ( & module. path ) . map_err ( |e| GrimpError :: FileReadError {
229+ path : module. path . display ( ) . to_string ( ) ,
230+ error : e. to_string ( ) ,
231+ } ) ?;
232+
233+ let imported_objects = parse_imports_from_code ( & code, module. path . to_str ( ) . unwrap_or ( "" ) ) ?;
234+
235+ Ok ( ParsedModule {
201236 module : module. clone ( ) ,
202237 imported_objects,
203238 } )
0 commit comments