1
- use std:: num :: NonZeroUsize ;
1
+ use std:: process :: ExitCode ;
2
2
use std:: sync:: Mutex ;
3
3
4
4
use clap:: Parser ;
5
+ use colored:: Colorize ;
5
6
use crossbeam:: channel as crossbeam_channel;
6
- use red_knot_workspace:: site_packages:: site_packages_dirs_of_venv;
7
7
8
+ use red_knot_server:: run_server;
8
9
use red_knot_workspace:: db:: RootDatabase ;
10
+ use red_knot_workspace:: site_packages:: site_packages_dirs_of_venv;
9
11
use red_knot_workspace:: watch;
10
12
use red_knot_workspace:: watch:: WorkspaceWatcher ;
11
13
use red_knot_workspace:: workspace:: WorkspaceMetadata ;
@@ -83,13 +85,34 @@ pub enum Command {
83
85
Server ,
84
86
}
85
87
86
- #[ allow(
87
- clippy:: print_stdout,
88
- clippy:: unnecessary_wraps,
89
- clippy:: print_stderr,
90
- clippy:: dbg_macro
91
- ) ]
92
- pub fn main ( ) -> anyhow:: Result < ( ) > {
88
+ #[ 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 ( )
111
+ }
112
+ }
113
+ }
114
+
115
+ fn run ( ) -> anyhow:: Result < ExitStatus > {
93
116
let Args {
94
117
command,
95
118
current_directory,
@@ -101,20 +124,12 @@ pub fn main() -> anyhow::Result<()> {
101
124
watch,
102
125
} = Args :: parse_from ( std:: env:: args ( ) . collect :: < Vec < _ > > ( ) ) ;
103
126
104
- let verbosity = verbosity. level ( ) ;
105
- countme:: enable ( verbosity. is_trace ( ) ) ;
106
-
107
127
if matches ! ( command, Some ( Command :: Server ) ) {
108
- let four = NonZeroUsize :: new ( 4 ) . unwrap ( ) ;
109
-
110
- // by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
111
- let worker_threads = std:: thread:: available_parallelism ( )
112
- . unwrap_or ( four)
113
- . max ( four) ;
114
-
115
- return red_knot_server:: Server :: new ( worker_threads) ?. run ( ) ;
128
+ return run_server ( ) . map ( |( ) | ExitStatus :: Success ) ;
116
129
}
117
130
131
+ let verbosity = verbosity. level ( ) ;
132
+ countme:: enable ( verbosity. is_trace ( ) ) ;
118
133
let _guard = setup_tracing ( verbosity) ?;
119
134
120
135
let cwd = if let Some ( cwd) = current_directory {
@@ -167,17 +182,35 @@ pub fn main() -> anyhow::Result<()> {
167
182
}
168
183
} ) ?;
169
184
170
- if watch {
171
- main_loop. watch ( & mut db) ?;
185
+ let exit_status = if watch {
186
+ main_loop. watch ( & mut db) ?
172
187
} else {
173
- main_loop. run ( & mut db) ;
188
+ main_loop. run ( & mut db)
174
189
} ;
175
190
176
- tracing:: trace!( "Counts for entire CLI run :\n {}" , countme:: get_all( ) ) ;
191
+ tracing:: trace!( "Counts for entire CLI run:\n {}" , countme:: get_all( ) ) ;
177
192
178
193
std:: mem:: forget ( db) ;
179
194
180
- Ok ( ( ) )
195
+ Ok ( exit_status)
196
+ }
197
+
198
+ #[ derive( Copy , Clone ) ]
199
+ pub enum ExitStatus {
200
+ /// Checking was successful and there were no errors.
201
+ Success = 0 ,
202
+
203
+ /// Checking was successful but there were errors.
204
+ Failure = 1 ,
205
+
206
+ /// Checking failed.
207
+ Error = 2 ,
208
+ }
209
+
210
+ impl From < ExitStatus > for ExitCode {
211
+ fn from ( status : ExitStatus ) -> Self {
212
+ ExitCode :: from ( status as u8 )
213
+ }
181
214
}
182
215
183
216
struct MainLoop {
@@ -205,7 +238,7 @@ impl MainLoop {
205
238
)
206
239
}
207
240
208
- fn watch ( mut self , db : & mut RootDatabase ) -> anyhow:: Result < ( ) > {
241
+ fn watch ( mut self , db : & mut RootDatabase ) -> anyhow:: Result < ExitStatus > {
209
242
tracing:: debug!( "Starting watch mode" ) ;
210
243
let sender = self . sender . clone ( ) ;
211
244
let watcher = watch:: directory_watcher ( move |event| {
@@ -216,19 +249,21 @@ impl MainLoop {
216
249
217
250
self . run ( db) ;
218
251
219
- Ok ( ( ) )
252
+ Ok ( ExitStatus :: Success )
220
253
}
221
254
222
- fn run ( mut self , db : & mut RootDatabase ) {
255
+ fn run ( mut self , db : & mut RootDatabase ) -> ExitStatus {
223
256
self . sender . send ( MainLoopMessage :: CheckWorkspace ) . unwrap ( ) ;
224
257
225
- self . main_loop ( db) ;
258
+ let result = self . main_loop ( db) ;
226
259
227
260
tracing:: debug!( "Exiting main loop" ) ;
261
+
262
+ result
228
263
}
229
264
230
265
#[ allow( clippy:: print_stderr) ]
231
- fn main_loop ( & mut self , db : & mut RootDatabase ) {
266
+ fn main_loop ( & mut self , db : & mut RootDatabase ) -> ExitStatus {
232
267
// Schedule the first check.
233
268
tracing:: debug!( "Starting main loop" ) ;
234
269
@@ -263,7 +298,11 @@ impl MainLoop {
263
298
}
264
299
265
300
if self . watcher . is_none ( ) {
266
- return ;
301
+ return if result. is_empty ( ) {
302
+ ExitStatus :: Success
303
+ } else {
304
+ ExitStatus :: Failure
305
+ } ;
267
306
}
268
307
269
308
tracing:: trace!( "Counts after last check:\n {}" , countme:: get_all( ) ) ;
@@ -279,12 +318,14 @@ impl MainLoop {
279
318
self . sender . send ( MainLoopMessage :: CheckWorkspace ) . unwrap ( ) ;
280
319
}
281
320
MainLoopMessage :: Exit => {
282
- return ;
321
+ return ExitStatus :: Success ;
283
322
}
284
323
}
285
324
286
325
tracing:: debug!( "Waiting for next main loop message." ) ;
287
326
}
327
+
328
+ ExitStatus :: Success
288
329
}
289
330
}
290
331
0 commit comments