[Micromamba] Add `env update` (#2827)

* Remove weird piece of code

* Add micromamba env update

* Add env update test

* Add xtl check in tests
This commit is contained in:
Hind-M 2023-09-13 13:15:37 +02:00 committed by GitHub
parent e064563ee4
commit 806287599c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 55 deletions

View File

@ -16,7 +16,12 @@
namespace mamba
{
void update(Configuration& config, bool update_all = false, bool prune = false);
void update(
Configuration& config,
bool update_all = false,
bool prune_deps = false,
bool remove_not_specified = false
);
}
#endif

View File

@ -16,7 +16,7 @@
namespace mamba
{
void update(Configuration& config, bool update_all, bool prune)
void update(Configuration& config, bool update_all, bool prune_deps, bool remove_not_specified)
{
auto& ctx = config.context();
@ -118,7 +118,7 @@ namespace mamba
keep_specs.push_back(it.second.name);
}
solver_flag |= SOLVER_SOLVABLE_ALL;
if (prune)
if (prune_deps)
{
solver_flag |= SOLVER_CLEANDEPS;
}
@ -127,6 +127,20 @@ namespace mamba
}
else
{
if (remove_not_specified)
{
auto hist_map = prefix_data.history().get_requested_specs_map();
std::vector<std::string> remove_specs;
for (auto& it : hist_map)
{
if (std::find(update_specs.begin(), update_specs.end(), it.second.name)
== update_specs.end())
{
remove_specs.push_back(it.second.name);
}
}
solver.add_jobs(remove_specs, SOLVER_ERASE | SOLVER_CLEANDEPS);
}
solver.add_jobs(update_specs, solver_flag);
}

View File

@ -9,6 +9,7 @@
#include "mamba/api/configuration.hpp"
#include "mamba/api/create.hpp"
#include "mamba/api/remove.hpp"
#include "mamba/api/update.hpp"
#include "mamba/core/channel.hpp"
#include "mamba/core/environments_manager.hpp"
#include "mamba/core/prefix_data.hpp"
@ -45,16 +46,60 @@ set_env_command(CLI::App* com, Configuration& config)
init_general_options(com, config);
init_prefix_options(com, config);
// env list subcommand
auto* list_subcom = com->add_subcommand("list", "List known environments");
init_general_options(list_subcom, config);
init_prefix_options(list_subcom, config);
list_subcom->callback(
[&config]
{
const auto& ctx = config.context();
config.load();
EnvironmentsManager env_manager{ ctx };
if (ctx.output_params.json)
{
nlohmann::json res;
const auto pfxs = env_manager.list_all_known_prefixes();
std::vector<std::string> envs(pfxs.size());
std::transform(
pfxs.begin(),
pfxs.end(),
envs.begin(),
[](const fs::u8path& path) { return path.string(); }
);
res["envs"] = envs;
std::cout << res.dump(4) << std::endl;
return;
}
// format and print table
printers::Table t({ "Name", "Active", "Path" });
t.set_alignment(
{ printers::alignment::left, printers::alignment::left, printers::alignment::left }
);
t.set_padding({ 2, 2, 2 });
for (auto& env : env_manager.list_all_known_prefixes())
{
bool is_active = (env == ctx.prefix_params.target_prefix);
t.add_row({ get_env_name(ctx, env), is_active ? "*" : "", env.string() });
}
t.print(std::cout);
}
);
// env create subcommand
auto* create_subcom = com->add_subcommand(
"create",
"Create new environment (pre-commit.com compatibility alias for 'micromamba create')"
);
init_install_options(create_subcom, config);
create_subcom->callback([&] { return mamba::create(config); });
// env export subcommand
static bool explicit_format = false;
static int no_md5 = 0;
static bool no_build = false;
@ -62,8 +107,10 @@ set_env_command(CLI::App* com, Configuration& config)
static bool from_history = false;
auto* export_subcom = com->add_subcommand("export", "Export environment");
init_general_options(export_subcom, config);
init_prefix_options(export_subcom, config);
export_subcom->add_flag("-e,--explicit", explicit_format, "Use explicit format");
export_subcom->add_flag("--no-md5,!--md5", no_md5, "Disable md5");
export_subcom->add_flag("--no-build,!--build", no_build, "Disable the build string in spec");
@ -161,52 +208,11 @@ set_env_command(CLI::App* com, Configuration& config)
}
);
list_subcom->callback(
[&config]
{
const auto& ctx = config.context();
config.load();
EnvironmentsManager env_manager{ ctx };
if (ctx.output_params.json)
{
nlohmann::json res;
const auto pfxs = env_manager.list_all_known_prefixes();
std::vector<std::string> envs(pfxs.size());
std::transform(
pfxs.begin(),
pfxs.end(),
envs.begin(),
[](const fs::u8path& path) { return path.string(); }
);
res["envs"] = envs;
std::cout << res.dump(4) << std::endl;
return;
}
// format and print table
printers::Table t({ "Name", "Active", "Path" });
t.set_alignment(
{ printers::alignment::left, printers::alignment::left, printers::alignment::left }
);
t.set_padding({ 2, 2, 2 });
for (auto& env : env_manager.list_all_known_prefixes())
{
bool is_active = (env == ctx.prefix_params.target_prefix);
t.add_row({ get_env_name(ctx, env), is_active ? "*" : "", env.string() });
}
t.print(std::cout);
}
);
// env remove subcommand
auto* remove_subcom = com->add_subcommand("remove", "Remove an environment");
init_general_options(remove_subcom, config);
init_prefix_options(remove_subcom, config);
create_subcom->callback([&] { return mamba::create(config); });
remove_subcom->callback(
[&config]
{
@ -236,4 +242,29 @@ set_env_command(CLI::App* com, Configuration& config)
}
}
);
// env update subcommand
auto* update_subcom = com->add_subcommand("update", "Update an environment");
init_general_options(update_subcom, config);
init_prefix_options(update_subcom, config);
auto& file_specs = config.at("file_specs");
update_subcom->add_option(
"-f,--file",
file_specs.get_cli_config<std::vector<std::string>>(),
file_specs.description()
);
static bool remove_not_specified = false;
update_subcom->add_flag(
"--prune",
remove_not_specified,
"Remove installed packages not specified in the command and in environment file"
);
update_subcom->callback(
[&config]
{ update(config, /*update_all*/ false, /*prune_deps*/ false, remove_not_specified); }
);
}

