Skip to content

Commit

Permalink
dnf5: Print command line hints after resolve failure
Browse files Browse the repository at this point in the history
Print to the user relevant hints about what command line options could
be used to fix transaction resolving issues.
It can suggest using --skip-unavailable, --no-best,
--allowerasing, --skip-broken, and --setopt=optional_metadata_types=filelists

Fixes: #548
  • Loading branch information
m-blaha committed Mar 22, 2024
1 parent e8c5901 commit 8683bb4
Showing 1 changed file with 108 additions and 2 deletions.
110 changes: 108 additions & 2 deletions dnf5/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,106 @@ static void print_new_leaves(Context & context) {
} // namespace dnf5


// Recursively search command and its parents whether given argument is configured
static bool has_named_arg(libdnf5::cli::ArgumentParser::Command * command, std::string_view arg_name) {
while (command) {
for (const auto & arg : command->get_named_args()) {
if (arg->get_long_name() == arg_name) {
return true;
}
}
command = command->get_parent() == command ? nullptr : command->get_parent();
}
return false;
}

static void print_resolve_hints(dnf5::Context & context) {
auto & conf = context.base.get_config();
std::vector<std::string> hints;
auto transaction_problems = context.get_transaction()->get_problems();
auto * command = context.get_selected_command()->get_argument_parser_command();

// hint --skip-unavailable if a package was not found
if ((transaction_problems & libdnf5::GoalProblem::NOT_FOUND) == libdnf5::GoalProblem::NOT_FOUND &&
!conf.get_skip_unavailable_option().get_value()) {
const std::string_view arg{"--skip-unavailable"};
if (has_named_arg(command, arg.substr(2))) {
hints.emplace_back(libdnf5::utils::sformat(_("{} to skip unavailable packages"), arg));
}
}

if ((transaction_problems & libdnf5::GoalProblem::SOLVER_ERROR) == libdnf5::GoalProblem::SOLVER_ERROR) {
bool conflict = false;
bool broken_file_dep = false;
bool best = false;
// walk through all solver problem to detect a conflict, missing file dependency and best
for (const auto & resolve_log : context.get_transaction()->get_resolve_logs()) {
if (resolve_log.get_problem() == libdnf5::GoalProblem::SOLVER_ERROR) {
for (const auto & solv_prob : resolve_log.get_solver_problems()->get_problems()) {
for (const auto & [rule, params] : solv_prob) {
switch (rule) {
case libdnf5::ProblemRules::RULE_PKG_CONFLICTS:
// TODO(mblaha): we should check whether the conflict involves an installed package (missing API).
// https://github.com/rpm-software-management/dnf5/issues/1324
conflict = true;
break;
case libdnf5::ProblemRules::RULE_PKG_NOTHING_PROVIDES_DEP:
if (params.size() >= 1) {
if (params[0].starts_with('/')) {
broken_file_dep = true;
}
}
break;
case libdnf5::ProblemRules::RULE_BEST_1:
case libdnf5::ProblemRules::RULE_BEST_2:
best = true;
break;
default:
break;
}
}
}
}
}

if (conf.get_best_option().get_value() && best) {
const std::string_view arg{"--no-best"};
hints.emplace_back(
libdnf5::utils::sformat(_("{} to not limit the transaction to the best candidates"), arg));
}

if (!context.get_goal()->get_allow_erasing() && conflict) {
const std::string_view arg{"--allowerasing"};
if (has_named_arg(command, arg.substr(2))) {
hints.emplace_back(
libdnf5::utils::sformat(_("{} to allow erasing of installed packages to resolve problems"), arg));
}
}

if (broken_file_dep) {
const std::string_view arg{"--setopt=optional_metadata_types=filelists"};
auto optional_metadata = conf.get_optional_metadata_types_option().get_value();
if (!optional_metadata.contains("filelists")) {
hints.emplace_back(libdnf5::utils::sformat(_("{} to load additional filelists metadata"), arg));
}
}

if (!conf.get_skip_broken_option().get_value()) {
const std::string_view arg{"--skip-broken"};
if (has_named_arg(command, arg.substr(2))) {
hints.emplace_back(libdnf5::utils::sformat(_("{} to skip uninstallable packages"), arg));
}
}
}

if (hints.size() > 0) {
std::cerr << _("You can try to add to command line:") << std::endl;
for (const auto & hint : hints) {
std::cerr << " " << hint << std::endl;
}
}
}

int main(int argc, char * argv[]) try {
// Creates a vector of loggers with one circular memory buffer logger
std::vector<std::unique_ptr<libdnf5::Logger>> loggers;
Expand Down Expand Up @@ -1110,6 +1210,7 @@ int main(int argc, char * argv[]) try {
command->load_additional_packages();

command->run();

if (auto goal = context.get_goal(false)) {
context.set_transaction(goal->resolve());

Expand Down Expand Up @@ -1150,14 +1251,19 @@ int main(int argc, char * argv[]) try {
context.download_and_run(*context.get_transaction());
}
} catch (libdnf5::cli::GoalResolveError & ex) {
std::cerr << ex.what() << std::endl;
if (!any_repos_from_system_configuration && base.get_config().get_installroot_option().get_value() != "/" &&
!base.get_config().get_use_host_config_option().get_value()) {
std::cout
std::cerr
<< "No repositories were loaded from the installroot. To use the configuration and repositories "
"of the host system, pass --use-host-config."
<< std::endl;
} else {
if (context.get_transaction() != nullptr) {
// download command can throw GoalResolveError without context.transaction being set
print_resolve_hints(context);
}
}
std::cerr << ex.what() << std::endl;
return static_cast<int>(libdnf5::cli::ExitCode::ERROR);
} catch (libdnf5::cli::ArgumentParserError & ex) {
std::cerr << ex.what() << _(". Add \"--help\" for more information about the arguments.") << std::endl;
Expand Down

0 comments on commit 8683bb4

Please sign in to comment.