From 3674c213de6d1cf096e5ca69219b7e698db595bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Lamotte=20=28Klaim=29?= Date: Thu, 10 Mar 2022 18:34:45 +0100 Subject: [PATCH 1/2] Add support for environment lock file in `create` and `install` comands. - add lockfile parsing functions with tests - integrate lockfile detection and reading into `create` and `install` --- libmamba/CMakeLists.txt | 3 + libmamba/include/mamba/api/install.hpp | 1 + libmamba/include/mamba/core/context.hpp | 2 + libmamba/include/mamba/core/env_lockfile.hpp | 123 ++++++++++ .../include/mamba/core/error_handling.hpp | 3 +- libmamba/include/mamba/core/transaction.hpp | 10 + libmamba/include/mamba/core/util.hpp | 6 + libmamba/include/mamba/core/util_scope.hpp | 46 ++++ libmamba/src/api/create.cpp | 8 +- libmamba/src/api/install.cpp | 89 +++++--- libmamba/src/core/env_lockfile.cpp | 214 ++++++++++++++++++ libmamba/src/core/transaction.cpp | 65 ++++++ libmamba/tests/CMakeLists.txt | 3 + .../env_lockfile_test/bad_package-lock.yaml | 15 ++ .../env_lockfile_test/bad_version-lock.yaml | 1 + .../good_multiple_packages-lock.yaml | 189 ++++++++++++++++ .../good_no_package-lock.yaml | 23 ++ .../good_one_package-lock.yaml | 34 +++ libmamba/tests/test_env_lockfile.cpp | 126 +++++++++++ libmamba/tests/test_util.cpp | 44 ++++ 20 files changed, 975 insertions(+), 30 deletions(-) create mode 100644 libmamba/include/mamba/core/env_lockfile.hpp create mode 100644 libmamba/include/mamba/core/util_scope.hpp create mode 100644 libmamba/src/core/env_lockfile.cpp create mode 100644 libmamba/tests/env_lockfile_test/bad_package-lock.yaml create mode 100644 libmamba/tests/env_lockfile_test/bad_version-lock.yaml create mode 100644 libmamba/tests/env_lockfile_test/good_multiple_packages-lock.yaml create mode 100644 libmamba/tests/env_lockfile_test/good_no_package-lock.yaml create mode 100644 libmamba/tests/env_lockfile_test/good_one_package-lock.yaml create mode 100644 libmamba/tests/test_env_lockfile.cpp diff --git a/libmamba/CMakeLists.txt b/libmamba/CMakeLists.txt index 8b13074115..d1439c8e89 100644 --- a/libmamba/CMakeLists.txt +++ b/libmamba/CMakeLists.txt @@ -133,6 +133,7 @@ set(LIBMAMBA_SOURCES ${LIBMAMBA_SOURCE_DIR}/core/util_os.cpp ${LIBMAMBA_SOURCE_DIR}/core/validate.cpp ${LIBMAMBA_SOURCE_DIR}/core/virtual_packages.cpp + ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile.cpp # API (high-level) ${LIBMAMBA_SOURCE_DIR}/api/c_api.cpp ${LIBMAMBA_SOURCE_DIR}/api/channel_loader.cpp @@ -192,8 +193,10 @@ set(LIBMAMBA_HEADERS ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_os.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_random.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_scope.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/core/validate.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/core/virtual_packages.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/env_lockfile.hpp # API (high-level) ${LIBMAMBA_INCLUDE_DIR}/mamba/api/c_api.h ${LIBMAMBA_INCLUDE_DIR}/mamba/api/channel_loader.hpp diff --git a/libmamba/include/mamba/api/install.hpp b/libmamba/include/mamba/api/install.hpp index 29dff569c1..fc0671274b 100644 --- a/libmamba/include/mamba/api/install.hpp +++ b/libmamba/include/mamba/api/install.hpp @@ -33,6 +33,7 @@ namespace mamba int is_retry = 0); void install_explicit_specs(const std::vector& specs, bool create_env = false); + void install_lockfile_specs(const fs::path& lockfile_specs, bool create_env = false); namespace detail { diff --git a/libmamba/include/mamba/core/context.hpp b/libmamba/include/mamba/core/context.hpp index f33c77e910..ddb7515691 100644 --- a/libmamba/include/mamba/core/context.hpp +++ b/libmamba/include/mamba/core/context.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #define ROOT_ENV_NAME "base" @@ -111,6 +112,7 @@ namespace mamba // TODO check writable and add other potential dirs std::vector envs_dirs; std::vector pkgs_dirs; + std::optional env_lockfile; bool use_index_cache = false; std::size_t local_repodata_ttl = 1; // take from header diff --git a/libmamba/include/mamba/core/env_lockfile.hpp b/libmamba/include/mamba/core/env_lockfile.hpp new file mode 100644 index 0000000000..484a37eeff --- /dev/null +++ b/libmamba/include/mamba/core/env_lockfile.hpp @@ -0,0 +1,123 @@ +// Copyright (c) 2022, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + + +#ifndef MAMBA_CORE_ENVIRONMENT_LOCKFILE_HPP +#define MAMBA_CORE_ENVIRONMENT_LOCKFILE_HPP + +#include +#include +#include +#include + +#include + +#include "fsutil.hpp" +#include "package_info.hpp" +#include "error_handling.hpp" + +namespace mamba +{ + + enum class file_parsing_error_code + { + unknown_failure, /// Something failed while parsing but we can't identify what. + unsuported_version, /// The version of the file does not matched supported ver. + parsing_failure, /// The content of the file doesnt match the expected format/language + /// structure or constraints. + invalid_data, /// The structure of the data in the file is fine but some fields have + /// invalid values for our purpose. + }; + + struct EnvLockFileError + { + file_parsing_error_code parsing_error_code = file_parsing_error_code::unknown_failure; + std::optional yaml_error_type{}; + + static const EnvLockFileError& get_details(const mamba_error& error) + { + return std::any_cast(error.data()); + } + + template + static mamba_error make_error(file_parsing_error_code error_code, + StringT&& msg, + std::optional yaml_error_type = std::nullopt) + { + return mamba_error{ std::forward(msg), + mamba_error_code::env_lockfile_parsing_failed, + EnvLockFileError{ error_code, yaml_error_type } }; + } + }; + + class EnvironmentLockFile + { + public: + struct Channel + { + std::string url; + std::vector used_env_vars; + }; + + struct Meta + { + std::unordered_map content_hash; + std::vector channels; + std::vector platforms; + std::vector sources; + }; + + struct Package + { + mamba::PackageInfo info; + bool is_optional = false; + std::string category; + std::string manager; + std::string platform; + }; + + EnvironmentLockFile(Meta metadata, std::vector packages) + : metadata(std::move(metadata)) + , packages(std::move(packages)) + { + } + + std::vector get_packages_for(std::string_view category, + std::string_view platform, + std::string_view manager) const; + + const std::vector& get_all_packages() const + { + return packages; + } + const Meta& get_metadata() const + { + return metadata; + } + + private: + Meta metadata; + std::vector packages; + }; + + /// Read an environment lock YAML file and returns it's structured content or an error if + /// failed. + tl::expected read_environment_lockfile( + const fs::path& lockfile_location); + + + /// Returns `true` if the filename matches names of files which should be interpreted as conda + /// environment lockfile. NOTE: this does not check if the file exists. + inline bool is_env_lockfile_name(const std::string_view filename) + { + return ends_with(filename, "-lock.yml") || ends_with(filename, "-lock.yaml"); + } + + +} + + +#endif diff --git a/libmamba/include/mamba/core/error_handling.hpp b/libmamba/include/mamba/core/error_handling.hpp index 2f945e6e35..a14b8be76b 100644 --- a/libmamba/include/mamba/core/error_handling.hpp +++ b/libmamba/include/mamba/core/error_handling.hpp @@ -18,12 +18,13 @@ namespace mamba enum class mamba_error_code { unknown, + aggregated, prefix_data_not_loaded, subdirdata_not_loaded, cache_not_loaded, repodata_not_loaded, configurable_bad_cast, - aggregated + env_lockfile_parsing_failed, }; class mamba_error : public std::runtime_error diff --git a/libmamba/include/mamba/core/transaction.hpp b/libmamba/include/mamba/core/transaction.hpp index b3fa6490bf..79ce9cc77e 100644 --- a/libmamba/include/mamba/core/transaction.hpp +++ b/libmamba/include/mamba/core/transaction.hpp @@ -26,6 +26,7 @@ #include "repo.hpp" #include "thread_utils.hpp" #include "transaction_context.hpp" +#include "env_lockfile.hpp" extern "C" { @@ -122,6 +123,11 @@ namespace mamba MultiPackageCache& caches); MTransaction(MSolver& solver, MultiPackageCache& caches); + // Only use if the packages have been solved previously already. + MTransaction(MPool& pool, + const std::vector& packages, + MultiPackageCache& caches); + ~MTransaction(); MTransaction(const MTransaction&) = delete; @@ -166,6 +172,10 @@ namespace mamba MTransaction create_explicit_transaction_from_urls(MPool& pool, const std::vector& urls, MultiPackageCache& package_caches); + + MTransaction create_explicit_transaction_from_lockfile(MPool& pool, + const fs::path& env_lockfile_path, + MultiPackageCache& package_caches); } // namespace mamba #endif // MAMBA_TRANSACTION_HPP diff --git a/libmamba/include/mamba/core/util.hpp b/libmamba/include/mamba/core/util.hpp index 711994b768..e4df945c14 100644 --- a/libmamba/include/mamba/core/util.hpp +++ b/libmamba/include/mamba/core/util.hpp @@ -326,6 +326,12 @@ namespace mamba std::tuple, std::unique_ptr> prepare_wrapped_call( const fs::path& prefix, const std::vector& cmd); + /// Returns `true` if the filename matches names of files which should be interpreted as YAML. + /// NOTE: this does not check if the file exists. + inline bool is_yaml_file_name(const std::string_view filename) + { + return ends_with(filename, ".yml") || ends_with(filename, ".yaml"); + } } // namespace mamba diff --git a/libmamba/include/mamba/core/util_scope.hpp b/libmamba/include/mamba/core/util_scope.hpp new file mode 100644 index 0000000000..bada98ad70 --- /dev/null +++ b/libmamba/include/mamba/core/util_scope.hpp @@ -0,0 +1,46 @@ + +#ifndef MAMBA_CORE_UTIL_SCOPE_HPP +#define MAMBA_CORE_UTIL_SCOPE_HPP + +#include +#include "spdlog/fmt/fmt.h" +#include "mamba/core/output.hpp" + +namespace mamba +{ + + template + struct on_scope_exit + { + F func; + + explicit on_scope_exit(F&& f) + : func(std::forward(f)) + { + } + + ~on_scope_exit() + { + try + { + func(); + } + catch (const std::exception& ex) + { + LOG_ERROR << fmt::format("Scope exit error (catched and ignored): {}", ex.what()); + } + catch (...) + { + LOG_ERROR << "Scope exit unknown error (catched and ignored)"; + } + } + + // Deactivate copy & move until we implement moves + on_scope_exit(const on_scope_exit&) = delete; + on_scope_exit& operator=(const on_scope_exit&) = delete; + on_scope_exit(on_scope_exit&&) = delete; + on_scope_exit& operator=(on_scope_exit&&) = delete; + }; +} + +#endif diff --git a/libmamba/src/api/create.cpp b/libmamba/src/api/create.cpp index 526f268726..74e86f3cdc 100644 --- a/libmamba/src/api/create.cpp +++ b/libmamba/src/api/create.cpp @@ -59,7 +59,13 @@ namespace mamba detail::store_platform_config(ctx.target_prefix, ctx.platform); } - if (!create_specs.empty()) + if (Context::instance().env_lockfile) + { + const auto lockfile_path = Context::instance().env_lockfile.value(); + LOG_DEBUG << "Lockfile: " << lockfile_path.string(); + install_lockfile_specs(lockfile_path, true); + } + else if (!create_specs.empty()) { if (use_explicit) install_explicit_specs(create_specs, true); diff --git a/libmamba/src/api/install.cpp b/libmamba/src/api/install.cpp index 8d2ddb2350..fbb341f41d 100644 --- a/libmamba/src/api/install.cpp +++ b/libmamba/src/api/install.cpp @@ -18,6 +18,7 @@ #include "mamba/core/transaction.hpp" #include "mamba/core/util.hpp" #include "mamba/core/virtual_packages.hpp" +#include "mamba/core/env_lockfile.hpp" #include "termcolor/termcolor.hpp" @@ -320,7 +321,13 @@ namespace mamba auto& install_specs = config.at("specs").value>(); auto& use_explicit = config.at("explicit_install").value(); - if (!install_specs.empty()) + if (Context::instance().env_lockfile) + { + const auto lockfile_path = Context::instance().env_lockfile.value(); + LOG_DEBUG << "Lockfile: " << lockfile_path.string(); + install_lockfile_specs(lockfile_path, false); + } + else if (!install_specs.empty()) { if (use_explicit) { @@ -498,39 +505,59 @@ namespace mamba } } - - void install_explicit_specs(const std::vector& specs, bool create_env) + namespace detail { - MPool pool; - auto& ctx = Context::instance(); - auto exp_prefix_data = PrefixData::create(ctx.target_prefix); - if (!exp_prefix_data) + // TransactionFunc: (MPool& pool, MultiPackageCache& package_caches) -> MTransaction + template + void install_explicit_with_transaction(TransactionFunc create_transaction, bool create_env) { - // TODO: propagate tl::expected mechanism - throw std::runtime_error(exp_prefix_data.error().what()); - } - PrefixData& prefix_data = exp_prefix_data.value(); + MPool pool; + auto& ctx = Context::instance(); + auto exp_prefix_data = PrefixData::create(ctx.target_prefix); + if (!exp_prefix_data) + { + // TODO: propagate tl::expected mechanism + throw std::runtime_error("could not load prefix data"); + } + PrefixData& prefix_data = exp_prefix_data.value(); - fs::path pkgs_dirs(Context::instance().root_prefix / "pkgs"); - MultiPackageCache pkg_caches({ pkgs_dirs }); + fs::path pkgs_dirs(Context::instance().root_prefix / "pkgs"); + MultiPackageCache pkg_caches({ pkgs_dirs }); - auto transaction = create_explicit_transaction_from_urls(pool, specs, pkg_caches); + auto transaction = create_transaction(pool, pkg_caches); - prefix_data.add_packages(get_virtual_packages()); - MRepo::create(pool, prefix_data); + prefix_data.add_packages(get_virtual_packages()); + MRepo::create(pool, prefix_data); - if (ctx.json) - transaction.log_json(); + if (ctx.json) + transaction.log_json(); - if (transaction.prompt()) - { - if (create_env && !Context::instance().dry_run) - detail::create_target_directory(ctx.target_prefix); + if (transaction.prompt()) + { + if (create_env && !Context::instance().dry_run) + detail::create_target_directory(ctx.target_prefix); - transaction.execute(prefix_data); + transaction.execute(prefix_data); + } } } + void install_explicit_specs(const std::vector& specs, bool create_env) + { + detail::install_explicit_with_transaction( + [&](auto& pool, auto& pkg_caches) + { return create_explicit_transaction_from_urls(pool, specs, pkg_caches); }, + create_env); + } + + void install_lockfile_specs(const fs::path& lockfile, bool create_env) + { + detail::install_explicit_with_transaction( + [&](auto& pool, auto& pkg_caches) + { return create_explicit_transaction_from_lockfile(pool, lockfile, pkg_caches); }, + create_env); + } + namespace detail { void create_empty_target(const fs::path& prefix) @@ -558,9 +585,9 @@ namespace mamba if (file_specs.size() == 0) return; - for (auto& file : file_specs) + for (const auto& file : file_specs) { - if ((ends_with(file, ".yml") || ends_with(file, ".yaml")) && file_specs.size() != 1) + if (is_yaml_file_name(file) && file_specs.size() != 1) { throw std::runtime_error("Can only handle 1 yaml file!"); } @@ -569,9 +596,15 @@ namespace mamba for (auto& file : file_specs) { // read specs from file :) - if (ends_with(file, ".yml") || ends_with(file, ".yaml")) + if (is_env_lockfile_name(file)) + { + const auto lockfile_path = fs::absolute(file); + LOG_DEBUG << "File spec Lockfile: " << lockfile_path.string(); + Context::instance().env_lockfile = lockfile_path; + } + else if (is_yaml_file_name(file)) { - auto parse_result = read_yaml_file(file); + const auto parse_result = read_yaml_file(file); if (parse_result.channels.size() != 0) { @@ -610,7 +643,7 @@ namespace mamba } else { - std::vector file_contents = read_lines(file); + const std::vector file_contents = read_lines(file); if (file_contents.size() == 0) { throw std::runtime_error(concat("Got an empty file: ", file)); diff --git a/libmamba/src/core/env_lockfile.cpp b/libmamba/src/core/env_lockfile.cpp new file mode 100644 index 0000000000..ac0c438c5e --- /dev/null +++ b/libmamba/src/core/env_lockfile.cpp @@ -0,0 +1,214 @@ +// Copyright (c) 2022, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include "mamba/core/env_lockfile.hpp" + +#include +#include + +#include "mamba/core/output.hpp" + +#include "mamba/core/match_spec.hpp" + +namespace mamba +{ + namespace env_lockfile_v1 + { + using Package = EnvironmentLockFile::Package; + + tl::expected read_package_info(const YAML::Node& package_node) + { + Package package{ + /* .info = */ mamba::PackageInfo{ package_node["name"].as() }, + /* .is_optional = */ + [&] + { + if (const auto& optional_node = package_node["optional"]) + return optional_node.as(); + return false; + }(), + /* .category = */ package_node["category"].as(), + /* .manager = */ package_node["manager"].as(), + /* .platform = */ package_node["platform"].as(), + }; + + package.info.version = package_node["version"].as(); + const auto& hash_node = package_node["hash"]; + if (const auto& md5_node = hash_node["md5"]) + package.info.md5 = md5_node.as(); + if (const auto& sha256_node = hash_node["sha256"]) + package.info.sha256 = sha256_node.as(); + if (package.info.sha256.empty() && package.info.md5.empty()) + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::invalid_data, + "either package 'package.info.hash.md5' or 'package.info.hash.sha256' must be specified, found none")); + + package.info.url = package_node["url"].as(); + const MatchSpec spec{ package.info.url }; + package.info.fn = spec.name; + package.info.build_string = spec.build; + + for (const auto& dependency : package_node["dependencies"]) + { + const auto dependency_name = dependency.first.as(); + const auto dependency_constraint = dependency.second.as(); + package.info.depends.push_back( + fmt::format("{} {}", dependency_name, dependency_constraint)); + } + + if (const auto& constraints_nodes = package_node["constrains"]) + { + for (const auto& dependency : constraints_nodes) + { + const auto constraint_dep_name = dependency.first.as(); + const auto constraint_expr = dependency.second.as(); + package.info.constrains.push_back( + fmt::format("{} {}", constraint_dep_name, constraint_expr)); + } + } + + return package; + } + + tl::expected read_metadata( + const YAML::Node& metadata_node) + { + EnvironmentLockFile::Meta metadata; + + for (const auto& platform_node : metadata_node["platforms"]) + { + metadata.platforms.push_back(platform_node.as()); + } + if (metadata.platforms.empty()) + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::invalid_data, + "at least one 'metadata.platform.*' must be specified, found none")); + + for (const auto& source_node : metadata_node["sources"]) + { + metadata.sources.push_back(source_node.as()); + } + if (metadata.sources.empty()) + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::invalid_data, + "at least one 'metadata.source.*' must be specified, found none")); + + for (const auto& channel_node : metadata_node["channels"]) + { + EnvironmentLockFile::Channel channel; + channel.url = channel_node["url"].as(); + channel.used_env_vars + = channel_node["used_env_vars"].as>(); + metadata.channels.push_back(std::move(channel)); + } + + for (const auto& node_pair : metadata_node["content_hash"]) + { + const auto& platform_node = node_pair.first; + const auto& hash_node = node_pair.first; + metadata.content_hash.emplace(platform_node.as(), + hash_node.as()); + } + if (metadata.content_hash.empty()) + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::invalid_data, + "at least one 'metadata.content_hash.*' value must be specified, found none")); + + return metadata; + } + + tl::expected read_environment_lockfile( + const YAML::Node& lockfile_yaml) + { + const auto& maybe_metadata = read_metadata(lockfile_yaml["metadata"]); + if (!maybe_metadata) + return tl::unexpected(maybe_metadata.error()); + + auto metadata = maybe_metadata.value(); + + std::vector packages; + for (const auto& package_node : lockfile_yaml["package"]) + { + if (auto maybe_package = read_package_info(package_node)) + { + packages.push_back(maybe_package.value()); + } + else + { + return tl::unexpected(maybe_package.error()); + } + } + + return EnvironmentLockFile{ std::move(metadata), std::move(packages) }; + } + } + + tl::expected read_environment_lockfile( + const fs::path& lockfile_location) + { + const auto file_path = fs::absolute( + lockfile_location); // Having the complete path helps with logging and error reports. + try + { + // TODO: add fields validation here (using some schema validation tool) + const YAML::Node lockfile_content = YAML::LoadFile(file_path); + const auto lockfile_version = lockfile_content["version"].as(); + switch (lockfile_version) + { + case 1: + return env_lockfile_v1::read_environment_lockfile(lockfile_content); + + default: + { + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::unsuported_version, + fmt::format( + "Failed to read environment lockfile at '{}' : unknown version '{}'", + file_path.string(), + lockfile_version))); + } + } + } + catch (const YAML::Exception& err) + { + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::parsing_failure, + fmt::format( + "YAML parsing error while reading environment lockfile located at '{}' : {}", + file_path.string(), + err.what()), + std::type_index{ typeid(err) })); + } + catch (...) + { + return tl::unexpected(EnvLockFileError::make_error( + file_parsing_error_code::parsing_failure, + fmt::format("unknown error while reading environment lockfile located at '{}'", + file_path.string()))); + } + } + + std::vector EnvironmentLockFile::get_packages_for(std::string_view category, + std::string_view platform, + std::string_view manager) const + { + std::vector results; + + // TODO: c++20 - rewrite this with ranges + const auto package_predicate = [&](const auto& package) + { + return package.platform == platform && package.category == category + && package.manager == manager; + }; + for (const auto& package : packages) + { + if (package_predicate(package)) + results.push_back(package.info); + } + + return results; + } +} diff --git a/libmamba/src/core/transaction.cpp b/libmamba/src/core/transaction.cpp index 6b2cd981e0..62c8ae2a54 100644 --- a/libmamba/src/core/transaction.cpp +++ b/libmamba/src/core/transaction.cpp @@ -15,6 +15,7 @@ #include "mamba/core/output.hpp" #include "mamba/core/pool.hpp" #include "mamba/core/thread_utils.hpp" +#include "mamba/core/util_scope.hpp" #include "termcolor/termcolor.hpp" @@ -713,6 +714,49 @@ namespace mamba } } + MTransaction::MTransaction(MPool& pool, + const std::vector& packages, + MultiPackageCache& caches) + : m_multi_cache(caches) + { + LOG_WARNING << "MTransaction::MTransaction - packages already resolved (lockfile)"; + MRepo& mrepo = MRepo::create(pool, "__explicit_specs__", packages); + pool.create_whatprovides(); + + Queue job; + queue_init(&job); + const on_scope_exit _job_release{ [&] { queue_free(&job); } }; + + Queue decision; + queue_init(&decision); + const on_scope_exit _decision_release{ [&] { queue_free(&decision); } }; + + Id pkg_id = {}; + Solvable* solvable = nullptr; + + FOR_REPO_SOLVABLES(mrepo.repo(), pkg_id, solvable) + { + queue_push(&decision, pkg_id); + } + + m_transaction = transaction_create_decisionq((Pool*) pool, &decision, nullptr); + transaction_order(m_transaction, 0); + + init(); + + m_history_entry = History::UserRequest::prefilled(); + + std::vector specs_to_install; + for (const auto& pkginfo : packages) + { + specs_to_install.push_back(MatchSpec( + fmt::format("{}=={}={}", pkginfo.name, pkginfo.version, pkginfo.build_string))); + } + + m_transaction_context = TransactionContext( + Context::instance().target_prefix, find_python_version(), specs_to_install); + } + MTransaction::~MTransaction() { LOG_INFO << "Freeing transaction."; @@ -1507,4 +1551,25 @@ namespace mamba } return MTransaction(pool, {}, specs_to_install, package_caches); } + + MTransaction create_explicit_transaction_from_lockfile(MPool& pool, + const fs::path& env_lockfile_path, + MultiPackageCache& package_caches) + { + const auto maybe_lockfile = read_environment_lockfile(env_lockfile_path); + if (!maybe_lockfile) + throw maybe_lockfile.error(); // NOTE: we cannot return an `un/expected` because + // MTransaction is not move-enabled. + + const auto lockfile_data = maybe_lockfile.value(); + + constexpr auto default_category = "main"; + constexpr auto default_manager = "conda"; + + const auto packages = lockfile_data.get_packages_for( + default_category, Context::instance().platform, default_manager); + + return MTransaction{ pool, packages, package_caches }; + } + } // namespace mamba diff --git a/libmamba/tests/CMakeLists.txt b/libmamba/tests/CMakeLists.txt index e8e4726bdf..3b49e41057 100644 --- a/libmamba/tests/CMakeLists.txt +++ b/libmamba/tests/CMakeLists.txt @@ -26,6 +26,7 @@ set(TEST_SRCS test_validate.cpp test_virtual_packages.cpp test_util.cpp + test_env_lockfile.cpp ) message(STATUS "Building libmamba C++ tests") @@ -47,6 +48,8 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/validation_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/repodata_json_cache DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/env_lockfile_test + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) target_link_libraries(test_libmamba PRIVATE GTest::GTest GTest::Main Threads::Threads) target_link_libraries(test_libmamba PUBLIC libmamba) diff --git a/libmamba/tests/env_lockfile_test/bad_package-lock.yaml b/libmamba/tests/env_lockfile_test/bad_package-lock.yaml new file mode 100644 index 0000000000..4f545b275c --- /dev/null +++ b/libmamba/tests/env_lockfile_test/bad_package-lock.yaml @@ -0,0 +1,15 @@ +version: 1 + +package: +- category: main + dependencies: {} + ## Commented out some fields to make it an incomplete definition + # hash: + # md5: d7c89558ba9fa0495403155b64376d81 + # sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + # manager: conda + # optional: false + name: _libgcc_mutex + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + version: '0.1' diff --git a/libmamba/tests/env_lockfile_test/bad_version-lock.yaml b/libmamba/tests/env_lockfile_test/bad_version-lock.yaml new file mode 100644 index 0000000000..5343812f05 --- /dev/null +++ b/libmamba/tests/env_lockfile_test/bad_version-lock.yaml @@ -0,0 +1 @@ +version: -1 diff --git a/libmamba/tests/env_lockfile_test/good_multiple_packages-lock.yaml b/libmamba/tests/env_lockfile_test/good_multiple_packages-lock.yaml new file mode 100644 index 0000000000..a37987df80 --- /dev/null +++ b/libmamba/tests/env_lockfile_test/good_multiple_packages-lock.yaml @@ -0,0 +1,189 @@ +# This lock file was generated by conda-lock (https://github.com/conda-incubator/conda-lock). DO NOT EDIT! +# +# A "lock file" contains a concrete list of package versions (with checksums) to be installed. Unlike +# e.g. `conda env create`, the resulting environment will not change as new package versions become +# available, unless you explicitly update the lock file. +# +# Install this environment as "YOURENV" with: +# conda-lock install -n YOURENV --file conda-lock.yml +# To update a single package to the latest version compatible with the version constraints in the source: +# conda-lock lock --lockfile conda-lock.yml --update PACKAGE +# To re-solve the entire environment, e.g. after changing a version constraint in the source file: +# conda-lock -f environment.yml --lockfile conda-lock.yml +metadata: + channels: + - url: conda-forge + used_env_vars: [] + - url: defaults + used_env_vars: [] + content_hash: + linux-64: 1173e3c96ce20d063a5701b325b76deb97394f891af270af4ee0cb7cc1f6e838 + osx-64: d01c1f5433f30bdbcd3bdad8d9b096774ab55f1210c094acdc61a35b32b28d67 + win-64: 310b23581083bfb983927c40d3bdc86162192d7b26ffd7bffc385f627c155697 + platforms: + - linux-64 + - osx-64 + - win-64 + sources: + - environment.yml +package: +- category: main + dependencies: {} + hash: + md5: d7c89558ba9fa0495403155b64376d81 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + manager: conda + name: _libgcc_mutex + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + version: '0.1' +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + hash: + md5: 8e91f1f21417c9ab1265240ee4f9db1e + sha256: 4d705a82c554d1abb80aedd0593e0abde54f71b7a5c87492c750c9759b7706fd + manager: conda + name: libgomp + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_13.tar.bz2 + version: 11.2.0 +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + libgomp: '>=7.5.0' + hash: + md5: 561e277319a41d4f24f5c05a9ef63c04 + sha256: 81c74d38c80345e195106dc3a5b4063b61f2209402bf9f6c7e2abadef4f544a3 + manager: conda + name: _openmp_mutex + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2 + version: '4.5' +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + _openmp_mutex: '>=4.5' + hash: + md5: 63eaf0f146cc80abd84743d48d667da4 + sha256: 5c9c8a23e45215e0c218a477c69054ed2ac577c4499795649dd3343687d380ff + manager: conda + name: libgcc-ng + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2 + version: 11.2.0 +- category: main + dependencies: + libgcc-ng: '>=7.5.0' + hash: + md5: dcddf696ff5dfcab567100d691678e18 + sha256: 8292882ea5cfbe2e6b708432dfab0668f2acddb96ab7618163001acbd13678e4 + manager: conda + name: libzlib + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libgcc-ng: '>=7.5.0' + libzlib: 1.2.11 h36c2ea0_1013 + hash: + md5: cf7190238072a41e9579e4476a6a60b8 + sha256: cec48db35a7def0011bfdaa2b91e5e05d2a0ad788b8871a213eb8cacfeb7418a + manager: conda + name: zlib + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: {} + hash: + md5: a3a6a53beaa92c5cfe52ee3a198e78cc + sha256: 2421046db13b5f161a4330ff4f0e536999bce1ea3b8db5eb0d78e045146707ca + manager: conda + name: libzlib + optional: false + platform: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.11-h9173be1_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libzlib: 1.2.11 h9173be1_1013 + hash: + md5: cf985617d679990418c380099620b01a + sha256: 9102c5f89c78c56b0bb0766a074f509d67362cf97aa66d706d4e95e9061bb03c + manager: conda + name: zlib + optional: false + platform: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.2.11-h9173be1_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: {} + hash: + md5: 6d666b6ea8251231ff508062d1e41f9c + sha256: e5a8634df6ee84745dfe27f40ace7b6e45646a4b7bc7dbeb1efe1bb6128e44b9 + manager: conda + name: ucrt + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.20348.0-h57928b3_0.tar.bz2 + version: 10.0.20348.0 +- category: main + dependencies: + ucrt: '>=10.0.20348.0' + hash: + md5: 33d07ebe91062743eabc9e53a60d18e1 + sha256: f2efbbe3465a34b195edd218d5572c998d94c5964d4e495c3d7f95c8bb5fcaac + manager: conda + name: vs2015_runtime + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.29.30037-h902a5da_6.tar.bz2 + version: 14.29.30037 +- category: main + dependencies: + vs2015_runtime: '>=14.28.29325' + hash: + md5: c2aecbc9b00ba6f352e27d3d61fd31fb + sha256: c6e7d2b9ceafe2cc302fb8fce1dfcc46b49c5333757424a34294bffdfb5569be + manager: conda + name: vc + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.2-hb210afc_6.tar.bz2 + version: '14.2' +- category: main + dependencies: + vc: '>=14.1,<15.0a0' + vs2015_runtime: '>=14.16.27012' + hash: + md5: b28dd2488b4e5f892c67071acc1d0a8c + sha256: 5b7e002932c0138d78d251caae0c571d13f857ff90e7ce21d58d67073381250e + manager: conda + name: libzlib + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.11-h8ffe710_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libzlib: 1.2.11 h8ffe710_1013 + vc: '>=14.1,<15.0a0' + vs2015_runtime: '>=14.16.27012' + hash: + md5: 866517df4fd8bb813bc20c24cf7b8f05 + sha256: 5b5db5ec4c2eb51a2bb8c5e22df9938703fd292da8a41c1e8355d5972f9fe12c + manager: conda + name: zlib + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.11-h8ffe710_1013.tar.bz2 + version: 1.2.11 +version: 1 diff --git a/libmamba/tests/env_lockfile_test/good_no_package-lock.yaml b/libmamba/tests/env_lockfile_test/good_no_package-lock.yaml new file mode 100644 index 0000000000..6c05c047e3 --- /dev/null +++ b/libmamba/tests/env_lockfile_test/good_no_package-lock.yaml @@ -0,0 +1,23 @@ + +version: 1 + +# TODO: change the metadata to make them valid/matching actual locked env +metadata: + channels: + - url: conda-forge + used_env_vars: [] + - url: defaults + used_env_vars: [] + content_hash: + linux-64: 1173e3c96ce20d063a5701b325b76deb97394f891af270af4ee0cb7cc1f6e838 + osx-64: d01c1f5433f30bdbcd3bdad8d9b096774ab55f1210c094acdc61a35b32b28d67 + win-64: 310b23581083bfb983927c40d3bdc86162192d7b26ffd7bffc385f627c155697 + platforms: + - linux-64 + - osx-64 + - win-64 + sources: + - environment.yml + + +package: diff --git a/libmamba/tests/env_lockfile_test/good_one_package-lock.yaml b/libmamba/tests/env_lockfile_test/good_one_package-lock.yaml new file mode 100644 index 0000000000..f54d5a5e62 --- /dev/null +++ b/libmamba/tests/env_lockfile_test/good_one_package-lock.yaml @@ -0,0 +1,34 @@ +version: 1 + +# TODO: change the metadata to make them valid/matching actual locked env +metadata: + channels: + - url: conda-forge + used_env_vars: [] + - url: defaults + used_env_vars: [] + content_hash: + linux-64: 1173e3c96ce20d063a5701b325b76deb97394f891af270af4ee0cb7cc1f6e838 + osx-64: d01c1f5433f30bdbcd3bdad8d9b096774ab55f1210c094acdc61a35b32b28d67 + win-64: 310b23581083bfb983927c40d3bdc86162192d7b26ffd7bffc385f627c155697 + platforms: + - linux-64 + - osx-64 + - win-64 + sources: + - environment.yml + +package: +- category: main + dependencies: + vc: '>=14.1,<15.0a0' + vs2015_runtime: '>=14.16.27012' + hash: + md5: b28dd2488b4e5f892c67071acc1d0a8c + sha256: 5b7e002932c0138d78d251caae0c571d13f857ff90e7ce21d58d67073381250e + manager: conda + name: libzlib + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.11-h8ffe710_1013.tar.bz2 + version: 1.2.11 diff --git a/libmamba/tests/test_env_lockfile.cpp b/libmamba/tests/test_env_lockfile.cpp new file mode 100644 index 0000000000..4046ce15d2 --- /dev/null +++ b/libmamba/tests/test_env_lockfile.cpp @@ -0,0 +1,126 @@ +#include + +#include + +#include "mamba/core/env_lockfile.hpp" +#include "mamba/core/fsutil.hpp" + +namespace mamba +{ + TEST(env_lockfile, absent_file_fails) + { + const auto maybe_lockfile = read_environment_lockfile("this/file/does/not/exists"); + ASSERT_FALSE(maybe_lockfile); + const auto error = maybe_lockfile.error(); + ASSERT_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code()); + + const auto& error_details = EnvLockFileError::get_details(error); + EXPECT_EQ(file_parsing_error_code::parsing_failure, error_details.parsing_error_code); + ASSERT_TRUE(error_details.yaml_error_type); + const std::type_index bad_file_error_id{ typeid(YAML::BadFile) }; + EXPECT_EQ(bad_file_error_id, error_details.yaml_error_type.value()); + + // NOTE: one could attempt to check if opening a file which is not an YAML file + // would fail. Unfortunately YAML parsers will accept any kind of file, + // and assume it is YAML or at worse a comment or raw string. So there + // is no good way to check that. + } + + TEST(env_lockfile, invalid_version_fails) + { + const fs::path invalid_version_lockfile_path{ "env_lockfile_test/bad_version-lock.yaml" }; + const auto maybe_lockfile = read_environment_lockfile(invalid_version_lockfile_path); + ASSERT_FALSE(maybe_lockfile); + const auto error = maybe_lockfile.error(); + ASSERT_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code()); + const auto& error_details = EnvLockFileError::get_details(error); + EXPECT_EQ(file_parsing_error_code::unsuported_version, error_details.parsing_error_code); + } + + TEST(env_lockfile, valid_no_package_succeed) + { + const fs::path lockfile_path{ "env_lockfile_test/good_no_package-lock.yaml" }; + const auto maybe_lockfile = read_environment_lockfile(lockfile_path); + ASSERT_TRUE(maybe_lockfile) << maybe_lockfile.error().what(); + const auto lockfile = maybe_lockfile.value(); + EXPECT_TRUE(lockfile.get_all_packages().empty()); + } + + TEST(env_lockfile, invalid_package_fails) + { + const fs::path lockfile_path{ "env_lockfile_test/bad_package-lock.yaml" }; + const auto maybe_lockfile = read_environment_lockfile(lockfile_path); + ASSERT_FALSE(maybe_lockfile); + const auto error = maybe_lockfile.error(); + ASSERT_EQ(mamba_error_code::env_lockfile_parsing_failed, error.error_code()); + const auto& error_details = EnvLockFileError::get_details(error); + EXPECT_EQ(file_parsing_error_code::parsing_failure, error_details.parsing_error_code); + } + + TEST(env_lockfile, valid_one_package_succeed) + { + const fs::path lockfile_path{ "env_lockfile_test/good_one_package-lock.yaml" }; + const auto maybe_lockfile = read_environment_lockfile(lockfile_path); + ASSERT_TRUE(maybe_lockfile) << maybe_lockfile.error().what(); + const auto lockfile = maybe_lockfile.value(); + EXPECT_EQ(lockfile.get_all_packages().size(), 1); + } + + TEST(env_lockfile, valid_multiple_packages_succeed) + { + const fs::path lockfile_path{ "env_lockfile_test/good_multiple_packages-lock.yaml" }; + const auto maybe_lockfile = read_environment_lockfile(lockfile_path); + ASSERT_TRUE(maybe_lockfile) << maybe_lockfile.error().what(); + const auto lockfile = maybe_lockfile.value(); + EXPECT_GT(lockfile.get_all_packages().size(), 1); + } + + TEST(env_lockfile, get_specific_packages) + { + const fs::path lockfile_path{ "env_lockfile_test/good_multiple_packages-lock.yaml" }; + const auto lockfile = read_environment_lockfile(lockfile_path).value(); + EXPECT_TRUE(lockfile.get_packages_for("", "", "").empty()); + { + const auto packages = lockfile.get_packages_for("main", "linux-64", "conda"); + EXPECT_FALSE(packages.empty()); + EXPECT_GT(packages.size(), 4); + } + } + + + TEST(is_env_lockfile_name, basics) + { + EXPECT_TRUE(is_env_lockfile_name("something-lock.yaml")); + EXPECT_TRUE(is_env_lockfile_name("something-lock.yml")); + EXPECT_TRUE(is_env_lockfile_name("/some/dir/something-lock.yaml")); + EXPECT_TRUE(is_env_lockfile_name("/some/dir/something-lock.yml")); + EXPECT_TRUE(is_env_lockfile_name("../../some/dir/something-lock.yaml")); + EXPECT_TRUE(is_env_lockfile_name("../../some/dir/something-lock.yml")); + + EXPECT_TRUE(is_env_lockfile_name(fs::path{ "something-lock.yaml" }.string())); + EXPECT_TRUE(is_env_lockfile_name(fs::path{ "something-lock.yml" }.string())); + EXPECT_TRUE(is_env_lockfile_name(fs::path{ "/some/dir/something-lock.yaml" }.string())); + EXPECT_TRUE(is_env_lockfile_name(fs::path{ "/some/dir/something-lock.yml" }.string())); + EXPECT_TRUE( + is_env_lockfile_name(fs::path{ "../../some/dir/something-lock.yaml" }.string())); + EXPECT_TRUE(is_env_lockfile_name(fs::path{ "../../some/dir/something-lock.yml" }.string())); + + EXPECT_FALSE(is_env_lockfile_name("something")); + EXPECT_FALSE(is_env_lockfile_name("something-lock")); + EXPECT_FALSE(is_env_lockfile_name("/some/dir/something")); + EXPECT_FALSE(is_env_lockfile_name("../../some/dir/something")); + + EXPECT_FALSE(is_env_lockfile_name("something.yaml")); + EXPECT_FALSE(is_env_lockfile_name("something.yml")); + EXPECT_FALSE(is_env_lockfile_name("/some/dir/something.yaml")); + EXPECT_FALSE(is_env_lockfile_name("/some/dir/something.yml")); + EXPECT_FALSE(is_env_lockfile_name("../../some/dir/something.yaml")); + EXPECT_FALSE(is_env_lockfile_name("../../some/dir/something.yml")); + + EXPECT_FALSE(is_env_lockfile_name(fs::path{ "something" }.string())); + EXPECT_FALSE(is_env_lockfile_name(fs::path{ "something-lock" }.string())); + EXPECT_FALSE(is_env_lockfile_name(fs::path{ "/some/dir/something" }.string())); + EXPECT_FALSE(is_env_lockfile_name(fs::path{ "../../some/dir/something" }.string())); + } + +} diff --git a/libmamba/tests/test_util.cpp b/libmamba/tests/test_util.cpp index 8663257116..037a61150a 100644 --- a/libmamba/tests/test_util.cpp +++ b/libmamba/tests/test_util.cpp @@ -4,6 +4,8 @@ #include "mamba/core/util.hpp" #include "mamba/core/util_random.hpp" +#include "mamba/core/util_scope.hpp" + namespace mamba { @@ -44,4 +46,46 @@ namespace mamba EXPECT_LE(value, arbitrary_max); } } + + TEST(on_scope_exit, basics) + { + bool executed = false; + { + on_scope_exit _{ [&] { executed = true; } }; + EXPECT_FALSE(executed); + } + EXPECT_TRUE(executed); + } + + TEST(is_yaml_file_name, basics) + { + EXPECT_TRUE(is_yaml_file_name("something.yaml")); + EXPECT_TRUE(is_yaml_file_name("something.yml")); + EXPECT_TRUE(is_yaml_file_name("something-lock.yaml")); + EXPECT_TRUE(is_yaml_file_name("something-lock.yml")); + EXPECT_TRUE(is_yaml_file_name("/some/dir/something.yaml")); + EXPECT_TRUE(is_yaml_file_name("/some/dir/something.yaml")); + EXPECT_TRUE(is_yaml_file_name("../../some/dir/something.yml")); + EXPECT_TRUE(is_yaml_file_name("../../some/dir/something.yml")); + + EXPECT_TRUE(is_yaml_file_name(fs::path{ "something.yaml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "something.yml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "something-lock.yaml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "something-lock.yml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "/some/dir/something.yaml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "/some/dir/something.yml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "../../some/dir/something.yaml" }.string())); + EXPECT_TRUE(is_yaml_file_name(fs::path{ "../../some/dir/something.yml" }.string())); + + EXPECT_FALSE(is_yaml_file_name("something")); + EXPECT_FALSE(is_yaml_file_name("something-lock")); + EXPECT_FALSE(is_yaml_file_name("/some/dir/something")); + EXPECT_FALSE(is_yaml_file_name("../../some/dir/something")); + + EXPECT_FALSE(is_yaml_file_name(fs::path{ "something" }.string())); + EXPECT_FALSE(is_yaml_file_name(fs::path{ "something-lock" }.string())); + EXPECT_FALSE(is_yaml_file_name(fs::path{ "/some/dir/something" }.string())); + EXPECT_FALSE(is_yaml_file_name(fs::path{ "../../some/dir/something" }.string())); + } + } From 22d91d3f604f9a82cae7fc9a1d95ad6b05249ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Lamotte=20=28Klaim=29?= Date: Thu, 7 Apr 2022 11:50:19 +0200 Subject: [PATCH 2/2] micromamba tests: added lockfile test --- micromamba/tests/test_create.py | 27 +++- micromamba/tests/test_env-lock.yaml | 189 ++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 micromamba/tests/test_env-lock.yaml diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 755f4b18ff..b4aae3c2fc 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -11,6 +11,8 @@ from .helpers import * +source_dir_path = os.path.dirname(os.path.realpath(__file__)) + class TestCreate: @@ -26,8 +28,13 @@ class TestCreate: os.path.join("~", "mamba_spec_files_test_" + random_string()) ) + test_lockfile_path = os.path.realpath( + os.path.join(source_dir_path, "test_env-lock.yaml") + ) + @classmethod def setup_class(cls): + assert os.path.exists(TestCreate.test_lockfile_path) os.environ["MAMBA_ROOT_PREFIX"] = TestCreate.root_prefix os.makedirs(TestCreate.spec_files_location, exist_ok=True) @@ -100,10 +107,12 @@ def test_specs(self, source, file_type, existing_cache): ] file_content = ["@EXPLICIT"] + explicit_specs specs = explicit_specs - else: # yaml + elif file_type == "yaml": spec_file += ".yaml" file_content = ["dependencies:", " - xtensor >0.20", " - xsimd"] specs += ["xtensor >0.20", "xsimd"] + else: + raise RuntimeError("unhandled file type : ", file_type) os.makedirs(TestCreate.root_prefix, exist_ok=True) with open(spec_file, "w") as f: @@ -120,6 +129,22 @@ def test_specs(self, source, file_type, existing_cache): json_res = create(*cmd, "--json") assert json_res["success"] == True + def test_lockfile(self): + cmd_prefix = ["-p", TestCreate.prefix] + f_name = random_string() + spec_file = os.path.join(TestCreate.spec_files_location, f_name) + "-lock.yaml" + shutil.copyfile(TestCreate.test_lockfile_path, spec_file) + assert os.path.exists(spec_file) + + res = create(*cmd_prefix, "-f", spec_file, "--json") + assert res["success"] == True + + packages = umamba_list(*cmd_prefix, "--json") + assert any( + package["name"] == "zlib" and package["version"] == "1.2.11" + for package in packages + ) + @pytest.mark.parametrize("root_prefix", (None, "env_var", "cli")) @pytest.mark.parametrize("target_is_root", (False, True)) @pytest.mark.parametrize("cli_prefix", (False, True)) diff --git a/micromamba/tests/test_env-lock.yaml b/micromamba/tests/test_env-lock.yaml new file mode 100644 index 0000000000..a37987df80 --- /dev/null +++ b/micromamba/tests/test_env-lock.yaml @@ -0,0 +1,189 @@ +# This lock file was generated by conda-lock (https://github.com/conda-incubator/conda-lock). DO NOT EDIT! +# +# A "lock file" contains a concrete list of package versions (with checksums) to be installed. Unlike +# e.g. `conda env create`, the resulting environment will not change as new package versions become +# available, unless you explicitly update the lock file. +# +# Install this environment as "YOURENV" with: +# conda-lock install -n YOURENV --file conda-lock.yml +# To update a single package to the latest version compatible with the version constraints in the source: +# conda-lock lock --lockfile conda-lock.yml --update PACKAGE +# To re-solve the entire environment, e.g. after changing a version constraint in the source file: +# conda-lock -f environment.yml --lockfile conda-lock.yml +metadata: + channels: + - url: conda-forge + used_env_vars: [] + - url: defaults + used_env_vars: [] + content_hash: + linux-64: 1173e3c96ce20d063a5701b325b76deb97394f891af270af4ee0cb7cc1f6e838 + osx-64: d01c1f5433f30bdbcd3bdad8d9b096774ab55f1210c094acdc61a35b32b28d67 + win-64: 310b23581083bfb983927c40d3bdc86162192d7b26ffd7bffc385f627c155697 + platforms: + - linux-64 + - osx-64 + - win-64 + sources: + - environment.yml +package: +- category: main + dependencies: {} + hash: + md5: d7c89558ba9fa0495403155b64376d81 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + manager: conda + name: _libgcc_mutex + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + version: '0.1' +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + hash: + md5: 8e91f1f21417c9ab1265240ee4f9db1e + sha256: 4d705a82c554d1abb80aedd0593e0abde54f71b7a5c87492c750c9759b7706fd + manager: conda + name: libgomp + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_13.tar.bz2 + version: 11.2.0 +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + libgomp: '>=7.5.0' + hash: + md5: 561e277319a41d4f24f5c05a9ef63c04 + sha256: 81c74d38c80345e195106dc3a5b4063b61f2209402bf9f6c7e2abadef4f544a3 + manager: conda + name: _openmp_mutex + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2 + version: '4.5' +- category: main + dependencies: + _libgcc_mutex: 0.1 conda_forge + _openmp_mutex: '>=4.5' + hash: + md5: 63eaf0f146cc80abd84743d48d667da4 + sha256: 5c9c8a23e45215e0c218a477c69054ed2ac577c4499795649dd3343687d380ff + manager: conda + name: libgcc-ng + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2 + version: 11.2.0 +- category: main + dependencies: + libgcc-ng: '>=7.5.0' + hash: + md5: dcddf696ff5dfcab567100d691678e18 + sha256: 8292882ea5cfbe2e6b708432dfab0668f2acddb96ab7618163001acbd13678e4 + manager: conda + name: libzlib + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libgcc-ng: '>=7.5.0' + libzlib: 1.2.11 h36c2ea0_1013 + hash: + md5: cf7190238072a41e9579e4476a6a60b8 + sha256: cec48db35a7def0011bfdaa2b91e5e05d2a0ad788b8871a213eb8cacfeb7418a + manager: conda + name: zlib + optional: false + platform: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: {} + hash: + md5: a3a6a53beaa92c5cfe52ee3a198e78cc + sha256: 2421046db13b5f161a4330ff4f0e536999bce1ea3b8db5eb0d78e045146707ca + manager: conda + name: libzlib + optional: false + platform: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.11-h9173be1_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libzlib: 1.2.11 h9173be1_1013 + hash: + md5: cf985617d679990418c380099620b01a + sha256: 9102c5f89c78c56b0bb0766a074f509d67362cf97aa66d706d4e95e9061bb03c + manager: conda + name: zlib + optional: false + platform: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.2.11-h9173be1_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: {} + hash: + md5: 6d666b6ea8251231ff508062d1e41f9c + sha256: e5a8634df6ee84745dfe27f40ace7b6e45646a4b7bc7dbeb1efe1bb6128e44b9 + manager: conda + name: ucrt + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.20348.0-h57928b3_0.tar.bz2 + version: 10.0.20348.0 +- category: main + dependencies: + ucrt: '>=10.0.20348.0' + hash: + md5: 33d07ebe91062743eabc9e53a60d18e1 + sha256: f2efbbe3465a34b195edd218d5572c998d94c5964d4e495c3d7f95c8bb5fcaac + manager: conda + name: vs2015_runtime + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.29.30037-h902a5da_6.tar.bz2 + version: 14.29.30037 +- category: main + dependencies: + vs2015_runtime: '>=14.28.29325' + hash: + md5: c2aecbc9b00ba6f352e27d3d61fd31fb + sha256: c6e7d2b9ceafe2cc302fb8fce1dfcc46b49c5333757424a34294bffdfb5569be + manager: conda + name: vc + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.2-hb210afc_6.tar.bz2 + version: '14.2' +- category: main + dependencies: + vc: '>=14.1,<15.0a0' + vs2015_runtime: '>=14.16.27012' + hash: + md5: b28dd2488b4e5f892c67071acc1d0a8c + sha256: 5b7e002932c0138d78d251caae0c571d13f857ff90e7ce21d58d67073381250e + manager: conda + name: libzlib + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.11-h8ffe710_1013.tar.bz2 + version: 1.2.11 +- category: main + dependencies: + libzlib: 1.2.11 h8ffe710_1013 + vc: '>=14.1,<15.0a0' + vs2015_runtime: '>=14.16.27012' + hash: + md5: 866517df4fd8bb813bc20c24cf7b8f05 + sha256: 5b5db5ec4c2eb51a2bb8c5e22df9938703fd292da8a41c1e8355d5972f9fe12c + manager: conda + name: zlib + optional: false + platform: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.11-h8ffe710_1013.tar.bz2 + version: 1.2.11 +version: 1