[red-knot] Add verbosity argument to CLI (#12404)

This commit is contained in:
Micha Reiser 2024-07-19 13:38:24 +02:00 committed by GitHub
parent a62e2d2000
commit ad19b3fd0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 89 additions and 36 deletions

View file

@ -0,0 +1,2 @@
pub(crate) mod target_version;
pub(crate) mod verbosity;

View file

@ -0,0 +1,34 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum VerbosityLevel {
Info,
Debug,
Trace,
}
/// Logging flags to `#[command(flatten)]` into your CLI
#[derive(clap::Args, Debug, Clone, Default)]
#[command(about = None, long_about = None)]
pub(crate) struct Verbosity {
#[arg(
long,
short = 'v',
help = "Use verbose output (or `-vv` and `-vvv` for more verbose output)",
action = clap::ArgAction::Count,
global = true,
)]
verbose: u8,
}
impl Verbosity {
/// Returns the verbosity level based on the number of `-v` flags.
///
/// Returns `None` if the user did not specify any verbosity flags.
pub(crate) fn level(&self) -> Option<VerbosityLevel> {
match self.verbose {
0 => None,
1 => Some(VerbosityLevel::Info),
2 => Some(VerbosityLevel::Debug),
_ => Some(VerbosityLevel::Trace),
}
}
}

View file

