1- use std:: process:: ExitCode ;
1+ use std:: process:: { ExitCode , Termination } ;
22use std:: sync:: Mutex ;
33
4+ use anyhow:: { anyhow, Context } ;
45use clap:: Parser ;
56use colored:: Colorize ;
67use crossbeam:: channel as crossbeam_channel;
8+ use salsa:: plumbing:: ZalsaDatabase ;
79
810use red_knot_server:: run_server;
911use red_knot_workspace:: db:: RootDatabase ;
@@ -12,7 +14,7 @@ use red_knot_workspace::watch;
1214use red_knot_workspace:: watch:: WorkspaceWatcher ;
1315use red_knot_workspace:: workspace:: WorkspaceMetadata ;
1416use ruff_db:: program:: { ProgramSettings , SearchPathSettings } ;
15- use ruff_db:: system:: { OsSystem , System , SystemPathBuf } ;
17+ use ruff_db:: system:: { OsSystem , System , SystemPath , SystemPathBuf } ;
1618use target_version:: TargetVersion ;
1719
1820use crate :: logging:: { setup_tracing, Verbosity } ;
@@ -86,30 +88,25 @@ pub enum Command {
8688}
8789
8890#[ 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 ( ) ;
111106 }
112- }
107+
108+ ExitStatus :: Error
109+ } )
113110}
114111
115112fn run ( ) -> anyhow:: Result < ExitStatus > {
@@ -132,28 +129,43 @@ fn run() -> anyhow::Result<ExitStatus> {
132129 countme:: enable ( verbosity. is_trace ( ) ) ;
133130 let _guard = setup_tracing ( verbosity) ?;
134131
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( ) ) ) ?
141136 } ;
142137
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+
143151 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 ( ) ;
157169
158170 // TODO: Respect the settings from the workspace metadata. when resolving the program settings.
159171 let program_settings = ProgramSettings {
@@ -207,9 +219,9 @@ pub enum ExitStatus {
207219 Error = 2 ,
208220}
209221
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 )
213225 }
214226}
215227
@@ -262,12 +274,11 @@ impl MainLoop {
262274 result
263275 }
264276
265- #[ allow( clippy:: print_stderr) ]
266277 fn main_loop ( & mut self , db : & mut RootDatabase ) -> ExitStatus {
267278 // Schedule the first check.
268279 tracing:: debug!( "Starting main loop" ) ;
269280
270- let mut revision = 0usize ;
281+ let mut revision = 0u64 ;
271282
272283 while let Ok ( message) = self . receiver . recv ( ) {
273284 match message {
@@ -282,7 +293,7 @@ impl MainLoop {
282293 // Send the result back to the main loop for printing.
283294 sender
284295 . send ( MainLoopMessage :: CheckCompleted { result, revision } )
285- . ok ( ) ;
296+ . unwrap ( ) ;
286297 }
287298 } ) ;
288299 }
@@ -291,17 +302,20 @@ impl MainLoop {
291302 result,
292303 revision : check_revision,
293304 } => {
305+ let has_diagnostics = !result. is_empty ( ) ;
294306 if check_revision == revision {
295- eprintln ! ( "{}" , result. join( "\n " ) ) ;
307+ for diagnostic in result {
308+ tracing:: error!( "{}" , diagnostic) ;
309+ }
296310 } else {
297311 tracing:: debug!( "Discarding check result for outdated revision: current: {revision}, result revision: {check_revision}" ) ;
298312 }
299313
300314 if self . watcher . is_none ( ) {
301- return if result. is_empty ( ) {
302- ExitStatus :: Success
303- } else {
315+ return if has_diagnostics {
304316 ExitStatus :: Failure
317+ } else {
318+ ExitStatus :: Success
305319 } ;
306320 }
307321
@@ -318,6 +332,10 @@ impl MainLoop {
318332 self . sender . send ( MainLoopMessage :: CheckWorkspace ) . unwrap ( ) ;
319333 }
320334 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 ( ) ;
321339 return ExitStatus :: Success ;
322340 }
323341 }
@@ -344,10 +362,7 @@ impl MainLoopCancellationToken {
344362#[ derive( Debug ) ]
345363enum MainLoopMessage {
346364 CheckWorkspace ,
347- CheckCompleted {
348- result : Vec < String > ,
349- revision : usize ,
350- } ,
365+ CheckCompleted { result : Vec < String > , revision : u64 } ,
351366 ApplyChanges ( Vec < watch:: ChangeEvent > ) ,
352367 Exit ,
353368}
0 commit comments