diff --git a/include/libdnf5/base/goal.hpp b/include/libdnf5/base/goal.hpp index a4a711c0d..fbf2702e1 100644 --- a/include/libdnf5/base/goal.hpp +++ b/include/libdnf5/base/goal.hpp @@ -65,6 +65,12 @@ class LIBDNF_API Goal { /// @param settings A structure to override default goal settings. void add_install(const std::string & spec, const libdnf5::GoalJobSettings & settings = libdnf5::GoalJobSettings()); + /// Process spec to install related debug info and debug source packages + /// @param spec A string with installation spec + /// @param settings A structure to override default goal settings. + void add_debug_install( + const std::string & spec, const libdnf5::GoalJobSettings & settings = libdnf5::GoalJobSettings()); + /// High level API for an artifact upgrade. See `add_install()` for details. /// @param spec A string with upgrade spec /// @param settings A structure to override default goal settings. diff --git a/include/libdnf5/base/goal_elements.hpp b/include/libdnf5/base/goal_elements.hpp index a38a5802c..608ba3bf5 100644 --- a/include/libdnf5/base/goal_elements.hpp +++ b/include/libdnf5/base/goal_elements.hpp @@ -152,7 +152,8 @@ enum class GoalAction { REPLAY_REINSTALL, REPLAY_REASON_CHANGE, REPLAY_REASON_OVERRIDE, - REVERT_COMPS_UPGRADE + REVERT_COMPS_UPGRADE, + INSTALL_DEBUG }; /// Convert GoalAction enum to user-readable string diff --git a/libdnf5/base/goal.cpp b/libdnf5/base/goal.cpp index 6415d0bdd..ccd3561b1 100644 --- a/libdnf5/base/goal.cpp +++ b/libdnf5/base/goal.cpp @@ -48,6 +48,7 @@ along with libdnf. If not, see . #include #include #include +#include namespace { @@ -74,6 +75,46 @@ void add_obsoletes_to_data(const libdnf5::rpm::PackageQuery & base_query, libdnf data |= obsoletes_query; } +/// Add install job of debug packages for installed packages to Goal +/// +/// @return bool False when no match for any package +bool install_debug_from_packages( + libdnf5::BaseWeakPtr base, + std::string & debug_name, + const std::vector & packages, + libdnf5::solv::IdQueue & result_queue, + libdnf5::rpm::solv::GoalPrivate & goal, + bool skip_broken, + bool best, + bool clean_requirements_on_remove) { + std::vector nevras; + for (const auto & package : packages) { + std::string nevra(debug_name); + nevra.append("-"); + nevra.append(package.get_epoch()); + nevra.append(":"); + nevra.append(package.get_version()); + nevra.append("-"); + nevra.append(package.get_release()); + nevra.append("."); + nevra.append(package.get_arch()); + nevras.emplace_back(std::move(nevra)); + } + libdnf5::rpm::PackageQuery query(base); + query.filter_nevra(nevras); + if (query.empty()) { + return false; + } + libdnf5::solv::IdQueue install_queue; + for (auto package : query) { + Id id = package.get_id().id; + install_queue.push_back(id); + result_queue.push_back(id); + } + goal.add_install(install_queue, skip_broken, best, clean_requirements_on_remove); + return true; +} + } // namespace @@ -114,6 +155,8 @@ class Goal::Impl { std::pair add_install_to_goal( base::Transaction & transaction, GoalAction action, const std::string & spec, GoalJobSettings & settings); + std::pair add_install_debug_to_goal( + base::Transaction & transaction, const std::string & spec, GoalJobSettings & settings); void add_provide_install_to_goal(const std::string & spec, GoalJobSettings & settings); GoalProblem add_reinstall_to_goal( base::Transaction & transaction, const std::string & spec, GoalJobSettings & settings); @@ -248,6 +291,10 @@ void Goal::add_install(const std::string & spec, const libdnf5::GoalJobSettings p_impl->add_spec(GoalAction::INSTALL, spec, settings); } +void Goal::add_debug_install(const std::string & spec, const libdnf5::GoalJobSettings & settings) { + p_impl->add_spec(GoalAction::INSTALL_DEBUG, spec, settings); +} + void Goal::add_upgrade(const std::string & spec, const libdnf5::GoalJobSettings & settings, bool minimal) { p_impl->add_spec(minimal ? GoalAction::UPGRADE_MINIMAL : GoalAction::UPGRADE, spec, settings); } @@ -555,6 +602,11 @@ GoalProblem Goal::Impl::add_specs_to_goal(base::Transaction & transaction) { settings.resolve_best(cfg_main), settings.resolve_clean_requirements_on_remove()); } break; + case GoalAction::INSTALL_DEBUG: { + auto [problem, idqueue] = add_install_debug_to_goal(transaction, spec, settings); + rpm_goal.add_transaction_user_installed(idqueue); + ret |= problem; + } break; case GoalAction::INSTALL_OR_REINSTALL: { libdnf_throw_assertion("Unsupported action \"INSTALL_OR_REINSTALL\""); } @@ -1450,6 +1502,178 @@ std::pair Goal::Impl::add_install_to_goal( return {GoalProblem::NO_PROBLEM, result_queue}; } +std::pair Goal::Impl::add_install_debug_to_goal( + base::Transaction & transaction, const std::string & spec, GoalJobSettings & settings) { + auto & cfg_main = base->get_config(); + bool skip_unavailable = settings.resolve_skip_unavailable(cfg_main); + auto log_level = skip_unavailable ? libdnf5::Logger::Level::WARNING : libdnf5::Logger::Level::ERROR; + bool best = settings.resolve_best(cfg_main); + bool clean_requirements_on_remove = settings.resolve_clean_requirements_on_remove(); + bool skip_broken = settings.resolve_skip_broken(cfg_main); + + libdnf5::solv::IdQueue result_queue; + + rpm::PackageQuery query(base); + auto nevra_pair = query.resolve_pkg_spec(spec, settings, false); + if (!nevra_pair.first) { + auto problem = transaction.p_impl->report_not_found(GoalAction::INSTALL_DEBUG, spec, settings, log_level); + if (skip_unavailable) { + return {GoalProblem::NO_PROBLEM, result_queue}; + } else { + return {problem, result_queue}; + } + } + // Use a package name as a key + std::unordered_map> candidate_map; + std::unordered_map> available; + for (auto package : query) { + if (package.is_installed()) { + candidate_map[package.get_name()].push_back(package); + } else { + available[package.get_name()].push_back(package); + } + } + // installed versions of packages have priority, replace / add them to the m + candidate_map.merge(available); + + const std::string debug_suffix{"-debuginfo"}; + const std::string debug_source_suffix{"-debugsource"}; + + // Remove debuginfo packages if their base packages are in the query. + // They can get there through globs and they break the installation + // of debug packages with the same version as the installed base + // packages. If the base package of a debuginfo package is not in + // the query, the user specified a debug package on the command + // line. We don't want to ignore those, so we will install them. + // But, in this case the version will not be matched to the + // installed version of the base package, as that would require + // another query and is further complicated if the user specifies a + // version themselves etc. + for (auto iter = candidate_map.begin(); iter != candidate_map.end();) { + auto name = iter->first; + if (libdnf5::utils::string::ends_with(name, debug_suffix)) { + name.resize(name.size() - debug_suffix.size()); + auto iterator = candidate_map.find(name); + if (iterator != candidate_map.end()) { + // remove debuginfo when base name is in candidate_map + candidate_map.erase(iter++); + continue; + } + // Install debuginfo and remove it from candiddates (to prevent double testing) + libdnf5::solv::IdQueue install_queue; + for (auto package : iter->second) { + Id pkg_id = package.get_id().id; + install_queue.push_back(pkg_id); + result_queue.push_back(pkg_id); + } + rpm_goal.add_install(install_queue, skip_broken, best, clean_requirements_on_remove); + candidate_map.erase(iter++); + continue; + } else if (libdnf5::utils::string::ends_with(name, debug_source_suffix)) { + name.resize(name.size() - debug_source_suffix.size()); + auto iterator = candidate_map.find(name); + if (iterator != candidate_map.end()) { + candidate_map.erase(iter++); + continue; + } + // Install debugsource and remove it from candiddates (to prevent double testing) + libdnf5::solv::IdQueue install_queue; + for (auto package : iter->second) { + Id pkg_id = package.get_id().id; + install_queue.push_back(pkg_id); + result_queue.push_back(pkg_id); + } + rpm_goal.add_install(install_queue, skip_broken, best, clean_requirements_on_remove); + candidate_map.erase(iter++); + continue; + } + ++iter; + } + for (auto & item : candidate_map) { + auto & first_pkg = *item.second.begin(); + auto debug_name = first_pkg.get_debuginfo_name(); + auto debuginfo_name_of_source = first_pkg.get_debuginfo_name_of_source(); + auto debug_source_name = first_pkg.get_debugsource_name(); + + if (first_pkg.is_installed()) { + std::unordered_map> arch_map; + for (auto & package : item.second) { + arch_map[package.get_arch()].push_back(package); + } + for (auto arch_item : arch_map) { + if (!install_debug_from_packages( + base, + debug_name, + arch_item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // Because there is no debuginfo for the package, lets install deguginfo of the source package + if (!install_debug_from_packages( + base, + debuginfo_name_of_source, + arch_item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // TODO(jmracek) report when proper debug RPM is not found + } + } + if (!install_debug_from_packages( + base, + debug_source_name, + arch_item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // TODO(jmracek) report when proper debug RPM is not found + } + } + continue; + } + if (!install_debug_from_packages( + base, + debug_name, + item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // Because there is no debuginfo for the package, lets install deguginfo of the source package + if (!install_debug_from_packages( + base, + debuginfo_name_of_source, + item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // TODO(jmracek) report when proper debug RPM is not found + } + } + if (!install_debug_from_packages( + base, + debug_source_name, + item.second, + result_queue, + rpm_goal, + skip_broken, + best, + clean_requirements_on_remove)) { + // TODO(jmracek) report when proper debug RPM is not found + } + } + return {GoalProblem::NO_PROBLEM, result_queue}; +} + void Goal::Impl::add_provide_install_to_goal(const std::string & spec, GoalJobSettings & settings) { auto & cfg_main = base->get_config(); bool skip_broken = settings.resolve_skip_broken(cfg_main); diff --git a/libdnf5/base/goal_elements.cpp b/libdnf5/base/goal_elements.cpp index 46455801a..9d4461a84 100644 --- a/libdnf5/base/goal_elements.cpp +++ b/libdnf5/base/goal_elements.cpp @@ -396,6 +396,8 @@ std::string goal_action_to_string(GoalAction action) { return _("Reason override"); case GoalAction::REVERT_COMPS_UPGRADE: return _("Revert comps upgrade"); + case GoalAction::INSTALL_DEBUG: + return _("Install debug RPMs"); } return ""; }