diff --git a/libmamba/include/mamba/api/install.hpp b/libmamba/include/mamba/api/install.hpp index 0306e2bcda..a23176aef7 100644 --- a/libmamba/include/mamba/api/install.hpp +++ b/libmamba/include/mamba/api/install.hpp @@ -52,6 +52,9 @@ namespace mamba bool freeze_installed ) -> solver::Request; + /** + * Append file/global/python pins to a solver request and apply solver flags. + */ void add_pins_to_request( solver::Request& request, const Context& ctx, diff --git a/libmamba/include/mamba/core/pinning.hpp b/libmamba/include/mamba/core/pinning.hpp index 5e67f7f01d..a24e72610d 100644 --- a/libmamba/include/mamba/core/pinning.hpp +++ b/libmamba/include/mamba/core/pinning.hpp @@ -22,7 +22,7 @@ namespace mamba } std::vector - python_pin(PrefixData& prefix_data, const std::vector& specs); + python_pin(const PrefixData& prefix_data, const std::vector& specs); std::vector file_pins(const fs::u8path& file); } diff --git a/libmamba/src/api/install.cpp b/libmamba/src/api/install.cpp index bf09575d70..668be448a0 100644 --- a/libmamba/src/api/install.cpp +++ b/libmamba/src/api/install.cpp @@ -22,11 +22,8 @@ #include "mamba/core/package_database_loader.hpp" #include "mamba/core/pinning.hpp" #include "mamba/core/transaction.hpp" -#include "mamba/core/util_os.hpp" -#include "mamba/core/virtual_packages.hpp" #include "mamba/download/downloader.hpp" #include "mamba/fs/filesystem.hpp" -#include "mamba/solver/libsolv/solver.hpp" #include "mamba/specs/match_spec.hpp" #include "mamba/util/path_manip.hpp" #include "mamba/util/string.hpp" @@ -314,14 +311,7 @@ namespace mamba void install_revision(Configuration& config, std::size_t revision) { - config.at("use_target_prefix_fallback").set_value(true); - config.at("use_default_prefix_fallback").set_value(true); - config.at("use_root_prefix_fallback").set_value(true); - config.at("target_prefix_checks") - .set_value( - MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX - | MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX - ); + configure_common_prefix_fallbacks(config, /* create_base= */ false); config.load(); auto& context = config.context(); @@ -334,15 +324,7 @@ namespace mamba { auto& ctx = config.context(); - config.at("create_base").set_value(true); - config.at("use_target_prefix_fallback").set_value(true); - config.at("use_default_prefix_fallback").set_value(true); - config.at("use_root_prefix_fallback").set_value(true); - config.at("target_prefix_checks") - .set_value( - MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX - | MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX - ); + configure_common_prefix_fallbacks(config, /* create_base= */ true); config.load(); auto& install_specs = config.at("specs").value>(); @@ -481,8 +463,11 @@ namespace mamba { using Request = solver::Request; - const auto estimated_jobs_count = request.jobs.size() - + (!no_pin) * ctx.pinned_packages.size() + !no_py_pin; + const auto& pinned_packages = ctx.pinned_packages; + const auto& solver_flags = ctx.solver_flags; + + const auto estimated_jobs_count = request.jobs.size() + (!no_pin) * pinned_packages.size() + + !no_py_pin; request.jobs.reserve(estimated_jobs_count); if (!no_pin) { @@ -496,7 +481,7 @@ namespace mamba } ); } - for (const auto& pin : ctx.pinned_packages) + for (const auto& pin : pinned_packages) { request.jobs.emplace_back( Request::Pin{ @@ -522,6 +507,7 @@ namespace mamba ); } } + request.flags = solver_flags; } void print_request_pins_to(const solver::Request& request, std::ostream& out) @@ -549,36 +535,6 @@ namespace mamba namespace { - void print_activation_message(const Context& ctx) - { - // Check that the target prefix is not active before printing the activation message - if (util::get_env("CONDA_PREFIX") != ctx.prefix_params.target_prefix) - { - // Get the name of the executable used directly from the command. - const auto executable = get_self_exe_path().stem().string(); - - // Get the name of the environment - const auto environment = env_name( - ctx.envs_dirs, - ctx.prefix_params.root_prefix, - ctx.prefix_params.target_prefix - ); - - Console::stream() << "\nTo activate this environment, use:\n\n" - " " - << executable << " activate " << environment - << "\n\n" - "Or to execute a single command in this environment, use:\n\n" - " " - << executable - << " run " - // Use -n or -p depending on if the env_name is a full prefix or - // just a name. - << (environment == ctx.prefix_params.target_prefix ? "-p " : "-n ") - << environment << " mycommand\n"; - } - } - void install_specs_impl( Context& ctx, ChannelContext& channel_context, @@ -598,54 +554,10 @@ namespace mamba auto& env_vars = config.at("spec_file_env_vars").value>(); auto& no_env = config.at("no_env").value(); - if (ctx.prefix_params.target_prefix.empty()) - { - throw std::runtime_error("No active target prefix"); - } - if (!fs::exists(ctx.prefix_params.target_prefix) && create_env == false) - { - throw std::runtime_error( - fmt::format("Prefix does not exist at: {}", ctx.prefix_params.target_prefix.string()) - ); - } - - MultiPackageCache package_caches{ ctx.pkgs_dirs, ctx.validation_params }; - - populate_context_channels_from_specs(raw_specs, ctx); - - if (ctx.channels.empty() && !ctx.offline) - { - LOG_WARNING << "No 'channels' specified"; - } - - solver::libsolv::Database db{ - channel_context.params(), - { - ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Libsolv, - }, - }; - add_logger_to_database(db); - - std::vector root_packages = ctx.repodata_use_shards - ? build_sharded_root_packages(raw_specs) - : std::vector{}; - - auto maybe_load = load_channels(ctx, channel_context, db, package_caches, root_packages); - if (!maybe_load) - { - throw maybe_load.error(); - } - - auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!maybe_prefix_data) - { - throw maybe_prefix_data.error(); - } - PrefixData& prefix_data = maybe_prefix_data.value(); - - load_installed_packages_in_database(ctx, db, prefix_data); + validate_target_prefix_and_channels(ctx, create_env); + auto [db, package_caches] = prepare_solver_context(ctx, channel_context, raw_specs); + auto prefix_data = load_prefix_data_and_installed(ctx, channel_context, db); auto request = create_install_request( prefix_data, @@ -653,8 +565,8 @@ namespace mamba InstallRequestOptions{ .freeze_installed = freeze_installed, .prefix_data_interoperability = ctx.prefix_data_interoperability } ); + add_pins_to_request(request, ctx, prefix_data, raw_specs, no_pin, no_py_pin); - request.flags = ctx.solver_flags; { auto out = Console::stream(); @@ -662,67 +574,43 @@ namespace mamba // Console stream prints on destruction } - if (Console::can_report_status()) - { - Console::instance().print( - fmt::format("{:<85} {:>20}", "Resolving Environment", "⧖ Starting") - ); - } - auto outcome = solver::libsolv::Solver() - .solve( - db, - request, - ctx.experimental_matchspec_parsing - ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Mixed - ) - .value(); - if (Console::can_report_status()) - { - Console::instance().print( - fmt::format("{:<85} {:>20}", "Resolving Environment", "✔ Done") - ); - } + auto outcome = solve_request_with_status(ctx.experimental_matchspec_parsing, db, request); - if (auto* unsolvable = std::get_if(&outcome)) - { - unsolvable->explain_problems_to( + if (handle_unsolvable_with_retry( + outcome, + ctx.graphics_params.palette, + ctx.output_params.json, + retry_clean_cache, + is_retry, + ctx.local_repodata_ttl, db, - LOG_ERROR, + /* retry_fn= */ + [&]() + { + bool retry = true; + install_specs_impl( + ctx, + channel_context, + config, + raw_specs, + create_env, + remove_prefix_on_failure, + retry + ); + }, + /* pre_throw_hint= */ + [&]() { - /* .unavailable= */ ctx.graphics_params.palette.failure, - /* .available= */ ctx.graphics_params.palette.success, + if (freeze_installed) + { + Console::instance().print( + "Possible hints:\n - 'freeze_installed' is turned on\n" + ); + } } - ); - if (retry_clean_cache && !is_retry) - { - ctx.local_repodata_ttl = 2; - bool retry = true; - return install_specs_impl( - ctx, - channel_context, - config, - raw_specs, - create_env, - remove_prefix_on_failure, - retry - ); - } - if (freeze_installed) - { - Console::instance().print("Possible hints:\n - 'freeze_installed' is turned on\n"); - } - - if (ctx.output_params.json) - { - Console::instance().json_write( - { { "success", false }, { "solver_problems", unsolvable->problems(db) } } - ); - } - throw mamba_error( - "Could not solve for environment specs", - mamba_error_code::satisfiablitity_error - ); + )) + { + return; } std::vector locks; @@ -734,73 +622,52 @@ namespace mamba Console::instance().json_write({ { "success", true } }); - // The point here is to delete the database before executing the transaction. - // The database can have high memory impact, since installing packages - // requires downloading, extracting, and launching Python interpreters for - // creating ``.pyc`` files. - // Ideally this whole function should be properly refactored and the transaction itself - // should not need the database. - auto trans = [&](auto database) - { - return MTransaction( // - ctx, - database, - request, - std::get(outcome), - package_caches - ); - }(std::move(db)); - - if (ctx.output_params.json) - { - trans.log_json(); - } + auto trans = make_transaction_from_solution(ctx, std::move(db), request, outcome, package_caches); Console::stream(); - - if (trans.prompt(ctx, channel_context)) - { - if (create_env && !ctx.dry_run) - { - detail::create_target_directory(ctx, ctx.prefix_params.target_prefix); - } - - detail::populate_state_file(ctx.prefix_params.target_prefix, env_vars, no_env); - - trans.execute(ctx, channel_context, prefix_data); - - // Print activation message only if the environment is freshly created - if (create_env && !ctx.dry_run) + bool transaction_accepted = execute_transaction( + trans, + ctx, + channel_context, + prefix_data, + /* before_execute= */ + [&]() { - print_activation_message(ctx); - } - - if (!ctx.dry_run) + if (create_env && !ctx.dry_run) + { + detail::create_target_directory(ctx, ctx.prefix_params.target_prefix); + } + detail::populate_state_file(ctx.prefix_params.target_prefix, env_vars, no_env); + }, + /* after_execute= */ + [&]() { - for (auto other_spec : config.at("others_pkg_mgrs_specs") - .value>()) + if (create_env && !ctx.dry_run) { - auto result = install_for_other_pkgmgr(ctx, other_spec, pip::Update::No); - if (!result) - { - static_assert( - std::is_base_of_v - ); - throw std::move(result).error(); - } + print_activation_message(ctx); } - } - } - else - { - // Aborting new env creation - // but the directory was already created because of `store_platform_config` call - // => Remove the created directory - if (remove_prefix_on_failure && fs::is_directory(ctx.prefix_params.target_prefix)) + }, + /* on_abort= */ + [&]() { - fs::remove_all(ctx.prefix_params.target_prefix); + // Aborting new env creation + // but the directory was already created because of `store_platform_config` call + // => Remove the created directory + if (remove_prefix_on_failure && fs::is_directory(ctx.prefix_params.target_prefix)) + { + fs::remove_all(ctx.prefix_params.target_prefix); + } } - } + ); + const auto other_specs = config.at("others_pkg_mgrs_specs") + .value>(); + execute_other_pkg_managers_if_needed( + transaction_accepted, + ctx.dry_run, + other_specs, + ctx, + pip::Update::No + ); } } @@ -839,33 +706,15 @@ namespace mamba bool remove_prefix_on_failure ) { - solver::libsolv::Database database{ - channel_context.params(), - { - ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Libsolv, - }, - }; - add_logger_to_database(database); + auto database = make_solver_database(ctx.experimental_matchspec_parsing, channel_context); init_channels(ctx, channel_context); // Some use cases provide a list of explicit specs, but an empty // context. We need to create channels from the specs to be able // to download packages. init_channels_from_package_urls(ctx, channel_context, specs); - auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!maybe_prefix_data) - { - // TODO: propagate tl::expected mechanism - throw std::runtime_error( - fmt::format("could not load prefix data: {}", maybe_prefix_data.error().what()) - ); - } - PrefixData& prefix_data = maybe_prefix_data.value(); - MultiPackageCache pkg_caches(ctx.pkgs_dirs, ctx.validation_params); - - load_installed_packages_in_database(ctx, database, prefix_data); + auto prefix_data = load_prefix_data_and_installed(ctx, channel_context, database); std::vector others; // Note that the Transaction will gather the Solvables, @@ -879,46 +728,47 @@ namespace mamba lock_pkgs.push_back(LockFile(c)); } - if (ctx.output_params.json) - { - transaction.log_json(); - } - - if (transaction.prompt(ctx, channel_context)) - { - if (create_env && !ctx.dry_run) - { - detail::create_target_directory(ctx, ctx.prefix_params.target_prefix); - } - - transaction.execute(ctx, channel_context, prefix_data); - - // Print activation message only if the environment is freshly created - if (create_env && !ctx.dry_run) + bool transaction_accepted = execute_transaction( + transaction, + ctx, + channel_context, + prefix_data, + /* before_execute= */ + [&]() { - print_activation_message(ctx); - } - - for (auto other_spec : others) + if (create_env && !ctx.dry_run) + { + detail::create_target_directory(ctx, ctx.prefix_params.target_prefix); + } + }, + /* after_execute= */ + [&]() { - auto result = install_for_other_pkgmgr(ctx, other_spec, pip::Update::No); - if (!result.has_value()) + // Print activation message only if the environment is freshly created + if (create_env && !ctx.dry_run) { - static_assert(std::is_base_of_v); - throw std::move(result).error(); + print_activation_message(ctx); } - } - } - else - { - // Aborting new env creation - // but the directory was already created because of `store_platform_config` call - // => Remove the created directory - if (remove_prefix_on_failure && fs::is_directory(ctx.prefix_params.target_prefix)) + }, + /* on_abort= */ + [&]() { - fs::remove_all(ctx.prefix_params.target_prefix); + // Aborting new env creation + // but the directory was already created because of `store_platform_config` call + // => Remove the created directory + if (remove_prefix_on_failure && fs::is_directory(ctx.prefix_params.target_prefix)) + { + fs::remove_all(ctx.prefix_params.target_prefix); + } } - } + ); + execute_other_pkg_managers_if_needed( + transaction_accepted, + ctx.dry_run, + others, + ctx, + pip::Update::No + ); } } diff --git a/libmamba/src/api/remove.cpp b/libmamba/src/api/remove.cpp index 985d82a740..3714675f56 100644 --- a/libmamba/src/api/remove.cpp +++ b/libmamba/src/api/remove.cpp @@ -9,13 +9,14 @@ #include "mamba/core/channel_context.hpp" #include "mamba/core/output.hpp" #include "mamba/core/package_cache.hpp" -#include "mamba/core/package_database_loader.hpp" #include "mamba/core/prefix_data.hpp" #include "mamba/core/transaction.hpp" #include "mamba/solver/libsolv/repo_info.hpp" #include "mamba/solver/libsolv/solver.hpp" #include "mamba/solver/request.hpp" +#include "utils.hpp" + namespace mamba { RemoveResult remove(Configuration& config, int flags) @@ -124,48 +125,13 @@ namespace mamba bool force ) { - if (ctx.prefix_params.target_prefix.empty()) - { - LOG_ERROR << "No active target prefix."; - throw std::runtime_error("Aborted."); - } - - auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!exp_prefix_data) - { - // TODO: propagate tl::expected mechanism - throw std::runtime_error(exp_prefix_data.error().what()); - } - PrefixData& prefix_data = exp_prefix_data.value(); - - solver::libsolv::Database database{ - channel_context.params(), - { - ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Libsolv, - }, - }; - add_logger_to_database(database); - load_installed_packages_in_database(ctx, database, prefix_data); + validate_target_prefix_and_channels(ctx, /* create_env= */ false); + auto database = make_solver_database(ctx.experimental_matchspec_parsing, channel_context); + auto prefix_data = load_prefix_data_and_installed(ctx, channel_context, database); const fs::u8path pkgs_dirs(ctx.prefix_params.root_prefix / "pkgs"); MultiPackageCache package_caches({ pkgs_dirs }, ctx.validation_params); - auto execute_transaction = [&](MTransaction& transaction) - { - if (ctx.output_params.json) - { - transaction.log_json(); - } - - auto prompt_entry = transaction.prompt(ctx, channel_context); - if (prompt_entry) - { - transaction.execute(ctx, channel_context, prefix_data); - } - return prompt_entry; - }; - if (force) { std::vector pkgs_to_remove; @@ -184,7 +150,7 @@ namespace mamba } } auto transaction = MTransaction(ctx, database, pkgs_to_remove, {}, package_caches); - return execute_transaction(transaction); + return mamba::execute_transaction(transaction, ctx, channel_context, prefix_data); } else { @@ -209,13 +175,7 @@ namespace mamba .value(); if (auto* unsolvable = std::get_if(&outcome)) { - if (ctx.output_params.json) - { - Console::instance().json_write( - { { "success", false }, - { "solver_problems", unsolvable->problems(database) } } - ); - } + write_unsolvable_json_if_needed(ctx.output_params.json, database, *unsolvable); throw mamba_error( "Could not solve for environment specs", mamba_error_code::satisfiablitity_error @@ -223,15 +183,15 @@ namespace mamba } Console::instance().json_write({ { "success", true } }); - auto transaction = MTransaction( + auto transaction = make_transaction_from_solution( ctx, - database, + std::move(database), request, - std::get(outcome), + outcome, package_caches ); - return execute_transaction(transaction); + return mamba::execute_transaction(transaction, ctx, channel_context, prefix_data); } } } diff --git a/libmamba/src/api/update.cpp b/libmamba/src/api/update.cpp index 567df9c47b..255c3b5774 100644 --- a/libmamba/src/api/update.cpp +++ b/libmamba/src/api/update.cpp @@ -4,20 +4,16 @@ // // The full license is in the file LICENSE, distributed with this software. -#include - -#include "mamba/api/channel_loader.hpp" #include "mamba/api/configuration.hpp" +#include "mamba/api/install.hpp" #include "mamba/api/update.hpp" #include "mamba/core/channel_context.hpp" #include "mamba/core/context.hpp" #include "mamba/core/output.hpp" -#include "mamba/core/package_database_loader.hpp" -#include "mamba/core/pinning.hpp" +#include "mamba/core/package_cache.hpp" +#include "mamba/core/prefix_data.hpp" #include "mamba/core/transaction.hpp" -#include "mamba/core/virtual_packages.hpp" #include "mamba/solver/libsolv/database.hpp" -#include "mamba/solver/libsolv/solver.hpp" #include "mamba/solver/request.hpp" #include "mamba/specs/match_spec.hpp" @@ -27,8 +23,6 @@ namespace mamba { namespace { - using command_args = std::vector; - auto create_update_request( PrefixData& prefix_data, std::vector specs, @@ -138,6 +132,7 @@ namespace mamba return request; } + } void update(Configuration& config, const UpdateParams& update_params) @@ -145,68 +140,26 @@ namespace mamba auto& ctx = config.context(); // `env update` case - if (update_params.env_update == EnvUpdate::Yes) - { - config.at("create_base").set_value(true); - } - config.at("use_target_prefix_fallback").set_value(true); - config.at("use_default_prefix_fallback").set_value(true); - config.at("use_root_prefix_fallback").set_value(true); - config.at("target_prefix_checks") - .set_value( - MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX - | MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX - ); + configure_common_prefix_fallbacks( + config, + /* create_base= */ update_params.env_update == EnvUpdate::Yes + ); config.load(); const auto& raw_update_specs = config.at("specs").value>(); - auto channel_context = ChannelContext::make_conda_compatible(ctx); + auto& no_pin = config.at("no_pin").value(); + auto& no_py_pin = config.at("no_py_pin").value(); + auto& retry_clean_cache = config.at("retry_clean_cache").value(); - populate_context_channels_from_specs(raw_update_specs, ctx); - - solver::libsolv::Database db{ - channel_context.params(), - { - ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Libsolv, - }, - }; - add_logger_to_database(db); - - MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params); - - std::vector root_packages = ctx.repodata_use_shards - ? build_sharded_root_packages(raw_update_specs) - : std::vector{}; - - auto exp_loaded = load_channels(ctx, channel_context, db, package_caches, root_packages); - if (!exp_loaded) - { - throw std::runtime_error(exp_loaded.error().what()); - } + validate_target_prefix_and_channels(ctx, /* create_env= */ false); + auto [db, package_caches] = prepare_solver_context(ctx, channel_context, raw_update_specs); - auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); - if (!exp_prefix_data) - { - // TODO: propagate tl::expected mechanism - throw std::runtime_error(exp_prefix_data.error().what()); - } - PrefixData& prefix_data = exp_prefix_data.value(); - - load_installed_packages_in_database(ctx, db, prefix_data); + auto prefix_data = load_prefix_data_and_installed(ctx, channel_context, db); auto request = create_update_request(prefix_data, raw_update_specs, update_params); - add_pins_to_request( - request, - ctx, - prefix_data, - raw_update_specs, - /* no_pin= */ config.at("no_pin").value(), - /* no_py_pin = */ config.at("no_py_pin").value() - ); - request.flags = ctx.solver_flags; + add_pins_to_request(request, ctx, prefix_data, raw_update_specs, no_pin, no_py_pin); { auto out = Console::stream(); @@ -214,81 +167,58 @@ namespace mamba // Console stream prints on destruction } - if (Console::can_report_status()) - { - Console::instance().print( - fmt::format("{:<85} {:>20}", "Resolving Environment", "⧖ Starting") - ); - } - auto outcome = solver::libsolv::Solver() - .solve( - db, - request, - ctx.experimental_matchspec_parsing - ? solver::libsolv::MatchSpecParser::Mamba - : solver::libsolv::MatchSpecParser::Mixed - ) - .value(); - if (Console::can_report_status()) - { - Console::instance().print(fmt::format("{:<85} {:>20}", "Resolving Environment", "✔ Done")); - } - if (auto* unsolvable = std::get_if(&outcome)) + auto outcome = solve_request_with_status(ctx.experimental_matchspec_parsing, db, request); + + // No retry at all: mark as already retried so handle_unsolvable_with_retry() never + // re-solves. + handle_unsolvable_with_retry( + outcome, + ctx.graphics_params.palette, + ctx.output_params.json, + retry_clean_cache, + /* is_retry= */ true, + ctx.local_repodata_ttl, + db, + /* retry_fn= */ []() {} + ); + + std::vector locks; + for (auto& c : ctx.pkgs_dirs) { - unsolvable->explain_problems_to( - db, - LOG_ERROR, - { - /* .unavailable= */ ctx.graphics_params.palette.failure, - /* .available= */ ctx.graphics_params.palette.success, - } - ); - if (ctx.output_params.json) - { - Console::instance().json_write( - { { "success", false }, { "solver_problems", unsolvable->problems(db) } } - ); - } - throw mamba_error( - "Could not solve for environment specs", - mamba_error_code::satisfiablitity_error - ); + locks.push_back(LockFile(c)); } Console::instance().json_write({ { "success", true } }); - auto transaction = MTransaction( - ctx, - db, - request, - std::get(outcome), - package_caches - ); + auto trans = make_transaction_from_solution(ctx, std::move(db), request, outcome, package_caches); - auto execute_transaction = [&](MTransaction& trans) - { - if (ctx.output_params.json) - { - trans.log_json(); - } + Console::stream(); - bool yes = trans.prompt(ctx, channel_context); - if (yes) + auto transaction_accepted = execute_transaction( + trans, + ctx, + channel_context, + prefix_data, + /* before_execute= */ {}, + /* after_execute= */ + [&]() { - trans.execute(ctx, channel_context, prefix_data); - } - }; + if (update_params.env_update == EnvUpdate::Yes && !ctx.dry_run) + { + print_activation_message(ctx); + } + }, + /* on_abort= */ {} + ); - execute_transaction(transaction); - for (auto other_spec : - config.at("others_pkg_mgrs_specs").value>()) - { - auto result = install_for_other_pkgmgr(ctx, other_spec, pip::Update::Yes); - if (!result.has_value()) - { - static_assert(std::is_base_of_v); - throw std::move(result).error(); - } - } + const auto other_specs = config.at("others_pkg_mgrs_specs") + .value>(); + execute_other_pkg_managers_if_needed( + transaction_accepted, + ctx.dry_run, + other_specs, + ctx, + pip::Update::Yes + ); } } diff --git a/libmamba/src/api/utils.cpp b/libmamba/src/api/utils.cpp index b85c39418c..bb0a895b5f 100644 --- a/libmamba/src/api/utils.cpp +++ b/libmamba/src/api/utils.cpp @@ -12,11 +12,22 @@ #include // TODO includes to be removed after moving some functions/structs around +#include "mamba/api/channel_loader.hpp" +#include "mamba/api/configuration.hpp" #include "mamba/api/install.hpp" // other_pkg_mgr_spec +#include "mamba/core/channel_context.hpp" #include "mamba/core/context.hpp" +#include "mamba/core/environments_manager.hpp" #include "mamba/core/output.hpp" +#include "mamba/core/package_cache.hpp" +#include "mamba/core/package_database_loader.hpp" +#include "mamba/core/prefix_data.hpp" +#include "mamba/core/transaction.hpp" #include "mamba/core/util.hpp" +#include "mamba/core/util_os.hpp" #include "mamba/fs/filesystem.hpp" +#include "mamba/solver/libsolv/database.hpp" +#include "mamba/solver/request.hpp" #include "mamba/specs/match_spec.hpp" #include "mamba/util/environment.hpp" @@ -285,4 +296,278 @@ namespace mamba add_pip_if_python(root_packages); return root_packages; } + + void print_activation_message(const Context& ctx) + { + // Check that the target prefix is not active before printing the activation message + if (util::get_env("CONDA_PREFIX") != ctx.prefix_params.target_prefix) + { + // Get the name of the executable used directly from the command. + const auto executable = get_self_exe_path().stem().string(); + + // Get the name of the environment + const auto environment = env_name( + ctx.envs_dirs, + ctx.prefix_params.root_prefix, + ctx.prefix_params.target_prefix + ); + + Console::stream() << "\nTo activate this environment, use:\n\n" + " " + << executable << " activate " << environment + << "\n\n" + "Or to execute a single command in this environment, use:\n\n" + " " + << executable + << " run " + // Use -n or -p depending on if the env_name is a full prefix or + // just a name. + << (environment == ctx.prefix_params.target_prefix ? "-p " : "-n ") + << environment << " mycommand\n"; + } + } + + solver::libsolv::Solver::Outcome solve_request_with_status( + bool experimental_matchspec_parsing, + solver::libsolv::Database& db, + const solver::Request& request + ) + { + if (Console::can_report_status()) + { + Console::instance().print( + fmt::format("{:<85} {:>20}", "Resolving Environment", "⧖ Starting") + ); + } + auto outcome = solver::libsolv::Solver() + .solve( + db, + request, + experimental_matchspec_parsing + ? solver::libsolv::MatchSpecParser::Mamba + : solver::libsolv::MatchSpecParser::Mixed + ) + .value(); + if (Console::can_report_status()) + { + Console::instance().print(fmt::format("{:<85} {:>20}", "Resolving Environment", "✔ Done")); + } + return outcome; + } + + solver::libsolv::Database + make_solver_database(bool experimental_matchspec_parsing, ChannelContext& channel_context) + { + solver::libsolv::Database db{ + channel_context.params(), + { + experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba + : solver::libsolv::MatchSpecParser::Libsolv, + }, + }; + add_logger_to_database(db); + return db; + } + + void configure_common_prefix_fallbacks(Configuration& config, bool create_base) + { + if (create_base) + { + config.at("create_base").set_value(true); + } + config.at("use_target_prefix_fallback").set_value(true); + config.at("use_default_prefix_fallback").set_value(true); + config.at("use_root_prefix_fallback").set_value(true); + config.at("target_prefix_checks") + .set_value( + MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX + | MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX + ); + } + + void validate_target_prefix_and_channels(const Context& ctx, bool create_env) + { + if (ctx.prefix_params.target_prefix.empty()) + { + throw std::runtime_error("No active target prefix"); + } + if (!fs::exists(ctx.prefix_params.target_prefix) && !create_env) + { + throw std::runtime_error( + fmt::format("Prefix does not exist at: {}", ctx.prefix_params.target_prefix.string()) + ); + } + if (ctx.channels.empty() && !ctx.offline) + { + LOG_WARNING << "No 'channels' specified"; + } + } + + std::pair prepare_solver_context( + Context& ctx, + ChannelContext& channel_context, + const std::vector& raw_specs + ) + { + populate_context_channels_from_specs(raw_specs, ctx); + auto db = make_solver_database(ctx.experimental_matchspec_parsing, channel_context); + + MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params); + auto root_packages = ctx.repodata_use_shards ? build_sharded_root_packages(raw_specs) + : std::vector{}; + auto maybe_load = load_channels(ctx, channel_context, db, package_caches, root_packages); + if (!maybe_load) + { + throw maybe_load.error(); + } + return { std::move(db), std::move(package_caches) }; + } + + PrefixData load_prefix_data_and_installed( + Context& ctx, + ChannelContext& channel_context, + solver::libsolv::Database& db + ) + { + auto maybe_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context); + if (!maybe_prefix_data) + { + throw std::runtime_error(maybe_prefix_data.error().what()); + } + PrefixData prefix_data = std::move(maybe_prefix_data).value(); + load_installed_packages_in_database(ctx, db, prefix_data); + return prefix_data; + } + + bool handle_unsolvable_with_retry( + solver::libsolv::Solver::Outcome& outcome, + const Palette& palette, + bool json_output, + bool retry_clean_cache, + bool is_retry, + std::size_t& local_repodata_ttl, + solver::libsolv::Database& db, + const std::function& retry_fn, + const std::function& pre_throw_hint + ) + { + auto* unsolvable = std::get_if(&outcome); + if (unsolvable == nullptr) + { + return false; + } + unsolvable->explain_problems_to( + db, + LOG_ERROR, + { + /* .unavailable= */ palette.failure, + /* .available= */ palette.success, + } + ); + if (retry_clean_cache && !is_retry) + { + local_repodata_ttl = 2; + retry_fn(); + return true; + } + if (pre_throw_hint) + { + pre_throw_hint(); + } + write_unsolvable_json_if_needed(json_output, db, *unsolvable); + throw mamba_error( + "Could not solve for environment specs", + mamba_error_code::satisfiablitity_error + ); + } + + void write_unsolvable_json_if_needed( + bool json_output, + solver::libsolv::Database& db, + solver::libsolv::UnSolvable& unsolvable + ) + { + if (json_output) + { + Console::instance().json_write( + { { "success", false }, { "solver_problems", unsolvable.problems(db) } } + ); + } + } + + bool execute_transaction( + MTransaction& transaction, + Context& ctx, + ChannelContext& channel_context, + PrefixData& prefix_data, + const std::function& before_execute, + const std::function& after_execute, + const std::function& on_abort + ) + { + if (ctx.output_params.json) + { + transaction.log_json(); + } + const auto should_execute = transaction.prompt(ctx, channel_context); + if (should_execute) + { + if (before_execute) + { + before_execute(); + } + transaction.execute(ctx, channel_context, prefix_data); + if (after_execute) + { + after_execute(); + } + } + else if (on_abort) + { + on_abort(); + } + return should_execute; + } + + MTransaction make_transaction_from_solution( + Context& ctx, + solver::libsolv::Database db, + const solver::Request& request, + const solver::libsolv::Solver::Outcome& outcome, + MultiPackageCache& package_caches + ) + { + return MTransaction(ctx, db, request, std::get(outcome), package_caches); + } + + void execute_other_pkg_managers( + const std::vector& other_specs, + const Context& ctx, + pip::Update update + ) + { + for (const auto& other_spec : other_specs) + { + auto result = install_for_other_pkgmgr(ctx, other_spec, update); + if (!result) + { + static_assert(std::is_base_of_v); + throw std::move(result).error(); + } + } + } + + void execute_other_pkg_managers_if_needed( + bool transaction_accepted, + bool dry_run, + const std::vector& other_specs, + const Context& ctx, + pip::Update update + ) + { + if (transaction_accepted && !dry_run) + { + execute_other_pkg_managers(other_specs, ctx, update); + } + } } diff --git a/libmamba/src/api/utils.hpp b/libmamba/src/api/utils.hpp index 6018b45ec3..9b04dd423c 100644 --- a/libmamba/src/api/utils.hpp +++ b/libmamba/src/api/utils.hpp @@ -7,15 +7,35 @@ #ifndef MAMBA_UTILS_HPP #define MAMBA_UTILS_HPP +#include #include #include +#include #include +#include "mamba/solver/libsolv/solver.hpp" + #include "tl/expected.hpp" namespace mamba { + class ChannelContext; + class Configuration; class Context; + class MTransaction; + class PrefixData; + class MultiPackageCache; + struct Palette; + + namespace solver + { + struct Request; + + namespace libsolv + { + class Database; + } + } namespace detail { @@ -33,12 +53,18 @@ namespace mamba using command_args = std::vector; + /** + * Build the command-line invocation for a secondary package manager (e.g. pip/uv). + */ tl::expected install_for_other_pkgmgr( const Context& ctx, const detail::other_pkg_mgr_spec& other_spec, pip::Update update ); + /** + * Populate context channels from user-provided specs. + */ void populate_context_channels_from_specs(const std::vector& raw_matchspecs, Context& context); @@ -63,6 +89,128 @@ namespace mamba */ std::vector build_sharded_root_packages(const std::vector& raw_specs); + /** + * Print environment activation guidance for the current target prefix. + */ + void print_activation_message(const Context& ctx); + + /** + * Solve a request and render the solver status through the current console. + */ + solver::libsolv::Solver::Outcome solve_request_with_status( + bool experimental_matchspec_parsing, + solver::libsolv::Database& db, + const solver::Request& request + ); + + /** + * Create a libsolv database configured for the current matching behavior. + */ + solver::libsolv::Database + make_solver_database(bool experimental_matchspec_parsing, ChannelContext& channel_context); + + /** + * Apply shared prefix fallback defaults used by install/update entry points. + */ + void configure_common_prefix_fallbacks(Configuration& config, bool create_base); + + /** + * Validate target prefix preconditions and warn when no channels are configured. + */ + void validate_target_prefix_and_channels(const Context& ctx, bool create_env); + + /** + * Prepare solver state: channels, package cache, database, and root package loading. + */ + std::pair prepare_solver_context( + Context& ctx, + ChannelContext& channel_context, + const std::vector& raw_specs + ); + + /** + * Load target prefix metadata and register installed packages in the solver database. + */ + PrefixData load_prefix_data_and_installed( + Context& ctx, + ChannelContext& channel_context, + solver::libsolv::Database& db + ); + + /** + * Handle unsatisfiable outcomes, including optional one-shot retry with clean cache policy. + * + * Returns ``true`` if a retry callback was triggered and the caller should return early. + */ + bool handle_unsolvable_with_retry( + solver::libsolv::Solver::Outcome& outcome, + const Palette& palette, + bool json_output, + bool retry_clean_cache, + bool is_retry, + std::size_t& local_repodata_ttl, + solver::libsolv::Database& db, + const std::function& retry_fn, + const std::function& pre_throw_hint = {} + ); + + /** + * Emit unsatisfiable diagnostics as JSON when JSON output mode is enabled. + */ + void write_unsolvable_json_if_needed( + bool json_output, + solver::libsolv::Database& db, + solver::libsolv::UnSolvable& unsolvable + ); + + /** + * Execute a transaction with optional hooks and return whether it was accepted. + */ + bool execute_transaction( + MTransaction& transaction, + Context& ctx, + ChannelContext& channel_context, + PrefixData& prefix_data, + const std::function& before_execute = {}, + const std::function& after_execute = {}, + const std::function& on_abort = {} + ); + + /** + * Build a transaction from a solved outcome. + * + * The database is passed by value and moved into ``MTransaction`` so solver memory can + * be released before transaction execution starts. This is used by both install/update + * flows to reduce peak memory usage during package download/extraction and ``.pyc`` creation. + */ + MTransaction make_transaction_from_solution( + Context& ctx, + solver::libsolv::Database db, + const solver::Request& request, + const solver::libsolv::Solver::Outcome& outcome, + MultiPackageCache& package_caches + ); + + /** + * Execute secondary package manager operations (e.g. pip/uv) for recorded specs. + */ + void execute_other_pkg_managers( + const std::vector& other_specs, + const Context& ctx, + pip::Update update + ); + + /** + * Conditionally execute secondary package manager operations after transaction evaluation. + */ + void execute_other_pkg_managers_if_needed( + bool transaction_accepted, + bool dry_run, + const std::vector& other_specs, + const Context& ctx, + pip::Update update + ); + } #endif // MAMBA_UTILS_HPP diff --git a/libmamba/src/core/pinning.cpp b/libmamba/src/core/pinning.cpp index 74dccd4bfe..a0bbff1cfa 100644 --- a/libmamba/src/core/pinning.cpp +++ b/libmamba/src/core/pinning.cpp @@ -15,7 +15,7 @@ namespace mamba { std::vector - python_pin(PrefixData& prefix_data, const std::vector& specs) + python_pin(const PrefixData& prefix_data, const std::vector& specs) { std::vector pins; std::string py_version;