View File

@ -26,20 +26,20 @@ set_remove_command(CLI::App* subcom, Configuration& config)
"Specs to remove from the environment"
);
static bool remove_all = false, force = false, prune = true;
static bool remove_all = false, force = false, prune_deps = true;
subcom->add_flag("-a,--all", remove_all, "Remove all packages in the environment");
subcom->add_flag(
"-f,--force",
force,
"Force removal of package (note: consistency of environment is not guaranteed!"
);
subcom->add_flag("--prune,!--no-prune", prune, "Prune dependencies (default)");
subcom->add_flag("--prune-deps,!--no-prune-deps", prune_deps, "Prune dependencies (default)");
subcom->callback(
[&config]
{
int flags = 0;
if (prune)
if (prune_deps)
{
flags |= MAMBA_REMOVE_PRUNE;
}

View File

@ -164,14 +164,14 @@ set_update_command(CLI::App* subcom, Configuration& config)
{
init_install_options(subcom, config);
static bool prune = true;
static bool prune_deps = true;
static bool update_all = false;
subcom->add_flag("--prune,!--no-prune", prune, "Prune dependencies (default)");
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->callback([&] { return update(config, update_all, prune); });
subcom->callback([&] { return update(config, update_all, prune_deps); });
}
void

View File

@ -264,8 +264,6 @@ def update(*args, default_channel=True, no_rc=True, no_dry_run=False, **kwargs):
except json.decoder.JSONDecodeError as e:
print(f"Error when loading JSON output from {res}")
raise (e)
print(f"Error when executing '{' '.join(cmd)}'")
raise
return res.decode()
except subprocess.CalledProcessError as e:

View File

@ -127,6 +127,61 @@ def test_env_remove(tmp_home, tmp_root_prefix):
assert str(env_fp) not in lines
env_yaml_content = """
channels:
- conda-forge
dependencies:
- python
"""
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
@pytest.mark.parametrize("prune", (False, True))
def test_env_update(tmp_home, tmp_root_prefix, tmp_path, prune):
env_name = "env-create-update"
# Create env with python=3.6.15 and xtensor=0.20.0
helpers.create(
"python=3.6.15", "xtensor=0.20.0", "-n", env_name, "--json", no_dry_run=True
)
packages = helpers.umamba_list("-n", env_name, "--json")
assert any(
package["name"] == "python" and package["version"] == "3.6.15"
for package in packages
)
assert any(
package["name"] == "xtensor" and package["version"] == "0.20.0"
for package in packages
)
assert any(package["name"] == "xtl" for package in packages)
# Update python
from packaging.version import Version
env_file_yml = tmp_path / "test_env.yaml"
env_file_yml.write_text(env_yaml_content)
cmd = ["update", "-n", env_name, f"--file={env_file_yml}", "-y"]
if prune:
cmd += ["--prune"]
helpers.run_env(*cmd)
packages = helpers.umamba_list("-n", env_name, "--json")
assert any(
package["name"] == "python" and Version(package["version"]) > Version("3.6.15")
for package in packages
)
if prune:
assert not any(package["name"] == "xtensor" for package in packages)
# Make sure dependencies of removed pkgs are removed as well (xtl is a dep of xtensor)
assert not any(package["name"] == "xtl" for package in packages)
else:
assert any(
package["name"] == "xtensor" and package["version"] == "0.20.0"
for package in packages
)
assert any(package["name"] == "xtl" for package in packages)
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_explicit_export_topologically_sorted(tmp_home, tmp_prefix):
"""Explicit export must have dependencies before dependent packages."""

View File

@ -104,11 +104,11 @@ class TestRemove:
assert res["actions"]["UNLINK"][0]["name"] == "xtl"
assert res["actions"]["PREFIX"] == TestRemove.prefix
def test_remove_noprune(self, env_created):
def test_remove_no_prune_deps(self, env_created):
env_pkgs = [p["name"] for p in umamba_list("-p", TestRemove.prefix, "--json")]
install("xframe", "-n", TestRemove.env_name, no_dry_run=True)
res = remove("xtensor", "-p", TestRemove.prefix, "--json", "--no-prune")
res = remove("xtensor", "-p", TestRemove.prefix, "--json", "--no-prune-deps")
keys = {"dry_run", "success", "prefix", "actions"}
assert keys.issubset(set(res.keys()))