Exit with an error if there are check failures (#12735)

This commit is contained in:
Micha Reiser 2024-08-08 09:10:18 +02:00 committed by GitHub
parent dc6aafecc2
commit df7345e118
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 95 additions and 37 deletions

View file

@ -1,11 +1,13 @@
use std::num::NonZeroUsize; use std::process::ExitCode;
use std::sync::Mutex; use std::sync::Mutex;
use clap::Parser; use clap::Parser;
use colored::Colorize;
use crossbeam::channel as crossbeam_channel; use crossbeam::channel as crossbeam_channel;
use red_knot_workspace::site_packages::site_packages_dirs_of_venv;
use red_knot_server::run_server;
use red_knot_workspace::db::RootDatabase; use red_knot_workspace::db::RootDatabase;
use red_knot_workspace::site_packages::site_packages_dirs_of_venv;
use red_knot_workspace::watch; use red_knot_workspace::watch;
use red_knot_workspace::watch::WorkspaceWatcher; use red_knot_workspace::watch::WorkspaceWatcher;
use red_knot_workspace::workspace::WorkspaceMetadata; use red_knot_workspace::workspace::WorkspaceMetadata;
@ -83,13 +85,34 @@ pub enum Command {
Server, Server,
} }
#[allow( #[allow(clippy::print_stdout, clippy::unnecessary_wraps, clippy::print_stderr)]
clippy::print_stdout, pub fn main() -> ExitCode {
clippy::unnecessary_wraps, match run() {
clippy::print_stderr, Ok(status) => status.into(),
clippy::dbg_macro Err(error) => {
)] {
pub fn main() -> anyhow::Result<()> { use std::io::Write;
// Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
let mut stderr = std::io::stderr().lock();
// This communicates that this isn't a linter error but ruff itself hard-errored for
// some reason (e.g. failed to resolve the configuration)
writeln!(stderr, "{}", "ruff failed".red().bold()).ok();
// Currently we generally only see one error, but e.g. with io errors when resolving
// the configuration it is help to chain errors ("resolving configuration failed" ->
// "failed to read file: subdir/pyproject.toml")
for cause in error.chain() {
writeln!(stderr, " {} {cause}", "Cause:".bold()).ok();
}
}
ExitStatus::Error.into()
}
}
}
fn run() -> anyhow::Result<ExitStatus> {
let Args { let Args {
command, command,
current_directory, current_directory,
@ -101,20 +124,12 @@ pub fn main() -> anyhow::Result<()> {
watch, watch,
} = Args::parse_from(std::env::args().collect::<Vec<_>>()); } = Args::parse_from(std::env::args().collect::<Vec<_>>());
let verbosity = verbosity.level();
countme::enable(verbosity.is_trace());
if matches!(command, Some(Command::Server)) { if matches!(command, Some(Command::Server)) {
let four = NonZeroUsize::new(4).unwrap(); return run_server().map(|()| ExitStatus::Success);
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
let worker_threads = std::thread::available_parallelism()
.unwrap_or(four)
.max(four);
return red_knot_server::Server::new(worker_threads)?.run();
} }
let verbosity = verbosity.level();
countme::enable(verbosity.is_trace());
let _guard = setup_tracing(verbosity)?; let _guard = setup_tracing(verbosity)?;
let cwd = if let Some(cwd) = current_directory { let cwd = if let Some(cwd) = current_directory {
@ -167,17 +182,35 @@ pub fn main() -> anyhow::Result<()> {
} }
})?; })?;
if watch { let exit_status = if watch {
main_loop.watch(&mut db)?; main_loop.watch(&mut db)?
} else { } else {
main_loop.run(&mut db); main_loop.run(&mut db)
}; };
tracing::trace!("Counts for entire CLI run:\n{}", countme::get_all()); tracing::trace!("Counts for entire CLI run:\n{}", countme::get_all());
std::mem::forget(db); std::mem::forget(db);
Ok(()) Ok(exit_status)
}
#[derive(Copy, Clone)]
pub enum ExitStatus {
/// Checking was successful and there were no errors.
Success = 0,
/// Checking was successful but there were errors.
Failure = 1,
/// Checking failed.
Error = 2,
}
impl From<ExitStatus> for ExitCode {
fn from(status: ExitStatus) -> Self {
ExitCode::from(status as u8)
}
} }
struct MainLoop { struct MainLoop {
@ -205,7 +238,7 @@ impl MainLoop {
) )
} }
fn watch(mut self, db: &mut RootDatabase) -> anyhow::Result<()> { fn watch(mut self, db: &mut RootDatabase) -> anyhow::Result<ExitStatus> {
tracing::debug!("Starting watch mode"); tracing::debug!("Starting watch mode");
let sender = self.sender.clone(); let sender = self.sender.clone();
let watcher = watch::directory_watcher(move |event| { let watcher = watch::directory_watcher(move |event| {
@ -216,19 +249,21 @@ impl MainLoop {
self.run(db); self.run(db);
Ok(()) Ok(ExitStatus::Success)
} }
fn run(mut self, db: &mut RootDatabase) { fn run(mut self, db: &mut RootDatabase) -> ExitStatus {
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap(); self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
self.main_loop(db); let result = self.main_loop(db);
tracing::debug!("Exiting main loop"); tracing::debug!("Exiting main loop");
result
} }
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
fn main_loop(&mut self, db: &mut RootDatabase) { fn main_loop(&mut self, db: &mut RootDatabase) -> ExitStatus {
// Schedule the first check. // Schedule the first check.
tracing::debug!("Starting main loop"); tracing::debug!("Starting main loop");
@ -263,7 +298,11 @@ impl MainLoop {
} }
if self.watcher.is_none() { if self.watcher.is_none() {
return; return if result.is_empty() {
ExitStatus::Success
} else {
ExitStatus::Failure
};
} }
tracing::trace!("Counts after last check:\n{}", countme::get_all()); tracing::trace!("Counts after last check:\n{}", countme::get_all());
@ -279,12 +318,14 @@ impl MainLoop {
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap(); self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
} }
MainLoopMessage::Exit => { MainLoopMessage::Exit => {
return; return ExitStatus::Success;
} }
} }
tracing::debug!("Waiting for next main loop message."); tracing::debug!("Waiting for next main loop message.");
} }
ExitStatus::Success
} }
} }

