Skip to content

Commit

Permalink
Support multiple env yaml specs (#2993)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update micromamba/tests/test_create.py

Co-authored-by: Jonas Haag <[email protected]>

* suggestions

* separate loop

* appease the linter

* another test

---------

Co-authored-by: Jonas Haag <[email protected]>
  • Loading branch information
jchorl and jonashaag authored Dec 19, 2023
1 parent 212d1e9 commit 57a6a69
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 34 deletions.
1 change: 1 addition & 0 deletions dev/environment-micromamba-static.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
channels:
- conda-forge
dependencies:
- python >=3.12
# libmamba build dependencies
- cxx-compiler
- cmake >=3.16
Expand Down
2 changes: 1 addition & 1 deletion docs/source/installation/micromamba-installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions docs/source/user_guide/micromamba.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
*******************
Expand Down
40 changes: 36 additions & 4 deletions libmamba/src/api/install.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down
54 changes: 45 additions & 9 deletions micromamba/tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
14 changes: 5 additions & 9 deletions micromamba/tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,11 @@ def test_multiple_spec_files(self, type, existing_cache):

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))
Expand Down
14 changes: 5 additions & 9 deletions micromamba/tests/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,15 +461,11 @@ def test_multiple_spec_files(self, type, env_created):

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)
Expand Down

0 comments on commit 57a6a69

Please sign in to comment.