diff --git a/constructor/nsis/main.nsi.tmpl b/constructor/nsis/main.nsi.tmpl index da08f58bf..1bb81ba52 100644 --- a/constructor/nsis/main.nsi.tmpl +++ b/constructor/nsis/main.nsi.tmpl @@ -1638,8 +1638,8 @@ Section "Install" SetOutPath "{{ env.conda_meta }}" File "{{ env.history_abspath }}" - # Add frozen marker file if configured {%- if env.frozen_abspath %} + # Add frozen marker file if configured SetOutPath "{{ env.conda_meta }}" File "{{ env.frozen_abspath }}" {%- endif %} diff --git a/examples/protected_base/construct.yaml b/examples/protected_base/construct.yaml index c43044761..7e2e09a49 100644 --- a/examples/protected_base/construct.yaml +++ b/examples/protected_base/construct.yaml @@ -12,13 +12,18 @@ specs: - python - conda +freeze_base: + conda: {} + extra_envs: - default: + env1: specs: - python - pip - conda + freeze_env: + conda: + message: This environment is frozen. extra_files: - - frozen.json: conda-meta/frozen - - frozen.json: envs/default/conda-meta/frozen + - frozen.json: envs/env2/conda-meta/frozen diff --git a/examples/protected_base/frozen.json b/examples/protected_base/frozen.json index 0967ef424..087c29648 100644 --- a/examples/protected_base/frozen.json +++ b/examples/protected_base/frozen.json @@ -1 +1 @@ -{} +{"message": "This env is frozen via extra_files."} diff --git a/tests/test_examples.py b/tests/test_examples.py index e68d04052..49f3c6149 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -20,6 +20,7 @@ from conda.core.prefix_data import PrefixData from conda.models.version import VersionOrder as Version from ruamel.yaml import YAML +from contextlib import nullcontext from constructor.utils import ( StandaloneExe, @@ -1509,32 +1510,44 @@ def test_not_in_installed_menu_list_(tmp_path, request, no_registry): CONDA_EXE == StandaloneExe.CONDA and check_version(CONDA_EXE_VERSION, min_version="25.5.0", max_version="25.7.0") ), - reason="conda-standalone 25.5.x fails with protected environments and older versions ignore frozen files", + reason="conda-standalone 25.5.x fails with protected environments", strict=True, ) -def test_frozen_environment(tmp_path, request): - input_path = _example_path("protected_base") - for installer, install_dir in create_installer(input_path, tmp_path): - _run_installer( - input_path, - installer, - install_dir, - request=request, - uninstall=False, - ) +@pytest.mark.parametrize( + "has_conflict", + ( + pytest.param(True, id="with-conflict"), + pytest.param(False, id="without-conflict"), + ), +) +def test_frozen_environment(tmp_path, request, has_conflict): + example_path = _example_path("protected_base") + input_path = tmp_path / "input" - expected_frozen_paths = { - install_dir / "conda-meta" / "frozen", - install_dir / "envs" / "default" / "conda-meta" / "frozen", - } + context = pytest.raises(subprocess.CalledProcessError) if has_conflict else nullcontext() - actual_frozen_paths = set() - for env in install_dir.glob("**/conda-meta/history"): - frozen_file = env.parent / "frozen" - assert frozen_file.exists() - actual_frozen_paths.add(frozen_file) + shutil.copytree(str(example_path), str(input_path)) - assert expected_frozen_paths == actual_frozen_paths, ( - f"Expected: {sorted(str(p) for p in expected_frozen_paths)}\n" - f"Found: {sorted(str(p) for p in actual_frozen_paths)}" - ) + with open(input_path / "construct.yaml") as f: + config = YAML().load(f) + + if has_conflict: + config.setdefault("extra_files", []).append({"frozen.json": "conda-meta/frozen"}) + with open(input_path / "construct.yaml", "w") as f: + YAML().dump(config, f) + + with context as c: + for installer, install_dir in create_installer(input_path, tmp_path): + _run_installer(input_path, installer, install_dir, request=request, uninstall=False) + + expected_frozen = { + install_dir / "conda-meta" / "frozen": config["freeze_base"]["conda"], + install_dir / "envs" / "env1" / "conda-meta" / "frozen": config["extra_envs"]["env1"]["freeze_env"]["conda"], + } + + for frozen_path, expected_content in expected_frozen.items(): + assert frozen_path.exists() + assert json.loads(frozen_path.read_text()) == expected_content + + if has_conflict: + assert all(s in c.value.stderr for s in ("RuntimeError", "freeze_base / freeze_env", "extra_files", "base"))