View file

@ -1,8 +1,11 @@
#![allow(dead_code)] #![allow(dead_code)]
use anyhow::Context;
pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument}; pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument};
pub use server::Server;
pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session}; pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session};
use std::num::NonZeroUsize;
use crate::server::Server;
#[macro_use] #[macro_use]
mod message; mod message;
@ -23,3 +26,18 @@ pub(crate) type Result<T> = anyhow::Result<T>;
pub(crate) fn version() -> &'static str { pub(crate) fn version() -> &'static str {
env!("CARGO_PKG_VERSION") env!("CARGO_PKG_VERSION")
} }
pub fn run_server() -> anyhow::Result<()> {
let four = NonZeroUsize::new(4).unwrap();
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
let worker_threads = std::thread::available_parallelism()
.unwrap_or(four)
.max(four);
Server::new(worker_threads)
.context("Failed to start server")?
.run()?;
Ok(())
}

View file

@ -25,7 +25,7 @@ pub(crate) use connection::ClientSender;
pub(crate) type Result<T> = std::result::Result<T, api::Error>; pub(crate) type Result<T> = std::result::Result<T, api::Error>;
pub struct Server { pub(crate) struct Server {
connection: Connection, connection: Connection,
client_capabilities: ClientCapabilities, client_capabilities: ClientCapabilities,
worker_threads: NonZeroUsize, worker_threads: NonZeroUsize,
@ -33,7 +33,7 @@ pub struct Server {
} }
impl Server { impl Server {
pub fn new(worker_threads: NonZeroUsize) -> crate::Result<Self> { pub(crate) fn new(worker_threads: NonZeroUsize) -> crate::Result<Self> {
let connection = ConnectionInitializer::stdio(); let connection = ConnectionInitializer::stdio();
let (id, init_params) = connection.initialize_start()?; let (id, init_params) = connection.initialize_start()?;
@ -113,7 +113,7 @@ impl Server {
}) })
} }
pub fn run(self) -> crate::Result<()> { pub(crate) fn run(self) -> crate::Result<()> {
type PanicHook = Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>; type PanicHook = Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>;
struct RestorePanicHook { struct RestorePanicHook {
hook: Option<PanicHook>, hook: Option<PanicHook>,

View file

@ -85,7 +85,6 @@ pub fn main() -> ExitCode {
match run(args) { match run(args) {
Ok(code) => code.into(), Ok(code) => code.into(),
Err(err) => { Err(err) => {
#[allow(clippy::print_stderr)]
{ {
use std::io::Write; use std::io::Write;