Skip to content

Commit df7345e

Browse files
authored
Exit with an error if there are check failures (#12735)
1 parent dc6aafe commit df7345e

File tree

4 files changed

+95
-37
lines changed

4 files changed

+95
-37
lines changed

crates/red_knot/src/main.rs

+73-32
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use std::num::NonZeroUsize;
1+
use std::process::ExitCode;
22
use std::sync::Mutex;
33

44
use clap::Parser;
5+
use colored::Colorize;
56
use crossbeam::channel as crossbeam_channel;
6-
use red_knot_workspace::site_packages::site_packages_dirs_of_venv;
77

8+
use red_knot_server::run_server;
89
use red_knot_workspace::db::RootDatabase;
10+
use red_knot_workspace::site_packages::site_packages_dirs_of_venv;
911
use red_knot_workspace::watch;
1012
use red_knot_workspace::watch::WorkspaceWatcher;
1113
use red_knot_workspace::workspace::WorkspaceMetadata;
@@ -83,13 +85,34 @@ pub enum Command {
8385
Server,
8486
}
8587

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> {
93116
let Args {
94117
command,
95118
current_directory,
@@ -101,20 +124,12 @@ pub fn main() -> anyhow::Result<()> {
101124
watch,
102125
} = Args::parse_from(std::env::args().collect::<Vec<_>>());
103126

104-
let verbosity = verbosity.level();
105-
countme::enable(verbosity.is_trace());
106-
107127
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);
116129
}
117130

131+
let verbosity = verbosity.level();
132+
countme::enable(verbosity.is_trace());
118133
let _guard = setup_tracing(verbosity)?;
119134

120135
let cwd = if let Some(cwd) = current_directory {
@@ -167,17 +182,35 @@ pub fn main() -> anyhow::Result<()> {
167182
}
168183
})?;
169184

170-
if watch {
171-
main_loop.watch(&mut db)?;
185+
let exit_status = if watch {
186+
main_loop.watch(&mut db)?
172187
} else {
173-
main_loop.run(&mut db);
188+
main_loop.run(&mut db)
174189
};
175190

176-
tracing::trace!("Counts for entire CLI run :\n{}", countme::get_all());
191+
tracing::trace!("Counts for entire CLI run:\n{}", countme::get_all());
177192

178193
std::mem::forget(db);
179194

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+
}
181214
}
182215

183216
struct MainLoop {
@@ -205,7 +238,7 @@ impl MainLoop {
205238
)
206239
}
207240

208-
fn watch(mut self, db: &mut RootDatabase) -> anyhow::Result<()> {
241+
fn watch(mut self, db: &mut RootDatabase) -> anyhow::Result<ExitStatus> {
209242
tracing::debug!("Starting watch mode");
210243
let sender = self.sender.clone();
211244
let watcher = watch::directory_watcher(move |event| {
@@ -216,19 +249,21 @@ impl MainLoop {
216249

217250
self.run(db);
218251

219-
Ok(())
252+
Ok(ExitStatus::Success)
220253
}
221254

222-
fn run(mut self, db: &mut RootDatabase) {
255+
fn run(mut self, db: &mut RootDatabase) -> ExitStatus {
223256
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
224257

225-
self.main_loop(db);
258+
let result = self.main_loop(db);
226259

227260
tracing::debug!("Exiting main loop");
261+
262+
result
228263
}
229264

230265
#[allow(clippy::print_stderr)]
231-
fn main_loop(&mut self, db: &mut RootDatabase) {
266+
fn main_loop(&mut self, db: &mut RootDatabase) -> ExitStatus {
232267
// Schedule the first check.
233268
tracing::debug!("Starting main loop");
234269

@@ -263,7 +298,11 @@ impl MainLoop {
263298
}
264299

265300
if self.watcher.is_none() {
266-
return;
301+
return if result.is_empty() {
302+
ExitStatus::Success
303+
} else {
304+
ExitStatus::Failure
305+
};
267306
}
268307

269308
tracing::trace!("Counts after last check:\n{}", countme::get_all());
@@ -279,12 +318,14 @@ impl MainLoop {
279318
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
280319
}
281320
MainLoopMessage::Exit => {
282-
return;
321+
return ExitStatus::Success;
283322
}
284323
}
285324

286325
tracing::debug!("Waiting for next main loop message.");
287326
}
327+
328+
ExitStatus::Success
288329
}
289330
}
290331

crates/red_knot_server/src/lib.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#![allow(dead_code)]
22

3+
use anyhow::Context;
34
pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument};
4-
pub use server::Server;
55
pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session};
6+
use std::num::NonZeroUsize;
7+
8+
use crate::server::Server;
69

710
#[macro_use]
811
mod message;
@@ -23,3 +26,18 @@ pub(crate) type Result<T> = anyhow::Result<T>;
2326
pub(crate) fn version() -> &'static str {
2427
env!("CARGO_PKG_VERSION")
2528
}
29+
30+
pub fn run_server() -> anyhow::Result<()> {
31+
let four = NonZeroUsize::new(4).unwrap();
32+
33+
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
34+
let worker_threads = std::thread::available_parallelism()
35+
.unwrap_or(four)
36+
.max(four);
37+
38+
Server::new(worker_threads)
39+
.context("Failed to start server")?
40+
.run()?;
41+
42+
Ok(())
43+
}

crates/red_knot_server/src/server.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ pub(crate) use connection::ClientSender;
2525

2626
pub(crate) type Result<T> = std::result::Result<T, api::Error>;
2727

28-
pub struct Server {
28+
pub(crate) struct Server {
2929
connection: Connection,
3030
client_capabilities: ClientCapabilities,
3131
worker_threads: NonZeroUsize,
3232
session: Session,
3333
}
3434

3535
impl Server {
36-
pub fn new(worker_threads: NonZeroUsize) -> crate::Result<Self> {
36+
pub(crate) fn new(worker_threads: NonZeroUsize) -> crate::Result<Self> {
3737
let connection = ConnectionInitializer::stdio();
3838

3939
let (id, init_params) = connection.initialize_start()?;
@@ -113,7 +113,7 @@ impl Server {
113113
})
114114
}
115115

116-
pub fn run(self) -> crate::Result<()> {
116+
pub(crate) fn run(self) -> crate::Result<()> {
117117
type PanicHook = Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>;
118118
struct RestorePanicHook {
119119
hook: Option<PanicHook>,

crates/ruff/src/main.rs

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ pub fn main() -> ExitCode {
8585
match run(args) {
8686
Ok(code) => code.into(),
8787
Err(err) => {
88-
#[allow(clippy::print_stderr)]
8988
{
9089
use std::io::Write;
9190

0 commit comments

Comments
 (0)