diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90feeb3..99a4f21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Git Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install Dependencies run: rake build_dependencies:install diff --git a/package/yast2-pkg-bindings.changes b/package/yast2-pkg-bindings.changes index 7010c4f..1afeb7b 100644 --- a/package/yast2-pkg-bindings.changes +++ b/package/yast2-pkg-bindings.changes @@ -1,3 +1,13 @@ +------------------------------------------------------------------- +Tue Jan 16 14:32:15 UTC 2024 - Ladislav Slezák + +- Added new pkg calls for managing and resolving the solver + conflits. Added PkgSolveProblems(), PkgSetSolveSolutions() + and PkgResetSolveSolutions() calls. (gh#openSUSE/agama#944) +- Libzypp 17.31.26+ uses "N/A" for unknown repository types + instead of "NONE", support both cases (bsc#1218859) +- 5.0.3 + ------------------------------------------------------------------- Wed Sep 20 15:54:13 UTC 2023 - Ladislav Slezák diff --git a/package/yast2-pkg-bindings.spec b/package/yast2-pkg-bindings.spec index 6bf0cf7..dabf8c0 100644 --- a/package/yast2-pkg-bindings.spec +++ b/package/yast2-pkg-bindings.spec @@ -17,7 +17,7 @@ Name: yast2-pkg-bindings -Version: 5.0.2 +Version: 5.0.3 Release: 0 Summary: YaST2 - Package Manager Access License: GPL-2.0-only diff --git a/smoke_test_run.rb b/smoke_test_run.rb index 6636423..a00a4b2 100755 --- a/smoke_test_run.rb +++ b/smoke_test_run.rb @@ -32,6 +32,10 @@ def check_y2log # no idea why that happens at Travis, let's ignore that... y2log.reject! { |l| l =~ /error: Interrupted system call/ } + # ignore solver errors, we create them deliberately + y2log.reject! { |l| l =~ /Solverrun finished with an ERROR/ } + y2log.reject! { |l| l =~ /PkgSolve: [0-9]+ packages failed/ } + if !y2log.empty? puts "Found errors in #{log_file}:" puts y2log @@ -150,7 +154,7 @@ def check_y2log removed = Yast::Pkg.ResolvableRemove("yast2-core", :package) raise "Cannot select yast2-core to uninstall" unless removed -puts "OK (yast2-core selected to uninstall" +puts "OK (yast2-core selected to uninstall)" selected_packages = Yast::Pkg.Resolvables({kind: :package, status: :selected}, [:name]) raise "A package is selected to install (???)" unless selected_packages.empty? @@ -161,6 +165,11 @@ def check_y2log raise "yast2-core not selected to uninstall" unless removed_packages.include?({"name" => "yast2-core"}) puts "OK (yast2-core selected to uninstall)" +Yast::Pkg.ResolvableNeutral("yast2-core", :package, false) # false = no force +removed_packages = Yast::Pkg.Resolvables({kind: :package, status: :removed}, [:name]) +raise "A package still selected to uninstall" if !removed_packages.empty? +puts "OK (yast2-core not selected to uninstall)" + installed_products = Yast::Pkg.Resolvables({kind: :product, status: :installed}, [:name, :display_name]) available_products = Yast::Pkg.Resolvables({kind: :product, status: :available}, [:name, :display_name]) selected_products = Yast::Pkg.Resolvables({kind: :product, status: :selected}, [:name, :display_name]) @@ -172,5 +181,42 @@ def check_y2log puts "Found #{available_products.size} available products: #{available_products.map{|p| p["display_name"]}}" puts "OK" +puts "Selecting opera package to install" +Yast::Pkg.ResolvableInstall("opera", :package) +solved = Yast::Pkg.PkgSolve(false) +# the OSS repository providing the X11 dependencies is disabled, the package +# cannot be installed without them +raise "A solver problem is expected" if solved + +errors = Yast::Pkg.PkgSolveErrors +raise "One solver error is expected" if errors != 1 +puts "OK (found one dependency error)" + +problems = Yast::Pkg.PkgSolveProblems +raise "One solver problem is expected" if problems.size != 1 +puts "OK (found one dependency problem)" + +problem = problems.first +# select the "do not install" solution +solution = problem["solutions"].find{|s| s["description"].match(/do not install/i)} + +puts "Selecting a conflict solution" +# set the selected solution +Yast::Pkg.PkgSetSolveSolutions( + [ + { + "description" => problem["description"], + "details" => problem["details"], + "solution_description" => solution["description"], + "solution_details" => solution["details"] + } + ] +) + +# run the solver again +solved = Yast::Pkg.PkgSolve(false) +raise "A solver problem still exists" if !solved +puts "OK (no dependency error anymore)" + # scan y2log for errors check_y2log diff --git a/src/Package.cc b/src/Package.cc index aa60cd5..9ac2fb2 100644 --- a/src/Package.cc +++ b/src/Package.cc @@ -2112,6 +2112,242 @@ PkgFunctions::PkgSolveErrors() return YCPVoid(); } +/** + @builtin PkgSolveProblems + @short Returns details about the solver problems + + Only valid after a call of PkgSolve/PkgSolveCheckTargetOnly that returned false, + otherwise the result might not correspond to the current state. + + @note The texts in the result are translated and respect the current locale. + + @return list of solver problems with suggested solutions + + @example Returned data structure + [ + { + # description of the problem + "description" => , + # details of the problem, can be a multiline text, can an empty string + "details" => , + # list of proposed solutions, only one of then can be applied + "solutions" => + [ + { + # description of the solution + "description" => , + # details of the solution, can be a multiline text, can an empty string + "details" => + } + ] + } + ] + + @example Example data + [ + { + "description" => "the to be installed yast2-theme-SLE-4.5.0-lp154.2.1.noarch " \ + "conflicts with 'namespace:otherproviders(yast2-branding)' provided by the " \ + "installed yast2-theme-5.0.1-lp154.2.1.noarch", + "details" => "", + "solutions" => + [ + { + "description" => "Following actions will be done:", + "details" => + "deinstallation of yast2-theme-5.0.1-lp154.2.1.noarch\n" + + "deinstallation of yast2-theme-oxygen-5.0.1-lp154.2.1.noarch\n" + + "deinstallation of yast2-theme-breeze-5.0.1-lp154.2.1.noarch" + }, + { + "description" => "do not install yast2-theme-SLE-4.5.0-lp154.2.1.noarch", + "details" => "" + } + ] + } + ] +*/ +YCPValue +PkgFunctions::PkgSolveProblems() +{ + YCPList ret; + auto problems = zypp_ptr()->resolver()->problems(); + for_( problem, problems.begin(), problems.end() ) + { + YCPMap problem_item; + // details about the problem + problem_item->add(YCPString("description"), YCPString((*problem)->description())); + problem_item->add(YCPString("details"), YCPString((*problem)->details())); + + YCPList solutions; + for_( solution, (*problem)->solutions().begin(), (*problem)->solutions().end() ) + { + YCPMap solution_item; + // details about the solution + solution_item->add(YCPString("description"), YCPString((*solution)->description())); + solution_item->add(YCPString("details"), YCPString((*solution)->details())); + + solutions->add(solution_item); + } + problem_item->add(YCPString("solutions"), solutions); + + ret->add(problem_item); + } + return ret; +} + +/** + * @builtin PkgSetSolveSolutions + * + * Apply the proposed solver solutions. The input data is similar to the + * PkgSolveProblems() return value, just the solution data is a simple map, + * see the example. + * + * @see PkgSolveProblems + * + * @note When there are too many conflicts it might be better to apply the + * solutions iteratively in small batches instead of solving all problems at once. + * Some solutions might actually fix multiple problems. + * + * @note You need to run the solver after applying the solutions. + * + * @example Input data structure + * [ + * { + * # description of the problem (from the PkgSolveProblems() data) + * "description" => , + * # details of the problem (from the PkgSolveProblems() data) + * "details" => , + * # description of the solution (from the PkgSolveProblems() data) + * "solution_description" => , + * # details of the solution (from the PkgSolveProblems() data) + * "solution_details" => + * } + * ] + * + * @example Example call + * # see the PkgSolveProblems() example + * PkgSetSolveSolutions([ + * { + * "description" => "the to be installed yast2-theme-SLE-4.5.0-lp154.2.1.noarch " \ + * "a word with 'namespace:otherproviders(yast2-branding)' provided by " \ + * "the installed yast2-theme-5.0.1-lp154.2.1.noarch", + * "details" => "", + * "solution_description" => "Following actions will be done:", + * "solution_details" => "deinstallation of yast2-theme-5.0.1-lp154.2.1.noarch\n" \ + * "deinstallation of yast2-theme-oxygen-5.0.1-lp154.2.1.noarch\n" \ + * "deinstallation of yast2-theme-breeze-5.0.1-lp154.2.1.noarch" + * } + * ]) + * + * @param solutions List of requested solutions + * @return YCPValue + */ +YCPValue +PkgFunctions::PkgSetSolveSolutions(const YCPList& solutions) +{ + y2milestone("Requested %d solutions", solutions->size()); + + zypp::ProblemSolutionList user_solutions; + bool error = false; + for (int index = 0; index < solutions->size(); index++ ) + { + if (solutions->value(index).isNull() || !solutions->value(index)->isMap()) + { + y2error("Pkg::PkgSetSolveSolutions: entry not a map at index %d", index); + error = true; + continue; + } + + YCPMap solution = solutions->value(index)->asMap(); + + // check missing keys + if (solution->value(YCPString("description")).isNull() + || solution->value(YCPString("details")).isNull() + || solution->value(YCPString("solution_description")).isNull() + || solution->value(YCPString("solution_details")).isNull()) + { + y2error("Pkg::PkgSetSolveSolutions: missing required map key at index %d", index); + error = true; + continue; + } + + // check string keys + if (!solution->value(YCPString("description"))->isString() + || !solution->value(YCPString("details"))->isString() + || !solution->value(YCPString("solution_description"))->isString() + || !solution->value(YCPString("solution_details"))->isString()) + { + y2error("Pkg::PkgSetSolveSolutions: non-string map value at index %d", index); + error = true; + continue; + } + + // get the keys + std::string problem_description = solution->value(YCPString("description"))->asString()->value(); + std::string problem_details = solution->value(YCPString("details"))->asString()->value(); + std::string solution_description = solution->value(YCPString("solution_description"))->asString()->value(); + std::string solution_details = solution->value(YCPString("solution_details"))->asString()->value(); + + auto problems = zypp_ptr()->resolver()->problems(); + bool found = false; + for_( problem, problems.begin(), problems.end() ) + { + // find the problem + if ((*problem)->description() == problem_description && (*problem)->details() == problem_details) + { + for_( solution, (*problem)->solutions().begin(), (*problem)->solutions().end() ) + { + // find the solution + if ((*solution)->description() == solution_description && (*solution)->details() == solution_details) + { + // remember the found solution + user_solutions.push_back(*solution); + found = true; + break; + } + } + break; + } + } + + // the specified solution was not found + if (!found) error = true; + } + + y2milestone("Applying %ld solutions", user_solutions.size()); + zypp::getZYpp()->resolver()->applySolutions(user_solutions); + + return YCPBoolean(!error); +} + +/** + * @builtin PkgResetSolveSolutions + * + * Resets all applied solver solutions. + * + * @note You need to run the solver after applying the solutions. + * + * @note The changes done by the previous solutions are kept. It only resets + * the internal libzypp lists so the solutions are not applied again, but the + * already done changes are not reverted. E.g. when a solution was to uninstall + * a package then the packages is still selected to uninstall even after resetting + * the applied solutions. + * + * @note The applied solutions are done on the user level, technically it is + * possible to revert them manually if there are no other user changes. + * + * @param solutions List of applied solutions + * + * @return YCPVoid + */ +YCPValue +PkgFunctions::PkgResetSolveSolutions() +{ + zypp::getZYpp()->resolver()->undo(); + return YCPVoid(); +} + namespace { /////////////////////////////////////////////////////////////////// diff --git a/src/PkgFunctions.h b/src/PkgFunctions.h index 57fd2b0..f8b0499 100644 --- a/src/PkgFunctions.h +++ b/src/PkgFunctions.h @@ -695,6 +695,12 @@ class PkgFunctions YCPBoolean PkgSolveCheckTargetOnly (); /* TYPEINFO: integer()*/ YCPValue PkgSolveErrors (); + /* TYPEINFO: list>()*/ + YCPValue PkgSolveProblems (); + /* TYPEINFO: boolean(list>)*/ + YCPValue PkgSetSolveSolutions (const YCPList& solutions); + /* TYPEINFO: void()*/ + YCPValue PkgResetSolveSolutions (); YCPValue CommitHelper(const zypp::ZYppCommitPolicy *policy); /* TYPEINFO: list(integer)*/ YCPValue PkgCommit (const YCPInteger& medianr); diff --git a/src/Source_Misc.cc b/src/Source_Misc.cc index 92c225d..eaf41c2 100644 --- a/src/Source_Misc.cc +++ b/src/Source_Misc.cc @@ -119,6 +119,8 @@ std::string PkgFunctions::zypp2yastType(const std::string &type) type_conversion_table["yast2"] = "YaST"; type_conversion_table["plaindir"] = "Plaindir"; type_conversion_table["NONE"] = "NONE"; + // since libzypp-17.31.26 + type_conversion_table["N/A"] = "NONE"; } std::map::const_iterator it = type_conversion_table.find(type);