mirror of https://github.com/mamba-org/mamba.git
[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:
parent
e064563ee4
commit
806287599c
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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); }
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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()))
|
||||
|
|
Loading…
Reference in New Issue