diff --git a/dnf5/commands/debuginfo-install/debuginfo-install.cpp b/dnf5/commands/debuginfo-install/debuginfo-install.cpp new file mode 100644 index 000000000..6dee2887b --- /dev/null +++ b/dnf5/commands/debuginfo-install/debuginfo-install.cpp @@ -0,0 +1,79 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#include "debuginfo-install.hpp" + +#include + +#include + + +namespace dnf5 { + + +using namespace libdnf5::cli; + + +void DebuginfoInstallCommand::set_parent_command() { + auto * arg_parser_parent_cmd = get_session().get_argument_parser().get_root_command(); + auto * arg_parser_this_cmd = get_argument_parser_command(); + arg_parser_parent_cmd->register_command(arg_parser_this_cmd); + arg_parser_parent_cmd->get_group("software_management_commands").register_argument(arg_parser_this_cmd); +} + +void DebuginfoInstallCommand::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + auto & cmd = *get_argument_parser_command(); + + get_argument_parser_command()->set_description("Install debuginfo packages."); + + allow_erasing = std::make_unique(*this); + auto skip_broken = std::make_unique(*this); + auto skip_unavailable = std::make_unique(*this); + + patterns_to_debuginfo_install_options = parser.add_new_values(); + auto patterns_arg = parser.add_new_positional_arg( + "patterns", + ArgumentParser::PositionalArg::AT_LEAST_ONE, + parser.add_init_value(std::unique_ptr(new libdnf5::OptionString(nullptr))), + patterns_to_debuginfo_install_options); + patterns_arg->set_description("Patterns"); + cmd.register_positional_arg(patterns_arg); +} + +void DebuginfoInstallCommand::configure() { + auto & context = get_context(); + context.set_load_system_repo(true); + context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); + context.get_base().get_repo_sack()->enable_debug_repos(); +} + +void DebuginfoInstallCommand::run() { + auto goal = get_context().get_goal(); + auto settings = libdnf5::GoalJobSettings(); + goal->set_allow_erasing(allow_erasing->get_value()); + + for (const auto & pattern : *patterns_to_debuginfo_install_options) { + auto option = dynamic_cast(pattern.get()); + goal->add_debug_install(option->get_value(), settings); + } +} + +} // namespace dnf5 diff --git a/dnf5/commands/debuginfo-install/debuginfo-install.hpp b/dnf5/commands/debuginfo-install/debuginfo-install.hpp new file mode 100644 index 000000000..1294c78a8 --- /dev/null +++ b/dnf5/commands/debuginfo-install/debuginfo-install.hpp @@ -0,0 +1,47 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef DNF5_COMMANDS_DEBUGINFO_INSTALL_DEBUGINFO_INSTALL_HPP +#define DNF5_COMMANDS_DEBUGINFO_INSTALL_DEBUGINFO_INSTALL_HPP + +#include +#include + +namespace dnf5 { + + +class DebuginfoInstallCommand : public Command { +public: + explicit DebuginfoInstallCommand(Context & context) : Command(context, "debuginfo-install") {} + void set_parent_command() override; + void set_argument_parser() override; + void configure() override; + void run() override; + + std::vector> * patterns_to_debuginfo_install_options{nullptr}; + + std::unique_ptr allow_erasing; +}; + + +} // namespace dnf5 + + +#endif // DNF5_COMMANDS_DEBUGINFO_INSTALL_DEBUGINFO_INSTALL_HPP diff --git a/dnf5/main.cpp b/dnf5/main.cpp index c8bdcf7dc..314ac2c53 100644 --- a/dnf5/main.cpp +++ b/dnf5/main.cpp @@ -23,6 +23,7 @@ along with libdnf. If not, see . #include "commands/check-upgrade/check-upgrade.hpp" #include "commands/check/check.hpp" #include "commands/clean/clean.hpp" +#include "commands/debuginfo-install/debuginfo-install.hpp" #include "commands/distro-sync/distro-sync.hpp" #include "commands/downgrade/downgrade.hpp" #include "commands/download/download.hpp" @@ -671,6 +672,7 @@ static void add_commands(Context & context) { context.add_and_initialize_command(std::make_unique(context)); context.add_and_initialize_command(std::make_unique(context)); context.add_and_initialize_command(std::make_unique(context)); + context.add_and_initialize_command(std::make_unique(context)); context.add_and_initialize_command(std::make_unique(context)); context.add_and_initialize_command(std::make_unique(context)); context.add_and_initialize_command(std::make_unique(context)); diff --git a/doc/changes.rst b/doc/changes.rst index 0de9dcd62..8e9130724 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -172,6 +172,9 @@ Changes to individual commands * Existing repository files are not modified; drop-in override files are created instead. * See the :ref:`config-manager documentation ` for more information. +``debuginfo-install`` + * Now does not support `autoupdate` functionality. The permanent enablement of debug repositories can be achieved + using `config-manager` command. ``distro-sync`` * Now when any argument doesn't match an installed package, DNF5 fails. The behavior can be modified by the ``--skip-unavailable`` option. * Dropped ``distrosync`` and ``distribution-synchronization`` aliases. diff --git a/doc/commands/debuginfo-install.rst b/doc/commands/debuginfo-install.rst new file mode 100644 index 000000000..8af7cb58d --- /dev/null +++ b/doc/commands/debuginfo-install.rst @@ -0,0 +1,72 @@ +.. + Copyright Contributors to the libdnf project. + + This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + + Libdnf is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Libdnf is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libdnf. If not, see . + +.. _debuginfo_install_command_ref-label: + +########################## + Debuginfo-install Command +########################## + +Synopsis +======== + +``dnf5 debuginfo-install [options] ...`` + + +Description +=========== + +Install the associated debuginfo packages for a given package specification. +The command temporary enables corresponding debug repository for each enabled +repository using following algorithm. When enabled repository ID has suffix `-rpm` +then it enables -debug-rpms. When enabled repository does not have suffix `-rpm` +it enables repository using pattern -debuginfo. + +When regular upgrade of debuginfo packages is expected, then it requires enabling +of debug repository permanently using `config-manager` command. + +Arguments +========= + +```` + The pattern to install the associated debuginfo package for. + +Options +======= + +``--allowerasing`` + | Allow erasing of installed packages to resolve any potential dependency problems. + +``--skip-broken`` + | Resolve any dependency problems by removing packages that are causing problems from the transaction. + +``--skip-unavailable`` + | Allow skipping packages that are not available in repositories. All available packages will be installed. + + +Examples +======== + +``dnf debuginfo-install foobar`` + Install the debuginfo packages for the foobar package. + +``dnf upgrade --enablerepo=*-debuginfo -debuginfo`` + Upgrade debuginfo package of a . + +``dnf upgrade --enablerepo=*-debuginfo "*-debuginfo"`` + Upgrade all debuginfo packages. 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/include/libdnf5/repo/repo_sack.hpp b/include/libdnf5/repo/repo_sack.hpp index d81e536f1..549576d89 100644 --- a/include/libdnf5/repo/repo_sack.hpp +++ b/include/libdnf5/repo/repo_sack.hpp @@ -142,6 +142,12 @@ class LIBDNF_API RepoSack : public sack::Sack { /// @since 5.0 void enable_source_repos(); + /// For each enabled repository enable corresponding debug repository. + /// When repo ID has suffix -rpm then it enables -debug-rpms + /// otherwise it enables -debuginfo + /// @since 5.2.4 + void enable_debug_repos(); + ~RepoSack(); private: 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 ""; } diff --git a/libdnf5/repo/repo_sack.cpp b/libdnf5/repo/repo_sack.cpp index 5667697f3..61bbfb4b7 100644 --- a/libdnf5/repo/repo_sack.cpp +++ b/libdnf5/repo/repo_sack.cpp @@ -784,6 +784,28 @@ void RepoSack::enable_source_repos() { } } +void RepoSack::enable_debug_repos() { + RepoQuery enabled_repos(p_impl->base); + enabled_repos.filter_enabled(true); + for (const auto & repo : enabled_repos) { + RepoQuery debug_query(p_impl->base); + // There is no way how to find source (or debuginfo) repository for + // given repo. This is only guessing according to the current practice: + auto repoid = repo->get_id(); + if (libdnf5::utils::string::ends_with(repoid, "-rpms")) { + debug_query.filter_id(repoid.substr(0, repoid.size() - 5) + "-debug-rpms"); + } else { + debug_query.filter_id(repoid + "-debuginfo"); + } + for (auto & debug_repo : debug_query) { + if (!debug_repo->is_enabled()) { + // TODO(mblaha): log debug repo enabling + debug_repo->enable(); + } + } + } +} + void RepoSack::internalize_repos() { auto rq = RepoQuery(p_impl->base); for (auto & repo : rq.get_data()) {