diff --git a/libdnf5/module/module_sack.cpp b/libdnf5/module/module_sack.cpp index e2af5429de..e57a2c9e74 100644 --- a/libdnf5/module/module_sack.cpp +++ b/libdnf5/module/module_sack.cpp @@ -23,6 +23,7 @@ along with libdnf. If not, see . #include "module/module_metadata.hpp" #include "module/module_sack_impl.hpp" #include "solv/solv_map.hpp" +#include "utils/string.hpp" #include "libdnf5/base/base.hpp" #include "libdnf5/base/base_weak.hpp" @@ -777,14 +778,90 @@ bool ModuleSack::Impl::enable(const std::string & name, const std::string & stre } +// module dict { name : {stream : [solvable id] } +static std::map>> create_module_dict( + const ModuleQuery & module_query) { + std::map>> module_dict; + for (const auto & module_item : module_query.list()) { + module_dict[module_item.get_name()][module_item.get_stream()].push_back(module_item.get_id().id); + } + return module_dict; +} + + +std::vector ModuleSack::Impl::prune_module_dict( + std::map>> & module_dict) { + // Vector of module names with multiple streams to enable + std::vector multiple_stream_modules; + + for (auto & module_dict_iter : module_dict) { + auto name = module_dict_iter.first; + auto & stream_dict = module_dict_iter.second; + auto module_status = module_db->get_status(name); + + // Multiple streams match the requested spec + if (stream_dict.size() > 1) { + // Get stream that is either enabled (for ENABLED module), or default (otherwise) + std::string enabled_or_default_stream; + if (module_status == ModuleStatus::ENABLED) { + enabled_or_default_stream = module_db->get_enabled_stream(name); + } else { + enabled_or_default_stream = module_sack->get_default_stream(name); + } + + // Module doesn't have any enabled nor default stream + if (enabled_or_default_stream.empty()) { + multiple_stream_modules.emplace_back(name); + continue; + } + + // The enabled or default stream is not one of the possible streams + if (stream_dict.find(enabled_or_default_stream) == stream_dict.end()) { + multiple_stream_modules.emplace_back(name); + continue; + } + + // Remove all streams except for the enabled_or_default_stream + for (auto iter = stream_dict.begin(); iter != stream_dict.end();) { + if (iter->first != enabled_or_default_stream) { + stream_dict.erase(iter++); + } else { + ++iter; + } + } + } + } + return multiple_stream_modules; +} + + bool ModuleSack::Impl::enable(const std::string & module_spec, bool count) { module_db->initialize(); + // For the given module_spec, create a dict { name : {stream : [solvable id] } + auto module_dict = create_module_dict(module_spec_to_query(base, module_spec)); + // Keep only enabled or default streams if possible + auto multiple_stream_modules = prune_module_dict(module_dict); + // If there are any modules with multiple streams to be enabled, throw an error + if (!multiple_stream_modules.empty()) { + throw EnableMultipleStreamsError( + M_("Cannot enable multiple streams of one module at the same time. Affected modules: {}"), + utils::string::join(multiple_stream_modules, ", ")); + } + bool changed = false; libdnf5::solv::IdQueue queue; - for (const auto & module_item : module_spec_to_query(base, module_spec)) { - queue.push_back(module_item.get_id().id); - changed |= enable(module_item.get_name(), module_item.get_stream(), count); + for (const auto & module_dict_iter : module_dict) { + std::string name = module_dict_iter.first; + for (const auto & stream_dict_iter : module_dict_iter.second) { + // Enable this stream + changed |= enable(name, stream_dict_iter.first, count); + // Create a queue of ids for the stream to be enabled, because it better matches user requirements + // than just "module(name:stream)" provides. (E.g. user might have requested specific context or version.) + for (const auto & id : stream_dict_iter.second) { + queue.push_back(id); + } + } } modules_to_enable.push_back(queue); diff --git a/libdnf5/module/module_sack_impl.hpp b/libdnf5/module/module_sack_impl.hpp index d16406276d..1ea45dff8c 100644 --- a/libdnf5/module/module_sack_impl.hpp +++ b/libdnf5/module/module_sack_impl.hpp @@ -188,6 +188,12 @@ class ModuleSack::Impl { /// @return If platform id was detected, it returns a pair where the first item is the platform /// module name and second is the platform stream. Otherwise std::nullopt is returned. std::optional> detect_platform_name_and_stream() const; + + /// @brief Keep only one stream for each module. If more than one stream is originally there, keep only + // the enabled or default one if possible. + /// @return List of module names with multiple streams and no enabled or default one. + std::vector prune_module_dict( + std::map>> & module_dict); }; inline const std::vector> & ModuleSack::Impl::get_modules() {