mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
[red-knot] Add verbosity argument to CLI (#12404)
This commit is contained in:
parent
a62e2d2000
commit
ad19b3fd0e
6 changed files with 89 additions and 36 deletions
2
crates/red_knot/src/cli/mod.rs
Normal file
2
crates/red_knot/src/cli/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub(crate) mod target_version;
|
||||||
|
pub(crate) mod verbosity;
|
34
crates/red_knot/src/cli/verbosity.rs
Normal file
34
crates/red_knot/src/cli/verbosity.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue