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() {