@ -17,9 +17,10 @@ use red_knot::workspace::WorkspaceMetadata;
use ruff_db::program::{ProgramSettings, SearchPathSettings}; use ruff_db::program::{ProgramSettings, SearchPathSettings};
use ruff_db::system::{OsSystem, System, SystemPathBuf}; use ruff_db::system::{OsSystem, System, SystemPathBuf};
use self::target_version::TargetVersion; use cli::target_version::TargetVersion;
use cli::verbosity::{Verbosity, VerbosityLevel};
mod target_version; mod cli;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
@ -43,14 +44,19 @@ struct Args {
help = "Custom directory to use for stdlib typeshed stubs" help = "Custom directory to use for stdlib typeshed stubs"
)] )]
custom_typeshed_dir: Option<SystemPathBuf>, custom_typeshed_dir: Option<SystemPathBuf>,
#[arg( #[arg(
long, long,
value_name = "PATH", value_name = "PATH",
help = "Additional path to use as a module-resolution source (can be passed multiple times)" help = "Additional path to use as a module-resolution source (can be passed multiple times)"
)] )]
extra_search_path: Vec<SystemPathBuf>, extra_search_path: Vec<SystemPathBuf>,
#[arg(long, help = "Python version to assume when resolving types", default_value_t = TargetVersion::default(), value_name="VERSION")] #[arg(long, help = "Python version to assume when resolving types", default_value_t = TargetVersion::default(), value_name="VERSION")]
target_version: TargetVersion, target_version: TargetVersion,
#[clap(flatten)]
verbosity: Verbosity,
} }
#[allow( #[allow(
@ -60,16 +66,18 @@ struct Args {
clippy::dbg_macro clippy::dbg_macro
)] )]
pub fn main() -> anyhow::Result<()> { pub fn main() -> anyhow::Result<()> {
countme::enable(true);
setup_tracing();
let Args { let Args {
current_directory, current_directory,
custom_typeshed_dir, custom_typeshed_dir,
extra_search_path: extra_paths, extra_search_path: extra_paths,
target_version, target_version,
verbosity,
} = Args::parse_from(std::env::args().collect::<Vec<_>>()); } = Args::parse_from(std::env::args().collect::<Vec<_>>());
let verbosity = verbosity.level();
countme::enable(verbosity == Some(VerbosityLevel::Trace));
setup_tracing(verbosity);
let cwd = if let Some(cwd) = current_directory { let cwd = if let Some(cwd) = current_directory {
let canonicalized = cwd.as_utf8_path().canonicalize_utf8().unwrap(); let canonicalized = cwd.as_utf8_path().canonicalize_utf8().unwrap();
SystemPathBuf::from_utf8_path_buf(canonicalized) SystemPathBuf::from_utf8_path_buf(canonicalized)
@ -97,7 +105,7 @@ pub fn main() -> anyhow::Result<()> {
// cache and load the cache if it exists. // cache and load the cache if it exists.
let mut db = RootDatabase::new(workspace_metadata, program_settings, system); let mut db = RootDatabase::new(workspace_metadata, program_settings, system);
let (main_loop, main_loop_cancellation_token) = MainLoop::new(); let (main_loop, main_loop_cancellation_token) = MainLoop::new(verbosity);
// Listen to Ctrl+C and abort the watch mode. // Listen to Ctrl+C and abort the watch mode.
let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token)); let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token));
@ -126,18 +134,19 @@ pub fn main() -> anyhow::Result<()> {
} }
struct MainLoop { struct MainLoop {
orchestrator_sender: crossbeam_channel::Sender<OrchestratorMessage>, verbosity: Option<VerbosityLevel>,
main_loop_receiver: crossbeam_channel::Receiver<MainLoopMessage>, orchestrator: crossbeam_channel::Sender<OrchestratorMessage>,
receiver: crossbeam_channel::Receiver<MainLoopMessage>,
} }
impl MainLoop { impl MainLoop {
fn new() -> (Self, MainLoopCancellationToken) { fn new(verbosity: Option<VerbosityLevel>) -> (Self, MainLoopCancellationToken) {
let (orchestrator_sender, orchestrator_receiver) = crossbeam_channel::bounded(1); let (orchestrator_sender, orchestrator_receiver) = crossbeam_channel::bounded(1);
let (main_loop_sender, main_loop_receiver) = crossbeam_channel::bounded(1); let (main_loop_sender, main_loop_receiver) = crossbeam_channel::bounded(1);
let mut orchestrator = Orchestrator { let mut orchestrator = Orchestrator {
receiver: orchestrator_receiver, receiver: orchestrator_receiver,
sender: main_loop_sender.clone(), main_loop: main_loop_sender.clone(),
revision: 0, revision: 0,
}; };
@ -147,8 +156,9 @@ impl MainLoop {
( (
Self { Self {
orchestrator_sender, verbosity,
main_loop_receiver, orchestrator: orchestrator_sender,
receiver: main_loop_receiver,
}, },
MainLoopCancellationToken { MainLoopCancellationToken {
sender: main_loop_sender, sender: main_loop_sender,
@ -158,29 +168,27 @@ impl MainLoop {
fn file_changes_notifier(&self) -> FileChangesNotifier { fn file_changes_notifier(&self) -> FileChangesNotifier {
FileChangesNotifier { FileChangesNotifier {
sender: self.orchestrator_sender.clone(), sender: self.orchestrator.clone(),
} }
} }
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
fn run(self, db: &mut RootDatabase) { fn run(self, db: &mut RootDatabase) {
self.orchestrator_sender self.orchestrator.send(OrchestratorMessage::Run).unwrap();
.send(OrchestratorMessage::Run)
.unwrap();
for message in &self.main_loop_receiver { for message in &self.receiver {
tracing::trace!("Main Loop: Tick"); tracing::trace!("Main Loop: Tick");
match message { match message {
MainLoopMessage::CheckWorkspace { revision } => { MainLoopMessage::CheckWorkspace { revision } => {
let db = db.snapshot(); let db = db.snapshot();
let sender = self.orchestrator_sender.clone(); let orchestrator = self.orchestrator.clone();
// Spawn a new task that checks the workspace. This needs to be done in a separate thread // Spawn a new task that checks the workspace. This needs to be done in a separate thread
// to prevent blocking the main loop here. // to prevent blocking the main loop here.
rayon::spawn(move || { rayon::spawn(move || {
if let Ok(result) = db.check() { if let Ok(result) = db.check() {
sender orchestrator
.send(OrchestratorMessage::CheckCompleted { .send(OrchestratorMessage::CheckCompleted {
diagnostics: result, diagnostics: result,
revision, revision,
@ -195,10 +203,14 @@ impl MainLoop {
} }
MainLoopMessage::CheckCompleted(diagnostics) => { MainLoopMessage::CheckCompleted(diagnostics) => {
eprintln!("{}", diagnostics.join("\n")); eprintln!("{}", diagnostics.join("\n"));
eprintln!("{}", countme::get_all()); if self.verbosity == Some(VerbosityLevel::Trace) {
eprintln!("{}", countme::get_all());
}
} }
MainLoopMessage::Exit => { MainLoopMessage::Exit => {
eprintln!("{}", countme::get_all()); if self.verbosity == Some(VerbosityLevel::Trace) {
eprintln!("{}", countme::get_all());
}
return; return;
} }
} }
@ -208,7 +220,7 @@ impl MainLoop {
impl Drop for MainLoop { impl Drop for MainLoop {
fn drop(&mut self) { fn drop(&mut self) {
self.orchestrator_sender self.orchestrator
.send(OrchestratorMessage::Shutdown) .send(OrchestratorMessage::Shutdown)
.unwrap(); .unwrap();
} }
@ -240,7 +252,7 @@ impl MainLoopCancellationToken {
struct Orchestrator { struct Orchestrator {
/// Sends messages to the main loop. /// Sends messages to the main loop.
sender: crossbeam_channel::Sender<MainLoopMessage>, main_loop: crossbeam_channel::Sender<MainLoopMessage>,
/// Receives messages from the main loop. /// Receives messages from the main loop.
receiver: crossbeam_channel::Receiver<OrchestratorMessage>, receiver: crossbeam_channel::Receiver<OrchestratorMessage>,
revision: usize, revision: usize,
@ -252,7 +264,7 @@ impl Orchestrator {
while let Ok(message) = self.receiver.recv() { while let Ok(message) = self.receiver.recv() {
match message { match message {
OrchestratorMessage::Run => { OrchestratorMessage::Run => {
self.sender self.main_loop
.send(MainLoopMessage::CheckWorkspace { .send(MainLoopMessage::CheckWorkspace {
revision: self.revision, revision: self.revision,
}) })
@ -265,7 +277,7 @@ impl Orchestrator {
} => { } => {
// Only take the diagnostics if they are for the latest revision. // Only take the diagnostics if they are for the latest revision.
if self.revision == revision { if self.revision == revision {
self.sender self.main_loop
.send(MainLoopMessage::CheckCompleted(diagnostics)) .send(MainLoopMessage::CheckCompleted(diagnostics))
.unwrap(); .unwrap();
} else { } else {
@ -313,8 +325,8 @@ impl Orchestrator {
}, },
default(std::time::Duration::from_millis(10)) => { default(std::time::Duration::from_millis(10)) => {
// No more file changes after 10 ms, send the changes and schedule a new analysis // No more file changes after 10 ms, send the changes and schedule a new analysis
self.sender.send(MainLoopMessage::ApplyChanges(changes)).unwrap(); self.main_loop.send(MainLoopMessage::ApplyChanges(changes)).unwrap();
self.sender.send(MainLoopMessage::CheckWorkspace { revision: self.revision}).unwrap(); self.main_loop.send(MainLoopMessage::CheckWorkspace { revision: self.revision}).unwrap();
return; return;
} }
} }
@ -349,7 +361,14 @@ enum OrchestratorMessage {
FileChanges(Vec<FileWatcherChange>), FileChanges(Vec<FileWatcherChange>),
} }
fn setup_tracing() { fn setup_tracing(verbosity: Option<VerbosityLevel>) {
let trace_level = match verbosity {
None => Level::WARN,
Some(VerbosityLevel::Info) => Level::INFO,
Some(VerbosityLevel::Debug) => Level::DEBUG,
Some(VerbosityLevel::Trace) => Level::TRACE,
};
let subscriber = Registry::default().with( let subscriber = Registry::default().with(
tracing_tree::HierarchicalLayer::default() tracing_tree::HierarchicalLayer::default()
.with_indent_lines(true) .with_indent_lines(true)
@ -359,9 +378,7 @@ fn setup_tracing() {
.with_targets(true) .with_targets(true)
.with_writer(|| Box::new(std::io::stderr())) .with_writer(|| Box::new(std::io::stderr()))
.with_timer(Uptime::default()) .with_timer(Uptime::default())
.with_filter(LoggingFilter { .with_filter(LoggingFilter { trace_level }),
trace_level: Level::TRACE,
}),
); );
tracing::subscriber::set_global_default(subscriber).unwrap(); tracing::subscriber::set_global_default(subscriber).unwrap();

View file

@ -125,11 +125,11 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
} = program.search_paths(db.upcast()); } = program.search_paths(db.upcast());
if let Some(custom_typeshed) = custom_typeshed { if let Some(custom_typeshed) = custom_typeshed {
tracing::debug!("Custom typeshed directory: {custom_typeshed}"); tracing::info!("Custom typeshed directory: {custom_typeshed}");
} }
if !extra_paths.is_empty() { if !extra_paths.is_empty() {
tracing::debug!("extra search paths: {extra_paths:?}"); tracing::info!("extra search paths: {extra_paths:?}");
} }
let current_directory = db.system().current_directory(); let current_directory = db.system().current_directory();
@ -174,7 +174,7 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step // TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
let target_version = program.target_version(db.upcast()); let target_version = program.target_version(db.upcast());
tracing::debug!("Target version: {target_version}"); tracing::info!("Target version: {target_version}");
// Filter out module resolution paths that point to the same directory on disk (the same invariant maintained by [`sys.path` at runtime]). // Filter out module resolution paths that point to the same directory on disk (the same invariant maintained by [`sys.path` at runtime]).
// (Paths may, however, *overlap* -- e.g. you could have both `src/` and `src/foo` // (Paths may, however, *overlap* -- e.g. you could have both `src/` and `src/foo`

View file

@ -58,7 +58,7 @@ impl Files {
/// ///
/// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory. /// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory.
/// In these cases, a file with status [`FileStatus::Deleted`] is returned. /// In these cases, a file with status [`FileStatus::Deleted`] is returned.
#[tracing::instrument(level = "debug", skip(self, db), ret)] #[tracing::instrument(level = "trace", skip(self, db), ret)]
fn system(&self, db: &dyn Db, path: &SystemPath) -> File { fn system(&self, db: &dyn Db, path: &SystemPath) -> File {
let absolute = SystemPath::absolute(path, db.system().current_directory()); let absolute = SystemPath::absolute(path, db.system().current_directory());
let absolute = FilePath::System(absolute); let absolute = FilePath::System(absolute);
@ -102,7 +102,7 @@ impl Files {
/// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path /// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path
/// exists and `None` otherwise. /// exists and `None` otherwise.
#[tracing::instrument(level = "debug", skip(self, db), ret)] #[tracing::instrument(level = "trace", skip(self, db), ret)]
fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<File> { fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<File> {
let file = match self let file = match self
.inner .inner