From 663665b2957b6a9d0ebeae9e61d9fb1a495195f9 Mon Sep 17 00:00:00 2001 From: David Gilligan-Cook Date: Tue, 30 Apr 2024 10:48:24 -0700 Subject: [PATCH] Adds --stop-on-block/--stop option to solver. This stops the solver when it first reaches a BLOCKED state and would step back. This is useful when working out missing dependencies, e.g. when porting a package between OSes or python versions. Signed-off-by: David Gilligan-Cook --- crates/spk-cli/common/src/flags.rs | 5 +++ crates/spk-solve/src/io.rs | 52 ++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/crates/spk-cli/common/src/flags.rs b/crates/spk-cli/common/src/flags.rs index 0bc441c4d..adffbd54b 100644 --- a/crates/spk-cli/common/src/flags.rs +++ b/crates/spk-cli/common/src/flags.rs @@ -1078,6 +1078,10 @@ pub struct DecisionFormatterSettings { /// comparing them. #[clap(long)] compare_solvers: bool, + + /// Stop the solver the first time it is BLOCKED. + #[clap(long, alias = "stop")] + stop_on_block: bool, } impl DecisionFormatterSettings { @@ -1115,6 +1119,7 @@ impl DecisionFormatterSettings { .with_status_bar(self.status_bar) .with_solver_to_run(self.solver_to_run.into()) .with_search_space_size(self.show_search_size) + .with_stop_on_block(self.stop_on_block) .with_compare_solvers(self.compare_solvers); Ok(builder) } diff --git a/crates/spk-solve/src/io.rs b/crates/spk-solve/src/io.rs index 3281d1367..f47dafa3f 100644 --- a/crates/spk-solve/src/io.rs +++ b/crates/spk-solve/src/io.rs @@ -89,11 +89,22 @@ pub fn format_note(note: &Note) -> String { } } -pub fn change_is_relevant_at_verbosity(change: &Change, verbosity: u8) -> bool { +pub fn change_is_relevant_at_verbosity( + change: &Change, + verbosity: u8, + stop_on_block: bool, +) -> bool { use Change::*; let relevant_level = match change { SetPackage(_) => 1, - StepBack(_) => 1, + StepBack(_) => { + // More relevant when stop-on-block is enabled. + if stop_on_block { + 0 + } else { + 1 + } + } RequestPackage(_) => 2, RequestVar(_) => 2, SetOptions(_) => 3, @@ -201,12 +212,22 @@ where pub fn iter(&mut self) -> impl Stream> + '_ { stream! { + let mut stop_because_blocked = false; 'outer: loop { if let Some(next) = self.output_queue.pop_front() { yield Ok(next); continue 'outer; } + // Check if the solver should stop because the last + // decision was a step-back (BLOCKED) with stop-on-block set + if stop_because_blocked { + yield(Err(Error::SolverInterrupted( + "hit BLOCKED state with '--stop-on-block' enabled.".to_string(), + ))); + continue 'outer; + } + while self.output_queue.is_empty() { // First, check if the solver has taken too long or the // user has interrupted the solver @@ -309,13 +330,17 @@ where StepBack(spk_solve_graph::StepBack { destination, .. }) => { fill = "!"; new_level = destination.state_depth; + // Ensures the solver will stop before the next + // decision because of this (BLOCKED) change, if + // stop-on-block is enabled. + stop_because_blocked = self.settings.stop_on_block; } _ => { fill = "."; } } - if !change_is_relevant_at_verbosity(change, self.verbosity) { + if !change_is_relevant_at_verbosity(change, self.verbosity, self.settings.stop_on_block) { continue; } @@ -429,6 +454,7 @@ pub struct DecisionFormatterBuilder { solver_to_run: MultiSolverKind, show_search_space_size: bool, compare_solvers: bool, + stop_on_block: bool, } impl Default for DecisionFormatterBuilder { @@ -447,6 +473,7 @@ impl Default for DecisionFormatterBuilder { solver_to_run: MultiSolverKind::Unchanged, show_search_space_size: false, compare_solvers: false, + stop_on_block: false, } } } @@ -535,6 +562,11 @@ impl DecisionFormatterBuilder { self } + pub fn with_stop_on_block(&mut self, enable: bool) -> &mut Self { + self.stop_on_block = enable; + self + } + pub fn build(&self) -> DecisionFormatter { let too_long_seconds = if self.verbosity_increase_seconds == 0 || (self.verbosity_increase_seconds > self.timeout && self.timeout > 0) @@ -575,6 +607,7 @@ impl DecisionFormatterBuilder { solver_to_run: self.solver_to_run.clone(), show_search_space_size: self.show_search_space_size, compare_solvers: self.compare_solvers, + stop_on_block: self.stop_on_block, }, } } @@ -634,6 +667,7 @@ pub(crate) struct DecisionFormatterSettings { pub(crate) solver_to_run: MultiSolverKind, pub(crate) show_search_space_size: bool, pub(crate) compare_solvers: bool, + pub(crate) stop_on_block: bool, } enum LoopOutcome { @@ -723,6 +757,7 @@ impl DecisionFormatter { solver_to_run: MultiSolverKind::Unchanged, show_search_space_size: false, compare_solvers: false, + stop_on_block: false, }, } } @@ -1089,7 +1124,7 @@ impl DecisionFormatter { self.send_sentry_warning_message( &runtime.solver, solve_time, - if mesg.contains("by user") { + if mesg.contains("by user") || mesg.contains("--stop-on-block") { SentryWarning::SolverInterruptedByUser } else { SentryWarning::SolverInterruptedByTimeout @@ -1097,8 +1132,13 @@ impl DecisionFormatter { ); output_location.output_message(format!("{}", mesg.yellow())); - output_location - .output_message(self.format_solve_stats(&runtime.solver, solve_time)); + // Show the solver stats after an interruption, unless + // it was due to being BLOCKED with stop-on-block + // being set without report-time also being set. + if !self.settings.stop_on_block || self.settings.report_time { + output_location + .output_message(self.format_solve_stats(&runtime.solver, solve_time)); + } if self.settings.show_search_space_size { // This solution is likely to be partial, empty,