From 5f255e1ef369b3b091279f9e905aec15c470b9ab Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 08:05:13 -0700 Subject: [PATCH 01/13] Refactor to add conda-standalone hash and remove flat keys in info.json --- constructor/briefcase.py | 6 +++--- constructor/docker_build.py | 2 +- constructor/main.py | 22 +++++++++++++--------- constructor/osxpkg.py | 6 +++--- constructor/shar.py | 6 +++--- constructor/utils.py | 4 ++-- constructor/winexe.py | 6 +++--- tests/test_examples.py | 2 +- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/constructor/briefcase.py b/constructor/briefcase.py index 27c7beaf8..26674d5fb 100644 --- a/constructor/briefcase.py +++ b/constructor/briefcase.py @@ -225,7 +225,7 @@ def _setup_envs_commands(info: dict) -> list[dict]: env_config = info["extra_envs"][env_name] # Needed for shortcuts_flags function if "_conda_exe_type" not in env_config: - env_config["_conda_exe_type"] = info.get("_conda_exe_type") + env_config["_conda_exe_type"] = info.get("_conda_exe", {}).get("type") channel_info = { "channels": env_config.get("channels", info.get("channels", ())), "channels_remap": env_config.get("channels_remap", info.get("channels_remap", ())), @@ -625,14 +625,14 @@ def _stage_user_scripts(self, pkgs_dir: Path) -> None: shutil.copy(script_path, pkgs_dir / dest_name) def _stage_conda(self, external_dir: Path) -> None: - copy_conda_exe(external_dir, self.conda_exe_name, self.info["_conda_exe"]) + copy_conda_exe(external_dir, self.conda_exe_name, self.info["_conda_exe"]["path"]) def create(info, verbose=False): if not IS_WINDOWS: raise OSError(f"Invalid platform '{sys.platform}'. MSI installers require Windows.") - if not info.get("_conda_exe_supports_logging"): + if not info.get("_conda_exe", {}).get("supports_logging"): raise ValueError("MSI installers require conda-standalone with logging support.") # Check briefcase exists before doing any work diff --git a/constructor/docker_build.py b/constructor/docker_build.py index 43f2c112f..8a7bd184f 100644 --- a/constructor/docker_build.py +++ b/constructor/docker_build.py @@ -69,7 +69,7 @@ def generate_dockerfile(info: dict, docker_dir: Path) -> Path: base_image=info.get("docker_base_image"), default_prefix=info.get("default_prefix", f"/opt/{info['name'].lower()}"), installer_filename=Path(info["_outpath"]).name, - conda_exe_name=format_conda_exe_name(info["_conda_exe"]), + conda_exe_name=format_conda_exe_name(info["_conda_exe"]["path"]), name=info["name"], version=info["version"], labels=info.get("docker_labels", {}), diff --git a/constructor/main.py b/constructor/main.py index c73c07332..ae2ad957b 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -37,6 +37,7 @@ StandaloneExe, check_version, has_docker_buildx, + hash_files, identify_conda_exe, normalize_path, yield_lines, @@ -246,7 +247,7 @@ def main_build( info["_output_dir"] = output_dir info["_platform"] = platform info["_download_dir"] = join(cache_dir, platform) - info["_conda_exe"] = abspath(conda_exe) + info["_conda_exe"] = {"path": abspath(conda_exe)} info["_debug"] = debug if installer_type: info["installer_type"] = installer_type @@ -274,11 +275,14 @@ def main_build( ): sys.exit("Error: cannot construct a macOS 'pkg' installer on '%s'" % cc_platform) - exe_type, exe_version = identify_conda_exe(info.get("_conda_exe")) + exe_path = info["_conda_exe"]["path"] + exe_type, exe_version = identify_conda_exe(exe_path) if exe_version is not None: exe_version = Version(exe_version) - info["_conda_exe_type"] = exe_type - info["_conda_exe_version"] = exe_version + info["_conda_exe"]["type"] = exe_type + info["_conda_exe"]["version"] = exe_version + info["_conda_exe"]["SHA256"] = hash_files([Path(exe_path)], "sha256")["sha256"], + } if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable sys.exit("Error: micromamba is not supported on Windows installers.") @@ -397,9 +401,9 @@ def main_build( else: info["_ignore_condarcs_arg"] = "" - info["_conda_exe_supports_logging"] = _conda_exe_supports_logging( - info["_conda_exe"], - info["_conda_exe_type"], + info["_conda_exe"]["supports_logging"] = _conda_exe_supports_logging( + exe_path, + exe_type, ) if InstallerTypes.PKG in itypes: @@ -422,8 +426,8 @@ def main_build( if osname == "win": info["_win_install_needs_python_exe"] = _win_install_needs_python_exe( - info["_conda_exe"], - info["_conda_exe_type"], + exe_path, + exe_type, ) info["installer_type"] = itypes[0] diff --git a/constructor/osxpkg.py b/constructor/osxpkg.py index 5805d86af..85b09221b 100644 --- a/constructor/osxpkg.py +++ b/constructor/osxpkg.py @@ -406,7 +406,7 @@ def move_script(src, dst, info, ensure_shebang=False, user_script_type=None): variables["no_rcs_arg"] = info.get("_ignore_condarcs_arg", "") variables["script_env_variables"] = info.get("script_env_variables", {}) variables["initialize_conda"] = info.get("initialize_conda", "classic") - variables["conda_exe_name"] = format_conda_exe_name(info["_conda_exe"]) + variables["conda_exe_name"] = format_conda_exe_name(info["_conda_exe"]["path"]) data = render_template(data, **variables) @@ -636,8 +636,8 @@ def create(info, verbose=False): for dist in all_dists: os.link(join(CACHE_DIR, dist), join(pkgs_dir, dist)) - exe_name = format_conda_exe_name(info["_conda_exe"]) - copy_conda_exe(prefix, exe_name, info["_conda_exe"]) + exe_name = format_conda_exe_name(info["_conda_exe"]["path"]) + copy_conda_exe(prefix, exe_name, info["_conda_exe"]["path"]) # Sign conda-standalone so it can pass notarization codesigner = None diff --git a/constructor/shar.py b/constructor/shar.py index 44e5c7824..f30a172eb 100644 --- a/constructor/shar.py +++ b/constructor/shar.py @@ -112,7 +112,7 @@ def get_header(conda_exec, tarball, info): virtual_specs = parse_virtual_specs(info) min_osx_version = virtual_specs.get("__osx", {}).get("min") or "" variables["min_osx_version"] = min_osx_version - variables["conda_exe_name"] = format_conda_exe_name(info["_conda_exe"]) + variables["conda_exe_name"] = format_conda_exe_name(info["_conda_exe"]["path"]) min_glibc_version = virtual_specs.get("__glibc", {}).get("min") or "" variables["min_glibc_version"] = min_glibc_version @@ -210,7 +210,7 @@ def create(info, verbose=False): t.add(join(info["_download_dir"], fn), "pkgs/" + fn) t.close() - info["_internal_conda_files"] = copy_conda_exe(tmp_dir, "_conda", info["_conda_exe"]) + info["_internal_conda_files"] = copy_conda_exe(tmp_dir, "_conda", info["_conda_exe"]["path"]) if info["_internal_conda_files"]: conda_exe_payloads: dict[str, tuple[int, int, bool]] = {} memfile = BytesIO() @@ -231,7 +231,7 @@ def create(info, verbose=False): maybe_memfile = (memfile,) else: maybe_memfile = () - conda_exec = info["_conda_exe"] + conda_exec = info["_conda_exe"]["path"] header = get_header(conda_exec, tarball, info) shar_path = info["_outpath"] with open(shar_path, "wb") as fo: diff --git a/constructor/utils.py b/constructor/utils.py index e03e7e846..222dd82e0 100644 --- a/constructor/utils.py +++ b/constructor/utils.py @@ -216,7 +216,7 @@ def ensure_transmuted_ext(info, url): """ if ( info.get("transmute_file_type") == ".conda" - and info.get("_conda_exe_type") == StandaloneExe.MAMBA + and info.get("_conda_exe", {}).get("type") == StandaloneExe.MAMBA ): if url.lower().endswith(".tar.bz2"): url = url[:-8] + ".conda" @@ -307,7 +307,7 @@ def shortcuts_flags(info) -> str: # not set: we create all shortcuts (default behaviour) return "" if menu_packages: - if info.get("_conda_exe_type") == StandaloneExe.MAMBA: + if info.get("_conda_exe", {}).get("type") == StandaloneExe.MAMBA: logger.warning( "Micromamba does not support '--shortcuts-only'. Will install all shortcuts." ) diff --git a/constructor/winexe.py b/constructor/winexe.py index cff693e72..e7a04c55c 100644 --- a/constructor/winexe.py +++ b/constructor/winexe.py @@ -113,7 +113,7 @@ def setup_envs_commands(info, dir_path): env_info = info["extra_envs"][env_name] # Needed for shortcuts_flags function if "_conda_exe_type" not in env_info: - env_info["_conda_exe_type"] = info.get("_conda_exe_type") + env_info["_conda_exe_type"] = info.get("_conda_exe", {}).get("type") channel_info = { "channels": env_info.get("channels", info.get("channels", ())), "channels_remap": env_info.get("channels_remap", info.get("channels_remap", ())), @@ -282,7 +282,7 @@ def make_nsi( # UPPERCASE variables are unescaped (and unquoted) variables["CONDA_LOG_ARG"] = ( - '--log-file "${STEP_LOG}"' if info.get("_conda_exe_supports_logging") else "" + '--log-file "${STEP_LOG}"' if info.get("_conda_exe", {}).get("supports_logging") else "" ) variables["NAME"] = name variables["NSIS_DIR"] = NSIS_DIR @@ -376,7 +376,7 @@ def create(info, verbose=False): preconda_write_files(info, tmp_dir) copied_extra_files = copy_extra_files(info.get("extra_files", []), tmp_dir) copied_temp_extra_files = copy_extra_files(info.get("temp_extra_files", []), tmp_dir) - extra_conda_exe_files = copy_conda_exe(tmp_dir, "_conda.exe", info["_conda_exe"]) + extra_conda_exe_files = copy_conda_exe(tmp_dir, "_conda.exe", info["_conda_exe"]["path"]) pre_dst = join(tmp_dir, "pre_install.bat") pre_install_script = info.get("pre_install") diff --git a/tests/test_examples.py b/tests/test_examples.py index 3f3a06d7c..0933a0a5f 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2005,7 +2005,7 @@ def test_output_files(tmp_path, installer_type): # Test that info.json contains serialized objects info_json = json.loads((root_path / "info.json").read_text()) - assert isinstance(info_json.get("_conda_exe_version"), str) + assert isinstance(info_json.get("_conda_exe").get("version"), str) _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( "Build environment packages is not a list." From 7b536a4f5f88761f6bde45c7875837f21dc7a703 Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 08:27:14 -0700 Subject: [PATCH 02/13] Remove typo --- constructor/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/constructor/main.py b/constructor/main.py index ae2ad957b..55e7c3207 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -281,8 +281,7 @@ def main_build( exe_version = Version(exe_version) info["_conda_exe"]["type"] = exe_type info["_conda_exe"]["version"] = exe_version - info["_conda_exe"]["SHA256"] = hash_files([Path(exe_path)], "sha256")["sha256"], - } + info["_conda_exe"]["SHA256"] = (hash_files([Path(exe_path)], "sha256")["sha256"],) if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable sys.exit("Error: micromamba is not supported on Windows installers.") From 818bd62d8baae38a4b18ff18a0b12cebf0e67b1f Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 12:25:26 -0700 Subject: [PATCH 03/13] Remove trailing comma --- constructor/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constructor/main.py b/constructor/main.py index 55e7c3207..9e971b2e3 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -281,7 +281,7 @@ def main_build( exe_version = Version(exe_version) info["_conda_exe"]["type"] = exe_type info["_conda_exe"]["version"] = exe_version - info["_conda_exe"]["SHA256"] = (hash_files([Path(exe_path)], "sha256")["sha256"],) + info["_conda_exe"]["SHA256"] = (hash_files([Path(exe_path)], "sha256")["sha256"]) if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable sys.exit("Error: micromamba is not supported on Windows installers.") From e6c5947a62e592cd853caf21fc3ef45342a2d3ec Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 12:56:27 -0700 Subject: [PATCH 04/13] Test for non-empty strings --- tests/test_examples.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_examples.py b/tests/test_examples.py index 0933a0a5f..796124dc5 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2005,7 +2005,9 @@ def test_output_files(tmp_path, installer_type): # Test that info.json contains serialized objects info_json = json.loads((root_path / "info.json").read_text()) - assert isinstance(info_json.get("_conda_exe").get("version"), str) + assert isinstance(info_json.get["_conda_exe"]["version"], str) and info_json["_conda_exe"]["version"] + assert isinstance(info_json.get["_conda_exe"]["path"], str) and info_json["_conda_exe"]["path"] + assert isinstance(info_json.get["_conda_exe"]["sha256"], str) and info_json["_conda_exe"]["sha256"] _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( "Build environment packages is not a list." From 44c576e942fc8edfd7f6ac76298cb6a2ed3cc5df Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 13:21:18 -0700 Subject: [PATCH 05/13] Only calculate hash if info.json in outputs --- constructor/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/constructor/main.py b/constructor/main.py index 9e971b2e3..f84c7bdcf 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -24,7 +24,7 @@ from . import __version__ from ._schema import InstallerTypes -from .build_outputs import process_build_outputs +from .build_outputs import process_build_outputs, _validate_output from .conda_interface import SUPPORTED_PLATFORMS, cc_platform from .conda_interface import VersionOrder as Version from .construct import SCHEMA_PATH, ns_platform @@ -281,7 +281,8 @@ def main_build( exe_version = Version(exe_version) info["_conda_exe"]["type"] = exe_type info["_conda_exe"]["version"] = exe_version - info["_conda_exe"]["SHA256"] = (hash_files([Path(exe_path)], "sha256")["sha256"]) + if any("info.json" in _validate_output(o) for o in info.get("build_outputs", ())): + info["_conda_exe"]["sha256"] = hash_files([Path(exe_path)], "sha256")["sha256"] if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable sys.exit("Error: micromamba is not supported on Windows installers.") From f90222eb735320d54c91a0facc0c2b1e90fa1525 Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 15:22:40 -0700 Subject: [PATCH 06/13] Make payload keys non-flat --- constructor/shar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constructor/shar.py b/constructor/shar.py index f30a172eb..03c1e6c00 100644 --- a/constructor/shar.py +++ b/constructor/shar.py @@ -96,8 +96,8 @@ def get_header(conda_exec, tarball, info): variables["default_prefix"] = info.get("default_prefix", "${HOME:-/opt}/%s" % name.lower()) variables["first_payload_size"] = getsize(conda_exec) variables["second_payload_size"] = getsize(tarball) - variables["conda_exe_payloads"] = info.get("_conda_exe_payloads", {}) - variables["conda_exe_payloads_size"] = info.get("_conda_exe_payloads_size", 0) + variables["conda_exe_payloads"] = info["_conda_exe"].get("payloads", {}) + variables["conda_exe_payloads_size"] = info["_conda_exe"].get("payloads_size", 0) variables["final_channels"] = get_final_channels(info) variables["conclusion_text"] = info.get("conclusion_text", "installation finished.") variables["pycache"] = "__pycache__" From 250a05a06996fbd722017f6356802e5bea3e1665 Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Wed, 19 Aug 2026 15:23:20 -0700 Subject: [PATCH 07/13] Fix precommit --- constructor/main.py | 2 +- tests/test_examples.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/constructor/main.py b/constructor/main.py index f84c7bdcf..a07ff3a61 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -24,7 +24,7 @@ from . import __version__ from ._schema import InstallerTypes -from .build_outputs import process_build_outputs, _validate_output +from .build_outputs import _validate_output, process_build_outputs from .conda_interface import SUPPORTED_PLATFORMS, cc_platform from .conda_interface import VersionOrder as Version from .construct import SCHEMA_PATH, ns_platform diff --git a/tests/test_examples.py b/tests/test_examples.py index 796124dc5..333b31d88 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2005,9 +2005,14 @@ def test_output_files(tmp_path, installer_type): # Test that info.json contains serialized objects info_json = json.loads((root_path / "info.json").read_text()) - assert isinstance(info_json.get["_conda_exe"]["version"], str) and info_json["_conda_exe"]["version"] + assert ( + isinstance(info_json.get["_conda_exe"]["version"], str) + and info_json["_conda_exe"]["version"] + ) assert isinstance(info_json.get["_conda_exe"]["path"], str) and info_json["_conda_exe"]["path"] - assert isinstance(info_json.get["_conda_exe"]["sha256"], str) and info_json["_conda_exe"]["sha256"] + assert ( + isinstance(info_json.get["_conda_exe"]["sha256"], str) and info_json["_conda_exe"]["sha256"] + ) _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( "Build environment packages is not a list." From 552e37130ad3e5573e7cb9cb24aa59c585088ae8 Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Thu, 20 Aug 2026 13:35:40 -0700 Subject: [PATCH 08/13] Fix syntax typos --- tests/test_briefcase.py | 2 +- tests/test_examples.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_briefcase.py b/tests/test_briefcase.py index 02a6f8d2a..31aa8f40e 100644 --- a/tests/test_briefcase.py +++ b/tests/test_briefcase.py @@ -30,7 +30,7 @@ mock_info = { "name": "MockInfo", "version": "1.0.0", - "_conda_exe": str(Path(sys.prefix) / "standalone_conda" / "conda.exe"), + "_conda_exe": {"path": str(Path(sys.prefix) / "standalone_conda" / "conda.exe")}, "_download_dir": "", "_dists": [], "_platform": cc_platform, diff --git a/tests/test_examples.py b/tests/test_examples.py index 333b31d88..56b6151a0 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2006,12 +2006,15 @@ def test_output_files(tmp_path, installer_type): # Test that info.json contains serialized objects info_json = json.loads((root_path / "info.json").read_text()) assert ( - isinstance(info_json.get["_conda_exe"]["version"], str) + isinstance(info_json.get("_conda_exe").get("version"), str) and info_json["_conda_exe"]["version"] ) - assert isinstance(info_json.get["_conda_exe"]["path"], str) and info_json["_conda_exe"]["path"] assert ( - isinstance(info_json.get["_conda_exe"]["sha256"], str) and info_json["_conda_exe"]["sha256"] + isinstance(info_json.get("_conda_exe").get("path"), str) and info_json["_conda_exe"]["path"] + ) + assert ( + isinstance(info_json.get("_conda_exe").get("sha256"), str) + and info_json["_conda_exe"]["sha256"] ) _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( From 110cae266d00d026538d87ac92d55ab744d8a88a Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Thu, 20 Aug 2026 14:42:40 -0700 Subject: [PATCH 09/13] Fix payload keys --- constructor/shar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constructor/shar.py b/constructor/shar.py index 03c1e6c00..c716f2b71 100644 --- a/constructor/shar.py +++ b/constructor/shar.py @@ -225,8 +225,8 @@ def create(info, verbose=False): conda_exe_payloads[relative_path] = (start, end, executable) start = end - info["_conda_exe_payloads"] = conda_exe_payloads - info["_conda_exe_payloads_size"] = end + info["_conda_exe"]["payloads"] = conda_exe_payloads + info["_conda_exe"]["payloads_size"] = end memfile.seek(0) maybe_memfile = (memfile,) else: From a66406fea35e20ae5b1daed059813a2171bb564e Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Fri, 21 Aug 2026 10:18:59 -0700 Subject: [PATCH 10/13] Add news file --- news/1329-conda-standalone-metadata | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 news/1329-conda-standalone-metadata diff --git a/news/1329-conda-standalone-metadata b/news/1329-conda-standalone-metadata new file mode 100644 index 000000000..e90181547 --- /dev/null +++ b/news/1329-conda-standalone-metadata @@ -0,0 +1,19 @@ +### Enhancements + +* Add SHA256 hash of bootstrapper to `info.json` and consolidate bootstrapper metadata under `_conda_exe`, including its hash, type, version, and path. (#1329) + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* From 15390251d88a871bf7448fb76bdb2b4e2cbd0d9d Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Fri, 21 Aug 2026 11:06:49 -0700 Subject: [PATCH 11/13] Make code improvements --- constructor/briefcase.py | 5 +++-- constructor/main.py | 3 ++- constructor/winexe.py | 5 +++-- tests/test_examples.py | 20 +++++++++----------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/constructor/briefcase.py b/constructor/briefcase.py index 26674d5fb..4bef9791a 100644 --- a/constructor/briefcase.py +++ b/constructor/briefcase.py @@ -224,8 +224,9 @@ def _setup_envs_commands(info: dict) -> list[dict]: for env_name in info.get("_extra_envs_info", {}): env_config = info["extra_envs"][env_name] # Needed for shortcuts_flags function - if "_conda_exe_type" not in env_config: - env_config["_conda_exe_type"] = info.get("_conda_exe", {}).get("type") + if "_conda_exe" not in env_config: + env_config["_conda_exe"] = {} + env_config["_conda_exe"]["type"] = info.get("_conda_exe", {}).get("type") channel_info = { "channels": env_config.get("channels", info.get("channels", ())), "channels_remap": env_config.get("channels_remap", info.get("channels_remap", ())), diff --git a/constructor/main.py b/constructor/main.py index a07ff3a61..0f59f73b5 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -282,7 +282,8 @@ def main_build( info["_conda_exe"]["type"] = exe_type info["_conda_exe"]["version"] = exe_version if any("info.json" in _validate_output(o) for o in info.get("build_outputs", ())): - info["_conda_exe"]["sha256"] = hash_files([Path(exe_path)], "sha256")["sha256"] + info["_conda_exe"]["sha256"] = hash_files([Path(exe_path)], "sha256") + if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable sys.exit("Error: micromamba is not supported on Windows installers.") diff --git a/constructor/winexe.py b/constructor/winexe.py index e7a04c55c..ffcd6678a 100644 --- a/constructor/winexe.py +++ b/constructor/winexe.py @@ -112,8 +112,9 @@ def setup_envs_commands(info, dir_path): for env_name in info.get("_extra_envs_info", {}): env_info = info["extra_envs"][env_name] # Needed for shortcuts_flags function - if "_conda_exe_type" not in env_info: - env_info["_conda_exe_type"] = info.get("_conda_exe", {}).get("type") + if "_conda_exe" not in env_info: + env_info["_conda_exe"] = {} + env_info["_conda_exe"]["type"] = info.get("_conda_exe", {}).get("type") channel_info = { "channels": env_info.get("channels", info.get("channels", ())), "channels_remap": env_info.get("channels_remap", info.get("channels_remap", ())), diff --git a/tests/test_examples.py b/tests/test_examples.py index 56b6151a0..27c9775b9 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2005,17 +2005,15 @@ def test_output_files(tmp_path, installer_type): # Test that info.json contains serialized objects info_json = json.loads((root_path / "info.json").read_text()) - assert ( - isinstance(info_json.get("_conda_exe").get("version"), str) - and info_json["_conda_exe"]["version"] - ) - assert ( - isinstance(info_json.get("_conda_exe").get("path"), str) and info_json["_conda_exe"]["path"] - ) - assert ( - isinstance(info_json.get("_conda_exe").get("sha256"), str) - and info_json["_conda_exe"]["sha256"] - ) + conda_exe = info_json["_conda_exe"] + + assert isinstance(conda_exe.get("version"), str) and conda_exe["version"] + assert isinstance(conda_exe.get("path"), str) and conda_exe["path"] + assert isinstance(conda_exe.get("type"), str) and conda_exe["type"] + + expected_sha256 = hashlib.sha256(Path(conda_exe["path"]).read_bytes()).hexdigest() + assert conda_exe["sha256"] == expected_sha256 + _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( "Build environment packages is not a list." From ed2e36eafe3703d8f7e40fdfc6600ba2caf71574 Mon Sep 17 00:00:00 2001 From: Jaida Rice Date: Fri, 21 Aug 2026 13:10:42 -0700 Subject: [PATCH 12/13] Test structure of hash --- tests/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_examples.py b/tests/test_examples.py index 27c9775b9..f75117a9e 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2012,7 +2012,7 @@ def test_output_files(tmp_path, installer_type): assert isinstance(conda_exe.get("type"), str) and conda_exe["type"] expected_sha256 = hashlib.sha256(Path(conda_exe["path"]).read_bytes()).hexdigest() - assert conda_exe["sha256"] == expected_sha256 + assert conda_exe["sha256"] == {"sha256": expected_sha256} _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), ( From 13ad4270ad362eebb88b1738a23d2942b9a2c0be Mon Sep 17 00:00:00 2001 From: Jaida Rice <100002667+Jrice1317@users.noreply.github.com> Date: Fri, 21 Aug 2026 15:08:12 -0700 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Marco Esters --- constructor/main.py | 2 +- tests/test_examples.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/constructor/main.py b/constructor/main.py index 0f59f73b5..6460e138b 100644 --- a/constructor/main.py +++ b/constructor/main.py @@ -282,7 +282,7 @@ def main_build( info["_conda_exe"]["type"] = exe_type info["_conda_exe"]["version"] = exe_version if any("info.json" in _validate_output(o) for o in info.get("build_outputs", ())): - info["_conda_exe"]["sha256"] = hash_files([Path(exe_path)], "sha256") + info["_conda_exe"]["sha256"] = hash_files([Path(exe_path)], "sha256")["sha256"] if osname == "win" and exe_type == StandaloneExe.MAMBA: # TODO: Investigate errors on Windows and re-enable diff --git a/tests/test_examples.py b/tests/test_examples.py index f75117a9e..27c9775b9 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2012,7 +2012,7 @@ def test_output_files(tmp_path, installer_type): assert isinstance(conda_exe.get("type"), str) and conda_exe["type"] expected_sha256 = hashlib.sha256(Path(conda_exe["path"]).read_bytes()).hexdigest() - assert conda_exe["sha256"] == {"sha256": expected_sha256} + assert conda_exe["sha256"] == expected_sha256 _build_environment_packages = info_json.get("_build_environment_packages") assert isinstance(_build_environment_packages, list), (