1
- use std:: process:: ExitCode ;
1
+ use std:: process:: { ExitCode , Termination } ;
2
2
use std:: sync:: Mutex ;
3
3
4
+ use anyhow:: { anyhow, Context } ;
4
5
use clap:: Parser ;
5
6
use colored:: Colorize ;
6
7
use crossbeam:: channel as crossbeam_channel;
8
+ use salsa:: plumbing:: ZalsaDatabase ;
7
9
8
10
use red_knot_server:: run_server;
9
11
use red_knot_workspace:: db:: RootDatabase ;
@@ -12,7 +14,7 @@ use red_knot_workspace::watch;
12
14
use red_knot_workspace:: watch:: WorkspaceWatcher ;
13
15
use red_knot_workspace:: workspace:: WorkspaceMetadata ;
14
16
use ruff_db:: program:: { ProgramSettings , SearchPathSettings } ;
15
- use ruff_db:: system:: { OsSystem , System , SystemPathBuf } ;
17
+ use ruff_db:: system:: { OsSystem , System , SystemPath , SystemPathBuf } ;
16
18
use target_version:: TargetVersion ;
17
19
18
20
use crate :: logging:: { setup_tracing, Verbosity } ;
@@ -86,30 +88,25 @@ pub enum Command {
86
88
}
87
89
88
90
#[ allow( clippy:: print_stdout, clippy:: unnecessary_wraps, clippy:: print_stderr) ]
89
- pub fn main ( ) -> ExitCode {
90
- match run ( ) {
91
- Ok ( status) => status. into ( ) ,
92
- Err ( error) => {
93
- {
94
- use std:: io:: Write ;
95
-
96
- // Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
97
- let mut stderr = std:: io:: stderr ( ) . lock ( ) ;
98
-
99
- // This communicates that this isn't a linter error but ruff itself hard-errored for
100
- // some reason (e.g. failed to resolve the configuration)
101
- writeln ! ( stderr, "{}" , "ruff failed" . red( ) . bold( ) ) . ok ( ) ;
102
- // Currently we generally only see one error, but e.g. with io errors when resolving
103
- // the configuration it is help to chain errors ("resolving configuration failed" ->
104
- // "failed to read file: subdir/pyproject.toml")
105
- for cause in error. chain ( ) {
106
- writeln ! ( stderr, " {} {cause}" , "Cause:" . bold( ) ) . ok ( ) ;
107
- }
108
- }
109
-
110
- ExitStatus :: Error . into ( )
91
+ pub fn main ( ) -> ExitStatus {
92
+ run ( ) . unwrap_or_else ( |error| {
93
+ use std:: io:: Write ;
94
+
95
+ // Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
96
+ let mut stderr = std:: io:: stderr ( ) . lock ( ) ;
97
+
98
+ // This communicates that this isn't a linter error but Red Knot itself hard-errored for
99
+ // some reason (e.g. failed to resolve the configuration)
100
+ writeln ! ( stderr, "{}" , "ruff failed" . red( ) . bold( ) ) . ok ( ) ;
101
+ // Currently we generally only see one error, but e.g. with io errors when resolving
102
+ // the configuration it is help to chain errors ("resolving configuration failed" ->
103
+ // "failed to read file: subdir/pyproject.toml")
104
+ for cause in error. chain ( ) {
105
+ writeln ! ( stderr, " {} {cause}" , "Cause:" . bold( ) ) . ok ( ) ;
111
106
}
112
- }
107
+
108
+ ExitStatus :: Error
109
+ } )
113
110
}
114
111
115
112
fn run ( ) -> anyhow:: Result < ExitStatus > {
@@ -132,28 +129,43 @@ fn run() -> anyhow::Result<ExitStatus> {
132
129
countme:: enable ( verbosity. is_trace ( ) ) ;
133
130
let _guard = setup_tracing ( verbosity) ?;
134
131
135
- let cwd = if let Some ( cwd) = current_directory {
136
- let canonicalized = cwd. as_utf8_path ( ) . canonicalize_utf8 ( ) . unwrap ( ) ;
137
- SystemPathBuf :: from_utf8_path_buf ( canonicalized)
138
- } else {
139
- let cwd = std:: env:: current_dir ( ) . unwrap ( ) ;
140
- SystemPathBuf :: from_path_buf ( cwd) . unwrap ( )
132
+ // The base path to which all CLI arguments are relative to.
133
+ let cli_base_path = {
134
+ let cwd = std:: env:: current_dir ( ) . context ( "Failed to get the current working directory" ) ?;
135
+ SystemPathBuf :: from_path_buf ( cwd) . map_err ( |path| anyhow ! ( "The current working directory '{}' contains non-unicode characters. Red Knot only supports unicode paths." , path. display( ) ) ) ?
141
136
} ;
142
137
138
+ let cwd = current_directory
139
+ . map ( |cwd| {
140
+ if cwd. as_std_path ( ) . is_dir ( ) {
141
+ Ok ( SystemPath :: absolute ( & cwd, & cli_base_path) )
142
+ } else {
143
+ Err ( anyhow ! (
144
+ "Provided current-directory path '{cwd}' is not a directory."
145
+ ) )
146
+ }
147
+ } )
148
+ . transpose ( ) ?
149
+ . unwrap_or_else ( || cli_base_path. clone ( ) ) ;
150
+
143
151
let system = OsSystem :: new ( cwd. clone ( ) ) ;
144
- let workspace_metadata =
145
- WorkspaceMetadata :: from_path ( system. current_directory ( ) , & system) . unwrap ( ) ;
146
-
147
- let site_packages = if let Some ( venv_path) = venv_path {
148
- let venv_path = system. canonicalize_path ( & venv_path) . unwrap_or ( venv_path) ;
149
- assert ! (
150
- system. is_directory( & venv_path) ,
151
- "Provided venv-path {venv_path} is not a directory!"
152
- ) ;
153
- site_packages_dirs_of_venv ( & venv_path, & system) . unwrap ( )
154
- } else {
155
- vec ! [ ]
156
- } ;
152
+ let workspace_metadata = WorkspaceMetadata :: from_path ( system. current_directory ( ) , & system) ?;
153
+
154
+ // TODO: Verify the remaining search path settings eagerly.
155
+ let site_packages = venv_path
156
+ . map ( |venv_path| {
157
+ let venv_path = SystemPath :: absolute ( venv_path, & cli_base_path) ;
158
+
159
+ if system. is_directory ( & venv_path) {
160
+ Ok ( site_packages_dirs_of_venv ( & venv_path, & system) ?)
161
+ } else {
162
+ Err ( anyhow ! (
163
+ "Provided venv-path {venv_path} is not a directory!"
164
+ ) )
165
+ }
166
+ } )
167
+ . transpose ( ) ?
168
+ . unwrap_or_default ( ) ;
157
169
158
170
// TODO: Respect the settings from the workspace metadata. when resolving the program settings.
159
171
let program_settings = ProgramSettings {
@@ -207,9 +219,9 @@ pub enum ExitStatus {
207
219
Error = 2 ,
208
220
}
209
221
210
- impl From < ExitStatus > for ExitCode {
211
- fn from ( status : ExitStatus ) -> Self {
212
- ExitCode :: from ( status as u8 )
222
+ impl Termination for ExitStatus {
223
+ fn report ( self ) -> ExitCode {
224
+ ExitCode :: from ( self as u8 )
213
225
}
214
226
}
215
227
@@ -262,12 +274,11 @@ impl MainLoop {
262
274
result
263
275
}
264
276
265
- #[ allow( clippy:: print_stderr) ]
266
277
fn main_loop ( & mut self , db : & mut RootDatabase ) -> ExitStatus {
267
278
// Schedule the first check.
268
279
tracing:: debug!( "Starting main loop" ) ;
269
280
270
- let mut revision = 0usize ;
281
+ let mut revision = 0u64 ;
271
282
272
283
while let Ok ( message) = self . receiver . recv ( ) {
273
284
match message {
@@ -282,7 +293,7 @@ impl MainLoop {
282
293
// Send the result back to the main loop for printing.
283
294
sender
284
295
. send ( MainLoopMessage :: CheckCompleted { result, revision } )
285
- . ok ( ) ;
296
+ . unwrap ( ) ;
286
297
}
287
298
} ) ;
288
299
}
@@ -291,17 +302,20 @@ impl MainLoop {
291
302
result,
292
303
revision : check_revision,
293
304
} => {
305
+ let has_diagnostics = !result. is_empty ( ) ;
294
306
if check_revision == revision {
295
- eprintln ! ( "{}" , result. join( "\n " ) ) ;
307
+ for diagnostic in result {
308
+ tracing:: error!( "{}" , diagnostic) ;
309
+ }
296
310
} else {
297
311
tracing:: debug!( "Discarding check result for outdated revision: current: {revision}, result revision: {check_revision}" ) ;
298
312
}
299
313
300
314
if self . watcher . is_none ( ) {
301
- return if result. is_empty ( ) {
302
- ExitStatus :: Success
303
- } else {
315
+ return if has_diagnostics {
304
316
ExitStatus :: Failure
317
+ } else {
318
+ ExitStatus :: Success
305
319
} ;
306
320
}
307
321
@@ -318,6 +332,10 @@ impl MainLoop {
318
332
self . sender . send ( MainLoopMessage :: CheckWorkspace ) . unwrap ( ) ;
319
333
}
320
334
MainLoopMessage :: Exit => {
335
+ // Cancel any pending queries and wait for them to complete.
336
+ // TODO: Don't use Salsa internal APIs
337
+ // [Zulip-Thread](https://salsa.zulipchat.com/#narrow/stream/333573-salsa-3.2E0/topic/Expose.20an.20API.20to.20cancel.20other.20queries)
338
+ let _ = db. zalsa_mut ( ) ;
321
339
return ExitStatus :: Success ;
322
340
}
323
341
}
@@ -344,10 +362,7 @@ impl MainLoopCancellationToken {
344
362
#[ derive( Debug ) ]
345
363
enum MainLoopMessage {
346
364
CheckWorkspace ,
347
- CheckCompleted {
348
- result : Vec < String > ,
349
- revision : usize ,
350
- } ,
365
+ CheckCompleted { result : Vec < String > , revision : u64 } ,
351
366
ApplyChanges ( Vec < watch:: ChangeEvent > ) ,
352
367
Exit ,
353
368
}
0 commit comments