Support multiple env yaml specs (#2993)

* tested and working

* docs update

* disallow multiple spec types

* add test for only one yaml specifies channel

* fix install yaml test

* Update micromamba/tests/test_create.py

Co-authored-by: Jonas Haag <jonas@lophus.org>

* Update micromamba/tests/test_create.py

Co-authored-by: Jonas Haag <jonas@lophus.org>

* suggestions

* separate loop

* appease the linter

* another test

---------

Co-authored-by: Jonas Haag <jonas@lophus.org>
This commit is contained in:
Josh Chorlton 2023-12-19 07:56:59 +00:00 committed by GitHub
parent 212d1e9ad2
commit 57a6a691be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 34 deletions

View File

@ -1,6 +1,7 @@
channels:
- conda-forge
dependencies:
- python >=3.12
# libmamba build dependencies
- cxx-compiler
- cmake >=3.16

View File

@ -188,7 +188,7 @@ To build from source, install the development dependencies, using a Conda compat
.. code-block:: bash
micromamba create -n mamba --file dev/environment-static.yml
micromamba create -n mamba --file dev/environment-micromamba-static.yml
micromamba activate -n mamba
Use CMake from this environment to drive the build:

View File

@ -167,8 +167,8 @@ They are used the same way as text files:
.. note::
CLI options will keep :ref:`precedence<precedence-resolution>` over *target prefix* or *channels* specified in spec files.
.. warning::
``YAML`` spec files do not allow multiple files.
.. note::
You can pass multiple ``YAML`` spec files by repeating the ``-f,--file`` argument.
Explicit spec files
*******************

View File

@ -782,6 +782,14 @@ namespace mamba
namespace detail
{
enum SpecType
{
unknown,
env_lockfile,
yaml,
other
};
void create_empty_target(const Context& context, const fs::u8path& prefix)
{
detail::create_target_directory(context, prefix);
@ -816,12 +824,31 @@ namespace mamba
return;
}
for (const auto& file : file_specs)
mamba::detail::SpecType spec_type = mamba::detail::unknown;
for (auto& file : file_specs)
{
if (is_yaml_file_name(file) && file_specs.size() != 1)
mamba::detail::SpecType current_file_spec_type = mamba::detail::unknown;
if (is_env_lockfile_name(file))
{
throw std::runtime_error("Can only handle 1 yaml file!");
current_file_spec_type = mamba::detail::env_lockfile;
}
else if (is_yaml_file_name(file))
{
current_file_spec_type = mamba::detail::yaml;
}
else
{
current_file_spec_type = mamba::detail::other;
}
if (spec_type != mamba::detail::unknown && spec_type != current_file_spec_type)
{
throw std::runtime_error(
"found multiple spec file types, all spec files must be of same format (yaml, txt, explicit spec, etc.)"
);
}
spec_type = current_file_spec_type;
}
for (auto& file : file_specs)
@ -858,10 +885,15 @@ namespace mamba
channels.set_cli_value(updated_channels);
}
if (parse_result.name.size() != 0)
if (parse_result.name.size() != 0 && !env_name.configured())
{
env_name.set_cli_yaml_value(parse_result.name);
}
else if (parse_result.name.size() != 0 && parse_result.name != env_name.value<std::string>())
{
LOG_WARNING << "YAML specs have different environment names. Using "
<< env_name.value<std::string>();
}
if (parse_result.dependencies.size() != 0)
{

View File

@ -318,15 +318,51 @@ def test_multiple_spec_files(tmp_home, tmp_root_prefix, tmp_path, type):
cmd += ["-f", spec_file]
if type == "yaml":
with pytest.raises(subprocess.CalledProcessError):
helpers.create(*cmd, "--print-config-only")
else:
res = helpers.create(*cmd, "--print-config-only")
if type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
res = helpers.create(*cmd, "--print-config-only")
if type == "yaml" or type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_multiple_spec_files_different_types(tmp_home, tmp_root_prefix, tmp_path):
env_prefix = tmp_path / "myenv"
cmd = ["-p", env_prefix]
spec_file_1 = tmp_path / "env1.yaml"
spec_file_1.write_text("dependencies: [xtensor]")
spec_file_2 = tmp_path / "env2.txt"
spec_file_2.write_text("xsimd")
cmd += ["-f", spec_file_1, "-f", spec_file_2]
with pytest.raises(subprocess.CalledProcessError) as info:
helpers.create(*cmd, "--print-config-only")
assert "found multiple spec file types" in info.value.stderr.decode()
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
def test_multiple_yaml_specs_only_one_has_channels(tmp_home, tmp_root_prefix, tmp_path):
env_prefix = tmp_path / "myenv"
cmd = ["-p", env_prefix]
spec_file_1 = tmp_path / "env1.yaml"
spec_file_1.write_text("dependencies: [xtensor]")
spec_file_2 = tmp_path / "env2.yaml"
spec_file_2.write_text(
"dependencies: [xsimd]\nchannels: [bioconda]",
)
cmd += ["-f", spec_file_1, "-f", spec_file_2]
res = helpers.create(*cmd, "--print-config-only", default_channel=False)
assert res["channels"] == ["bioconda"]
assert res["specs"] == ["xtensor", "xsimd"]
def test_multiprocessing():

View File

@ -266,15 +266,11 @@ class TestInstall:
cmd += ["-f", file]
if type == "yaml":
with pytest.raises(subprocess.CalledProcessError):
helpers.install(*cmd, "--print-config-only")
else:
res = helpers.install(*cmd, "--print-config-only")
if type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
res = helpers.install(*cmd, "--print-config-only")
if type == "yaml" or type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
@pytest.mark.parametrize("priority", (None, "disabled", "flexible", "strict"))
@pytest.mark.parametrize("no_priority", (None, True))

View File

@ -461,15 +461,11 @@ class TestUpdateConfig:
cmd += ["-f", file]
if type == "yaml":
with pytest.raises(helpers.subprocess.CalledProcessError):
helpers.install(*cmd, "--print-config-only")
else:
res = helpers.install(*cmd, "--print-config-only")
if type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
res = helpers.install(*cmd, "--print-config-only")
if type == "yaml" or type == "classic":
assert res["specs"] == specs
else: # explicit
assert res["specs"] == [explicit_specs[0]]
def test_channel_specific(self, env_created):
helpers.install("quantstack::sphinx", no_dry_run=True)