diff --git a/libmamba/include/mamba/core/package_fetcher.hpp b/libmamba/include/mamba/core/package_fetcher.hpp index bf326b59b2..3d1578ada3 100644 --- a/libmamba/include/mamba/core/package_fetcher.hpp +++ b/libmamba/include/mamba/core/package_fetcher.hpp @@ -116,6 +116,7 @@ namespace mamba void update_monitor(progress_callback_t* cb, PackageExtractEvent event) const; specs::PackageInfo m_package_info; + MultiPackageCache* m_caches = nullptr; fs::u8path m_tarball_path; fs::u8path m_cache_path; diff --git a/libmamba/src/api/update.cpp b/libmamba/src/api/update.cpp index c8cdce8a73..936ae45c1e 100644 --- a/libmamba/src/api/update.cpp +++ b/libmamba/src/api/update.cpp @@ -70,10 +70,20 @@ namespace mamba request.jobs.emplace_back(Request::UpdateAll{ /* .clean_dependencies= */ false }); } - // Install everything else + // Specs passed with `update --all`: + // - Update jobs constrain already-installed packages without globally pinning them + // - Install jobs add packages that are not yet in the prefix for (auto& ms : parsed_specs) { - request.jobs.emplace_back(Request::Install{ std::move(ms) }); + const auto& match_spec_name = ms.name().to_string(); + if (prefix_data.records().contains(match_spec_name)) + { + request.jobs.emplace_back(Request::Update{ std::move(ms) }); + } + else + { + request.jobs.emplace_back(Request::Install{ std::move(ms) }); + } } } else diff --git a/libmamba/src/core/package_cache.cpp b/libmamba/src/core/package_cache.cpp index fc8d023104..abf428e926 100644 --- a/libmamba/src/core/package_cache.cpp +++ b/libmamba/src/core/package_cache.cpp @@ -656,6 +656,9 @@ namespace mamba void MultiPackageCache::clear_query_cache(const specs::PackageInfo& s) { + const std::string pkg = s.long_str(); + m_cached_tarballs.erase(pkg); + m_cached_extracted_dirs.erase(pkg); for (auto& c : m_caches) { c.clear_query_cache(s); diff --git a/libmamba/src/core/package_fetcher.cpp b/libmamba/src/core/package_fetcher.cpp index 353914aac0..7f9d4fcfe5 100644 --- a/libmamba/src/core/package_fetcher.cpp +++ b/libmamba/src/core/package_fetcher.cpp @@ -140,6 +140,7 @@ namespace mamba PackageFetcher::PackageFetcher(const specs::PackageInfo& pkg_info, MultiPackageCache& caches) : m_package_info(pkg_info) + , m_caches(&caches) { const fs::u8path extracted_cache = caches.get_extracted_dir_path(m_package_info); if (extracted_cache.empty()) @@ -360,6 +361,7 @@ namespace mamba LOG_DEBUG << "Extracted to '" << extract_path.string() << "'"; write_repodata_record(extract_path); update_urls_txt(); + m_caches->clear_query_cache(m_package_info); update_monitor(cb, PackageExtractEvent::extract_success); } catch (const std::logic_error&) diff --git a/libmamba/src/core/transaction.cpp b/libmamba/src/core/transaction.cpp index 3a4d58dc94..e6c64dc800 100644 --- a/libmamba/src/core/transaction.cpp +++ b/libmamba/src/core/transaction.cpp @@ -26,6 +26,7 @@ #include "mamba/core/execution.hpp" #include "mamba/core/output.hpp" #include "mamba/core/package_fetcher.hpp" +#include "mamba/core/package_handling.hpp" #include "mamba/core/repo_checker_store.hpp" #include "mamba/core/thread_utils.hpp" #include "mamba/core/transaction.hpp" @@ -57,6 +58,58 @@ namespace mamba && caches.get_tarball_path(pkg_info).empty(); } + /** + * Resolve the extracted package cache directory for linking. + * + * Clears stale negative cache entries (e.g. from before fetch/extract in the same + * transaction) and, if needed, re-extracts from a cached tarball at link time. + */ + fs::u8path resolve_extracted_cache_path( + const specs::PackageInfo& pkg, + MultiPackageCache& caches, + const Context& ctx + ) + { + auto lookup = [&]() { return caches.get_extracted_dir_path(pkg); }; + + if (auto path = lookup(); !path.empty()) + { + return path; + } + + caches.clear_query_cache(pkg); + if (auto path = lookup(); !path.empty()) + { + return path; + } + + PackageFetcher fetcher(pkg, caches); + if (fetcher.needs_download()) + { + LOG_ERROR << "Cannot find a valid extracted directory cache for '" << pkg.filename + << "'"; + throw std::runtime_error("Package cache error."); + } + + if (fetcher.needs_extract()) + { + const auto extract_options = ExtractOptions::from_context(ctx); + if (!fetcher.extract(extract_options)) + { + LOG_ERROR << "Failed to extract package '" << pkg.filename << "' for linking"; + throw std::runtime_error("Package cache error."); + } + caches.clear_query_cache(pkg); + if (auto path = lookup(); !path.empty()) + { + return path; + } + } + + LOG_ERROR << "Cannot find a valid extracted directory cache for '" << pkg.filename << "'"; + throw std::runtime_error("Package cache error."); + } + auto explicit_spec(const specs::PackageInfo& pkg) -> specs::MatchSpec { auto out = specs::MatchSpec(); @@ -241,7 +294,7 @@ namespace mamba request, [&](const auto& item) { m_history_entry.update.push_back(item.spec.to_string()); } ); - solver::for_each_of( + solver::for_each_of( request, [&](const auto& item) { m_history_entry.remove.push_back(item.spec.to_string()); } ); @@ -764,7 +817,7 @@ namespace mamba } Console::stream() << "Linking " << pkg.str(); - const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg, false)); + const fs::u8path cache_path(resolve_extracted_cache_path(pkg, m_multi_cache, ctx)); LinkPackage lp(pkg, cache_path, &transaction_context); try { diff --git a/libmamba/tests/src/core/test_package_cache.cpp b/libmamba/tests/src/core/test_package_cache.cpp index a2bbe76094..025e64670c 100644 --- a/libmamba/tests/src/core/test_package_cache.cpp +++ b/libmamba/tests/src/core/test_package_cache.cpp @@ -177,5 +177,21 @@ namespace mamba MultiPackageCache cache({ long_pkgs_dir }, params); REQUIRE(cache.get_extracted_dir_path(pkg_info) == long_pkgs_dir / rel_path); } + + SECTION("Stale negative cache is cleared and package is found") + { + MultiPackageCache cache({ pkgs_dir }, params); + + // Negative lookup is cached before the extracted directory exists + REQUIRE(cache.get_extracted_dir_path(pkg_info).empty()); + + write_repodata_record(hierarchical_dir / "info" / "repodata_record.json", pkg_info); + + // Without clearing the query cache, the negative result is reused + REQUIRE(cache.get_extracted_dir_path(pkg_info).empty()); + + cache.clear_query_cache(pkg_info); + REQUIRE(cache.get_extracted_dir_path(pkg_info) == pkgs_dir / rel_path); + } } } // namespace mamba diff --git a/micromamba/src/update.cpp b/micromamba/src/update.cpp index 2cb871b5c5..4ac5faa2c7 100644 --- a/micromamba/src/update.cpp +++ b/micromamba/src/update.cpp @@ -38,7 +38,11 @@ set_update_command(CLI::App* subcom, Configuration& config) subcom->add_flag("--prune-deps,!--no-prune-deps", prune_deps, "Prune dependencies (default)"); subcom->get_option("specs")->description("Specs to update in the environment"); - subcom->add_flag("-a,--all", update_all, "Update all packages in the environment"); + subcom->add_flag( + "-a,--all", + update_all, + "Update all packages in the environment (optionally constrained by specs)" + ); subcom->callback( [&] diff --git a/micromamba/tests/test_update.py b/micromamba/tests/test_update.py index 01662ce7c5..d4c34fa12b 100644 --- a/micromamba/tests/test_update.py +++ b/micromamba/tests/test_update.py @@ -221,6 +221,19 @@ def test_update_all(self, env_created): pkgs = helpers.umamba_list("-n", TestUpdate.env_name, "--json") assert any(pkg["name"] == "numpy" for pkg in pkgs) + def test_update_all_with_constraint(self, env_created): + update_res = helpers.update("--all", "xtensor<=" + self.medium_old_version, "--json") + xtensor_link = [ + to_link for to_link in update_res["actions"]["LINK"] if to_link["name"] == "xtensor" + ][0] + assert xtensor_link["version"].startswith(self.medium_old_version) + + if helpers.dry_run_tests == helpers.DryRun.OFF: + with open(Path(self.prefix) / "conda-meta" / "history") as h: + history = h.read() + assert "update specs" in history + assert "xtensor<=" + self.medium_old_version in history + @pytest.mark.parametrize( "alias", [