From ddd3ac7853a28594561fbfbd2205c1399d588dcd Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 14:30:20 -0500 Subject: [PATCH 01/16] COMP: Adopt pixi and pre-commit for build environment --- .gitattributes | 2 + .gitignore | 11 +- .pre-commit-config.yaml | 69 + CONTRIBUTING.md | 76 + Utilities/Hooks/kw-commit-msg.py | 4 +- Utilities/Hooks/prepare-commit-msg | 8 +- pixi.lock | 14103 +++++++++++++++++++++++++++ pixi.toml | 213 + pyproject.toml | 77 + tests/__init__.py | 0 10 files changed, 14555 insertions(+), 8 deletions(-) create mode 100644 .gitattributes create mode 100644 CONTRIBUTING.md create mode 100644 pixi.lock create mode 100644 pixi.toml create mode 100644 pyproject.toml create mode 100644 tests/__init__.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..887a2c18 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/.gitignore b/.gitignore index cffba1ed..8bab1746 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ lib lib64 MANIFEST oneTBB-prefix/ -pyproject.toml # Installer logs pip-log.txt @@ -54,6 +53,9 @@ pip-log.txt .tox coverage.xml htmlcov +.pytest_cache/ +.hypothesis/ +__pycache__/ # Translations *.mo @@ -73,3 +75,10 @@ docs/_build # IDE junk .idea/* *.swp +/itkVersion.py +# PyPI credentials +.pypirc + +# pixi environments +.pixi/* +!.pixi/config.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b15a4760..aeb1c397 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,73 @@ +# Pre-commit configuration for ITKPythonPackage +# Run `pre-commit install` to install git hooks locally +# Run `pre-commit run --all-files` to check all files +# Run `pre-commit autoupdate` to update hook versions + +# Exclude generated/vendored directories from all hooks +exclude: ^(\.pixi/|build/|docs/|pixi\.lock$) + repos: + # General file hygiene + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + stages: [pre-commit] + - id: end-of-file-fixer + stages: [pre-commit] + - id: check-yaml + stages: [pre-commit] + - id: check-toml + stages: [pre-commit] + - id: check-merge-conflict + stages: [pre-commit] + - id: check-added-large-files + args: [--maxkb=500] + stages: [pre-commit] + # Enforce LF line endings everywhere except Windows PowerShell scripts + - id: mixed-line-ending + args: [--fix=lf] + exclude: \.ps1$ + stages: [pre-commit] + + # Python: formatting + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + stages: [pre-commit] + + # Python: linting + import sorting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.6 + hooks: + - id: ruff + args: [--fix] + stages: [pre-commit] + + # Shell: linting + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + stages: [pre-commit] + + # Shell: formatting + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.12.0-2 + hooks: + - id: shfmt + args: [-i, "2", -w] + stages: [pre-commit] + + # TOML: formatting + - repo: https://github.com/ComPWA/taplo-pre-commit + rev: v0.9.3 + hooks: + - id: taplo-format + stages: [pre-commit] + + # ITK commit message hooks (matching ITK/Utilities/Hooks/) - repo: local hooks: - id: local-prepare-commit-msg diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e203da30 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,76 @@ +# Contributing to ITKPythonPackage + +## Getting Started + +Clone the repository and install the pre-commit hooks: + +```bash +git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git +cd ITKPythonPackage +```` + +Install [Pixi](https://pixi.sh) for managing the build environment: + +```bash +curl -fsSL https://pixi.sh/install.sh | bash +pixi install +``` + +Install pre-commit hooks + +```bash +pixi run pre-commit-install +# optionally run pre-commit hooks +pixi run pre-commit-run +``` + + +## Development Workflow + +### Code Style + +Pre-commit hooks enforce all formatting and linting automatically on commit: + +- **Python**: Black (formatting), Ruff (linting + import sorting) +- **Shell**: ShellCheck (linting), shfmt (formatting) +- **TOML**: Taplo (formatting) + +Run against all files manually: + +```bash +pre-commit run --all-files +``` + +### Commit Messages + +This project uses the ITK commit message convention, `PREFIX: Description`, +enforced by Commitizen: + +``` +ENH: Add support for Python 3.12 wheels +BUG: Correct cmake args not propagating to module builds +COMP: Update pre-commit hook versions +DOC: Clarify aarch64 build requirements +``` + +Valid prefixes are `BUG:`, `COMP:`, `DOC:`, `ENH:`, `PERF:` and `STYLE:`, +and the description starts with a capital letter. Commitizen will reject +commits that don't follow this format. + +### Building Docs + +```bash +pip install -r docs/requirements-docs.txt +sphinx-build -W -b html docs docs/_build/html +``` + +## Submitting a Pull Request + +1. Create a branch from `main` +2. Make your changes and ensure `pre-commit run --all-files` passes +3. Open a PR, fill out the template, including which platforms you tested on +4. CI will run pre-commit checks automatically + +## Questions + +For build questions or general ITK support, use the [ITK Discourse forum](https://discourse.itk.org). diff --git a/Utilities/Hooks/kw-commit-msg.py b/Utilities/Hooks/kw-commit-msg.py index 0d6b0af8..f569d058 100755 --- a/Utilities/Hooks/kw-commit-msg.py +++ b/Utilities/Hooks/kw-commit-msg.py @@ -21,10 +21,8 @@ import re import subprocess import sys - from pathlib import Path - DEFAULT_LINE_LENGTH: int = 78 @@ -116,7 +114,7 @@ def main(): r"^(Merge|Revert|BUG:|COMP:|DOC:|ENH:|PERF:|STYLE:|WIP:)\s", subject_line ): die( - f"""Start ITK commit messages with a standard prefix (and a space): + """Start ITK commit messages with a standard prefix (and a space): BUG: - fix for runtime crash or incorrect result COMP: - compiler error or warning fix DOC: - documentation change diff --git a/Utilities/Hooks/prepare-commit-msg b/Utilities/Hooks/prepare-commit-msg index 276fd093..8868d323 100755 --- a/Utilities/Hooks/prepare-commit-msg +++ b/Utilities/Hooks/prepare-commit-msg @@ -18,7 +18,7 @@ #========================================================================== egrep-q() { - egrep "$@" >/dev/null 2>/dev/null + grep -E "$@" >/dev/null 2>/dev/null } # First argument is file containing commit message. @@ -45,9 +45,9 @@ instructions='#\ # Follow the first line commit summary with an empty line, then a detailed\ # description in one or more paragraphs.\ #' && -sed '/^# On branch.*$/ a\ + sed '/^# On branch.*$/ a\ '"$instructions"' /^# Not currently on any branch.*$/ a\ '"$instructions"' -' "$commit_msg" > "$commit_msg_tmp" && -mv "$commit_msg_tmp" "$commit_msg" +' "$commit_msg" >"$commit_msg_tmp" && + mv "$commit_msg_tmp" "$commit_msg" diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 00000000..ba638fe7 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,14103 @@ +version: 7 +platforms: +- name: linux-64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +- name: linux-64-linux-5-4 + subdir: linux-64 + virtual-packages: + - __linux=5.4 + - __glibc=2.28 + - __unix=0=0 + - __archspec=0=x86_64 +- name: linux-aarch64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=aarch64 +- name: linux-aarch64-linux-5-4 + subdir: linux-aarch64 + virtual-packages: + - __linux=5.4 + - __glibc=2.28 + - __unix=0=0 + - __archspec=0=aarch64 +- name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __osx=13.0 + - __archspec=0=m1 +- name: osx-arm64-macos-14-0 + subdir: osx-arm64 + virtual-packages: + - __osx=14.0 + - __unix=0=0 + - __archspec=0=m1 +- name: win-64 + virtual-packages: + - __win=10.0 + - __archspec=0=x86_64 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py314hdafbbf9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.5.0-hd28c85e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.5.0-h12fcf84_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py314hcbd71af_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py314hdafbbf9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py314h9891dd4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py314h5bd0f2a_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py314h0f05182_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py314h0f05182_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.4-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hab81395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-h2d22210_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py314h0f05182_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + linux-aarch64: + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.6-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py314h0bd77cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py314h28a4750_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h20c602a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py314h51f160d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.2-hcab7f73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.6-gpl_hbe7d12b_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.19.0-hc57f145_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h31becfc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.5.0-hc712cdd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.5.0-h3ad78e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py314h709e558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.67.0-ha888d0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.35-hdda61c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h18c354c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.2-h79dcc73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.2-h825857f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-h5ad3122_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h80f16a2_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py314h28a4750_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py314hd7d8586_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py314h51f160d_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.3-hb06a95a_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.5.post0-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.5.post0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py314h2e8dab5_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py314h2e8dab5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.2.4-hfefdfc9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-h9f97df7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-h3618846_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py314hd7d8586_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py314h2e8dab5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.1.1-py314h4dc9dd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h403dcb5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py314h0612a62_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.6-gpl_h6fbacd7_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.1-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.5.0-h7950639_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.5.0-h85b9800_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.5.0-py314hf8f60b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.35-h5f525b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.2-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.2-h8d039ee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.2-py314h4dc9dd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py314h784bc60_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py314hb84d1df_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.3-h4c637c5_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.5.post0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.5.post0-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.17-py314ha14b1ff_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py314ha14b1ff_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.4-ha7d2532_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-ha0f8610_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h2b2570c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py314h6cfcd04_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py314h9d33bd4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-win_hba80fca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.1.1-py314h86ab7b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h7f4e812_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/frozendict-2.4.7-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.5.0-h06825f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.5.0-h9ae1bf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.5.0-py314h532c739_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.35-h8883371_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.4.2-py314h13fbf68_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.1.2-py314h909e829_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py314h5a2d7ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.3-h4b44e0e_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.5.post0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.5.post0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.17-py314hc5dbbe4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py314hc5dbbe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.2.4-h49e36cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-h9f585f1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h63977a8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py314h909e829_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + dev: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py311h6f959d0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311heb3be6f_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hebe6cf0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py311h904a5e5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.7.2-py311hf8ae3fa_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-h76c4fd7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hypothesis-6.168.0-py311hf77984d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbc21106_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_h3152399_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.22.0-ha042cf0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h373387f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.9.0-py311h13990c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.9.0-h7fab031_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.9.0-py311hb6bacf0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmsgpack-c-6.1.0-h54a6638_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h74cf4be_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpsl-0.23.1-hd9e3e90_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.11.16-h0c77377_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.39-h72ddc62_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-h6154650_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.3-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hf3af7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-h7df9aa5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-hee9eb32_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-hebe6cf0_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.5.2-py311hf8ae3fa_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.2-py311h4c6fd42_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.25.0-py310hefeb9ab_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py311h0d48ccd_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.5-py311ha6e1976_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.16-h5f976f7_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.8.post0-hee9eb32_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.8.post0-h1c70be6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py311h194be0f_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.6.11-h4dbf13b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hb6aa676_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-he64ecbb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py311hdf67eae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + linux-aarch64: + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/backports.zstd-1.7.0-py311h9a9e91b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py311hf1a560e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.8-h29ee22c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.1.1-py311hc2bebf7_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.7.2-py311hee276e8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h166da81_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/hypothesis-6.168.0-py311h8ca180e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-py311h3512406_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h5bc82ec_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-h095d8e5_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.46.1-default_h1979696_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.9-gpl_h1204ebd_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.22.0-h86273da_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321hc48eb74_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h80f16a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.7.0-hdaad0be_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-16.2.0-h205dda4_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-16.2.0-h8acb6b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h1ea5142_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.9.0-py311he2bbb06_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.9.0-hf00976d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.9.0-py311h2b99d05_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmsgpack-c-6.1.0-h7ac5ae9_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.68.1-ha4b982d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.1-h86ecc28_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpsl-0.23.1-hb8431ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpython-3.11.16-hc3dd739_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.39-h6e0bd25_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.4-h399dd60_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h7572e3e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-16.2.0-hef695bb_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42.3-hd6fdeab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.38-h80f16a2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.4-h2e5089f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.4-h92b9305_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-hfb11796_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h29ee22c_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py311h2dad8b0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.5.2-py311he98b269_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.2.2-py311h8802582_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.6-h2b6f883_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.4-he6ad1d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/py-rattler-0.25.0-py310h44f7655_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py311h5a00dd8_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.46.5-py311h8c3699e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.11.16-h94ad73b_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py311h164a683_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-ha7194a6_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.8.post0-hfb11796_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.8.post0-h63b641b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py311h0de9f98_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.6.11-h45ce4d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-hb3fff47_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-hb434046_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_hf03c496_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py311hfca10b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h29ee22c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h7ac5ae9_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h9d15635_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py311hdee5a0d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311h9fb86b2_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py311h2e98d63_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.7.2-py311h50b1a05_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h3feff0a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h99e4e11_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hypothesis-6.168.0-py311hfcb3ee1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h34f8a20_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h76147b0_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.22.0-h6651222_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321h26f1114_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h1a92334_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-he4c29f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.9.0-py311h39d259c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.9.0-h5900e61_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.9.0-py311h01962e9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmsgpack-c-6.1.0-h784d473_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h435687b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.1-hdb0c161_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.11.16-h4e5ec87_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.39-h626c152_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-hbf2880b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.4-heb56d2d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.4-h550178d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-hdca3b7d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h74c22ad_1003.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.5.2-py311hf19aa8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py311h2f25576_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/py-rattler-0.25.0-py310h2dd3e6a_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py311h5edb25e_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py311hcfb3d13_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.16-hd05a0c4_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.8.post0-hdca3b7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.8.post0-h2fd3d4c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py311hd322c8c_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.6.11-hfbe1efa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-h760df91_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h17e24d4_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py311h572238d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-h784d473_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-win_hba80fca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyh6be1c34_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py311h71c1bcc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311h7862a2f_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.1.1-py311h3485c13_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.7.2-py311h86c7a16_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h1ced75a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hypothesis-6.168.0-py311h66afae6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h5112557_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h719d79b_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_h8fdc8af_101.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.22.0-hdb0ef4a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.9.0-py311h5893078_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.9.0-h8faee1a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.9.0-py311h024d1a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmsgpack-c-6.1.0-h5112557_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpsl-0.23.1-h9b16d47_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpython-3.11.16-hcc31f7b_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.39-h8883371_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h734d217_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.4-h3cfd58e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.4-he095d88_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h6a83c73_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1003.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.5.2-py311h5dfdfe8_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.2-py311h275cad7_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/py-rattler-0.25.0-py310hb39080a_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py311h3485c13_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.5-py311hf51aa87_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.16-hb12b558_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.8.post0-h5112557_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.8.post0-hf5d2508_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py311hf893f09_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.6.11-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-hd227c35_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h18a1a76_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py311h3fd045d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-h5112557_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + linux-py311: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py311h6b1f9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.45.1-default_h4852527_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.11.0-h4d9bdce_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cmake-3.31.8-hc85cc9f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py311h38be061_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-14.3.0-he8ccf15_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.11.0-hfcd1e18_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/doxygen-1.13.2-h8e693c7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py311h49ec1c0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-14.3.0-h0dff253_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_21.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-14.3.0-h76987e4_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-he467f4b_21.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.5.0-hd28c85e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.5.0-h12fcf84_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py311hc5d5c7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py311h38be061_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py311hdf67eae_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.13.2-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py311h2e04523_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/patchelf-0.17.2-h58526e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py311h49ec1c0_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.6-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py311haee01d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py311haee01d2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.4-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hab81395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-h2d22210_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py311hdf67eae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/auditwheel-6.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyelftools-0.32-pyh707e725_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + linux-aarch64: + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/backports.zstd-1.3.0-py311h6ce31b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils-2.45.1-default_hf1166c9_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils_impl_linux-aarch64-2.45.1-default_h5f4c503_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils_linux-aarch64-2.45.1-default_hf1166c9_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py311h14a79a7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.6-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-compiler-1.11.0-hdceaead_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py311h460c349_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cmake-3.31.8-hc9d863e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py311hec3470c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-gcc-specs-14.3.0-hadff5d6_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cxx-compiler-1.11.0-h7b35c40_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/doxygen-1.13.2-h5e0f5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h20c602a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py311h19352d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc-14.3.0-h2e72a27_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc_impl_linux-aarch64-14.3.0-h533bfc8_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc_linux-aarch64-14.3.0-h118592a_21.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx-14.3.0-ha384071_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx_impl_linux-aarch64-14.3.0-h0d4f5d4_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx_linux-aarch64-14.3.0-h32e4f2e_21.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.6-gpl_hbe7d12b_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-5_haddc8a3_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-5_hd72aa62_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.19.0-hc57f145_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h31becfc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libhwloc-2.13.0-default_ha95e27d_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-5_h88aeb00_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.5.0-hc712cdd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.5.0-h3ad78e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py311h6a8bf82_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.68.1-hd3077d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.1-h86ecc28_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.30-pthreads_h9d3fd7e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsanitizer-14.3.0-hedb4206_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.36-hdda61c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h18c354c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-ng-15.2.0-hdbbeba8_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuv-1.51.0-he30d5cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.2-h79dcc73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.2-h825857f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-h5ad3122_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h80f16a2_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py311hec3470c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py311hfca10b7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ninja-1.13.2-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.3-py311h669026d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/patchelf-0.17.2-h884eca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py311h19352d5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.11.15-h91f4b29_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py311h164a683_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.5.post0-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.5.post0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rhash-1.4.6-h86ecc28_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py311h51cfe5d_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py311h51cfe5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.2.4-hfefdfc9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-h9f97df7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-h3618846_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py311hfca10b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py311h51cfe5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/auditwheel-6.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-aarch64-4.18.0-h05a177a_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-aarch64-14.3.0-h25ba3ff_118.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-aarch64-14.3.0-h57c8d61_118.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyelftools-0.32-pyh707e725_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-aarch64-2.28-h585391f_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + macosx-py311: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + osx-arm64-macos-14-0: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-18.1.8-he32a8d3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.12.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-10.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-10.2.1-hea71cf2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/vcs_versioning-2.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.48.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-8_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py311hdee5a0d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311h9fb86b2_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-compiler-1.10.0-hdf49b6b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1021.4-hb4fb6a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_osx-arm64-1021.4-h12580ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py311h2e98d63_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-18-18.1.8-default_hf3020a7_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-18.1.8-default_hf9bcbb7_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-18.1.8-h2ae9ea5_25.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_osx-arm64-18.1.8-h07b0088_25.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx-18.1.8-default_h65f67e2_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_impl_osx-arm64-18.1.8-h555f467_25.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_osx-arm64-18.1.8-h07b0088_25.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-3.31.8-h54ad630_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-18.1.8-h855ad52_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.7.2-py311h50b1a05_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h3feff0a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cxx-compiler-1.10.0-hba80287_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/doxygen-1.18.0-py310h3226bdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h99e4e11_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h34f8a20_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-954.16-h4c6efb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-954.16-h9d5fcb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h76147b0_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-11_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-11_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf3020a7_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.22.0-h6651222_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-devel-18.1.8-h6dc3340_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321h26f1114_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h1a92334_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-he4c29f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-11_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm18-18.1.8-default_h8e162e0_12.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.9.0-py311h39d259c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.9.0-h5900e61_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.9.0-py311h01962e9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmsgpack-c-6.1.0-h784d473_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h435687b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.34-openmp_h5e6e99c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.1-hdb0c161_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.11.16-h4e5ec87_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.39-h626c152_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-hbf2880b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.4-heb56d2d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.4-h550178d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.1-hdb3d66b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-18-18.1.8-default_h8e162e0_12.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-18.1.8-default_h8e162e0_12.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-hdca3b7d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h74c22ad_1003.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.5.2-py311hf19aa8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py311h2f25576_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ninja-1.13.2-h3feff0a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py311hbd1492f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/py-rattler-0.25.0-py310h2dd3e6a_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py311h5edb25e_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py311hcfb3d13_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.16-hd05a0c4_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.8.post0-hdca3b7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.8.post0-h2fd3d4c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rhash-1.4.6-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py311hd322c8c_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.6.11-hfbe1efa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-h760df91_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h17e24d4_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py311h572238d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-h784d473_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + - pypi: https://files.pythonhosted.org/packages/2b/dd/437e601fa9d2b6bf8507256daf52196a226c3b340261f4cde8fac8e853ea/delocate-0.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl + manylinux228-py311: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64-linux-5-4: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py311h6b1f9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cmake-3.31.8-hc85cc9f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py311h38be061_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/doxygen-1.13.2-h8e693c7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py311h49ec1c0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.5.0-hd28c85e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.5.0-h12fcf84_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py311hc5d5c7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py311h38be061_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py311hdf67eae_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.13.2-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py311h2e04523_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/patchelf-0.17.2-h58526e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py311h49ec1c0_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.6-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py311haee01d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py311haee01d2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.4-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hab81395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-h2d22210_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py311hdf67eae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/auditwheel-6.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyelftools-0.32-pyh707e725_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + linux-aarch64-linux-5-4: + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/backports.zstd-1.3.0-py311h6ce31b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py311h14a79a7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.6-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py311h460c349_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cmake-3.31.8-hc9d863e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py311hec3470c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/doxygen-1.13.2-h5e0f5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h20c602a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py311h19352d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.2-hcab7f73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.6-gpl_hbe7d12b_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-5_haddc8a3_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-5_hd72aa62_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.19.0-hc57f145_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h31becfc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-5_h88aeb00_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.5.0-hc712cdd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.5.0-h3ad78e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py311h6a8bf82_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.67.0-ha888d0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.1-h86ecc28_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.30-pthreads_h9d3fd7e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.36-hdda61c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h18c354c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-ng-15.2.0-hdbbeba8_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuv-1.51.0-he30d5cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.2-h79dcc73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.2-h825857f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-h5ad3122_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h80f16a2_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py311hec3470c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py311hfca10b7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ninja-1.13.2-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.2-py311h669026d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/patchelf-0.17.2-h884eca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py311h19352d5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.11.15-h91f4b29_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py311h164a683_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.5.post0-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.5.post0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rhash-1.4.6-h86ecc28_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py311h51cfe5d_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py311h51cfe5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.2.4-hfefdfc9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-h9f97df7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-h3618846_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py311hfca10b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py311h51cfe5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/auditwheel-6.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyelftools-0.32-pyh707e725_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + publish: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py314hdafbbf9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gh-2.88.1-h76a2195_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.5.0-hd28c85e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.5.0-h12fcf84_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py314hcbd71af_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.36-h9463b59_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py314hdafbbf9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py314h9891dd4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py314h5bd0f2a_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py314h0f05182_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py314h0f05182_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.4-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hab81395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-h2d22210_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py314h0f05182_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + linux-aarch64: + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.6-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py314h0bd77cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py314h28a4750_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h20c602a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py314h51f160d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gh-2.88.1-h94b2740_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.6-gpl_hbe7d12b_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.19.0-hc57f145_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h31becfc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.5-hfae3067_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.5.0-hc712cdd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.5.0-h3ad78e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py314h709e558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.68.1-hd3077d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.36-hdda61c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h18c354c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42-h1022ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.2-h79dcc73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.2-h825857f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-h5ad3122_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h80f16a2_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py314h28a4750_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py314hd7d8586_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py314h51f160d_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.3-hb06a95a_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.5.post0-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.5.post0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py314h2e8dab5_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py314h2e8dab5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.2.4-hfefdfc9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-h9f97df7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-h3618846_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py314hd7d8586_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h5ad3122_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py314h2e8dab5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.1.1-py314h4dc9dd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h403dcb5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py314h0612a62_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gh-2.88.1-h01237fd_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.6-gpl_h6fbacd7_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.5.0-h7950639_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.5.0-h85b9800_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.5.0-py314hf8f60b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.36-h7d962ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.2-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.2-h8d039ee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.2-py314h4dc9dd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py314h784bc60_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py314hb84d1df_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.3-h4c637c5_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.5.post0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.5.post0-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.17-py314ha14b1ff_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py314ha14b1ff_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.4-ha7d2532_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-ha0f8610_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h2b2570c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py314h6cfcd04_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py314h9d33bd4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-win_hba80fca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.1.1-py314h86ab7b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h7f4e812_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/frozendict-2.4.7-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gh-2.88.1-h36e2d1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.5.0-h06825f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.5.0-h9ae1bf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.5.0-py314h532c739_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.36-h8883371_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.4.2-py314h13fbf68_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.1.2-py314h909e829_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py314h5a2d7ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.3-h4b44e0e_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.5.post0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.5.post0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.17-py314hc5dbbe4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py314hc5dbbe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.2.4-h49e36cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-h9f585f1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h63977a8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py314h909e829_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + windows-py311: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-win_hba80fca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.3.0-py311h71c1bcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/c-compiler-1.11.0-h528c1b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cmake-3.31.8-hdcbee5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.1.1-py311h1ea47a8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cxx-compiler-1.11.0-h1c1089f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/doxygen-1.13.2-hbf3f430_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h7f4e812_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/frozendict-2.4.7-py311h3485c13_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/git-2.53.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.5.0-h06825f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.5.0-h9ae1bf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.5.0-py311hb0041a4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.35-h8883371_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libuv-1.51.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.4.2-py311h3e6a449_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.1.2-py311h3fd045d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ninja-1.13.2-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py311h80b3fa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py311h3485c13_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.5.post0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.5.post0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.17-py311hf893f09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py311hf893f09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.2.4-h49e36cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-h9f585f1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h63977a8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py311h3fd045d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/74/bf0ea7b89e114af19a38c2341746fd1f17f1de23fa8d8019cbcd39719c5f/delvewheel-1.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py311h6b1f9c4_0.conda + sha256: 246e50ec7fc222875c6ecfa3feab77f5661dc43e26397bc01d9e0310e3cd48a0 + md5: adda5ef2a74c9bdb338ff8a51192898a + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 244920 + timestamp: 1767044984647 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py311h6f959d0_1.conda + sha256: b6be9fb502245705469b05807e9a28d3265f7fdef765bd00d5132f40a0fda92b + md5: 30f9861fbea080176375281b31055fc0 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + run_exports: {} + size: 248331 + timestamp: 1788491293611 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.45.1-default_h4852527_101.conda + sha256: 2851d34944b056d028543f0440fb631aeeff204151ea09589d8d9c13882395de + md5: 9902aeb08445c03fb31e01beeb173988 + depends: + - binutils_impl_linux-64 >=2.45.1,<2.45.2.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 35128 + timestamp: 1770267175160 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_101.conda + sha256: 74341b26a2b9475dc14ba3cf12432fcd10a23af285101883e720216d81d44676 + md5: 83aa53cb3f5fc849851a84d777a60551 + depends: + - ld_impl_linux-64 2.45.1 default_hbd61a6d_101 + - sysroot_linux-64 + - zstd >=1.5.7,<1.6.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 3744895 + timestamp: 1770267152681 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_101.conda + sha256: 4826f97d33cbe54459970a1e84500dbe0cccf8326aaf370e707372ae20ec5a47 + md5: dec96579f9a7035a59492bf6ee613b53 + depends: + - binutils_impl_linux-64 2.45.1 default_hfdba357_101 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 36060 + timestamp: 1770267177798 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda + sha256: c36eb061d9ead85f97644cfb740d485dba9b8823357f35c17851078e95e975c1 + md5: 86daecb8e4ed1042d5dc6efbe0152590 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 367573 + timestamp: 1764017405384 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311heb3be6f_4.conda + sha256: d641ef23bdfce1b1a889845489fce65e00311d6ed22e6b345074ec07627db7d5 + md5: 7f5e2b7716d10c9a762ee94112143ca8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 h39a168f_4 + license: MIT + license_family: MIT + run_exports: {} + size: 367207 + timestamp: 1788480468171 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda + sha256: 3ad3500bff54a781c29f16ce1b288b36606e2189d0b0ef2f67036554f47f12b0 + md5: 8910d2c46f7e7b519129f486e0fe927a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 367376 + timestamp: 1764017265553 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + sha256: 1a0d382c515ebf55f8ee1f38c8b81bc95af5c2acc42ad53b66bc5df932032f96 + md5: e675fabcf81499adc7edf58124fb1e01 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 257808 + timestamp: 1785906269155 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e + md5: 920bb03579f15389b9e512095ad995b7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 207882 + timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hebe6cf0_2.conda + sha256: 9c37cc233238adf366ea75b0e845662a5be07ceadf62e458183516369340274b + md5: 3ad2b403be8fc5612b9159e2ce621f69 + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 228700 + timestamp: 1787169971173 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.11.0-h4d9bdce_0.conda + sha256: 8e7a40f16400d7839c82581410aa05c1f8324a693c9d50079f8c50dc9fb241f0 + md5: abd85120de1187b0d1ec305c2173c71b + depends: + - binutils + - gcc + - gcc_linux-64 14.* + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6693 + timestamp: 1753098721814 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + sha256: 3ad13377356c86d3a945ae30e9b8c8734300925ef81a3cb0a9db0d755afbe7bb + md5: 3912e4373de46adafd8f1e97e4bd166b + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 303338 + timestamp: 1761202960110 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + sha256: c6339858a0aaf5d939e00d345c98b99e4558f285942b27232ac098ad17ac7f8e + md5: cf45f4278afd6f4e6d03eda0f435d527 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 300271 + timestamp: 1761203085220 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py311h904a5e5_3.conda + sha256: 1f9c8856b629d9c546cd6574345e7d3621f59fdde8c2902b88e00f09764d6295 + md5: 6c91369847e5ec8456c8af44e6d99986 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 310350 + timestamp: 1788485287159 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cmake-3.31.8-hc85cc9f_0.conda + sha256: 10660ed21b9d591f8306028bd36212467b94f23bc2f78faec76524f6ee592613 + md5: 057e16a12847eea846ecf99710d3591b + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libcurl >=8.14.1,<9.0a0 + - libexpat >=2.7.1,<3.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 + - libstdcxx >=14 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - rhash >=1.4.6,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 20991288 + timestamp: 1757877168657 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py311h38be061_0.conda + sha256: 9cd790637fcab45938ad49753d15b0cfbe847f9f0d4a1b7213d30b6fb412fede + md5: 52f7c28c287da1a28cb9cabda1f5d0cf + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-env >=2.6 + - conda-build >=25.9 + - conda-content-trust >=0.1.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1286583 + timestamp: 1772191375962 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.1.1-py314hdafbbf9_0.conda + sha256: 287ebc90f9659755cf626e1dd1d405690bef16e805942b4a3d8008e2376ff8bf + md5: c1c5e3d6e85f5fe5a47789d7a10b140f + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-build >=25.9 + - conda-env >=2.6 + - conda-content-trust >=0.1.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1307275 + timestamp: 1772191378974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-26.7.2-py311hf8ae3fa_1.conda + sha256: a18bbddcdaa207ed766b548f8ba77a7655233bf97fab338f1a0295d783823a51 + md5: 54350c89df7ceb3455997a05c130daea + depends: + - archspec >=0.2.3 + - backports.zstd >=1.3.0 + - boltons >=23.0.0 + - charset-normalizer + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - menuinst >=2 + - msgpack-python >=1.1.1 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.6.0 + - pycosat >=0.6.3 + - python + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.20 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - conda-libmamba-solver >=26.4.1 + - conda-lockfiles >=0.2.0 + - conda-pypi >=0.10.1 + - conda-rattler-solver >=0.1.1 + - conda-self >=0.2.0 + - python_abi 3.11.* *_cp311 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.3.0 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1438999 + timestamp: 1788876747682 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-14.3.0-he8ccf15_18.conda + sha256: b90ec0e6a9eb22f7240b3584fe785457cff961fec68d40e6aece5d596f9bbd9a + md5: 0e3e144115c43c9150d18fa20db5f31c + depends: + - gcc_impl_linux-64 >=14.3.0,<14.3.1.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 31705 + timestamp: 1771378159534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + sha256: 0d9405d9f2de5d4b15d746609d87807aac10e269072d6408b769159762ed113d + md5: d17488e343e4c5c0bd0db18b3934d517 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: CC0-1.0 + purls: [] + size: 24283 + timestamp: 1756734785482 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_1.conda + sha256: 6a905f317b93104444dc74875933a7cdf65fa14ae05dd6485ece6ffd9d0b5e89 + md5: 451367db888b29ad0db6b298669fbe77 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: CC0-1.0 + run_exports: {} + size: 24388 + timestamp: 1785920465106 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.11.0-hfcd1e18_0.conda + sha256: 3fcc97ae3e89c150401a50a4de58794ffc67b1ed0e1851468fcc376980201e25 + md5: 5da8c935dca9186673987f79cef0b2a5 + depends: + - c-compiler 1.11.0 h4d9bdce_0 + - gxx + - gxx_linux-64 14.* + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6635 + timestamp: 1753098722177 +- conda: https://conda.anaconda.org/conda-forge/linux-64/doxygen-1.13.2-h8e693c7_0.conda + sha256: 349c4c872357b4a533e127b2ade8533796e8e062abc2cd685756a1a063ae1e35 + md5: 0869f41ea5c64643dd2f5b47f32709ca + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libiconv >=1.17,<2.0a0 + - libstdcxx >=13 + license: GPL-2.0-only + license_family: GPL + purls: [] + size: 13148627 + timestamp: 1738164137421 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-h76c4fd7_1.conda + sha256: 25d14a197601fdd616f2e501431b52887e69b31e7defbc1679381d718357ceb7 + md5: a70bb6d42ad121aef456e0007e92bed6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 199072 + timestamp: 1785915412102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + sha256: d4e92ba7a7b4965341dc0fca57ec72d01d111b53c12d11396473115585a9ead6 + md5: f7d7a4104082b39e3b3473fbd4a38229 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 198107 + timestamp: 1767681153946 +- conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py311h49ec1c0_0.conda + sha256: df99ccc63c065875cc150f451aaab03e6733898219cf2f248543fe53b08deff0 + md5: cfc74ea184e02e531732abb17778e9fc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 32010 + timestamp: 1763082979695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py314h5bd0f2a_0.conda + sha256: 3e04b8293122e923f1c66599525e8b0e743f532b5c53d932c41105311b721526 + md5: 7a884102c0e9fb57012a6f49259475fc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 31627 + timestamp: 1763082886391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-14.3.0-h0dff253_18.conda + sha256: 9b34b57b06b485e33a40d430f71ac88c8f381673592507cf7161c50ff0832772 + md5: 52d6457abc42e320787ada5f9033fa99 + depends: + - conda-gcc-specs + - gcc_impl_linux-64 14.3.0 hbdf3cc3_18 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 29506 + timestamp: 1771378321585 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda + sha256: 3b31a273b806c6851e16e9cf63ef87cae28d19be0df148433f3948e7da795592 + md5: 30bb690150536f622873758b0e8d6712 + depends: + - binutils_impl_linux-64 >=2.45 + - libgcc >=14.3.0 + - libgcc-devel_linux-64 14.3.0 hf649bbc_118 + - libgomp >=14.3.0 + - libsanitizer 14.3.0 h8f1669f_18 + - libstdcxx >=14.3.0 + - libstdcxx-devel_linux-64 14.3.0 h9f08a49_118 + - sysroot_linux-64 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 76302378 + timestamp: 1771378056505 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_21.conda + sha256: 27ad0cd10dccffca74e20fb38c9f8643ff8fce56eee260bf89fa257d5ab0c90a + md5: 1403ed5fe091bd7442e4e8a229d14030 + depends: + - gcc_impl_linux-64 14.3.0.* + - binutils_linux-64 + - sysroot_linux-64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28946 + timestamp: 1770908213807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gh-2.88.1-h76a2195_0.conda + sha256: 78f9fbbf18272e92e62bc1672d34178760ced73edc1e818ebd5fd00296b5df88 + md5: 0290cdefa20d98adb936c465ccb76ad6 + depends: + - __glibc >=2.17,<3.0.a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 22875681 + timestamp: 1773332470542 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-14.3.0-h76987e4_18.conda + sha256: 1b490c9be9669f9c559db7b2a1f7d8b973c58ca0c6f21a5d2ba3f0ab2da63362 + md5: 19189121d644d4ef75fed05383bc75f5 + depends: + - gcc 14.3.0 h0dff253_18 + - gxx_impl_linux-64 14.3.0 h2185e75_18 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28883 + timestamp: 1771378355605 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda + sha256: 38ffca57cc9c264d461ac2ce9464a9d605e0f606d92d831de9075cb0d95fc68a + md5: 6514b3a10e84b6a849e1b15d3753eb22 + depends: + - gcc_impl_linux-64 14.3.0 hbdf3cc3_18 + - libstdcxx-devel_linux-64 14.3.0 h9f08a49_118 + - sysroot_linux-64 + - tzdata + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 14566100 + timestamp: 1771378271421 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-he467f4b_21.conda + sha256: 1e07c197e0779fa9105e59cd55a835ded96bfde59eb169439736a89b27b48e5d + md5: 7b51f4ff82eeb1f386bfee20a7bed3ed + depends: + - gxx_impl_linux-64 14.3.0.* + - gcc_linux-64 ==14.3.0 h298d278_21 + - binutils_linux-64 + - sysroot_linux-64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 27503 + timestamp: 1770908213813 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hypothesis-6.168.0-py311hf77984d_0.conda + noarch: python + sha256: 10255e137ffd6a64fc98f6f5089653cdaa713a2747698f5eab7b0ddec90b9e97 + md5: b8adb43f2abf9982c054e3af88297893 + depends: + - python + - sortedcontainers >=2.1.0,<3.0.0 + - exceptiongroup >=1.0.0 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __glibc >=2.17 + license: MPL-2.0 + license_family: MOZILLA + run_exports: {} + size: 640880 + timestamp: 1788949833088 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 + md5: 186a18e3ba246eccfc7cff00cd19a870 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 12728445 + timestamp: 1767969922681 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + sha256: 9f07834f0c546ab14d885ce0366285f61f44e326c0edd1fc63b8294e113ae432 + md5: 72a381cbad04f24b1c2a43ef707f45b4 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14459115 + timestamp: 1786545741408 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-h7cc23a3_1.conda + sha256: dd053c96dcb0dcfd59422aefea9d2fe937a190167f34fba7893ec1e10a7e8963 + md5: ba55d1b89fd7775e67de8291029b4059 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: LGPL-2.1-or-later + run_exports: + weak: + - keyutils >=1.6.3,<2.0a0 + size: 135295 + timestamp: 1786739238128 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + size: 134088 + timestamp: 1754905959823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + sha256: 3e307628ca3527448dd1cb14ad7bb9d04d1d28c7d4c5f97ba196ae984571dd25 + md5: fb53fb07ce46a575c5d004bbc96032c2 + depends: + - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1386730 + timestamp: 1769769569681 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbc21106_2.conda + sha256: 2a5c38c85e63df84c4e69ee71439841ce570d259ae3060627bb9a49a938d66f4 + md5: 53318d715316929a574f83591308b1f8 + depends: + - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=15 + - libstdcxx >=15 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1394333 + timestamp: 1786762112514 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 + md5: 12bd9a3f089ee6c9266a37dab82afabd + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 725507 + timestamp: 1770267139900 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 728002 + timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 449500f2c089da11c40f5c21312e3e07 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.46.1 + license: GPL-3.0-only + license_family: GPL + run_exports: {} + size: 745303 + timestamp: 1784214507189 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda + sha256: 69ea8da58658ad26cb64fb0bfccd8a3250339811f0b57c6b8a742e5e51bacf70 + md5: 981d372c31a23e1aa9965d4e74d085d5 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 887139 + timestamp: 1773243188979 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_h3152399_101.conda + sha256: bfae027a67d02325cd6b75edd60beb67cf24483af9df0ae9ef74d4438e41c806 + md5: 680520cff78a974d564cd99cdf5ec0b5 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 875346 + timestamp: 1787292369121 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + build_number: 5 + sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c + md5: c160954f7418d7b6e87eaf05a8913fa9 + depends: + - libopenblas >=0.3.30,<0.3.31.0a0 + - libopenblas >=0.3.30,<1.0a0 + constrains: + - mkl <2026 + - liblapack 3.11.0 5*_openblas + - libcblas 3.11.0 5*_openblas + - blas 2.305 openblas + - liblapacke 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18213 + timestamp: 1765818813880 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + build_number: 5 + sha256: 0cbdcc67901e02dc17f1d19e1f9170610bd828100dc207de4d5b6b8ad1ae7ad8 + md5: 6636a2b6f1a87572df2970d3ebc87cc0 + depends: + - libblas 3.11.0 5_h4a7cf45_openblas + constrains: + - liblapacke 3.11.0 5*_openblas + - blas 2.305 openblas + - liblapack 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18194 + timestamp: 1765818837135 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + sha256: a0390fd0536ebcd2244e243f5f00ab8e76ab62ed9aa214cd54470fe7496620f4 + md5: d50608c443a30c341c24277d28290f76 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libnghttp2 >=1.67.0,<2.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + size: 466704 + timestamp: 1773218522665 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.22.0-ha042cf0_0.conda + sha256: 483b7eb97f56fbb3a9cb7190d33012dfa0a5aaed99ede61465563cd97dd82504 + md5: e617deee7af8eb0c04f6296d12b54157 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=15 + - libnghttp2 >=1.68.1,<2.0a0 + - libpsl >=0.23.1,<0.24.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.8,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + run_exports: + weak: + - libcurl >=8.22.0,<9.0a0 + size: 499651 + timestamp: 1788340719230 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h373387f_1.conda + sha256: 6473eb8caf2aae830f37caa93db9b26dddf7ac84b63229e8bf7fc0e5c3ab95b0 + md5: 50708d3b951d0f8e2d7f2df5b5edc040 + depends: + - ncurses + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - ncurses >=6.6,<7.0a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 135098 + timestamp: 1786616658086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b + depends: + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h280c20c_3.conda + sha256: e4418f68d01a6307c4431b585c71b584f40189877364e542ee87deb777f5e85b + md5: 7bc31538d6e3fb349e897353969237ec + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: BSD-2-Clause OR GPL-2.0-or-later + license_family: GPL + run_exports: + weak: + - libev >=4.33,<4.34.0a0 + size: 43220 + timestamp: 1785917328200 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 + md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT + purls: [] + size: 76798 + timestamp: 1771259418166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + sha256: e8c2b57f6aacabdf2f1b0924bd4831ce5071ba080baa4a9e8c0d720588b6794c + md5: 49f570f3bc4c874a06ea69b7225753af + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.5.* + license: MIT + license_family: MIT + purls: [] + size: 76624 + timestamp: 1774719175983 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + sha256: 16feffd9ddbbe5b718515d38ee376c685ba95491cd901244e24671d20b952a77 + md5: b24d3c612f71e7aa74158d92106318b2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + run_exports: {} + size: 77856 + timestamp: 1781203599810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + sha256: c8c7583ef063bc3c430f1d48298e5ad24f796a35c22ed5b14183325825a61106 + md5: 6525a0b06aa4fd390795f0740636a9dd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 68004 + timestamp: 1787753412298 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 + md5: 0aa00f03f9e39fb9876085dee11a85d4 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1041788 + timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_5.conda + sha256: 33d1b5d57c5a55a474330c5ec41fe7001a5aa600d9639389e8ce5edafae216a4 + md5: 3b62b92f0f082b67c59618ebf07617a7 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==16.2.0=*_5 + - libgomp 16.2.0 he0feb66_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: {} + size: 1057064 + timestamp: 1789485807107 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 + md5: d5e96b1ed75ca01906b3d2469b4ce493 + depends: + - libgcc 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27526 + timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee + md5: 9063115da5bc35fdc3e1002e69b9ef6e + depends: + - libgfortran5 15.2.0 h68bc16d_18 + constrains: + - libgfortran-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27523 + timestamp: 1771378269450 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 + md5: 646855f357199a12f02a87382d429b75 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 2482475 + timestamp: 1771378241063 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 + md5: 239c5e9546c38a1e884d69effcf4c882 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 603262 + timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_5.conda + sha256: 224a5a09e258a1a257089a9af310e9336e1d36834959718a9fab8679742dd822 + md5: af44890f8a2beefd3a83805a59a65014 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 641644 + timestamp: 1789485759120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + sha256: 5041d295813dfb84652557839825880aae296222ab725972285c5abe3b6e4288 + md5: c197985b58bc813d26b42881f0021c82 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2436378 + timestamp: 1770953868164 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + sha256: f943117edb9cd4d9c61cc972eee5a34291dc55ea7a6e9e38da104995841cbcb6 + md5: f92233bf33e24a25668bb2119e2c51f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: LGPL-2.1-only + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 789471 + timestamp: 1787033836207 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-only + purls: [] + size: 790176 + timestamp: 1754908768807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + build_number: 5 + sha256: c723b6599fcd4c6c75dee728359ef418307280fa3e2ee376e14e85e5bbdda053 + md5: b38076eb5c8e40d0106beda6f95d7609 + depends: + - libblas 3.11.0 5_h4a7cf45_openblas + constrains: + - blas 2.305 openblas + - liblapacke 3.11.0 5*_openblas + - libcblas 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18200 + timestamp: 1765818857876 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb + md5: c7c83eecbb72d88b940c249af56c8b17 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.2.* + license: 0BSD + purls: [] + size: 113207 + timestamp: 1768752626120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab + md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 112995 + timestamp: 1786348617826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.5.0-hd28c85e_0.conda + sha256: ca2d384f30e1582b7c6ae9f1406fb51f993e44c92799e3cabf2130f69c7261d5 + md5: a24532d0660b8a58ca2955301281f71e + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - zstd >=1.5.7,<1.6.0a0 + - reproc >=14.2,<15.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - libarchive >=3.8.5,<3.9.0a0 + - openssl >=3.5.4,<4.0a0 + - nlohmann_json-abi ==3.12.0 + - fmt >=12.1.0,<12.2.0a0 + - simdjson >=4.2.4,<4.3.0a0 + - libsolv >=0.7.35,<0.8.0a0 + - reproc-cpp >=14.2,<15.0a0 + - libcurl >=8.18.0,<9.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2567543 + timestamp: 1767884157869 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.9.0-py311h13990c4_1.conda + sha256: 881e4e3fec8585afb92c51c10a48b0000963d773728bacc4fdfc40183412d657 + md5: 1ae697b7f5afd035f9fe5c034d517cd2 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + - openssl >=3.5.8,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libsolv >=0.7.39,<0.8.0a0 + - simdjson >=4.6.11,<4.7.0a0 + - reproc >=14.2.8.post0,<14.3.0a0 + - libcurl >=8.22.0,<9.0a0 + - libmsgpack-c >=6.1.0,<7.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - fmt >=12.1.0,<12.2.0a0 + - nlohmann_json-abi ==3.12.0 + - libarchive >=3.8.9,<3.9.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmamba >=2.9.0,<2.10.0a0 + size: 2892426 + timestamp: 1789061577265 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.5.0-h12fcf84_0.conda + sha256: 75deca8004690aed0985845f734927e48aa49a683ee09b3d9ff4e22ace9e4a8e + md5: c866cdca3b16b5c7a0fd9916d5d64577 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 20396 + timestamp: 1767884157870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-spdlog-2.9.0-h7fab031_1.conda + sha256: 0ebaca580c3264b27573893781a6bb5d5e496239dca7beaba13bddc4314fcbdf + md5: fd329e752ef8f500a21c279e998edc92 + depends: + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libmamba >=2.9.0,<2.10.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 20465 + timestamp: 1789061577265 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py311hc5d5c7c_0.conda + sha256: ebd16f6685bdc2f0cf595f0f441b638b378a43e76e2124763c35d548df09084b + md5: f72a2daebdd84c968828602bfa081ae5 + depends: + - python + - libmamba ==2.5.0 hd28c85e_0 + - libmamba-spdlog ==2.5.0 h12fcf84_0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - zstd >=1.5.7,<1.6.0a0 + - fmt >=12.1.0,<12.2.0a0 + - python_abi 3.11.* *_cp311 + - openssl >=3.5.4,<4.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - nlohmann_json-abi ==3.12.0 + - pybind11-abi ==11 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 944797 + timestamp: 1767884157872 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.5.0-py314hcbd71af_0.conda + sha256: 578a2ec09095c81674af0fa88ee44083c3a4ab10c31b498ddfa519fc9ff855b1 + md5: 54125b30d5031808bca8018561843f40 + depends: + - python + - libmamba ==2.5.0 hd28c85e_0 + - libmamba-spdlog ==2.5.0 h12fcf84_0 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + - pybind11-abi ==11 + - fmt >=12.1.0,<12.2.0a0 + - nlohmann_json-abi ==3.12.0 + - spdlog >=1.17.0,<1.18.0a0 + - openssl >=3.5.4,<4.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 948138 + timestamp: 1767884157875 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.9.0-py311hb6bacf0_1.conda + sha256: df42234c40a84b7258fc36965a4659e2695446e75234af7595fe182a39a4b7ce + md5: 0245a27909650da89636840e6867cbed + depends: + - python + - libmamba ==2.9.0 py311h13990c4_1 + - libmamba-spdlog ==2.9.0 h7fab031_1 + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - nlohmann_json-abi ==3.12.0 + - pybind11-abi ==11 + - spdlog >=1.17.0,<1.18.0a0 + - fmt >=12.1.0,<12.2.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - libmamba >=2.9.0,<2.10.0a0 + - openssl >=3.5.8,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmambapy >=2.9.0,<2.10.0a0 + size: 992593 + timestamp: 1789061577265 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmsgpack-c-6.1.0-h54a6638_7.conda + sha256: c23ac21547d9fac6be14b6b3463dd29fb0fd4aba5c64c4a9876dfa6d2e3158a9 + md5: 9ab61308317dbf6609e2142b16b792a5 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + constrains: + - msgpack-c ==6.1.0 *_7 + license: BSL-1.0 + run_exports: + weak: + - libmsgpack-c >=6.1.0,<7.0a0 + size: 40166 + timestamp: 1786189331007 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 + md5: b499ce4b026493a13774bcf0f4c33849 + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.5,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 666600 + timestamp: 1756834976695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h74cf4be_1.conda + sha256: 11a2f54a6f391e25738bce0023e6ef499600147bbcf21c1fa69d2e251b03cc6a + md5: 75d211f04ac87506f1f43420712a69fe + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.8,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=15 + - libstdcxx >=15 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libnghttp2 >=1.68.1,<2.0a0 + size: 649974 + timestamp: 1787177490105 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.6,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + run_exports: + weak: + - libnsl >=2.0.1,<2.1.0a0 + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 + md5: be43915efc66345cccb3c310b6ed0374 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + constrains: + - openblas >=0.3.30,<0.3.31.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 5927939 + timestamp: 1763114673331 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpsl-0.23.1-hd9e3e90_1.conda + sha256: 09e8effdc89bc5d021349318d32b85594f5b8d8e3ab8f90a85b81f8fe695a566 + md5: 77be60417aa572afcb48cbe0f967401d + depends: + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libpsl >=0.23.1,<0.24.0a0 + size: 72519 + timestamp: 1786970753847 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.11.16-h0c77377_2_cpython.conda + build_number: 2 + sha256: 9c945fbdc2292a8a39e412cb99672722c60ddf38b9002a52d5bd5954a0b25844 + md5: 55c7bd7fa76a498266addcddf2356647 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + license: Python-2.0 + run_exports: {} + size: 7831580 + timestamp: 1788393477692 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda + sha256: e03ed186eefb46d7800224ad34bad1268c9d19ecb8f621380a50601c6221a4a7 + md5: ad3a0e2dc4cce549b2860e2ef0e6d75b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14.3.0 + - libstdcxx >=14.3.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 7949259 + timestamp: 1771377982207 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda + sha256: 2fc2cdc8ea4dfd9277ae910fa3cfbf342d7890837a2002cf427fd306a869150b + md5: 21769ce326958ec230cdcbd0f2ad97eb + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 518374 + timestamp: 1754325691186 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.36-h9463b59_0.conda + sha256: 2a336d83e25e67b69548ee233188fa612cbce6809b3e2d45dd0b6520d75b3870 + md5: e6e2535fc6b69b08cdbaeab01aa1c277 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 519098 + timestamp: 1773328331358 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.39-h72ddc62_1.conda + sha256: c55a7242db95609b6f0b56b49ef2f5c1796060c0a221bbca3d61fbe19bf05add + md5: f5ac3845eb3e5a6c74100e3a7730c110 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libsolv >=0.7.39,<0.8.0a0 + size: 528489 + timestamp: 1786955817362 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 + md5: fd893f6a3002a635b5e50ceb9dd2c0f4 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 951405 + timestamp: 1772818874251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + sha256: f20d70da54e5b31dd4a51fb1efeaafafd5ab6dd8d7ac9d1438eaa2526ac4ed3d + md5: e72bbec309c2b0f37823ee7d4fabfcd3 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: blessing + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 974348 + timestamp: 1787051145557 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-h6154650_1.conda + sha256: 7e463000fa1cba3f3a6597b776f6ab301d425a96e3bc20d0d9d76a3b9f237aa3 + md5: 5c526561e1d2a2e071941174eae84557 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libssh2 >=1.11.1,<2.0a0 + size: 306045 + timestamp: 1786713107897 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 + md5: eecce068c7e4eddeb169591baac20ac4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 304790 + timestamp: 1745608545575 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e + md5: 1b08cd684f34175e4514474793d44bcb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 he0feb66_18 + constrains: + - libstdcxx-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5852330 + timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_5.conda + sha256: 38cadde30fad8f7c101f9eec79f60bf03fc99310c7195d23f8b30832516d666f + md5: 75b43a2912a0354204696708e4f34b8d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 16.2.0 ha9f2e26_5 + constrains: + - libstdcxx-ng ==16.2.0=*_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: {} + size: 6618916 + timestamp: 1789485826390 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + sha256: 3c902ffd673cb3c6ddde624cdb80f870b6c835f8bf28384b0016e7d444dd0145 + md5: 6235adb93d064ecdf3d44faee6f468de + depends: + - libstdcxx 15.2.0 h934c35e_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27575 + timestamp: 1771378314494 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee + md5: db409b7c1720428638e7c0d509d3e1b5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40311 + timestamp: 1766271528534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda + sha256: bc1b08c92626c91500fd9f26f2c797f3eb153b627d53e9c13cd167f1e12b2829 + md5: 38ffe67b78c9d4de527be8315e5ada2c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40297 + timestamp: 1775052476770 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.3-hcfc3c73_0.conda + sha256: aa58bbba56644ffd062a4a9b358782c5eb7560ccead2ab8f7c5c6ede0a7d33a6 + md5: 74a0a409d9f4561265d36b789d3f398a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libuuid >=2.42.3,<3.0a0 + size: 39998 + timestamp: 1788347719520 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b + md5: 0f03292cc56bf91a077a134ea8747118 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 895108 + timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + sha256: f7e9292dd219a6435bbb1223da9586c3e70d66d169c5a92f08db3f2127df04e9 + md5: f7a7ff5a6ab331e037abd34f379a631d + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: LGPL-2.1-or-later + run_exports: + weak: + - libxcrypt >=4.4.38 + size: 101957 + timestamp: 1785887123445 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda + sha256: 08d2b34b49bec9613784f868209bb7c3bb8840d6cf835ff692e036b09745188c + md5: f3bc152cb4f86babe30f3a4bf0dbef69 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.2 + license: MIT + license_family: MIT + purls: [] + size: 557492 + timestamp: 1772704601644 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hf3af7cc_0.conda + sha256: 9a1685c8d8b8488cd7882a9cd6e673e687211ca763ebbcd1e91b6f594eeadc45 + md5: b7545c57a73a51d72aa6eee5695cf051 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.4 + license: MIT + license_family: MIT + run_exports: {} + size: 569196 + timestamp: 1788635197456 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda + sha256: 275c324f87bda1a3b67d2f4fcc3555eeff9e228a37655aa001284a7ceb6b0392 + md5: e49238a1609f9a4a844b09d9926f2c3d + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2-16 2.15.2 hca6bf5a_0 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 45968 + timestamp: 1772704614539 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-h7df9aa5_0.conda + sha256: 053d85821ce5e36b7ce03faea5930382f6af695df9e2cd68e5d8d273072d1551 + md5: ea192d1ef556bf1d55de4acb8c0f5b65 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 hf3af7cc_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 47077 + timestamp: 1788635202698 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 60963 + timestamp: 1727963148474 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + sha256: eb8a0db0aa570124f7d2a93d7c7f596e3390df5e047818d873baad32985fc736 + md5: 0de0122d9570a8ab637c6b73db268389 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 63713 + timestamp: 1785362952714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 + md5: 9de5350a85c4a20c685259b889aa6393 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 167055 + timestamp: 1733741040117 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-hee9eb32_2.conda + sha256: b8c0e930183e6b7f83cd35ace102f2b8c2f0faae41dc05255dc72f247dfc556a + md5: e38a3253d72ab07d471483f24947e2a2 + depends: + - libgcc >=15 + - libstdcxx >=15 + - __glibc >=2.17,<3.0.a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - lz4-c >=1.10.0,<1.11.0a0 + size: 181812 + timestamp: 1786717122815 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + sha256: 5c6bbeec116e29f08e3dad3d0524e9bc5527098e12fc432c0e5ca53ea16337d4 + md5: 45161d96307e3a447cc3eb5896cf6f8c + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + size: 191060 + timestamp: 1753889274283 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-hebe6cf0_1003.conda + sha256: a0a1400198e5302d8367f2ed53437ee3cc41dbc463cb481921ce765578780b7a + md5: 10e0aa58ed390ba598b728badf9eb1b7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: GPL-2.0-or-later + license_family: GPL + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 191573 + timestamp: 1787049167898 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py311h3778330_1.conda + sha256: 710e207b2e91308a34bcfe547c60ad86c1fa294827266ba18548c1fe1a9d8333 + md5: f9efdf9b0f3d0cc309d56af6edf2a6b0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 26756 + timestamp: 1772445078834 +- conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py311h38be061_0.conda + sha256: 096f1db22fd47ddcf5df82aed016ef8cef4c1b810925bf63b67cfc3f9ba67cdb + md5: ba48e568ec4fdbcb8f3fef87bca9fddb + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 186910 + timestamp: 1765733175922 +- conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.2-py314hdafbbf9_0.conda + sha256: 223306ba7f78599abc65f8ca14116650c228cbdca273ca15804544ac343a9e69 + md5: 608ee3d8065e9b1e9e1f3115d8aab9ab + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 188752 + timestamp: 1765733220513 +- conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.5.2-py311hf8ae3fa_2.conda + sha256: 2a7c2a293883a28c27de71d016aa4a481312168af8b1a0f7076e462f4b6797d2 + md5: a53033a6087f707b045be637ccb4b81d + depends: + - python + - tomli-w + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + run_exports: {} + size: 206885 + timestamp: 1788033910741 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py311hdf67eae_1.conda + sha256: 8c81a6208def64afc3e208326d78d7af60bcbc32d44afe1269b332df84084f29 + md5: c1153b2cb3318889ce624a3b4f0db7f7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 102979 + timestamp: 1762504186626 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py314h9891dd4_1.conda + sha256: d41c2734d314303e329680aeef282766fe399a0ce63297a68a2f8f9b43b1b68a + md5: c6752022dcdbf4b9ef94163de1ab7f03 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 103380 + timestamp: 1762504077009 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.2-py311h4c6fd42_2.conda + sha256: 68bf1fecf02957529d7f526cf183f245a2ab0f10c35a411a9e0c410827c01d22 + md5: 980466a8d71ee28ab17b2ec628111746 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 114157 + timestamp: 1788525098501 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + sha256: 5d46557214ed184381dafe835b7c94a474a1c3b307a08a250b1ea4779b44ffb3 + md5: ee6c0cd80a60961a1f48aa3e0b91f986 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 911196 + timestamp: 1786355078102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.13.2-h171cf75_0.conda + sha256: 6f7d59dbec0a7b00bf5d103a4306e8886678b796ff2151b62452d4582b2a53fb + md5: b518e9e92493721281a60fa975bddc65 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 186323 + timestamp: 1763688260928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py311h2e04523_1.conda + sha256: 2f9971a62316b9acb6ade749cebb59ffe750d1c2d99fe7061c6440589f6d3299 + md5: a8105076864776eceae69d64d30e24d7 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 9385101 + timestamp: 1770098496391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c + md5: f61eb8cd60ff9057122a3d338b99c00f + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3164551 + timestamp: 1769555830639 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + sha256: 4747b2d6a8336f52343bceb8a1ebf41a9e6e665b9d2e3f989972de53a310599e + md5: 16e3034a330cc625f2c685edec60cf4e + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=15 + license: Apache-2.0 + license_family: Apache + run_exports: + weak: + - openssl >=3.6.4,<4.0a0 + size: 3202955 + timestamp: 1787698780103 +- conda: https://conda.anaconda.org/conda-forge/linux-64/patchelf-0.17.2-h58526e2_0.conda + sha256: eb355ac225be2f698e19dba4dcab7cb0748225677a9799e9cc8e4cadc3cb738f + md5: ba76a6a448819560b5f8b08a9c74f415 + depends: + - libgcc-ng >=7.5.0 + - libstdcxx-ng >=7.5.0 + license: GPL-3.0-or-later + license_family: GPL + purls: [] + size: 94048 + timestamp: 1673473024463 +- conda: https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.25.0-py310hefeb9ab_5.conda + noarch: python + sha256: 6bf07e71961b9c63169056f638ab00e0504f24b6d4c57e0bb6c5375b4a9bf89f + md5: eeb15389d638a5a99098e9471ec35182 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - _python_abi3_support 1.* + - cpython >=3.10 + - openssl >=3.5.8,<4.0a0 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 12072905 + timestamp: 1788524945832 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py311h0d48ccd_5.conda + sha256: e8d1f077b89df39e6dfe38b4ddb7498f55f65a57dd6c2cbc4f004928b432b4cd + md5: aaf4405628ebec5ad160ba9d71c1884b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 88945 + timestamp: 1788489341224 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py311h49ec1c0_3.conda + sha256: 61c07e45a0a0c7a2b0dc986a65067fc2b00aba51663b7b05d4449c7862d7a390 + md5: 77c1b47af5775a813193f7870be8644a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 88491 + timestamp: 1757744790912 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py314h5bd0f2a_3.conda + sha256: ccef6174b89815de1ede61b3b20ebccfe301865f2954d789e0b5c1e52dd45f91 + md5: be49bb746ad2cd2ba6737b4afd6cf32a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 88644 + timestamp: 1757744868118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.5-py311ha6e1976_1.conda + sha256: 155f6d81262674afcc33f59b8ea2ae1be7d2ba54934f02e8f606dffa53749067 + md5: 5e2d89867688b49795363286e5d9360e + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 1858432 + timestamp: 1787928981213 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 + md5: a5ebcefec0c12a333bcd6d7bf3bddc1f + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 30949404 + timestamp: 1772730362552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.16-h5f976f7_2_cpython.conda + build_number: 2 + sha256: 35375a255970809e8b0a1c744d56533f4da5339769ef5f5a24fe165ca321fcf3 + md5: 1d34da7fd53578abed31372e70a7ef4e + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libpython 3.11.16 h0c77377_2_cpython + - libsqlite >=3.53.4,<4.0a0 + - libuuid >=2.42.3,<3.0a0 + - libxcrypt >=4.4.38 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.8,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + run_exports: + weak: + - python_abi 3.11.* *_cp311 + noarch: + - python + size: 23172280 + timestamp: 1788393513780 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda + build_number: 101 + sha256: cb0628c5f1732f889f53a877484da98f5a0e0f47326622671396fb4f2b0cd6bd + md5: c014ad06e60441661737121d3eae8a60 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 36702440 + timestamp: 1770675584356 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + sha256: c9a6cd2c290d7c3d2b30ea34a0ccda30f770e8ddb2937871f2c404faf60d0050 + md5: a24add9a3bababee946f3bc1c829acfe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 206190 + timestamp: 1770223702917 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda + sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d + md5: 2035f68f96be30dc60a5dfd7452c7941 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=compressed-mapping + size: 202391 + timestamp: 1770223462836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + sha256: 01b3fe073a66e321970d09e04b388708f8cbdca5cdfbfcb7c9eeb470ad10383d + md5: 69c01c781e7c8190bbdaf79e3848c5ca + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 348899 + timestamp: 1787033801506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + sha256: a1973f41a6b956f1305f9aaefdf14b2f35a8c9615cfe5f143f1784ed9aa6bf47 + md5: 69fbc0a9e42eb5fe6733d2d60d818822 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 34194 + timestamp: 1731925834928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.8.post0-hee9eb32_0.conda + sha256: fc23277984a1bfff6edfaf4956484f3daa2f6271035050480eddb3c4c9076d46 + md5: 3248db26fec71163db19d13fbabecd42 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc >=14.2.8.post0,<14.3.0a0 + size: 37774 + timestamp: 1788160449779 +- conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + sha256: 568485837b905b1ea7bdb6e6496d914b83db57feda57f6050d5a694977478691 + md5: 828302fca535f9cfeb598d5f7c204323 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - reproc 14.2.5.post0 hb9d3cd8_0 + license: MIT + license_family: MIT + purls: [] + size: 25665 + timestamp: 1731925852714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.8.post0-h1c70be6_0.conda + sha256: 86baa58c2aa797fc9c60d8dac7c9b084651464fcad2c6a2bea8036fcc1d3dd7d + md5: 04afaf7b13f8ac3c947230a24d835c94 + depends: + - reproc ==14.2.8.post0 hee9eb32_0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - reproc >=14.2.8.post0,<14.3.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + size: 29221 + timestamp: 1788160449779 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.6-hb9d3cd8_1.conda + sha256: d5c73079c1dd2c2a313c3bfd81c73dbd066b7eb08d213778c8bff520091ae894 + md5: c1c9b02933fdb2cfb791d936c20e887e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 193775 + timestamp: 1748644872902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py311haee01d2_2.conda + sha256: 5324d0353c9cca813501ea9b20186f8e6835cd4ff6e4dfa897c5faeecce8cdaa + md5: 672395a7f912a9eda492959b7632ae6c + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 294535 + timestamp: 1766175791754 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.17-py314h0f05182_2.conda + sha256: 59f7773ed8c6f2bcab3e953e2b4816c86cf71e5508dc571f8073b8c2294b5787 + md5: 8ea76c64b49b16c37769ea7c4dca993b + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 308487 + timestamp: 1766175778417 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py311h194be0f_5.conda + sha256: 1ebd313d88ab117c3d9a1597692b6156a6f99f9a1297a0072c90f414d01b6b11 + md5: e717037e5933d798cc1e84cc48a5abc9 + depends: + - python + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 159979 + timestamp: 1788881215986 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py311haee01d2_1.conda + sha256: 2d7e8976b0542b7aae1a9e4383b7b1135e64e9e190ce394aed44983adc6eb3f2 + md5: e3dfd8043a0fac038fe0d7c2d08ac28c + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 153044 + timestamp: 1766159530795 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.15-py314h0f05182_1.conda + sha256: 3bd8db7556e87c98933a47ff9f962af7b8e0dc3757a72180b27cbfcb1f98d2d9 + md5: 4f35ae1228a6c5d9df593367ffe8dda1 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 150041 + timestamp: 1766159514023 +- conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.4-hb700be7_0.conda + sha256: ffe0c49e65486b485e66c7e116b1782189c970c16cb2fe9710a568e44bb9ede3 + md5: da6caa4c932708d447fb80eed702cb4e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 294996 + timestamp: 1766034103379 +- conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.6.11-h4dbf13b_0.conda + sha256: e313740ff17c7912994225ad62c795ef0fdc19d7e6a08a4e6bff3e597205f79c + md5: a65fbdd7742df8c2e87304a988889e4f + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + license: Apache-2.0 + license_family: APACHE + run_exports: + weak: + - simdjson >=4.6.11,<4.7.0a0 + size: 358626 + timestamp: 1788630509372 +- conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hab81395_1.conda + sha256: c650f3df027afde77a5fbf58600ec4ed81a9edddf81f323cfb3e260f6dc19f56 + md5: a3b0e874fa56f72bc54e5c595712a333 + depends: + - __glibc >=2.17,<3.0.a0 + - fmt >=12.1.0,<12.2.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 196681 + timestamp: 1767781665629 +- conda: https://conda.anaconda.org/conda-forge/linux-64/spdlog-1.17.0-hb6aa676_2.conda + sha256: 5fca627690d56c6f87a9dabafceee54cb75d7da392309bb080333948c048365a + md5: 5e32ceeb7b9514ce4730565052829dbd + depends: + - __glibc >=2.17,<3.0.a0 + - fmt >=12.1.0,<12.2.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - spdlog >=1.17.0,<1.18.0a0 + size: 197438 + timestamp: 1785924346496 +- conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-h2d22210_1.conda + sha256: 7d313578d79ece2b328084d906958888b5a474326a24833317d95a71e264b219 + md5: a4935b2eea119342f6a9d666e821984d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - openssl >=3.5.0,<4.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: [] + size: 4319647 + timestamp: 1748302828104 +- conda: https://conda.anaconda.org/conda-forge/linux-64/taplo-0.10.0-he64ecbb_2.conda + sha256: 4fadb3cddac0461c6715fa987944d6876e7402efc037ab5c05707b788d7dfcf9 + md5: 718059e50f92fa30cb9b4daa597b41ce + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - openssl >=3.5.6,<4.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 4099420 + timestamp: 1780697891321 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + build_number: 104 + sha256: a1a241d172c1ccab067ba245206dd048bc3c2d1b84504b53c9468e99adfc16a1 + md5: 676a2f9b1c4fcf57b70aa5c65f6c60d0 + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - xorg-libx11 >=1.8.13,<2.0a0 + license: TCL + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3566806 + timestamp: 1787272857910 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py311hdf67eae_0.conda + sha256: 31ad759e2d7668f1615021bca0ae2358a284939e3ca3fcb971b3f7aa83c2a6d6 + md5: 5a715cf5e3dc6cd68717c50e47dd7b6b + depends: + - __glibc >=2.17,<3.0.a0 + - cffi + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + run_exports: {} + size: 14898 + timestamp: 1769438724694 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda + sha256: c84034056dc938c853e4f61e72e5bd37e2ec91927a661fb9762f678cbea52d43 + md5: 5d3c008e54c7f49592fca9c32896a76f + depends: + - __glibc >=2.17,<3.0.a0 + - cffi + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + size: 15004 + timestamp: 1769438727085 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 85189 + timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + sha256: d164dfa75ecd538f6fd68765defcc06aa875bc697b9b215362d79a2a73125dd0 + md5: e741576fb8f89821ac7c1c537322a33d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 84936 + timestamp: 1787228426393 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + sha256: 4b0b713a4308864a59d5f0b66ac61b7960151c8022511cdc914c0c0458375eca + md5: 92b90f5f7a322e74468bb4909c7354b5 + depends: + - libstdcxx >=13 + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 223526 + timestamp: 1745307989800 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h54a6638_1.conda + sha256: 425808ebd2a20bfc460995293995e1fa0510e93c300535e2ec3622bd3d84288c + md5: 18bf4b05b458fb8135987e47364f3dcb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml-cpp >=0.8.0,<0.9.0a0 + size: 242654 + timestamp: 1785923983284 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_1.conda + sha256: d534a6518c2d8eccfa6579d75f665261484f0f2f7377b50402446a9433d46234 + md5: ca45bfd4871af957aaa5035593d5efd2 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 466893 + timestamp: 1762512695614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py314h0f05182_1.conda + sha256: e589f694b44084f2e04928cabd5dda46f20544a512be2bdb0d067d498e4ac8d0 + md5: 2930a6e1c7b3bc5f66172e324a8f5fc3 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 473605 + timestamp: 1762512687493 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + sha256: 47d682b9f6d6ec9eb1a6e6c3e75ea6273e899e78fb7fc59f81d39745009fbc60 + md5: aa459086047c0e5e27023ab19f8cb86a + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 601301 + timestamp: 1786599621503 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: a2527b1d81792a0ccd2c05850960df119c2b6d8f5fdec97f2db7d25dc23b1068 + md5: 468fd3bb9e1f671d36c2cbc677e56f1d + depends: + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 28926 + timestamp: 1770939656741 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/backports.zstd-1.3.0-py311h6ce31b0_0.conda + sha256: b11baef215becac7d657289bc4171640be7ec6ff5068d3f9fb8a2b0e06242852 + md5: 219e216268cad83c58f10435ce2001fb + depends: + - python + - python 3.11.* *_cpython + - libgcc >=14 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 250129 + timestamp: 1767044999147 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/backports.zstd-1.7.0-py311h9a9e91b_1.conda + sha256: ef0ff992d2a3cb379a8310979b38802db9494a9c36c7a33685a491696bf54028 + md5: 23dbf68e403c5c3126cf045c8d740867 + depends: + - python + - libgcc >=15 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + run_exports: {} + size: 249460 + timestamp: 1788491287547 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils-2.45.1-default_hf1166c9_101.conda + sha256: 7113440420c6f31742c2b29d7590900362007a0bb0d31f9bc5c9a1379d9ab702 + md5: 77f58300ab7d95ce79f9c2c13ad72d5c + depends: + - binutils_impl_linux-aarch64 >=2.45.1,<2.45.2.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 35322 + timestamp: 1770267247190 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils_impl_linux-aarch64-2.45.1-default_h5f4c503_101.conda + sha256: e90ab42a5225dc1eaa6e4e7201cd7b8ed52dad6ec46814be7e5a4039433ae85c + md5: df6e1dc38cbe5642350fa09d4a1d546b + depends: + - ld_impl_linux-aarch64 2.45.1 default_h1979696_101 + - sysroot_linux-aarch64 + - zstd >=1.5.7,<1.6.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 4741684 + timestamp: 1770267224406 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/binutils_linux-aarch64-2.45.1-default_hf1166c9_101.conda + sha256: 4ed3cf8af327b1c8b7e71433c98eb0154027e07b726136e81235276e9025489a + md5: 99924e610d9960dc3d8b865614787cec + depends: + - binutils_impl_linux-aarch64 2.45.1 default_h5f4c503_101 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 36223 + timestamp: 1770267249899 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py311h14a79a7_1.conda + sha256: bf73f124e8dd683c5f414b9bea077246fcdec3f6c530bd83234b5eb329b52423 + md5: 292e7c014bfab5c77a2ff9c92728bb50 + depends: + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 he30d5cf_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 373346 + timestamp: 1764017600174 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py311hf1a560e_4.conda + sha256: db54dc8c5c2ade71ce23a3b8c88c4c68c31ab8f797fe1ad8426faecead98cbf6 + md5: bc7351a3de5985a46dbcfde04f4c07da + depends: + - libgcc >=15 + - libstdcxx >=15 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 h384ecca_4 + license: MIT + license_family: MIT + run_exports: {} + size: 367664 + timestamp: 1788480421097 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda + sha256: 5a5b0cdcd7ed89c6a8fb830924967f6314a2b71944bc1ebc2c105781ba97aa75 + md5: a1b5c571a0923a205d663d8678df4792 + depends: + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 he30d5cf_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 373193 + timestamp: 1764017486851 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_10.conda + sha256: 24eacc8a20fd7c4616566178562bef7f9344eb4a8700cfc3180fa75a6ff9d39f + md5: fd544ef1c672d645bf78e5819cbb8f91 + depends: + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 194694 + timestamp: 1785906301397 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda + sha256: b3495077889dde6bb370938e7db82be545c73e8589696ad0843a32221520ad4c + md5: 840d8fc0d7b3209be93080bc20e07f2d + depends: + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 192412 + timestamp: 1771350241232 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.6-he30d5cf_0.conda + sha256: 7ec8a68efe479e2e298558cbc2e79d29430d5c7508254268818c0ae19b206519 + md5: 1dfbec0d08f112103405756181304c16 + depends: + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 217215 + timestamp: 1765214743735 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.34.8-h29ee22c_2.conda + sha256: 3838d10a78c206a1a4ec7ff041880b4f9b8ecde3520c911daae9e06baeb8858e + md5: eb80eb38625b8552a099aa88f8ba5db7 + depends: + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 241210 + timestamp: 1787169968125 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/c-compiler-1.11.0-hdceaead_0.conda + sha256: a16c5078619d60e54f75336ed2bbb4ee0fb6f711de02dd364983748beda31e04 + md5: 89bc32110bba0dc160bb69427e196dc4 + depends: + - binutils + - gcc + - gcc_linux-aarch64 14.* + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6721 + timestamp: 1753098688332 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py311h460c349_1.conda + sha256: a0a49dc4fc0d177ba75d797660145d9405f956944af75d0c402d7733c6c71f60 + md5: eef8f2527e60a82df85eba1cc4f9d5e1 + depends: + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 321642 + timestamp: 1761203947874 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.0.0-py314h0bd77cf_1.conda + sha256: 728e55b32bf538e792010308fbe55d26d02903ddc295fbe101167903a123dd6f + md5: f333c475896dbc8b15efd8f7c61154c7 + depends: + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 318357 + timestamp: 1761203973223 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.1.1-py311hc2bebf7_3.conda + sha256: 3fd76ae353007c9e4276b53edb3dbda529147386097999fcfc0715b66cb8d3bc + md5: 336f09b122b9a219edfcd0b329fa5305 + depends: + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 332548 + timestamp: 1788485269704 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cmake-3.31.8-hc9d863e_0.conda + sha256: 91cbfd13132b7253a6ef001292f5cf59deac38d2cbc86d6c1ad6ebf344cd998c + md5: 855e698fd08794a573cd6104be8da4d0 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libcurl >=8.14.1,<9.0a0 + - libexpat >=2.7.1,<3.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 + - libstdcxx >=14 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - rhash >=1.4.6,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 20216587 + timestamp: 1757877248575 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py311hec3470c_0.conda + sha256: 6c0920b4eaf5c13201c05c7de3080289468a1281ad9eb14719635815acf44523 + md5: 2d434ed94247068c4cc439c22d51b693 + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-env >=2.6 + - conda-build >=25.9 + - conda-content-trust >=0.1.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1288665 + timestamp: 1772191444841 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.1.1-py314h28a4750_0.conda + sha256: 694fdd47a58ea57163ca5bb64bbe1332d413644f3a2cfa9ea515828aa98b73ab + md5: d511b5ac95faec60409f48c00a6d8f06 + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.1.1 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1311034 + timestamp: 1772191450615 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-26.7.2-py311hee276e8_1.conda + sha256: 386c22dd13be92ba994efbfb30ff4c67b77f489dee68b7a6ef84c63ff2f50112 + md5: ffd0325d347ebdbc89ab80028607d2bd + depends: + - archspec >=0.2.3 + - backports.zstd >=1.3.0 + - boltons >=23.0.0 + - charset-normalizer + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - menuinst >=2 + - msgpack-python >=1.1.1 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.6.0 + - pycosat >=0.6.3 + - python + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.20 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - conda-libmamba-solver >=26.4.1 + - conda-lockfiles >=0.2.0 + - conda-pypi >=0.10.1 + - conda-rattler-solver >=0.1.1 + - conda-self >=0.2.0 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.3.0 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1442951 + timestamp: 1788876749538 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/conda-gcc-specs-14.3.0-hadff5d6_18.conda + sha256: 7b018e74d2f828e887faabc9d5c5bef6d432c3356dcac3e691ee6b24bc82ef52 + md5: 184c1aba41c40e6bc59fa91b37cd7c3f + depends: + - gcc_impl_linux-aarch64 >=14.3.0,<14.3.1.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 31474 + timestamp: 1771377963347 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_0.conda + sha256: 4edebca2a9dadd8afabb3948a007edae2072e4869d8959b3e51aebc2367e1df4 + md5: 13f0be21fdd39114c28501ac21f0dd00 + depends: + - libstdcxx >=14 + - libgcc >=14 + license: CC0-1.0 + purls: [] + size: 25132 + timestamp: 1756734793606 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cpp-expected-1.3.1-hdc560ac_1.conda + sha256: 178992e56e45feec10bb4292c8e5a9dd13b997470e624a58b1af14004765c697 + md5: 9d634b13d6b78f143345f2932ae80700 + depends: + - libstdcxx >=14 + - libgcc >=14 + license: CC0-1.0 + run_exports: {} + size: 25271 + timestamp: 1785920466877 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cxx-compiler-1.11.0-h7b35c40_0.conda + sha256: b87cd33501867d999caa1a57e488e69dc9e08011ec8685586df754302247a7a4 + md5: 0234c63e6b36b1677fd6c5238ef0a4ec + depends: + - c-compiler 1.11.0 hdceaead_0 + - gxx + - gxx_linux-aarch64 14.* + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6705 + timestamp: 1753098688728 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/doxygen-1.13.2-h5e0f5ae_0.conda + sha256: 671fc9849fcdb9138daf2ab6b2d45b0650055ba1496cda19c415f57cabc381b8 + md5: 9091aa1c92ed01d5fe3d34d7e585b6a1 + depends: + - libgcc >=13 + - libiconv >=1.17,<2.0a0 + - libstdcxx >=13 + license: GPL-2.0-only + license_family: GPL + purls: [] + size: 13227405 + timestamp: 1738171320483 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h166da81_1.conda + sha256: 9576231133ef25127cf8855492a6e34a3189e3a8986cc69ebfef6516670d4822 + md5: 2ba56bd59c36644d783ef8a8086ec0e4 + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 197306 + timestamp: 1785915387774 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fmt-12.1.0-h20c602a_0.conda + sha256: 7826619c80af5a5fb0c1f2a965c93f4b92670523e12ff45c592daa3f11340746 + md5: 067209b690c2d7f42e1e4c370d1aff12 + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 197671 + timestamp: 1767681179883 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py311h19352d5_0.conda + sha256: 0951b89bc10e54b5e20c6bd8ef990e5afc155d13d713c45a525fe87f478980a6 + md5: 9ef6a694ecd981dad43ab8f1864bcf43 + depends: + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 32258 + timestamp: 1763083003765 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/frozendict-2.4.7-py314h51f160d_0.conda + sha256: 669bfad0045393c79ad96fea1900d30d9c497cde6dd79ff6500be53245874c67 + md5: 723828d9832e586bf2f9c52c38d4bf01 + depends: + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 31504 + timestamp: 1763082941890 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc-14.3.0-h2e72a27_18.conda + sha256: debc5c801b3af35f1d474aead8621c4869a022d35ca3c5195a9843d81c1c9ab4 + md5: db4bf1a70c2481c06fe8174390a325c0 + depends: + - conda-gcc-specs + - gcc_impl_linux-aarch64 14.3.0 h533bfc8_18 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 29438 + timestamp: 1771378102660 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc_impl_linux-aarch64-14.3.0-h533bfc8_18.conda + sha256: e2488aac8472cfdff4f8a893861acd1ce1c66eafb28e7585ec52fe4e7546df7e + md5: 2ac1b579c1560e021a4086d0d704e2be + depends: + - binutils_impl_linux-aarch64 >=2.45 + - libgcc >=14.3.0 + - libgcc-devel_linux-aarch64 14.3.0 h25ba3ff_118 + - libgomp >=14.3.0 + - libsanitizer 14.3.0 hedb4206_18 + - libstdcxx >=14.3.0 + - libstdcxx-devel_linux-aarch64 14.3.0 h57c8d61_118 + - sysroot_linux-aarch64 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 69149627 + timestamp: 1771377858762 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gcc_linux-aarch64-14.3.0-h118592a_21.conda + sha256: 2b4c579549e63f8f7e29aa332a95b85a5a33976d6caf42d7c3dc147d2939d7a0 + md5: dfe811f86ef2d8f511263ef38b773a39 + depends: + - gcc_impl_linux-aarch64 14.3.0.* + - binutils_linux-aarch64 + - sysroot_linux-aarch64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28666 + timestamp: 1770908257439 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gh-2.88.1-h94b2740_0.conda + sha256: bfa267b3a68c629b8d5a3bbb06dc828ba724f68a1e10aa5cc62667cca690d16a + md5: e1f7ec5f227660d3e0ce658e9acb3827 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 20986983 + timestamp: 1773335353762 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx-14.3.0-ha384071_18.conda + sha256: 09fb56bcb1594d667e39b1ff4fced377f1b3f6c83f5b651d500db0b4865df68a + md5: 3d5380505980f8859a796af4c1b49452 + depends: + - gcc 14.3.0 h2e72a27_18 + - gxx_impl_linux-aarch64 14.3.0 h0d4f5d4_18 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28822 + timestamp: 1771378129202 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx_impl_linux-aarch64-14.3.0-h0d4f5d4_18.conda + sha256: 859a78ff16bef8d1d1d89d0604929c3c256ac0248b9a688e8defe9bbc027c886 + md5: a12277d1ec675dbb993ad72dce735530 + depends: + - gcc_impl_linux-aarch64 14.3.0 h533bfc8_18 + - libstdcxx-devel_linux-aarch64 14.3.0 h57c8d61_118 + - sysroot_linux-aarch64 + - tzdata + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 13513218 + timestamp: 1771378064341 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx_linux-aarch64-14.3.0-h32e4f2e_21.conda + sha256: 10059135f9960de93f991ce7fb6ef9d833dc2ac675459a1a08def052e5a29667 + md5: 3114b029596eff0aeb9fc0c81f598211 + depends: + - gxx_impl_linux-aarch64 14.3.0.* + - gcc_linux-aarch64 ==14.3.0 h118592a_21 + - binutils_linux-aarch64 + - sysroot_linux-aarch64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 27275 + timestamp: 1770908257444 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/hypothesis-6.168.0-py311h8ca180e_0.conda + noarch: python + sha256: 4cec51b04b08f99662449331a37bf95842f96eda2912ede421841b6bbb659c22 + md5: 55ea7be004774a70a8771159c9412bff + depends: + - python + - sortedcontainers >=2.1.0,<3.0.0 + - exceptiongroup >=1.0.0 + - libgcc >=15 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __glibc >=2.17 + license: MPL-2.0 + license_family: MOZILLA + run_exports: {} + size: 636795 + timestamp: 1788949810428 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.2-hcab7f73_0.conda + sha256: dcbaa3042084ac58685e3ef4547e4c4be9d37dc52b92ea18581288af95e48b52 + md5: 998ee7d53e32f7ab57fc35707285527e + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 12851689 + timestamp: 1772208964788 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda + sha256: 49ba6aed2c6b482bb0ba41078057555d29764299bc947b990708617712ef6406 + md5: 546da38c2fa9efacf203e2ad3f987c59 + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 12837286 + timestamp: 1773822650615 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-py311h3512406_2.conda + sha256: 54d921defd947adb58a90e10203d3bfe6c1f209f5f1a1ad2c6a9f7617f2fb59e + md5: b35bbb1b957440ed742f35fb96eb7f1c + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14688525 + timestamp: 1786545779464 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h5bc82ec_1.conda + sha256: 3999b1472f1e549a0b766428e2823abf20626aac5314b474c9b76bf6eb679def + md5: 6d9be306ecb8dfc9ff46f02cb367d5c2 + depends: + - libgcc >=15 + license: LGPL-2.1-or-later + run_exports: + weak: + - keyutils >=1.6.3,<2.0a0 + size: 129546 + timestamp: 1786739205968 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda + sha256: 5ce830ca274b67de11a7075430a72020c1fb7d486161a82839be15c2b84e9988 + md5: e7df0aab10b9cbb73ab2a467ebfaf8c7 + depends: + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + size: 129048 + timestamp: 1754906002667 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-h095d8e5_2.conda + sha256: 601d79af94d9562ba70e2d4947034ba159aae293a3c860855335a7c76c14400e + md5: a4d0da122ba952abf76981e76d0d283c + depends: + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=15 + - libstdcxx >=15 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1538590 + timestamp: 1786762058856 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda + sha256: b53999d888dda53c506b264e8c02b5f5c8e022c781eda0718f007339e6bc90ba + md5: d9ca108bd680ea86a963104b6b3e95ca + depends: + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1517436 + timestamp: 1769773395215 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_101.conda + sha256: 44527364aa333be631913451c32eb0cae1e09343827e9ce3ccabd8d962584226 + md5: 35b2ae7fadf364b8e5fb8185aaeb80e5 + depends: + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-aarch64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 875924 + timestamp: 1770267209884 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda + sha256: 7abd913d81a9bf00abb699e8987966baa2065f5132e37e815f92d90fc6bba530 + md5: a21644fc4a83da26452a718dc9468d5f + depends: + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-aarch64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 875596 + timestamp: 1774197520746 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.46.1-default_h1979696_102.conda + sha256: 2c4901f4227b0850328ed0c69f958b30ad2cd18982f7a31c7c1f911827004d08 + md5: 489444d0acb2a579d2a002d85a08c059 + depends: + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-aarch64 2.46.1 + license: GPL-3.0-only + license_family: GPL + run_exports: {} + size: 905305 + timestamp: 1784214534868 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.6-gpl_hbe7d12b_100.conda + sha256: 635a78a04461e82150263ee9a1fbf6c965de0b811a10e87392a1e44be1115a39 + md5: f2c45cbb67fd0f668a1a0c152b70dc5f + depends: + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 1000663 + timestamp: 1773243230880 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libarchive-3.8.9-gpl_h1204ebd_101.conda + sha256: 5f6a00f0fdefd023b45d9688d3b74e73f9e996a2ae73d976bf8215194d8c183f + md5: e6866e2cdd76f4746a987af670a1613c + depends: + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 1006265 + timestamp: 1787292419768 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-5_haddc8a3_openblas.conda + build_number: 5 + sha256: 700f3c03d0fba8e687a345404a45fbabe781c1cf92242382f62cef2948745ec4 + md5: 5afcea37a46f76ec1322943b3c4dfdc0 + depends: + - libopenblas >=0.3.30,<0.3.31.0a0 + - libopenblas >=0.3.30,<1.0a0 + constrains: + - mkl <2026 + - libcblas 3.11.0 5*_openblas + - liblapack 3.11.0 5*_openblas + - liblapacke 3.11.0 5*_openblas + - blas 2.305 openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18369 + timestamp: 1765818610617 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-5_hd72aa62_openblas.conda + build_number: 5 + sha256: 3fad5c9de161dccb4e42c8b1ae8eccb33f4ed56bccbcced9cbb0956ae7869e61 + md5: 0b2f1143ae2d0aa4c991959d0daaf256 + depends: + - libblas 3.11.0 5_haddc8a3_openblas + constrains: + - liblapack 3.11.0 5*_openblas + - liblapacke 3.11.0 5*_openblas + - blas 2.305 openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18371 + timestamp: 1765818618899 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.19.0-hc57f145_0.conda + sha256: 75c1b2f9cff7598c593dda96c55963298bebb5bcb5a77af0b4c41cb03d26100b + md5: d5306c7ec07faf48cfb0e552c67339e0 + depends: + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libnghttp2 >=1.67.0,<2.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + size: 485694 + timestamp: 1773218484057 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.22.0-h86273da_0.conda + sha256: 8634a43ca7161d49b94098d8d27f32040ccf19b5227b37d05826ec8b5729b3c4 + md5: d79a5b16c3aaff5269f9118aa1d9f1ba + depends: + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=15 + - libnghttp2 >=1.68.1,<2.0a0 + - libpsl >=0.23.1,<0.24.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.8,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + run_exports: + weak: + - libcurl >=8.22.0,<9.0a0 + size: 524318 + timestamp: 1788340661060 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda + sha256: c0b27546aa3a23d47919226b3a1635fccdb4f24b94e72e206a751b33f46fd8d6 + md5: fb640d776fc92b682a14e001980825b1 + depends: + - ncurses + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 148125 + timestamp: 1738479808948 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321hc48eb74_1.conda + sha256: 2d7218da06f1121619335bff25a2669df810dee90d2eb65b17f8b2e6b1c0d372 + md5: 6e06eb2c9fda76721699bcdd759b9c60 + depends: + - ncurses + - libgcc >=14 + - ncurses >=6.6,<7.0a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 149007 + timestamp: 1786616643904 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h31becfc_2.conda + sha256: 973af77e297f1955dd1f69c2cbdc5ab9dfc88388a5576cd152cda178af0fd006 + md5: a9a13cb143bbaa477b1ebaefbe47a302 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 115123 + timestamp: 1702146237623 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h80f16a2_3.conda + sha256: 4475f79a020e9ff2a5a0308c48cb2970a25d55c3dd724a97ab28bfac3be6cd49 + md5: f679b30a53d410d0b5ae0cb8f5b7397e + depends: + - libgcc >=14 + license: BSD-2-Clause OR GPL-2.0-or-later + license_family: GPL + run_exports: + weak: + - libev >=4.33,<4.34.0a0 + size: 45746 + timestamp: 1785917328690 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda + sha256: 995ce3ad96d0f4b5ed6296b051a0d7b6377718f325bc0e792fbb96b0e369dad7 + md5: 57f3b3da02a50a1be2a6fe847515417d + depends: + - libgcc >=14 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT + purls: [] + size: 76564 + timestamp: 1771259530958 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.5-hfae3067_0.conda + sha256: 6d438fc0bfdb263c24654fe49c09b31f06ec78eb709eb386392d2499af105f85 + md5: 05d1e0b30acd816a192c03dc6e164f4d + depends: + - libgcc >=14 + constrains: + - expat 2.7.5.* + license: MIT + license_family: MIT + purls: [] + size: 76523 + timestamp: 1774719129371 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_1.conda + sha256: 20a5726bc8705d91437c9e6ef83b30da64a1719b869656d20a1ee818333ea5ac + md5: fac3b65a605cd253037fdf3daf2de8d9 + depends: + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + run_exports: {} + size: 77649 + timestamp: 1781203572523 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda + sha256: 3df4c539449aabc3443bbe8c492c01d401eea894603087fca2917aa4e1c2dea9 + md5: 2f364feefb6a7c00423e80dcb12db62a + depends: + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 55952 + timestamp: 1769456078358 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.7.0-hdaad0be_1.conda + sha256: b1879d78b622c5f4817fd6d77fe7ae3ab7b501542e3c2f4ac77198c54c62c189 + md5: f9e5b3e446b526b34a7e25ecec08efd9 + depends: + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 63137 + timestamp: 1787753382033 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda + sha256: 43df385bedc1cab11993c4369e1f3b04b4ca5d0ea16cba6a0e7f18dbc129fcc9 + md5: 552567ea2b61e3a3035759b2fdb3f9a6 + depends: + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 h8acb6b2_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 622900 + timestamp: 1771378128706 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-16.2.0-h205dda4_5.conda + sha256: e39d4701155a133927a90843165c0ac7f5ac5c2f684fb4ad83862f16a8ed4e6d + md5: 0b5bcf498a9e65d337bede418c9294b1 + depends: + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==16.2.0=*_5 + - libgomp 16.2.0 h8acb6b2_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: {} + size: 627589 + timestamp: 1789485830171 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda + sha256: 83bb0415f59634dccfa8335d4163d1f6db00a27b36666736f9842b650b92cf2f + md5: 4feebd0fbf61075a1a9c2e9b3936c257 + depends: + - libgcc 15.2.0 h8acb6b2_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27568 + timestamp: 1771378136019 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda + sha256: 7dcd7dff2505d56fd5272a6e712ec912f50a46bf07dc6873a7e853694304e6e4 + md5: 41f261f5e4e2e8cbd236c2f1f15dae1b + depends: + - libgfortran5 15.2.0 h1b7bec0_18 + constrains: + - libgfortran-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27587 + timestamp: 1771378169244 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda + sha256: 85347670dfb4a8d4c13cd7cae54138dcf2b1606b6bede42eef5507bf5f9660c6 + md5: 574d88ce3348331e962cfa5ed451b247 + depends: + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1486341 + timestamp: 1771378148102 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda + sha256: fc716f11a6a8525e27a5d332ef6a689210b0d2a4dd1133edc0f530659aa9faa6 + md5: 4faa39bf919939602e594253bd673958 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 588060 + timestamp: 1771378040807 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-16.2.0-h8acb6b2_5.conda + sha256: 4f50ea4061b3e7c59f98be9c7f0621bc086d392219f9e349f350b89274200d91 + md5: 9f75d29a595b0b62c9b9299c2561216a + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 617425 + timestamp: 1789485762815 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libhwloc-2.13.0-default_ha95e27d_1000.conda + sha256: 88888d99e81c93e7331f2eb0fec08b3c4a47a1bfa1c88b3e641f6568569b6261 + md5: 974183f6420938051e2f3208922d057f + depends: + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2453519 + timestamp: 1770953713701 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h1ea5142_3.conda + sha256: d22c666eb887afd2119aac2110e23a0e817e9391bda99293421f7c46dab1564c + md5: 951b54a36388613a99820b33b997f690 + depends: + - libgcc >=15 + license: LGPL-2.1-only + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 785924 + timestamp: 1787033793216 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda + sha256: 1473451cd282b48d24515795a595801c9b65b567fe399d7e12d50b2d6cdb04d9 + md5: 5a86bf847b9b926f3a4f203339748d78 + depends: + - libgcc >=14 + license: LGPL-2.1-only + purls: [] + size: 791226 + timestamp: 1754910975665 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-5_h88aeb00_openblas.conda + build_number: 5 + sha256: 692222d186d3ffbc99eaf04b5b20181fd26aee1edec1106435a0a755c57cce86 + md5: 88d1e4133d1182522b403e9ba7435f04 + depends: + - libblas 3.11.0 5_haddc8a3_openblas + constrains: + - liblapacke 3.11.0 5*_openblas + - blas 2.305 openblas + - libcblas 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18392 + timestamp: 1765818627104 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda + sha256: 843c46e20519651a3e357a8928352b16c5b94f4cd3d5481acc48be2e93e8f6a3 + md5: 96944e3c92386a12755b94619bae0b35 + depends: + - libgcc >=14 + constrains: + - xz 5.8.2.* + license: 0BSD + purls: [] + size: 125916 + timestamp: 1768754941722 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_1.conda + sha256: f760669fd1cea27f689f411c0d5488e26ce50e382c9b63e4ad348f959850124a + md5: e0e6411a60d00186eec7c73f4118ab67 + depends: + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 125578 + timestamp: 1786348561649 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.5.0-hc712cdd_0.conda + sha256: 480913318158db625ca7862a2447eda8aa84c8a315d4f86cc1764a5f1050e61c + md5: 45c3a33d8c6db2382678b3c90af03dd4 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - libstdcxx >=14 + - libgcc >=14 + - nlohmann_json-abi ==3.12.0 + - zstd >=1.5.7,<1.6.0a0 + - libsolv >=0.7.35,<0.8.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - reproc-cpp >=14.2,<15.0a0 + - fmt >=12.1.0,<12.2.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - reproc >=14.2,<15.0a0 + - openssl >=3.5.4,<4.0a0 + - simdjson >=4.2.4,<4.3.0a0 + - libarchive >=3.8.5,<3.9.0a0 + - libcurl >=8.18.0,<9.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2414027 + timestamp: 1767884182395 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-2.9.0-py311he2bbb06_1.conda + sha256: d467e39377029dd297cc3479cc81d54098013b4e2ded561dd500d469d8caaf28 + md5: 0c386620f05c16d35290a0c1f2027374 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - libstdcxx >=15 + - libgcc >=15 + - libsolv >=0.7.39,<0.8.0a0 + - libcurl >=8.22.0,<9.0a0 + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + - libmsgpack-c >=6.1.0,<7.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - openssl >=3.5.8,<4.0a0 + - simdjson >=4.6.11,<4.7.0a0 + - nlohmann_json-abi ==3.12.0 + - spdlog >=1.17.0,<1.18.0a0 + - zstd >=1.5.7,<1.6.0a0 + - reproc >=14.2.8.post0,<14.3.0a0 + - fmt >=12.1.0,<12.2.0a0 + - libarchive >=3.8.9,<3.9.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmamba >=2.9.0,<2.10.0a0 + size: 2727635 + timestamp: 1789061566582 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.5.0-h3ad78e7_0.conda + sha256: d100a04349427cfb8cc76b4c0736bd27fd77ccb9c07e8a25e9988cbf718c610b + md5: 4abcea9b345dd46ecc86ca7c20f3db62 + depends: + - libstdcxx >=14 + - libgcc >=14 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23017 + timestamp: 1767884182397 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmamba-spdlog-2.9.0-hf00976d_1.conda + sha256: a3496f476a4162dfa03ee7e1f732f425809f5ca0d3119b96b78b5a750998aea9 + md5: cd3bd6dbf646898c6d79c570438bfa08 + depends: + - libstdcxx >=15 + - libgcc >=15 + - libmamba >=2.9.0,<2.10.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 20443 + timestamp: 1789061566582 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py311h6a8bf82_0.conda + sha256: caef2c6e32e15afc8bb1cf66151223738a29d45cf639acad0267bce948518cc7 + md5: e022e9542b664361fcbc1f9c5528a0a8 + depends: + - python + - libmamba ==2.5.0 hc712cdd_0 + - libmamba-spdlog ==2.5.0 h3ad78e7_0 + - python 3.11.* *_cpython + - libstdcxx >=14 + - libgcc >=14 + - nlohmann_json-abi ==3.12.0 + - fmt >=12.1.0,<12.2.0a0 + - libmamba >=2.5.0,<2.6.0a0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + - pybind11-abi ==11 + - openssl >=3.5.4,<4.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - spdlog >=1.17.0,<1.18.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 832040 + timestamp: 1767884182400 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.5.0-py314h709e558_0.conda + sha256: c226d92922115e9e4a0097b15521b0238a69a028ed0d525f5a5ed6696314ed69 + md5: b9108a7151ea087a321b7ebe3aaba999 + depends: + - python + - libmamba ==2.5.0 hc712cdd_0 + - libmamba-spdlog ==2.5.0 h3ad78e7_0 + - python 3.14.* *_cp314 + - libstdcxx >=14 + - libgcc >=14 + - fmt >=12.1.0,<12.2.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - python_abi 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - libmamba >=2.5.0,<2.6.0a0 + - pybind11-abi ==11 + - openssl >=3.5.4,<4.0a0 + - nlohmann_json-abi ==3.12.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 834849 + timestamp: 1767884182402 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmambapy-2.9.0-py311h2b99d05_1.conda + sha256: 45905f08ef2e2d132a2f5f93227f6881abeee7cf851d39f8456a66e8ea8cb29b + md5: 8dfcaa3b2f325bca86e69ada7d8f2c4b + depends: + - python + - libmamba ==2.9.0 py311he2bbb06_1 + - libmamba-spdlog ==2.9.0 hf00976d_1 + - libstdcxx >=15 + - libgcc >=15 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + - pybind11-abi ==11 + - libmamba >=2.9.0,<2.10.0a0 + - openssl >=3.5.8,<4.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - nlohmann_json-abi ==3.12.0 + - spdlog >=1.17.0,<1.18.0a0 + - fmt >=12.1.0,<12.2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmambapy >=2.9.0,<2.10.0a0 + size: 871945 + timestamp: 1789061566582 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda + sha256: 57c0dd12d506e84541c4e877898bd2a59cca141df493d34036f18b2751e0a453 + md5: 7b9813e885482e3ccb1fa212b86d7fd0 + depends: + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 114056 + timestamp: 1769482343003 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmsgpack-c-6.1.0-h7ac5ae9_7.conda + sha256: 44b4b6a6c14e92ac5b268f202444210b679e8cb5a8406fa43f8b00407094e219 + md5: f2db2171a033d3293e053142227e9df7 + depends: + - libgcc >=14 + - libstdcxx >=14 + constrains: + - msgpack-c ==6.1.0 *_7 + license: BSL-1.0 + run_exports: + weak: + - libmsgpack-c >=6.1.0,<7.0a0 + size: 42683 + timestamp: 1786189337365 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.67.0-ha888d0e_0.conda + sha256: b03f406fd5c3f865a5e08c89b625245a9c4e026438fd1a445e45e6a0d69c2749 + md5: 981082c1cc262f514a5a2cf37cab9b81 + depends: + - c-ares >=1.34.5,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 728661 + timestamp: 1756835019535 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.68.1-ha4b982d_1.conda + sha256: 0c28d734512fa2c66a232b2f3968eae7124dbd6ad42c594c752f1d08dfb86195 + md5: ff4b2b7c169c438bf648fb14f369c3b9 + depends: + - c-ares >=1.34.8,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=15 + - libstdcxx >=15 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libnghttp2 >=1.68.1,<2.0a0 + size: 734829 + timestamp: 1787177407983 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.68.1-hd3077d7_0.conda + sha256: 13782715b9eeebc4ad16d36e84ca569d1495e3516aea3fe546a32caa0a597d82 + md5: be5f0f007a4500a226ef001115535a3d + depends: + - c-ares >=1.34.6,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 726928 + timestamp: 1773854039807 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.1-h86ecc28_1.conda + sha256: c0dc4d84198e3eef1f37321299e48e2754ca83fd12e6284754e3cb231357c3a5 + md5: d5d58b2dc3e57073fe22303f5fed4db7 + depends: + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + run_exports: + weak: + - libnsl >=2.0.1,<2.1.0a0 + size: 34831 + timestamp: 1750274211000 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.30-pthreads_h9d3fd7e_4.conda + sha256: 794a7270ea049ec931537874cd8d2de0ef4b3cef71c055cfd8b4be6d2f4228b0 + md5: 11d7d57b7bdd01da745bbf2b67020b2e + depends: + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + constrains: + - openblas >=0.3.30,<0.3.31.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4959359 + timestamp: 1763114173544 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpsl-0.23.1-hb8431ed_1.conda + sha256: 757f79c182ac3080cf416acd91c7643b8074b8b2e5c1eba0ffc5a16897feb84b + md5: 5ab41e5e70b78eb00ef62cdaa35cd86f + depends: + - libgcc >=15 + - libstdcxx >=15 + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libpsl >=0.23.1,<0.24.0a0 + size: 73118 + timestamp: 1786970733378 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpython-3.11.16-hc3dd739_2_cpython.conda + build_number: 2 + sha256: 03f798f7a64df9b6577c9114163ebce75c6f6347cf3a2e6598e1399f8b7deb3c + md5: 40bc6cb8fc66f7be25168e76685cfb22 + depends: + - libgcc >=15 + - libstdcxx >=15 + license: Python-2.0 + run_exports: {} + size: 7277187 + timestamp: 1788393584758 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsanitizer-14.3.0-hedb4206_18.conda + sha256: 48641a458e3da681038af7ebdab143f9b6861ad9d1dcc2b4997ff2b744709423 + md5: 03feac8b6e64b72ae536fdb264e2618d + depends: + - libgcc >=14.3.0 + - libstdcxx >=14.3.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 7526147 + timestamp: 1771377792671 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.35-hdda61c4_0.conda + sha256: f68dde30e903721825214310a98ff2444857d168b12ef657c064aad22a620f06 + md5: 3e817cbcc10f018c547a1b4885094b15 + depends: + - libstdcxx >=14 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 535157 + timestamp: 1754325829284 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.36-hdda61c4_0.conda + sha256: 25019059bcbe38bdf66899d3d7b5dd48d17e01b2ff9f9e8e4d494ce51e156d75 + md5: 9b21c050436d116716f113cecd3bbcb8 + depends: + - libstdcxx >=14 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 535353 + timestamp: 1773328550407 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsolv-0.7.39-h6e0bd25_1.conda + sha256: a6a28f9b2bd6f629a5b581f2f767c0973cc222defeab47743ac6aa470dcffe75 + md5: d34592f8e14023ab063c19f6c70d2b35 + depends: + - libstdcxx >=15 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libsolv >=0.7.39,<0.8.0a0 + size: 579234 + timestamp: 1786955808256 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda + sha256: 1ddaf91b44fae83856276f4cb7ce544ffe41d4b55c1e346b504c6b45f19098d6 + md5: 77891484f18eca74b8ad83694da9815e + depends: + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 952296 + timestamp: 1772818881550 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.4-h399dd60_1.conda + sha256: fae75e68e9dbc3c90dcf93d4bae6315f1330c95aaf1404a721e8468266c23475 + md5: 27e16aa1f45c787aa181549be6f209f4 + depends: + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: blessing + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 972932 + timestamp: 1787051100646 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h18c354c_0.conda + sha256: 1e289bcce4ee6a5817a19c66e296f3c644dcfa6e562e5c1cba807270798814e7 + md5: eecc495bcfdd9da8058969656f916cc2 + depends: + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 311396 + timestamp: 1745609845915 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.11.1-h7572e3e_1.conda + sha256: f6fef76c1c8899d7976030871b8cd8016b0a306d2b8519834cd6491d6a42fb82 + md5: 833be3f226277d894df3c7a7131d9532 + depends: + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libssh2 >=1.11.1,<2.0a0 + size: 315682 + timestamp: 1786713068743 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda + sha256: 31fdb9ffafad106a213192d8319b9f810e05abca9c5436b60e507afb35a6bc40 + md5: f56573d05e3b735cb03efeb64a15f388 + depends: + - libgcc 15.2.0 h8acb6b2_18 + constrains: + - libstdcxx-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5541411 + timestamp: 1771378162499 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-16.2.0-hef695bb_5.conda + sha256: aaac28ea86cc162a2e1719165d1fcab915e796f403482f32d8af11d99dcb7ed9 + md5: 17d44d3c9198f3bd108cd24a87e0d75f + depends: + - libgcc 16.2.0 h205dda4_5 + constrains: + - libstdcxx-ng ==16.2.0=*_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + run_exports: {} + size: 6236582 + timestamp: 1789485847583 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-ng-15.2.0-hdbbeba8_18.conda + sha256: 035a31cde134e706e30029a837a31f729ad32b7c5bca023271dfe91a8ba6c896 + md5: 699d294376fe18d80b7ce7876c3a875d + depends: + - libstdcxx 15.2.0 hef695bb_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27645 + timestamp: 1771378204663 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda + sha256: c37a8e89b700646f3252608f8368e7eb8e2a44886b92776e57ad7601fc402a11 + md5: cf2861212053d05f27ec49c3784ff8bb + depends: + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 43453 + timestamp: 1766271546875 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42-h1022ec0_0.conda + sha256: 7d427edf58c702c337bf62bc90f355b7fc374a65fd9f70ea7a490f13bb76b1b9 + md5: a0b5de740d01c390bdbb46d7503c9fab + depends: + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 43567 + timestamp: 1775052485727 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42.3-hd6fdeab_0.conda + sha256: 3530c720c8b9dbb5aba0712d77fbb158e98d7a533ba1cf4968d42a98c41b7f24 + md5: b759892402ee563731fbbb43860cae00 + depends: + - libgcc >=15 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libuuid >=2.42.3,<3.0a0 + size: 43358 + timestamp: 1788347735193 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuv-1.51.0-he30d5cf_1.conda + sha256: 7a0fb5638582efc887a18b7d270b0c4a6f6e681bf401cab25ebafa2482569e90 + md5: 8e62bf5af966325ee416f19c6f14ffa3 + depends: + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 629238 + timestamp: 1753948296190 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda + sha256: 6b46c397644091b8a26a3048636d10b989b1bf266d4be5e9474bf763f828f41f + md5: b4df5d7d4b63579d081fd3a4cf99740e + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 114269 + timestamp: 1702724369203 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.38-h80f16a2_0.conda + sha256: d3514900e2121972e435f7803763c1843dad6709e48aa02a2b47c4d481f83be7 + md5: b1a5cb7ff2d8f5911ba1fb6897176098 + depends: + - libgcc >=14 + license: LGPL-2.1-or-later + run_exports: + weak: + - libxcrypt >=4.4.38 + size: 133882 + timestamp: 1785887114864 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.2-h79dcc73_0.conda + sha256: da6b2ebbcecc158200d90be39514e4e902971628029b35b7f6ad57270659c5d9 + md5: e3ec9079759d35b875097d6a9a69e744 + depends: + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.2 + license: MIT + license_family: MIT + purls: [] + size: 598438 + timestamp: 1772704671710 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-16-2.15.4-h2e5089f_0.conda + sha256: 58bf7fcf64bae7f624a5e2aed860ddea614a8ec13c8c43fe150420a275b8bdb5 + md5: 01dc16e9c00110df9c4197f8ffbab206 + depends: + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.4 + license: MIT + license_family: MIT + run_exports: {} + size: 631072 + timestamp: 1788635155018 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.2-h825857f_0.conda + sha256: 3e51e1952cb60c8107094b6b78473d91ff49d428ad4bef6806124b383e8fe29c + md5: 19de96909ee1198e2853acd8aba89f6c + depends: + - icu >=78.2,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2-16 2.15.2 h79dcc73_0 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 47837 + timestamp: 1772704681112 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxml2-2.15.4-h92b9305_0.conda + sha256: 3345c88fa9ebb373bcf4069ede898950005fa2d1707e56a13a1ac6339919492f + md5: 066a1f1d5dc4e9d5c5a52670f8e0fef1 + depends: + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 h2e5089f_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 48693 + timestamp: 1788635159263 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda + sha256: 5a2c1eeef69342e88a98d1d95bff1603727ab1ff4ee0e421522acd8813439b84 + md5: 08aad7cbe9f5a6b460d0976076b6ae64 + depends: + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 66657 + timestamp: 1727963199518 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda + sha256: eb111e32e5a7313a5bf799c7fb2419051fa2fe7eff74769fac8d5a448b309f7f + md5: 502006882cf5461adced436e410046d1 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 69833 + timestamp: 1774072605429 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_3.conda + sha256: 76efa6cc9d7e6f5ee3bbca0939f64054af2bdfb3c3632531f8e633cf6c2ea41e + md5: bd534c2fbe56d8c2ea3b2d8f5e12bca8 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 70108 + timestamp: 1785276540870 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-h5ad3122_1.conda + sha256: 67e55058d275beea76c1882399640c37b5be8be4eb39354c94b610928e9a0573 + md5: 6654e411da94011e8fbe004eacb8fe11 + depends: + - libgcc >=13 + - libstdcxx >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 184953 + timestamp: 1733740984533 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.10.0-hfb11796_2.conda + sha256: e8392843a18954fef038f2721fde65c4dbe7693fcd07677e5e74fca85b1a690c + md5: 36a6fab65bc51cf71761eb5339d8ea04 + depends: + - libgcc >=15 + - libstdcxx >=15 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - lz4-c >=1.10.0,<1.11.0a0 + size: 218765 + timestamp: 1786717160600 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h29ee22c_1003.conda + sha256: 37ef465819e0a28d96a527a536106d21624ada3b15a798a98efd741be25ff4aa + md5: c7334d99b22d15d1f4ff9c2454185544 + depends: + - libgcc >=15 + license: GPL-2.0-or-later + license_family: GPL + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 212884 + timestamp: 1787049173097 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lzo-2.10-h80f16a2_1002.conda + sha256: 036428c7b9fd22889108d04c91cecc431f95dc3dba2ede3057330c8125080fd5 + md5: 97af2e332449dd9e92ad7db93b02e918 + depends: + - libgcc >=14 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + size: 190187 + timestamp: 1753889356434 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py311h2dad8b0_1.conda + sha256: 6e831e7d699fbb5a11f086e40cd9ce5c2cd37c7e92b44835b773a22bec273f51 + md5: eab18e6c4b54950bc224d9dd300611ed + depends: + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 27101 + timestamp: 1772446335262 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py311hec3470c_0.conda + sha256: 803b3fc3b4c0c4abc8d2c720793c1cbe784506762a0dcdcb736805c0aebc797d + md5: b2b440c867b680594a750a00c3425417 + depends: + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 187037 + timestamp: 1765733232285 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.4.2-py314h28a4750_0.conda + sha256: ed5d3d0b646207b15aea1022ed10ccccfebfa271b152eeb6169b767a02b632a5 + md5: 41fc65edb06fbcc2c2e68c02d06f6bf7 + depends: + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 188584 + timestamp: 1765733234092 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/menuinst-2.5.2-py311he98b269_2.conda + sha256: df201459de2dc67579a26dc2eb37a51fc72e2e028120b499eb27a058836608aa + md5: c505ee08ec9a9485a47a4910606e7390 + depends: + - python + - tomli-w + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + run_exports: {} + size: 207110 + timestamp: 1788033901150 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py311hfca10b7_1.conda + sha256: 35a36e3062bf84a70c0105b1553a1aa7b16a1dad19a97945efeea2b92ca95f23 + md5: 560e361fc9aff96fdd179e7e5d41471e + depends: + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 100340 + timestamp: 1762504276815 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.1.2-py314hd7d8586_1.conda + sha256: 124a778a7065d75d9d36d80563e75d3a13455b160a761cd38a5ce8f50f87705c + md5: e93c66201de71acb9e99d94f84f897b1 + depends: + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 100176 + timestamp: 1762504193305 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.2.2-py311h8802582_2.conda + sha256: 8037346ebda7b9ba0dba8c90c805f1160e8bc371099ed71b0e18fd5fd61a6cb4 + md5: 84931ce4056b8167f4194dcd82d6f632 + depends: + - python + - libstdcxx >=15 + - libgcc >=15 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 112894 + timestamp: 1788525094701 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda + sha256: 91cfb655a68b0353b2833521dc919188db3d8a7f4c64bea2c6a7557b24747468 + md5: 182afabe009dc78d8b73100255ee6868 + depends: + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 926034 + timestamp: 1738196018799 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.6-h2b6f883_1.conda + sha256: d69a04914139627f0a6bfd19412d6c7f1e37edc896af84a6011e07e8bc1e69fa + md5: c3b4349171ca987ff33a1de61f7b3f96 + depends: + - libgcc >=14 + license: X11 AND BSD-3-Clause + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 955422 + timestamp: 1786355019431 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ninja-1.13.2-hdc560ac_0.conda + sha256: 45fbc7c8c44681f5cefba1e5b26ca504a4485b000c5dfaa31cec0b7bc78d0de4 + md5: 8b5222a41b5d51fb1a5a2c514e770218 + depends: + - libstdcxx >=14 + - libgcc >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 182666 + timestamp: 1763688214250 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.2-py311h669026d_1.conda + sha256: b40796e2b47878a38ffa5e76703a1d4d9f5fc09787cdf54fed9f4af8b66d1eb8 + md5: 6b411688a868a37d95abfb99ebc8b88e + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - python 3.11.* *_cpython + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 8462316 + timestamp: 1770098524745 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.3-py311h669026d_0.conda + sha256: 33e4d3e097a9d05e9e09d2895f4bd20f7fc93c8cb0df35da4716d0ba836a0d86 + md5: 23c6d37dec83159283cfeee4fceebf84 + depends: + - python + - python 3.11.* *_cpython + - libgcc >=14 + - libstdcxx >=14 + - libblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 8464385 + timestamp: 1773839300790 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda + sha256: 7f8048c0e75b2620254218d72b4ae7f14136f1981c5eb555ef61645a9344505f + md5: 25f5885f11e8b1f075bccf4a2da91c60 + depends: + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3692030 + timestamp: 1769557678657 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.4-he6ad1d5_0.conda + sha256: a70c05a9baba9b1ce17c7e8b9479029372069b3bf402dbf200bc03696c531bed + md5: 4aa504e081d16263e90c335df5499b4d + depends: + - ca-certificates + - libgcc >=15 + license: Apache-2.0 + license_family: Apache + run_exports: + weak: + - openssl >=3.6.4,<4.0a0 + size: 3712923 + timestamp: 1787698545024 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/patchelf-0.17.2-h884eca8_0.conda + sha256: 8b98158f36a7a92013a1982ab7a60947151350ac5c513c1d1575825d0fa52518 + md5: bbd8dee69c4ac2e2d07bca100b8fcc31 + depends: + - libgcc-ng >=7.5.0 + - libstdcxx-ng >=7.5.0 + license: GPL-3.0-or-later + license_family: GPL + purls: [] + size: 101306 + timestamp: 1673473812166 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/py-rattler-0.25.0-py310h44f7655_5.conda + noarch: python + sha256: 6d9ef1a2b2c792f7b9c273c51bc3705e2ee8f8204f6fea21f8746384758a6cdd + md5: 27dbde5bda3aef8e71d4bb23e2798f7f + depends: + - python + - libgcc >=15 + - _python_abi3_support 1.* + - cpython >=3.10 + - openssl >=3.5.8,<4.0a0 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 11886352 + timestamp: 1788524935087 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py311h19352d5_3.conda + sha256: 792ef42d5691757c087c2214e03e6e5cd1470075bc99c53a62067690345f80be + md5: 8b3741984919ba072ef6d1243e543336 + depends: + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 88731 + timestamp: 1757744856802 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py311h5a00dd8_5.conda + sha256: 9d6bbd78f4724589baad392f54e7df1af9e3a5d2ae753b5c818ec3316ac84540 + md5: ce116ee2c350c78c6218b756cd88724a + depends: + - libgcc >=15 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 89164 + timestamp: 1788489308164 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pycosat-0.6.6-py314h51f160d_3.conda + sha256: 20c29d8283581ed5fece2ea5ebac9c1f78756ff523d9c866b50110d8fadc3b7f + md5: 180a289ef4d6ce81588a13f55a4b7238 + depends: + - libgcc >=14 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 89501 + timestamp: 1757744787708 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.46.5-py311h8c3699e_1.conda + sha256: 280b4a5a6a2ac600d167b21ff0c9534eabffc58e6a98c293d6d56496f195f259 + md5: 0c6c160c845a4b9c7d438632513f7c26 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - libgcc >=15 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 1751240 + timestamp: 1787928980741 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.11.15-h91f4b29_0_cpython.conda + sha256: da3aa4c63af904d38c2e0b6ceecfa539d60c2653ac3cff7cae79d87298dc4fb0 + md5: bb09184ea3313703da05516cd730e8f8 + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-aarch64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 14306513 + timestamp: 1772728906052 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.11.16-h94ad73b_2_cpython.conda + build_number: 2 + sha256: 7b989ba1e62e7084a5c9619c9c9b4e28ec171ea2ea4fc09124475a7691c4d35d + md5: 3c44f90c42e8b2c78f01ba272434560b + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-aarch64 >=2.36.1 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libpython 3.11.16 hc3dd739_2_cpython + - libsqlite >=3.53.4,<4.0a0 + - libuuid >=2.42.3,<3.0a0 + - libxcrypt >=4.4.38 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.8,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + run_exports: + weak: + - python_abi 3.11.* *_cp311 + noarch: + - python + size: 22811470 + timestamp: 1788393612364 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.3-hb06a95a_101_cp314.conda + build_number: 101 + sha256: 87e9dff5646aba87cecfbc08789634c855871a7325169299d749040b0923a356 + md5: 205011b36899ff0edf41b3db0eda5a44 + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-aarch64 >=2.36.1 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 37305578 + timestamp: 1770674395875 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py311h164a683_1.conda + sha256: e3175f507827cd575b5a7b7b50058cd253977f8286079cb3a6bf0aeaa878071b + md5: 7e1888f50cc191b7817848dbf6f90590 + depends: + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 201473 + timestamp: 1770223445971 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda + sha256: 496b5e65dfdd0aaaaa5de0dcaaf3bceea00fcb4398acf152f89e567c82ec1046 + md5: 9ae2c92975118058bd720e9ba2bb7c58 + depends: + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 195678 + timestamp: 1770223441816 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-ha7194a6_1.conda + sha256: 80167dcf73a96b6d0c1bb2298d7b8b9bc21b27cc5bf56979f9c548b49a4d1722 + md5: 5b399a7afa10098f2a6b02263181426e + depends: + - libgcc >=15 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 364344 + timestamp: 1787033761576 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda + sha256: fe695f9d215e9a2e3dd0ca7f56435ab4df24f5504b83865e3d295df36e88d216 + md5: 3d49cad61f829f4f0e0611547a9cda12 + depends: + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 357597 + timestamp: 1765815673644 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.5.post0-h86ecc28_0.conda + sha256: 3bbcfd61da8ab71c0b7b5bbff471669f64e6a3fb759411a46a2d4fd31a9642cc + md5: 70b14ba118c2c19b240a39577b3e607a + depends: + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 36102 + timestamp: 1745309589538 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-14.2.8.post0-hfb11796_0.conda + sha256: fc4e3e5383d14406a88cef3cd5a9f572fdfef709fd51bec8c291557653c3345a + md5: 52653cdc7eb154c8d570e8fda7efac9f + depends: + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc >=14.2.8.post0,<14.3.0a0 + size: 38985 + timestamp: 1788160439083 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.5.post0-h5ad3122_0.conda + sha256: 57831399b5c2ccc4a2ec4fad4ec70609ddc0e7098a1d8cca62e063860fd1674b + md5: fde98968589573ad478b504642319105 + depends: + - libgcc >=13 + - libstdcxx >=13 + - reproc 14.2.5.post0 h86ecc28_0 + license: MIT + license_family: MIT + purls: [] + size: 26291 + timestamp: 1745309832653 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/reproc-cpp-14.2.8.post0-h63b641b_0.conda + sha256: d5f3a22646bb38a952e11d87c7b5b8d09aff4b62834efbc2f55d10ffd23d44cf + md5: 42fad327a6215d0e2c5cf3acfd831841 + depends: + - reproc ==14.2.8.post0 hfb11796_0 + - libstdcxx >=15 + - libgcc >=15 + - reproc >=14.2.8.post0,<14.3.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + size: 29167 + timestamp: 1788160439083 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rhash-1.4.6-h86ecc28_1.conda + sha256: 0fe6f40213f2d8af4fcb7388eeb782a4e496c8bab32c189c3a34b37e8004e5a4 + md5: 745d02c0c22ea2f28fbda2cb5dbec189 + depends: + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 207475 + timestamp: 1748644952027 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py311h51cfe5d_2.conda + sha256: b965d2023a41acbf76bd965e106835d19e6a26cd69c4e1d439df17471336226e + md5: ecf191a9fa6a518ca91aaa49e3c87584 + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - libgcc >=14 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 298156 + timestamp: 1766175797018 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml-0.18.17-py314h2e8dab5_2.conda + sha256: baedba4fb0d0929ffcbebd001876c115d23c5620457eb9347cdac5978963c262 + md5: 1f86e018331edc941dedc796c4373fe3 + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - libgcc >=14 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 311783 + timestamp: 1766175823921 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py311h0de9f98_5.conda + sha256: 27717bed393b243826c516f6f855fbb1f6f94c507949a52529221bb520b22ff4 + md5: d6dc2c0f7a9e131878ef157215c80ac3 + depends: + - python + - libgcc >=15 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 152663 + timestamp: 1788882469055 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py311h51cfe5d_1.conda + sha256: 77be521169904f2687cb09b6ab8d6bcee0d73d06284bfed84617668ad81c22f3 + md5: 471b202396dfeb16adde1930cd7a8001 + depends: + - python + - libgcc >=14 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 152961 + timestamp: 1766159559428 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.15-py314h2e8dab5_1.conda + sha256: 18a34470b351fccfe6694215b01a9a68a0a9979336b0ea85709bbdeef0658e1c + md5: ca756356e2920f248a74cb42e0b4578d + depends: + - python + - python 3.14.* *_cp314 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 148495 + timestamp: 1766159541094 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.2.4-hfefdfc9_0.conda + sha256: 8731e7cb9438deb3275c4d33d402b99da12f250c4b0bd635a58784c5a01e38f5 + md5: 829b13867c30e5d44ab7d851bfdb5d63 + depends: + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 260530 + timestamp: 1766034125791 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/simdjson-4.6.11-h45ce4d5_0.conda + sha256: 4d476046276b1c575f4c77acfe7f3fbec5b6ab558925f32f3a9feeb298c208ca + md5: cebdce2b76e45bb3c183b3d5ca1939c8 + depends: + - libstdcxx >=15 + - libgcc >=15 + license: Apache-2.0 + license_family: APACHE + run_exports: + weak: + - simdjson >=4.6.11,<4.7.0a0 + size: 318677 + timestamp: 1788630529367 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-h9f97df7_1.conda + sha256: 36f5c0d73d88760438388bc940a65af4d9de3ecaf6fa5656a22a060d262d56f5 + md5: 910d3cbb4ccf371b6355a98b1233eb8f + depends: + - fmt >=12.1.0,<12.2.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 195699 + timestamp: 1767781668042 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/spdlog-1.17.0-hb3fff47_2.conda + sha256: 56a009d553dd669850e61608d71d584172d0ccd4f5c48efe73563c93845a74f3 + md5: 0ae158a495544229d7d28a242131a03a + depends: + - fmt >=12.1.0,<12.2.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - spdlog >=1.17.0,<1.18.0a0 + size: 196389 + timestamp: 1785924378981 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-h3618846_1.conda + sha256: 50944952f49509d1125bfafe2a958e55f5dc585dd6f7608c6707dd892e8356a8 + md5: 9bdb00138021e1f22b8d14a2f7fe52dc + depends: + - libgcc >=13 + - openssl >=3.5.0,<4.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: [] + size: 4155701 + timestamp: 1748302870538 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/taplo-0.10.0-hb434046_2.conda + sha256: ccb8d8c30d8ea0741724065e2f328051dc0d983ada052999e9252c30c6cd3d05 + md5: edde7f5cf2bb58fcfe3947daad580f25 + depends: + - libgcc >=14 + - openssl >=3.5.6,<4.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 3969209 + timestamp: 1780697914100 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda + sha256: e25c314b52764219f842b41aea2c98a059f06437392268f09b03561e4f6e5309 + md5: 7fc6affb9b01e567d2ef1d05b84aa6ed + depends: + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3368666 + timestamp: 1769464148928 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_hf03c496_4.conda + build_number: 104 + sha256: a1aa0d0f0b3d539644ff992e6dd0facf950c79df58fc37590ce5be84ff5e3f22 + md5: ebaded4b4b74c2cc43a754ded4eed76f + depends: + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + constrains: + - xorg-libx11 >=1.8.13,<2.0a0 + license: TCL + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3664220 + timestamp: 1787272859143 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py311hfca10b7_0.conda + sha256: 8870876bc36f47d7aea163843e01399c37153c7f448335f629d37f276bfa5010 + md5: 07e977e2a642a2348385860eb6fa84e9 + depends: + - cffi + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + run_exports: {} + size: 15709 + timestamp: 1769438766154 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ukkonen-1.1.0-py314hd7d8586_0.conda + sha256: 0a7efe469d7e2a34a1d017bc51cf6fb9a51436730256983694c5ad74a33bd4e0 + md5: f3a967efabf9cf450b7741487318ea6e + depends: + - cffi + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + size: 15756 + timestamp: 1769438772414 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h29ee22c_3.conda + sha256: 97e0fbe447c8f8bb1789047f37448062ebecda29bd264fcdf6129b8d08f174c9 + md5: 6c97c1f44a81be1b4d5ba15a06153256 + depends: + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 88597 + timestamp: 1787228457350 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda + sha256: 66265e943f32ce02396ad214e27cb35f5b0490b3bd4f064446390f9d67fa5d88 + md5: 032d8030e4a24fe1f72c74423a46fb88 + depends: + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 88088 + timestamp: 1753484092643 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h5ad3122_0.conda + sha256: e146d83cdcf92506ab709c6e10acabd18a3394a23e6334a322c57e5d1d6d9f26 + md5: b9e5a9da5729019c4f216cf0d386a70c + depends: + - libstdcxx >=13 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 213281 + timestamp: 1745308220432 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-cpp-0.8.0-h7ac5ae9_1.conda + sha256: 0ea50b4f031e9da009e0537a1f29f5192941439c07bd07c98d69661c6f2aae9e + md5: 1e61fa792e6cc3f0aec45ba3054db647 + depends: + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml-cpp >=0.8.0,<0.9.0a0 + size: 230153 + timestamp: 1785923988072 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py311h51cfe5d_1.conda + sha256: ddeec193065b235166fb9f8ca4e5cbb931215ab90cbd17e9f9d753c8966b57b1 + md5: c8b3365fe290eeee3084274948012394 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - python 3.11.* *_cpython + - libgcc >=14 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 459426 + timestamp: 1762512724303 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstandard-0.25.0-py314h2e8dab5_1.conda + sha256: 051f12494f28f9de8b1bf1a787646c1f675d8eba0ba0eac79ab96ef960d24746 + md5: db33d0e8888bef6ef78207c5e6106a5b + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - python 3.14.* *_cp314 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 465094 + timestamp: 1762512736835 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda + sha256: 569990cf12e46f9df540275146da567d9c618c1e9c7a0bc9d9cfefadaed20b75 + md5: c3655f82dcea2aa179b291e7099c1fcc + depends: + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 614429 + timestamp: 1764777145593 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h9d15635_7.conda + sha256: 427fd14bcb3b8659796fecc682716617409350fb5a98e5b7b47558a10d1a2fc7 + md5: d942e34ac3920ba83f8b2d0169570187 + depends: + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 615477 + timestamp: 1786599613561 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: 3845f3d75991bae0fb90884662f4327c + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 8144 + timestamp: 1784221492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + sha256: b8fcb994134d3918d1c64a9d62f4168ff85d5bfb89e698102fe4ed679fe0df24 + md5: 108c928d2a8551832dbc762b535e90bb + depends: + - python >=3.10 + - typing-extensions >=4.0.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/annotated-types?source=hash-mapping + run_exports: {} + size: 19461 + timestamp: 1784935220549 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.15.1-pyh5ded981_1.conda + sha256: fc25a84c9c1ad4b48c136ad2363100e8b0faa3b0ab8985e61c9c8fa0b3431945 + md5: 33cdc5ba7c481b371f08f88e6c048c55 + depends: + - idna >=2.8 + - python >=3.11 + - typing_extensions >=4.16.0 + - python + constrains: + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/anyio?source=compressed-mapping + run_exports: {} + size: 175600 + timestamp: 1788612816954 +- conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + sha256: eb68e1ce9e9a148168a4b1e257a8feebffdb0664b557bb526a1e4853f2d2fc00 + md5: 845b38297fca2f2d18a29748e2ece7fa + depends: + - python >=3.9 + license: MIT OR Apache-2.0 + purls: + - pkg:pypi/archspec?source=hash-mapping + run_exports: {} + size: 50894 + timestamp: 1737352715041 +- conda: https://conda.anaconda.org/conda-forge/noarch/auditwheel-6.6.0-pyhd8ed1ab_0.conda + sha256: edba8fbf77c737ee991ae56f3cf5297f23c82eeb6033d6cfb4b78c2545cc66f0 + md5: b0eb66b5e5335471c1148199a26ceb79 + depends: + - packaging >=20.9 + - pyelftools >=0.24 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/auditwheel?source=hash-mapping + size: 51557 + timestamp: 1767633848607 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + noarch: generic + sha256: c31ab719d256bc6f89926131e88ecd0f0c5d003fe8481852c6424f4ec6c7eb29 + md5: a2ac7763a9ac75055b68f325d3255265 + depends: + - python >=3.14 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: [] + size: 7514 + timestamp: 1767044983590 +- conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + sha256: ea5f4c876eff2ed469551b57f1cc889a3c01128bf3e2e10b1fea11c3ef39eac2 + md5: c7eb87af73750d6fd97eff8bbee8cb9c + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/boltons?source=hash-mapping + size: 302296 + timestamp: 1749686302834 +- conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + sha256: 8fd76dc72d31a8688159039cfbb17eabe21c65792a96b5245be04e5887c2291c + md5: 52f3c30dbe025efb8050021f35648949 + depends: + - python >=3.11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/boltons?source=hash-mapping + run_exports: {} + size: 321343 + timestamp: 1788865918329 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + sha256: 37950019c59b99585cee5d30dbc2cc9696ed4e11f5742606a4db1621ed8f94d6 + md5: f001e6e220355b7f87403a4d0e5bf1ca + depends: + - __win + license: ISC + purls: [] + size: 147734 + timestamp: 1772006322223 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc + md5: 4492fd26db29495f0ba23f146cd5638d + depends: + - __unix + license: ISC + purls: [] + size: 147413 + timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + sha256: 95e8e74062a5fe5f870ac8c90302b6e89945165fdaed7810606e84ddee6aac12 + md5: e27d2ac27b096dc51fedfcf775a53f9b + depends: + - __win + license: ISC + run_exports: {} + size: 132136 + timestamp: 1784754918886 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 + md5: 0f51e2391ade309db462a55611263e9c + depends: + - __unix + license: ISC + purls: [] + run_exports: {} + size: 131780 + timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-2.0.1-pyhcf101f3_0.conda + sha256: 39358005c3cc44d8315ed79789b18a960d69365be979970cc6bae8f7a6177703 + md5: 70e764e549c6c25de5e429d6e4a28b39 + depends: + - cached_property >=2.0.1,<2.0.2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=compressed-mapping + run_exports: {} + size: 3720 + timestamp: 1787678456483 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-2.0.1-pyhcf101f3_0.conda + sha256: 4bd96dab5a434e4ee59fb6910cae704d4c5b453907d4886272a9943d37e42b6f + md5: 256d863ded0f79464391a075944d24a6 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=compressed-mapping + run_exports: {} + size: 16597 + timestamp: 1787678456483 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 + md5: 765c4d97e877cdbbb88ff33152b86125 + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=compressed-mapping + size: 151445 + timestamp: 1772001170301 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: 37e13edbe3b48f1095a9d085ef9cd83b + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=hash-mapping + run_exports: {} + size: 137015 + timestamp: 1784717699092 +- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + sha256: aa589352e61bb221351a79e5946d56916e3c595783994884accdb3b97fe9d449 + md5: 381bd45fb7aa032691f3063aff47e3a1 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cfgv?source=hash-mapping + run_exports: {} + size: 13589 + timestamp: 1763607964133 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + sha256: 05ea76a016c77839b64f9f8ec581775f6c8a259044bd5b45a177e46ab4e7feac + md5: beb628209b2b354b98203066f90b3287 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + size: 53210 + timestamp: 1772816516728 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda + sha256: d86dfd428b2e3c364fa90e07437c8405d635aa4ef54b25ab51d9c712be4112a5 + md5: 49ee13eb9b8f44d63879c69b8a40a74b + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 58510 + timestamp: 1773660086450 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + sha256: cb60ef3e0631c8bacb4f7057196dee4496091a22baa3bb4b9bccb12c7e1c921b + md5: e0ac3accc64e23e40969d660e5f58ac8 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + run_exports: {} + size: 64487 + timestamp: 1786835648298 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + run_exports: {} + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-18.1.8-he32a8d3_2.conda + sha256: fd0e6d142d38ac54404fb8f8413fae181a2c58e7aca3f6304df58f03ab8df55b + md5: e30e8ab85b13f0cab4c345d7758d525c + depends: + - clang 18.1.8.* + constrains: + - compiler-rt 18.1.8 + - clangxx 18.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 10204065 + timestamp: 1757436348646 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda + sha256: 3629dea4698cf120bb62ac9b6908c9182902a62d743f7d81bb8ecfb42d45fcbe + md5: 2f36e8e974c0c8a889870669c96a93c5 + depends: + - python >=3.11 + - backports.zstd >=1.3.0 + - conda >=25 + - conda-package-streaming >=0.12.0 + - jinja2 + - msgpack-python >=1.1.1 + - ruamel.yaml + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-index?source=hash-mapping + run_exports: {} + size: 214539 + timestamp: 1788522660257 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_1.conda + sha256: 001812b000c9791db53a85b0f215952b7497ca1fd6e3070314e20f707556765b + md5: 1d545b8b06123889395de8a3674fc0e7 + depends: + - boltons >=23.0.0 + - libmambapy >=2.0.0 + - msgpack-python >=1.1.1 + - python >=3.10 + - requests >=2.28.0,<3 + - zstandard >=0.15 + constrains: + - conda >=25.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-libmamba-solver?source=hash-mapping + size: 56969 + timestamp: 1770137431666 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-26.7.0-pyhcf101f3_0.conda + sha256: b7cc67470a361330edd8d9e331cafd4807dd77bc766ca197a3783ed3db66dfeb + md5: 49592004caf4336815fd5282bbb72172 + depends: + - python >=3.10 + - libmambapy >=2.0.0,!=2.6.2 + - boltons >=23.0.0 + - python + constrains: + - conda >=26.5 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-libmamba-solver?source=hash-mapping + run_exports: {} + size: 46828 + timestamp: 1784200590190 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-lockfiles-0.2.1-pyhd8ed1ab_0.conda + sha256: eb20fc63ce6438e10d7c64890bd23d0c68f62a479f2bae8b3c892e13949daf03 + md5: 7f1b127b170f9f0d01eca4b54a046ee5 + depends: + - conda >=26.3.0 + - pydantic >=2.12.5,<3 + - python >=3.10 + - ruamel.yaml + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-lockfiles?source=hash-mapping + run_exports: {} + size: 20633 + timestamp: 1783438353470 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + sha256: 8b2b1c235b7cbfa8488ad88ff934bdad25bac6a4c035714681fbff85b602f3f0 + md5: 32c158f481b4fd7630c565030f7bc482 + depends: + - conda-package-streaming >=0.9.0 + - python >=3.9 + - requests + - zstandard >=0.15 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-package-handling?source=hash-mapping + size: 257995 + timestamp: 1736345601691 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.6.0-pyh5ded981_0.conda + sha256: 34b3c276e8426d851c62b905014ef4c05cfcc090f052d7ec4da1dda53e10bc57 + md5: 412f13a9700d4455dfca5c5e23be9404 + depends: + - python >=3.11 + - conda-package-streaming >=0.13.0 + - backports.zstd >=1.1.0 + - requests + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-package-handling?source=hash-mapping + run_exports: {} + size: 257896 + timestamp: 1788438543851 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + sha256: 11b76b0be2f629e8035be1d723ccb6e583eb0d2af93bde56113da7fa6e2f2649 + md5: ff75d06af779966a5aeae1be1d409b96 + depends: + - python >=3.9 + - zstandard >=0.15 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-package-streaming?source=hash-mapping + size: 21933 + timestamp: 1751548225624 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.13.0-pyhd8ed1ab_0.conda + sha256: af3b666ea6043a146901cd151d6201126bbe19af8307b58fb175015e3710064c + md5: b4e3dcace82b486f69bc693f30120205 + depends: + - backports.zstd + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-package-streaming?source=hash-mapping + run_exports: {} + size: 23954 + timestamp: 1781293054998 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-pypi-0.12.0-pyh5ded981_0.conda + sha256: 5c41f9946b00966e1c216697ed34219bf054cb1c2fc9a41d2934bdce0c15c48c + md5: e294f0e16705c5678d8faf71636a5455 + depends: + - python >=3.11 + - packaging + - unearth + - python-build + - python-installer >=1.0 + - platformdirs + - conda-index >=0.12.0 + - conda-package-streaming >=0.11 + - python + constrains: + - conda >=26.1.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/conda-pypi?source=compressed-mapping + run_exports: {} + size: 252688 + timestamp: 1788892743969 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-rattler-solver-0.1.1-pyhcf101f3_0.conda + sha256: 77ea286de14f5d2dd0c376627ec41d0c226ad94ad8b25262f36712070324aa05 + md5: f5d15171925a157707e1ad165179449c + depends: + - python >=3.10 + - conda >=26.5.0 + - py-rattler >=0.25.0,<0.26.0a0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-rattler-solver?source=hash-mapping + run_exports: {} + size: 37334 + timestamp: 1781212602267 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-self-0.2.1-pyhd8ed1ab_0.conda + sha256: efd3b7b79a0e3993a174ab0140794479e2479b59fd053605acaaa25718f0f013 + md5: ac705504abf28bb924a021a3d5f4b55f + depends: + - conda >=26.1.1 + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda-self?source=hash-mapping + run_exports: {} + size: 22510 + timestamp: 1784724984642 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.16-py311hd8ed1ab_2.conda + noarch: generic + sha256: be18e37b4ab19a72b2ceac449e4ad11e5e18756aeb1c2a5b7f8811bff0c2e030 + md5: 18b8c7456c7a5052ca5d0002957a6f29 + depends: + - python >=3.11,<3.12.0a0 + - python_abi * *_cp311 + license: Python-2.0 + purls: [] + run_exports: {} + size: 48577 + timestamp: 1788392044498 +- conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + sha256: 6d977f0b2fc24fee21a9554389ab83070db341af6d6f09285360b2e09ef8b26e + md5: 003b8ba0a94e2f1e117d0bd46aebc901 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/distlib?source=hash-mapping + size: 275642 + timestamp: 1752823081585 +- conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + sha256: e2753997b8bd34205f42be01b8bab8037423dc30c02a1ec12de23e5b4c0b0a2e + md5: 58638f77697c4f6726753eb8be34818b + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/distlib?source=hash-mapping + run_exports: {} + size: 303705 + timestamp: 1781320269259 +- conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + sha256: 5603c7d0321963bb9b4030eadabc3fd7ca6103a38475b4e0ed13ed6d97c86f4e + md5: 0a2014fd9860f8b1eaa0b1f3d3771a08 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/distro?source=hash-mapping + run_exports: {} + size: 41773 + timestamp: 1734729953882 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + run_exports: {} + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.2-pyhd8ed1ab_0.conda + sha256: dddea9ec53d5e179de82c24569d41198f98db93314f0adae6b15195085d5567f + md5: f58064cec97b12a7136ebb8a6f8a129b + depends: + - python >=3.10 + license: Unlicense + purls: + - pkg:pypi/filelock?source=compressed-mapping + size: 25845 + timestamp: 1773314012590 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.6-pyhd8ed1ab_0.conda + sha256: f20f0d74a3e31131ec02e24dc555bcf1446bd275c968fc601ec86a8633f7608d + md5: 42afc06f6512975dc9ee5afc15dcc3ee + depends: + - python >=3.11 + license: Unlicense + purls: + - pkg:pypi/filelock?source=compressed-mapping + run_exports: {} + size: 78889 + timestamp: 1788940924870 +- conda: https://conda.anaconda.org/conda-forge/noarch/frozendict-2.4.7-pyh851646a_2.conda + sha256: 32b75b9a3097c4156ba5070739bbfdcc0bc23890c0faa0dbff569ecb16b8f44f + md5: 43fdb6bcc1227a22290d0bc02a9484c9 + depends: + - python >=3.10 + - python + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + run_exports: {} + size: 34300 + timestamp: 1781203600051 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 + depends: + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h11?source=hash-mapping + run_exports: {} + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + size: 95967 + timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + sha256: 307dd6ec90140c3cf4171071b0e5e870abec314f4565c1edd5bc433e942cdcc0 + md5: e652ac7756069c456d0da2a922cd7df5 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.2,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + run_exports: {} + size: 100789 + timestamp: 1785796355216 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + sha256: fdcea5d7cb314485d3907192ef024c704311548c5b0cbeb390cd1951051e29d2 + md5: b395909221b9bd1df066e5930e18855b + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + run_exports: {} + size: 32884 + timestamp: 1782283986153 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb + depends: + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpcore?source=hash-mapping + run_exports: {} + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 + depends: + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpx?source=hash-mapping + run_exports: {} + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + run_exports: {} + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.18-pyhd8ed1ab_0.conda + sha256: 3bae1b612ccc71e49c5795a369a82c4707ae6fd4e63360e8ecc129f9539f779b + md5: 635d1a924e1c55416fce044ed96144a2 + depends: + - python >=3.10 + - ukkonen + license: MIT + license_family: MIT + purls: + - pkg:pypi/identify?source=hash-mapping + size: 79749 + timestamp: 1774239544252 +- conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda + sha256: 381cedccf0866babfc135d65ee40b778bd20e927d2a5ec810f750c5860a7c5b8 + md5: 84a3233b709a289a4ddd7a2fd27dd988 + depends: + - python >=3.10 + - ukkonen + license: MIT + license_family: MIT + purls: + - pkg:pypi/identify?source=hash-mapping + run_exports: {} + size: 79757 + timestamp: 1776455344188 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 + md5: 53abe63df7e10a6ba605dc5f9f961d36 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 50721 + timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + sha256: 1c35a59c1545ad0fdaddf1fbde7bcfa6ef41a8d68c3a2b0b4a291be00676163e + md5: a39ae05027e9b707742e41b30d296b75 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=compressed-mapping + run_exports: {} + size: 177433 + timestamp: 1787059857580 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 + md5: 63ccfdc3a3ce25b027b8767eb722fca8 + depends: + - python >=3.9 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=hash-mapping + size: 34641 + timestamp: 1747934053147 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda + sha256: 82ab2a0d91ca1e7e63ab6a4939356667ef683905dea631bc2121aa534d347b16 + md5: 080594bf4493e6bae2607e65390c520a + depends: + - python >=3.10 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=compressed-mapping + size: 34387 + timestamp: 1773931568510 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + sha256: 95a074a7d3f58b3494c068d4789c364dcb591c56b70ca6b12f149a540f9aeea4 + md5: 77ec68e0aa61c3fff9ecbf840f93d64e + depends: + - python >=3.10 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=compressed-mapping + run_exports: {} + size: 34920 + timestamp: 1787943971257 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.5.2-pyhd8ed1ab_0.conda + sha256: a99a3dafdfff2bb648d2b10637c704400295cb2ba6dc929e2d814870cf9f6ae5 + md5: e376ea42e9ae40f3278b0f79c9bf9826 + depends: + - importlib_resources >=6.5.2,<6.5.3.0a0 + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 9724 + timestamp: 1736252443859 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-7.1.0-pyhd8ed1ab_0.conda + sha256: 6a2f86ef0965605d742b5b94229bf8b829258d0a9f640e3651901cc72ef9a0a5 + md5: e3bffa82b874f8b9a2631bddb3869529 + depends: + - importlib_resources >=7.1.0,<7.1.1.0a0 + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 10354 + timestamp: 1776068852701 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 + md5: c85c76dc67d75619a92f51dfbce06992 + depends: + - python >=3.9 + - zipp >=3.1.0 + constrains: + - importlib-resources >=6.5.2,<6.5.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-resources?source=hash-mapping + size: 33781 + timestamp: 1736252433366 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + sha256: a563a51aa522998172838e867e6dedcf630bc45796e8612f5a1f6d73e9c8125a + md5: 0ba6225c279baf7ea9473a62ea0ec9ae + depends: + - python >=3.10 + - zipp >=3.1.0 + constrains: + - importlib-resources >=7.1.0,<7.1.1.0a0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-resources?source=hash-mapping + run_exports: {} + size: 34809 + timestamp: 1776068839274 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 13387 + timestamp: 1760831448842 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d + depends: + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + run_exports: {} + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + sha256: 304955757d1fedbe344af43b12b5467cca072f83cce6109361ba942e186b3993 + md5: cb60ae9cf02b9fcb8004dec4089e5691 + depends: + - jsonpointer >=1.9 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpatch?source=hash-mapping + size: 17311 + timestamp: 1733814664790 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + sha256: 1a1328476d14dfa8b84dbacb7f7cd7051c175498406dc513ca6c679dc44f3981 + md5: cd2214824e36b0180141d422aba01938 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + size: 13967 + timestamp: 1765026384757 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae + md5: 89bf346df77603055d3c8fe5811691e6 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + size: 14190 + timestamp: 1774311356147 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda + sha256: 41557eeadf641de6aeae49486cef30d02a6912d8da98585d687894afd65b356a + md5: 86d9cba083cd041bfbf242a01a7a1999 + constrains: + - sysroot_linux-64 ==2.28 + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + size: 1278712 + timestamp: 1765578681495 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-aarch64-4.18.0-h05a177a_9.conda + sha256: 5d224bf4df9bac24e69de41897c53756108c5271a0e5d2d2f66fd4e2fbc1d84b + md5: bb3b7cad9005f2cbf9d169fb30263f3e + constrains: + - sysroot_linux-aarch64 ==2.28 + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + size: 1248134 + timestamp: 1765578613607 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda + sha256: 1abc6a81ee66e8ac9ac09a26e2d6ad7bba23f0a0cc3a6118654f036f9c0e1854 + md5: 06901733131833f5edd68cf3d9679798 + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 3084533 + timestamp: 1771377786730 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-aarch64-14.3.0-h25ba3ff_118.conda + sha256: 058fab0156cb13897f7e4a2fc9d63c922d3de09b6429390365f91b62f1dddb0e + md5: 3733752e5a7a0737c8c4f1897f2074f9 + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 2335839 + timestamp: 1771377646960 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda + sha256: b1c3824769b92a1486bf3e2cc5f13304d83ae613ea061b7bc47bb6080d6dfdba + md5: 865a399bce236119301ebd1532fced8d + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 20171098 + timestamp: 1771377827750 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-aarch64-14.3.0-h57c8d61_118.conda + sha256: 609585a02b05a2b0f2cabb18849328455cbce576f2e3eb8108f3ef7f4cb165a6 + md5: bcf29f2ed914259a258204b05346abb1 + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 17565700 + timestamp: 1771377672552 +- conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + sha256: 2a909594ca78843258e4bda36e43d165cda844743329838a29402823c8f20dec + md5: 59659d0213082bc13be8500bab80c002 + license: MIT + license_family: MIT + purls: [] + size: 4335 + timestamp: 1758194464430 +- conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_2.conda + sha256: 30d271dd63391e053aa3c393c5d93fb622dec5d50a64107171ee28242660608e + md5: 849b8852621992c3f3a1901c939a0399 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - nlohmann_json-abi ==3.12.0 + size: 4365 + timestamp: 1785920576349 +- conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + sha256: 4fa40e3e13fc6ea0a93f67dfc76c96190afd7ea4ffc1bac2612d954b42cdc3ee + md5: eb52d14a901e23c39e9e7b4a1a5c015f + depends: + - python >=3.10 + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nodeenv?source=hash-mapping + run_exports: {} + size: 40866 + timestamp: 1766261270149 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 + md5: 58335b26c38bf4a20f399384c33cbcf9 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + run_exports: {} + size: 62477 + timestamp: 1745345660407 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 + md5: b76541e68fea4d511b1ac46a28dcd2c6 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=compressed-mapping + size: 72010 + timestamp: 1769093650580 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + sha256: c432626b16768b8dab228bfb706f7060c2d462a21c516d240f68f2f902b5a044 + md5: 936687ed80f295a1f5dbcf8bd34c252c + depends: + - python >=3.9 + - python + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 116363 + timestamp: 1785888127370 +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda + sha256: 29ea20d0faf20374fcd61c25f6d32fb8e9a2c786a7f1473a0c3ead359470fbe1 + md5: 2908273ac396d2cd210a8127f5f1c0d6 + depends: + - python >=3.10 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=hash-mapping + size: 53739 + timestamp: 1769677743677 +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + sha256: 6eaee417d33f298db79bc7185ab1208604c0e6cf51dade34cd513c6f9db9c6f3 + md5: 11adc78451c998c0fd162584abfa3559 + depends: + - python >=3.10 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=hash-mapping + run_exports: {} + size: 56559 + timestamp: 1777271601895 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda + sha256: 8e1497814a9997654ed7990a79c054ea5a42545679407acbc6f7e809c73c9120 + md5: 67bdec43082fd8a9cffb9484420b39a2 + depends: + - python >=3.10,<3.13.0a0 + - setuptools + - wheel + license: MIT + license_family: MIT + purls: + - pkg:pypi/pip?source=hash-mapping + size: 1181790 + timestamp: 1770270305795 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + sha256: c205bae42eb11b364fe3663a817cbcbc20a8ef544c6b2ff6f391013487117259 + md5: 77b6cf3b0689d9fc68561df0db9b856d + depends: + - python >=3.10,<3.13.0a0 + - setuptools + - wheel + license: MIT + license_family: MIT + purls: + - pkg:pypi/pip?source=hash-mapping + run_exports: {} + size: 1199593 + timestamp: 1785914492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-unix_hf108a03_0.conda + sha256: c84a62f421f3ba388df06df7f414d7b568ad4bc3c33a7799b3405f213a3b1ff5 + md5: 07b709969aa53039501c5960e45794b8 + depends: + - __unix + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7034 + timestamp: 1763572165675 +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-pycharm-0.0.10-win_hba80fca_0.conda + sha256: c0399f79f0656df7e265ae53630e08cad2d2203a2f39181ff1a68b3b39466d0d + md5: 6dea6b7cca5948b0cfd6eeb5ddecce67 + depends: + - __win + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7042 + timestamp: 1763572121812 +- conda: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.12.1.2-pyhd8ed1ab_0.conda + sha256: 353fd5a2c3ce31811a6272cd328874eb0d327b1eafd32a1e19001c4ad137ad3a + md5: dc702b2fae7ebe770aff3c83adb16b63 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pkginfo?source=hash-mapping + run_exports: {} + size: 30536 + timestamp: 1739984682585 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.8-pyh5ded981_0.conda + sha256: 8cbae1fed8437e63c9bcc691927402edb3809fccb26b15eb095df7dd2eb73e5f + md5: 48e9ecf08b0578a4313f13b2e2b0fd72 + depends: + - python >=3.11 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + run_exports: {} + size: 27718 + timestamp: 1788959137036 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.4-pyhcf101f3_0.conda + sha256: 0289f0a38337ee201d984f8f31f11f6ef076cfbbfd0ab9181d12d9d1d099bf46 + md5: 82c1787f2a65c0155ef9652466ee98d6 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + size: 25646 + timestamp: 1773199142345 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pluggy?source=compressed-mapping + run_exports: {} + size: 25877 + timestamp: 1764896838868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.1-pyha770c72_0.conda + sha256: 5b81b7516d4baf43d0c185896b245fa7384b25dc5615e7baa504b7fa4e07b706 + md5: 7f3ac694319c7eaf81a0325d6405e974 + depends: + - cfgv >=2.0.0 + - identify >=1.0.0 + - nodeenv >=0.11.1 + - python >=3.10 + - pyyaml >=5.1 + - virtualenv >=20.10.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pre-commit?source=compressed-mapping + size: 200827 + timestamp: 1765937577534 +- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + sha256: fc0a0620a8f829c46787a9a13ddbae8b6049f2e77da353a8a2adaa01af0da7e2 + md5: c36b88db560b4e74f294380613f1a239 + depends: + - cfgv >=2.0.0 + - identify >=1.0.0 + - nodeenv >=0.11.1 + - python >=3.10 + - pyyaml >=5.1 + - virtualenv >=20.10.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pre-commit?source=compressed-mapping + run_exports: {} + size: 201879 + timestamp: 1786408353117 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + sha256: 9e7fe12f727acd2787fb5816b2049cef4604b7a00ad3e408c5e709c298ce8bf1 + md5: f0599959a2447c1e544e216bddf393fa + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pybind11-abi ==11 + size: 14671 + timestamp: 1752769938071 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + sha256: e27e0473fc6723311a0bd48b89b616fa1b996a2f7a2b555338cbbcfb9c640568 + md5: 9c5491066224083c41b6d5635ed7107b + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + run_exports: {} + size: 55886 + timestamp: 1779293633166 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + sha256: 701ed8122021f62e1c9a46bdf4932ada911c10afa56ba0459f56da940bcbe5a7 + md5: 2d0f9304fc1cb1e3e94e7b37c14cd70a + depends: + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 + - python >=3.10 + - annotated-types >=0.6.0 + - pydantic-core ==2.46.5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic?source=compressed-mapping + run_exports: {} + size: 346633 + timestamp: 1787941273167 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyelftools-0.32-pyh707e725_1.conda + sha256: de3a334388959397ba7b69ee92d5dfaa0c1bd6a7ffcf49c3c4de2ac045c69d28 + md5: eae78c632c980c396cf6f711cf515c3a + depends: + - __unix + - python >=3.9 + license: Unlicense + purls: + - pkg:pypi/pyelftools?source=hash-mapping + size: 149336 + timestamp: 1744148364068 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + sha256: f5f015ff1bc3e1b7fc08eee096b1865234189ae0b82bebdb86a5369116e42fa0 + md5: 2882dee445dfa45b0c5afce3ffb7730a + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 959376 + timestamp: 1786995678795 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.11.0-pyhd8ed1ab_0.conda + sha256: 7e709bde682104c241674f8005fd560d7ea8599458c94d03ed51ef8a4ae7d737 + md5: cd6dae6c673c8f12fe7267eac3503961 + depends: + - packaging >=23.2 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyproject-metadata?source=hash-mapping + size: 25200 + timestamp: 1770672303277 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.12.1-pyhd8ed1ab_0.conda + sha256: 9951d325225765ab01977f151abf9165cab68e5b73b93eac210dfb7322b74c65 + md5: 5997c30f8b2310c4acf067a1a7c7ccc3 + depends: + - packaging >=23.2 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyproject-metadata?source=hash-mapping + run_exports: {} + size: 27343 + timestamp: 1783140019536 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + sha256: 065ac44591da9abf1ff740feb25929554b920b00d09287a551fcced2c9791092 + md5: d4582021af437c931d7d77ec39007845 + depends: + - python >=3.9 + - tomli >=1.1.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyproject-hooks?source=hash-mapping + run_exports: {} + size: 15528 + timestamp: 1733710122949 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + run_exports: {} + size: 21784 + timestamp: 1733217448189 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + run_exports: {} + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + sha256: 430051d80765207a7d782b2b188230ba1489d35c6e75fd9903f76cb9fda4af16 + md5: 64c98a12c4e23eb238bf66bbecafdf3c + depends: + - colorama + - pygments >=2.7.2 + - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 + - tomli >=1 + - exceptiongroup >=1 + - python + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + run_exports: {} + size: 306724 + timestamp: 1782127176429 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.6.0-pyhc364b38_0.conda + sha256: d5bad775346fbd21cce7922b29cab2587fe599517654de65c3d2fe50a7126279 + md5: 1e696c762d003f8f4af971b6dccb8c1e + depends: + - python >=3.10 + - colorama + - importlib-metadata >=4.6 + - packaging >=24.0 + - pyproject_hooks + - tomli >=1.1.0 + - python + constrains: + - build <0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/build?source=compressed-mapping + run_exports: {} + size: 33461 + timestamp: 1787910624806 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.1-pyhcf101f3_0.conda + sha256: 5a70a9cbcf48be522c2b82df8c7a57988eed776f159142b0d30099b61f31a35e + md5: f2e88fc463b249bc1f40d9ca969d9b5e + depends: + - python >=3.10 + - filelock >=3.15.4 + - platformdirs <5,>=4.3.6 + - python + license: MIT + purls: + - pkg:pypi/python-discovery?source=compressed-mapping + size: 34137 + timestamp: 1774605818480 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda + sha256: e48089f2927811994b916d31953563097bc7e8d856965fbdeea3b4d407e3b89f + md5: e87738e32eaf5dfa98a5d49f611d6312 + depends: + - python >=3.10 + - filelock >=3.15.4 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/python-discovery?source=compressed-mapping + run_exports: {} + size: 38928 + timestamp: 1787953569064 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.11.16-hd8ed1ab_2.conda + sha256: caec48ea90cf78622e923366d405ec29a419d916cb1690c2319ef76f984b9233 + md5: fab671748c957da704c76efb0686dcdb + depends: + - cpython 3.11.16.* + - python_abi * *_cp311 + license: Python-2.0 + purls: [] + run_exports: {} + size: 48553 + timestamp: 1788392053631 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-installer-1.0.1-pyh332efcf_0.conda + sha256: 9c18fa1ebd0c839b6ea12e99d510a0968c2288cc423185cb34bf7332b97684e9 + md5: 5cc7e2e78962dc902cae9e29c2aa4b2f + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/installer?source=hash-mapping + run_exports: {} + size: 239330 + timestamp: 1778554210561 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda + build_number: 9 + sha256: bcfbad269758f4617035314fe379ee655bc2d9db5282e7a00d61450b7cefa3cf + md5: 18e91a03be08122180f208723e42a52e + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6770 + timestamp: 1788302830198 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + build_number: 8 + sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 + md5: 0539938c55b6b1a59b560e843ad864a4 + constrains: + - python 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6989 + timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + sha256: 7813c38b79ae549504b2c57b3f33394cea4f2ad083f0994d2045c2e24cb538c5 + md5: c65df89a0b2e321045a9e01d1337b182 + depends: + - python >=3.10 + - certifi >=2017.4.17 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.21.1,<3 + - python + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=compressed-mapping + size: 63602 + timestamp: 1766926974520 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + sha256: c0249bc4bf4c0e8e06d0e7b4d117a5d593cc4ab2144d5006d6d47c83cb0af18e + md5: 10afbb4dbf06ff959ad25a92ccee6e59 + depends: + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + size: 63712 + timestamp: 1774894783063 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da + md5: 4a85203c1d80c1059086ae860836ffb9 + depends: + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + run_exports: {} + size: 68709 + timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/ruamel.yaml-0.19.1-pyhcf101f3_0.conda + sha256: b48bebe297a63ae60f52e50be328262e880702db4d9b4e86731473ada459c2a1 + md5: 06ad944772941d5dae1e0d09848d8e49 + depends: + - python >=3.10 + - ruamel.yaml.clib >=0.2.15 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + run_exports: {} + size: 98448 + timestamp: 1767538149184 +- conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-0.11.6-pyh7e86bf3_1.conda + sha256: bf802baa1a770c0ca631624a3627f84cae7978b56da02826b37ff1c5852a720a + md5: 4965b07e5cae94005cbe759e39f3def1 + depends: + - typing_extensions >=3.10.0 + - python >=3.8 + - exceptiongroup >=1.0 + - importlib-metadata >=4.13 + - importlib-resources >=1.3 + - packaging >=21.3 + - pathspec >=0.10.1 + - tomli >=1.2.2 + - typing-extensions >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/scikit-build-core?source=hash-mapping + run_exports: {} + size: 213181 + timestamp: 1755919500167 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 + md5: 8e194e7b992f99a5015edbd4ebd38efd + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=compressed-mapping + size: 639697 + timestamp: 1773074868565 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + sha256: 9e200ee5f9ff19a4d94e4b51c4856d53dec849f91032f345cf0c6bc3d51a7183 + md5: 62ac906f1cd582c6c264c95625cb9d6f + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=compressed-mapping + run_exports: {} + size: 524488 + timestamp: 1786282924579 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-10.2.1-pyhcf101f3_0.conda + sha256: 8eb9daf6fc70111abf73f848c6d32d2769aa1fba550f793b865076fd4e33fb3a + md5: eefa3bc61c9224107d3a9afeb37552a9 + depends: + - python >=3.10 + - vcs_versioning >=2.0.0.dev0 + - packaging >=20 + - setuptools + - tomli >=1 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools-scm?source=hash-mapping + run_exports: {} + size: 29407 + timestamp: 1784653562396 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda + sha256: 2161ac35fc22770b248bab0be2cc3b5bd765f528a9e60e7f3be784fd8d0d605a + md5: e2e4d7094d0580ccd62e2a41947444f3 + depends: + - importlib-metadata + - packaging >=20.0 + - python >=3.10 + - setuptools >=45 + - tomli >=1.0.0 + - typing-extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools-scm?source=hash-mapping + size: 52539 + timestamp: 1760965125925 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-10.2.1-hea71cf2_0.conda + sha256: 455767721a5a0a28bcb574ddc3b9c0b42989839db1652e4bfa7bfaccca2c88de + md5: 3dab6a6584747027738b1425381b25e6 + depends: + - setuptools-scm ==10.2.1 pyhcf101f3_0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 3832 + timestamp: 1784653562396 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools_scm-9.2.2-hd8ed1ab_0.conda + sha256: 60eec92bd931bbcba700b9a70b1446dccbf62d3298e408d3a28e09b8ecd96d98 + md5: 7537abc2590073ffde5545c648ad6974 + depends: + - setuptools-scm >=9.2.2,<9.2.3.0a0 + license: MIT + license_family: MIT + purls: [] + size: 6653 + timestamp: 1760965126461 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/sniffio?source=hash-mapping + run_exports: {} + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + sha256: d1e3e06b5cf26093047e63c8cc77b70d970411c5cbc0cb1fad461a8a8df599f7 + md5: 0401a17ae845fa72c7210e206ec5647d + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 28657 + timestamp: 1738440459037 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_9.conda + sha256: c47299fe37aebb0fcf674b3be588e67e4afb86225be4b0d452c7eb75c086b851 + md5: 13dc3adbc692664cd3beabd216434749 + depends: + - __glibc >=2.28 + - kernel-headers_linux-64 4.18.0 he073ed8_9 + - tzdata + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + size: 24008591 + timestamp: 1765578833462 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-aarch64-2.28-h585391f_9.conda + sha256: 1bd2db6b2e451247bab103e4a0128cf6c7595dd72cb26d70f7fadd9edd1d1bc3 + md5: fdf07ab944a222ff28c754914fdb0740 + depends: + - __glibc >=2.28 + - kernel-headers_linux-aarch64 4.18.0 h05a177a_9 + - tzdata + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + size: 23644746 + timestamp: 1765578629426 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + sha256: 62940c563de45790ba0f076b9f2085a842a65662268b02dd136a8e9b1eaf47a8 + md5: 72e780e9aa2d0a3295f59b1874e3768b + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21453 + timestamp: 1768146676791 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + run_exports: {} + size: 21561 + timestamp: 1774492402955 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda + sha256: 304834f2438017921d69f05b3f5a6394b42dc89a90a6128a46acbf8160d377f6 + md5: 32e37e8fe9ef45c637ee38ad51377769 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli-w?source=hash-mapping + run_exports: {} + size: 12680 + timestamp: 1736962345843 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 + md5: e5ce43272193b38c2e9037446c1d9206 + depends: + - python >=3.10 + - __unix + - python + license: MPL-2.0 and MIT + purls: + - pkg:pypi/tqdm?source=compressed-mapping + size: 94132 + timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda + sha256: 63cc2def6e168622728c7800ed6b3c1761ceecb18b354c81cee1a0a94c09900a + md5: af77160f8428924c17db94e04aa69409 + depends: + - python >=3.10 + - colorama + - __win + - python + license: MPL-2.0 and MIT + purls: + - pkg:pypi/tqdm?source=hash-mapping + size: 93399 + timestamp: 1770153445242 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyh6be1c34_0.conda + sha256: 8221a7b1cf63171db3a81fddd1d9e58502dc65c6938e64901c01815f63fe4210 + md5: 09073bfb6fbb3e1c7d0528b4d91e9508 + depends: + - python >=3.11 + - colorama + - __win + - python + constrains: + - envwrap >=0.2 + - ipywidgets >=6.0 + license: MPL-2.0 and MIT + run_exports: {} + size: 95465 + timestamp: 1789187329737 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.1-pyhfa0c392_0.conda + sha256: 7c40c84de3de3624c2a753496122dbb880903c796d183e9d4be6c15c310c4bab + md5: ffc91f29264eeb8d3ba96fdc897316c3 + depends: + - python >=3.11 + - __unix + - python + constrains: + - envwrap >=0.2 + - ipywidgets >=6.0 + license: MPL-2.0 and MIT + purls: + - pkg:pypi/tqdm?source=compressed-mapping + run_exports: {} + size: 95977 + timestamp: 1789187326570 +- conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + sha256: eece5be81588c39a855a0b70da84e0febb878a6d91dd27d6d21370ce9e5c5a46 + md5: c2db35b004913ec69bcac64fb0783de0 + depends: + - python >=3.10,<4 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/truststore?source=hash-mapping + run_exports: {} + size: 24279 + timestamp: 1766494826559 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 94080 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + sha256: 293c66b208468d186a94ff486c2ffbf67859a2bde8c88e965fc9d06c517191b2 + md5: 880e91eac5d926568ef278e20554148e + depends: + - python >=3.10 + - typing_extensions >=4.15.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/typing-inspection?source=compressed-mapping + run_exports: {} + size: 21070 + timestamp: 1786818918217 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + run_exports: {} + size: 52631 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: fcb489df604d100968b737f2cb6076c6 + license: LicenseRef-Public-Domain + purls: [] + run_exports: {} + size: 118849 + timestamp: 1784250406640 +- conda: https://conda.anaconda.org/conda-forge/noarch/unearth-0.18.3-pyhd8ed1ab_0.conda + sha256: 00a56cdd16bf3a398f6e87d7696c92a3012d823b8e42ba7d3bda2ec2fa5d8947 + md5: 2f0f2fb0f8a61529464d95a87b10eda0 + depends: + - cached-property >=1.5.2 + - httpx + - packaging >=20 + - python >=3.10 + - requests >=2.25 + license: MIT + license_family: MIT + purls: + - pkg:pypi/unearth?source=hash-mapping + run_exports: {} + size: 288616 + timestamp: 1786845009822 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a + md5: 9272daa869e03efe68833e3dc7a02130 + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 103172 + timestamp: 1767817860341 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + sha256: c5511c190ab55168ca412f3c54592fbf677ae6250784cb72b227a7cf3f538f7d + md5: 407a3d3778570bae8e8fcf554e803e43 + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.11 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=compressed-mapping + run_exports: {} + size: 107485 + timestamp: 1789566710244 +- conda: https://conda.anaconda.org/conda-forge/noarch/vcs_versioning-2.2.2-pyhcf101f3_0.conda + sha256: 5728b15adf4e2877e510996e0d617d1531ccd8e55ca59358f60d3a10aaead5fa + md5: efbdc1f76721fb4ae7a1dbb5fff72562 + depends: + - python >=3.10 + - packaging >=20 + - tomli >=1 + - typing_extensions >=4.1 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/vcs-versioning?source=hash-mapping + run_exports: {} + size: 83180 + timestamp: 1782748145197 +- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.0-pyhcf101f3_0.conda + sha256: b83246d145ba0e6814d2ed0b616293e56924e6c7d6649101f5a4f97f9e757ed1 + md5: 704c22301912f7e37d0a92b2e7d5942d + depends: + - python >=3.10 + - distlib >=0.3.7,<1 + - filelock <4,>=3.24.2 + - importlib-metadata >=6.6 + - platformdirs >=3.9.1,<5 + - python-discovery >=1 + - typing_extensions >=4.13.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/virtualenv?source=compressed-mapping + size: 4647775 + timestamp: 1773133660203 +- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.9-pyh5ded981_0.conda + sha256: ca02653d02c45de60dec25400d0a11f2edc07a3dd191e7e1f5c3b4a454e27c8b + md5: 861471c1a801f8ee06568ffb6ec26cb7 + depends: + - python >=3.11 + - distlib >=0.3.7,<1 + - filelock >=3.24.2,<4 + - platformdirs >=3.9.1,<5 + - python-discovery >=1.6 + - typing_extensions >=4.13.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/virtualenv?source=compressed-mapping + run_exports: {} + size: 3895514 + timestamp: 1788948703859 +- conda: https://conda.anaconda.org/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda + sha256: b72270395326dc56de9bd6ca82f63791b3c8c9e2b98e25242a9869a4ca821895 + md5: f622897afff347b715d046178ad745a5 + depends: + - __win + license: MIT + license_family: MIT + purls: [] + size: 238764 + timestamp: 1745560912727 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda + sha256: d6cf2f0ebd5e09120c28ecba450556ce553752652d91795442f0e70f837126ae + md5: bdbd7385b4a67025ac2dba4ef8cb6a8f + depends: + - packaging >=24.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wheel?source=hash-mapping + size: 31858 + timestamp: 1769139207397 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.48.0-pyhd8ed1ab_0.conda + sha256: ba64b29b6d418024cb565081a1799262cb2780d2534ff4c14f76aa858c4286cd + md5: 9a2124517bfd5d792e0f7b86eefdfeb8 + depends: + - packaging >=24.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wheel?source=compressed-mapping + run_exports: {} + size: 34528 + timestamp: 1786613220176 +- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + run_exports: {} + size: 9555 + timestamp: 1733130678956 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae + md5: 30cd29cb87d819caead4d55184c1d115 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/zipp?source=hash-mapping + size: 24194 + timestamp: 1764460141901 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + sha256: 210bd31c22bb88f5e2a167df24c95bb5f152b2ada7502f9b8c49d1f5366db423 + md5: ba3dcdc8584155c97c648ae9c044b7a3 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/zipp?source=hash-mapping + run_exports: {} + size: 24190 + timestamp: 1779159948016 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-8_kmp_llvm.conda + build_number: 8 + sha256: c7c98ce74f6a7ff25aaa4706d06fa41d63a333add4d697b73aa4d8d2d7ad7651 + md5: a2d706b4a7d603524133037c34f7fb76 + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - _openmp_mutex >=4.5 + size: 8016 + timestamp: 1788046437162 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py311hdee5a0d_1.conda + sha256: e7ddb3f07e6c8324ddb703a20a8bcf4f3fde21eead6de992a3c590d8aee133bd + md5: 84605954dc82112de562d037c15f5789 + depends: + - python + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + run_exports: {} + size: 245366 + timestamp: 1788491298274 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311h9fb86b2_4.conda + sha256: a84b80afc7559470dd373c49c65ebf195a0c5f45222c32f4dc138e2d659156fa + md5: f325d23f4253d618d3c6013fa018b02a + depends: + - __osx >=11.0 + - libcxx >=21 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 h1dcdb26_4 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=compressed-mapping + run_exports: {} + size: 365231 + timestamp: 1788480433142 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda + sha256: 5c2e471fd262fcc3c5a9d5ea4dae5917b885e0e9b02763dbd0f0d9635ed4cb99 + md5: f9501812fe7c66b6548c7fcaa1c1f252 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359854 + timestamp: 1764018178608 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + sha256: 8ec22f0ba25cbfc2e64d70cf29459eccd7ffdf6436f6a6ff15bbfef799f7d4f6 + md5: b50612e7d190b8061ab4e7dc119cf4d5 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 124965 + timestamp: 1785906749812 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 + md5: bcb3cba70cf1eec964a03b4ba7775f01 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 180327 + timestamp: 1765215064054 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda + sha256: 82bdd5a3fcada6afef6255a9bf41172f07b362f36e04a354dc2abc0a29bfb756 + md5: 4cc01a374215de38387d45e19c8c5c9c + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 197819 + timestamp: 1787169996553 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-compiler-1.10.0-hdf49b6b_0.conda + sha256: efc71f2ae5901bea633c67468b3aa774b6bcf46c9433e1ab5d640e3faf1680b9 + md5: 7ca1bdcc45db75f54ed7b3ac969ed888 + depends: + - cctools >=949.0.1 + - clang_osx-arm64 18.* + - ld64 >=530 + - llvm-openmp + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6758 + timestamp: 1751115540465 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1021.4-hb4fb6a3_0.conda + sha256: 5492bfbb871086056daa5e7992f5845e317e09a882d1fe5fb94b3b2766462abc + md5: 0db10a7dbc9494ca7a918b762722f41b + depends: + - cctools_osx-arm64 1021.4 h12580ec_0 + - ld64 954.16 h4c6efb1_0 + - libllvm18 >=18.1.8,<18.2.0a0 + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 21511 + timestamp: 1752819117398 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_osx-arm64-1021.4-h12580ec_0.conda + sha256: 9754bae92bfeafb1c4d724161ea402101769b0239fddbcec1de5b1612dcbae87 + md5: 8e0c8bd08a32fe607b6e504f8e0a00b5 + depends: + - __osx >=11.0 + - ld64_osx-arm64 >=954.16,<954.17.0a0 + - libcxx + - libllvm18 >=18.1.8,<18.2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-tools 18.1.* + - sigtool + constrains: + - ld64 954.16.* + - cctools 1021.4.* + - clang 18.1.* + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 793113 + timestamp: 1752819079152 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + sha256: 5b5ee5de01eb4e4fd2576add5ec9edfc654fbaf9293e7b7ad2f893a67780aa98 + md5: 10dd19e4c797b8f8bdb1ec1fbb6821d7 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 292983 + timestamp: 1761203354051 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py311h2e98d63_3.conda + sha256: 830d0f23504fbadf973a7d9dbac478c22cc29421c79cac997c27accd0bc4d20d + md5: acce75a8c4b412e35679889f28ac3748 + depends: + - __osx >=11.0 + - libffi >=3.7.0,<3.8.0a0 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping + run_exports: {} + size: 295545 + timestamp: 1788485303298 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-18-18.1.8-default_hf3020a7_18.conda + sha256: 4a4842c748e576a55c1852d30ae8669205ee2556ed0e9e69d81e3d32820b95eb + md5: 479f38a7c5b5e494d2e7c315e6815d56 + depends: + - __osx >=11.0 + - libclang-cpp18.1 18.1.8 default_hf3020a7_18 + - libcxx >=18.1.8 + - libllvm18 >=18.1.8,<18.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 829091 + timestamp: 1773505965984 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-18.1.8-default_hf9bcbb7_18.conda + sha256: 6fd7e17d483bee93d47ca6d0669224633655f779c653cde34278bcb8f8e3fd0d + md5: f33377f3388b9642e8f65f155088229c + depends: + - clang-18 18.1.8 default_hf3020a7_18 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 93328 + timestamp: 1773506098832 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-18.1.8-h2ae9ea5_25.conda + sha256: 35273ca91fb998f979402c76066af008ad3108a61a3b161d881fcb37700a48bb + md5: 9eb023cfc47dac4c22097b9344a943b4 + depends: + - cctools_osx-arm64 + - clang 18.1.8.* + - compiler-rt 18.1.8.* + - ld64_osx-arm64 + - llvm-tools 18.1.8.* + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18331 + timestamp: 1748575702758 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_osx-arm64-18.1.8-h07b0088_25.conda + sha256: 4d1e695cd776783f6192fb8d4c7892c03e9f1f185dbb96cefdaab2f183430281 + md5: d9ee862b94f4049c9e121e6dd18cc874 + depends: + - clang_impl_osx-arm64 18.1.8 h2ae9ea5_25 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 21563 + timestamp: 1748575706504 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx-18.1.8-default_h65f67e2_18.conda + sha256: 723e7483a82ff27d826180fc1c34ad7cf73052bd751da414d5aead740f788131 + md5: 40d91666bd6145e8f81fb5790c439243 + depends: + - clang 18.1.8 default_hf9bcbb7_18 + - libcxx-devel 18.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 93392 + timestamp: 1773506152471 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_impl_osx-arm64-18.1.8-h555f467_25.conda + sha256: d0fb67a4ed19e9b40db08fce19afb342dcebc3609374a1b86c0d7a40abaf655c + md5: 4d72782682bc7d61a3612fea2c93299f + depends: + - clang_osx-arm64 18.1.8 h07b0088_25 + - clangxx 18.1.8.* + - libcxx >=18 + - libllvm18 >=18.1.8,<18.2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18459 + timestamp: 1748575734378 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_osx-arm64-18.1.8-h07b0088_25.conda + sha256: aae6cad658c9899f13d5941bcadc942f1a471acdfc43cd86c3635d0e353babc9 + md5: 4280e791148c1f9a3f8c0660d7a54acb + depends: + - clang_osx-arm64 18.1.8 h07b0088_25 + - clangxx_impl_osx-arm64 18.1.8 h555f467_25 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - libcxx >=18 + size: 19924 + timestamp: 1748575738546 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-3.31.8-h54ad630_0.conda + sha256: 55f8adaa565e9494aca45cf9b75689354479a179efe088522be345f0c5acca3b + md5: 7a61882f98f88aee01bae8a76d5990ba + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libcurl >=8.14.1,<9.0a0 + - libcxx >=19 + - libexpat >=2.7.1,<3.0a0 + - liblzma >=5.8.1,<6.0a0 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - rhash >=1.4.6,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 16632236 + timestamp: 1757877846468 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-18.1.8-h855ad52_2.conda + sha256: 93a26ec6d7e3d93db2d80c794e9710fc422d436680da165e78d7ff73b8ef6c12 + md5: 8fa0332f248b2f65fdd497a888ab371e + depends: + - __osx >=11.0 + - clang 18.1.8.* + - compiler-rt_osx-arm64 18.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 95924 + timestamp: 1757436417546 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.1.1-py314h4dc9dd8_0.conda + sha256: e07221f4bda014e492e9a8e99dc5ec307147afd44483346e58eaec6770f10865 + md5: 29a62536271a0869f2d0ecb04e834634 + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-content-trust >=0.1.1 + - conda-build >=25.9 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1310581 + timestamp: 1772191962239 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.7.2-py311h50b1a05_1.conda + sha256: 139682511b7c56e323e37f8e2f82e4f16c4f513e53c62fe7719fd0ccfee9b763 + md5: e7599e9d5846afce7d62cdbc4e3a7631 + depends: + - archspec >=0.2.3 + - backports.zstd >=1.3.0 + - boltons >=23.0.0 + - charset-normalizer + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - menuinst >=2 + - msgpack-python >=1.1.1 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.6.0 + - pycosat >=0.6.3 + - python + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.20 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - conda-libmamba-solver >=26.4.1 + - conda-lockfiles >=0.2.0 + - conda-pypi >=0.10.1 + - conda-rattler-solver >=0.1.1 + - conda-self >=0.2.0 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.3.0 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1442808 + timestamp: 1788877040465 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-26.7.2-py311h50b1a05_2.conda + sha256: 17ee3c7f280b0995a0129aa22fc6f0bf4821fd9d0cc8edf9e9a43aa71539c0c7 + md5: ed1383b1548602c31b2065269b0dbca1 + depends: + - archspec >=0.2.3 + - backports.zstd >=1.3.0 + - boltons >=23.0.0 + - charset-normalizer + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - menuinst >=2 + - msgpack-python >=1.1.1 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.6.0 + - pycosat >=0.6.3 + - python + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.20 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - conda-libmamba-solver >=26.4.1 + - conda-lockfiles >=0.2.0 + - conda-pypi >=0.10.1 + - conda-rattler-solver >=0.1.1 + - conda-self >=0.2.0 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.3.0 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=compressed-mapping + run_exports: {} + size: 1443070 + timestamp: 1789721537414 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h3feff0a_1.conda + sha256: 83819551a243ac95a96867977a0c7e2661d9e06ca268026c93d2c9f93969424f + md5: 93748a43e1a27821795af6701b7c06be + depends: + - libcxx >=19 + - __osx >=11.0 + license: CC0-1.0 + purls: [] + run_exports: {} + size: 25909 + timestamp: 1785920661291 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda + sha256: a7380056125a29ddc4c4efc4e39670bc8002609c70f143d92df801b42e0b486f + md5: 5cb4f9b93055faf7b6ae76da6123f927 + depends: + - __osx >=11.0 + - libcxx >=19 + license: CC0-1.0 + purls: [] + size: 24960 + timestamp: 1756734870487 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cxx-compiler-1.10.0-hba80287_0.conda + sha256: 52cbfc615a9727294fccdd507f11919ca01ff29bd928bb5aa0b211697a983e9f + md5: 7fca30a1585a85ec8ab63579afcac5d3 + depends: + - c-compiler 1.10.0 hdf49b6b_0 + - clangxx_osx-arm64 18.* + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6784 + timestamp: 1751115541888 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/doxygen-1.18.0-py310h3226bdb_0.conda + sha256: b7c2aa55df787bea73a0736525d666334f46d3cb60f500cc96f721baed991b26 + md5: e38fde3d8b43b4607fa433ae57e955b4 + depends: + - libiconv + - __osx >=11.0 + - libcxx >=21 + - libiconv >=1.18,<2.0a0 + license: GPL-2.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 14613657 + timestamp: 1788247276044 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h403dcb5_0.conda + sha256: dba5d4a93dc62f20e4c2de813ccf7beefed1fb54313faff9c4f2383e4744c8e5 + md5: ae2f556fbb43e5a75cc80a47ac942a8e + depends: + - __osx >=11.0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + size: 180970 + timestamp: 1767681372955 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h99e4e11_1.conda + sha256: ca42427b99ff92228e907bed5f10a1df868180997571d29c230faad2c38e35fd + md5: 891b1202d7b835f215e26a4db685ec7c + depends: + - __osx >=11.0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 181008 + timestamp: 1785915450316 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py314h0612a62_0.conda + sha256: 614c604a4ff2ee59579d6ad6489726b56027bd49f50f4674cdaf98c6a0daa6be + md5: 8e7628502497ee2940f8864a22e3bd0c + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 32182 + timestamp: 1763083208667 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gh-2.88.1-h01237fd_0.conda + sha256: ed5ac50ece778e035d7739dc7547023c0391b44d20b98d7ef473b06cbdb5d040 + md5: 1bb23382c23eae0e52a48bfd4c3cd5e6 + depends: + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 21742200 + timestamp: 1773332985739 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hypothesis-6.168.0-py311hfcb3ee1_0.conda + noarch: python + sha256: 250fe136514207ce0fd6854981924216dda7e881896573005868b230bd4e2817 + md5: 9f879f20e4e4ae7a8c4c5ead3fc3eddb + depends: + - python + - sortedcontainers >=2.1.0,<3.0.0 + - exceptiongroup >=1.0.0 + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __osx >=11.0 + license: MPL-2.0 + license_family: MOZILLA + run_exports: {} + size: 627624 + timestamp: 1788949815970 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + sha256: 24bc62335106c30fecbcc1dba62c5eba06d18b90ea1061abd111af7b9c89c2d7 + md5: 114e6bfe7c5ad2525eb3597acdbf2300 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 12389400 + timestamp: 1772209104304 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 12361647 + timestamp: 1773822915649 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + sha256: 6cdb5dee54c72e56ab189fb3ad33cb28533553d42590e7e831160248f4416a43 + md5: a5efc0b42bb8b42e97d0a29ae3e3c187 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14070242 + timestamp: 1786545847761 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h34f8a20_2.conda + sha256: aaea1d42b07769920db2af7471ece9399d2613448863da64ad8272998db5db34 + md5: 15235dd10450d67bc25ccafd5b46d2bc + depends: + - __osx >=11.0 + - libcxx >=21 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1165740 + timestamp: 1786762145768 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + sha256: c0a0bf028fe7f3defcdcaa464e536cf1b202d07451e18ad83fdd169d15bef6ed + md5: e446e1822f4da8e5080a9de93474184d + depends: + - __osx >=11.0 + - libcxx >=19 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1160828 + timestamp: 1769770119811 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-954.16-h4c6efb1_0.conda + sha256: 722595fb6f81552a88864f79f87238f0dba8e2d3f6c5adf4322a66259c4ea825 + md5: 04733a89c85df5b0fa72826b9e88b3a8 + depends: + - ld64_osx-arm64 954.16 h9d5fcb0_0 + - libllvm18 >=18.1.8,<18.2.0a0 + constrains: + - cctools_osx-arm64 1021.4.* + - cctools 1021.4.* + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 18863 + timestamp: 1752819098768 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-954.16-h9d5fcb0_0.conda + sha256: 825b56e7016fa64f3fb3b25ba535e838c914264c71ba47075bab91b56a738cbb + md5: f46ccafd4b646514c45cf9857f9b4059 + depends: + - __osx >=11.0 + - libcxx + - libllvm18 >=18.1.8,<18.2.0a0 + - sigtool + - tapi >=1300.6.5,<1301.0a0 + constrains: + - ld 954.16.* + - cctools_osx-arm64 1021.4.* + - cctools 1021.4.* + - clang >=18.1.8,<19.0a0 + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 1022059 + timestamp: 1752819033976 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.6-gpl_h6fbacd7_100.conda + sha256: 57fcc5cb6203cb0e119f46be708c8b2cf2bae47dc7580e5b4e76bd4b4c6d164a + md5: 4133c0cef1c6a25426b35f790e006648 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 791560 + timestamp: 1773243648871 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h76147b0_101.conda + sha256: cee5639cdfc5121339516e2cdf2f8338e376ca6ac42a45bba77b8b7e199b8305 + md5: bdcec8546f15ded1b3718309e027e603 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 798787 + timestamp: 1787292999432 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-11_h51639a9_openblas.conda + build_number: 11 + sha256: 718c3e1a16ad946caeeba041520dc18b7af461226bd8c28faef78b181920dbb4 + md5: 364639b771dbb4ede335d965f55a23ac + depends: + - libopenblas >=0.3.34,<0.3.35.0a0 + - libopenblas >=0.3.34,<1.0a0 + constrains: + - blas 2.311 openblas + - libcblas 3.11.0 11*_openblas + - liblapack 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + - mkl <2027 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 18300 + timestamp: 1789061064192 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-11_hb0561ab_openblas.conda + build_number: 11 + sha256: 3cebd672d8bd7c776a381422ea3fc48131343aeda2879974734715ac6a05c292 + md5: 73bde7d713e790b3168707e02102e87b + depends: + - libblas 3.11.0 11_h51639a9_openblas + constrains: + - blas 2.311 openblas + - liblapack 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18324 + timestamp: 1789061068736 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf3020a7_18.conda + sha256: 400a6c44a33bb58b18349bab81808750be2a44c85e38f3b0fc4ecd550684e9d6 + md5: 4c5aaaf2f27ea600c899f64b01c9f1b0 + depends: + - __osx >=11.0 + - libcxx >=18.1.8 + - libllvm18 >=18.1.8,<18.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libclang-cpp18.1 >=18.1.8,<18.2.0a0 + size: 13335319 + timestamp: 1773505818840 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda + sha256: c4d581b067fa60f9dc0e1c5f18b756760ff094a03139e6b206eb98d185ae2bb1 + md5: 9fc7771fc8104abed9119113160be15a + depends: + - __osx >=11.0 + - krb5 >=1.22.2,<1.23.0a0 + - libnghttp2 >=1.67.0,<2.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + size: 399616 + timestamp: 1773219210246 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.22.0-h6651222_0.conda + sha256: 46def32ec20d4dc6bce75d90fe5f2465a71cc22acef608c5ba37cc392e80e01d + md5: a7234b8d8e19a089dcb1eaaa18de1045 + depends: + - __osx >=11.0 + - krb5 >=1.22.2,<1.23.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libpsl >=0.23.1,<0.24.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.8,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + run_exports: + weak: + - libcurl >=8.22.0,<9.0a0 + size: 419925 + timestamp: 1788340708264 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.1-h55c6f16_0.conda + sha256: 3c8142cdd3109c250a926c492ec45bc954697b288e5d1154ada95272ffa21be8 + md5: 7a290d944bc0c481a55baf33fa289deb + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 570281 + timestamp: 1773203613980 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda + sha256: d1402087c8792461bfc081629e8aa97e6e577a31ae0b84e6b9cc144a18f48067 + md5: 4280e0a7fd613b271e022e60dea0138c + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 568094 + timestamp: 1774439202359 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda + sha256: 3f902803f0fc2643a4f82ff15a06811d974ff5fb6fa8f957720a7ef84e13ad98 + md5: b61106a0b5ac7cfe874f8b2e4f067dfa + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 578565 + timestamp: 1788869180613 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-devel-18.1.8-h6dc3340_8.conda + sha256: ff83d001603476033eca155ce77f7ba614d9dc70c5811e2ce9915a3cadacb56f + md5: fdf0850d6d1496f33e3996e377f605ed + depends: + - libcxx >=18.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 794791 + timestamp: 1742451369695 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321h26f1114_1.conda + sha256: 257c926f19e32bdb981fc674c76966049b6fc73f706bae58e9fed8757ad1da70 + md5: 843ef89082f368cb889305084d3b483c + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 107742 + timestamp: 1786616721640 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 + md5: 44083d2d2c2025afca315c7a172eab2b + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107691 + timestamp: 1738479560845 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h1a92334_3.conda + sha256: c0100064506ae8abb432c5a506d474f10af2cf48c33d62bc221fb28b6d6ff6ac + md5: 19e86c8a6a47e92bb2e70ca12e758c5c + depends: + - __osx >=11.0 + license: BSD-2-Clause OR GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - libev >=4.33,<4.34.0a0 + size: 39991 + timestamp: 1785917376956 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f + md5: 36d33e440c31857372a72137f78bacf5 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107458 + timestamp: 1702146414478 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + sha256: 03887d8080d6a8fe02d75b80929271b39697ecca7628f0657d7afaea87761edf + md5: a92e310ae8dfc206ff449f362fc4217f + depends: + - __osx >=11.0 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT + size: 68199 + timestamp: 1771260020767 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + sha256: 06780dec91dd25770c8cf01e158e1062fbf7c576b1406427475ce69a8af75b7e + md5: a32123f93e168eaa4080d87b0fb5da8a + depends: + - __osx >=11.0 + constrains: + - expat 2.7.5.* + license: MIT + license_family: MIT + purls: [] + size: 68192 + timestamp: 1774719211725 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + sha256: 5af74261101e3c777399c6294b2b5d290e508153268eb2e9ff99c4d69834612f + md5: a915151d5d3c5bf039f5ccc8402a436f + depends: + - __osx >=11.0 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 69362 + timestamp: 1781203631990 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40979 + timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + sha256: 2783389a7b9dda04c62cc54c6bbeb03dd3cef24b3fc642715d02a3fa19464a9a + md5: 216bbcc23c11e9695b3db4a8771d76eb + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 43637 + timestamp: 1787753403688 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_5.conda + sha256: f1bb2e00832ccfb67fcf66e01005e47d55db2c993a3de7c7b3d07b88edb5ef41 + md5: 90408ee3891a10f54795b45927b41666 + depends: + - _openmp_mutex + constrains: + - libgcc-ng ==16.2.0=*_5 + - libgomp 16.2.0 5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 365714 + timestamp: 1789485572279 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_5.conda + sha256: 8afe97e015b025e71823d8195da6b97c9177b0dedc2d5072fe15ec0f39086f25 + md5: 592ca0c6b8c4e1952fc28dbec011a86b + depends: + - libgfortran5 16.2.0 hdb7a957_5 + constrains: + - libgfortran-ng ==16.2.0=*_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 99933 + timestamp: 1789485673521 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_5.conda + sha256: e0046e236fefb28cdb93edda0acea93199f4f867a1925c26d3f5f0574d8d79c7 + md5: e4e2987faf21e97c1a8d91f57c79818d + depends: + - libgcc >=16.2.0 + constrains: + - libgfortran 16.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 554489 + timestamp: 1789485578946 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + sha256: de0336e800b2af9a40bdd694b03870ac4a848161b35c8a2325704f123f185f03 + md5: 4d5a7445f0b25b6a3ddbb56e790f5251 + depends: + - __osx >=11.0 + license: LGPL-2.1-only + purls: [] + size: 750379 + timestamp: 1754909073836 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-he4c29f2_3.conda + sha256: 689a14968267f2f97c07112fca5636e7d756036b9aee969911267c20bd3eea1f + md5: 17f4744c0873e9f77ac9b5cd0a27c187 + depends: + - __osx >=11.0 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 750816 + timestamp: 1787033961086 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-11_hd9741b5_openblas.conda + build_number: 11 + sha256: e7697df761eeb243272801a8c883dc1de6018a7f738746b443ef1396f0a78075 + md5: d67ef1462e77d20cd3e0d27c876c3c9a + depends: + - libblas 3.11.0 11_h51639a9_openblas + constrains: + - blas 2.311 openblas + - libcblas 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18284 + timestamp: 1789061072707 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm18-18.1.8-default_h8e162e0_12.conda + sha256: 82f2326bfbcd63ab7113400e871564eb1618c159db9c369fe5bfbd56149341fa + md5: 07fa1c8d37db7e95117220979f1f4ba3 + depends: + - __osx >=11.0 + - libcxx >=19 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libllvm18 >=18.1.8,<18.2.0a0 + size: 25869042 + timestamp: 1773654942297 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + sha256: 7bfc7ffb2d6a9629357a70d4eadeadb6f88fa26ebc28f606b1c1e5e5ed99dc7e + md5: 009f0d956d7bfb00de86901d16e486c7 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.2.* + license: 0BSD + purls: [] + size: 92242 + timestamp: 1768752982486 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + sha256: 23d0630046a3e8b164d8f80f2b74ed2605af2e7050ab9913018056402fae4311 + md5: 8ab10323068b107661a4b9a4af84f3b5 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 91720 + timestamp: 1786348695846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.5.0-h7950639_0.conda + sha256: 78fccef61fe4d6763c60e19dd5bc8aad7db553188609e3bd91159d158aecabaa + md5: 29e8e662089a1226a90a5dc5d499b7b7 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - libcxx >=19 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - reproc >=14.2,<15.0a0 + - libsolv >=0.7.35,<0.8.0a0 + - fmt >=12.1.0,<12.2.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - libarchive >=3.8.5,<3.9.0a0 + - nlohmann_json-abi ==3.12.0 + - spdlog >=1.17.0,<1.18.0a0 + - simdjson >=4.2.4,<4.3.0a0 + - libcurl >=8.18.0,<9.0a0 + - openssl >=3.5.4,<4.0a0 + - reproc-cpp >=14.2,<15.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 1653523 + timestamp: 1767884201469 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.9.0-py311h39d259c_1.conda + sha256: cf887a697dbc057b780e91e3cef9ddd4f8b620ed942abfb66726cbe95a1455c0 + md5: da893e49bdbd34c28cf6e5dab7415e78 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - __osx >=11.0 + - libcxx >=21 + - reproc >=14.2.8.post0,<14.3.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - fmt >=12.1.0,<12.2.0a0 + - libsolv >=0.7.39,<0.8.0a0 + - libcurl >=8.22.0,<9.0a0 + - simdjson >=4.6.11,<4.7.0a0 + - libmsgpack-c >=6.1.0,<7.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libarchive >=3.8.9,<3.9.0a0 + - nlohmann_json-abi ==3.12.0 + - openssl >=3.5.8,<4.0a0 + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libmamba >=2.9.0,<2.10.0a0 + size: 1853047 + timestamp: 1789061583159 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.5.0-h85b9800_0.conda + sha256: c0efbd9cf938a44fe98cfeb77d9bbb119f58bf0202ec5009b7d6621c6b96721c + md5: 6a3a2f6d6e90363288a96bc355b417c2 + depends: + - libcxx >=19 + - __osx >=11.0 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23068 + timestamp: 1767884201476 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-spdlog-2.9.0-h5900e61_1.conda + sha256: 86be9822154fe883d7ae6b9b74f29aad07b95d66d4eb775d71812445b77dab89 + md5: 732633e95ec580e01a023c5550014b3c + depends: + - libcxx >=21 + - __osx >=11.0 + - libmamba >=2.9.0,<2.10.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 21678 + timestamp: 1789061583159 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.5.0-py314hf8f60b7_0.conda + sha256: 11824fa9b721afe43cfd83ba899c893c1dba0b42c85ec618aa9284fd2ad66d6c + md5: c215957a49ac69b30d94a39eb1182009 + depends: + - python + - libmamba ==2.5.0 h7950639_0 + - libmamba-spdlog ==2.5.0 h85b9800_0 + - __osx >=11.0 + - python 3.14.* *_cp314 + - libcxx >=19 + - yaml-cpp >=0.8.0,<0.9.0a0 + - libmamba >=2.5.0,<2.6.0a0 + - zstd >=1.5.7,<1.6.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - nlohmann_json-abi ==3.12.0 + - python_abi 3.14.* *_cp314 + - openssl >=3.5.4,<4.0a0 + - fmt >=12.1.0,<12.2.0a0 + - pybind11-abi ==11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 769644 + timestamp: 1767884201481 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.9.0-py311h01962e9_1.conda + sha256: 2676bb5fbb5b70af3e37e3efc14f9a7be596a1fcfe1339d6261a98e465c360eb + md5: debf33966cd3f8e88dc561f2b67bfecc + depends: + - python + - libmamba ==2.9.0 py311h39d259c_1 + - libmamba-spdlog ==2.9.0 h5900e61_1 + - libcxx >=21 + - __osx >=11.0 + - spdlog >=1.17.0,<1.18.0a0 + - zstd >=1.5.7,<1.6.0a0 + - openssl >=3.5.8,<4.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - python_abi 3.11.* *_cp311 + - fmt >=12.1.0,<12.2.0a0 + - nlohmann_json-abi ==3.12.0 + - libmamba >=2.9.0,<2.10.0a0 + - pybind11-abi ==11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=compressed-mapping + run_exports: + weak: + - libmambapy >=2.9.0,<2.10.0a0 + size: 766157 + timestamp: 1789061583159 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 + md5: 57c4be259f5e0b99a5983799a228ae55 + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 73690 + timestamp: 1769482560514 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmsgpack-c-6.1.0-h784d473_7.conda + sha256: 7eea97b70943a054f90405b1c17da0c8b25348c77bc815978fac05d99e574334 + md5: bfb208af98c6c65859211b972e45cafd + depends: + - libcxx >=19 + - __osx >=11.0 + constrains: + - msgpack-c ==6.1.0 *_7 + license: BSL-1.0 + purls: [] + run_exports: + weak: + - libmsgpack-c >=6.1.0,<7.0a0 + size: 39302 + timestamp: 1786189396450 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda + sha256: a07cb53b5ffa2d5a18afc6fd5a526a5a53dd9523fbc022148bd2f9395697c46d + md5: a4b4dd73c67df470d091312ab87bf6ae + depends: + - __osx >=11.0 + - c-ares >=1.34.5,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + size: 575454 + timestamp: 1756835746393 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h435687b_1.conda + sha256: 445440d8cccccb6585f71d1eb7c949d64a2af2ced5481d924621424ff3b6a398 + md5: ac9902dcbe0417f50cf95ab2bde062fa + depends: + - __osx >=11.0 + - c-ares >=1.34.8,<2.0a0 + - libcxx >=21 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libnghttp2 >=1.68.1,<2.0a0 + size: 567024 + timestamp: 1787177422467 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b + depends: + - __osx >=11.0 + - c-ares >=1.34.6,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 576526 + timestamp: 1773854624224 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.34-openmp_h5e6e99c_1.conda + sha256: 18f167fdaa26f67a123c59766f4730b41866d2c0920b5c29d74a93d3a2e92a96 + md5: 9c874eb9d4441a658ef3acf6ac56dc3e + depends: + - __osx >=11.0 + - libgcc >=15 + - libgfortran + - libgfortran5 >=15.3.0 + - llvm-openmp >=21.1.8 + constrains: + - openblas >=0.3.34,<0.3.35.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libopenblas >=0.3.34,<1.0a0 + size: 4249236 + timestamp: 1789023664380 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.1-hdb0c161_1.conda + sha256: c8c3153df39abedf3413483f672151ad70f1adb0c06c192bdf7ec432c3616b07 + md5: 5d270f716a2b4bc13df9af8612071b17 + depends: + - __osx >=11.0 + - libcxx >=21 + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libpsl >=0.23.1,<0.24.0a0 + size: 72673 + timestamp: 1786970928205 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.11.16-h4e5ec87_2_cpython.conda + build_number: 2 + sha256: 84d73b53c8de05071a39806611f0f37f8f915510ef8998f6d749411b738a8080 + md5: 671bf1db2eb4a12fa79867f82544a29b + depends: + - __osx >=11.0 + - libcxx >=21 + license: Python-2.0 + purls: [] + run_exports: {} + size: 1699014 + timestamp: 1788392316133 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + sha256: fcfa03afe31f40dfabf011629d99836adbebbc3ed1b38c7e9eee9ffc7581975b + md5: c70eb797aa92a294b09ef8f41f6bd578 + depends: + - __osx >=11.0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 36651 + timestamp: 1786115069018 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.35-h5f525b2_0.conda + sha256: 6da97a1c572659c2be3c3f2f39d9238dac5af2b1fd546adf2b735b0fda2ed8ec + md5: b7ffc6dc926929b9b35af5084a761f26 + depends: + - libcxx >=19 + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 428408 + timestamp: 1754325703193 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.36-h7d962ec_0.conda + sha256: d7eeef792c8b12804c99b6a2c66e635e4f7d7a3d34713af070c2aa6c41c9b750 + md5: 038c047500a1db74206cf56811e80e08 + depends: + - __osx >=11.0 + - libcxx >=19 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 428233 + timestamp: 1773328439105 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.39-h626c152_1.conda + sha256: 45e2047bfd53afe77b275ac26c561a63fa0b50f42819bd7d0e73eb83f40593f8 + md5: b1685ac2297c78e075531b2b93cbd53e + depends: + - __osx >=11.0 + - libcxx >=21 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libsolv >=0.7.39,<0.8.0a0 + size: 434198 + timestamp: 1786955859375 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + sha256: beb0fd5594d6d7c7cd42c992b6bb4d66cbb39d6c94a8234f15956da99a04306c + md5: f6233a3fddc35a2ec9f617f79d6f3d71 + depends: + - __osx >=11.0 + - icu >=78.2,<79.0a0 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 918420 + timestamp: 1772819478684 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + sha256: 839b31d4830e896b4d315b551d27f2bb08026bc946df04bcc360d8627c3ba2cd + md5: fde96d40ebe9a9cb34e4122380189ddb + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 942754 + timestamp: 1787051243846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + sha256: 8bfe837221390ffc6f111ecca24fa12d4a6325da0c8d131333d63d6c37f27e0a + md5: b68e8f66b94b44aaa8de4583d3d4cc40 + depends: + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 279193 + timestamp: 1745608793272 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-hbf2880b_1.conda + sha256: a056e518c3d2d09fbb1778cea80c0c95338fb24adf1a817bbf90fb484850a304 + md5: d957a8eda98c49b073e8dcfd677c127a + depends: + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libssh2 >=1.11.1,<2.0a0 + size: 280492 + timestamp: 1786714226814 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_1.conda + sha256: 4f47de9de1990efd998edbbd6793f89c8f02ffde987ae8120b1c006acefd2a04 + md5: de09bd0f175611e94f21b28f8c708e80 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libuv >=1.52.1,<2.0a0 + size: 122729 + timestamp: 1785914645797 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.2-h5ef1a60_0.conda + sha256: 6432259204e78c8a8a815afae987fbf60bd722605fe2c4b022e65196b17d4537 + md5: b284e2b02d53ef7981613839fb86beee + depends: + - __osx >=11.0 + - icu >=78.2,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.2 + license: MIT + license_family: MIT + purls: [] + size: 466220 + timestamp: 1772704950232 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.4-heb56d2d_0.conda + sha256: 8a26f7c94ab849c1930da36c298f4b82e892382b70ae865cd7274188d9d783e2 + md5: b40633f711a5eb8617917e0e944b9965 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.4 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 469299 + timestamp: 1788635201153 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.2-h8d039ee_0.conda + sha256: 99cb32dd06a2e58c12981b71a84b052293f27b5ab042e3f21d895f5d7ee13eff + md5: e476ba84e57f2bd2004a27381812ad4e + depends: + - __osx >=11.0 + - icu >=78.2,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2-16 2.15.2 h5ef1a60_0 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 41206 + timestamp: 1772704982288 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.4-h550178d_0.conda + sha256: 81217fcd4d4abdc69ee63cdb9cdb22e05a20b9ba7a76f596579914a838fe5960 + md5: 4b97a34d06740b578c32deba195daf5a + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 heb56d2d_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 41375 + timestamp: 1788635206084 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b + md5: 369964e85dc26bfe78f41399b366c435 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + size: 46438 + timestamp: 1727963202283 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + sha256: a18fa5d5bac452401459f966cf0d872224e8080c4ff93c77e168d43ab42ef9d7 + md5: f39288f0ea63ae962e1a2e4f355a0d75 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 47822 + timestamp: 1785277049190 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.1-hdb3d66b_0.conda + sha256: 012e606dc31de2e4e173636a2ec13bc9b075498015cb65605b29e4f0bb9e481b + md5: 22ecd4403fcaf2fbbafa2cd53a0e0f0c + depends: + - __osx >=11.0 + constrains: + - intel-openmp <0.0a0 + - openmp 23.1.1|23.1.1.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + strong: + - llvm-openmp >=23.1.1 + size: 287782 + timestamp: 1788967337071 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-18-18.1.8-default_h8e162e0_12.conda + sha256: 39cc992f6f600dff6d77186495a8aa24fc3bec65fa59f67fe3f1579cafd6cef9 + md5: 5a9a475df620e76c80da30335596c38e + depends: + - __osx >=11.0 + - libllvm18 18.1.8 default_h8e162e0_12 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 23486860 + timestamp: 1773655148634 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-18.1.8-default_h8e162e0_12.conda + sha256: 5e400b2fe5915ce5a50035e3281c21605228b14d12db98f3a762535424d787c4 + md5: 6897c16fbfedfef8c2d4da1effc006f0 + depends: + - __osx >=11.0 + - libllvm18 18.1.8 default_h8e162e0_12 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - llvm-tools-18 18.1.8 default_h8e162e0_12 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - llvmdev 18.1.8 + - clang-tools 18.1.8 + - clang 18.1.8 + - llvm 18.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 94349 + timestamp: 1773655291188 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + sha256: 94d3e2a485dab8bdfdd4837880bde3dd0d701e2b97d6134b8806b7c8e69c8652 + md5: 01511afc6cc1909c5303cf31be17b44f + depends: + - __osx >=11.0 + - libcxx >=18 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 148824 + timestamp: 1733741047892 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-hdca3b7d_2.conda + sha256: 1887867b393eb3c2b336deaffcf228574821ea1e0dcb9ef7bf6c9f59cec40f03 + md5: d47f7f3481c6f2fcc4ed22802f61c6dd + depends: + - __osx >=11.0 + - libcxx >=21 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - lz4-c >=1.10.0,<1.11.0a0 + size: 171838 + timestamp: 1786717209785 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h74c22ad_1003.conda + sha256: 95d5f16bfa62ff50980a5e0329920bb2b5ec66d0e0d6c87954603713c3519ece + md5: 7fad2bcb66997fc0a53442db98031a39 + depends: + - __osx >=11.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 154521 + timestamp: 1787049250312 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + sha256: db40fd25c6306bfda469f84cddd8b5ebb9aa08d509cecb49dfd0bb8228466d0c + md5: e56eaa1beab0e7fed559ae9c0264dd88 + depends: + - __osx >=11.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + size: 152755 + timestamp: 1753889267953 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda + sha256: d635f2b1d9e19e8e68c5d33150f7e4f62df08ef2ef0e85977f743e81939afc01 + md5: ff068874356bbc7f9bd2d793f809f44b + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 26511 + timestamp: 1772445369187 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.2-py314h4dc9dd8_0.conda + sha256: cfe6079a2193f9ecf0279bff01eb57499a5a081607ea3c83b2c5522c00a538ca + md5: 312610ff2ccaa1e4995f4fb3d8c04f96 + depends: + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 188284 + timestamp: 1765733422732 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.5.2-py311hf19aa8c_1.conda + sha256: fbbf529317870a8d848161954c21e8cd3e05f7f089b84d0ccd4478063c16c872 + md5: 52866f8fe129a1f05a7f1b57159f4869 + depends: + - python + - tomli-w + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + run_exports: {} + size: 205960 + timestamp: 1786008936278 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py314h784bc60_1.conda + sha256: 9dc4ebe88064cf96bb97a4de83be10fbc52a24d2ff48a4561fb0fed337b526f0 + md5: 305227e4de261896033ad8081e8b52ae + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 92381 + timestamp: 1762504601981 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py311h2f25576_2.conda + sha256: bb05bafdedf0abe4d38969c1f04b320dd9d3f3eeabe84fb2498ec4706486203e + md5: bbd31dda4bf21964798f15fd1b5446f4 + depends: + - python + - libcxx >=21 + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/msgpack?source=hash-mapping + run_exports: {} + size: 101503 + timestamp: 1788525105579 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 + md5: 068d497125e4bf8a66bf707254fff5ae + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 797030 + timestamp: 1738196177597 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + sha256: 7024a48c8c0d0114ed4ab53c76bf9275d50e91ba7cea367a9aead638d3c29c68 + md5: 3dfa0d0316dc246cd44937a557de4501 + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 804298 + timestamp: 1786355189145 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ninja-1.13.2-h3feff0a_1.conda + sha256: 3309eaa32a97afb323edb380563206c660637046eed93886d4b6b1b0ca270076 + md5: db95a98a49313f75abcad60aefa720ed + depends: + - libcxx >=19 + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 164371 + timestamp: 1786713083876 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py311hbd1492f_0.conda + sha256: 08e5062ab9bce23adef1c62282a99d035780e43eb8a843b0f11d8a1e967fe123 + md5: 7738446d4be7ac8b56e6d6e3bdb7e52b + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + run_exports: + weak: + - numpy >=1.23,<3 + size: 7456206 + timestamp: 1779169211856 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + sha256: 361f5c5e60052abc12bdd1b50d7a1a43e6a6653aab99a2263bf2288d709dcf67 + md5: f4f6ad63f98f64191c3e77c5f5f29d76 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3104268 + timestamp: 1769556384749 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + sha256: f23239eacd75c4c50705e68fae1aa3292da473e6a3a4abe2330f1e6afa680704 + md5: ae71ab40048c19a389a7dcccb86c2481 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.4,<4.0a0 + size: 3110142 + timestamp: 1787698648639 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/py-rattler-0.25.0-py310h2dd3e6a_5.conda + noarch: python + sha256: f1549f5f3ed4cac1d5aa252b6aa244c024c84cede8b56e95213a7d8c40e5c037 + md5: e4503f021d9250cbc247dde89f499379 + depends: + - python + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.10 + constrains: + - __osx >=11.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/py-rattler?source=hash-mapping + run_exports: {} + size: 10609109 + timestamp: 1788524939961 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py311h5edb25e_5.conda + sha256: 0440ee2eba421dcecbd5fda2ee7a69bef541260d51605eabec19d6f4dd7d2460 + md5: 67142eda4d86a9cf088183a63a4cb287 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + run_exports: {} + size: 86586 + timestamp: 1788489351411 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py314hb84d1df_3.conda + sha256: ec919254bca5ba7ef89ac1284582aa91297b89c3b640feda05a3cab0a0402db2 + md5: b23019984b3399dada68581c20e6ef85 + depends: + - __osx >=11.0 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 92986 + timestamp: 1757744788529 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py311hcfb3d13_1.conda + sha256: 19485dcfce416f4b3adf46723407169eab6773b4741c08f960cc8c673b782eda + md5: 0506900719d35be117f29dd7d3869164 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + run_exports: {} + size: 1698149 + timestamp: 1787928984591 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.16-hd05a0c4_2_cpython.conda + build_number: 2 + sha256: dbdb7e9f1f91d6eab8cba59c9bb3d46f81b0fac2a2bc30bad03af0caf3b7dda3 + md5: 5a51f688fc616b12e21e038cfd04de27 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libpython 3.11.16 h4e5ec87_2_cpython + - libsqlite >=3.53.4,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.8,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.11.* *_cp311 + noarch: + - python + size: 13755577 + timestamp: 1788392365884 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.3-h4c637c5_101_cp314.conda + build_number: 101 + sha256: fccce2af62d11328d232df9f6bbf63464fd45f81f718c661757f9c628c4378ce + md5: 753c8d0447677acb7ddbcc6e03e82661 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 13522698 + timestamp: 1770675365241 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + sha256: 984e73d7957460689e10533059de8adb38a308853d298900a37acc58edd84cec + md5: e4b908da7cd496b3fa6798c0f60a2a19 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 192948 + timestamp: 1770223655988 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda + sha256: 95f385f9606e30137cf0b5295f63855fd22223a4cf024d306cf9098ea1c4a252 + md5: dcf51e564317816cb8d546891019b3ab + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 189475 + timestamp: 1770223788648 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 + md5: f8381319127120ce51e081dce4865cf4 + depends: + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 313930 + timestamp: 1765813902568 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + sha256: d6782b430e6dce4fe8c7ac118cd988d5a193eb4a7f60741edfaede58016b6110 + md5: 9347fd56a002e6b58946831d48fe62f6 + depends: + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 314395 + timestamp: 1787033878899 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.5.post0-h5505292_0.conda + sha256: a5f0dbfa8099a3d3c281ea21932b6359775fd8ce89acc53877a6ee06f50642bc + md5: f1d129089830365d9dac932c4dd8c675 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 32023 + timestamp: 1731926255834 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.8.post0-hdca3b7d_0.conda + sha256: f5198a70187913b2d801092f844b27ed58f1c519f293442e006a03a4d59d8feb + md5: 85d3473e23745290824a52cad54edfed + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - reproc >=14.2.8.post0,<14.3.0a0 + size: 37211 + timestamp: 1788160507406 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.5.post0-h286801f_0.conda + sha256: f1b6aa9d9131ea159a5883bc5990b91b4b8f56eb52e0dc2b01aa9622e14edc81 + md5: 11a3d09937d250fc4423bf28837d9363 + depends: + - __osx >=11.0 + - libcxx >=18 + - reproc 14.2.5.post0 h5505292_0 + license: MIT + license_family: MIT + purls: [] + size: 24834 + timestamp: 1731926355120 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.8.post0-h2fd3d4c_0.conda + sha256: f70bf33f32023195071a1bc3450bd0b1621d90a4096970fab20c7874c806c633 + md5: 7b59ae73b965a752c487ae12812d27eb + depends: + - reproc ==14.2.8.post0 hdca3b7d_0 + - __osx >=11.0 + - libcxx >=21 + - reproc >=14.2.8.post0,<14.3.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + size: 29958 + timestamp: 1788160507406 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rhash-1.4.6-h84a0fba_2.conda + sha256: b4da3ab88c407b4133d39512b3b615d6fb020c89d39f54491862e6731528ab95 + md5: 5cec61f5b18ca8c5bbb1ad2dc5923a8e + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - rhash >=1.4.6,<2.0a0 + size: 185483 + timestamp: 1786732022220 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.17-py314ha14b1ff_2.conda + sha256: 0f498e343be464219a99424260ff442144d0461a3a5eb30c9de6081af358b281 + md5: 87fc3a204f105e7e61c60a72509cccbd + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - __osx >=11.0 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 311922 + timestamp: 1766175797575 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py311hd322c8c_5.conda + sha256: 678998e78b3b913d07bf63f95d5b0ca12a021899e1f0f65e4aa98f1dd9ed2b12 + md5: 955a8af2107aef636b877b1243ba347e + depends: + - python + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=compressed-mapping + run_exports: {} + size: 128620 + timestamp: 1788881223251 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.15-py314ha14b1ff_1.conda + sha256: ad575cae7f662b2dafca88c4ce05120e322f825c0610e54b0a116550c817bbbe + md5: 5836fbf79e5f279ffbe4ba06066c51a3 + depends: + - python + - python 3.14.* *_cp314 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 133016 + timestamp: 1766159585543 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-0.1.3-h98dc951_1.conda + sha256: eb1dfb600a58c803ea6e4442f3f3f60c37b7f989292eb701523ad226540ed31b + md5: 0f87ccc21a1ac1415a4693b6eaa356e1 + depends: + - __osx >=11.0 + - libsigtool 0.1.3 h98dc951_1 + - openssl >=3.5.7,<4.0a0 + - sigtool-codesign 0.1.3 h98dc951_1 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 117761 + timestamp: 1786115116956 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + sha256: 7efd6d7d18bf9cc5788bfe1c1e1620d9dbbe00a81c646814929a82ff31944cf3 + md5: a322c0a0d3b5be99ee11f9ccec99b60e + depends: + - __osx >=11.0 + - libsigtool 0.1.3 h98dc951_1 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 114653 + timestamp: 1786115093248 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.4-ha7d2532_0.conda + sha256: 142758c665c2a896c1f275213068b324e92f378b03ba8d0019f57d72ea319515 + md5: b6ac50035bdc00e3f01322c43062b855 + depends: + - __osx >=11.0 + - libcxx >=19 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 252462 + timestamp: 1766034371359 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.6.11-hfbe1efa_0.conda + sha256: f71c2b4b7a26fe1dde566da612440f96eb940b2362f54b62a16415a26419eb8f + md5: 867c0f276e3e4a3db48b1a3e3f1e0636 + depends: + - __osx >=11.0 + - libcxx >=21 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - simdjson >=4.6.11,<4.7.0a0 + size: 311692 + timestamp: 1788630512409 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-h760df91_2.conda + sha256: 791d91a18647346c4318a4177add14abc546632fc2bf12a049cc328025368054 + md5: e4df5c4de3a06437b69ff6255c258ca2 + depends: + - __osx >=11.0 + - fmt >=12.1.0,<12.2.0a0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - spdlog >=1.17.0,<1.18.0a0 + size: 167030 + timestamp: 1785925098650 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/spdlog-1.17.0-ha0f8610_1.conda + sha256: 465e81abc0e662937046a2c6318d1a9e74baee0addd51234d36e08bae6811296 + md5: 1885f7cface8cd627774407eeacb2caf + depends: + - __osx >=11.0 + - fmt >=12.1.0,<12.2.0a0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + size: 166603 + timestamp: 1767781942683 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda + sha256: 37cd4f62ec023df8a6c6f9f6ffddde3d6620a83cbcab170a8fff31ef944402e5 + md5: b703bc3e6cba5943acf0e5f987b5d0e2 + depends: + - __osx >=11.0 + - libcxx >=17.0.0.a0 + - ncurses >=6.5,<7.0a0 + license: NCSA + license_family: MIT + purls: [] + run_exports: {} + size: 207679 + timestamp: 1725491499758 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h17e24d4_2.conda + sha256: 7cd99a6af769a497ab50d44d6697bb0cb50b20153153679aee1baef687963209 + md5: 8ee9613094590d6b7cbb2e98e5a75314 + depends: + - __osx >=11.0 + - openssl >=3.5.6,<4.0a0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 3742965 + timestamp: 1780697918469 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/taplo-0.10.0-h2b2570c_1.conda + sha256: c05b9d0bb740f48671d643d0e258639a3e727785b1eb62582385943ddabc5b6b + md5: 7b818d29210b93c231bc5bb0cd133d3b + depends: + - __osx >=11.0 + - openssl >=3.5.0,<4.0a0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 4005794 + timestamp: 1748302845549 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3127137 + timestamp: 1769460817696 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + sha256: 857d89087ae7f2c328cf256728affcb7343031a148b4af847334d248a9cf564c + md5: 90a01bf394d1c594e0eb9258f967d828 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3342183 + timestamp: 1787272852357 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py311h572238d_0.conda + sha256: 1bf370d4d4698b339d54342b2d653d9ed26f5004af2da3e5556b641fb5e56e1b + md5: 2855259ce88c41c1b2297041ec75f540 + depends: + - __osx >=11.0 + - cffi + - libcxx >=19 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + run_exports: {} + size: 14760 + timestamp: 1769438970347 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py314h6cfcd04_0.conda + sha256: 033dbf9859fe58fb85350cf6395be6b1346792e1766d2d5acab538a6eb3659fb + md5: e229f444fbdb28d8c4f40e247154d993 + depends: + - __osx >=11.0 + - cffi + - libcxx >=19 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + size: 14884 + timestamp: 1769439056290 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + sha256: 3e78c43207502418fc5fb2317bbbefe5df8b6fc1051d90bdcd1c881c76bb4193 + md5: 31cf6dcc138abe673c9440cd6fe6c6fe + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 77530 + timestamp: 1787228556857 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac + md5: 78a0fe9e9c50d2c381e8ee47e3ea437d + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 83386 + timestamp: 1753484079473 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-h784d473_1.conda + sha256: 450d4c6ad26bc2c18eb9420261568b86918b118c96ab4a7f2176c352b94101a2 + md5: 04b9b2b8cc9e14758965758d9c423b34 + depends: + - libcxx >=19 + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml-cpp >=0.8.0,<0.9.0a0 + size: 136591 + timestamp: 1785924101870 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda + sha256: 66ba31cfb8014fdd3456f2b3b394df123bbd05d95b75328b7c4131639e299749 + md5: 30475b3d0406587cf90386a283bb3cd0 + depends: + - libcxx >=18 + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 136222 + timestamp: 1745308075886 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py314h9d33bd4_1.conda + sha256: cdeb350914094e15ec6310f4699fa81120700ca7ab7162a6b3421f9ea9c690b4 + md5: 8a92a736ab23b4633ac49dcbfcc81e14 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - python 3.14.* *_cp314 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 397786 + timestamp: 1762512730914 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 + md5: ab136e4c34e97f34fb621d2592a393d8 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 433413 + timestamp: 1764777166076 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + sha256: da867f5092eb0cb746d353694f0098031fd9817a4ce7d5743121209ae0f406ca + md5: 4ec2684c73812cc2c3d78379384a39cc + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 433687 + timestamp: 1786599629846 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.3.0-py311h71c1bcc_0.conda + sha256: 5a30429e009b93c6dffe539cf0e3d220ef8d36ea42d36ca5c26b603cb3319c71 + md5: 49eb28c4f92e8a7440e3da6d8e8b5e58 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 243289 + timestamp: 1767045012846 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py311h71c1bcc_1.conda + sha256: 9da59e9eac2d9e2f2e94a7515259a2321963af5226d8f4938ddfe4cce10b16bb + md5: c69729bedbe0187fe381380dce369e8f + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT AND EPL-2.0 + run_exports: {} + size: 246371 + timestamp: 1788491304158 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311h7862a2f_4.conda + sha256: 6aae644b62defc0c2952d4f0e702c0a722d02697f9deda928f5c378f00b38692 + md5: 46961c0c38544ef0b1d76843a43b7aa9 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hf02afa3_4 + license: MIT + license_family: MIT + run_exports: {} + size: 336838 + timestamp: 1788480445043 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_1.conda + sha256: 1803c838946d79ef6485ae8c7dafc93e28722c5999b059a34118ef758387a4c9 + md5: b0c459f98ac5ea504a9d9df6242f7ee1 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335333 + timestamp: 1764018370925 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda + sha256: 6854ee7675135c57c73a04849c29cbebc2fb6a3a3bfee1f308e64bf23074719b + md5: 1302b74b93c44791403cbeee6a0f62a3 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335782 + timestamp: 1764018443683 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + sha256: 04767466ee9227c9c57ab2c6503e0149177d34111c7418d2f420297acb1eb229 + md5: c3301c058362f340100d91cd8be0393f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 55919 + timestamp: 1785906343696 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 56115 + timestamp: 1771350256444 +- conda: https://conda.anaconda.org/conda-forge/win-64/c-compiler-1.11.0-h528c1b4_0.conda + sha256: 55e04bd4af61500cb8cae064386be57d18fbfdf676655ff1c97c7e5d146c6e34 + md5: 6d994ff9ab924ba11c2c07e93afbe485 + depends: + - vs2022_win-64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6938 + timestamp: 1753098808371 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_1.conda + sha256: c9caca6098e3d92b1a269159b759d757518f2c477fbbb5949cb9fee28807c1f1 + md5: f02335db0282d5077df5bc84684f7ff9 + depends: + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 297941 + timestamp: 1761203850323 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + sha256: 924f2f01fa7a62401145ef35ab6fc95f323b7418b2644a87fea0ea68048880ed + md5: c360170be1c9183654a240aadbedad94 + depends: + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 294731 + timestamp: 1761203441365 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.1.1-py311h3485c13_3.conda + sha256: 3bd25424cd8a3bf0d3f750fdf5a260ed9727f4f8512e425a14caa3797f063182 + md5: 0cdd8e5ab6f28690fa174c66bbf9cdac + depends: + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: {} + size: 350398 + timestamp: 1788485365478 +- conda: https://conda.anaconda.org/conda-forge/win-64/cmake-3.31.8-hdcbee5b_0.conda + sha256: 074400a63931d8d571b2b2284bc5f105fd578c381847b05d5e3d0b03c3db8f69 + md5: 96afa0e05c4a683b1c3de91b0259b235 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libcurl >=8.14.1,<9.0a0 + - libexpat >=2.7.1,<3.0a0 + - liblzma >=5.8.1,<6.0a0 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 14669008 + timestamp: 1757878123930 +- conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.1.1-py311h1ea47a8_0.conda + sha256: a2d7a3b3b48bf15bb705a6b6dce061e0ee05275159fbe105acea30be989d13cd + md5: 07554fc0615396c760ca7bebd6a3797c + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-env >=2.6 + - conda-build >=25.9 + - conda-content-trust >=0.1.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1287539 + timestamp: 1772191674230 +- conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.1.1-py314h86ab7b2_0.conda + sha256: 1b6770ef97301830842c57ad0c18ad25858c0d62e2a1b1248d9b37fb1ce3eaf6 + md5: 2ce9348410797986e1c59939d6162826 + depends: + - archspec >=0.2.3 + - boltons >=23.0.0 + - charset-normalizer + - conda-libmamba-solver >=25.4.0 + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - jsonpatch >=1.32 + - menuinst >=2 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.0.0 + - pycosat >=0.6.3 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.19 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - zstandard >=0.19.0 + constrains: + - conda-env >=2.6 + - conda-content-trust >=0.1.1 + - conda-build >=25.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conda?source=hash-mapping + size: 1309098 + timestamp: 1772191684896 +- conda: https://conda.anaconda.org/conda-forge/win-64/conda-26.7.2-py311h86c7a16_1.conda + sha256: 64419ce1f7c9e8fe261f26dc50f186cfa4fbf8597a6fa75ded5d444a7c8e1651 + md5: e9b9aecf4fb5480de26ac8361178f89a + depends: + - archspec >=0.2.3 + - backports.zstd >=1.3.0 + - boltons >=23.0.0 + - charset-normalizer + - conda-package-handling >=2.2.0 + - distro >=1.5.0 + - frozendict >=2.4.2 + - menuinst >=2 + - msgpack-python >=1.1.1 + - packaging >=23.0 + - platformdirs >=3.10.0 + - pluggy >=1.6.0 + - pycosat >=0.6.3 + - python + - requests >=2.28.0,<3 + - ruamel.yaml >=0.11.14,<0.20 + - setuptools >=60.0.0 + - tqdm >=4 + - truststore >=0.8.0 + - conda-libmamba-solver >=26.4.1 + - conda-lockfiles >=0.2.0 + - conda-pypi >=0.10.1 + - conda-rattler-solver >=0.1.1 + - conda-self >=0.2.0 + - python_abi 3.11.* *_cp311 + constrains: + - conda-build >=25.9 + - conda-content-trust >=0.3.0 + - conda-env >=2.6 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1445968 + timestamp: 1788876749743 +- conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_0.conda + sha256: cd24ac6768812d53c3b14c29b468cc9a5516b71e1880d67f58d98d9787f4cc3a + md5: 444e9f7d9b3f69006c3af5db59e11364 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: CC0-1.0 + purls: [] + size: 21733 + timestamp: 1756734797622 +- conda: https://conda.anaconda.org/conda-forge/win-64/cpp-expected-1.3.1-h477610d_1.conda + sha256: ca7f86a8f5d91dffcbc0a720bb4c3be8b35ccfb8e1e44cfa43c95dd13b6c83fc + md5: 8f83a0d68492c0d19528ce750d6566b9 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: CC0-1.0 + run_exports: {} + size: 22027 + timestamp: 1785920525392 +- conda: https://conda.anaconda.org/conda-forge/win-64/cxx-compiler-1.11.0-h1c1089f_0.conda + sha256: c888f4fe9ec117c1c01bfaa4c722ca475ebbb341c92d1718afa088bb0d710619 + md5: 4d94d3c01add44dc9d24359edf447507 + depends: + - vs2022_win-64 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6957 + timestamp: 1753098809481 +- conda: https://conda.anaconda.org/conda-forge/win-64/doxygen-1.13.2-hbf3f430_0.conda + sha256: 7a0fd40fd704e97a8f6533a081ba29579766d7a60bcb8e5de76679b066c4a72e + md5: 5cb2e11931773612d7a24b53f0c57594 + depends: + - libiconv >=1.17,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: GPL-2.0-only + license_family: GPL + purls: [] + size: 9219343 + timestamp: 1738165042524 +- conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h1ced75a_1.conda + sha256: 1d6d6f1bf284e50cf617ce9a6250e0bf70bd0d30b068568364d6b1852aad9466 + md5: ca8fa0e01ed34bb161f4e8e86dfda22e + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 187511 + timestamp: 1785915492086 +- conda: https://conda.anaconda.org/conda-forge/win-64/fmt-12.1.0-h7f4e812_0.conda + sha256: cce96406ec353692ab46cd9d992eddb6923979c1a342cbdba33521a7c234176f + md5: 6e226b58e18411571aaa57a16ad10831 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 186390 + timestamp: 1767681264793 +- conda: https://conda.anaconda.org/conda-forge/win-64/frozendict-2.4.7-py311h3485c13_0.conda + sha256: 2f719c3f15a54d02b5970075d013440eb9cc16503128cd9a8e97cb6275c57c82 + md5: b17cb83dcb04b06a3377c3ce8423bca9 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 32227 + timestamp: 1763083050764 +- conda: https://conda.anaconda.org/conda-forge/win-64/frozendict-2.4.7-py314h5a2d7ad_0.conda + sha256: 87f206e4ac1ee9cc7099f3bbb05c039650b9a606bebb59c3778e5f2050795930 + md5: dbac0ebdc50febbb706fcd765227c90f + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/frozendict?source=hash-mapping + size: 32072 + timestamp: 1763082973024 +- conda: https://conda.anaconda.org/conda-forge/win-64/gh-2.88.1-h36e2d1d_0.conda + sha256: b286e07b5aa44db0e4969789da8b27ec62974108efd45900a444b72f32941859 + md5: 70ee65f0ce705a07b4848aec9ce97014 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 22357323 + timestamp: 1773332611831 +- conda: https://conda.anaconda.org/conda-forge/win-64/git-2.53.0-h57928b3_0.conda + sha256: 6bc7eb1202395c044ff24f175f9f6d594bdc4c620e0cfa9e03ed46ef34c265ba + md5: b63e3ad61f7507fc72479ab0dde1a6be + license: GPL-2.0-or-later and LGPL-2.1-or-later + purls: [] + size: 123465948 + timestamp: 1770982612399 +- conda: https://conda.anaconda.org/conda-forge/win-64/hypothesis-6.168.0-py311h66afae6_0.conda + noarch: python + sha256: f62b4379e5ebd99717e55b288d76eef6b07ec533efe7dab1b71e5a5e53ffc93a + md5: a9dda0fe34b12e047c65d25a973b2b70 + depends: + - python + - sortedcontainers >=2.1.0,<3.0.0 + - exceptiongroup >=1.0.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - _python_abi3_support 1.* + - cpython >=3.11 + license: MPL-2.0 + license_family: MOZILLA + run_exports: {} + size: 553834 + timestamp: 1788949818044 +- conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h5112557_2.conda + sha256: 75c549b55b673e15de8785a8e5dd85bca7eb612eee0ff4dc8d7bdaa15eacbdbb + md5: e596942e8ee6ee17fdcf1e6a77757a66 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 16835644 + timestamp: 1784916416303 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 + md5: 4432f52dc0c8eb6a7a6abc00a037d93c + depends: + - openssl >=3.5.5,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 751055 + timestamp: 1769769688841 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h719d79b_2.conda + sha256: 63ff03324e903eb01a715ccf357df56d66224e61952fd6615d86490ebefb3285 + md5: 93f5a01dec294a2228f757fe2f3432d4 + depends: + - openssl >=3.5.7,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 753425 + timestamp: 1786762169034 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda + sha256: 0cc7f963d689fcadc0f7e83eb1f538ea73543af92e2b988d552a3d12cceb56e6 + md5: c76cc84cfafa74e43d8951db29983ebb + depends: + - bzip2 >=1.0.8,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.5,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 1106665 + timestamp: 1773243755298 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_h8fdc8af_101.conda + sha256: 71cb6e49282d2aee1eb0249a290dc2333b0063ac2ecec69116ad569d822a0aaf + md5: 74c32a74e1b188a4e14fc359c721e4b9 + depends: + - bzip2 >=1.0.8,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 1151402 + timestamp: 1787292629824 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + build_number: 5 + sha256: f0cb7b2697461a306341f7ff32d5b361bb84f3e94478464c1e27ee01fc8f276b + md5: f9decf88743af85c9c9e05556a4c47c0 + depends: + - mkl >=2025.3.0,<2026.0a0 + constrains: + - liblapack 3.11.0 5*_mkl + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl + - liblapacke 3.11.0 5*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 67438 + timestamp: 1765819100043 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + build_number: 5 + sha256: 49dc59d8e58360920314b8d276dd80da7866a1484a9abae4ee2760bc68f3e68d + md5: b3fa8e8b55310ba8ef0060103afb02b5 + depends: + - libblas 3.11.0 5_hf2e6a31_mkl + constrains: + - liblapack 3.11.0 5*_mkl + - liblapacke 3.11.0 5*_mkl + - blas 2.305 mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 68079 + timestamp: 1765819124349 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + sha256: 6b2143ba5454b399dab4471e9e1d07352a2f33b569975e6b8aedc2d9bf51cbb0 + md5: ed181e29a7ebf0f60b84b98d6140a340 + depends: + - krb5 >=1.22.2,<1.23.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: curl + license_family: MIT + purls: [] + size: 392543 + timestamp: 1773218585056 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.22.0-hdb0ef4a_0.conda + sha256: 5fe063cffa90ad3ef04e25c76b1a79cdbc4f6c43747164a7dcdd89c4faebb84e + md5: e8405e754eaac42d66f957222fcfd876 + depends: + - krb5 >=1.22.2,<1.23.0a0 + - libpsl >=0.23.1,<0.24.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: curl + license_family: MIT + run_exports: + weak: + - libcurl >=8.22.0,<9.0a0 + size: 413226 + timestamp: 1788340784214 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + sha256: b31f6fb629c4e17885aaf2082fb30384156d16b48b264e454de4a06a313b533d + md5: 1c1ced969021592407f16ada4573586d + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT + purls: [] + size: 70323 + timestamp: 1771259521393 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + sha256: 6850c3a4d5dc215b86f58518cfb8752998533d6569b08da8df1da72e7c68e571 + md5: bfb43f52f13b7c56e7677aa7a8efdf0c + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.5.* + license: MIT + license_family: MIT + purls: [] + size: 70609 + timestamp: 1774719377850 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + sha256: 1a54d874addda73b6f7164d5f3905821277a1831bcc05edd74b3085391688571 + md5: ccc490c81ffe14181861beac0e8f3169 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + run_exports: {} + size: 71631 + timestamp: 1781203724164 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 45831 + timestamp: 1769456418774 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + sha256: 613e8077c5cca5df7fe84ade7d5050301bee3c6c4c450ffe61d34a349ab4f11c + md5: ceb38b0ba900847d8da4289c3c8e5708 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 49992 + timestamp: 1787753517346 +- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + sha256: 8cdf11333a81085468d9aa536ebb155abd74adc293576f6013fc0c85a7a90da3 + md5: 3b576f6860f838f950c570f4433b086e + depends: + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - libxml2 + - libxml2-16 >=2.14.6 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2411241 + timestamp: 1765104337762 +- conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + sha256: 0dcdb1a5f01863ac4e8ba006a8b0dc1a02d2221ec3319b5915a1863254d7efa7 + md5: 64571d1dd6cdcfa25d0664a5950fdaa2 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-only + purls: [] + size: 696926 + timestamp: 1754909290005 +- conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_3.conda + sha256: 35e04e3ddac7720fc7c550a1c9f998604299d6a7bd1f3ec9b2825d825061daa2 + md5: a8a2abdf0f901bc4779d9b7be0845921 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-only + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 694899 + timestamp: 1787033851701 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + build_number: 5 + sha256: a2d33f5cc2b8a9042f2af6981c6733ab1a661463823eaa56595a9c58c0ab77e1 + md5: e62c42a4196dee97d20400612afcb2b1 + depends: + - libblas 3.11.0 5_hf2e6a31_mkl + constrains: + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl + - liblapacke 3.11.0 5*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 80225 + timestamp: 1765819148014 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + sha256: f25bf293f550c8ed2e0c7145eb404324611cfccff37660869d97abf526eb957c + md5: ba0bfd4c3cf73f299ffe46ff0eaeb8e3 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - xz 5.8.2.* + license: 0BSD + purls: [] + size: 106169 + timestamp: 1768752763559 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + sha256: d36c4a1e1f80fd08e18a407e03622ff2f34dfdd022da6488ad19603dea19e6d5 + md5: 880a0c8549479b198af21ba5dc49b109 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - xz 5.8.3.* + license: 0BSD + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 105809 + timestamp: 1786348717883 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.5.0-h06825f5_0.conda + sha256: 632acf45a127242ca0dddc942c635bd43de718c51477ac000ed579410b7363a3 + md5: df96c0c3390a753e0c2df57fdbf98d97 + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libarchive >=3.8.5,<3.9.0a0 + - libcurl >=8.18.0,<9.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - zstd >=1.5.7,<1.6.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - reproc >=14.2,<15.0a0 + - fmt >=12.1.0,<12.2.0a0 + - nlohmann_json-abi ==3.12.0 + - libsolv >=0.7.35,<0.8.0a0 + - simdjson >=4.2.4,<4.3.0a0 + - openssl >=3.5.4,<4.0a0 + - reproc-cpp >=14.2,<15.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4995231 + timestamp: 1767884194847 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-2.9.0-py311h5893078_1.conda + sha256: 81cfe7f66b7d0430d8d0fe2e566b058976bd853bf3db71675bea8ebb594dea6d + md5: a614f16a38fa66c64ee8ead44a5eaf5c + depends: + - cpp-expected >=1.3.1,<1.3.2.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - simdjson >=4.6.11,<4.7.0a0 + - reproc >=14.2.8.post0,<14.3.0a0 + - libmsgpack-c >=6.1.0,<7.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libcurl >=8.22.0,<9.0a0 + - libsolv >=0.7.39,<0.8.0a0 + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + - fmt >=12.1.0,<12.2.0a0 + - spdlog >=1.17.0,<1.18.0a0 + - libarchive >=3.8.9,<3.9.0a0 + - nlohmann_json-abi ==3.12.0 + - openssl >=3.5.8,<4.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmamba >=2.9.0,<2.10.0a0 + size: 5416017 + timestamp: 1789061563074 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.5.0-h9ae1bf1_0.conda + sha256: 172991ecd8ffe8aadff9f083b8c1de282d91e670c939cdfff5970ccacbe98548 + md5: 463b59502db7115998f7d537ccd932df + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libmamba >=2.5.0,<2.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18007 + timestamp: 1767884194852 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmamba-spdlog-2.9.0-h8faee1a_1.conda + sha256: a2a7798ac7eee58530b6970c16dcd3a75026c73c6709bde557d33d5533f2c7a7 + md5: 802f51d8d4d49fdbcb0e7a24d60d2d62 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libmamba >=2.9.0,<2.10.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 18397 + timestamp: 1789061563074 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.5.0-py311hb0041a4_0.conda + sha256: d3ec31a3cfb43b07d04899cfd84376a31049be9611ab249b4889f0672e6d81b9 + md5: 2b522dd15f2449d921759864fd0648aa + depends: + - python + - libmamba ==2.5.0 h06825f5_0 + - libmamba-spdlog ==2.5.0 h9ae1bf1_0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - nlohmann_json-abi ==3.12.0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + - libmamba >=2.5.0,<2.6.0a0 + - fmt >=12.1.0,<12.2.0a0 + - pybind11-abi ==11 + - yaml-cpp >=0.8.0,<0.9.0a0 + - spdlog >=1.17.0,<1.18.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 583683 + timestamp: 1767884194856 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.5.0-py314h532c739_0.conda + sha256: 3b5bec6472c07448e8485def16a11157c32295e9016130809fc4b3b1e6dda174 + md5: c9cd67775b772d64b54239fefb4d8504 + depends: + - python + - libmamba ==2.5.0 h06825f5_0 + - libmamba-spdlog ==2.5.0 h9ae1bf1_0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - fmt >=12.1.0,<12.2.0a0 + - zstd >=1.5.7,<1.6.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - nlohmann_json-abi ==3.12.0 + - spdlog >=1.17.0,<1.18.0a0 + - pybind11-abi ==11 + - libmamba >=2.5.0,<2.6.0a0 + - python_abi 3.14.* *_cp314 + - openssl >=3.5.4,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/libmambapy?source=hash-mapping + size: 587744 + timestamp: 1767884194858 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmambapy-2.9.0-py311h024d1a5_1.conda + sha256: f2ea64e62881e9025d35f16eda0a3565409ccb15344fc2a65ff7233eee839f89 + md5: 80ccdaf0e75e3c0bd145990d558162a4 + depends: + - python + - libmamba ==2.9.0 py311h5893078_1 + - libmamba-spdlog ==2.9.0 h8faee1a_1 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - spdlog >=1.17.0,<1.18.0a0 + - libmamba >=2.9.0,<2.10.0a0 + - zstd >=1.5.7,<1.6.0a0 + - openssl >=3.5.8,<4.0a0 + - pybind11-abi ==11 + - python_abi 3.11.* *_cp311 + - yaml-cpp >=0.8.0,<0.9.0a0 + - fmt >=12.1.0,<12.2.0a0 + - nlohmann_json-abi ==3.12.0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libmambapy >=2.9.0,<2.10.0a0 + size: 592209 + timestamp: 1789061563074 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 + md5: e4a9fc2bba3b022dad998c78856afe47 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 89411 + timestamp: 1769482314283 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmsgpack-c-6.1.0-h5112557_7.conda + sha256: 9b81aaa83ae15710a3466053bdf9090b1a4ba9ec40c8231b90f0efd5e3d02855 + md5: 7928697b653d56d99504b533b02f3c64 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + constrains: + - msgpack-c ==6.1.0 *_7 + license: BSL-1.0 + run_exports: + weak: + - libmsgpack-c >=6.1.0,<7.0a0 + size: 40862 + timestamp: 1786189315611 +- conda: https://conda.anaconda.org/conda-forge/win-64/libpsl-0.23.1-h9b16d47_1.conda + sha256: e53fe3b09f82b59b644264133d6d148ac31bb5451b7583cff55690bf038f2f62 + md5: 39cdf8c4f59e4d253cfa8091c8b3d7de + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libpsl >=0.23.1,<0.24.0a0 + size: 73511 + timestamp: 1786970749988 +- conda: https://conda.anaconda.org/conda-forge/win-64/libpython-3.11.16-hcc31f7b_2_cpython.conda + build_number: 2 + sha256: 04f062b03acf1ddc4beec719f96e55f1d2e7fb95f5ec9b165a2842fd8f477b59 + md5: 10fade090385803157f532053aca2c74 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Python-2.0 + run_exports: {} + size: 48658 + timestamp: 1788391959103 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.35-h8883371_0.conda + sha256: 80ccb7857fa2b60679a5209ca04334c86c46a441e8f4f2859308b69f8e1e928a + md5: 987be7025314bcfe936de3f0e91082b5 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 466924 + timestamp: 1754325716718 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.36-h8883371_0.conda + sha256: a5abc59cd2644b964a303cc62ef8c2e08adad05d14941196130cd718d00e6eea + md5: 3ab153559588783712f13ab285a0a506 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 468194 + timestamp: 1773328370793 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsolv-0.7.39-h8883371_1.conda + sha256: e42da29bf02c95828216e0719de48f4d466779b53c7b0ca233a93c949c51147d + md5: 6d41739b93a9f1870d7031a8e2ca2ee3 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libsolv >=0.7.39,<0.8.0a0 + size: 469787 + timestamp: 1786955834309 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + sha256: 5fccf1e4e4062f8b9a554abf4f9735a98e70f82e2865d0bfdb47b9de94887583 + md5: 8830689d537fda55f990620680934bb1 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1297302 + timestamp: 1772818899033 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + sha256: 0a45d7c0f20146fff787a106f8fa187872e309c70975ff8f0936e188914c26ad + md5: a72d495965b144bb7da033642d389047 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 1314919 + timestamp: 1787051140039 +- conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h734d217_1.conda + sha256: 1097b08429b4f53f5751a32c6d99a083d227ca7f7812c0b239eea124cec073a0 + md5: a21dd9ca39e74910bb96989feb916420 + depends: + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - libssh2 >=1.11.1,<2.0a0 + size: 295149 + timestamp: 1786713186180 +- conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + sha256: cbdf93898f2e27cefca5f3fe46519335d1fab25c4ea2a11b11502ff63e602c09 + md5: 9dce2f112bfd3400f4f432b3d0ac07b2 + depends: + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 292785 + timestamp: 1745608759342 +- conda: https://conda.anaconda.org/conda-forge/win-64/libuv-1.51.0-hfd05255_1.conda + sha256: f03dc82e6fb1725788e73ae97f0cd3d820d5af0d351a274104a0767035444c59 + md5: 31e1545994c48efc3e6ea32ca02a8724 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 297087 + timestamp: 1753948490874 +- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + sha256: 0fccf2d17026255b6e10ace1f191d0a2a18f2d65088fd02430be17c701f8ffe0 + md5: 8a86073cf3b343b87d03f41790d8b4e5 + depends: + - ucrt + constrains: + - pthreads-win32 <0.0a0 + - msys2-conda-epoch <0.0a0 + license: MIT AND BSD-3-Clause-Clear + purls: [] + size: 36621 + timestamp: 1759768399557 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + sha256: b8c71b3b609c7cfe17f3f2a47c75394d7b30acfb8b34ad7a049ea8757b4d33df + md5: e365238134188e42ed36ee996159d482 + depends: + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libxml2 2.15.2 + - icu <0.0a0 + license: MIT + license_family: MIT + purls: [] + size: 520078 + timestamp: 1772704728534 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.4-h3cfd58e_0.conda + sha256: 9fa71cec30425c9cc8eda6af3574af1faf4fefa5e239c9e037b1f778ef3ce8ac + md5: bab5489f4bd38494ffaa83d6ab924c1f + depends: + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libxml2 2.15.4 + license: MIT + license_family: MIT + run_exports: {} + size: 515800 + timestamp: 1788635268845 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda + sha256: f905eb7046987c336122121759e7f09144729f6898f48cd06df2a945b86998d8 + md5: 1007e1bfe181a2aee214779ee7f13d30 + depends: + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxml2-16 2.15.2 h692994f_0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - icu <0.0a0 + license: MIT + license_family: MIT + purls: [] + size: 43681 + timestamp: 1772704748950 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.4-he095d88_0.conda + sha256: a19bb3821830f6b1281e782844d5dd265f796dfed2e4812b16c9995a8c778c88 + md5: 3ce54d0f8f24dfab99d82414411640aa + depends: + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 h3cfd58e_0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 43845 + timestamp: 1788635276599 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 + md5: 41fbfac52c601159df6c01f875de31b9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 55476 + timestamp: 1727963768015 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + sha256: 0629c2cc0404d3bb29d6baa7b4ba62da80797015e86de050db81ea5a07050527 + md5: 5d2ff29d465097458cc3ff6569151991 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 58529 + timestamp: 1785276664143 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + sha256: bb55a3736380759d338f87aac68df4fd7d845ae090b94400525f5d21a55eea31 + md5: e5505e0b7d6ef5c19d5c0c1884a2f494 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - openmp 22.1.0|22.1.0.* + - intel-openmp <0.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + size: 347404 + timestamp: 1772025050288 +- conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + sha256: 632cf3bdaf7a7aeb846de310b6044d90917728c73c77f138f08aa9438fc4d6b5 + md5: 0b69331897a92fac3d8923549d48d092 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 139891 + timestamp: 1733741168264 +- conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h6a83c73_2.conda + sha256: 6824e36be0f95ae516dc36dddd18f69cbad0e4e07906fcb10a5f26a8dc471903 + md5: 3e0691cb20fcaf922df78ccea08b771a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: BSD-2-Clause + license_family: BSD + run_exports: + weak: + - lz4-c >=1.10.0,<1.11.0a0 + size: 150673 + timestamp: 1786717127870 +- conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda + sha256: 344f4f225c6dfb523fb477995545542224c37a5c86161f053a1a18fe547aa979 + md5: c5cb4159f0eea65663b31dd1e49bbb71 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + size: 165589 + timestamp: 1753889311940 +- conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1003.conda + sha256: e26c855c4b5d797ba5a9e96a6392d58676981660849b99fdb939aee3164e8323 + md5: 3678b093a471615102e97124f37b233f + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: GPL-2.0-or-later + license_family: GPL + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 165289 + timestamp: 1787049159945 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py311h3f79411_1.conda + sha256: 3d37fb1900e31131f84549560e7a4bfea5f39aa3ecd73345fef1f33975cf0baa + md5: f55de41c947bdd2ff9bbeffedf8089f7 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 29362 + timestamp: 1772445178723 +- conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.4.2-py311h3e6a449_0.conda + sha256: 0482c0c5962ba3da648071affec41b416a51c6afbf52264ea40434e43993b0cf + md5: 25b1fdcd2436106cab080dc1932dba63 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 179383 + timestamp: 1765733304312 +- conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.4.2-py314h13fbf68_0.conda + sha256: 1f4535acbc5a2d75e2b6d83fca659c468bc2597af798fdf2e45091265a094a32 + md5: ced101aaa08315eb8a058b1190500d12 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause AND MIT + purls: + - pkg:pypi/menuinst?source=hash-mapping + size: 179510 + timestamp: 1765733497067 +- conda: https://conda.anaconda.org/conda-forge/win-64/menuinst-2.5.2-py311h5dfdfe8_2.conda + sha256: 506e974841e686a20a398c0202a6f1ecaa018cc099947c16f27982a2dc9cc116 + md5: eaceabc41b0f7354ff4c103b7b128d20 + depends: + - python + - tomli-w + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT + run_exports: {} + size: 197656 + timestamp: 1788033918999 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + sha256: b2b4c84b95210760e4d12319416c60ab66e03674ccdcbd14aeb59f82ebb1318d + md5: fd05d1e894497b012d05a804232254ed + depends: + - llvm-openmp >=21.1.8 + - tbb >=2022.3.0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + size: 100224829 + timestamp: 1767634557029 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.1.2-py311h3fd045d_1.conda + sha256: 9883b64dea87c50e98fabc05719ff0fdc347f57d7bacda19bcd69b80d8c436d4 + md5: b0f2fb2eadce667ad09ca7d3ff868c71 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 87848 + timestamp: 1762504210288 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.1.2-py314h909e829_1.conda + sha256: 2ce1f564d5aa2e0637c03692baeea4ecf234c7fb2a43e7810c369e1b054d7a30 + md5: ad4584f884d029b02fc9eaf89afc5d9f + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 88657 + timestamp: 1762504357246 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.2-py311h275cad7_2.conda + sha256: 7d0ab6a67502c90eb9d5ab7501cd78e665772c0625dbd1e576e8d403c5a3a8e6 + md5: c1cbbe406a14fe1348ef81b833eeb352 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 89981 + timestamp: 1788525101274 +- conda: https://conda.anaconda.org/conda-forge/win-64/ninja-1.13.2-h477610d_0.conda + sha256: e41a945c34a5f0bd2109b73a65486cd93023fa0a9bcba3ef98f9a3da40ba1180 + md5: 7ecb9f2f112c66f959d2bb7dbdb89b67 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 309417 + timestamp: 1763688227932 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py311h80b3fa1_1.conda + sha256: c5cd26fb28d92d6c3843b96489f433ef87d1866d03a746f7228230b74bef431a + md5: a824c6667179120c458beb9e9394932f + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 7803678 + timestamp: 1770098404597 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + sha256: 53a5ad2e5553b8157a91bb8aa375f78c5958f77cb80e9d2ce59471ea8e5c0bd6 + md5: eb585509b815415bc964b2c7e11c7eb3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9343023 + timestamp: 1769557547888 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + sha256: 9dddb559ba49744d5d94092d8d13cb0567f5c3b3f439f3acf28433d1f4256acc + md5: 73cb1783f47c452be4c309430fbef89f + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + run_exports: + weak: + - openssl >=3.6.4,<4.0a0 + size: 9474879 + timestamp: 1787699876495 +- conda: https://conda.anaconda.org/conda-forge/win-64/py-rattler-0.25.0-py310hb39080a_5.conda + noarch: python + sha256: 4a2588998a517e5393d6d9b27c5f8b48ea62252d41cc0c4c2e51028a7a369dff + md5: e5e9c249bd333b8e8fe3786dfc84219b + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - _python_abi3_support 1.* + - cpython >=3.10 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 13160262 + timestamp: 1788524937530 +- conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py311h3485c13_3.conda + sha256: f2968e9d6516d7df4c678ce99e922285f244bfaf65069fb2cb3d307b4661f889 + md5: b0cdbb8f4ecd5e1750400ef8bbcb390b + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 79248 + timestamp: 1757744865552 +- conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py311h3485c13_5.conda + sha256: 45711bf126ef718ff24ff3ecba802182cc3dea8f3a7d7241096f4ceecd30c605 + md5: 6ec2e623a62146aaa90378b076d668c0 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: {} + size: 80363 + timestamp: 1788489400539 +- conda: https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.6-py314h5a2d7ad_3.conda + sha256: e14a8c506e699c968596f1a07e925d63dcc49ee9f3623c4c8a5541ad186e9071 + md5: 4e6be0e1a7a9cd7dde994137b8ebd5cf + depends: + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycosat?source=hash-mapping + size: 79551 + timestamp: 1757744726713 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.5-py311hf51aa87_1.conda + sha256: 6efcdaf484b2f489dd3df97c81ea4a038a38506c7b4a25f09032648dd8e700a1 + md5: 70c94f5f5e8d5ef1a492e5da51f9266e + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 1870889 + timestamp: 1787928977441 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 + md5: d09dbf470b41bca48cbe6a78ba1e009b + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 18416208 + timestamp: 1772728847666 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.16-hb12b558_2_cpython.conda + build_number: 2 + sha256: f8c7ba41d2bafe4b9f0be3f8f13a399dae76f5b83e679f63d86a26e8527f7c7a + md5: 253b0adaeefbea921fe6982124be0e64 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libpython 3.11.16 hcc31f7b_2_cpython + - libsqlite >=3.53.4,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.8,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + run_exports: + weak: + - python_abi 3.11.* *_cp311 + noarch: + - python + size: 18442021 + timestamp: 1788392017572 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.3-h4b44e0e_101_cp314.conda + build_number: 101 + sha256: 3f99d83bfd95b9bdae64a42a1e4bf5131dc20b724be5ac8a9a7e1ac2c0f006d7 + md5: 7ec2be7eaf59f83f3e5617665f3fbb2e + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - python_abi 3.14.* *_cp314 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 18273230 + timestamp: 1770675442998 + python_site_packages_path: Lib/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + sha256: 301c3ba100d25cd5ae37895988ee3ab986210d4d972aa58efed948fbe857773d + md5: a0153c033dc55203e11d1cac8f6a9cf2 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 187108 + timestamp: 1770223467913 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda + sha256: a2aff34027aa810ff36a190b75002d2ff6f9fbef71ec66e567616ac3a679d997 + md5: 0cd9b88826d0f8db142071eb830bce56 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=compressed-mapping + size: 181257 + timestamp: 1770223460931 +- conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.5.post0-h2466b09_0.conda + sha256: 112dee79da4f55de91f029dd9808f4284bc5e0cf0c4d308d4cec3381bf5bc836 + md5: c3ca4c18c99a3b9832e11b11af227713 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: [] + size: 37058 + timestamp: 1731926140985 +- conda: https://conda.anaconda.org/conda-forge/win-64/reproc-14.2.8.post0-h5112557_0.conda + sha256: 252b3d912d50e0575f1d64826426ee87076f27abd19fa734ba54dc1a1dadbee3 + md5: 7be4b88c22a3938a80a5107e95f46383 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc >=14.2.8.post0,<14.3.0a0 + size: 38678 + timestamp: 1788160440623 +- conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.5.post0-he0c23c2_0.conda + sha256: ccf49fb5149298015ab410aae88e43600954206608089f0dfb7aea8b771bbe8e + md5: d2ce31fa746dddeb37f24f32da0969e9 + depends: + - reproc 14.2.5.post0 h2466b09_0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: [] + size: 30096 + timestamp: 1731926177599 +- conda: https://conda.anaconda.org/conda-forge/win-64/reproc-cpp-14.2.8.post0-hf5d2508_0.conda + sha256: a62f567d8de231a7aec332447f0add8593c610426b594da1e5b8e1edf959dec3 + md5: 9f9e7861b48ce4f9d022961e92aef40c + depends: + - reproc ==14.2.8.post0 h5112557_0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - reproc >=14.2.8.post0,<14.3.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - reproc-cpp >=14.2.8.post0,<14.3.0a0 + size: 31779 + timestamp: 1788160440623 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.17-py311hf893f09_2.conda + sha256: 90f4058642a6badfe710b2d1b0dd8cd802485303a209e6591c3c77ed74f0c363 + md5: a4d4fcbe5a1b6e5bae7cb5a1bac4919d + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 292781 + timestamp: 1766175809044 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml-0.18.17-py314hc5dbbe4_2.conda + sha256: 62b73874b6c264a73e7904b33322128c4881c78dfe2fb9131becfbf84343ee2d + md5: 027ff6055e5c82ba4e085857f09302cb + depends: + - python + - ruamel.yaml.clib >=0.2.15 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 306951 + timestamp: 1766175833786 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py311hf893f09_1.conda + sha256: 5810ddd1efe4ac259ad0ff55778da71205a68edfbbb4d45488b270e7f2f23438 + md5: 4bf8909dc04f6ab8c6d7f2a2a4a51d19 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 107529 + timestamp: 1766159555820 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py311hf893f09_5.conda + sha256: aa52e86f7e5abc4cfa6163d241c863ec666898df06157090df92d03ccd0f2d99 + md5: d9e2f2263b0460729aec9dda14c7acb4 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + run_exports: {} + size: 121731 + timestamp: 1788881212900 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.15-py314hc5dbbe4_1.conda + sha256: b719637ce71e533193cd2bcacbf6ba5c10deaafa1be90d96040ee2314c6b17d1 + md5: 496de351b0f9afe9e245229528304f25 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 105668 + timestamp: 1766159584330 +- conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.2.4-h49e36cd_0.conda + sha256: 4bb3d41240e455bffc919042d8bbe64ae6cfd560dc9eeda0e84fd8f33b53da26 + md5: c625e0530b27e3bad5f59fa00744bbb8 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 298171 + timestamp: 1766034112737 +- conda: https://conda.anaconda.org/conda-forge/win-64/simdjson-4.6.11-h477610d_0.conda + sha256: b964734499b40786abfa9fbce00bd58e9977350c5527a9646b20f9841d2920c4 + md5: 075e31927fe43fbcee169227bf23aa33 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: Apache-2.0 + license_family: APACHE + run_exports: + weak: + - simdjson >=4.6.11,<4.7.0a0 + size: 420423 + timestamp: 1788630508984 +- conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-h9f585f1_1.conda + sha256: 90c9befa5f154463647c8e101bc7a4e05cb84b731e2dea5406bedfea02f8b012 + md5: 5c17c0a063b4d36b15d5f9c0ca5377a0 + depends: + - fmt >=12.1.0,<12.2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 174787 + timestamp: 1767781882230 +- conda: https://conda.anaconda.org/conda-forge/win-64/spdlog-1.17.0-hd227c35_2.conda + sha256: bd14e9166389be0c654e724e16290772b65745aee8d8bb8bacc65d3179330387 + md5: 0d1e29b811d1fea9ba0fe8c869fa5e96 + depends: + - fmt >=12.1.0,<12.2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + run_exports: + weak: + - spdlog >=1.17.0,<1.18.0a0 + size: 174998 + timestamp: 1785924400857 +- conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h18a1a76_2.conda + sha256: 4350e711a930e87eadaf87e633fd0339eecfd55a9116161fde6973066a755053 + md5: dd0703d842bbe23e13f99e3681e4b350 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + run_exports: {} + size: 4244234 + timestamp: 1780697930138 +- conda: https://conda.anaconda.org/conda-forge/win-64/taplo-0.10.0-h63977a8_1.conda + sha256: b38dd1dd1fe52d26f739d144a85fd277103320bd8e037b66a299457d5a827a04 + md5: 5fc2fa2f444b00e0f5b1f60a23f2c2f8 + depends: + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 4441127 + timestamp: 1748302918824 +- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + sha256: abd9a489f059fba85c8ffa1abdaa4d515d6de6a3325238b8e81203b913cf65a9 + md5: 0f9817ffbe25f9e69ceba5ea70c52606 + depends: + - libhwloc >=2.12.2,<2.12.3.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 155869 + timestamp: 1767886839029 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: TCL + license_family: BSD + purls: [] + size: 3526350 + timestamp: 1769460339384 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + sha256: f19618a3a82cc483dacf1c70a30367ee7b0c7e71b90f1f80e14feff44fbd3688 + md5: dd9eb33c99d352b6356f86964d6040f7 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: TCL + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3782376 + timestamp: 1787272860855 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + run_exports: {} + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py311h3fd045d_0.conda + sha256: f30fdd8cdc5aa9800d6f05ed772d8e5b6427202c4bf2c86513ac62070215f19d + md5: fb1cf06b4259739eaa61d94bc219c0e8 + depends: + - cffi + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + run_exports: {} + size: 18452 + timestamp: 1769438859987 +- conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.1.0-py314h909e829_0.conda + sha256: 96990a5948e0c30788360836d94bf6145fdac0c187695ed9b3c2d61d9e11d267 + md5: 54e012b629ac5a40c9b3fa32738375dc + depends: + - cffi + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + size: 18504 + timestamp: 1769438844417 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a + md5: 1e610f2416b6acdd231c5f573d754a0f + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19356 + timestamp: 1767320221521 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-ha367084_41.conda + sha256: 35444c55a92e2f7f7ba26bc70f81e56e52344f7d064c0fd4b40a46a58517b79c + md5: aa805b5522c2a98fa286e551a1f48546 + depends: + - vc14_runtime >=14.51.36247 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 21383 + timestamp: 1785359368566 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 + md5: 37eb311485d2d8b2c419449582046a42 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_34 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 683233 + timestamp: 1767320219644 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + sha256: 4e4cb599cdc41bf2109d1464c127b5bcbddf548ce3e322e612afb691338b48f8 + md5: ac5333bb3d429361f23adf704cc49a78 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.51.36247 habf1de7_41 + constrains: + - vs2015_runtime 14.51.36247.* *_41 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + run_exports: {} + size: 767955 + timestamp: 1785359364369 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 + md5: 242d9f25d2ae60c76b38a5e42858e51d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 115235 + timestamp: 1767320173250 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + sha256: 731e043390c9457299484d39e427221fc868a9249540a498a5a4f6456c7744d1 + md5: 350bb67a5c8e5f1c53347ac544ab6600 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.51.36247.* *_41 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + run_exports: + strong: + - vcomp14 >=14.51.36247 + size: 155910 + timestamp: 1785359349999 +- conda: https://conda.anaconda.org/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_34.conda + sha256: 05bc657625b58159bcea039a35cc89d1f8baf54bf4060019c2b559a03ba4a45e + md5: 1d699ffd41c140b98e199ddd9787e1e1 + depends: + - vswhere + constrains: + - vs_win-64 2022.14 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23060 + timestamp: 1767320175868 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 + md5: 433699cba6602098ae8957a323da2664 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 63944 + timestamp: 1753484092156 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-h5112557_1.conda + sha256: 643e9f439d6446c28e85f30b089e0fafbc937e57b7443f9e66d1dc2ef6bce6ff + md5: 04e7e743edaa756d863aaa3bf6c89aad + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml-cpp >=0.8.0,<0.9.0a0 + size: 150033 + timestamp: 1785924043282 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-cpp-0.8.0-he0c23c2_0.conda + sha256: 031642d753e0ebd666a76cea399497cc7048ff363edf7d76a630ee0a19e341da + md5: 9bb5064a9fca5ca8e7d7f1ae677354b6 + depends: + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 148572 + timestamp: 1745308037198 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_1.conda + sha256: 10f089bedef1a28c663ef575fb9cec66b2058e342c4cf4a753083ab07591008f + md5: b2d90bca78b57c17205ce3ca1c427813 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 375869 + timestamp: 1762512737575 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_1.conda + sha256: 87bf6ba2dcc59dfbb8d977b9c29d19b6845ad54e092ea8204dcec62d7b461a30 + md5: c1ef46c3666be935fbb7460c24950cff + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 381179 + timestamp: 1762512709971 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 + md5: 053b84beec00b71ea8ff7a4f84b55207 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 388453 + timestamp: 1764777142545 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + sha256: ca7daae4f218a11fab82cc2857f0ea518ec3f46acec60490485347a4c22c6b3e + md5: e4ac308c39d6d0e131154976da67cf3b + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 387535 + timestamp: 1786599623274 +- pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + name: docutils + version: 0.22.4 + sha256: d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl + name: rich + version: 14.3.3 + sha256: 793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl + name: nh3 + version: 0.3.4 + sha256: 0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2b/dd/437e601fa9d2b6bf8507256daf52196a226c3b340261f4cde8fac8e853ea/delocate-0.13.0-py3-none-any.whl + name: delocate + version: 0.13.0 + sha256: 11f7596f88984c33f76b27fe2eea7637d1ce369a9e0b6737bbc706b6426e862c + requires_dist: + - packaging>=20.9 + - typing-extensions>=4.12.2 + - macholib + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl + name: cryptography + version: 46.0.6 + sha256: 22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97 + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.6 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + name: twine + version: 6.2.0 + sha256: 418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8 + requires_dist: + - readme-renderer>=35.0 + - requests>=2.20 + - requests-toolbelt>=0.8.0,!=0.9.0 + - urllib3>=1.26.0 + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - keyring>=21.2.0 ; platform_machine != 'ppc64le' and platform_machine != 's390x' + - rfc3986>=1.4.0 + - rich>=12.0.0 + - packaging>=24.0 + - id + - keyring>=21.2.0 ; extra == 'keyring' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + name: requests-toolbelt + version: 1.0.0 + sha256: cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 + requires_dist: + - requests>=2.0.1,<3.0.0 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + name: id + version: 1.6.1 + sha256: f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca + requires_dist: + - urllib3>=2,<3 + - build ; extra == 'dev' + - bump>=1.3.2 ; extra == 'dev' + - id[test,lint] ; extra == 'dev' + - bandit ; extra == 'lint' + - interrogate ; extra == 'lint' + - mypy ; extra == 'lint' + - ruff<0.14.15 ; extra == 'lint' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pretend ; extra == 'test' + - coverage[toml] ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + name: nh3 + version: 0.3.4 + sha256: d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl + name: pefile + version: 2024.8.26 + sha256: 76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f + requires_python: '>=3.6.0' +- pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl + name: nh3 + version: 0.3.4 + sha256: c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + name: jaraco-classes + version: 3.4.0 + sha256: f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + requires_dist: + - more-itertools + - sphinx>=3.5 ; extra == 'docs' + - jaraco-packaging>=9.3 ; extra == 'docs' + - rst-linker>=1.9 ; extra == 'docs' + - furo ; extra == 'docs' + - sphinx-lint ; extra == 'docs' + - jaraco-tidelift>=1.4 ; extra == 'docs' + - pytest>=6 ; extra == 'testing' + - pytest-checkdocs>=2.4 ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-mypy ; extra == 'testing' + - pytest-enabler>=2.2 ; extra == 'testing' + - pytest-ruff>=0.2.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + name: keyring + version: 25.7.0 + sha256: be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f + requires_dist: + - pywin32-ctypes>=0.2.0 ; sys_platform == 'win32' + - secretstorage>=3.2 ; sys_platform == 'linux' + - jeepney>=0.4.2 ; sys_platform == 'linux' + - importlib-metadata>=4.11.4 ; python_full_version < '3.12' + - jaraco-classes + - jaraco-functools + - jaraco-context + - pytest>=6,!=8.1.* ; extra == 'test' + - pyfakefs ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - pygobject-stubs ; extra == 'type' + - shtab ; extra == 'type' + - types-pywin32 ; extra == 'type' + - shtab>=1.1.0 ; extra == 'completion' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/89/74/bf0ea7b89e114af19a38c2341746fd1f17f1de23fa8d8019cbcd39719c5f/delvewheel-1.12.0-py3-none-any.whl + name: delvewheel + version: 1.12.0 + sha256: 659ef85a83e4503e037e65570f7a87d2ad4e0ee55dc2d19d9afed420a031caf0 + requires_dist: + - pefile>=2024.8.26 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + name: markdown-it-py + version: 4.0.0 + sha256: 87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + name: more-itertools + version: 10.8.0 + sha256: 52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl + name: altgraph + version: 0.17.5 + sha256: f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597 +- pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + name: jeepney + version: 0.9.0 + sha256: 97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 + requires_dist: + - pytest ; extra == 'test' + - pytest-trio ; extra == 'test' + - pytest-asyncio>=0.17 ; extra == 'test' + - testpath ; extra == 'test' + - trio ; extra == 'test' + - async-timeout ; python_full_version < '3.11' and extra == 'test' + - trio ; extra == 'trio' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + name: mdurl + version: 0.1.2 + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl + name: secretstorage + version: 3.5.0 + sha256: 0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 + requires_dist: + - cryptography>=2.0 + - jeepney>=0.6 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + name: build + version: 1.4.0 + sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 + requires_dist: + - packaging>=24.0 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.11 ; python_full_version < '3.10' and extra == 'virtualenv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl + name: macholib + version: 1.16.4 + sha256: da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea + requires_dist: + - altgraph>=0.17 +- pypi: https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl + name: cryptography + version: 46.0.6 + sha256: 67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175 + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.6 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: nh3 + version: 0.3.4 + sha256: 0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl + name: pywin32-ctypes + version: 0.2.3 + sha256: 8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + name: readme-renderer + version: '44.0' + sha256: 2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 + requires_dist: + - nh3>=0.2.14 + - docutils>=0.21.2 + - pygments>=2.5.1 + - cmarkgfm>=0.8.0 ; extra == 'md' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + name: jaraco-context + version: 6.1.2 + sha256: bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535 + requires_dist: + - backports-tarfile ; python_full_version < '3.12' + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-test>=5.6.0 ; extra == 'test' + - portend ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.14 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + name: pygments + version: 2.20.0 + sha256: 81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + name: jaraco-functools + version: 4.4.0 + sha256: 9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176 + requires_dist: + - more-itertools + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-classes ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + name: rfc3986 + version: 2.0.0 + sha256: 50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd + requires_dist: + - idna ; extra == 'idna2008' + requires_python: '>=3.7' diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..0d906e7f --- /dev/null +++ b/pixi.toml @@ -0,0 +1,213 @@ +[workspace] +authors = [ + "Insight Software Consortium <>", + "Matt McCormick ", + "Jean-Christophe Fillion-Robin ", + "Tom Birdsong ", + "Hans J. Johnson ", + "Dženan Zukić ", + "Simon Rit ", + "Francois Budin ", + "LucasGandel ", + "Davis Marc Vigneault ", + "Jon Haitz Legarreta Gorroño ", + "Lee Newberg ", + "Cavan Riley ", +] + +name = "ITKPythonPackage" +version = "0.1.0" +channels = ["conda-forge"] +platforms = ["linux-64", "linux-aarch64", "osx-arm64", "win-64"] + +######################################## +# Common features used by all envs +######################################## + +# Build tooling shared by all platforms +[feature.build-dev-tools] +[feature.build-dev-tools.dependencies] +cmake = ">=3.26,<4" +doxygen = "<2" +ninja = ">=1.11.1,<2" +pkginfo = ">=1.12.1.2,<2" + +# Python packaging/build support shared by all platforms +[feature.python-dev-pkgs] +[feature.python-dev-pkgs.dependencies] +numpy = ">=1.26" +packaging = ">=25.0,<26" +pathspec = "*" +pyproject-metadata = "*" +scikit-build-core = ">=0.11.6,<0.12.0" +setuptools_scm = ">=9.2.0" +setuptools = ">=61" +tomli = "*" +pip = "*" + +[feature.python-dev-pkgs.pypi-dependencies] +build = "*" + +######################################## +# Manylinux-specific features +######################################## + +# Shared dependencies for all manylinux wheel builds +# (might be helpful for including newer manylinux versions later) +[feature.manylinux-common-build] +platforms = ["linux-64", "linux-aarch64"] + +[feature.manylinux-common-build.dependencies] +auditwheel = "*" +patchelf = "*" # Needed for auditwheel +wheel = "*" + +[feature.manylinux-common-build.pypi-dependencies] +build = "*" + +[feature.linux-build] +platforms = ["linux-64", "linux-aarch64"] + +[feature.linux-build.dependencies] +cxx-compiler = "==1.11.0" +c-compiler = "==1.11.0" +libhwloc = "*" # Needed for tbb +auditwheel = "*" +patchelf = "*" # Needed for auditwheel +wheel = "*" + +[feature.manylinux228-build] +platforms = ["linux-64", "linux-aarch64"] + +[feature.manylinux228-build.system-requirements] +linux = "5.4" +libc = { family = "glibc", version = "2.28" } + +[feature.linux-build.activation.env] +PYTHONNOUSERSITE = "1" # Exclude system .local site packages + +######################################## +# macOS-specific feature +######################################## + +[feature.macosx-build] +platforms = ["osx-arm64"] +system-requirements = { macos = "14.0" } + +# delocate is a Python tool; cxx-compiler is the macOS toolchain meta-package +[feature.macosx-build.pypi-dependencies] +delocate = "*" + +[feature.macosx-build.dependencies] +cxx-compiler = "==1.10.0" # XCode 16.0 before castxml required updates +c-compiler = "==1.10.0" # XCode 16.0 before castxml required updates +#libhwloc = "*" # Needed for tbb + +[feature.windows-build] +platforms = ["win-64"] + +[feature.windows-build.dependencies] +git = "*" # Git is not always available in PowerShell by default +cxx-compiler = "==1.11.0" # MSVC 2022 +c-compiler = "==1.11.0" # MSVC 2022 + +[feature.windows-build.pypi-dependencies] +delvewheel = "*" +# windows = "*" + +[feature.py311] +[feature.py311.dependencies] +python = ">=3.11,<3.12" + +#[feature.py312] +#[feature.py312.dependencies] +#python = ">=3.12,<3.13" +# +#[feature.py313] +#[feature.py313.dependencies] +#python = ">=3.13,<3.14" +# +#[feature.py314] +#[feature.py314.dependencies] +#python = ">=3.14,<3.15" + +######################################## +# Publishing feature (no compilers needed) +######################################## +[feature.publish] +[feature.publish.dependencies] +python = ">=3.11" +gh = "*" +zstd = "*" + +[feature.publish.pypi-dependencies] +twine = ">=6.0" + + +######################################## +# Developer-only testing feature. +# Never referenced by any build environment. +######################################## +[feature.dev] +[feature.dev.dependencies] +python = ">=3.11" +pytest = ">=8" +hypothesis = ">=6" + +[feature.dev.tasks.test] +cmd = "pytest -q" +description = "Run the developer unit test suite" + + +######################################## +# Environments: compose features per target +######################################## + +[environments] +publish = ["publish"] +dev = ["py311", "dev"] +linux-py311 = ["py311", "linux-build", "build-dev-tools", "python-dev-pkgs"] + +# Manylinux wheel-build env (docker manylinux228 images, CI, etc.) +manylinux228-py311 = [ + "py311", + "manylinux-common-build", + "manylinux228-build", + "build-dev-tools", + "python-dev-pkgs", +] + + +# macOS build env (Apple Silicon only) +macosx-py311 = ["py311", "build-dev-tools", "python-dev-pkgs", "macosx-build"] + +# Windows build env +windows-py311 = ["py311", "build-dev-tools", "python-dev-pkgs", "windows-build"] + +[dependencies] +# pixi-pycharm and conda are only needed to support development in pycharm +# https://pixi.prefix.dev/v0.21.1/ide_integration/pycharm/#how-to-use +pixi-pycharm = ">=0.0.10,<0.0.11" +conda = "*" +taplo = ">=0.8.1,<0.11" +pre-commit = "*" + +[tasks] +taplo = "fmt pixi.toml" + +[tasks.build-itk-wheels] +cmd = ["python", "scripts/build_wheels.py"] + +[tasks.pre-commit-install] +cmd = "pre-commit install -f -t pre-commit -t prepare-commit-msg -t commit-msg --hook-type commit-msg --install-hooks" + +[tasks.pre-commit-run] +cmd = "pre-commit run --all-files" + +[tasks.publish-wheels] +cmd = ["python", "Utilities/scripts/publish_wheels.py"] +description = "Upload built wheels to PyPI (or TestPyPI with --test)" + +[tasks.publish-tarball-cache] +cmd = ["python", "Utilities/scripts/publish_tarball_cache.py"] +description = "Upload .tar.zst/.zip build caches to GitHub Releases" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..66685b2d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,77 @@ +# Root-level pyproject.toml — developer tooling configuration only. +# This file configures linters/formatters for the ITKPythonPackage build scripts. +# It does NOT define a Python package; wheel-specific pyproject.toml files are +# generated dynamically by scripts/pyproject_configure.py into build subdirectories. + +[tool.black] +line-length = 88 +target-version = ["py311"] + +[tool.ruff] +line-length = 88 +target-version = "py311" + +[tool.ruff.lint] +# E/F: pycodestyle errors + pyflakes +# I: isort import ordering +# UP: pyupgrade (modernise syntax for Python 3.11+) +# B: flake8-bugbear (opinionated bug detection) +# W: pycodestyle warnings, including W605 invalid escape sequences +select = ["E", "F", "I", "UP", "B", "W"] +# E501 (line-too-long) is handled by black; suppress here to avoid duplication +ignore = ["E501"] + +[tool.commitizen] +name = "cz_customize" + +[tool.commitizen.customize] +message_template = "{{change_type}}: {{message}}" +example = "ENH: Add support for Python 3.12" +schema = ": " +schema_pattern = "(BUG|COMP|DOC|ENH|PERF|STYLE): [A-Z].*" +bump_pattern = "^(ENH|BUG|COMP|PERF)" +bump_map = { "ENH" = "MINOR", "BUG" = "PATCH", "COMP" = "PATCH", "PERF" = "PATCH" } +commit_parser = "^(?PBUG|COMP|DOC|ENH|PERF|STYLE): (?P.*)" +changelog_pattern = "^(BUG|COMP|DOC|ENH|PERF|STYLE)?(!)?" +change_type_map = { "ENH" = "Enhancements", "BUG" = "Bug Fixes", "COMP" = "Build Fixes", "DOC" = "Documentation", "PERF" = "Performance", "STYLE" = "Style" } +change_type_order = [ + "Enhancements", + "Bug Fixes", + "Build Fixes", + "Performance", + "Documentation", + "Style", +] +info = """ +ITK commit message convention: PREFIX: Description + +Valid prefixes: + BUG: a bug fix + COMP: a compilation/build fix + DOC: documentation changes + ENH: new feature or enhancement + PERF: performance improvement + STYLE: code style/formatting (no logic change) +""" + +[[tool.commitizen.customize.questions]] +type = "list" +name = "change_type" +message = "Select the type of change:" +choices = [ + { value = "ENH", name = "ENH: A new feature or enhancement", key = "e" }, + { value = "BUG", name = "BUG: A bug fix", key = "b" }, + { value = "COMP", name = "COMP: A compilation/build fix", key = "c" }, + { value = "DOC", name = "DOC: Documentation changes", key = "d" }, + { value = "PERF", name = "PERF: A performance improvement", key = "p" }, + { value = "STYLE", name = "STYLE: Code style/formatting (no logic change)", key = "s" }, +] + +[[tool.commitizen.customize.questions]] +type = "input" +name = "message" +message = "Commit description (start with capital letter):" + +[tool.pytest.ini_options] +pythonpath = ["scripts"] +testpaths = ["tests"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b From c937c0e67c71fcc932e33eb37846fe697aa9bc43 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 14:37:47 -0500 Subject: [PATCH 02/16] ENH: Add target platform and architecture resolution Co-Authored-By: Cavan Riley --- scripts/target_platform.py | 210 ++++++++++++++++++++++++++++++++++ tests/test_target_platform.py | 135 ++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 scripts/target_platform.py create mode 100644 tests/test_target_platform.py diff --git a/scripts/target_platform.py b/scripts/target_platform.py new file mode 100644 index 00000000..f6213b4d --- /dev/null +++ b/scripts/target_platform.py @@ -0,0 +1,210 @@ +"""Single source of truth for target platform and architecture. + +Every architecture spelling the build needs is a property here. Adding +a new architecture means adding one enum member and filling in each +property, so a missing translation is a construction error rather than +a silently-skipped branch. +""" + +from __future__ import annotations + +import os +from collections.abc import Mapping +from dataclasses import dataclass +from enum import Enum + + +class Arch(Enum): + """Canonical machine name. Never a per-OS nickname.""" + + X86_64 = "x86_64" + AARCH64 = "aarch64" + I686 = "i686" + + +_MACHINE_ALIASES: dict[str, Arch] = { + "x86_64": Arch.X86_64, + "amd64": Arch.X86_64, + "x64": Arch.X86_64, + "aarch64": Arch.AARCH64, + "arm64": Arch.AARCH64, + "i686": Arch.I686, + "i386": Arch.I686, +} + +_OS_NAMES = ("linux", "darwin", "windows") + +# (arch, manylinux_version) -> container image tag +_MANYLINUX_IMAGE_TAGS: dict[tuple[Arch, str], str] = { + # manylinux_2_28 is what ships; the _2_34 entry is deliberate headroom + # for evaluating a higher glibc floor and is not a shipping target. + (Arch.X86_64, "_2_34"): "latest", + (Arch.X86_64, "_2_28"): "20250913-6ea98ba", + (Arch.AARCH64, "_2_28"): "2025.08.12-1", +} + +_PIXI_SUFFIX: dict[tuple[str, Arch], str] = { + ("linux", Arch.X86_64): "x64", + ("linux", Arch.AARCH64): "aarch64", + ("linux", Arch.I686): "x86", + ("darwin", Arch.AARCH64): "arm64", + ("darwin", Arch.X86_64): "x86_64", + ("windows", Arch.X86_64): "x64", +} + +_MACOSX_DEPLOYMENT_TARGET = "14_0" + + +@dataclass(frozen=True) +class ContainerImage: + """A manylinux build image and the registry that serves it.""" + + name: str + registry: str + + @property + def reference(self) -> str: + return f"{self.registry}/{self.name}" + + +@dataclass(frozen=True) +class TargetPlatform: + """The platform being built *for*, with every spelling it needs.""" + + os_name: str + arch: Arch + manylinux_version: str = "" + + def __post_init__(self) -> None: + if self.os_name not in _OS_NAMES: + raise ValueError( + f"Unknown os_name {self.os_name!r}; " f"expected one of {_OS_NAMES}" + ) + if not isinstance(self.arch, Arch): + raise TypeError(f"arch must be an Arch, got {self.arch!r}") + if (self.os_name, self.arch) not in _PIXI_SUFFIX: + raise ValueError( + f"Unsupported combination {self.os_name}/{self.arch.value}" + ) + if self.manylinux_version: + if self.os_name != "linux": + raise ValueError("manylinux_version is only meaningful on linux") + if (self.arch, self.manylinux_version) not in _MANYLINUX_IMAGE_TAGS: + raise ValueError( + f"Unknown manylinux version " + f"{self.manylinux_version!r} for {self.arch.value}" + ) + + @property + def pixi_suffix(self) -> str: + """Naming convention used by pixi environments and build dirs.""" + return _PIXI_SUFFIX[(self.os_name, self.arch)] + + @property + def build_dir_suffix(self) -> str: + """Trailing token of ``ITK--_`` build dirs.""" + return self.pixi_suffix + + @property + def linux_triple(self) -> str: + if self.os_name != "linux": + raise ValueError("linux_triple is only defined on linux") + return f"{self.arch.value}-linux-gnu" + + @property + def cmake_osx_arch(self) -> str: + if self.os_name != "darwin": + raise ValueError("cmake_osx_arch is only defined on darwin") + return "arm64" if self.arch is Arch.AARCH64 else self.arch.value + + @property + def wheel_plat_tag(self) -> str: + if self.os_name == "linux": + return f"manylinux{self.manylinux_version}_{self.arch.value}" + if self.os_name == "darwin": + return f"macosx_{_MACOSX_DEPLOYMENT_TARGET}_" f"{self.cmake_osx_arch}" + return "win_amd64" + + @property + def container_image(self) -> ContainerImage: + if self.os_name != "linux" or not self.manylinux_version: + raise ValueError("container_image requires a manylinux linux target") + tag = _MANYLINUX_IMAGE_TAGS[(self.arch, self.manylinux_version)] + if self.arch is Arch.X86_64: + # dockcross spells the arch with a hyphen and calls it x64. + return ContainerImage( + name=f"manylinux{self.manylinux_version}-x64:{tag}", + registry="docker.io/dockcross", + ) + return ContainerImage( + name=f"manylinux{self.manylinux_version}_{self.arch.value}:{tag}", + registry="quay.io/pypa", + ) + + @classmethod + def detect( + cls, + env: Mapping[str, str] | None = None, + *, + manylinux_version: str | None = None, + ) -> TargetPlatform: + """Resolve the *target* platform from uname and the environment. + + Parameters + ---------- + manylinux_version : str, optional + Overrides the ambient ``MANYLINUX_VERSION`` environment + variable; ``""`` means "ignore ambient manylinux state". + """ + env = os.environ if env is None else env + uname = os.uname() if hasattr(os, "uname") else None + sysname = uname.sysname.lower() if uname else "" + if sysname.startswith("linux"): + os_name = "linux" + elif sysname.startswith("darwin"): + os_name = "darwin" + elif os.name == "nt": + os_name = "windows" + else: + raise ValueError(f"Unsupported operating system {sysname!r}") + + # PROCESSOR_ARCHITECTURE describes the process environment, not the + # package config mapping callers pass in as ``env``; fall back to the + # real environment so Windows, which has no os.uname, still resolves. + ambient_machine = env.get("PROCESSOR_ARCHITECTURE") or os.environ.get( + "PROCESSOR_ARCHITECTURE", "" + ) + machine = env.get("TARGET_ARCH") or ( + uname.machine if uname else ambient_machine + ) + arch = _MACHINE_ALIASES.get(machine.lower()) + if arch is None: + raise ValueError(f"Unknown machine {machine!r}") + + return cls( + os_name=os_name, + arch=arch, + manylinux_version=( + env.get("MANYLINUX_VERSION", "") + if manylinux_version is None + else manylinux_version + ), + ) + + +def host_arch(env: Mapping[str, str] | None = None) -> Arch: + """Architecture of the machine running the build. + + Deliberately distinct from the target: the QEMU-emulation decision + is host-versus-target, and collapsing the two breaks cross-builds. + """ + env = os.environ if env is None else env + uname = os.uname() if hasattr(os, "uname") else None + ambient_machine = env.get("PROCESSOR_ARCHITECTURE") or os.environ.get( + "PROCESSOR_ARCHITECTURE", "" + ) + machine = uname.machine if uname else ambient_machine + arch = _MACHINE_ALIASES.get(machine.lower()) + if arch is None: + raise ValueError(f"Unknown host machine {machine!r}") + return arch diff --git a/tests/test_target_platform.py b/tests/test_target_platform.py new file mode 100644 index 00000000..813cf4b5 --- /dev/null +++ b/tests/test_target_platform.py @@ -0,0 +1,135 @@ +import re +import tomllib +from pathlib import Path + +import pytest +from hypothesis import given +from hypothesis import strategies as st +from target_platform import ( + _MACOSX_DEPLOYMENT_TARGET, + Arch, + ContainerImage, + TargetPlatform, +) # noqa: F401 (import check) + +NICKNAMES = {"x64", "arm64", "amd64", "x86"} + +# (os_name, arch, manylinux) -> (pixi_suffix, linux_triple, +# cmake_osx_arch, wheel_plat_tag) +EXPECTED = { + ("linux", Arch.X86_64, "_2_28"): ( + "x64", + "x86_64-linux-gnu", + None, + "manylinux_2_28_x86_64", + ), + ("linux", Arch.AARCH64, "_2_28"): ( + "aarch64", + "aarch64-linux-gnu", + None, + "manylinux_2_28_aarch64", + ), + ("linux", Arch.X86_64, "_2_34"): ( + "x64", + "x86_64-linux-gnu", + None, + "manylinux_2_34_x86_64", + ), + ("darwin", Arch.AARCH64, ""): ( + "arm64", + None, + "arm64", + "macosx_14_0_arm64", + ), + ("windows", Arch.X86_64, ""): ( + "x64", + None, + None, + "win_amd64", + ), +} + + +@pytest.mark.parametrize("key", sorted(EXPECTED, key=repr)) +def test_translations(key): + os_name, arch, manylinux = key + pixi, triple, osx, tag = EXPECTED[key] + plat = TargetPlatform(os_name=os_name, arch=arch, manylinux_version=manylinux) + assert plat.pixi_suffix == pixi + assert plat.wheel_plat_tag == tag + assert plat.build_dir_suffix == pixi + if triple is not None: + assert plat.linux_triple == triple + if osx is not None: + assert plat.cmake_osx_arch == osx + + +def test_unknown_manylinux_version_raises(): + with pytest.raises(ValueError, match="Unknown manylinux version"): + TargetPlatform("linux", Arch.X86_64, "_2_99") + + +def test_unsupported_os_arch_combination_raises(): + with pytest.raises(ValueError, match="Unsupported combination"): + TargetPlatform("darwin", Arch.I686) + + +def test_manylinux_on_non_linux_raises(): + with pytest.raises(ValueError, match="only meaningful on linux"): + TargetPlatform("darwin", Arch.AARCH64, "_2_28") + + +def test_x86_64_uses_dockcross_with_hyphen(): + image = TargetPlatform("linux", Arch.X86_64, "_2_28").container_image + assert image == ContainerImage( + name="manylinux_2_28-x64:20250913-6ea98ba", + registry="docker.io/dockcross", + ) + assert image.reference == ( + "docker.io/dockcross/manylinux_2_28-x64:20250913-6ea98ba" + ) + + +def test_aarch64_uses_pypa_with_underscore(): + image = TargetPlatform("linux", Arch.AARCH64, "_2_28").container_image + assert image == ContainerImage( + name="manylinux_2_28_aarch64:2025.08.12-1", + registry="quay.io/pypa", + ) + assert image.reference == ("quay.io/pypa/manylinux_2_28_aarch64:2025.08.12-1") + + +@given(machine=st.text(max_size=20)) +def test_detect_never_yields_a_nickname(machine): + """detect() returns a canonical Arch or raises; never a nickname.""" + env = {"TARGET_ARCH": machine} + try: + plat = TargetPlatform.detect(env) + except ValueError: + return + assert isinstance(plat.arch, Arch) + assert plat.arch.value not in NICKNAMES + assert plat.arch.value == plat.arch.value.lower() + + +def test_macos_wheel_plat_tag_is_installable_on_arm64(): + tag = TargetPlatform("darwin", Arch.AARCH64, "").wheel_plat_tag + match = re.fullmatch(r"macosx_(\d+)_0_arm64", tag) + assert match is not None + assert int(match.group(1)) >= 11 + + +def test_pixi_macos_system_requirement_matches_the_deployment_target(): + """pixi.toml is the third copy of the macOS version; bind it too. + + ``system-requirements = { macos = "14.0" }`` under + ``[feature.macosx-build]`` must agree with + ``target_platform._MACOSX_DEPLOYMENT_TARGET``, or the cached ITK build + and the wheel tag disagree. + """ + pixi_toml = Path(__file__).resolve().parent.parent / "pixi.toml" + config = tomllib.loads(pixi_toml.read_text()) + macos_requirement = config["feature"]["macosx-build"]["system-requirements"][ + "macos" + ] + assert macos_requirement.replace(".", "_") == _MACOSX_DEPLOYMENT_TARGET From efde3c93f7fe0433a6f54d7abbaa8b47bac439d9 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 14:43:46 -0500 Subject: [PATCH 03/16] ENH: Add direct coverage for TargetPlatform.detect() OS matrix Co-Authored-By: Cavan Riley --- tests/test_target_platform.py | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/test_target_platform.py b/tests/test_target_platform.py index 813cf4b5..fa35557e 100644 --- a/tests/test_target_platform.py +++ b/tests/test_target_platform.py @@ -1,6 +1,8 @@ +import os import re import tomllib from pathlib import Path +from types import SimpleNamespace import pytest from hypothesis import given @@ -99,6 +101,52 @@ def test_aarch64_uses_pypa_with_underscore(): assert image.reference == ("quay.io/pypa/manylinux_2_28_aarch64:2025.08.12-1") +def _fake_uname(sysname, machine): + return SimpleNamespace(sysname=sysname, machine=machine) + + +def test_detect_resolves_linux_sysname(monkeypatch): + monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "x86_64")) + monkeypatch.setattr(os, "name", "posix") + plat = TargetPlatform.detect({}) + assert plat.os_name == "linux" + + +def test_detect_resolves_darwin_sysname(monkeypatch): + monkeypatch.setattr(os, "uname", lambda: _fake_uname("Darwin", "arm64")) + monkeypatch.setattr(os, "name", "posix") + plat = TargetPlatform.detect({}) + assert plat.os_name == "darwin" + + +def test_detect_resolves_windows_via_os_name_nt(monkeypatch): + monkeypatch.delattr(os, "uname", raising=False) + monkeypatch.setattr(os, "name", "nt") + plat = TargetPlatform.detect({"PROCESSOR_ARCHITECTURE": "AMD64"}) + assert plat.os_name == "windows" + + +def test_detect_unsupported_sysname_raises(monkeypatch): + monkeypatch.setattr(os, "uname", lambda: _fake_uname("PlayStation", "x86_64")) + monkeypatch.setattr(os, "name", "posix") + with pytest.raises(ValueError, match="Unsupported operating system"): + TargetPlatform.detect({}) + + +def test_detect_target_arch_env_overrides_host_machine(monkeypatch): + monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "aarch64")) + monkeypatch.setattr(os, "name", "posix") + plat = TargetPlatform.detect({"TARGET_ARCH": "x86_64"}) + assert plat.arch is Arch.X86_64 + + +def test_detect_falls_back_to_uname_machine_without_target_arch(monkeypatch): + monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "aarch64")) + monkeypatch.setattr(os, "name", "posix") + plat = TargetPlatform.detect({}) + assert plat.arch is Arch.AARCH64 + + @given(machine=st.text(max_size=20)) def test_detect_never_yields_a_nickname(machine): """detect() returns a canonical Arch or raises; never a nickname.""" From ef0c6bd379a4a3bad711806172ea728d0918459c Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 14:52:09 -0500 Subject: [PATCH 04/16] ENH: Add remote module dependency and ITK version pinning Co-Authored-By: Cavan Riley --- scripts/module_dependencies.py | 327 ++++++++++++++++++++++++++++++ tests/test_module_dependencies.py | 142 +++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 scripts/module_dependencies.py create mode 100644 tests/test_module_dependencies.py diff --git a/scripts/module_dependencies.py b/scripts/module_dependencies.py new file mode 100644 index 00000000..5ca264cf --- /dev/null +++ b/scripts/module_dependencies.py @@ -0,0 +1,327 @@ +import os +import re +import shutil +import tomllib +from pathlib import Path + + +def update_module_itk_deps(pyproject_path: Path, itk_version: str) -> bool: + """Rewrite ITK dependency pins in a remote module's pyproject.toml. + + Replaces hard-coded ITK sub-package version pins (e.g. + ``itk-io == 5.4.*``) with a pin matching the ITK version being + built against (e.g. ``itk-io >= 5.4``). This ensures that + wheels produced for ITK 6 can be installed alongside ITK 6 + packages without pip dependency conflicts. + + The rewrite is idempotent: applying it twice yields the same file + as applying it once. + + .. note:: Strategy 1 (build-time rewrite) — interim solution. + + This approach rewrites the module's pyproject.toml on disk, + builds the wheel, then restores the original. It works today + with zero changes to remote modules but is inherently fragile + (regex-based, modifies the source tree). + + **Plan to migrate to Strategy 3 (scikit-build-core dynamic + metadata provider):** + + 1. Create a small installable package ``itk-build-metadata`` + that implements the scikit-build-core dynamic metadata + provider interface (see scikit-build-core docs: + ``tool.scikit-build.metadata..provider``). + + 2. The provider inspects the build environment at wheel-build + time to discover the ITK version — either from the + ``ITK_PACKAGE_VERSION`` env var (set by this build system), + from ``ITKConfig.cmake`` on ``CMAKE_PREFIX_PATH``, or from + an already-installed ``itk-core`` package. + + 3. It emits the correct ``Requires-Dist`` entries (e.g. + ``itk-io >= 5.4``) into the wheel metadata without + touching ``pyproject.toml`` on disk at all. + + 4. Remote modules opt in by declaring dynamic dependencies:: + + [project] + dynamic = ["dependencies"] + + [tool.scikit-build.metadata.dependencies] + provider = "itk_build_metadata" + provider-path = "." # or from installed package + + 5. Roll out incrementally: update ITKModuleTemplate first, + then migrate existing modules via the ``/update-itk-deps`` + skill (in REMOTE_MODULES/.claude/skills/). Modules that + have not migrated continue to work via this Strategy 1 + fallback, so both approaches coexist during the transition. + + 6. Once all ~60 remote modules have adopted Strategy 3, + this method can be removed. + + Parameters + ---------- + pyproject_path : Path + Path to the module's ``pyproject.toml``. + itk_version : str + The ITK PEP 440 version string being built (e.g. ``6.0.0b2``). + Used to compute the minimum major version for the ``>=`` pin. + + Returns + ------- + bool + *True* if any dependency was rewritten. + """ + with open(pyproject_path, "rb") as f: + pyproject_data = tomllib.load(f) + + # --- Strategy 3: module declares dynamic dependencies ----------------- + dynamic_fields = pyproject_data.get("project", {}).get("dynamic", []) + if "dependencies" in dynamic_fields: + # The module has opted into dynamic dependency resolution. + # Set ITK_PACKAGE_VERSION in the environment so the + # scikit-build-core metadata provider (itk-build-metadata) + # can emit the correct Requires-Dist at build time. + os.environ["ITK_PACKAGE_VERSION"] = itk_version + print( + f"Strategy 3: {pyproject_path.name} declares " + f'dynamic=["dependencies"]; set ITK_PACKAGE_VERSION=' + f"{itk_version} for metadata provider" + ) + return False # no file modification needed + + # --- Strategy 1: build-time rewrite (fallback) ------------------------ + text = pyproject_path.read_text(encoding="utf-8") + + # Only rewrite ITK *base* sub-packages whose versions are tied to + # the ITK release. Remote module cross-deps (e.g. + # itk-meshtopolydata == 0.12.*) are versioned independently and + # must NOT be rewritten — flag them for manual review instead. + _ITK_BASE_PACKAGES = ( + "itk-core", + "itk-numerics", + "itk-io", + "itk-filtering", + "itk-registration", + "itk-segmentation", + # Most remote modules depend on the metapackage rather than on the + # sub-packages; its version tracks the ITK release just the same. + "itk", + ) + # Longest first, so "itk" cannot shadow "itk-core" in the alternation. + _base_pkg_alt = "|".join( + re.escape(p) for p in sorted(_ITK_BASE_PACKAGES, key=len, reverse=True) + ) + # Modules pin either "itk == 5.4.*" or "itk~=5.4.0"; rewrite both forms. + pattern = re.compile( + rf'"({_base_pkg_alt})\s*(?:==\s*\d+\.\d+\.\*|~=\s*\d+\.\d+(?:\.\d+)?)"' + ) + + # Warn about pinned remote-module cross-deps that may also need + # attention but should not be auto-rewritten. + cross_dep_pattern = re.compile( + r'"(itk-[a-z][a-z0-9-]*)\s*(?:==\s*\d+\.\d+\.\*|~=\s*\d+\.\d+(?:\.\d+)?)"' + ) + for m in cross_dep_pattern.finditer(text): + pkg = m.group(1) + if pkg not in _ITK_BASE_PACKAGES: + print( + f" WARNING: {pyproject_path.name} pins remote module " + f"cross-dep {m.group(0)} — review manually" + ) + + # The minimum version floor is the MAJOR.MINOR of the ITK version + # being built. A wheel compiled against ITK 6.0 requires ITK >= 6.0 + # at install time; one compiled against ITK 5.4 requires >= 5.4. + # The build system itself supports building against any ITK from + # v5.4 through the latest (v5.5, v6.0, v7.1, etc.) — the floor + # simply reflects which ITK the wheel was actually linked against. + # Tags carry a leading "v" and a pre-release or dev suffix + # (e.g. v6.0rc01.dev20260915), neither of which belongs in the floor: + # the wheel works with the whole 6.0 series, not one release candidate. + # The pin is also bounded above: the wheel is compiled against this ITK + # major series and its ABI, so the next major must not satisfy it. + # The floor carries an explicit ".0b1" pre-release suffix. PEP 440 sorts + # pre-releases *below* their final release, so ``6.0.0b2`` does not + # satisfy ``>= 6.0`` -- the whole 6.0 RC series would be excluded by its + # own modules, and ``pip install --pre`` cannot rescue it because the + # ordering, not pre-release visibility, is what fails. Pinning the floor + # at the earliest pre-release of the series admits 6.0.0b1, the RCs and + # the final 6.0.0 alike. It also makes --pre unnecessary: a specifier + # that names a pre-release permits pre-releases for that requirement. + version_match = re.match(r"v?(\d+)\.(\d+)", itk_version) + if version_match: + min_floor = f"{version_match.group(1)}.{version_match.group(2)}.0b1" + max_exclusive = str(int(version_match.group(1)) + 1) + else: + # Not a version (e.g. a branch name): any pin built from it would + # not be a valid specifier, so leave the module's pins alone. + print( + f" Leaving ITK pins in {pyproject_path.name} unchanged: " + f"{itk_version!r} is not a MAJOR.MINOR version" + ) + return False + + changed = False + + def _replace(m: re.Match) -> str: + nonlocal changed + changed = True + pkg = m.group(1) + if max_exclusive is None: + return f'"{pkg} >= {min_floor}"' + return f'"{pkg} >= {min_floor}, < {max_exclusive}"' + + new_text = pattern.sub(_replace, text) + if changed: + pyproject_path.write_text(new_text, encoding="utf-8") + print( + f"Strategy 1: Updated ITK dependency pins in {pyproject_path} " + f"(>= {min_floor} for ITK {itk_version})" + ) + return changed + + +def build_module_dependencies(context) -> None: + """ + Build prerequisite ITK external modules, mirroring the behavior of + the platform shell scripts that use the ITK_MODULE_PREQ environment. + + Accepted formats in context.itk_module_deps (colon-delimited): + - "MeshToPolyData@v0.10.0" -> defaults to + "InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0" + - "InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0" + + For each dependency, clone the repository, checkout the given tag, + build it against the ITK tree this build already uses, then copy + headers and wrapping input files into the current module tree + (include/ and wrapping/), similar to the bash implementations. + The cache is not downloaded or extracted again for a dependency. + """ + + if len(context.itk_module_deps) == 0: + return + print(f"Building module dependencies: {context.itk_module_deps}") + context.module_dependencies_root_dir.mkdir(parents=True, exist_ok=True) + + # Normalize entries to "Org/Repo@Tag" + def _normalize(entry: str) -> str: + entry = entry.strip() + if not entry: + return "" + if "/" in entry: + # Already Org/Repo@Tag + return entry + # Short form: Name@Tag -> InsightSoftwareConsortium/ITKName@Tag + try: + name, tag = entry.split("@", 1) + except ValueError: + # If no tag, pass-through (unexpected) + return entry + repo = f"ITK{name}" + return f"InsightSoftwareConsortium/{repo}@{tag}" + + # Ensure working directories exist + module_root = Path(context.module_source_dir).resolve() + include_dir = module_root / "include" + wrapping_dir = module_root / "wrapping" + include_dir.mkdir(parents=True, exist_ok=True) + wrapping_dir.mkdir(parents=True, exist_ok=True) + + dep_entries = [e for e in (s for s in context.itk_module_deps.split(":")) if e] + normalized = [_normalize(e) for e in dep_entries] + normalized = [e for e in normalized if e] + + # Build each dependency in order + for _current_entry, entry in enumerate(normalized): + if len(entry) == 0: + continue + print(f"Get dependency module information for {entry}") + org = entry.split("/", 1)[0] + repo_tag = entry.split("/", 1)[1] + repo = repo_tag.split("@", 1)[0] + tag = repo_tag.split("@", 1)[1] if "@" in repo_tag else "" + + upstream = f"https://github.com/{org}/{repo}.git" + dependant_module_clone_dir = ( + context.module_dependencies_root_dir / repo + if context.module_dependencies_root_dir + else module_root / repo + ) + if not dependant_module_clone_dir.exists(): + context.echo_check_call( + ["git", "clone", upstream, dependant_module_clone_dir], + check=True, + ) + + # Checkout requested tag + context.echo_check_call( + [ + "git", + "-C", + dependant_module_clone_dir, + "fetch", + "--all", + "--tags", + ], + check=True, + ) + if tag: + context.echo_check_call( + ["git", "-C", dependant_module_clone_dir, "checkout", tag], + check=True, + ) + + if (dependant_module_clone_dir / "setup.py").exists(): + msg: str = ( + f"Old sci-kit-build with setup.py is no longer supported for {dependant_module_clone_dir} at {tag}" + ) + raise RuntimeError(msg) + + # Clone the current build environment and modify for the current module + dependent_module_build_setup = context.clone() + dependent_module_build_setup.module_source_dir = Path( + dependant_module_clone_dir + ) + dependent_module_build_setup.itk_module_deps = None # Prevent recursion + dependent_module_build_setup.run() + + # After building dependency, copy includes and wrapping files + # 1) Top-level include/* -> include/ + dep_include = dependant_module_clone_dir / "include" + if dep_include.exists(): + for src in dep_include.rglob("*"): + if src.is_file(): + rel = src.relative_to(dep_include) + dst = include_dir / rel + dst.parent.mkdir(parents=True, exist_ok=True) + try: + shutil.copy2(src, dst) + except Exception: + pass + + # 2) Any */build/*/include/* -> include/ + for sub in dependant_module_clone_dir.rglob("*build*/**/include"): + if sub.is_dir(): + for src in sub.rglob("*"): + if src.is_file(): + rel = src.relative_to(sub) + dst = include_dir / rel + dst.parent.mkdir(parents=True, exist_ok=True) + try: + shutil.copy2(src, dst) + except Exception: + pass + + # 3) Wrapping templates (*.in, *.init) -> wrapping/ + dep_wrapping = dependant_module_clone_dir / "wrapping" + if dep_wrapping.exists(): + for pattern in ("*.in", "*.init"): + for src in dep_wrapping.rglob(pattern): + if src.is_file(): + dst = wrapping_dir / src.name + try: + shutil.copy2(src, dst) + except Exception: + pass diff --git a/tests/test_module_dependencies.py b/tests/test_module_dependencies.py new file mode 100644 index 00000000..3cd8114a --- /dev/null +++ b/tests/test_module_dependencies.py @@ -0,0 +1,142 @@ +import re +import tomllib +from pathlib import Path + +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st +from module_dependencies import build_module_dependencies, update_module_itk_deps + +METAPACKAGE = """\ +[project] +name = "itk-example" +dependencies = ["itk == 5.4.*"] +""" + +SUBPACKAGES = """\ +[project] +name = "itk-example" +dependencies = ["itk-core == 5.4.*", "itk-filtering ~= 5.4"] +""" + +NO_ITK = """\ +[project] +name = "unrelated" +dependencies = ["numpy >= 1.26"] +""" + + +def _write(tmp_path: Path, text: str) -> Path: + path = tmp_path / "pyproject.toml" + path.write_text(text) + return path + + +def test_metapackage_pin_is_bounded(tmp_path): + path = _write(tmp_path, METAPACKAGE) + assert update_module_itk_deps(path, "v6.0.0") is True + assert '"itk >= 6.0.0b1, < 7"' in path.read_text() + + +def test_subpackage_pins_both_syntaxes(tmp_path): + path = _write(tmp_path, SUBPACKAGES) + assert update_module_itk_deps(path, "v6.0.0") is True + text = path.read_text() + assert '"itk-core >= 6.0.0b1, < 7"' in text + assert '"itk-filtering >= 6.0.0b1, < 7"' in text + + +def test_no_itk_pins_leaves_file_byte_identical(tmp_path): + path = _write(tmp_path, NO_ITK) + before = path.read_bytes() + assert update_module_itk_deps(path, "v6.0.0") is False + assert path.read_bytes() == before + + +def test_unparseable_version_leaves_file_unchanged(tmp_path): + path = _write(tmp_path, METAPACKAGE) + before = path.read_bytes() + assert update_module_itk_deps(path, "not-a-version") is False + assert path.read_bytes() == before + + +VERSIONS = st.builds( + lambda major, minor, suffix: f"v{major}.{minor}{suffix}", + major=st.integers(min_value=1, max_value=99), + minor=st.integers(min_value=0, max_value=99), + suffix=st.sampled_from(["", ".0", ".7", "rc01", "rc02", ".dev20260915"]), +) + + +@given(version=VERSIONS) +# Deadline disabled: each example does real filesystem I/O. +@settings(deadline=None) +def test_bound_is_derived_from_major_minor_only(version, tmp_path_factory): + """Floor is >= X.Y.0b1 and ceiling < X+1. + + The floor's ``.0b1`` is a fixed series marker, not the tag's own + pre-release: this is the property that catches ``itk >= v6.0rc01``. + """ + path = _write(tmp_path_factory.mktemp("pin"), METAPACKAGE) + assert update_module_itk_deps(path, version) is True + major, minor = version.lstrip("v").split(".")[:2] + # Only the leading digit run of the minor component is significant; + # a suffix glued directly onto it (e.g. "0rc01") must not contribute + # digits of its own to the expected floor. + minor = re.match(r"\d*", minor).group() + expected = f'"itk >= {major}.{minor}.0b1, < {int(major) + 1}"' + assert expected in path.read_text() + + +@given(version=VERSIONS) +# Deadline disabled: each example does real filesystem I/O. +@settings(deadline=None) +def test_rewrite_is_idempotent(version, tmp_path_factory): + """Maintainer decision 2026-09-16: the rewriter is idempotent.""" + path = _write(tmp_path_factory.mktemp("idem"), METAPACKAGE) + update_module_itk_deps(path, version) + once = path.read_bytes() + update_module_itk_deps(path, version) + assert path.read_bytes() == once + + +@given(version=VERSIONS) +# Deadline disabled: each example does real filesystem I/O. +@settings(deadline=None) +def test_output_is_still_valid_toml(version, tmp_path_factory): + path = _write(tmp_path_factory.mktemp("toml"), SUBPACKAGES) + update_module_itk_deps(path, version) + tomllib.loads(path.read_text()) + + +class _DepContext: + """A build context carrying only what build_module_dependencies reads.""" + + def __init__(self, module_source_dir: Path, deps: str, failing: str): + self.module_source_dir = module_source_dir + self.module_dependencies_root_dir = module_source_dir / "deps" + self.itk_module_deps = deps + self._failing = failing + self.calls: list[list[str]] = [] + + def echo_check_call(self, cmd, check: bool = False, **kwargs) -> int: + cmd = [str(c) for c in cmd] + self.calls.append(cmd) + if self._failing in cmd: + if check: + raise RuntimeError(f"git {self._failing} failed") + return 1 + if cmd[1] == "clone": + Path(cmd[3]).mkdir(parents=True, exist_ok=True) + return 0 + + def clone(self): # pragma: no cover - not reached by these tests + raise AssertionError("dependency build must not start after a git failure") + + +@pytest.mark.parametrize("failing", ["clone", "fetch", "checkout"]) +def test_dependency_git_failures_stop_the_build(tmp_path, failing): + """A pinned dependency tag is a constraint, not a suggestion.""" + context = _DepContext(tmp_path, "InsightSoftwareConsortium/ITKFoo@v1.2.3", failing) + with pytest.raises(RuntimeError): + build_module_dependencies(context) From 8a7b8bbd6376268bce20359cedd1eba9ff183ab3 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 15:07:48 -0500 Subject: [PATCH 05/16] ENH: Add platform builder hierarchy and build environment Co-Authored-By: Cavan Riley --- BuildWheelsSupport/CMakeLists.txt | 7 + LICENSE => BuildWheelsSupport/LICENSE | 0 .../WHEEL_NAMES.txt | 0 SuperbuildSupport/CMakeLists.txt | 7 + scripts/BuildManager.py | 134 ++ scripts/build_environment.py | 177 +++ scripts/build_python_instance_base.py | 1087 +++++++++++++++++ scripts/cmake_argument_builder.py | 107 ++ scripts/linux_build_python_instance.py | 254 ++++ scripts/macos_build_python_instance.py | 96 ++ scripts/posix_build_python_instance.py | 18 + scripts/pyproject_configure.py | 516 +++++--- scripts/wheel_builder_utils.py | 710 +++++++++++ scripts/windows_build_python_instance.py | 249 ++++ tests/conftest.py | 15 + tests/test_build_environment.py | 69 ++ tests/test_build_steps.py | 83 ++ tests/test_platform_builders.py | 103 ++ tests/test_stable_abi.py | 39 + tests/test_tool_resolution_env.py | 106 ++ tests/test_wheel_builder_utils.py | 88 ++ tests/test_windows_wheel_builder.py | 117 ++ 22 files changed, 3844 insertions(+), 138 deletions(-) create mode 100644 BuildWheelsSupport/CMakeLists.txt rename LICENSE => BuildWheelsSupport/LICENSE (100%) rename {scripts => BuildWheelsSupport}/WHEEL_NAMES.txt (100%) create mode 100644 SuperbuildSupport/CMakeLists.txt create mode 100644 scripts/BuildManager.py create mode 100644 scripts/build_environment.py create mode 100644 scripts/build_python_instance_base.py create mode 100644 scripts/cmake_argument_builder.py create mode 100644 scripts/linux_build_python_instance.py create mode 100644 scripts/macos_build_python_instance.py create mode 100644 scripts/posix_build_python_instance.py create mode 100644 scripts/wheel_builder_utils.py create mode 100644 scripts/windows_build_python_instance.py create mode 100644 tests/conftest.py create mode 100644 tests/test_build_environment.py create mode 100644 tests/test_build_steps.py create mode 100644 tests/test_platform_builders.py create mode 100644 tests/test_stable_abi.py create mode 100644 tests/test_tool_resolution_env.py create mode 100644 tests/test_wheel_builder_utils.py create mode 100644 tests/test_windows_wheel_builder.py diff --git a/BuildWheelsSupport/CMakeLists.txt b/BuildWheelsSupport/CMakeLists.txt new file mode 100644 index 00000000..dbaa4a66 --- /dev/null +++ b/BuildWheelsSupport/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.26.6 FATAL_ERROR) +# NOTE: 3.26.6 is the first cmake version to support Development.SABIModule + +project(ITKPythonPackageWheels CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ITKPythonPackage_Utils.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ITKPythonPackage_BuildWheels.cmake) diff --git a/LICENSE b/BuildWheelsSupport/LICENSE similarity index 100% rename from LICENSE rename to BuildWheelsSupport/LICENSE diff --git a/scripts/WHEEL_NAMES.txt b/BuildWheelsSupport/WHEEL_NAMES.txt similarity index 100% rename from scripts/WHEEL_NAMES.txt rename to BuildWheelsSupport/WHEEL_NAMES.txt diff --git a/SuperbuildSupport/CMakeLists.txt b/SuperbuildSupport/CMakeLists.txt new file mode 100644 index 00000000..25311ae9 --- /dev/null +++ b/SuperbuildSupport/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.26.6 FATAL_ERROR) +# NOTE: 3.26.6 is the first cmake version to support Development.SABIModule + +project(ITKPythonPackageSuperbuild CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ITKPythonPackage_Utils.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ITKPythonPackage_SuperBuild.cmake) diff --git a/scripts/BuildManager.py b/scripts/BuildManager.py new file mode 100644 index 00000000..a7fd9942 --- /dev/null +++ b/scripts/BuildManager.py @@ -0,0 +1,134 @@ +import json +import time +from datetime import datetime +from pathlib import Path + + +class BuildManager: + """Manage a JSON build report for multistep runs. + + Persists status and timing for each named step to a JSON file. + Steps already marked ``"done"`` are skipped on subsequent runs, + enabling resumable builds. + + Parameters + ---------- + report_path : Path + Path to the JSON report file (created if absent). + step_names : list[str] + Ordered names of build steps to track. + + Attributes + ---------- + report : dict + In-memory report structure with ``created_at``, ``updated_at``, + and ``steps`` keys. + """ + + def __init__(self, report_path: Path, step_names: list[str]): + self.report_path = Path(report_path) + self._init_structure(step_names) + self._load_if_exists() + + # ---- Public API ---- + def run_step(self, step_name: str, func, force_rerun=False) -> None: + """Execute a build step, recording timing and status. + + Parameters + ---------- + step_name : str + Key identifying the step in the report. + func : callable + Zero-argument callable that performs the step's work. + force_rerun : bool, optional + If True, re-execute even when the step is already ``"done"``. + + Raises + ------ + Exception + Re-raises any exception thrown by *func* after recording + the failure in the report. + """ + entry = self.report["steps"].setdefault(step_name, self._new_step_entry()) + if entry.get("status") == "done" and not force_rerun: + # Already completed in a previous run; skip + return + + # Mark start + entry["status"] = "running" + entry["started_at"] = self._now() + self.report["updated_at"] = entry["started_at"] + self.save() + + start = time.perf_counter() + try: + func() + except Exception as e: + # Record failure and re-raise + entry["status"] = "failed" + entry["finished_at"] = self._now() + entry["duration_sec"] = round(time.perf_counter() - start, 3) + entry["error"] = f"{type(e).__name__}: {e}" + self.report["updated_at"] = entry["finished_at"] + self.save() + raise + else: + # Record success + entry["status"] = "done" + entry["finished_at"] = self._now() + entry["duration_sec"] = round(time.perf_counter() - start, 3) + self.report["updated_at"] = entry["finished_at"] + self.save() + + def save(self) -> None: + """Write the current report to disk atomically.""" + self.report_path.parent.mkdir(parents=True, exist_ok=True) + tmp = self.report_path.with_suffix(self.report_path.suffix + ".tmp") + with open(tmp, "w", encoding="utf-8") as f: + json.dump(self.report, f, indent=2, sort_keys=True) + tmp.replace(self.report_path) + + # ---- Internal helpers ---- + def _init_structure(self, step_names: list[str]) -> None: + steps = {name: self._new_step_entry() for name in step_names} + now = self._now() + self.report = { + "created_at": now, + "updated_at": now, + "steps": steps, + } + + def _load_if_exists(self) -> None: + if not self.report_path.exists(): + return + try: + with open(self.report_path, encoding="utf-8") as f: + existing = json.load(f) + # Merge existing with current set of steps, preserving statuses + existing_steps = existing.get("steps", {}) + for name in self.report["steps"].keys(): + if name in existing_steps: + self.report["steps"][name] = existing_steps[name] + # Bring over timestamps + self.report["created_at"] = existing.get( + "created_at", self.report["created_at"] + ) + self.report["updated_at"] = existing.get( + "updated_at", self.report["updated_at"] + ) + except Exception as e: + # Corrupt or unreadable file; keep freshly initialized structure + raise RuntimeError(f"Failed to load build report: {e}") from e + + @staticmethod + def _now() -> str: + return datetime.now().isoformat(timespec="seconds") + + @staticmethod + def _new_step_entry() -> dict: + return { + "status": "pending", + "started_at": None, + "finished_at": None, + "duration_sec": None, + } diff --git a/scripts/build_environment.py b/scripts/build_environment.py new file mode 100644 index 00000000..afb89c81 --- /dev/null +++ b/scripts/build_environment.py @@ -0,0 +1,177 @@ +"""Build-environment plumbing shared by every platform builder. + +These are the pieces of the build that talk to the surrounding machine +rather than to a particular platform: running a command inside a pixi +environment, resolving interpreter paths in a Unix virtualenv, and +naming the pixi environment itself. They are module-level functions so +they can be exercised without constructing a builder. +""" + +import os +import subprocess +import sys +from pathlib import Path + +from wheel_builder_utils import run_commandLine_subprocess + + +def get_pixi_environment_name(platform_env: str) -> str: + """Return the pixi environment name for a platform environment. + + The pixi environment name is the same as the platform_env and is + related to the environment setups defined in pixi.toml in the root + of this git directory that contains these scripts. + """ + return platform_env + + +def echo_check_call( + cmd: list[str | Path] | tuple[str | Path] | str | Path, + pixi_env_name: str = "", + pixi_executable: str | Path | None = None, + run_dir: Path | None = None, + use_pixi_env: bool = True, + env=None, + **kwargs: dict, +) -> int: + """Print the command, then run it and return its exit status. + + Parameters + ---------- + cmd : + Command to execute. + pixi_env_name : + Pixi environment to run inside; empty disables the preamble. + pixi_executable : + Path to the pixi executable, required only when a preamble is built. + run_dir : + Working directory, also the root of the ``.pixi`` home. + **kwargs : + Additional keyword arguments forwarded to the subprocess runner. + + Returns + ------- + int + The command's return code. Note that a non-zero status is + *returned*, not raised, unless ``check=True`` is forwarded. + """ + + pixi_run_preamble: list[str] = [] + pixi_env: dict[str, str] = os.environ.copy() + if env is not None: + pixi_env.update(env) + if run_dir is not None: + pixi_env.update( + { + "PIXI_HOME": str(Path(run_dir) / ".pixi"), + } + ) + + # When this process is already running inside the requested environment, + # a further ``pixi run`` re-prepends that environment's directories to an + # already-activated PATH. On Windows each activation adds ~2.4 kB, and the + # second one overflows the cmd.exe 8191-character line limit, failing with + # "The input line is too long." Running in-process is equivalent here. + already_in_pixi_env = os.environ.get("PIXI_ENVIRONMENT_NAME") == pixi_env_name + if pixi_env_name and use_pixi_env and not already_in_pixi_env: + pixi_run_preamble = [ + str(pixi_executable), + "run", + "-e", + pixi_env_name, + "--", + ] + + # convert all items to strings (i.e. Path() to str) + cmd = pixi_run_preamble + [str(c) for c in cmd] + # Prepare a friendly command-line string for display + try: + if isinstance(cmd, list | tuple): + display_cmd = " ".join(cmd) + else: + display_cmd = str(cmd) + except Exception as e: + display_cmd = f"{str(cmd)}\nERROR: {e}" + sys.exit(1) + print(f">>Start Running: cd {run_dir} && {display_cmd}") + print("^" * 60) + print(cmd) + print("^" * 60) + print(kwargs) + print("^" * 60) + process_completion_info: subprocess.CompletedProcess = run_commandLine_subprocess( + cmd, env=pixi_env, cwd=run_dir, **kwargs + ) + cmd_return_status: int = process_completion_info.returncode + print("^" * 60) + print(f"< tuple[str, str, str, str, str]: + """Resolve Python interpreter and virtualenv paths on Unix. + + Parameters + ---------- + venv_dir : Path + Root of the Python virtual environment. + + Returns + ------- + tuple[str, str, str, str, str] + ``(python_executable, python_include_dir, python_library, + venv_bin_path, venv_base_dir)``. + + Raises + ------ + FileNotFoundError + If the Python executable does not exist under *venv_dir*. + """ + python_executable = venv_dir / "bin" / "python" + if not python_executable.exists(): + raise FileNotFoundError(f"Python executable not found: {python_executable}") + + # Compute Python include dir using sysconfig for the given interpreter + try: + python_include_dir = ( + subprocess.check_output( + [ + str(python_executable), + "-c", + "import sysconfig; print(sysconfig.get_paths()['include'])", + ], + text=True, + ).strip() + or "" + ) + except Exception as e: + print(f"Failed to compute Python include dir: {e}\n defaulting to empty") + python_include_dir = "" + + # modern CMake with Python3 can infer the library from executable; leave empty + python_library = "" + + # Update PATH + venv_bin_path = venv_dir / "bin" + return ( + str(python_executable), + str(python_include_dir), + str(python_library), + str(venv_bin_path), + str(venv_dir), + ) diff --git a/scripts/build_python_instance_base.py b/scripts/build_python_instance_base.py new file mode 100644 index 00000000..4a3b8498 --- /dev/null +++ b/scripts/build_python_instance_base.py @@ -0,0 +1,1087 @@ +import copy +import os +import shutil +import subprocess +from abc import ABC, abstractmethod +from collections import OrderedDict +from collections.abc import Callable +from os import environ +from pathlib import Path + +import build_environment +from BuildManager import BuildManager +from cmake_argument_builder import CMakeArgumentBuilder +from module_dependencies import build_module_dependencies, update_module_itk_deps +from pyproject_configure import STABLE_ABI_TAG, configure_one_pyproject_file +from target_platform import TargetPlatform +from wheel_builder_utils import ( + _remove_tree, + _which, + get_default_platform_build, +) + + +def verify_stable_abi_wheels(out_dir: Path, py_api: str) -> list[Path]: + """Return the wheels in *out_dir*, refusing any not tagged ``-abi3``. + + Raises + ------ + RuntimeError + If no wheel was produced, or any wheel carries a different tag. + """ + built_wheels = sorted(out_dir.glob("*.whl")) + if not built_wheels: + raise RuntimeError(f"No wheel was produced in {out_dir}") + mistagged = [w.name for w in built_wheels if f"{py_api}-abi3" not in w.name] + if mistagged: + raise RuntimeError( + f"Expected {py_api}-abi3 wheels, got: {', '.join(mistagged)}" + ) + return built_wheels + + +class BuildPythonInstanceBase(ABC): + """Abstract base class to build wheels for a single Python environment. + + Concrete subclasses implement platform-specific details (environment + setup, wheel fixup, tarball creation) while this class provides the + shared build orchestration, CMake configuration, and wheel-building + logic. + + Parameters + ---------- + platform_env : str + Platform/environment identifier (e.g. ``'manylinux228-py311'``). + build_dir_root : Path + Root directory for all build artifacts. + package_env_config : dict + Mutable configuration dictionary populated throughout the build. + cleanup : bool + Whether to remove intermediate build artifacts. + build_itk_tarball_cache : bool + Whether to create a reusable tarball of the ITK build tree. + cmake_options : list[str] + Extra ``-D`` options forwarded to CMake. + windows_extra_lib_paths : list[str] + Additional library paths for Windows wheel fixup (delvewheel). + dist_dir : Path + Output directory for built wheel files. + module_source_dir : Path, optional + Path to an external ITK remote module to build. + module_dependencies_root_dir : Path, optional + Directory where remote module dependencies are cloned. + itk_module_deps : str, optional + Colon-delimited dependency specifications for remote modules. + skip_itk_build : bool, optional + Skip the ITK C++ build step. + skip_itk_wheel_build : bool, optional + Skip the ITK wheel build step. + """ + + def __init__( + self, + *, + platform_env, + build_dir_root, + package_env_config: dict, + cleanup: bool, + build_itk_tarball_cache: bool, + cmake_options: list[str], + windows_extra_lib_paths: list[str], + dist_dir: Path, + module_source_dir: Path | None = None, + module_dependencies_root_dir: Path | None = None, + itk_module_deps: str | None = None, + skip_itk_build: bool | None = None, + skip_itk_wheel_build: bool | None = None, + ) -> None: + self.build_node_cpu_count: int = os.cpu_count() or 1 + self.platform_env = platform_env + self.ipp_dir = Path(__file__).parent.parent + + self.build_dir_root = build_dir_root + self.cmake_itk_source_build_configurations: CMakeArgumentBuilder = ( + CMakeArgumentBuilder() + ) + self.cmake_compiler_configurations: CMakeArgumentBuilder = ( + CMakeArgumentBuilder() + ) + # TODO: Partial refactoring cleanup later + package_env_config["IPP_SOURCE_DIR"] = self.ipp_dir + IPP_BuildWheelsSupport_DIR: Path = self.ipp_dir / "BuildWheelsSupport" + package_env_config["IPP_BuildWheelsSupport_DIR"] = IPP_BuildWheelsSupport_DIR + + self.package_env_config = package_env_config + self.target: TargetPlatform = TargetPlatform.detect(self.package_env_config) + + # declare this dict before self.prepare_build_env() or dict will be empty in later functions + self.venv_info_dict = { + # Filled in for each platform and each pyenvs + # "python_executable": None, + # "python_include_dir": None, + # "python_library": None, + # "venv_bin_path": None, + # "venv_base_dir": None, + } + + with open( + IPP_BuildWheelsSupport_DIR / "WHEEL_NAMES.txt", + encoding="utf-8", + ) as content: + self.wheel_names = [ + wheel_name.strip() for wheel_name in content.readlines() + ] + del package_env_config + + self.cleanup = cleanup + self.build_itk_tarball_cache = build_itk_tarball_cache + self.cmake_options = cmake_options + self.windows_extra_lib_paths = windows_extra_lib_paths + self.dist_dir = dist_dir + # Needed for processing remote modules and their dependencies + self.module_source_dir: Path = ( + Path(module_source_dir) if module_source_dir else None + ) + self.module_dependencies_root_dir: Path = ( + Path(module_dependencies_root_dir) if module_dependencies_root_dir else None + ) + self.itk_module_deps = itk_module_deps + self.skip_itk_build = skip_itk_build + self.skip_itk_wheel_build = skip_itk_wheel_build + self.prepare_build_env() + + self.package_env_config["BUILD_TYPE"] = "Release" + # Unified place to collect cmake -D definitions for this instance + self.cmake_cmdline_definitions: CMakeArgumentBuilder = CMakeArgumentBuilder() + # Seed from legacy cmake_options if provided as ['-D=', ...] + if cmake_options: + for opt in cmake_options: + if not opt.startswith("-D"): + continue + # Strip leading -D, split on first '=' into key and value + try: + key, value = opt[2:].split("=", 1) + except ValueError: + # Malformed option; skip to avoid breaking build + continue + # Preserve value verbatim (may contain quotes) + self.cmake_cmdline_definitions.set(key, value) + + self.cmake_compiler_configurations.update( + { + "CMAKE_BUILD_TYPE:STRING": self.package_env_config["BUILD_TYPE"], + } + ) + # Set cmake flags for the compiler if CC or CXX are specified + cxx_compiler: str = self.package_env_config.get("CXX", "") + if cxx_compiler != "": + self.cmake_compiler_configurations.set( + "CMAKE_CXX_COMPILER:STRING", cxx_compiler + ) + + c_compiler: str = self.package_env_config.get("CC", "") + if c_compiler != "": + self.cmake_compiler_configurations.set( + "CMAKE_C_COMPILER:STRING", c_compiler + ) + + if self.package_env_config.get("USE_CCACHE", "OFF") == "ON": + ccache_exe: Path = _which("ccache") + self.cmake_compiler_configurations.set( + "CMAKE_C_COMPILER_LAUNCHER:FILEPATH", f"{ccache_exe}" + ) + self.cmake_compiler_configurations.set( + "CMAKE_CXX_COMPILER_LAUNCHER:FILEPATH", f"{ccache_exe}" + ) + + self.cmake_itk_source_build_configurations.update( + # ITK wrapping options + { + "ITK_SOURCE_DIR:PATH": f"{self.package_env_config['ITK_SOURCE_DIR']}", + "BUILD_TESTING:BOOL": "OFF", + "ITK_WRAP_unsigned_short:BOOL": "ON", + "ITK_WRAP_double:BOOL": "ON", + "ITK_WRAP_complex_double:BOOL": "ON", + "ITK_WRAP_IMAGE_DIMS:STRING": "2;3;4", + "WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING": "PythonWheel", + "WRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL": "ON", + "PY_SITE_PACKAGES_PATH:PATH": ".", + "ITK_LEGACY_SILENT:BOOL": "ON", + "ITK_DEFAULT_THREADER:STRING": "Pool", + "ITK_WRAP_PYTHON:BOOL": "ON", + "ITK_WRAP_DOC:BOOL": "ON", + "DOXYGEN_EXECUTABLE:FILEPATH": f"{self.package_env_config['DOXYGEN_EXECUTABLE']}", + "Module_ITKTBB:BOOL": self.package_env_config["USE_TBB"], + "TBB_DIR:PATH": self.package_env_config["TBB_DIR"], + # Python settings + "SKBUILD:BOOL": "ON", + } + ) + + def update_venv_itk_build_configurations(self) -> None: + """Set ``Python3_ROOT_DIR`` in ITK build configurations from venv info.""" + # Python3_EXECUTABLE, Python3_INCLUDE_DIR, and Python3_LIBRARY are validated + # and resolved by find_package(Python3) in cmake/ITKPythonPackage_SuperBuild.cmake + # when not already defined. Python3_ROOT_DIR is set here to guide that search. + self.cmake_itk_source_build_configurations.set( + "Python3_ROOT_DIR:PATH", f"{self.venv_info_dict['python_root_dir']}" + ) + + def build_step_table(self) -> OrderedDict[str, Callable]: + """The ordered build steps for this run, keyed by recorded step name. + + Stages that do not apply are registered under a ``_skipped`` key + mapped to a no-op, so the step log records that they were bypassed + on purpose rather than silently absent. + """ + python_package_build_steps: OrderedDict[str, Callable] = OrderedDict( + { + "01_superbuild_support_components": self.build_superbuild_support_components, + "02_build_wrapped_itk_cplusplus": self.build_wrapped_itk_cplusplus, + "03_build_wheels": self.build_itk_python_wheels, + "04_post_build_fixup": self.post_build_fixup, + "05_final_import_test": self.final_import_test, + } + ) + + if self.skip_itk_build: + # Skip these steps if we are in the CI environment + python_package_build_steps = OrderedDict( + ( + ("02_build_wrapped_itk_cplusplus_skipped", (lambda: None)) + if k == "02_build_wrapped_itk_cplusplus" + else (k, v) + ) + for k, v in python_package_build_steps.items() + ) + if self.skip_itk_wheel_build: + # Steps 04 and 05 act on the ITK wheels that step 03 produces: + # 04 repairs itk_core and 05 pip-installs from dist/ and imports + # the result. When 03 is skipped -- the remote-module path, which + # takes ITK from a cache that deliberately excludes wheels -- + # there is nothing for either to act on. Left registered, 05 + # fails with "Could not find a version that satisfies the + # requirement itk (from versions: none)" and the module wheel + # in step 06 is never built. It only *looked* like it worked on a + # machine whose dist/ still held wheels from an earlier ITK build. + skipped_when_no_itk_wheels = { + "03_build_wheels": "03_build_wheels_skipped", + "04_post_build_fixup": "04_post_build_fixup_skipped", + "05_final_import_test": "05_final_import_test_skipped", + } + python_package_build_steps = OrderedDict( + ( + (skipped_when_no_itk_wheels[k], (lambda: None)) + if k in skipped_when_no_itk_wheels + else (k, v) + ) + for k, v in python_package_build_steps.items() + ) + + if self.module_source_dir is not None: + python_package_build_steps[ + f"06_build_external_module_wheel_{self.module_source_dir.name}" + ] = self.build_external_module_python_wheel + else: + python_package_build_steps["06_build_external_module_wheel_skipped"] = ( + lambda: None + ) + if self.build_itk_tarball_cache: + python_package_build_steps[ + f"07_build_itk_tarball_cache_{self.package_env_config['OS_NAME']}_{self.package_env_config['ARCH']}" + ] = self.build_tarball + if self.cleanup and not self.build_itk_tarball_cache: + # Cleanup globs dist/ITKPythonBuilds-*.tar*, so it is incompatible + # with the step that publishes that cache. + python_package_build_steps["08_post_build_cleanup"] = ( + self.post_build_cleanup + ) + return python_package_build_steps + + def run(self) -> None: + """Run the full build flow for this Python instance.""" + # Use BuildManager to persist and resume build steps + + # HACK + if self.itk_module_deps: + build_module_dependencies(self) + + python_package_build_steps = self.build_step_table() + + self.dist_dir.mkdir(parents=True, exist_ok=True) + build_report_fn: Path = self.dist_dir / f"build_log_{self.platform_env}.json" + build_manager: BuildManager = BuildManager( + build_report_fn, list(python_package_build_steps.keys()) + ) + build_manager.save() + for build_step_name, build_step_func in python_package_build_steps.items(): + print("=" * 80) + print( + f"Running build step: {build_step_name}: recording status in {build_report_fn}" + ) + # always force_rerun of the tarball step if requested + build_manager.run_step( + build_step_name, + build_step_func, + force_rerun=("tarball_cache" in build_step_name), + ) + build_manager.save() + print( + f"Build step {build_step_name} completed. Edit {build_report_fn} to rerun step." + ) + print("=" * 80) + + def build_superbuild_support_components(self): + """Configure and build the superbuild support components (ITK source, TBB).""" + # ----------------------------------------------------------------------- + # Build required components (optional local ITK source, TBB builds) used to populate the archive cache + + # Build up definitions using the builder + cmake_superbuild_argumets = CMakeArgumentBuilder() + if self.cmake_compiler_configurations: + cmake_superbuild_argumets.update(self.cmake_compiler_configurations.items()) + # Add superbuild-specific flags + cmake_superbuild_argumets.update( + { + "ITKPythonPackage_BUILD_PYTHON:BOOL": "OFF", + "ITKPythonPackage_USE_TBB:BOOL": self.package_env_config["USE_TBB"], + "ITK_SOURCE_DIR:PATH": f"{self.package_env_config['ITK_SOURCE_DIR']}", + "ITK_GIT_TAG:STRING": f"{self.package_env_config['ITK_GIT_TAG']}", + } + ) + # Start from any platform/user-provided defaults + if self.cmake_cmdline_definitions: + cmake_superbuild_argumets.update(self.cmake_cmdline_definitions.items()) + + cmd = [ + self.package_env_config["CMAKE_EXECUTABLE"], + "-G", + "Ninja", + ] + + cmd += cmake_superbuild_argumets.getCMakeCommandLineArguments() + + cmd += [ + "-S", + str(self.package_env_config["IPP_SOURCE_DIR"] / "SuperbuildSupport"), + "-B", + str(self.package_env_config["IPP_SUPERBUILD_BINARY_DIR"]), + ] + + self.echo_check_call(cmd, check=True) + self.echo_check_call( + [ + self.package_env_config["CMAKE_EXECUTABLE"], + "--build", + # "--load-average", + # str(self.build_node_cpu_count), + # "--parallel", + # str(self.build_node_cpu_count), + str(self.package_env_config["IPP_SUPERBUILD_BINARY_DIR"]), + ], + ) + + def fixup_wheels(self, lib_paths: str = ""): + """Apply platform-specific fixups to ``itk_core`` wheels for TBB linkage.""" + # TBB library fix-up (applies to itk_core wheel) + tbb_wheel = "itk_core" + for wheel in (self.build_dir_root / "dist").glob(f"{tbb_wheel}*.whl"): + self.fixup_wheel(str(wheel), lib_paths) + + def final_wheel_import_test(self, installed_dist_dir: Path): + """Install and smoke-test all ITK wheels from *installed_dist_dir*. + + Parameters + ---------- + installed_dist_dir : Path + Directory containing the built ``.whl`` files to install and + verify. + """ + self._pip_uninstall_itk_wildcard(self.package_env_config["PYTHON_EXECUTABLE"]) + checks = [ + ( + "pip install itk", + [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-m", + "pip", + "install", + "itk", + "--no-cache-dir", + "--no-index", + "-f", + str(installed_dist_dir), + ], + ), + ( + "import itk", + [self.package_env_config["PYTHON_EXECUTABLE"], "-c", "import itk;"], + ), + ( + "instantiate itk.Image", + [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-c", + "import itk; image = itk.Image[itk.UC, 2].New()", + ], + ), + ( + "import itk with lazy loading disabled", + [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-c", + "import itkConfig; itkConfig.LazyLoading=False; import itk;", + ], + ), + ( + "documentation tests", + [ + self.package_env_config["PYTHON_EXECUTABLE"], + str( + self.package_env_config["IPP_SOURCE_DIR"] + / "docs" + / "code" + / "test.py" + ), + ], + ), + ] + for description, cmd in checks: + exit_status = self.echo_check_call(cmd) + if exit_status != 0: + raise RuntimeError( + f"final_wheel_import_test failed at step {description!r} " + f"(exit status {exit_status})" + ) + print("Documentation tests passed.") + + def _pip_uninstall_itk_wildcard(self, python_executable: str | Path): + """Uninstall all installed packages whose name starts with 'itk'. + + pip does not support shell-style wildcards directly for uninstall, so we: + - run 'pip list --format=freeze' + - collect package names whose normalized name starts with 'itk' + - call 'pip uninstall -y ' if any are found + """ + python_executable = str(python_executable) + try: + proc = subprocess.run( + [python_executable, "-m", "pip", "list", "--format=freeze"], + check=True, + capture_output=True, + text=True, + ) + except subprocess.CalledProcessError as e: + print( + f"Warning: failed to list packages with pip at {python_executable}: {e}" + ) + return + + packages = [] + for line in proc.stdout.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + # Formats like 'name==version' or 'name @ URL' + name = line.split("==")[0].split(" @ ")[0].strip() + if name.lower().startswith("itk"): + packages.append(name) + + if packages: + print(f"Uninstalling existing ITK-related packages: {' '.join(packages)}") + exit_status = self.echo_check_call( + [python_executable, "-m", "pip", "uninstall", "-y", *packages] + ) + if exit_status != 0: + raise RuntimeError( + f"Failed to uninstall existing ITK packages {packages!r} " + f"(exit status {exit_status})" + ) + + def find_unix_exectable_paths( + self, + venv_dir: Path, + ) -> tuple[str, str, str, str, str]: + """Delegate to the module-level implementation.""" + return build_environment.find_unix_exectable_paths(venv_dir) + + def clone(self): + """Return a deep copy of this instance for building a dependency. + + Uses ``self.__class__`` so the returned object is the same concrete + subclass as the original (e.g. ``LinuxBuildPythonInstance``). + """ + cls = self.__class__ + new = cls.__new__(cls) + new.__dict__ = copy.deepcopy(self.__dict__) + return new + + def venv_paths(self) -> None: + """Populate ``venv_info_dict`` from the pixi-managed Python interpreter. + + Default Unix implementation shared by Linux and macOS. Windows + overrides this method with its own path conventions. + """ + primary_python_base_dir = Path( + self.package_env_config["PYTHON_EXECUTABLE"] + ).parent.parent + ( + python_executable, + python_include_dir, + python_library, + venv_bin_path, + venv_base_dir, + ) = self.find_unix_exectable_paths(primary_python_base_dir) + self.venv_info_dict = { + "python_executable": python_executable, + "python_include_dir": python_include_dir, + "python_library": python_library, + "venv_bin_path": venv_bin_path, + "venv_base_dir": venv_base_dir, + "python_root_dir": primary_python_base_dir, + } + + @abstractmethod + def fixup_wheel( + self, filepath, lib_paths: str = "", remote_module_wheel: bool = False + ): # pragma: no cover - abstract + """Apply platform-specific wheel repairs (auditwheel, delocate, delvewheel). + + Parameters + ---------- + filepath : str + Path to the ``.whl`` file to fix. + lib_paths : str, optional + Additional library search paths (semicolon-delimited on Windows). + remote_module_wheel : bool, optional + True when fixing a wheel for an external remote module. + """ + pass + + @abstractmethod + def build_tarball(self): + """Create a compressed archive of the ITK build tree for caching.""" + pass + + def post_build_cleanup(self) -> None: + """Remove intermediate build artifacts, leaving ``dist/`` intact. + + Actions: + - remove oneTBB-prefix (symlink or dir) + - remove ITKPythonPackage/, tools/, _skbuild/, build/ + - remove top-level *.egg-info + - remove ITK-* build tree and tarballs + - if ITK_MODULE_PREQ is set, remove cloned module dirs + """ + base = Path(self.package_env_config["IPP_SOURCE_DIR"]) + + def rm(tree_path: Path): + try: + _remove_tree(tree_path) + except Exception: + pass + + # 1) unlink oneTBB-prefix if it's a symlink or file + tbb_prefix_dir = base / "oneTBB-prefix" + try: + if tbb_prefix_dir.is_symlink() or tbb_prefix_dir.is_file(): + tbb_prefix_dir.unlink(missing_ok=True) # type: ignore[arg-type] + elif tbb_prefix_dir.exists(): + rm(tbb_prefix_dir) + except Exception: + pass + + # 2) standard build directories + for rel in ("ITKPythonPackage", "tools", "_skbuild", "build"): + rm(base / rel) + + # 3) egg-info folders at top-level + for p in base.glob("*.egg-info"): + rm(p) + + # 4) ITK build tree and tarballs, both under the build root rather than + # the source checkout: ITK--_ in build/, + # and the cache in dist/. + target_arch = self.package_env_config["ARCH"] + for p in (self.build_dir_root / "build").glob(f"ITK-*_{target_arch}"): + rm(p) + + # Tarballs + for p in (self.build_dir_root / "dist").glob("ITKPythonBuilds-*.tar*"): + rm(p) + + # 5) Optional module prerequisites cleanup (ITK_MODULE_PREQ) + # Format: "InsightSoftwareConsortium/ITKModuleA@v1.0:Kitware/ITKModuleB@sha" + itk_preq = self.package_env_config.get("ITK_MODULE_PREQ") or environ.get( + "ITK_MODULE_PREQ", "" + ) + if itk_preq: + for entry in itk_preq.split(":"): + entry = entry.strip() + if not entry: + continue + try: + module_name = entry.split("@", 1)[0].split("/", 1)[1] + except Exception: + continue + rm(base / module_name) + + def prepare_build_env(self) -> None: + """Skeleton shared by every platform. + + Subclasses vary only in ``_configure_toolchain``. + """ + self.venv_paths() + self.update_venv_itk_build_configurations() + self._configure_toolchain() + + build_root = self.build_dir_root / "build" + downloaded = build_root / ( + f"ITK-{self.platform_env}-{self.platform_env}" + f"_{self.target.build_dir_suffix}" + ) + if downloaded.exists(): + itk_binary_build_name = downloaded + else: + itk_binary_build_name = build_root / ( + f"ITK-{self.platform_env}-{self.get_pixi_environment_name()}" + f"_{self.target.build_dir_suffix}" + ) + self.cmake_itk_source_build_configurations.set( + "ITK_BINARY_DIR:PATH", itk_binary_build_name.as_posix() + ) + + def _configure_toolchain(self) -> None: + """Set platform-specific TBB, compiler, and deployment values.""" + raise NotImplementedError + + @abstractmethod + def post_build_fixup(self) -> None: # pragma: no cover - abstract + """Run platform-specific post-build wheel fixups. + + Called after all wheels are built but before the final import + test. Typically invokes ``fixup_wheel`` or ``fixup_wheels``. + """ + pass + + def final_import_test(self) -> None: # pragma: no cover + """Install and smoke-test the built wheels.""" + self.final_wheel_import_test(installed_dist_dir=self.dist_dir) + + @abstractmethod + def discover_python_venvs( + self, platform_os_name: str, platform_architechure: str + ) -> list[str]: + """Return available Python environment names for the given platform. + + Parameters + ---------- + platform_os_name : str + Operating system identifier. + platform_architechure : str + CPU architecture identifier. + + Returns + ------- + list[str] + Sorted list of discovered environment names. + """ + pass + + def build_external_module_python_wheel(self): + """Build a wheel for an external ITK remote module via scikit-build-core.""" + self.module_source_dir = Path(self.module_source_dir) + out_dir = self.module_source_dir / "dist" + out_dir.mkdir(parents=True, exist_ok=True) + + # Dynamically update ITK dependency pins to match the version being built. + # Back up the original pyproject.toml so the working tree is restored + # after the wheel is produced. + module_pyproject = self.module_source_dir / "pyproject.toml" + pyproject_orig = module_pyproject.with_suffix(".toml.orig") + pyproject_whl = module_pyproject.with_suffix(".toml.whl") + deps_rewritten = False + if module_pyproject.is_file(): + itk_ver = self.package_env_config.get("ITK_PACKAGE_VERSION", "") + if itk_ver: + shutil.copy2(module_pyproject, pyproject_orig) + deps_rewritten = update_module_itk_deps(module_pyproject, itk_ver) + + # Ensure venv tools are first in PATH + py_exe = str(self.package_env_config["PYTHON_EXECUTABLE"]) # Python3_EXECUTABLE + + # Compute Python include directory (Python3_INCLUDE_DIR) + py_include = self.venv_info_dict.get("python_include_dir", "") + if not py_include: + try: + py_include = ( + subprocess.check_output( + [ + py_exe, + "-c", + "import sysconfig; print(sysconfig.get_paths()['include'])", + ], + text=True, + ).strip() + or "" + ) + except Exception: + py_include = "" + + # Determine platform-specific settings (macOS) + config_settings: dict[str, str] = {} + + # ITK build path for external modules: prefer configured ITK binary dir + itk_build_path = self.cmake_itk_source_build_configurations.get( + "ITK_BINARY_DIR:PATH", + "", + ) + + wheel_py_api = STABLE_ABI_TAG + + # Base build command + cmd = [ + py_exe, + "-m", + "build", + "--verbose", + "--wheel", + "--outdir", + str(out_dir), + "--no-isolation", + "--skip-dependency-check", + f"--config-setting=cmake.build-type={self.package_env_config['BUILD_TYPE']}", + ] + + if self.target.os_name == "windows": + # pixi's conda-forge vs2022 activation exports + # CMAKE_GENERATOR="Visual Studio 17 2022", which scikit-build-core + # honours. That multi-config generator makes ITK's wrapping + # file(GENERATE) calls collide: "Evaluation file to be written + # multiple times with different content" on *.castxml.inc. ITK + # itself is configured with -G Ninja, so match it here. Passing -G + # explicitly also makes CMake ignore the ambient + # CMAKE_GENERATOR_PLATFORM/TOOLSET, which Ninja would reject. + cmd.append("--config-setting=cmake.args=-GNinja") + + # Collect scikit-build CMake definitions + defs = CMakeArgumentBuilder() + defs.update(self.cmake_compiler_configurations.items()) + # Propagate macOS specific defines if any were set above + for k, v in config_settings.items(): + defs.set(k, v) + + # Required defines for external module build + if itk_build_path: + defs.set("ITK_DIR:PATH", str(itk_build_path)) + defs.set("CMAKE_INSTALL_LIBDIR:STRING", "lib") + defs.set("WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING", "PythonWheel") + defs.set("PY_SITE_PACKAGES_PATH:PATH", ".") + defs.set("BUILD_TESTING:BOOL", "OFF") + defs.set("Python3_EXECUTABLE:FILEPATH", py_exe) + if py_include: + defs.set("Python3_INCLUDE_DIR:PATH", py_include) + + # Pass Python library paths when explicitly known (Windows) + py_library = self.venv_info_dict.get("python_library", "") + if py_library: + defs.set("Python3_LIBRARY:FILEPATH", str(py_library)) + py_sabi_library = self.venv_info_dict.get("python_sabi_library", "") + if py_sabi_library: + defs.set("Python3_SABI_LIBRARY:FILEPATH", str(py_sabi_library)) + + # Allow command-line cmake -D overrides to win last + if self.cmake_cmdline_definitions: + defs.update(self.cmake_cmdline_definitions.items()) + + # Append all cmake.define entries to the build cmd + cmd += defs.getPythonBuildCommandLineArguments() + + # Stable ABI setting if applicable + if wheel_py_api: + cmd += [f"--config-setting=wheel.py-api={wheel_py_api}"] + + # Module source directory to build + cmd += [self.module_source_dir] + + try: + self.echo_check_call(cmd, check=True) + + built_wheels = verify_stable_abi_wheels(out_dir, wheel_py_api) + + # Post-process produced wheels (e.g., delocate on macOS x86_64) + for wheel in built_wheels: + self.fixup_wheel(str(wheel), remote_module_wheel=True) + finally: + # Restore original pyproject.toml so the working tree stays clean + if deps_rewritten and pyproject_orig.is_file(): + shutil.copy2(module_pyproject, pyproject_whl) + shutil.move(str(pyproject_orig), str(module_pyproject)) + print( + f"Restored {module_pyproject} " + f"(modified version saved as {pyproject_whl.name})" + ) + + def build_itk_python_wheels(self): + """Build all ITK Python wheels listed in ``WHEEL_NAMES.txt``.""" + # Build wheels + for wheel_name in self.wheel_names: + print("#") + print(f"# Build ITK wheel {wheel_name} from {self.wheel_names}") + print("#") + # Configure pyproject.toml + wheel_configbuild_dir_root: Path = ( + self.build_dir_root + / "wheelbuilds" + / f"{wheel_name}_{self.get_pixi_environment_name()}" + ) + wheel_configbuild_dir_root.mkdir(parents=True, exist_ok=True) + configure_one_pyproject_file( + str(self.ipp_dir / "scripts"), + self.package_env_config, + wheel_configbuild_dir_root, + wheel_name, + ) + + # Generate wheel using + cmd = [ + str(self.package_env_config["PYTHON_EXECUTABLE"]), + "-m", + "build", + "--verbose", + "--wheel", + "--outdir", + str(self.build_dir_root / "dist"), + "--no-isolation", + "--skip-dependency-check", + f"--config-setting=cmake.build-type={self.package_env_config['BUILD_TYPE']}", + f"--config-setting=cmake.source-dir={self.package_env_config['IPP_SOURCE_DIR'] / 'BuildWheelsSupport'}", + f"--config-setting=build-dir={wheel_configbuild_dir_root/'build'}", + ] + # Build scikit-build defines via builder + scikitbuild_cmdline_args = CMakeArgumentBuilder() + scikitbuild_cmdline_args.update(self.cmake_compiler_configurations.items()) + scikitbuild_cmdline_args.update( + self.cmake_itk_source_build_configurations.items() + ) + scikitbuild_cmdline_args.update( + { + "ITKPythonPackage_USE_TBB:BOOL": self.package_env_config["USE_TBB"], + "ITKPythonPackage_ITK_BINARY_REUSE:BOOL": "ON", + "ITKPythonPackage_WHEEL_NAME:STRING": f"{wheel_name}", + "DOXYGEN_EXECUTABLE:FILEPATH": f"{self.package_env_config['DOXYGEN_EXECUTABLE']}", + } + ) + + if ( + self.cmake_cmdline_definitions + ): # Do last to override with command line items + scikitbuild_cmdline_args.update(self.cmake_cmdline_definitions.items()) + # Append all cmake.define entries + cmd += scikitbuild_cmdline_args.getPythonBuildCommandLineArguments() + # The location of the generated pyproject.toml file + cmd += [wheel_configbuild_dir_root] + self.echo_check_call(cmd, check=True) + + # Remove unnecessary files for building against ITK + if self.cleanup: + bp = Path( + self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"] + ) + for p in bp.rglob("*"): + if p.is_file() and p.suffix in [".cpp", ".xml", ".obj", ".o"]: + try: + p.unlink() + except OSError: + pass + _remove_tree(bp / "Wrapping" / "Generators" / "CastXML") + + def build_wrapped_itk_cplusplus(self): + """Configure and build the ITK C++ libraries with Python wrapping.""" + # Clean up previous invocations + if ( + self.cleanup + and Path( + self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"] + ).exists() + ): + _remove_tree( + Path(self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"]) + ) + + print("#") + print("# START-Build ITK C++") + print("#") + + # Build ITK python + cmd = [ + self.package_env_config["CMAKE_EXECUTABLE"], + "-G", + "Ninja", + ] + # Collect all -D definitions via builder + defs = CMakeArgumentBuilder() + defs.update(self.cmake_compiler_configurations.items()) + defs.update(self.cmake_itk_source_build_configurations.items()) + # NOTE Do cmake_cmdline_definitions last so they override internal defaults + defs.update(self.cmake_cmdline_definitions.items()) + cmd += defs.getCMakeCommandLineArguments() + cmd += [ + "-S", + self.package_env_config["ITK_SOURCE_DIR"], + "-B", + self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"], + ] + self.echo_check_call(cmd, check=True) + self.echo_check_call( + [ + self.package_env_config["NINJA_EXECUTABLE"], + f"-j{self.build_node_cpu_count}", + f"-l{self.build_node_cpu_count}", + "-C", + self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"], + ], + check=True, + ) + print("# FINISHED-Build ITK C++") + + def create_posix_tarball(self): + """Create a compressed tarball of the ITK Python build tree. + + Mirrors the historical scripts/*-build-tarball.sh behavior: + - zstd compress with options (-10 -T6 --long=31) + + Warns if directory structure doesn't match expected layout for GitHub Actions. + """ + arch_postfix: str = f"{self.package_env_config['ARCH']}" + # Fixup platform name for macOS, eventually need to standardize on macosx naming convention + platform_name: str = get_default_platform_build().split("-")[0] + tar_name: str = f"ITKPythonBuilds-{platform_name}-{arch_postfix}.tar" + itk_packaging_reference_dir = self.build_dir_root.parent + + # Write the cache under the build tree: container builds bind-mount that + # directory, while the archive root above it is not always mounted. dist/ + # is excluded from the archive below, so the cache cannot capture itself. + tarball_output_dir: Path = self.build_dir_root / "dist" + tarball_output_dir.mkdir(parents=True, exist_ok=True) + tar_path: Path = tarball_output_dir / tar_name + zst_path: Path = tarball_output_dir / f"{tar_name}.zst" + + itk_resources_build_dir: Path = self.build_dir_root + ipp_source_dir: Path = self.package_env_config["IPP_SOURCE_DIR"] + + # Validate directory structure and determine tarball strategy + issues = [] + + # Try to use relative paths first + try: + rel_build = itk_resources_build_dir.relative_to(itk_packaging_reference_dir) + rel_ipp = ipp_source_dir.relative_to(itk_packaging_reference_dir) + except ValueError: + # Fall back to absolute paths + rel_build = itk_resources_build_dir + rel_ipp = ipp_source_dir + itk_packaging_reference_dir = Path( + "/" + ) # Tar from root when using absolute paths + + if itk_resources_build_dir.parent != ipp_source_dir.parent: + issues.append("Build and source dirs are not siblings") + + if ipp_source_dir.name != "ITKPythonPackage": + issues.append( + f"Source dir is '{ipp_source_dir.name}', expected 'ITKPythonPackage'" + ) + + # Issue consolidated warning for compatibility issues + if issues: + print("\n" + "=" * 70) + print("WARNING: Tarball will NOT be compatible with GitHub Actions") + print("=" * 70) + for issue in issues: + print(f" * {issue}") + print( + "\nExpected structure: /{ITKPythonPackage, ITKPythonPackage-build}" + ) + print(f"Current: Build={itk_resources_build_dir}") + print(f" Source={ipp_source_dir}") + print( + "\nTarball will be created for local reuse but may not work in CI/CD." + ) + print("=" * 70 + "\n") + + # Build tarball include paths + tarball_include_paths = [ + str(rel_build), + str(rel_ipp), + ] + + if tar_path.exists(): + print(f"Removing existing tarball {tar_path}") + tar_path.unlink() + if zst_path.exists(): + print(f"Removing existing zstd tarball {zst_path}") + zst_path.unlink() + + # Create tarball + self.echo_check_call( + [ + "tar", + "-C", + str(itk_packaging_reference_dir), + "-cf", + str(tar_path), + "--exclude=*.o", + "--exclude=*.whl", # Do not include built wheels + "--exclude=*/dist/*", # Do not include the dist whl output directory + "--exclude=*/wheelbuilds/*", # Do not include the wheelbuild support directory + "--exclude=*/__pycache__/*", # Do not include __pycache__ + "--exclude=install_manifest_*.txt", # Do not include install manifest files + "--exclude=._*", # Exclude mac dot files + "--exclude=*/.git/*", + "--exclude=*/.idea/*", + "--exclude=*/.pixi/*", + "--exclude=*/castxml_inputs/*", + "--exclude=*/Wrapping/Modules/*", + *tarball_include_paths, + ] + ) + + # Compress with zstd + self.echo_check_call( + [ + "zstd", + "-f", + "-10", + "-T6", + "--long=31", + str(tar_path), + "-o", + str(zst_path), + ] + ) + + # The uncompressed archive is several GB; only the compressed cache is published. + tar_path.unlink(missing_ok=True) + + print(f"Tarball created: {zst_path}") + if issues: + print("Compatibility warnings above - review before using in CI/CD") + + def get_pixi_environment_name(self): + """Delegate, naming the pixi environment for this build.""" + return build_environment.get_pixi_environment_name(self.platform_env) + + def echo_check_call( + self, cmd, use_pixi_env: bool = True, env=None, **kwargs + ) -> int: + """Delegate, supplying this build's pixi environment name.""" + return build_environment.echo_check_call( + cmd, + pixi_env_name=self.get_pixi_environment_name(), + pixi_executable=self.package_env_config["PIXI_EXECUTABLE"], + run_dir=self.ipp_dir, + use_pixi_env=use_pixi_env, + env=env, + **kwargs, + ) diff --git a/scripts/cmake_argument_builder.py b/scripts/cmake_argument_builder.py new file mode 100644 index 00000000..22aee6d2 --- /dev/null +++ b/scripts/cmake_argument_builder.py @@ -0,0 +1,107 @@ +from collections.abc import Iterable, Iterator, Mapping + + +def drop_quotes(s: str) -> str: + """Strip surrounding double-quote characters from *s*.""" + return str(s).strip('"') + + +class CMakeArgumentBuilder: + """Manage CMake-style key/value definitions and render them as CLI args. + + Keys should include any CMake type suffix (e.g. + ``'CMAKE_BUILD_TYPE:STRING'``). Values are rendered verbatim. + + Parameters + ---------- + initial : Mapping[str, str], optional + Initial set of definitions to populate the builder. + + Examples + -------- + >>> flags = { + ... 'CMAKE_BUILD_TYPE:STRING': 'Release', + ... 'CMAKE_OSX_ARCHITECTURES:STRING': 'arm64', + ... } + >>> builder = CMakeArgumentBuilder(flags) + >>> builder.getCMakeCommandLineArguments() + ["-DCMAKE_BUILD_TYPE:STRING='Release'", "-DCMAKE_OSX_ARCHITECTURES:STRING='arm64'"] + >>> builder.getPythonBuildCommandLineArguments() + ['--config-setting=cmake.define.CMAKE_BUILD_TYPE:STRING=Release', + '--config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=arm64'] + """ + + def __init__(self, initial: Mapping[str, str] | None = None) -> None: + # dict preserves insertion order; keep user's order when possible + self._defs: dict[str, str] = dict(initial) if initial else {} + + # Basic mapping helpers (optional convenience) + def set(self, key: str, value: str) -> None: + """Set or replace a definition. + + Parameters + ---------- + key : str + CMake variable name, optionally with a type suffix + (e.g. ``'CMAKE_BUILD_TYPE:STRING'``). + value : str + Value for the definition. + """ + self._defs[key] = value + + def get(self, key: str, default: str | None = None) -> str | None: + """Return the value for *key*, or *default* if absent.""" + return self._defs.get(key, default) + + def update(self, other: Mapping[str, str] | Iterable[tuple[str, str]]) -> None: + """Merge definitions from *other* into this builder. + + Parameters + ---------- + other : Mapping[str, str] or Iterable[tuple[str, str]] + Definitions to merge. Existing keys are overwritten. + """ + if isinstance(other, Mapping): + self._defs.update(other) + else: + for k, v in other: + self._defs[k] = v + + def __contains__(self, key: str) -> bool: # pragma: no cover - trivial + return key in self._defs + + def __getitem__(self, key: str) -> str: # pragma: no cover - trivial + return self._defs[key] + + def __iter__(self) -> Iterator[str]: # pragma: no cover - trivial + return iter(self._defs) + + def items(self) -> Iterable[tuple[str, str]]: # pragma: no cover - trivial + """Return an iterable of ``(key, value)`` definition pairs.""" + return self._defs.items() + + # Renderers + def getCMakeCommandLineArguments(self) -> list[str]: + """Render definitions as CMake ``-D`` arguments. + + Returns + ------- + list[str] + A list like ``["-D=''", ...]``. + """ + return [f"""-D{k}='{drop_quotes(v)}'""" for k, v in self._defs.items()] + + def getPythonBuildCommandLineArguments(self) -> list[str]: + """Render definitions as scikit-build-core ``--config-setting`` arguments. + + Returns + ------- + list[str] + A list like + ``["--config-setting=cmake.define.=''", ...]``. + """ + prefix = "--config-setting=cmake.define." + return [f"""{prefix}{k}='{drop_quotes(v)}'""" for k, v in self._defs.items()] + + +__all__ = ["CMakeArgumentBuilder"] diff --git a/scripts/linux_build_python_instance.py b/scripts/linux_build_python_instance.py new file mode 100644 index 00000000..4eeed7cd --- /dev/null +++ b/scripts/linux_build_python_instance.py @@ -0,0 +1,254 @@ +import os +import shutil +from pathlib import Path + +from posix_build_python_instance import PosixBuildPythonInstance +from wheel_builder_utils import ( + _remove_tree, +) + + +class LinuxBuildPythonInstance(PosixBuildPythonInstance): + """Linux-specific wheel builder. + + Handles manylinux container builds, ``auditwheel`` wheel repair, and + Linux-specific compiler/target triple configuration. + """ + + def _configure_toolchain(self) -> None: + self.package_env_config["USE_TBB"] = "ON" + self.package_env_config["TBB_DIR"] = str( + self.build_dir_root / "build" / "oneTBB-prefix" / "lib" / "cmake" / "TBB" + ) + self.cmake_compiler_configurations.set( + "CMAKE_CXX_COMPILER_TARGET:STRING", self.target.linux_triple + ) + + def post_build_fixup(self) -> None: + """Repair wheels with ``auditwheel`` and retag the ITK meta-wheel.""" + manylinux_ver: str | None = self.package_env_config.get( + "MANYLINUX_VERSION", None + ) + if manylinux_ver: + # Repair all produced wheels with auditwheel for packages with so elements (starts with itk_) + whl = None + # cp39-cp39-linux itk_segmentation-6.0.0b2-cp39-cp39-linux_x86_64.whl + # Extract Python version from platform_env + if "-" in self.platform_env: + # in manylinux case, platform env is manylinux-cp311, for example, don't want anything before '-' + py_version = self.platform_env.split("-")[-1] + else: + py_version = self.platform_env + cp_prefix: str = py_version.replace("py", "cp").replace(".", "") + binary_wheel_glob_pattern: str = f"itk_*-*{cp_prefix}*-linux_*.whl" + dist_path: Path = self.build_dir_root / "dist" + for whl in dist_path.glob(binary_wheel_glob_pattern): + if whl.name.startswith("itk-"): + print( + f"Skipping the itk-meta wheel that has nothing to fixup {whl}" + ) + continue + self.fixup_wheel(str(whl)) + del whl + # Retag meta-wheel: Special handling for the itk meta wheel to adjust tag + # auditwheel does not process this "metawheel" correctly since it does not + # have any native SO's. + meta_wheel_glob_pattern: str = f"itk-*-*{cp_prefix}*-linux_*.whl" + for metawhl in dist_path.glob(meta_wheel_glob_pattern): + # Unpack, edit WHEEL tag, repack + metawheel_dir = self.build_dir_root / "metawheel" + metawheel_dir.mkdir(parents=True, exist_ok=True) + metawheel_dist = self.build_dir_root / "metawheel-dist" + metawheel_dist.mkdir(parents=True, exist_ok=True) + self.echo_check_call( + [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-m", + "wheel", + "unpack", + "--dest", + str(metawheel_dir), + str(metawhl), + ], + check=True, + ) + # Find unpacked dir + unpacked_dirs = list(metawheel_dir.glob("itk-*/itk*.dist-info/WHEEL")) + for wheel_file in unpacked_dirs: + content = wheel_file.read_text(encoding="utf-8").splitlines() + base = metawhl.name + if len(manylinux_ver) > 0: + base = metawhl.name.replace( + "linux", f"manylinux{manylinux_ver}" + ) + # Wheel filename: {name}-{version}-{python}-{abi}-{platform}.whl + # Tag must be only "{python}-{abi}-{platform}", not the full stem. + stem = Path(base).stem + parts = stem.split("-") + tag = "-".join(parts[-3:]) + new = [] + for line in content: + if line.startswith("Tag: "): + new.append(f"Tag: {tag}") + else: + new.append(line) + wheel_file.write_text("\n".join(new) + "\n", encoding="utf-8") + for fixed_dir in metawheel_dir.glob("itk-*"): + self.echo_check_call( + [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-m", + "wheel", + "pack", + "--dest", + str(metawheel_dist), + str(fixed_dir), + ], + check=True, + ) + # Move and clean + repacked = list(metawheel_dist.glob("*.whl")) + if not repacked: + raise RuntimeError( + f"Repacking {metawhl.name} produced no wheel in " + f"{metawheel_dist}; refusing to delete the original" + ) + for new_whl in repacked: + shutil.move( + str(new_whl), + str((self.build_dir_root / "dist") / new_whl.name), + ) + # Remove old and temp + try: + metawhl.unlink() + except OSError: + pass + _remove_tree(metawheel_dir) + _remove_tree(metawheel_dist) + + def _build_env_lib_dir(self) -> Path: + """Return the build environment's ``lib`` directory. + + Derived from ``PYTHON_EXECUTABLE`` (``/bin/python``) so that + it does not depend on ``CONDA_PREFIX`` being exported. + """ + python_executable = self.package_env_config.get("PYTHON_EXECUTABLE", "") + if not python_executable: + raise RuntimeError( + "PYTHON_EXECUTABLE is unset; cannot locate the build " + "environment library directory for auditwheel" + ) + lib_dir = Path(python_executable).resolve().parent.parent / "lib" + if not lib_dir.is_dir(): + raise RuntimeError( + f"Build environment library directory {lib_dir} does not exist " + f"(derived from PYTHON_EXECUTABLE={python_executable})" + ) + return lib_dir + + def fixup_wheel( + self, filepath, lib_paths: str = "", remote_module_wheel: bool = False + ) -> None: + """Repair a wheel with ``auditwheel`` and apply manylinux platform tags. + + Parameters + ---------- + filepath : str + Path to the ``.whl`` file. + lib_paths : str, optional + Unused on Linux (kept for interface compatibility). + remote_module_wheel : bool, optional + If True, output repaired wheel to the remote module's ``dist/`` + directory instead of the main build ``dist/``. + """ + # Use auditwheel to repair wheels and set manylinux tags + manylinux_ver = self.package_env_config.get("MANYLINUX_VERSION", "") + if len(manylinux_ver) > 1: + plat = None + if self.package_env_config["ARCH"] == "x64" and manylinux_ver: + plat = f"manylinux{manylinux_ver}_x86_64" + cmd = [ + self.package_env_config["PYTHON_EXECUTABLE"], + "-m", + "auditwheel", + "repair", + ] + if plat: + cmd += ["--plat", plat] + cmd += [ + str(filepath), + "-w", + ( + str(self.module_source_dir / "dist") + if remote_module_wheel + else str(self.build_dir_root / "dist") + ), + ] + # Provide LD_LIBRARY_PATH for oneTBB and common system paths + extra_lib = str( + self.package_env_config["IPP_SUPERBUILD_BINARY_DIR"].parent + / "oneTBB-prefix" + / "lib" + ) + env = dict(self.package_env_config) + env["LD_LIBRARY_PATH"] = ":".join( + entry + for entry in [ + str(self._build_env_lib_dir()), + env.get("LD_LIBRARY_PATH", ""), + extra_lib, + "/usr/lib64", + "/usr/lib", + ] + if entry + ) + print(f'RUNNING WITH PATH {os.environ["PATH"]}') + env["PATH"] = os.environ["PATH"] + repair_status = self.echo_check_call(cmd, env=env) + + filepath_obj = Path(filepath) + output_dir = ( + self.module_source_dir / "dist" + if remote_module_wheel + else self.build_dir_root / "dist" + ) + stem_prefix = "-".join(filepath_obj.stem.split("-")[:-1]) + repaired_wheel = ( + output_dir / f"{stem_prefix}-{self.target.wheel_plat_tag}.whl" + ) + + if repair_status != 0 or not repaired_wheel.exists(): + raise RuntimeError( + f"auditwheel repair failed for {filepath_obj.name} " + f"(exit status {repair_status}, expected output " + f"{repaired_wheel.name})" + ) + + # Remove the original linux_*.whl now that the repair is verified. + if ( + filepath_obj.exists() + and "-linux_" in filepath_obj.name + and filepath_obj.suffix == ".whl" + ): + print( + f"Removing original linux wheel after repair: {filepath_obj.name}" + ) + try: + _remove_tree(filepath_obj) + except OSError as e: + print(f"Warning: Could not remove {filepath_obj.name}: {e}") + else: + print( + "Building outside of manylinux environment does not require wheel fixups." + ) + return + + def discover_python_venvs( + self, platform_os_name: str, platform_architecture: str + ) -> list[str]: + """Extend the POSIX discovery with manylinux ``/opt/python`` installs.""" + names = super().discover_python_venvs(platform_os_name, platform_architecture) + opt_python = Path("/opt/python") + if opt_python.exists(): + names.extend(p.name for p in opt_python.iterdir() if p.is_dir()) + return sorted(names) diff --git a/scripts/macos_build_python_instance.py b/scripts/macos_build_python_instance.py new file mode 100644 index 00000000..d08af802 --- /dev/null +++ b/scripts/macos_build_python_instance.py @@ -0,0 +1,96 @@ +import os +import zipfile +from pathlib import Path + +from posix_build_python_instance import PosixBuildPythonInstance + + +class MacOSBuildPythonInstance(PosixBuildPythonInstance): + """macOS-specific wheel builder. + + Handles macOS deployment target and architecture settings, and repairs + every wheel with ``delocate`` on both x86_64 and arm64. + """ + + def _configure_toolchain(self) -> None: + self.package_env_config["USE_TBB"] = "OFF" + self.package_env_config["TBB_DIR"] = "NOT_FOUND" + macosx_target = self.package_env_config.get("MACOSX_DEPLOYMENT_TARGET", "") + if macosx_target: + self.cmake_compiler_configurations.set( + "CMAKE_OSX_DEPLOYMENT_TARGET:STRING", macosx_target + ) + # scikit-build-core reads this, not the CMake variable. + os.environ["MACOSX_DEPLOYMENT_TARGET"] = macosx_target + # sysconfig reports the interpreter's own build target and ignores + # MACOSX_DEPLOYMENT_TARGET, so without this the wheel tag would not + # match the compiled binaries. + os.environ["_PYTHON_HOST_PLATFORM"] = ( + f"macosx-{macosx_target}-{self.target.cmake_osx_arch}" + ) + self.cmake_compiler_configurations.set( + "CMAKE_OSX_ARCHITECTURES:STRING", self.target.cmake_osx_arch + ) + + def post_build_fixup(self) -> None: + """Run ``delocate`` on every wheel to bundle shared libraries.""" + # Every ITK wheel links libc++, not only itk_core as the base-class fixup assumes. + for wheel in (self.build_dir_root / "dist").glob("*.whl"): + self.fixup_wheel(str(wheel)) + + def fixup_wheel( + self, filepath, lib_paths: str = "", remote_module_wheel: bool = False + ) -> None: + """Repair a wheel using ``delocate``, cleaning AppleDouble files first. + + Parameters + ---------- + filepath : str + Path to the ``.whl`` file. + lib_paths : str, optional + Unused on macOS (kept for interface compatibility). + remote_module_wheel : bool, optional + Unused on macOS (kept for interface compatibility). + """ + self.remove_apple_double_files() + # delocate --require-archs errors on a wheel with no compiled binaries (e.g. the itk metapackage). + with zipfile.ZipFile(filepath) as wheel_zip: + has_binaries = any( + name.endswith((".so", ".dylib")) for name in wheel_zip.namelist() + ) + if not has_binaries: + print(f"Skipping delocate for {Path(filepath).name}: no compiled binaries") + return + + # arm64 needs this too: the pixi toolchain links @rpath/libc++ that only exists on the build host. + venv_bin_path = self.venv_info_dict.get("venv_bin_path", None) + if venv_bin_path: + delocate_listdeps = f"{venv_bin_path}/delocate-listdeps" + delocate_wheel = f"{venv_bin_path}/delocate-wheel" + self.echo_check_call([str(delocate_listdeps), str(filepath)]) + self.echo_check_call( + [ + str(delocate_wheel), + "--require-archs", + self.package_env_config["ARCH"], + str(filepath), + ], + check=True, + ) + else: + print( + "=" * 20 + + "WARNING: Could not find venv binary to delocate wheel" + + "=" * 20 + ) + + def remove_apple_double_files(self): + """Remove AppleDouble ``._*`` files using ``dot_clean`` if available.""" + try: + # Optional: clean AppleDouble files if tool is available + self.echo_check_call( + ["dot_clean", str(self.package_env_config["IPP_SOURCE_DIR"])] + ) + except Exception: + # dot_clean may not be available; continue without it + pass diff --git a/scripts/posix_build_python_instance.py b/scripts/posix_build_python_instance.py new file mode 100644 index 00000000..5d3407db --- /dev/null +++ b/scripts/posix_build_python_instance.py @@ -0,0 +1,18 @@ +from build_python_instance_base import BuildPythonInstanceBase + + +class PosixBuildPythonInstance(BuildPythonInstanceBase): + """Behavior shared by the Linux and macOS builders.""" + + def build_tarball(self): + """Create a zstd-compressed tarball of the ITK build tree.""" + self.create_posix_tarball() + + def discover_python_venvs( + self, platform_os_name: str, platform_architecture: str + ) -> list[str]: + """Return environment names found under the project venvs dir.""" + venvs_dir = self.build_dir_root / "venvs" + if not venvs_dir.exists(): + return [] + return sorted(p.name for p in venvs_dir.iterdir() if p.is_dir()) diff --git a/scripts/pyproject_configure.py b/scripts/pyproject_configure.py index 55ba9c2c..de8561a5 100755 --- a/scripts/pyproject_configure.py +++ b/scripts/pyproject_configure.py @@ -1,55 +1,52 @@ #!/usr/bin/env python - -"""CLI allowing to configure ``pyproject.toml`` found in ``ITKPythonPackage`` -source tree. - -Different version of ``pyproject.toml`` can be generated based on the value -of the `wheel_name` positional parameter. - -Usage:: - - pyproject_configure.py [-h] [--output-dir OUTPUT_DIR] wheel_name - - positional arguments: - wheel_name - - optional arguments: - -h, --help show this help message and exit - --output-dir OUTPUT_DIR - Output directory for configured 'pyproject.toml' - (default: /work) - - -Accepted values for `wheel_name` are ``itk`` and all values read from -``WHEEL_NAMES.txt``. -""" - import argparse import os import re +import shutil import sys +from pathlib import Path -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -PARAMETER_OPTION_DEFAULTS = { - "indent": 0, - "newline_if_set": False, - "newline_indent": 0, - "remove_line_if_empty": False, -} +from packaging.version import Version +from wheel_builder_utils import read_env_file -PARAMETER_OPTIONS = { - "PYPROJECT_PY_MODULES": {"indent": 8, "newline_if_set": True, "newline_indent": 4}, - "PYPROJECT_DEPENDENCIES": {"indent": 8, "remove_line_if_empty": True}, -} +# One cp311-abi3 wheel per platform, installable on 3.11 and newer. Pinned +# rather than derived from the interpreter running this driver. +STABLE_ABI_TAG = "cp311" def parameter_option(key, option): - """Return value of `option` associated with parameter `key`. - - If no option is found in `PARAMETER_OPTIONS`, default value from - `PARAMETER_OPTION_DEFAULTS` is returned. + """Return a formatting option for a template parameter. + + Parameters + ---------- + key : str + Template parameter name (e.g. ``'PYPROJECT_PY_MODULES'``). + option : str + Option to look up (``'indent'``, ``'newline_if_set'``, + ``'newline_indent'``, or ``'remove_line_if_empty'``). + + Returns + ------- + int or bool + The option value from ``PARAMETER_OPTIONS`` if defined, + otherwise the default from ``PARAMETER_OPTION_DEFAULTS``. """ + PARAMETER_OPTION_DEFAULTS = { + "indent": 0, + "newline_if_set": False, + "newline_indent": 0, + "remove_line_if_empty": False, + } + + PARAMETER_OPTIONS = { + "PYPROJECT_PY_MODULES": { + "indent": 8, + "newline_if_set": True, + "newline_indent": 4, + }, + "PYPROJECT_DEPENDENCIES": {"indent": 8, "remove_line_if_empty": True}, + } + default = PARAMETER_OPTION_DEFAULTS.get(option) if key not in PARAMETER_OPTIONS.keys(): return default @@ -58,13 +55,24 @@ def parameter_option(key, option): # Copied from scikit-ci/ci/utils.py def indent(text, prefix, predicate=None): - """Adds 'prefix' to the beginning of selected lines in 'text'. - If 'predicate' is provided, 'prefix' will only be added to the lines - where 'predicate(line)' is True. If 'predicate' is not provided, - it will default to adding 'prefix' to all non-empty lines that do not - consist solely of whitespace characters. - - Copied from textwrap.py available in python 3 (cpython/cpython@a2d2bef) + """Add *prefix* to the beginning of selected lines in *text*. + + Copied from ``textwrap.py`` (cpython/cpython@a2d2bef). + + Parameters + ---------- + text : str + The multiline string to indent. + prefix : str + String prepended to each selected line. + predicate : callable, optional + Called with each line; *prefix* is added only when it returns + True. Defaults to adding *prefix* to all non-blank lines. + + Returns + ------- + str + The indented text. """ if predicate is None: @@ -73,29 +81,51 @@ def predicate(line): def prefixed_lines(): for line in text.splitlines(True): - yield (prefix + line if predicate(line) else line) + yield prefix + line if predicate(line) else line return "".join(prefixed_lines()) def list_to_str(list_, newline=True): + """Join a list of strings as quoted, comma-separated items. + + Parameters + ---------- + list_ : list[str] + Items to format. + newline : bool, optional + Use newline separators when True, spaces when False. + + Returns + ------- + str + Formatted string like ``'"a",\\n"b"'``. + """ sep = ", " if newline: sep = ",\n" - return sep.join(['"%s"' % item for item in list_]) + return sep.join([f'"{item}"' for item in list_]) def configure(template_file, parameters, output_file): - """Configure `template_file` into `output_file` given a dictionary of - `parameters`. + """Substitute ``@KEY@`` placeholders in *template_file* and write *output_file*. + + Parameters + ---------- + template_file : str or Path + Input template containing ``@KEY@`` placeholders. + parameters : dict[str, str] + Mapping of placeholder names to substitution values. + output_file : str or Path + Destination path for the configured file. """ updated_lines = [] - with open(template_file, "r") as file_: + with open(template_file) as file_: lines = file_.readlines() for line in lines: append = True for key in parameters.keys(): - value = parameters[key].strip() + value = str(parameters[key]).strip() if ( key in line and not value @@ -107,8 +137,10 @@ def configure(template_file, parameters, output_file): value = indent(value, block_indent) newline_indent = " " * parameter_option(key, "newline_indent") if value.strip() and parameter_option(key, "newline_if_set"): - value = "\n%s\n%s" % (value, newline_indent) - line = line.replace("@%s@" % key, value) + value = f"\n{value}\n{newline_indent}" + line = line.replace(f"@{key}@", value) + # Windows paths need to have backslashes escaped preserved in writing of files + line = line.replace("\\", "\\\\") if append: updated_lines.append(line) @@ -117,16 +149,56 @@ def configure(template_file, parameters, output_file): def from_group_to_wheel(group): - return "itk-%s" % group.lower() + """Convert an ITK group name to its wheel package name. + Parameters + ---------- + group : str + ITK group name (e.g. ``'Core'``, ``'Filtering'``). -def update_wheel_pyproject_toml_parameters(): - global PYPROJECT_PY_PARAMETERS - for wheel_name in get_wheel_names(): - params = dict(ITK_PYPROJECT_PY_PARAMETERS) + Returns + ------- + str + Wheel name like ``'itk-core'``. + """ + return f"itk-{group.lower()}" + + +def update_wheel_pyproject_toml_parameters( + base_params: dict, + package_env_config: dict, + SCRIPT_NAME: str, + wheel_names: list, + wheel_dependencies: dict, +): + """Build a mapping of wheel name to ``pyproject.toml`` template parameters. + + This is a pure transformation and does not mutate global state. + + Parameters + ---------- + base_params : dict + Shared base parameters common to all wheels. + package_env_config : dict + Build environment configuration (ITK paths, versions, etc.). + SCRIPT_NAME : str + Name of the calling script, embedded in the generator field. + wheel_names : list[str] + Ordered list of wheel package names to generate parameters for. + wheel_dependencies : dict[str, list[str]] + Mapping from wheel name to its dependency list. + + Returns + ------- + dict[str, dict] + ``{wheel_name: parameters_dict}`` for each wheel. + """ + PYPROJECT_PY_PARAMETERS = {} + for wheel_name in wheel_names: + params = dict(base_params) # generator - params["PYPROJECT_GENERATOR"] = "python %s '%s'" % (SCRIPT_NAME, wheel_name) + params["PYPROJECT_GENERATOR"] = f"python {SCRIPT_NAME} '{wheel_name}'" # name if wheel_name == "itk-meta": @@ -182,18 +254,21 @@ def update_wheel_pyproject_toml_parameters(): # cmake_args params["PYPROJECT_CMAKE_ARGS"] = list_to_str( [ + f"-DITK_SOURCE_DIR={package_env_config['ITK_SOURCE_DIR']}", + f"-DITK_GIT_TAG:STRING={package_env_config['ITK_GIT_TAG']}", + f"-DITK_PACKAGE_VERSION:STRING={package_env_config['ITK_PACKAGE_VERSION']}", "-DITK_WRAP_unsigned_short:BOOL=ON", "-DITK_WRAP_double:BOOL=ON", "-DITK_WRAP_complex_double:BOOL=ON", "-DITK_WRAP_IMAGE_DIMS:STRING=2;3;4", "-DITK_WRAP_DOC:BOOL=ON", - "-DITKPythonPackage_WHEEL_NAME:STRING=%s" % wheel_name, + f"-DITKPythonPackage_WHEEL_NAME:STRING={wheel_name}", ], True, ) # install_requires - wheel_depends = get_wheel_dependencies()[wheel_name] + wheel_depends = list(wheel_dependencies[wheel_name]) # py_modules if wheel_name != "itk-core": @@ -205,35 +280,60 @@ def update_wheel_pyproject_toml_parameters(): PYPROJECT_PY_PARAMETERS[wheel_name] = params + return PYPROJECT_PY_PARAMETERS -def get_wheel_names(): - with open(os.path.join(SCRIPT_DIR, "WHEEL_NAMES.txt"), "r") as _file: - return [wheel_name.strip() for wheel_name in _file.readlines()] +def get_wheel_names(IPP_BuildWheelsSupport_DIR: str): + """Read the ordered list of wheel names from ``WHEEL_NAMES.txt``. -def get_version(): - from itkVersion import get_versions + Parameters + ---------- + IPP_BuildWheelsSupport_DIR : str + Directory containing ``WHEEL_NAMES.txt``. - version = get_versions()["package-version"] - return version + Returns + ------- + list[str] + Stripped wheel names, one per line. + """ + with open(os.path.join(IPP_BuildWheelsSupport_DIR, "WHEEL_NAMES.txt")) as _file: + return [wheel_name.strip() for wheel_name in _file.readlines()] def get_py_api(): - import sys + """Return the pinned stable-ABI tag. + + Returns + ------- + str + ``STABLE_ABI_TAG``. + """ + return STABLE_ABI_TAG + - if sys.version_info < (3, 11): - return "" - else: - return "cp" + str(sys.version_info.major) + str(sys.version_info.minor) +def get_wheel_dependencies(SCRIPT_DIR: str, version: str, wheel_names: list): + """Parse ITK CMake files to build a wheel dependency graph. + Parameters + ---------- + SCRIPT_DIR : str + Path to the ``scripts/`` directory. + version : str + PEP 440 version string pinned in each dependency. + wheel_names : list[str] + All known wheel names; used to build the ``itk-meta`` entry. -def get_wheel_dependencies(): - """Return a dictionary of ITK wheel dependencies.""" + Returns + ------- + dict[str, list[str]] + Mapping of wheel name to its list of pinned dependencies. + """ all_depends = {} regex_group_depends = r"set\s*\(\s*ITK\_GROUP\_([a-zA-Z0-9\_\-]+)\_DEPENDS\s*([a-zA-Z0-9\_\-\s]*)\s*" # noqa: E501 pattern = re.compile(regex_group_depends) - version = get_version() - with open(os.path.join(SCRIPT_DIR, "..", "CMakeLists.txt"), "r") as file_: + with open( + os.path.join(SCRIPT_DIR, "..", "cmake/ITKPythonPackage_BuildWheels.cmake") + ) as file_: for line in file_.readlines(): match = re.search(pattern, line) if not match: @@ -246,86 +346,226 @@ def get_wheel_dependencies(): all_depends[wheel] = _wheel_depends all_depends["itk-meta"] = [ wheel_name + "==" + version - for wheel_name in get_wheel_names() + for wheel_name in wheel_names if wheel_name != "itk-meta" ] all_depends["itk-meta"].append("numpy") return all_depends -SCRIPT_DIR = os.path.dirname(__file__) -SCRIPT_NAME = os.path.basename(__file__) - -ITK_PYPROJECT_PY_PARAMETERS = { - "PYPROJECT_GENERATOR": "python %s '%s'" % (SCRIPT_NAME, "itk"), - "PYPROJECT_NAME": r"itk", - "PYPROJECT_VERSION": get_version(), - "PYPROJECT_CMAKE_ARGS": r"", - "PYPROJECT_PY_API": get_py_api(), - "PYPROJECT_PLATLIB": r"true", - "PYPROJECT_PY_MODULES": list_to_str( - [ - "itkBase", - "itkConfig", - "itkExtras", - "itkHelpers", - "itkLazy", - "itkTemplate", - "itkTypes", - "itkVersion", - "itkBuildOptions", - ] - ), - "PYPROJECT_DOWNLOAD_URL": r"https://github.com/InsightSoftwareConsortium/ITK/releases", - "PYPROJECT_DESCRIPTION": r"ITK is an open-source toolkit for multidimensional image analysis", # noqa: E501 - "PYPROJECT_LONG_DESCRIPTION": r"ITK is an open-source, cross-platform library that " - "provides developers with an extensive suite of software " - "tools for image analysis. Developed through extreme " - "programming methodologies, ITK employs leading-edge " - "algorithms for registering and segmenting " - "multidimensional scientific images.", - "PYPROJECT_EXTRA_KEYWORDS": r'"scientific", "medical", "image", "imaging"', - "PYPROJECT_DEPENDENCIES": r"", -} - -PYPROJECT_PY_PARAMETERS = {"itk": ITK_PYPROJECT_PY_PARAMETERS} - -update_wheel_pyproject_toml_parameters() +def build_base_pyproject_parameters( + package_env_config: dict, SCRIPT_NAME: str, itk_package_version: str +): + """Return the base ``pyproject.toml`` template parameters for ITK. + + Parameters + ---------- + package_env_config : dict + Build environment configuration. + SCRIPT_NAME : str + Name of the calling script. + itk_package_version : str + PEP 440 version string for the ITK packages. + + Returns + ------- + dict[str, str] + Base parameter mapping shared across all wheel configurations. + """ + ITK_SOURCE_README: str = os.path.join( + package_env_config["ITK_SOURCE_DIR"], "README.md" + ) + return { + "PYPROJECT_GENERATOR": f"python {SCRIPT_NAME} 'itk'", + "PYPROJECT_NAME": r"itk", + "PYPROJECT_VERSION": itk_package_version, + "PYPROJECT_CMAKE_ARGS": r"", + "PYPROJECT_PY_API": get_py_api(), + "PYPROJECT_PLATLIB": r"true", + "ITK_SOURCE_DIR": package_env_config["ITK_SOURCE_DIR"], + "ITK_SOURCE_README": ITK_SOURCE_README, + "PYPROJECT_PY_MODULES": list_to_str( + [ + "itkBase", + "itkConfig", + "itkExtras", + "itkHelpers", + "itkLazy", + "itkTemplate", + "itkTypes", + "itkVersion", + "itkBuildOptions", + ] + ), + "PYPROJECT_DOWNLOAD_URL": r"https://github.com/InsightSoftwareConsortium/ITK/releases", + "PYPROJECT_DESCRIPTION": r"ITK is an open-source toolkit for multidimensional image analysis", # noqa: E501 + "PYPROJECT_LONG_DESCRIPTION": r"ITK is an open-source, cross-platform library that " + "provides developers with an extensive suite of software " + "tools for image analysis. Developed through extreme " + "programming methodologies, ITK employs leading-edge " + "algorithms for registering and segmenting " + "multidimensional scientific images.", + "PYPROJECT_EXTRA_KEYWORDS": r'"scientific", "medical", "image", "imaging"', + "PYPROJECT_DEPENDENCIES": r"", + } def main(): - # Defaults - default_output_dir = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) - # Parse arguments + SCRIPT_DIR = os.path.dirname(__file__) parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="""CLI allowing to configure ``pyproject.toml`` found in the `` ITKPythonPackage `` +source tree. + +Different versions of ``pyproject.toml`` can be generated based on the value +of the `wheel_name` positional parameter. + +Usage:: + + pyproject_configure.py [-h] [--output-dir OUTPUT_DIR] wheel_name + + positional arguments: + wheel_name + + optional arguments: + -h, --help show this help message and exit + --output-dir OUTPUT_DIR + Output directory for configured 'pyproject.toml' + (default: /work) + +Accepted values for `wheel_name` are ``itk`` and all values read from +``WHEEL_NAMES.txt``. +""", ) parser.add_argument("wheel_name") parser.add_argument( "--output-dir", type=str, help="Output directory for configured 'pyproject.toml'", - default=default_output_dir, + default=os.path.abspath(os.path.join(SCRIPT_DIR, "..")), + ) + parser.add_argument( + "--build-dir-root", + type=str, + default=f"{SCRIPT_DIR}/../", + help="The root of the build resources.", ) args = parser.parse_args() - template = os.path.join(SCRIPT_DIR, "pyproject.toml.in") - if args.wheel_name not in PYPROJECT_PY_PARAMETERS.keys(): - print("Unknown wheel name '%s'" % args.wheel_name) + print(f"Reading configuration settings from {args.env_file}") + package_env_config = read_env_file(args.env_file, args.build_dir_root) + + configure_one_pyproject_file( + SCRIPT_DIR, package_env_config, args.output_dir, args.wheel_name + ) + + +def configure_one_pyproject_file( + SCRIPT_DIR: str | bytes, package_env_config, output_dir, wheel_name: str = "itk" +): + """Generate a configured ``pyproject.toml`` for a single wheel. + + Parameters + ---------- + SCRIPT_DIR : str or bytes + Path to the ``scripts/`` directory containing templates. + package_env_config : dict + Build environment configuration. + output_dir : str or Path + Directory where ``pyproject.toml`` will be written. + wheel_name : str, optional + Which wheel to configure (default ``'itk'``). + """ + # Version needs to be python PEP 440 compliant (no leading v) + PEP440_VERSION: str = package_env_config["ITK_PACKAGE_VERSION"].removeprefix("v") + try: + Version( + PEP440_VERSION + ) # Raise InvalidVersion exception if not PEP 440 compliant + except ValueError: + print(f"Invalid PEP 440 version: {PEP440_VERSION}") + sys.exit(1) + + # Resolve script information locally + + IPP_BuildWheelsSupport_DIR = os.path.join(SCRIPT_DIR, "..", "BuildWheelsSupport") + SCRIPT_NAME = os.path.basename(__file__) + # Write itkVersion.py file to report ITK version in python. + write_itkVersion_py(Path(output_dir) / "itkVersion.py", PEP440_VERSION) + # Copy LICENSE file needed for each wheel + shutil.copy(Path(IPP_BuildWheelsSupport_DIR) / "LICENSE", output_dir) + + base_params = build_base_pyproject_parameters( + package_env_config, SCRIPT_NAME, PEP440_VERSION + ) + + wheel_names = get_wheel_names(IPP_BuildWheelsSupport_DIR) + wheel_dependencies = get_wheel_dependencies( + SCRIPT_DIR, base_params["PYPROJECT_VERSION"], wheel_names + ) + + PYPROJECT_PY_PARAMETERS = {"itk": dict(base_params)} + PYPROJECT_PY_PARAMETERS.update( + update_wheel_pyproject_toml_parameters( + base_params, + package_env_config, + SCRIPT_NAME, + wheel_names, + wheel_dependencies, + ) + ) + + if wheel_name not in PYPROJECT_PY_PARAMETERS.keys(): + print(f"Unknown wheel name '{wheel_name}'") sys.exit(1) # Configure 'pyproject.toml' - output_file = os.path.join(args.output_dir, "pyproject.toml") - configure(template, PYPROJECT_PY_PARAMETERS[args.wheel_name], output_file) - - # Configure or remove 'itk/__init__.py' - # init_py = os.path.join(args.output_dir, "itk", "__init__.py") - # if args.wheel_name in ["itk", "itk-core"]: - # with open(init_py, 'w') as file_: - # file_.write("# Stub required for package\n") - # else: - # if os.path.exists(init_py): - # os.remove(init_py) + output_file = os.path.join(output_dir, "pyproject.toml") + print(f"Generating: {output_file}") + template = os.path.join(SCRIPT_DIR, "pyproject.toml.in") + configure(template, PYPROJECT_PY_PARAMETERS[wheel_name], output_file) + + +def write_itkVersion_py(filename: str | Path, itk_package_version: str): + """Write an ``itkVersion.py`` file reporting the ITK package version. + + Parameters + ---------- + filename : str or Path + Output file path. + itk_package_version : str + PEP 440 version string to embed. + """ + itk_version_python_code = f""" +VERSION: str = '{itk_package_version}' + +def get_versions() -> str: + \"\"\"Returns versions for the ITK Python package. + + from itkVersion import get_versions + + # Returns the ITK repository version + get_versions()['version'] + + # Returns the package version. Since GitHub Releases do not support the '+' + # character in file names, this does not contain the local version + # identifier in nightly builds, i.e. + # + # '6.0.1.dev20251126' + # + # instead of + # + # '6.0.1.dev20251126+139.g922f2d9' + get_versions()['package-version'] + \"\"\" + + versions = {{}} + versions['version'] = VERSION + versions['package-version'] = VERSION.split('+')[0] + return versions +""" + with open(filename, "w") as wfid: + wfid.write(itk_version_python_code) if __name__ == "__main__": diff --git a/scripts/wheel_builder_utils.py b/scripts/wheel_builder_utils.py new file mode 100644 index 00000000..235a2964 --- /dev/null +++ b/scripts/wheel_builder_utils.py @@ -0,0 +1,710 @@ +"""This module provides convenient function facilitating scripting. + +These functions have been copied from scikit-build project. +See https://github.com/scikit-build/scikit-build +""" + +import filecmp +import os +import re +import shutil +import subprocess +import sys + +# from contextlib import contextmanager +from functools import wraps +from os import chdir as os_chdir +from os import environ +from pathlib import Path + + +class ContextDecorator: + """A base class or mixin that enables context managers to work as + decorators.""" + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __enter__(self): + # Note: Returning self means that in "with ... as x", x will be self + return self + + def __exit__(self, typ, val, traceback): + pass + + def __call__(self, func): + @wraps(func) + def inner(*args, **kwds): # pylint:disable=missing-docstring + with self: + return func(*args, **kwds) + + return inner + + +class push_dir(ContextDecorator): + """Context manager to change current directory.""" + + def __init__(self, directory=None, make_directory=False): + """ + :param directory: + Path to set as current working directory. If ``None`` + is passed, current working directory is used instead. + + :param make_directory: + If True, ``directory`` is created. + """ + self.directory = None + self.make_directory = None + self.old_cwd = None + super().__init__(directory=directory, make_directory=make_directory) + + def __enter__(self): + self.old_cwd = Path.cwd() + if self.directory: + if self.make_directory: + Path(self.directory).mkdir(parents=True, exist_ok=True) + os_chdir(self.directory) + return self + + def __exit__(self, typ, val, traceback): + os_chdir(self.old_cwd) + + +def _remove_tree(path: Path) -> None: + """Recursively delete a file or directory using pathlib only. + + Parameters + ---------- + path : Path + File or directory to remove. No error is raised if *path* + does not exist. + """ + if not path.exists(): + return + if path.is_file() or path.is_symlink(): + try: + path.unlink() + except OSError: + pass + return + for child in path.iterdir(): + _remove_tree(child) + try: + path.rmdir() + except OSError: + pass + + +def read_env_file( + file_path: os.PathLike | str, build_dir_root: os.PathLike | str +) -> dict[str, str]: + """Read a simple .env-style file and return a dict of key/value pairs. + + Supported syntax: + + - Blank lines and lines starting with ``#`` are ignored. + - Optional leading ``export`` prefix is stripped. + - ``KEY=VALUE`` pairs; surrounding single or double quotes are stripped. + - Whitespace around keys and the ``=`` is ignored. + - ``${BUILD_DIR_ROOT}`` in values is replaced with *build_dir_root*. + + This function does not perform variable expansion or modify ``os.environ``. + + Parameters + ---------- + file_path : os.PathLike or str + Path to the ``.env`` file. + build_dir_root : os.PathLike or str + Value substituted for ``${BUILD_DIR_ROOT}`` placeholders in values. + + Returns + ------- + dict[str, str] + Parsed key/value pairs. Returns an empty dict if the file does + not exist or cannot be read. + """ + result: dict[str, str] = {} + path = Path(file_path) + if not path.exists(): + return result + try: + content = path.read_text(encoding="utf-8") + except Exception: + return result + + for raw_line in content.splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("export "): + line = line[len("export ") :].lstrip() + if "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip() + value = value.replace("${BUILD_DIR_ROOT}", str(build_dir_root)) + # Strip surrounding quotes if present + if (len(value) >= 2) and ( + (value[0] == value[-1] == '"') or (value[0] == value[-1] == "'") + ): + value = value[1:-1] + result[key] = value + return result + + +def _which(exe_name: str) -> Path | None: + """Locate an executable on ``PATH`` using pathlib. + + A custom implementation is used because ``shutil.which`` does not + reliably resolve executables on Windows before Python 3.12. + + Parameters + ---------- + exe_name : str + Name (or path fragment) of the executable to find. + + Returns + ------- + Path or None + Absolute path to the first matching executable, or ``None`` if + not found. + """ + # shutil.which only works on windows after python 3.12. + pathext: list[str] = environ.get("PATHEXT", ".EXE;.BAT;.CMD;.exe;.bat;.cmd").split( + ";" + ) + paths: list[str] = environ.get("PATH", "").split(os.pathsep) + exe: Path = Path(exe_name) + candidates: list[Path] = ( + [exe] if exe.suffix else [Path(exe_name + ext) for ext in pathext] + ) + candidates = [exe] + candidates + for p in paths: + if not p: + continue + base = Path(p) + for c in candidates: + fp = base / c + try: + if fp.exists(): + return fp + except OSError: + continue + return None + + +def detect_platform() -> tuple[str, str]: + """Detect the current operating system and CPU architecture. + + Returns + ------- + tuple[str, str] + A ``(os_name, arch)`` pair where *os_name* is one of + ``'linux'``, ``'darwin'``, ``'windows'``, or ``'unknown'``, and + *arch* is a normalized architecture string (e.g. ``'x64'``, + ``'arm64'``, ``'x86_64'``, ``'aarch64'``). + """ + uname = os.uname() if hasattr(os, "uname") else None + sysname = ( + uname.sysname if uname else ("Windows" if os.name == "nt" else sys.platform) + ) + machine = ( + uname.machine + if uname + else (os.environ.get("PROCESSOR_ARCHITECTURE", "").lower()) + ) + os_name = ( + "linux" + if sysname.lower().startswith("linux") + else ( + "darwin" + if sysname.lower().startswith("darwin") or sys.platform == "darwin" + else ("windows" if os.name == "nt" else "unknown") + ) + ) + # Normalize machine + arch = machine + if os_name == "darwin": + if machine in ("x86_64",): + arch = "x86_64" + elif machine in ("arm64", "aarch64"): + arch = "arm64" + elif os_name == "linux": + if machine in ("x86_64",): + arch = "x64" + elif machine in ("i686", "i386"): + arch = "x86" + elif machine in ("aarch64",): + arch = "aarch64" + return os_name, arch +HOST_SYSTEM_ENVIRONMENT_NAME = "hostsystem" +"""Opt out of pixi tool resolution and use the host system's PATH.""" + + +def abort_if_wrong_pixi_environment(platform_env: str) -> None: + """Fail immediately when launched outside the requested pixi environment. + + Raises + ------ + SystemExit + If the active environment is not *platform_env*. + """ + active = os.environ.get("PIXI_ENVIRONMENT_NAME") + if active in (platform_env, HOST_SYSTEM_ENVIRONMENT_NAME): + return + where = ( + f"the {active!r} pixi environment" + if active + else "no pixi environment (plain shell)" + ) + sys.exit( + "\n" + "==============================================================\n" + " WRONG ENVIRONMENT - build aborted before doing any work\n" + "==============================================================\n" + f" running in : {where}\n" + f" required : {platform_env!r}\n" + "\n" + f" Re-run with the environment selected:\n" + f" pixi run -e {platform_env} python scripts/build_wheels.py ...\n" + "\n" + " 'pixi run' without '-e' uses the 'default' environment, which\n" + " does NOT carry the pinned cmake, ninja, doxygen and git. The\n" + " build would silently pick up whatever is on the system PATH.\n" + "\n" + " To deliberately build with host-system tools instead of pixi:\n" + f" PIXI_ENVIRONMENT_NAME={HOST_SYSTEM_ENVIRONMENT_NAME} python " + "scripts/build_wheels.py ...\n" + "==============================================================" + ) + + +def which_required_in_pixi_env( + name: str, pixi_executable: Path, platform_env: str +) -> Path: + """Locate a required executable inside a pixi environment. + + Returns + ------- + Path + Absolute path to the executable within *platform_env*. + + Raises + ------ + RuntimeError + If *name* is not resolvable inside *platform_env*. + """ + if os.environ.get("PIXI_ENVIRONMENT_NAME") in ( + platform_env, + HOST_SYSTEM_ENVIRONMENT_NAME, + ): + return which_required(name) + probe = run_commandLine_subprocess( + [ + pixi_executable, + "run", + "-e", + platform_env, + "python", + "-c", + f"import shutil; print(shutil.which({name!r}) or '')", + ], + env=os.environ.copy(), + ) + resolved = probe.stdout.strip() if probe.returncode == 0 else "" + if not resolved: + raise RuntimeError( + f"MISSING: {name} not found in pixi environment {platform_env!r}; " + f"aborting until required executables can be found" + ) + return Path(resolved) + + +def which_required(name: str) -> Path: + """Locate a required executable, raising when it is absent. + + Returns + ------- + Path + Absolute path to the executable. + + Raises + ------ + RuntimeError + If *name* is not on ``PATH``. + """ + path = _which(name) + if not path: + raise RuntimeError( + f"MISSING: {name} not found in PATH; aborting until required executables can be found" + ) + return path + + +def run_commandLine_subprocess( + cmd: list[str | Path], + cwd: Path | None = None, + env: dict = None, + check: bool = False, +) -> subprocess.CompletedProcess: + """Run a command via ``subprocess.run`` with logging. + + Parameters + ---------- + cmd : list[str or Path] + Command and arguments to execute. + cwd : Path, optional + Working directory for the subprocess. + env : dict, optional + Environment variables for the subprocess. + check : bool, optional + If True, raise ``RuntimeError`` on non-zero exit codes. + + Returns + ------- + subprocess.CompletedProcess + Completed process information including stdout, stderr, and + return code. + + Raises + ------ + RuntimeError + If *check* is True and the command exits with a non-zero code. + """ + cmd = [str(x) for x in cmd] + print(f"Running >>>>>: {' '.join(cmd)} ; # in cwd={cwd} with check={check}\n") + completion_info = subprocess.run( + cmd, + cwd=str(cwd) if cwd else None, + capture_output=True, + text=True, + env=env if env else None, + ) + if completion_info.returncode != 0 and check: + error_msg = "!~" * 40 + if env: + error_msg += "ENVIRONMENT: =================" + for k, v in env.items(): + error_msg += f"\n{k}={v}" + error_msg += "==============================" + if completion_info.stdout: + error_msg += f"\nStdout:\n {completion_info.stdout}" + if completion_info.stderr: + error_msg += f"\nStderr:\n {completion_info.stderr}" + error_msg += f"Command failed with exit code {completion_info.returncode}: {' '.join(cmd)}" + print(error_msg) + raise RuntimeError(error_msg) + + return completion_info + + +def git_describe_to_pep440(desc: str) -> str: + """ + Convert `git describe --tags --long --dirty --always` output + + # v6.0b03-3-g1a2b3c4 + # │ | │ └── abbreviated commit hash + # │ | └────── commits since tag + # | └────────── pre-release type and number + # └────────────── nearest tag + to a PEP 440–compatible version string. + + [N!]N(.N)*[{a|b|rc}N][.postN][.devN]+ + 111122222233333333333444444445555555666666666666 + 1 Epoch segment: N! + 2 Release segment: N(.N)* + 3 Pre-release segment: {a|b|rc}N + 4 Post-release segment: .postN + 5 Development release segment: .devN + 6 local info not used in version ordering. I.e. ignored by package resolution rules + """ + desc = desc.strip() + semver_format = "0.0.0" + + m = re.match( + r"^(v)*(?P\d+)(?P\.\d+)(?P\.\d+)*(?Pa|b|rc|alpha|beta)*0*(?P\d*)-*(?P\d*)-*g*(?P[0-9a-fA-F]+)*(?P.dirty)*$", + desc, + ) + if m: + groupdict = m.groupdict() + + semver_format = ( + f"{groupdict.get('majorver','')}" + f"{groupdict.get('minor','')}" + ) + patch = groupdict.get("patch", None) + if patch: + semver_format += f"{patch}" + else: + semver_format += ".0" + + prereleasemapping = { + "alpha": "a", + "a": "a", + "beta": "b", + "b": "b", + "rc": "rc", + "": "", + } + prerelease_name = prereleasemapping.get(groupdict.get("pretype", ""), None) + prerelnum = groupdict.get("prerelnum", None) + if prerelease_name and prerelnum and len(prerelease_name) > 0: + semver_format += f"{prerelease_name}{prerelnum}" + posttagcount = groupdict.get("posttagcount", None) + dirty = groupdict.get("dirty", None) + if ( + len(posttagcount) > 0 + and int(posttagcount) == 0 + and (dirty is None or len(dirty) == 0) + ): + # If exactly on a tag, then do not add post, or sha + return semver_format + else: + if posttagcount and int(posttagcount) > 0: + semver_format += f".post{posttagcount}" + sha = groupdict.get("sha", None) + if sha: + semver_format += f"+g{sha.lower()}" + if dirty: + semver_format += ".dirty" + return semver_format + + +def compute_itk_package_version( + itk_dir: Path, itk_git_tag: str, pixi_exec_path, env +) -> str: + """Compute a PEP 440 version string for the ITK package. + + Fetches tags, checks out *itk_git_tag*, and runs + ``git describe --tags --long --dirty --always`` to produce a version + via `git_describe_to_pep440`. + + Parameters + ---------- + itk_dir : Path + Path to the ITK source repository. + itk_git_tag : str + Git tag, branch, or commit to check out before describing. + pixi_exec_path : Path or str + Path to the pixi executable (unused but kept for API compat). + env : dict + Environment variables passed to Git subprocesses. + + Returns + ------- + str + PEP 440-compliant version string. + """ + fetch_result = run_commandLine_subprocess( + ["git", "fetch", "--tags"], + cwd=itk_dir, + env=env, + ) + if fetch_result.returncode != 0: + raise RuntimeError( + f"git fetch --tags failed in {itk_dir}: {fetch_result.stderr}" + ) + checkout_result = run_commandLine_subprocess( + ["git", "checkout", itk_git_tag], + cwd=itk_dir, + env=env, + ) + if checkout_result.returncode != 0: + # Keep the commit already checked out; see the matching comment in + # build_wheels.py. Forcing 'main' would move a cache-restored ITK + # source tree away from the commit its prebuilt binaries were + # compiled from. + head_result = run_commandLine_subprocess( + ["git", "rev-parse", "HEAD"], + cwd=itk_dir, + env=env, + ) + resolved = head_result.stdout.strip() if head_result.returncode == 0 else "" + print( + f"WARNING: Failed to checkout {itk_git_tag}; keeping the commit " + f"already checked out ({resolved or 'unknown'}): " + f"{checkout_result.stderr}" + ) + if resolved: + itk_git_tag = resolved + describe_result = run_commandLine_subprocess( + [ + "git", + "describe", + "--tags", + "--long", + "--dirty", + "--always", + ], + cwd=itk_dir, + env=env, + ) + desc = describe_result.stdout.strip() + if describe_result.returncode != 0 or not desc: + raise RuntimeError( + f"git describe produced no usable version in {itk_dir} " + f"(exit status {describe_result.returncode}): " + f"{describe_result.stderr}" + ) + version = git_describe_to_pep440(desc) + if version == "0.0.0": + raise RuntimeError( + f"git describe output {desc!r} does not yield an ITK version; " + f"refusing to build wheels named 0.0.0" + ) + + return version + + +def default_manylinux( + manylinux_version: str, os_name: str, arch: str, env: dict[str, str] +) -> tuple[str, str, str]: + """Resolve default manylinux container image details. + + Parameters + ---------- + manylinux_version : str + Manylinux specification (e.g. ``'_2_28'``, ``'_2_34'``). + os_name : str + Operating system name (only ``'linux'`` triggers resolution). + arch : str + Target architecture (``'x64'``, ``'aarch64'``). + env : dict[str, str] + Environment variables that may override image defaults + (``IMAGE_TAG``, ``CONTAINER_SOURCE``, ``MANYLINUX_IMAGE_NAME``). + + Returns + ------- + tuple[str, str, str] + ``(image_tag, image_name, container_source)``. + + Raises + ------ + RuntimeError + If *manylinux_version* or *arch* is unrecognized. + """ + image_tag = env.get("IMAGE_TAG", "") + container_source = env.get("CONTAINER_SOURCE", "") + image_name = env.get("MANYLINUX_IMAGE_NAME", "") + + if os_name == "linux": + if arch == "x64" and manylinux_version == "_2_34": + image_tag = image_tag or "latest" + elif arch == "x64" and manylinux_version == "_2_28": + image_tag = image_tag or "20250913-6ea98ba" + elif arch == "aarch64" and manylinux_version == "_2_28": + image_tag = image_tag or "2025.08.12-1" + elif manylinux_version == "": + image_tag = "" + else: + raise RuntimeError( + f"FAILURE: Unknown manylinux version {manylinux_version}" + ) + + if arch == "x64": + image_name = ( + image_name or f"manylinux{manylinux_version}-{arch}:{image_tag}" + ) + container_source = container_source or f"docker.io/dockcross/{image_name}" + elif arch == "aarch64": + image_name = ( + image_name or f"manylinux{manylinux_version}_{arch}:{image_tag}" + ) + container_source = container_source or f"quay.io/pypa/{image_name}" + else: + raise RuntimeError(f"Unknown target architecture {arch}") + + return image_tag, image_name, container_source + + +def resolve_oci_exe(env: dict[str, str]) -> str: + """Find an OCI container runtime (docker, podman, or nerdctl). + + Checks ``OCI_EXE`` in *env* first, then probes ``PATH`` for known + runtimes. Defaults to ``'docker'`` if nothing is found. + + Parameters + ---------- + env : dict[str, str] + Environment variables; ``OCI_EXE`` is checked first. + + Returns + ------- + str + Name or path of the OCI runtime executable. + """ + if env.get("OCI_EXE"): + return env["OCI_EXE"] + for cand in ("docker", "podman", "nerdctl"): + if _which(cand): # NOTE shutil.which ALWAYS RETURNS NONE ON WINDOWS before 3.12 + return cand + # Default to docker name if nothing found + return "docker" + + +def give_relative_path(bin_exec: Path, build_dir_root: Path) -> str: + bin_exec = Path(bin_exec).resolve() + build_dir_root = Path(build_dir_root).resolve() + if bin_exec.is_relative_to(build_dir_root): + return "${BUILD_DIR_ROOT}" + os.sep + str(bin_exec.relative_to(build_dir_root)) + return str(bin_exec) + + +def safe_copy_if_different(src: Path, dst: Path) -> None: + """Copy file only if destination is missing or contents differ. + + This avoids unnecessary overwrites and timestamp churn when files are identical. + """ + src = Path(src) + dst = Path(dst) + if not dst.exists(): + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src, dst) + return + try: + same = filecmp.cmp(src, dst, shallow=False) + except Exception as e: + # On any comparison failure, fall back to copying to be safe + same = False + print(f"WARNING: Failed to compare {src} to {dst}: {e}") + if not same: + shutil.copyfile(src, dst) + + +def get_default_platform_build(default_python_version: str = "py311") -> str: + """Return a default ``-`` build identifier. + + Inspects the ``PIXI_ENVIRONMENT_NAME`` environment variable first; + falls back to ``sys.platform`` detection. + + Parameters + ---------- + default_python_version : str, optional + Python version suffix used when not running inside pixi + (default ``'py311'``). + + Returns + ------- + str + A string like ``'manylinux_2_28-py311'`` or ``'macosx-py311'``. + """ + from_pixi = os.environ.get("PIXI_ENVIRONMENT_NAME", None) + if from_pixi and "-" in from_pixi: + manylinux_pixi_to_pattern_renaming: dict[str, str] = { + "manylinux228": "manylinux_2_28", + "manylinux234": "manylinux_2_34", + } + platform_prefix: str = from_pixi.split("-")[0] + python_version: str = from_pixi.split("-")[1] + platform_prefix = manylinux_pixi_to_pattern_renaming.get( + platform_prefix, platform_prefix + ) + return f"{platform_prefix}-{python_version}" + else: + if sys.platform == "darwin": + return f"macosx-{default_python_version}" + elif sys.platform.startswith("linux"): + return f"linux-{default_python_version}" + elif sys.platform == "win32": + return f"windows-{default_python_version}" + return f"unknown-{default_python_version}" diff --git a/scripts/windows_build_python_instance.py b/scripts/windows_build_python_instance.py new file mode 100644 index 00000000..1beac88b --- /dev/null +++ b/scripts/windows_build_python_instance.py @@ -0,0 +1,249 @@ +import re +import shutil +from pathlib import Path + +from build_python_instance_base import BuildPythonInstanceBase + + +class WindowsBuildPythonInstance(BuildPythonInstanceBase): + """Windows-specific wheel builder. + + Handles Windows path conventions, pixi-based Python environment + discovery, and ``delvewheel`` for wheel repair. + """ + + def _configure_toolchain(self) -> None: + self.package_env_config["USE_TBB"] = "ON" + self.package_env_config["TBB_DIR"] = str( + self.build_dir_root / "build" / "oneTBB-prefix" / "lib" / "cmake" / "TBB" + ) + + def post_build_fixup(self) -> None: + """Repair wheels with ``delvewheel``, including oneTBB library paths.""" + # append the oneTBB-prefix\\bin directory for fixing wheels built with local oneTBB + search_lib_paths = ( + [ + s + for s in str(self.windows_extra_lib_paths[0]).rstrip(";").split(";") + if s + ] + if self.windows_extra_lib_paths + else [] + ) + # oneTBB is built by 01_superbuild_support_components into + # /build/oneTBB-prefix, the same prefix + # _configure_toolchain() points TBB_DIR at. + search_lib_paths.append( + str(self.build_dir_root / "build" / "oneTBB-prefix" / "bin") + ) + search_lib_paths_str: str = ";".join(map(str, search_lib_paths)) + self.fixup_wheels(search_lib_paths_str) + + def fixup_wheel( + self, filepath, lib_paths: str = "", remote_module_wheel: bool = False + ) -> None: + """Repair a wheel using ``delvewheel`` with the given library paths. + + Parameters + ---------- + filepath : str + Path to the ``.whl`` file to repair. + lib_paths : str, optional + Semicolon-delimited directories to add to ``delvewheel --add-path``. + remote_module_wheel : bool, optional + If True, output the repaired wheel to the remote module's + ``dist/`` directory instead of the main build ``dist/``, + matching the Linux builder. + """ + # Windows fixup_wheel + lib_paths = lib_paths.strip() + lib_paths = lib_paths + ";" if lib_paths else "" + print(f"Library paths for fixup: {lib_paths}") + + # A remote module's repaired wheel belongs beside its source, where + # the module build wrote the unrepaired one. Sending it to the main + # build dist/ instead left the unrepaired wheel in place as the + # module's only visible artifact, so anything reading + # /dist/ picked up a wheel with no bundled DLLs. + output_dir = ( + self.module_source_dir / "dist" + if remote_module_wheel + else self.build_dir_root / "dist" + ) + delve_wheel = "delvewheel.exe" + cmd = [ + str(delve_wheel), + "repair", + "--no-mangle-all", + "--add-path", + lib_paths.strip(";"), + "--ignore-in-wheel", + "-w", + str(output_dir), + str(filepath), + ] + repair_status = self.echo_check_call(cmd) + + repaired_wheel = output_dir / Path(filepath).name + if repair_status != 0 or not repaired_wheel.exists(): + raise RuntimeError( + f"delvewheel repair failed for {Path(filepath).name} " + f"(exit status {repair_status}, expected output " + f"{repaired_wheel})" + ) + + def build_tarball(self): + r"""Create an archive of the ITK Python package build tree (Windows). + + Mirrors scripts/windows-build-tarball.ps1 behavior: + - Use 7-Zip, when present, to archive the full IPP tree into + ITKPythonBuilds-windows.zip at the parent directory of IPP (e.g., C:\P) + - Fallback to Python's zip archive creation if 7-Zip is unavailable + + Note: the historical shell script cleared ``IPP/dist`` first, because + ``dist`` then lived inside the IPP tree being archived and stale wheels + would have been baked into the cache. ``dist`` is now + ``/dist`` and is *not* one of the archived roots, so + clearing it only destroys the wheels this build just produced, + repaired and import-tested, while still reporting success. + """ + + # out_zip = self.build_dir_root / "build" / "ITKPythonBuilds-windows.zip" + out_zip = self.build_dir_root / "ITKPythonBuilds-windows.zip" + + # 1) Try to use 7-Zip if available + seven_zip_candidates = [ + Path(r"C:\\7-Zip\\7z.exe"), + Path(r"C:\\Program Files\\7-Zip\\7z.exe"), + Path(r"C:\\Program Files (x86)\\7-Zip\\7z.exe"), + ] + + seven_zip = None + for cand in seven_zip_candidates: + if cand.exists(): + seven_zip = cand + break + + if seven_zip is None: + # Try PATH lookup using where/which behavior from shutil + found = shutil.which("7z.exe") or shutil.which("7z") + if found: + seven_zip = Path(found) + + if seven_zip is not None: + # No -r: 7-Zip treats each name as a *recursive pattern* under -r, + # so "build" would match every directory of that name beneath the + # build root -- pulling e.g. /BioCell/build, a remote + # module's CMake tree, into the cache. Consumers then extract that + # stale tree over their own checkout and CMake aborts with + # "generator ... does not match the generator used previously". + # Directory arguments are archived recursively without -r; the + # -xr! exclusions below are recursive in their own right. + cmd = [ + str(seven_zip), + "a", + "-t7z", + str(out_zip), + str(self.build_dir_root / "ITK"), + str(self.build_dir_root / "build"), + str(self.ipp_dir), + "-xr!*.o", + "-xr!*.obj", # Windows equivalent of .o + "-xr!wheelbuilds", # Do not include the wheelbuild support directory + "-xr!__pycache__", # Do not include __pycache__ + "-xr!install_manifest_*.txt", # Do not include install manifest files + "-xr!.git", # Exclude git directory + "-xr!.idea", # Exclude IDE directory + "-xr!.pixi", # Exclude pixi environment + "-xr!castxml_inputs", + r"-xr!Wrapping\Modules", + "-xr!*.pdb", # Exclude debug symbols + ] + return_status: int = self.echo_check_call(cmd) + if return_status == 0: + return + + # 2) Fallback: create a .zip using Python's shutil + # This will create a zip archive named ITKPythonBuilds-windows.zip + if out_zip.exists(): + try: + out_zip.unlink() + except Exception: + pass + # make_archive requires base name without extension + base_name = str(out_zip.with_suffix("").with_suffix("")) + # shutil.make_archive will append .zip + shutil.make_archive( + base_name, + "zip", + root_dir=str(self.build_dir_root), + base_dir=str(self.build_dir_root.name), + ) + + def venv_paths(self) -> None: + """Populate ``venv_info_dict`` from the pixi-managed Python environment on Windows.""" + + def get_python_version(platform_env: str) -> None | tuple[int, int]: + pattern = re.compile(r"py3(?P\d+)") + m = pattern.search(platform_env) + if not m: + return None + return 3, int(m.group("minor")) + + # Get the python executable path + python_exe = Path(self.package_env_config["PYTHON_EXECUTABLE"]) + + # For the pixi environment structure: + # python.exe is at: /python.exe + # Headers are at: /include/ + # Libraries are at: /libs/ + env_root = python_exe.parent # C:/BDR/IPP/.pixi/envs/windows-py311 + + venv_bin_path = env_root # Where python.exe is + venv_base_dir = env_root + + # Python development files are directly under env root + python_include_dir = env_root / "include" + + python_major, python_minor = get_python_version(self.platform_env) + # Version-specific library (e.g., python311.lib) - required for + # CMake's FindPython3 to extract version info for Development.Module + xy_lib_ver = f"{python_major}{python_minor}" + python_library = env_root / "libs" / f"python{xy_lib_ver}.lib" + + # Stable ABI library (python3.lib) - for Development.SABIModule + if python_minor >= 11: + python_sabi_library = env_root / "libs" / f"python{python_major}.lib" + else: + python_sabi_library = python_library + + self.venv_info_dict = { + "python_include_dir": python_include_dir, # .../windows-py311/include + "python_library": python_library, # .../windows-py311/libs/python311.lib + "python_sabi_library": python_sabi_library, # .../windows-py311/libs/python3.lib + "venv_bin_path": venv_bin_path, # .../windows-py311 + "venv_base_dir": venv_base_dir, # .../windows-py311 + "python_root_dir": env_root, # .../windows-py311 + } + + def discover_python_venvs( + self, platform_os_name: str, platform_architecture: str + ) -> list[str]: + """Return default Windows Python environment names. + + Parameters + ---------- + platform_os_name : str + Operating system identifier (unused, kept for interface). + platform_architecture : str + Architecture suffix appended to each environment name. + + Returns + ------- + list[str] + Environment names like ``['311-x64']``. + """ + default_platform_envs = [ + f"311-{platform_architecture}", + ] + return default_platform_envs diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..1feb3e61 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,15 @@ +import pytest +from macos_build_python_instance import MacOSBuildPythonInstance +from target_platform import Arch, TargetPlatform + + +@pytest.fixture +def macos_builder(tmp_path): + """A macOS builder wired up enough to exercise its hooks.""" + builder = MacOSBuildPythonInstance.__new__(MacOSBuildPythonInstance) + builder.build_dir_root = tmp_path + builder.platform_env = "macosx-py311" + builder.target = TargetPlatform("darwin", Arch.AARCH64) + builder.package_env_config = {"ARCH": "arm64"} + builder.venv_info_dict = {"venv_bin_path": str(tmp_path / "bin")} + return builder diff --git a/tests/test_build_environment.py b/tests/test_build_environment.py new file mode 100644 index 00000000..e1ed1b72 --- /dev/null +++ b/tests/test_build_environment.py @@ -0,0 +1,69 @@ +import shutil +import sys + +import build_environment +import pytest + + +def test_echo_check_call_returns_zero_on_success(): + rc = build_environment.echo_check_call( + [sys.executable, "-c", "pass"], use_pixi_env=False + ) + assert rc == 0 + + +def test_echo_check_call_returns_nonzero_rather_than_raising(): + """It returns the exit status; it does not raise on its own. + + Callers that must stop the build therefore have to forward + ``check=True`` or inspect the returned status themselves. + """ + rc = build_environment.echo_check_call( + [sys.executable, "-c", "import sys; sys.exit(3)"], use_pixi_env=False + ) + assert rc == 3 + + +def test_echo_check_call_raises_when_check_is_forwarded(): + """``check=True`` reaches run_commandLine_subprocess, which raises.""" + with pytest.raises(RuntimeError): + build_environment.echo_check_call( + [sys.executable, "-c", "import sys; sys.exit(3)"], + use_pixi_env=False, + check=True, + ) + + +def test_get_pixi_environment_name_is_the_platform_env(): + assert build_environment.get_pixi_environment_name("macosx-py311") == "macosx-py311" + + +def test_find_unix_exectable_paths_raises_when_interpreter_absent(tmp_path): + with pytest.raises(FileNotFoundError): + build_environment.find_unix_exectable_paths(tmp_path) + + +def test_find_unix_exectable_paths_resolves_a_venv_layout(tmp_path): + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + # A real venv symlinks its interpreter, but creating a symlink needs + # SeCreateSymbolicLinkPrivilege off a POSIX box (Developer Mode or an + # elevated shell), so an unprivileged run raises WinError 1314. Copy the + # interpreter instead: find_unix_exectable_paths() runs it to read + # sysconfig, so the stand-in has to be executable, and a copied + # interpreter still is even without the .exe suffix. + try: + (bin_dir / "python").symlink_to(sys.executable) + except (OSError, NotImplementedError): + shutil.copy2(sys.executable, bin_dir / "python") + + exe, include_dir, library, venv_bin, venv_base = ( + build_environment.find_unix_exectable_paths(tmp_path) + ) + + assert exe == str(bin_dir / "python") + assert venv_bin == str(bin_dir) + assert venv_base == str(tmp_path) + # CMake infers the library from the interpreter, so it stays empty. + assert library == "" + assert include_dir diff --git a/tests/test_build_steps.py b/tests/test_build_steps.py new file mode 100644 index 00000000..a2d4d20f --- /dev/null +++ b/tests/test_build_steps.py @@ -0,0 +1,83 @@ +"""The step table drives BuildManager; its keys are what the step log records. + +A ``_skipped`` key mapped to a no-op is how a consciously bypassed stage is +made visible in ``build_log_.json``. +""" + +from pathlib import Path + + +def _configure( + builder, + *, + skip_itk_build=False, + skip_itk_wheel_build=False, + module=None, +): + builder.skip_itk_build = skip_itk_build + builder.skip_itk_wheel_build = skip_itk_wheel_build + builder.module_source_dir = Path(module) if module else None + builder.build_itk_tarball_cache = False + builder.cleanup = False + builder.itk_module_deps = None + return builder + + +def test_full_build_registers_every_stage(linux_builder): + keys = list(_configure(linux_builder).build_step_table()) + assert keys == [ + "01_superbuild_support_components", + "02_build_wrapped_itk_cplusplus", + "03_build_wheels", + "04_post_build_fixup", + "05_final_import_test", + "06_build_external_module_wheel_skipped", + ] + + +def test_skipping_itk_wheels_also_skips_the_stages_that_need_them(linux_builder): + """T7: the remote-module path takes ITK from a cache that excludes wheels. + + Step 04 repairs the ITK wheels and step 05 pip-installs them from dist/; + with step 03 skipped there are none, and 05 aborted the run before the + module wheel in 06 was ever built. + """ + builder = _configure( + linux_builder, + skip_itk_build=True, + skip_itk_wheel_build=True, + module="BioCell", + ) + keys = list(builder.build_step_table()) + assert keys == [ + "01_superbuild_support_components", + "02_build_wrapped_itk_cplusplus_skipped", + "03_build_wheels_skipped", + "04_post_build_fixup_skipped", + "05_final_import_test_skipped", + "06_build_external_module_wheel_BioCell", + ] + + +def test_skipped_stages_are_noops(linux_builder): + table = _configure(linux_builder, skip_itk_wheel_build=True).build_step_table() + for name in ( + "03_build_wheels_skipped", + "04_post_build_fixup_skipped", + "05_final_import_test_skipped", + ): + assert table[name]() is None + + +def test_skipping_only_the_itk_build_keeps_the_wheel_stages(linux_builder): + """Skipping the C++ build alone still builds, repairs and tests wheels.""" + keys = list(_configure(linux_builder, skip_itk_build=True).build_step_table()) + assert "02_build_wrapped_itk_cplusplus_skipped" in keys + assert "03_build_wheels" in keys + assert "04_post_build_fixup" in keys + assert "05_final_import_test" in keys + + +def test_module_step_is_named_after_the_module_directory(linux_builder): + keys = list(_configure(linux_builder, module="/src/ITKFoo").build_step_table()) + assert keys[-1] == "06_build_external_module_wheel_ITKFoo" diff --git a/tests/test_platform_builders.py b/tests/test_platform_builders.py new file mode 100644 index 00000000..bd4d3ee4 --- /dev/null +++ b/tests/test_platform_builders.py @@ -0,0 +1,103 @@ +import os +import zipfile +from pathlib import Path +from unittest.mock import patch + +from cmake_argument_builder import CMakeArgumentBuilder +from target_platform import Arch, TargetPlatform, host_arch + + +def test_build_dir_suffix_matches_pixi_suffix_on_all_platforms(): + """The ITK--_ expression has exactly one source.""" + for os_name, arch in ( + ("linux", Arch.X86_64), + ("linux", Arch.AARCH64), + ("darwin", Arch.AARCH64), + ("windows", Arch.X86_64), + ): + plat = TargetPlatform(os_name, arch) + assert plat.build_dir_suffix == plat.pixi_suffix + + +def _make_wheel(path: Path, members: dict[str, str]) -> Path: + with zipfile.ZipFile(path, "w") as zf: + for name, data in members.items(): + zf.writestr(name, data) + return path + + +def test_fixup_wheel_skips_wheel_with_no_binaries(tmp_path, macos_builder): + """The itk metapackage has no .so/.dylib, so delocate must not run.""" + wheel = _make_wheel( + tmp_path / "itk-6.0.0-py3-none-any.whl", {"itk/__init__.py": ""} + ) + # fixup_wheel calls remove_apple_double_files first, which itself + # calls echo_check_call — patch it out so the assertion below is + # about delocate and nothing else. + with patch.object(macos_builder, "remove_apple_double_files"): + with patch.object(macos_builder, "echo_check_call") as called: + macos_builder.fixup_wheel(str(wheel)) + flat = " ".join(str(a) for c in called.call_args_list for a in c.args[0]) + assert "delocate-wheel" not in flat + + +def test_fixup_wheel_runs_delocate_when_binaries_present(tmp_path, macos_builder): + wheel = _make_wheel( + tmp_path / "itk_core-6.0.0-cp311-abi3-macosx_11_0_arm64.whl", + {"itk/_ITKCommonPython.so": "\x7fELF"}, + ) + with patch.object(macos_builder, "echo_check_call") as called: + macos_builder.fixup_wheel(str(wheel)) + assert called.call_count >= 1 + flat = " ".join(str(a) for c in called.call_args_list for a in c.args[0]) + assert "delocate-wheel" in flat + assert "--require-archs" in flat + + +def test_posix_build_tarball_delegates(macos_builder): + """build_tarball is a base hook now; the Middle Man override is gone.""" + with patch.object(macos_builder, "create_posix_tarball") as created: + macos_builder.build_tarball() + created.assert_called_once() + + +def test_prepare_build_env_uses_existing_downloaded_binaries(macos_builder): + """Pin the downloaded-cache probe, whose platform_env appears twice. + + The spelling is preserved verbatim from the verified macOS builder. + """ + build_root = macos_builder.build_dir_root / "build" + doubled = build_root / "ITK-macosx-py311-macosx-py311_arm64" + doubled.mkdir(parents=True) + macos_builder.cmake_itk_source_build_configurations = CMakeArgumentBuilder() + with ( + patch.object(macos_builder, "venv_paths"), + patch.object(macos_builder, "update_venv_itk_build_configurations"), + patch.object(macos_builder, "_configure_toolchain"), + ): + macos_builder.prepare_build_env() + assert ( + macos_builder.cmake_itk_source_build_configurations.get("ITK_BINARY_DIR:PATH") + == doubled.as_posix() + ) + + +def test_configure_toolchain_routes_arch_through_the_target(macos_builder): + """macOS reads CMAKE_OSX_ARCHITECTURES from the type, not from ARCH.""" + macos_builder.cmake_compiler_configurations = CMakeArgumentBuilder() + macos_builder.package_env_config = {"ARCH": "ignored-by-design"} + macos_builder._configure_toolchain() + assert ( + macos_builder.cmake_compiler_configurations.get( + "CMAKE_OSX_ARCHITECTURES:STRING" + ) + == "arm64" + ) + assert macos_builder.package_env_config["TBB_DIR"] == "NOT_FOUND" + + +def test_host_arch_is_independent_of_target(): + """QEMU reads the host; a cross-build sets only the target.""" + target = TargetPlatform.detect({"TARGET_ARCH": "aarch64"}) + assert target.arch is Arch.AARCH64 + assert host_arch({}) in set(Arch) diff --git a/tests/test_stable_abi.py b/tests/test_stable_abi.py new file mode 100644 index 00000000..02ba92a8 --- /dev/null +++ b/tests/test_stable_abi.py @@ -0,0 +1,39 @@ +"""The stable-ABI tag is a project decision, not a property of the driver.""" + +from types import SimpleNamespace +from unittest.mock import patch + +import pyproject_configure +import pytest +from build_python_instance_base import verify_stable_abi_wheels +from pyproject_configure import STABLE_ABI_TAG, get_py_api + + +def test_stable_abi_tag_is_cp311(): + assert STABLE_ABI_TAG == "cp311" + + +def test_get_py_api_ignores_the_running_interpreter(): + """One cp311-abi3 wheel per platform, whatever python drives the build.""" + fake = SimpleNamespace(major=3, minor=14) + with patch.object(pyproject_configure.sys, "version_info", fake): + assert get_py_api() == "cp311" + + +def test_a_cp312_abi3_wheel_is_rejected(tmp_path): + """A cp312-abi3 wheel will not install on 3.11; refuse to ship it.""" + (tmp_path / "itk_foo-1.0.0-cp312-abi3-manylinux_2_28_x86_64.whl").touch() + with pytest.raises(RuntimeError, match="cp311-abi3"): + verify_stable_abi_wheels(tmp_path, STABLE_ABI_TAG) + + +def test_no_wheel_at_all_is_rejected(tmp_path): + """A build step that produced nothing must not report success.""" + with pytest.raises(RuntimeError, match="No wheel"): + verify_stable_abi_wheels(tmp_path, STABLE_ABI_TAG) + + +def test_a_cp311_abi3_wheel_is_accepted(tmp_path): + wheel = tmp_path / "itk_foo-1.0.0-cp311-abi3-manylinux_2_28_x86_64.whl" + wheel.touch() + assert verify_stable_abi_wheels(tmp_path, STABLE_ABI_TAG) == [wheel] diff --git a/tests/test_tool_resolution_env.py b/tests/test_tool_resolution_env.py new file mode 100644 index 00000000..27b458ec --- /dev/null +++ b/tests/test_tool_resolution_env.py @@ -0,0 +1,106 @@ +"""Build tools must resolve inside the target pixi environment.""" + +import subprocess +from pathlib import Path + +import pytest +from wheel_builder_utils import ( + abort_if_wrong_pixi_environment, + which_required_in_pixi_env, +) + + +def test_resolves_inside_target_env_not_launching_path(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "default") + seen = {} + + def fake_run(cmd, cwd=None, env=None, check=False): + seen["cmd"] = [str(part) for part in cmd] + return subprocess.CompletedProcess( + cmd, 0, "/envs/windows-py311/bin/doxygen\n", "" + ) + + monkeypatch.setattr("wheel_builder_utils.run_commandLine_subprocess", fake_run) + monkeypatch.setattr("wheel_builder_utils.shutil.which", lambda _: "/stray/doxygen") + + resolved = which_required_in_pixi_env("doxygen", Path("/bin/pixi"), "windows-py311") + + assert resolved == Path("/envs/windows-py311/bin/doxygen") + assert seen["cmd"][:4] == [str(Path("/bin/pixi")), "run", "-e", "windows-py311"] + + +def test_uses_current_env_when_already_inside_it(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "windows-py311") + monkeypatch.setattr( + "wheel_builder_utils._which", lambda _: Path("/envs/windows-py311/bin/cmake") + ) + + def explode(*_args, **_kwargs): # nesting pixi run would overflow cmd.exe + raise AssertionError("must not nest a second pixi run") + + monkeypatch.setattr("wheel_builder_utils.run_commandLine_subprocess", explode) + + assert which_required_in_pixi_env( + "cmake", Path("/bin/pixi"), "windows-py311" + ) == Path("/envs/windows-py311/bin/cmake") + + +def test_missing_tool_in_target_env_raises(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "default") + monkeypatch.setattr( + "wheel_builder_utils.run_commandLine_subprocess", + lambda *a, **k: subprocess.CompletedProcess(a[0] if a else [], 0, "\n", ""), + ) + monkeypatch.setattr("wheel_builder_utils.shutil.which", lambda _: "/stray/doxygen") + + with pytest.raises(RuntimeError, match="doxygen"): + which_required_in_pixi_env("doxygen", Path("/bin/pixi"), "windows-py311") + + +def test_wrong_environment_aborts_with_the_env_name(monkeypatch, capsys): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "default") + with pytest.raises(SystemExit) as excinfo: + abort_if_wrong_pixi_environment("windows-py311") + message = str(excinfo.value) + assert "windows-py311" in message + assert "default" in message + assert "pixi run -e windows-py311" in message + + +def test_no_pixi_environment_also_aborts(monkeypatch): + monkeypatch.delenv("PIXI_ENVIRONMENT_NAME", raising=False) + with pytest.raises(SystemExit): + abort_if_wrong_pixi_environment("linux-py311") + + +def test_correct_environment_is_silent(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "linux-py311") + assert abort_if_wrong_pixi_environment("linux-py311") is None + + +def test_hostsystem_opts_out_of_the_environment_guard(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "hostsystem") + assert abort_if_wrong_pixi_environment("macosx-py311") is None + + +def test_hostsystem_resolves_tools_from_host_path(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "hostsystem") + monkeypatch.setattr( + "wheel_builder_utils._which", lambda _: Path("/usr/local/bin/doxygen") + ) + + def explode(*_args, **_kwargs): + raise AssertionError("hostsystem must not probe a pixi environment") + + monkeypatch.setattr("wheel_builder_utils.run_commandLine_subprocess", explode) + + assert which_required_in_pixi_env( + "doxygen", Path("/bin/pixi"), "macosx-py311" + ) == Path("/usr/local/bin/doxygen") + + +def test_wrong_environment_message_mentions_hostsystem(monkeypatch): + monkeypatch.setenv("PIXI_ENVIRONMENT_NAME", "default") + with pytest.raises(SystemExit) as excinfo: + abort_if_wrong_pixi_environment("macosx-py311") + assert "hostsystem" in str(excinfo.value) diff --git a/tests/test_wheel_builder_utils.py b/tests/test_wheel_builder_utils.py new file mode 100644 index 00000000..8d2613cb --- /dev/null +++ b/tests/test_wheel_builder_utils.py @@ -0,0 +1,88 @@ +"""Failure handling in the process helpers the build driver relies on.""" + +import subprocess +from unittest.mock import patch + +import pytest +import wheel_builder_utils +from wheel_builder_utils import compute_itk_package_version, which_required + + +def _completed(returncode: int, stdout: str = "", stderr: str = ""): + return subprocess.CompletedProcess( + args=["git"], returncode=returncode, stdout=stdout, stderr=stderr + ) + + +def test_which_required_raises_for_a_missing_executable(): + """A missing tool must not become the string 'None' in an argv list.""" + with pytest.raises(RuntimeError, match="MISSING"): + which_required("no-such-executable-b7f3c1") + + +def test_compute_itk_package_version_refuses_an_unusable_describe(tmp_path): + """An empty `git describe` would otherwise name the wheels 0.0.0.""" + responses = { + "fetch": _completed(0), + "checkout": _completed(0), + "describe": _completed(128, "", "fatal: not a git repository"), + } + + def fake_run(cmd, cwd=None, env=None, check=False): + return responses[str(cmd[1])] + + with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): + with pytest.raises(RuntimeError, match="no usable version"): + compute_itk_package_version(tmp_path, "v6.0.0", None, {}) + + +def test_compute_itk_package_version_refuses_a_zero_version(tmp_path): + """A describe that parses to 0.0.0 is not a version to ship.""" + responses = { + "fetch": _completed(0), + "checkout": _completed(0), + "describe": _completed(0, "not-a-tag"), + } + + def fake_run(cmd, cwd=None, env=None, check=False): + return responses[str(cmd[1])] + + with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): + with pytest.raises(RuntimeError, match="0.0.0"): + compute_itk_package_version(tmp_path, "v6.0.0", None, {}) + + +def test_compute_itk_package_version_keeps_head_on_failed_checkout(tmp_path): + """A failed checkout must leave the tree where it is. + + Exactly one checkout is attempted: forcing a second one onto 'main' + would move a cache-restored ITK source tree away from the commit its + prebuilt binaries were compiled from. + """ + checked_out: list[str] = [] + + def fake_run(cmd, cwd=None, env=None, check=False): + verb = str(cmd[1]) + if verb == "checkout": + checked_out.append(str(cmd[2])) + return _completed(1, "", "no such ref") + if verb == "rev-parse": + return _completed(0, "abcdef1234567890abcdef1234567890abcdef12") + if verb == "describe": + return _completed(0, "v6.0.0-0-gabcdef1") + return _completed(0) + + with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): + version = compute_itk_package_version(tmp_path, "v9.9.9", None, {}) + + assert checked_out == ["v9.9.9"] + assert version.startswith("6.0") + + +def test_compute_itk_package_version_raises_when_fetch_fails(tmp_path): + def fake_run(cmd, cwd=None, env=None, check=False): + return _completed(1, "", "network down") + + with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): + with pytest.raises(RuntimeError, match="git fetch"): + compute_itk_package_version(tmp_path, "v6.0.0", None, {}) diff --git a/tests/test_windows_wheel_builder.py b/tests/test_windows_wheel_builder.py new file mode 100644 index 00000000..e91201be --- /dev/null +++ b/tests/test_windows_wheel_builder.py @@ -0,0 +1,117 @@ +"""Regression tests for the Windows wheel builder's fragile details. + +Each test here pins a defect that shipped once: a repaired remote-module +wheel written to the wrong ``dist/``, and a 7-Zip invocation whose ``-r`` +turned every archive argument into a recursive pattern. +""" + +from pathlib import Path +from unittest.mock import patch + + +def test_remote_module_repaired_wheel_lands_in_the_module_dist( + tmp_path, windows_builder +): + """A remote module's repaired wheel belongs in the module's own ``dist/``. + + The expectation is derived from ``module_source_dir`` rather than + written out, so hardcoding any other path in the builder fails here + even if the fixture happened to make the two agree. + """ + windows_builder.module_source_dir = tmp_path / "BioCell" + expected_dir = windows_builder.module_source_dir / "dist" + main_dist = windows_builder.build_dir_root / "dist" + main_dist.mkdir(parents=True, exist_ok=True) + wheel = main_dist / "biocell-1.0.0-cp311-abi3-win_amd64.whl" + wheel.touch() + + captured = {} + + def fake_repair(cmd, *args, **kwargs) -> int: + rendered = [str(c) for c in cmd] + out_dir = Path(rendered[rendered.index("-w") + 1]) + captured["out_dir"] = out_dir + out_dir.mkdir(parents=True, exist_ok=True) + (out_dir / wheel.name).touch() + return 0 + + with patch.object(windows_builder, "echo_check_call", side_effect=fake_repair): + windows_builder.fixup_wheel( + str(wheel), lib_paths="C:/tbb/bin", remote_module_wheel=True + ) + + assert captured["out_dir"] == expected_dir + assert (expected_dir / wheel.name).exists() + + +def test_non_remote_repaired_wheel_lands_in_the_build_dist(tmp_path, windows_builder): + """Without ``remote_module_wheel`` the main build ``dist/`` is still used.""" + windows_builder.module_source_dir = tmp_path / "BioCell" + main_dist = windows_builder.build_dir_root / "dist" + main_dist.mkdir(parents=True, exist_ok=True) + wheel = main_dist / "itk_core-6.0.0-cp311-abi3-win_amd64.whl" + wheel.touch() + + captured = {} + + def fake_repair(cmd, *args, **kwargs) -> int: + rendered = [str(c) for c in cmd] + out_dir = Path(rendered[rendered.index("-w") + 1]) + captured["out_dir"] = out_dir + out_dir.mkdir(parents=True, exist_ok=True) + (out_dir / wheel.name).touch() + return 0 + + with patch.object(windows_builder, "echo_check_call", side_effect=fake_repair): + windows_builder.fixup_wheel(str(wheel), lib_paths="C:/tbb/bin") + + assert captured["out_dir"] == main_dist + assert not (windows_builder.module_source_dir / "dist").exists() + + +def _run_build_tarball(windows_builder, tmp_path): + """Drive ``build_tarball`` down the 7-Zip branch and return the command.""" + windows_builder.ipp_dir = tmp_path / "IPP" + windows_builder.ipp_dir.mkdir(parents=True, exist_ok=True) + captured = {} + + def fake_call(cmd, *args, **kwargs) -> int: + captured["cmd"] = [str(c) for c in cmd] + return 0 + + with ( + patch("windows_build_python_instance.shutil.which", return_value="/fake/7z"), + patch.object(windows_builder, "echo_check_call", side_effect=fake_call), + ): + windows_builder.build_tarball() + return captured["cmd"] + + +def test_seven_zip_command_carries_no_recursive_flag(tmp_path, windows_builder): + """``-r`` makes 7-Zip treat each name as a recursive pattern. + + With it, the bare directory name ``build`` matches every directory of + that name beneath the build root, so a remote module's CMake tree is + swept into the cached tarball and later extracted over a consumer's + checkout. Directory arguments recurse without ``-r`` anyway. + """ + cmd = _run_build_tarball(windows_builder, tmp_path) + + assert cmd[1] == "a" + assert "-r" not in cmd + assert "-r0" not in cmd + + +def test_seven_zip_archives_absolute_roots_not_bare_names(tmp_path, windows_builder): + """Each archived root is an absolute path under the build root. + + A bare ``build``/``ITK`` name is what makes ``-r`` dangerous; pinning + the absolute form keeps the arguments unambiguous to 7-Zip. + """ + cmd = _run_build_tarball(windows_builder, tmp_path) + + for root in ("ITK", "build"): + expected = str(windows_builder.build_dir_root / root) + assert expected in cmd + assert root not in cmd + assert str(windows_builder.ipp_dir) in cmd From 18ab60fef81c0fe59b5a50ed9f4ce7ed6a2de31a Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 15:19:17 -0500 Subject: [PATCH 06/16] ENH: Test the builder template method on every platform Co-Authored-By: Cavan Riley --- tests/conftest.py | 71 ++++- tests/test_final_wheel_import_test.py | 93 ++++++ tests/test_platform_builders.py | 415 +++++++++++++++++++++++++- 3 files changed, 559 insertions(+), 20 deletions(-) create mode 100644 tests/test_final_wheel_import_test.py diff --git a/tests/conftest.py b/tests/conftest.py index 1feb3e61..e750fe65 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,72 @@ import pytest +from cmake_argument_builder import CMakeArgumentBuilder +from linux_build_python_instance import LinuxBuildPythonInstance from macos_build_python_instance import MacOSBuildPythonInstance from target_platform import Arch, TargetPlatform +from windows_build_python_instance import WindowsBuildPythonInstance -@pytest.fixture -def macos_builder(tmp_path): - """A macOS builder wired up enough to exercise its hooks.""" - builder = MacOSBuildPythonInstance.__new__(MacOSBuildPythonInstance) +def _make_builder(cls, tmp_path, platform_env, target, package_env_config): + """Build an instance wired up enough to exercise its hooks. + + ``__new__`` bypasses ``__init__`` deliberately: the constructor + resolves CMake and pixi paths that are irrelevant to these hooks and + unavailable in the dev environment. + """ + builder = cls.__new__(cls) builder.build_dir_root = tmp_path - builder.platform_env = "macosx-py311" - builder.target = TargetPlatform("darwin", Arch.AARCH64) - builder.package_env_config = {"ARCH": "arm64"} + builder.platform_env = platform_env + builder.target = target + builder.package_env_config = dict(package_env_config) builder.venv_info_dict = {"venv_bin_path": str(tmp_path / "bin")} + builder.cmake_compiler_configurations = CMakeArgumentBuilder() + builder.cmake_itk_source_build_configurations = CMakeArgumentBuilder() return builder + + +@pytest.fixture +def macos_builder(tmp_path): + """A macOS builder wired up enough to exercise its hooks.""" + return _make_builder( + MacOSBuildPythonInstance, + tmp_path, + "macosx-py311", + TargetPlatform("darwin", Arch.AARCH64), + {"ARCH": "arm64"}, + ) + + +@pytest.fixture +def linux_builder(tmp_path): + """A manylinux x86_64 builder.""" + return _make_builder( + LinuxBuildPythonInstance, + tmp_path, + "manylinux228-py311", + TargetPlatform("linux", Arch.X86_64, "_2_28"), + {"ARCH": "x64"}, + ) + + +@pytest.fixture +def linux_aarch64_builder(tmp_path): + """A manylinux aarch64 builder, covering the cross-build triple.""" + return _make_builder( + LinuxBuildPythonInstance, + tmp_path, + "manylinux228-py311", + TargetPlatform("linux", Arch.AARCH64, "_2_28"), + {"ARCH": "aarch64"}, + ) + + +@pytest.fixture +def windows_builder(tmp_path): + """A Windows x86_64 builder.""" + return _make_builder( + WindowsBuildPythonInstance, + tmp_path, + "windows-py311", + TargetPlatform("windows", Arch.X86_64), + {"ARCH": "x64"}, + ) diff --git a/tests/test_final_wheel_import_test.py b/tests/test_final_wheel_import_test.py new file mode 100644 index 00000000..09719c25 --- /dev/null +++ b/tests/test_final_wheel_import_test.py @@ -0,0 +1,93 @@ +from pathlib import Path +from subprocess import CompletedProcess +from unittest.mock import patch + +import pytest + + +@pytest.fixture(autouse=True) +def _wire_final_import_test_config(linux_builder, tmp_path): + linux_builder.package_env_config.update( + { + "PYTHON_EXECUTABLE": "python3", + "IPP_SOURCE_DIR": Path(tmp_path), + } + ) + + +@pytest.fixture(autouse=True) +def _no_itk_packages_installed(): + """Default `pip list` to empty so the uninstall step is a no-op. + + Without this, `_pip_uninstall_itk_wildcard` would run the real `pip + list` on whatever interpreter the test happens to resolve, which is + both slow and liable to enumerate unrelated real packages. + """ + with patch( + "build_python_instance_base.subprocess.run", + return_value=CompletedProcess(args=[], returncode=0, stdout="", stderr=""), + ): + yield + + +def test_final_wheel_import_test_raises_on_failed_install(tmp_path, linux_builder): + """A failed pip install must fail the step, not just print a warning.""" + with patch.object(linux_builder, "echo_check_call", return_value=1): + with pytest.raises(RuntimeError): + linux_builder.final_wheel_import_test(tmp_path) + + +def test_final_wheel_import_test_raises_on_failed_import(tmp_path, linux_builder): + """Install succeeds but a later import check fails: step must fail.""" + statuses = iter([0, 1]) + with patch.object( + linux_builder, "echo_check_call", side_effect=lambda *a, **k: next(statuses) + ): + with pytest.raises(RuntimeError): + linux_builder.final_wheel_import_test(tmp_path) + + +def test_final_wheel_import_test_passes_when_every_check_succeeds( + tmp_path, linux_builder +): + with patch.object(linux_builder, "echo_check_call", return_value=0) as called: + linux_builder.final_wheel_import_test(tmp_path) + assert called.call_count == 5 + + +def test_final_wheel_import_test_uninstalls_before_installing(tmp_path, linux_builder): + """The stale-install bug: uninstall must run BEFORE `pip install itk`. + + Checking only that both calls happened would still pass if the + uninstall ran last, which would fix nothing. + """ + with patch( + "build_python_instance_base.subprocess.run", + return_value=CompletedProcess( + args=[], returncode=0, stdout="itk-core==6.0rc01\n", stderr="" + ), + ): + with patch.object(linux_builder, "echo_check_call", return_value=0) as called: + linux_builder.final_wheel_import_test(tmp_path) + + commands = [call.args[0] for call in called.call_args_list] + uninstall_index = next(i for i, cmd in enumerate(commands) if "uninstall" in cmd) + install_index = next(i for i, cmd in enumerate(commands) if "install" in cmd) + assert uninstall_index < install_index + + +def test_final_wheel_import_test_raises_when_uninstall_fails(tmp_path, linux_builder): + """A failed uninstall must fail the step, not silently proceed to install.""" + with patch( + "build_python_instance_base.subprocess.run", + return_value=CompletedProcess( + args=[], returncode=0, stdout="itk-core==6.0rc01\n", stderr="" + ), + ): + with patch.object(linux_builder, "echo_check_call", return_value=1) as called: + with pytest.raises(RuntimeError): + linux_builder.final_wheel_import_test(tmp_path) + + commands = [call.args[0] for call in called.call_args_list] + assert len(commands) == 1 + assert "uninstall" in commands[0] diff --git a/tests/test_platform_builders.py b/tests/test_platform_builders.py index bd4d3ee4..f6eb472b 100644 --- a/tests/test_platform_builders.py +++ b/tests/test_platform_builders.py @@ -1,22 +1,95 @@ import os import zipfile from pathlib import Path +from types import SimpleNamespace from unittest.mock import patch +import pytest from cmake_argument_builder import CMakeArgumentBuilder from target_platform import Arch, TargetPlatform, host_arch -def test_build_dir_suffix_matches_pixi_suffix_on_all_platforms(): - """The ITK--_ expression has exactly one source.""" - for os_name, arch in ( - ("linux", Arch.X86_64), - ("linux", Arch.AARCH64), - ("darwin", Arch.AARCH64), - ("windows", Arch.X86_64), +def test_build_dir_suffix_is_the_documented_token_per_platform(): + """Spell out the ``_{arch}`` token of ``ITK--_``. + + Literal expectations, so a change to the mapping fails here. Comparing + ``build_dir_suffix`` against ``pixi_suffix`` would not: the former is + defined as ``return self.pixi_suffix``. + """ + expected = { + ("linux", Arch.X86_64): "x64", + ("linux", Arch.AARCH64): "aarch64", + ("linux", Arch.I686): "x86", + ("darwin", Arch.AARCH64): "arm64", + ("darwin", Arch.X86_64): "x86_64", + ("windows", Arch.X86_64): "x64", + } + for (os_name, arch), token in expected.items(): + assert TargetPlatform(os_name, arch).build_dir_suffix == token + + +@pytest.mark.parametrize( + ("builder_fixture", "expected_suffix"), + [ + ("linux_builder", "x64"), + ("linux_aarch64_builder", "aarch64"), + ("macos_builder", "arm64"), + ("windows_builder", "x64"), + ], +) +def test_prepare_build_env_sets_itk_binary_dir_on_every_platform( + request, builder_fixture, expected_suffix +): + """The ITK binary directory has exactly one construction site. + + This drives the real template method, including each subclass's real + ``_configure_toolchain``. A subclass that reintroduced its own path + construction, or that supplied the wrong architecture token, fails here. + """ + builder = request.getfixturevalue(builder_fixture) + with ( + patch.object(builder, "venv_paths"), + patch.object(builder, "update_venv_itk_build_configurations"), + ): + builder.prepare_build_env() + + expected = ( + builder.build_dir_root + / "build" + / f"ITK-{builder.platform_env}-{builder.platform_env}_{expected_suffix}" + ).as_posix() + assert ( + builder.cmake_itk_source_build_configurations.get("ITK_BINARY_DIR:PATH") + == expected + ) + + +def test_linux_configure_toolchain_takes_the_triple_from_the_target( + linux_builder, linux_aarch64_builder +): + """The four-branch ARCH cascade is gone; the type supplies the triple.""" + for builder, triple in ( + (linux_builder, "x86_64-linux-gnu"), + (linux_aarch64_builder, "aarch64-linux-gnu"), ): - plat = TargetPlatform(os_name, arch) - assert plat.build_dir_suffix == plat.pixi_suffix + builder._configure_toolchain() + assert ( + builder.cmake_compiler_configurations.get( + "CMAKE_CXX_COMPILER_TARGET:STRING" + ) + == triple + ) + assert builder.package_env_config["USE_TBB"] == "ON" + # One key, one type: TBB_DIR is a str on Linux as well as Windows. + assert isinstance(builder.package_env_config["TBB_DIR"], str) + + +def test_windows_configure_toolchain_sets_tbb_dir_as_str(windows_builder): + windows_builder._configure_toolchain() + assert windows_builder.package_env_config["USE_TBB"] == "ON" + tbb_dir = windows_builder.package_env_config["TBB_DIR"] + assert isinstance(tbb_dir, str) + assert tbb_dir.endswith("TBB") def _make_wheel(path: Path, members: dict[str, str]) -> Path: @@ -54,6 +127,127 @@ def test_fixup_wheel_runs_delocate_when_binaries_present(tmp_path, macos_builder assert "--require-archs" in flat +def _make_build_env(tmp_path: Path) -> Path: + """A minimal ``/{bin,lib}`` layout for PYTHON_EXECUTABLE.""" + env_root = tmp_path / "env" + (env_root / "bin").mkdir(parents=True, exist_ok=True) + (env_root / "lib").mkdir(parents=True, exist_ok=True) + (env_root / "bin" / "python").touch() + return env_root + + +def _prepare_linux_fixup(linux_builder, tmp_path, module_source_dir=None): + linux_builder.package_env_config.update( + { + "MANYLINUX_VERSION": "_2_28", + "PYTHON_EXECUTABLE": str(_make_build_env(tmp_path) / "bin" / "python"), + "IPP_SUPERBUILD_BINARY_DIR": tmp_path / "superbuild", + } + ) + linux_builder.module_source_dir = module_source_dir + dist_dir = tmp_path / "dist" + dist_dir.mkdir(exist_ok=True) + wheel = _make_wheel( + dist_dir / "itk_core-6.0.0-cp311-abi3-linux_x86_64.whl", + {"itk/_ITKCommonPython.so": "\x7fELF"}, + ) + repaired_name = "itk_core-6.0.0-cp311-abi3-manylinux_2_28_x86_64.whl" + return wheel, dist_dir / repaired_name + + +def test_fixup_wheel_raises_and_keeps_original_when_repair_fails( + tmp_path, linux_builder +): + """A failed auditwheel repair must not delete the input wheel.""" + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + with patch.object(linux_builder, "echo_check_call", return_value=1): + with pytest.raises(RuntimeError): + linux_builder.fixup_wheel(str(wheel)) + assert wheel.exists() + assert not repaired.exists() + + +def test_fixup_wheel_raises_when_repair_reports_success_but_output_missing( + tmp_path, linux_builder +): + """echo_check_call reporting 0 is not enough without the output wheel.""" + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + with patch.object(linux_builder, "echo_check_call", return_value=0): + with pytest.raises(RuntimeError): + linux_builder.fixup_wheel(str(wheel)) + assert wheel.exists() + + +def test_fixup_wheel_removes_original_only_after_verified_repair( + tmp_path, linux_builder +): + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + + def fake_repair(cmd, env=None): + repaired.touch() + return 0 + + with patch.object(linux_builder, "echo_check_call", side_effect=fake_repair): + linux_builder.fixup_wheel(str(wheel)) + assert not wheel.exists() + assert repaired.exists() + + +def _capture_repair_env(builder, wheel, repaired) -> dict: + captured_env = {} + + def fake_repair(cmd, env=None): + captured_env.update(env) + repaired.touch() + return 0 + + with patch.object(builder, "echo_check_call", side_effect=fake_repair): + builder.fixup_wheel(str(wheel)) + return captured_env + + +def test_fixup_wheel_ld_library_path_starts_with_the_build_env_lib_dir( + tmp_path, linux_builder, monkeypatch +): + """The environment's own lib dir must precede the system dirs. + + It is derived from PYTHON_EXECUTABLE, so it is present whether or not + CONDA_PREFIX happens to be exported. + """ + monkeypatch.delenv("CONDA_PREFIX", raising=False) + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + ld_library_path = _capture_repair_env(linux_builder, wheel, repaired)[ + "LD_LIBRARY_PATH" + ] + env_lib = str((_make_build_env(tmp_path) / "lib").resolve()) + # Compare positions in the joined string rather than indices in a + # ":"-split list: tmp_path carries a drive letter off a POSIX box, and + # its colon would split one entry into two. + assert ld_library_path.index(env_lib) < ld_library_path.index("/usr/lib64") + + +def test_fixup_wheel_ld_library_path_has_no_empty_entries(tmp_path, linux_builder): + """An empty entry means the working directory to the dynamic loader.""" + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + entries = _capture_repair_env(linux_builder, wheel, repaired)[ + "LD_LIBRARY_PATH" + ].split(":") + assert "" not in entries + + +def test_fixup_wheel_fails_loudly_when_the_build_env_lib_dir_is_absent( + tmp_path, linux_builder +): + """Silently omitting the env lib dir is what reintroduced defect C.""" + wheel, repaired = _prepare_linux_fixup(linux_builder, tmp_path) + linux_builder.package_env_config["PYTHON_EXECUTABLE"] = str( + tmp_path / "no-such-env" / "bin" / "python" + ) + with patch.object(linux_builder, "echo_check_call", return_value=0): + with pytest.raises(RuntimeError, match="library directory"): + linux_builder.fixup_wheel(str(wheel)) + + def test_posix_build_tarball_delegates(macos_builder): """build_tarball is a base hook now; the Middle Man override is gone.""" with patch.object(macos_builder, "create_posix_tarball") as created: @@ -69,7 +263,6 @@ def test_prepare_build_env_uses_existing_downloaded_binaries(macos_builder): build_root = macos_builder.build_dir_root / "build" doubled = build_root / "ITK-macosx-py311-macosx-py311_arm64" doubled.mkdir(parents=True) - macos_builder.cmake_itk_source_build_configurations = CMakeArgumentBuilder() with ( patch.object(macos_builder, "venv_paths"), patch.object(macos_builder, "update_venv_itk_build_configurations"), @@ -84,7 +277,6 @@ def test_prepare_build_env_uses_existing_downloaded_binaries(macos_builder): def test_configure_toolchain_routes_arch_through_the_target(macos_builder): """macOS reads CMAKE_OSX_ARCHITECTURES from the type, not from ARCH.""" - macos_builder.cmake_compiler_configurations = CMakeArgumentBuilder() macos_builder.package_env_config = {"ARCH": "ignored-by-design"} macos_builder._configure_toolchain() assert ( @@ -96,8 +288,205 @@ def test_configure_toolchain_routes_arch_through_the_target(macos_builder): assert macos_builder.package_env_config["TBB_DIR"] == "NOT_FOUND" -def test_host_arch_is_independent_of_target(): - """QEMU reads the host; a cross-build sets only the target.""" +def test_configure_toolchain_sets_python_host_platform_from_the_same_values( + macos_builder, monkeypatch +): + """_PYTHON_HOST_PLATFORM must be derived, not a second hardcoded fact. + + The version is a sentinel no production default could equal, so a + literal (e.g. "macosx-14.0-arm64") cannot coincidentally satisfy this. + Fails if the version or the architecture is hardcoded, or if the two + settings are allowed to drift apart. + """ + monkeypatch.delenv("_PYTHON_HOST_PLATFORM", raising=False) + monkeypatch.delenv("MACOSX_DEPLOYMENT_TARGET", raising=False) + macos_builder.package_env_config["MACOSX_DEPLOYMENT_TARGET"] = "99.5" + macos_builder._configure_toolchain() + assert ( + os.environ["_PYTHON_HOST_PLATFORM"] + == f"macosx-99.5-{macos_builder.target.cmake_osx_arch}" + ) + + +def test_configure_toolchain_leaves_python_host_platform_unset_without_a_target( + macos_builder, monkeypatch +): + """No macosx_target means no malformed 'macosx--' value.""" + monkeypatch.delenv("_PYTHON_HOST_PLATFORM", raising=False) + macos_builder.package_env_config.pop("MACOSX_DEPLOYMENT_TARGET", None) + macos_builder._configure_toolchain() + assert "_PYTHON_HOST_PLATFORM" not in os.environ + + +def test_host_arch_is_independent_of_target(monkeypatch): + """QEMU reads the host; a cross-build sets only the target. + + The host is pinned to Linux/x86_64 so the assertion does not depend on + where the suite runs. Off a POSIX box ``detect`` would otherwise pair + the real OS with the requested aarch64 target and raise on the + unsupported windows/aarch64 combination before reaching the assert. + """ + monkeypatch.setattr( + os, + "uname", + lambda: SimpleNamespace(sysname="Linux", machine="x86_64"), + raising=False, + ) + monkeypatch.setattr(os, "name", "posix") target = TargetPlatform.detect({"TARGET_ARCH": "aarch64"}) assert target.arch is Arch.AARCH64 assert host_arch({}) in set(Arch) + + +def _checking_echo(failing_tool: str): + """A stand-in with build_environment.echo_check_call's semantics. + + It returns a non-zero status for *failing_tool* and raises only when + the caller forwarded ``check=True`` — so a call site that omits the + flag sees the failure silently, exactly as production would. + """ + + def _echo(cmd, *args, check: bool = False, **kwargs) -> int: + rendered = " ".join(str(c) for c in cmd) + if failing_tool in rendered: + if check: + raise RuntimeError(f"command failed: {rendered}") + return 1 + return 0 + + return _echo + + +def _wire_itk_cplusplus(builder, tmp_path): + builder.cleanup = False + builder.build_node_cpu_count = 2 + builder.cmake_cmdline_definitions = CMakeArgumentBuilder() + builder.cmake_itk_source_build_configurations.set( + "ITK_BINARY_DIR:PATH", str(tmp_path / "itk-bld") + ) + builder.package_env_config.update( + { + "CMAKE_EXECUTABLE": "cmake", + "NINJA_EXECUTABLE": "ninja", + "ITK_SOURCE_DIR": str(tmp_path / "ITK"), + } + ) + + +@pytest.mark.parametrize("failing_tool", ["cmake", "ninja"]) +def test_itk_cplusplus_build_raises_when_a_step_fails( + tmp_path, linux_builder, failing_tool +): + """A failed configure or ninja must not be recorded as a done step.""" + _wire_itk_cplusplus(linux_builder, tmp_path) + with patch.object( + linux_builder, "echo_check_call", side_effect=_checking_echo(failing_tool) + ): + with pytest.raises(RuntimeError): + linux_builder.build_wrapped_itk_cplusplus() + + +def test_superbuild_support_components_raise_when_cmake_fails(tmp_path, linux_builder): + """The superbuild configure/build pair must stop the run on failure.""" + linux_builder.cmake_cmdline_definitions = CMakeArgumentBuilder() + linux_builder.package_env_config.update( + { + "CMAKE_EXECUTABLE": "cmake", + "USE_TBB": "ON", + "ITK_SOURCE_DIR": str(tmp_path / "ITK"), + "ITK_GIT_TAG": "main", + "IPP_SOURCE_DIR": tmp_path / "ipp", + "IPP_SUPERBUILD_BINARY_DIR": tmp_path / "superbuild", + } + ) + with patch.object( + linux_builder, "echo_check_call", side_effect=_checking_echo("cmake") + ): + with pytest.raises(RuntimeError): + linux_builder.build_superbuild_support_components() + + +def _prepare_metawheel(linux_builder, tmp_path) -> Path: + linux_builder.package_env_config.update( + { + "MANYLINUX_VERSION": "_2_28", + "PYTHON_EXECUTABLE": str(_make_build_env(tmp_path) / "bin" / "python"), + } + ) + dist_dir = tmp_path / "dist" + dist_dir.mkdir(exist_ok=True) + metawhl = _make_wheel( + dist_dir / "itk-6.0.0-cp311-abi3-linux_x86_64.whl", + {"itk-6.0.0.dist-info/WHEEL": "Tag: cp311-abi3-linux_x86_64"}, + ) + return metawhl + + +def test_metawheel_is_kept_when_repack_produces_nothing(tmp_path, linux_builder): + """The original meta-wheel must survive a repack that yields no wheel.""" + metawhl = _prepare_metawheel(linux_builder, tmp_path) + + def fake_unpack(cmd, *args, check: bool = False, **kwargs) -> int: + rendered = [str(c) for c in cmd] + if "unpack" in rendered: + unpacked = tmp_path / "metawheel" / "itk-6.0.0" / "itk.dist-info" + unpacked.mkdir(parents=True, exist_ok=True) + (unpacked / "WHEEL").write_text("Tag: cp311-abi3-linux_x86_64\n") + # "pack" reports success but writes nothing. + return 0 + + with patch.object(linux_builder, "echo_check_call", side_effect=fake_unpack): + with pytest.raises(RuntimeError, match="no wheel"): + linux_builder.post_build_fixup() + assert metawhl.exists() + + +def test_metawheel_repack_failure_raises_and_keeps_the_original( + tmp_path, linux_builder +): + """`wheel pack` returning non-zero must stop the fixup, not be ignored.""" + metawhl = _prepare_metawheel(linux_builder, tmp_path) + + def fake(cmd, *args, check: bool = False, **kwargs) -> int: + rendered = [str(c) for c in cmd] + if "unpack" in rendered: + unpacked = tmp_path / "metawheel" / "itk-6.0.0" / "itk.dist-info" + unpacked.mkdir(parents=True, exist_ok=True) + (unpacked / "WHEEL").write_text("Tag: cp311-abi3-linux_x86_64\n") + return 0 + if check: + raise RuntimeError("wheel pack failed") + return 1 + + with patch.object(linux_builder, "echo_check_call", side_effect=fake): + with pytest.raises(RuntimeError): + linux_builder.post_build_fixup() + assert metawhl.exists() + + +def test_windows_fixup_wheel_raises_when_delvewheel_fails(tmp_path, windows_builder): + """A failed delvewheel repair must not be reported as a success.""" + dist_dir = windows_builder.build_dir_root / "dist" + dist_dir.mkdir(parents=True, exist_ok=True) + wheel = dist_dir / "itk_core-6.0.0-cp311-abi3-win_amd64.whl" + wheel.touch() + with patch.object(windows_builder, "echo_check_call", return_value=1): + with pytest.raises(RuntimeError, match="delvewheel"): + windows_builder.fixup_wheel(str(wheel), lib_paths="C:/tbb/bin") + + +def test_windows_lib_paths_are_split_on_semicolons(windows_builder): + """--lib-paths arrives as one semicolon-delimited string, not characters.""" + windows_builder.windows_extra_lib_paths = ["C:/tbb/bin;C:/extra/bin;"] + captured = {} + + def fake_fixup_wheels(lib_paths): + captured["lib_paths"] = lib_paths + + with patch.object(windows_builder, "fixup_wheels", side_effect=fake_fixup_wheels): + windows_builder.post_build_fixup() + + entries = captured["lib_paths"].split(";") + assert "C:/tbb/bin" in entries + assert "C:/extra/bin" in entries + assert not any(len(e) == 1 for e in entries) From fb71b18c19ef91aadc635469698999a4fd635a92 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 16:06:32 -0500 Subject: [PATCH 07/16] ENH: Add wheel build driver, packaging, and publishing Co-Authored-By: Cavan Riley --- .pypirc.example | 23 + Utilities/scripts/build_all_latest_wheels.py | 205 ++++++ Utilities/scripts/publish_tarball_cache.py | 130 ++++ Utilities/scripts/publish_wheels.py | 109 +++ scripts/build_python_instance_base.py | 189 +----- scripts/build_wheels.py | 669 +++++++++++++++++++ scripts/install_pixi.py | 154 +++++ scripts/linux_build_python_instance.py | 9 +- scripts/pyproject.toml.in | 6 +- scripts/wheel_builder_utils.py | 117 +--- scripts/wheel_packaging.py | 219 ++++++ tests/test_itk_source_without_git.py | 108 +++ tests/test_module_dependencies_root.py | 49 ++ tests/test_target_platform.py | 56 +- tests/test_wheel_builder_utils.py | 8 + tests/test_wheel_packaging.py | 156 +++++ 16 files changed, 1900 insertions(+), 307 deletions(-) create mode 100644 .pypirc.example create mode 100755 Utilities/scripts/build_all_latest_wheels.py create mode 100644 Utilities/scripts/publish_tarball_cache.py create mode 100644 Utilities/scripts/publish_wheels.py create mode 100755 scripts/build_wheels.py create mode 100755 scripts/install_pixi.py create mode 100644 scripts/wheel_packaging.py create mode 100644 tests/test_itk_source_without_git.py create mode 100644 tests/test_module_dependencies_root.py create mode 100644 tests/test_wheel_packaging.py diff --git a/.pypirc.example b/.pypirc.example new file mode 100644 index 00000000..05b64161 --- /dev/null +++ b/.pypirc.example @@ -0,0 +1,23 @@ +# .pypirc.example -- DO NOT commit real credentials +# Copy to ~/.pypirc and fill in your token. +# +# Generate a token at: https://pypi.org/manage/account/token/ +# For TestPyPI: https://test.pypi.org/manage/account/token/ +# +# Alternatively, set environment variables: +# TWINE_USERNAME=__token__ +# TWINE_PASSWORD=pypi- + +[distutils] +index-servers = + pypi + testpypi + +[pypi] +username = __token__ +password = pypi-YOUR-TOKEN-HERE + +[testpypi] +repository = https://test.pypi.org/legacy/ +username = __token__ +password = pypi-YOUR-TOKEN-HERE diff --git a/Utilities/scripts/build_all_latest_wheels.py b/Utilities/scripts/build_all_latest_wheels.py new file mode 100755 index 00000000..01a39abc --- /dev/null +++ b/Utilities/scripts/build_all_latest_wheels.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Build ITK + all remote module Python wheels from latest main branches. + +Usage:: + + python scripts/build_all_latest_wheels.py [--platform-env linux-py311] + python scripts/build_all_latest_wheels.py --help +""" + +import argparse +import re +import shutil +import subprocess +import sys +from datetime import datetime +from pathlib import Path + + +def run(cmd: list[str], **kwargs) -> subprocess.CompletedProcess: + print(f" $ {' '.join(str(c) for c in cmd)}") + return subprocess.run(cmd, check=True, **kwargs) + + +def clone( + repo: str, dest: Path, branch: str | None = None, depth: int | None = 1 +) -> bool: + cmd = ["git", "clone"] + if depth is not None: + cmd += ["--depth", str(depth)] + if branch: + cmd += ["--branch", branch] + cmd += [repo, str(dest)] + try: + run(cmd, capture_output=True) + return True + except subprocess.CalledProcessError: + return False + + +def parse_remote_modules(itk_dir: Path) -> list[tuple[str, str]]: + """Parse ITK remote .cmake files, return [(name, git_url), ...].""" + modules = [] + for rc in sorted((itk_dir / "Modules" / "Remote").glob("*.remote.cmake")): + name = rc.stem.replace(".remote", "") + text = rc.read_text() + m = re.search(r"GIT_REPOSITORY\s+(\S+)", text) + if m: + modules.append((name, m.group(1))) + return modules + + +def build_wheels( + ipp_dir: Path, + platform_env: str, + build_dir: Path, + itk_source: Path, + itk_ref: str = "main", + module_source: Path | None = None, + skip_itk: bool = False, +) -> bool: + cmd = [ + "pixi", + "run", + "-e", + platform_env, + "--", + "python", + "scripts/build_wheels.py", + "--platform-env", + platform_env, + "--itk-git-tag", + itk_ref, + "--itk-source-dir", + str(itk_source), + "--no-build-itk-tarball-cache", + "--no-use-sudo", + "--build-dir-root", + str(build_dir), + ] + if module_source: + cmd += ["--module-source-dir", str(module_source)] + if skip_itk: + cmd += ["--skip-itk-build", "--skip-itk-wheel-build"] + + try: + run(cmd, cwd=ipp_dir) + return True + except subprocess.CalledProcessError: + return False + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--platform-env", default="linux-py311", help="Pixi environment name" + ) + parser.add_argument( + "--ipp-branch", + default="python-build-system", + help="ITKPythonPackage branch to use", + ) + parser.add_argument( + "--ipp-repo", + default="https://github.com/BRAINSia/ITKPythonPackage.git", + help="ITKPythonPackage git URL", + ) + parser.add_argument( + "--itk-repo", + default="https://github.com/InsightSoftwareConsortium/ITK.git", + help="ITK git URL", + ) + parser.add_argument( + "--itk-ref", + default="main", + help="ITK branch, tag, or commit hash (default: main)", + ) + args = parser.parse_args() + + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + workdir = Path(f"/tmp/{timestamp}_LatestITKPython") + dist_dir = workdir / "dist" + dist_dir.mkdir(parents=True) + build_dir = workdir / "build" + + print(f"=== Build directory: {workdir}") + print(f"=== Platform: {args.platform_env}") + print(f"=== ITK ref: {args.itk_ref}") + + # 1) Clone ITK + print(f"=== Cloning ITK ({args.itk_ref})...") + itk_dir = workdir / "ITK" + clone(args.itk_repo, itk_dir, depth=None) + run(["git", "checkout", args.itk_ref], cwd=itk_dir) + + # 2) Clone ITKPythonPackage + print(f"=== Cloning ITKPythonPackage ({args.ipp_branch})...") + ipp_dir = workdir / "ITKPythonPackage" + clone(args.ipp_repo, ipp_dir, branch=args.ipp_branch) + + # 3) Clone remote modules + print("=== Cloning remote modules...") + modules_dir = workdir / "modules" + modules_dir.mkdir() + remote_modules = parse_remote_modules(itk_dir) + + module_list: list[str] = [] + for name, repo in remote_modules: + mod_dir = modules_dir / name + if not clone(repo, mod_dir): + print(f" WARNING: Failed to clone {name}, skipping") + continue + # Keep only modules with Python wrapping + if (mod_dir / "wrapping").is_dir() and (mod_dir / "pyproject.toml").is_file(): + module_list.append(name) + else: + shutil.rmtree(mod_dir) + + print(f"=== {len(module_list)} modules with Python wrapping") + + # 4) Build ITK wheels + print("=== Building ITK Python wheels...") + if not build_wheels(ipp_dir, args.platform_env, build_dir, itk_dir, args.itk_ref): + print("FATAL: ITK wheel build failed") + sys.exit(1) + + # Copy ITK wheels to dist + for whl in (build_dir / "dist").glob("*.whl"): + shutil.copy2(whl, dist_dir) + + # 5) Build each remote module wheel + failed: list[str] = [] + for name in module_list: + print(f"=== Building {name}...") + mod_dir = modules_dir / name + if build_wheels( + ipp_dir, + args.platform_env, + build_dir, + itk_dir, + args.itk_ref, + module_source=mod_dir, + skip_itk=True, + ): + for whl in (mod_dir / "dist").glob("*.whl"): + shutil.copy2(whl, dist_dir) + else: + print(f" FAILED: {name}") + failed.append(name) + + # 6) Summary + wheels = list(dist_dir.glob("*.whl")) + print() + print("=== Build complete ===") + print(f"Wheels: {dist_dir}") + print(f"{len(wheels)} total wheels produced") + + if failed: + print(f"\nFailed modules ({len(failed)}):") + for name in failed: + print(f" {name}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Utilities/scripts/publish_tarball_cache.py b/Utilities/scripts/publish_tarball_cache.py new file mode 100644 index 00000000..440512f9 --- /dev/null +++ b/Utilities/scripts/publish_tarball_cache.py @@ -0,0 +1,130 @@ +"""Upload ITK build cache tarballs to a GitHub Release. + +Usage: + pixi run -e publish publish-tarball-cache --itk-package-version v6.0b02 + pixi run -e publish publish-tarball-cache --itk-package-version v6.0b02 --create-release + +Authentication: + Set the GH_TOKEN environment variable, or run `gh auth login` beforehand. + +Cache files are expected at {build-dir-root}/dist/ITKPythonBuilds-*.tar.zst +(POSIX) or {build-dir-root}/ITKPythonBuilds-*.zip (Windows). Caches from older +builds at {build-dir-root}/../ITKPythonBuilds-*.tar.zst are also picked up. +""" + +import argparse +import subprocess +import sys +from pathlib import Path + +ITKPYTHONBUILDS_REPO = "InsightSoftwareConsortium/ITKPythonBuilds" + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Upload tarball caches to a GitHub Release on ITKPythonBuilds" + ) + parser.add_argument( + "--build-dir-root", + type=Path, + default=Path(__file__).resolve().parent.parent.parent + / "ITKPythonPackage-build", + help="Root of the build directory (tarballs are in its parent). Default: ../ITKPythonPackage-build", + ) + parser.add_argument( + "--itk-package-version", + required=True, + help="Release tag name, e.g. v6.0b02", + ) + parser.add_argument( + "--repo", + default=ITKPYTHONBUILDS_REPO, + help=f"GitHub repository for the release (default: {ITKPYTHONBUILDS_REPO})", + ) + parser.add_argument( + "--create-release", + action="store_true", + help="Create the GitHub release if it does not already exist", + ) + args = parser.parse_args() + + build_directory = Path(args.build_dir_root) + dist_directory = build_directory / "dist" + legacy_tarball_dir = build_directory.parent + # POSIX builds write .tar.zst to dist/, Windows builds write .zip to the build + # directory; caches from older builds sit beside the build directory. + tarballs = sorted( + list(dist_directory.glob("ITKPythonBuilds-*.tar.zst")) + + list(legacy_tarball_dir.glob("ITKPythonBuilds-*.tar.zst")) + + list(build_directory.glob("ITKPythonBuilds-*.zip")) + ) + + if not tarballs: + print( + f"Error: No ITKPythonBuilds-*.tar.zst or .zip files found in " + f"{dist_directory}, {legacy_tarball_dir}, or {build_directory}.", + file=sys.stderr, + ) + return 1 + + print( + f"Found {len(tarballs)} cache file(s) under {build_directory} " + f"and {legacy_tarball_dir}:" + ) + for tb in tarballs: + size_mb = tb.stat().st_size / (1024 * 1024) + print(f" {tb.name} ({size_mb:.1f} MB)") + + tag = args.itk_package_version + + # Check if the release exists + result = subprocess.run( + ["gh", "release", "view", tag, "--repo", args.repo], + capture_output=True, + text=True, + ) + if result.returncode != 0: + if args.create_release: + print(f"\nCreating release '{tag}' on {args.repo}...") + create_result = subprocess.run( + [ + "gh", + "release", + "create", + tag, + "--repo", + args.repo, + "--title", + tag, + "--notes", + f"ITK Python build cache for {tag}", + ], + ) + if create_result.returncode != 0: + print("Error: Failed to create release.", file=sys.stderr) + return 1 + else: + print( + f"Error: Failed to create release with code: {result.returncode}\nwith message:\n{result.stderr}", + file=sys.stderr, + ) + return 1 + else: + print(f"\nRelease '{tag}' exists on {args.repo}.") + + # Upload each tarball (--clobber replaces existing assets with the same name) + for tb in tarballs: + print(f"Uploading {tb.name}...") + upload_result = subprocess.run( + ["gh", "release", "upload", tag, str(tb), "--repo", args.repo, "--clobber"], + ) + if upload_result.returncode != 0: + print(f"Error: Failed to upload {tb.name}.", file=sys.stderr) + return 1 + + print("\nAll uploads complete.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Utilities/scripts/publish_wheels.py b/Utilities/scripts/publish_wheels.py new file mode 100644 index 00000000..1daae0fd --- /dev/null +++ b/Utilities/scripts/publish_wheels.py @@ -0,0 +1,109 @@ +"""Upload ITK Python wheels to PyPI or TestPyPI. + +Usage: + pixi run -e publish publish-wheels --dist-directory /path/to/dist + pixi run -e publish publish-wheels --dist-directory /path/to/dist --test + +Authentication: + Set TWINE_USERNAME and TWINE_PASSWORD environment variables. + For token-based auth: + TWINE_USERNAME=__token__ + TWINE_PASSWORD=pypi- + + Alternatively, configure ~/.pypirc (see .pypirc.example in the repo root). +""" + +import argparse +import subprocess +import sys +from pathlib import Path + +TESTPYPI_URL = "https://test.pypi.org/legacy/" + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Upload ITK wheels to PyPI or TestPyPI" + ) + parser.add_argument( + "--dist-directory", + type=Path, + default=Path(__file__).resolve().parent.parent.parent + / "ITKPythonPackage-build/dist", + help="Root of the build dist directory containing dist/*.whl (default: ../ITKPythonPackage-build/dist)", + ) + parser.add_argument( + "--test", + action="store_true", + help="Upload to TestPyPI instead of production PyPI", + ) + parser.add_argument( + "--repository-url", + type=str, + default=None, + help="Custom repository URL (overrides --test)", + ) + parser.add_argument( + "--skip-existing", + action="store_true", + help="Skip wheels that have already been uploaded", + ) + args = parser.parse_args() + + dist_dir = args.dist_directory + wheels = sorted(dist_dir.glob("*.whl")) + + if not wheels: + print(f"Error: No .whl files found in {dist_dir}", file=sys.stderr) + return 1 + + print(f"Found {len(wheels)} wheel(s) in {dist_dir}:") + for w in wheels: + print(f" {w.name}") + + # PyPI rejects plain linux_* platform tags; only manylinux/musllinux wheels are uploadable. + non_portable = [ + w + for w in wheels + if any(tag.startswith("linux_") for tag in w.stem.split("-")[-1].split(".")) + ] + if non_portable: + print( + "Error: these wheels have a linux_* platform tag and cannot be uploaded " + "to PyPI. Rebuild them with a manylinux228-py* environment:", + file=sys.stderr, + ) + for w in non_portable: + print(f" {w.name}", file=sys.stderr) + return 1 + + # Validate wheel metadata before uploading + print("\nRunning twine check...") + result = subprocess.run( + ["twine", "check", *(str(w) for w in wheels)], + ) + if result.returncode != 0: + print( + "Error: twine check failed. Fix metadata issues before uploading.", + file=sys.stderr, + ) + return 1 + + # Build upload command + cmd = ["twine", "upload"] + if args.repository_url: + cmd += ["--repository-url", args.repository_url] + elif args.test: + cmd += ["--repository-url", TESTPYPI_URL] + if args.skip_existing: + cmd.append("--skip-existing") + cmd += [str(w) for w in wheels] + + target = args.repository_url or (TESTPYPI_URL if args.test else "PyPI") + print(f"\nUploading to {target}...") + result = subprocess.run(cmd) + return result.returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_python_instance_base.py b/scripts/build_python_instance_base.py index 4a3b8498..02088224 100644 --- a/scripts/build_python_instance_base.py +++ b/scripts/build_python_instance_base.py @@ -5,10 +5,10 @@ from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Callable -from os import environ from pathlib import Path import build_environment +import wheel_packaging from BuildManager import BuildManager from cmake_argument_builder import CMakeArgumentBuilder from module_dependencies import build_module_dependencies, update_module_itk_deps @@ -17,7 +17,6 @@ from wheel_builder_utils import ( _remove_tree, _which, - get_default_platform_build, ) @@ -564,67 +563,8 @@ def build_tarball(self): pass def post_build_cleanup(self) -> None: - """Remove intermediate build artifacts, leaving ``dist/`` intact. - - Actions: - - remove oneTBB-prefix (symlink or dir) - - remove ITKPythonPackage/, tools/, _skbuild/, build/ - - remove top-level *.egg-info - - remove ITK-* build tree and tarballs - - if ITK_MODULE_PREQ is set, remove cloned module dirs - """ - base = Path(self.package_env_config["IPP_SOURCE_DIR"]) - - def rm(tree_path: Path): - try: - _remove_tree(tree_path) - except Exception: - pass - - # 1) unlink oneTBB-prefix if it's a symlink or file - tbb_prefix_dir = base / "oneTBB-prefix" - try: - if tbb_prefix_dir.is_symlink() or tbb_prefix_dir.is_file(): - tbb_prefix_dir.unlink(missing_ok=True) # type: ignore[arg-type] - elif tbb_prefix_dir.exists(): - rm(tbb_prefix_dir) - except Exception: - pass - - # 2) standard build directories - for rel in ("ITKPythonPackage", "tools", "_skbuild", "build"): - rm(base / rel) - - # 3) egg-info folders at top-level - for p in base.glob("*.egg-info"): - rm(p) - - # 4) ITK build tree and tarballs, both under the build root rather than - # the source checkout: ITK--_ in build/, - # and the cache in dist/. - target_arch = self.package_env_config["ARCH"] - for p in (self.build_dir_root / "build").glob(f"ITK-*_{target_arch}"): - rm(p) - - # Tarballs - for p in (self.build_dir_root / "dist").glob("ITKPythonBuilds-*.tar*"): - rm(p) - - # 5) Optional module prerequisites cleanup (ITK_MODULE_PREQ) - # Format: "InsightSoftwareConsortium/ITKModuleA@v1.0:Kitware/ITKModuleB@sha" - itk_preq = self.package_env_config.get("ITK_MODULE_PREQ") or environ.get( - "ITK_MODULE_PREQ", "" - ) - if itk_preq: - for entry in itk_preq.split(":"): - entry = entry.strip() - if not entry: - continue - try: - module_name = entry.split("@", 1)[0].split("/", 1)[1] - except Exception: - continue - rm(base / module_name) + """Remove intermediate build artifacts, leaving ``dist/`` intact.""" + wheel_packaging.post_build_cleanup(self) def prepare_build_env(self) -> None: """Skeleton shared by every platform. @@ -946,127 +886,8 @@ def build_wrapped_itk_cplusplus(self): print("# FINISHED-Build ITK C++") def create_posix_tarball(self): - """Create a compressed tarball of the ITK Python build tree. - - Mirrors the historical scripts/*-build-tarball.sh behavior: - - zstd compress with options (-10 -T6 --long=31) - - Warns if directory structure doesn't match expected layout for GitHub Actions. - """ - arch_postfix: str = f"{self.package_env_config['ARCH']}" - # Fixup platform name for macOS, eventually need to standardize on macosx naming convention - platform_name: str = get_default_platform_build().split("-")[0] - tar_name: str = f"ITKPythonBuilds-{platform_name}-{arch_postfix}.tar" - itk_packaging_reference_dir = self.build_dir_root.parent - - # Write the cache under the build tree: container builds bind-mount that - # directory, while the archive root above it is not always mounted. dist/ - # is excluded from the archive below, so the cache cannot capture itself. - tarball_output_dir: Path = self.build_dir_root / "dist" - tarball_output_dir.mkdir(parents=True, exist_ok=True) - tar_path: Path = tarball_output_dir / tar_name - zst_path: Path = tarball_output_dir / f"{tar_name}.zst" - - itk_resources_build_dir: Path = self.build_dir_root - ipp_source_dir: Path = self.package_env_config["IPP_SOURCE_DIR"] - - # Validate directory structure and determine tarball strategy - issues = [] - - # Try to use relative paths first - try: - rel_build = itk_resources_build_dir.relative_to(itk_packaging_reference_dir) - rel_ipp = ipp_source_dir.relative_to(itk_packaging_reference_dir) - except ValueError: - # Fall back to absolute paths - rel_build = itk_resources_build_dir - rel_ipp = ipp_source_dir - itk_packaging_reference_dir = Path( - "/" - ) # Tar from root when using absolute paths - - if itk_resources_build_dir.parent != ipp_source_dir.parent: - issues.append("Build and source dirs are not siblings") - - if ipp_source_dir.name != "ITKPythonPackage": - issues.append( - f"Source dir is '{ipp_source_dir.name}', expected 'ITKPythonPackage'" - ) - - # Issue consolidated warning for compatibility issues - if issues: - print("\n" + "=" * 70) - print("WARNING: Tarball will NOT be compatible with GitHub Actions") - print("=" * 70) - for issue in issues: - print(f" * {issue}") - print( - "\nExpected structure: /{ITKPythonPackage, ITKPythonPackage-build}" - ) - print(f"Current: Build={itk_resources_build_dir}") - print(f" Source={ipp_source_dir}") - print( - "\nTarball will be created for local reuse but may not work in CI/CD." - ) - print("=" * 70 + "\n") - - # Build tarball include paths - tarball_include_paths = [ - str(rel_build), - str(rel_ipp), - ] - - if tar_path.exists(): - print(f"Removing existing tarball {tar_path}") - tar_path.unlink() - if zst_path.exists(): - print(f"Removing existing zstd tarball {zst_path}") - zst_path.unlink() - - # Create tarball - self.echo_check_call( - [ - "tar", - "-C", - str(itk_packaging_reference_dir), - "-cf", - str(tar_path), - "--exclude=*.o", - "--exclude=*.whl", # Do not include built wheels - "--exclude=*/dist/*", # Do not include the dist whl output directory - "--exclude=*/wheelbuilds/*", # Do not include the wheelbuild support directory - "--exclude=*/__pycache__/*", # Do not include __pycache__ - "--exclude=install_manifest_*.txt", # Do not include install manifest files - "--exclude=._*", # Exclude mac dot files - "--exclude=*/.git/*", - "--exclude=*/.idea/*", - "--exclude=*/.pixi/*", - "--exclude=*/castxml_inputs/*", - "--exclude=*/Wrapping/Modules/*", - *tarball_include_paths, - ] - ) - - # Compress with zstd - self.echo_check_call( - [ - "zstd", - "-f", - "-10", - "-T6", - "--long=31", - str(tar_path), - "-o", - str(zst_path), - ] - ) - - # The uncompressed archive is several GB; only the compressed cache is published. - tar_path.unlink(missing_ok=True) - - print(f"Tarball created: {zst_path}") - if issues: - print("Compatibility warnings above - review before using in CI/CD") + """Create a compressed tarball of the ITK Python build tree.""" + wheel_packaging.create_posix_tarball(self) def get_pixi_environment_name(self): """Delegate, naming the pixi environment for this build.""" diff --git a/scripts/build_wheels.py b/scripts/build_wheels.py new file mode 100755 index 00000000..4bd4c985 --- /dev/null +++ b/scripts/build_wheels.py @@ -0,0 +1,669 @@ +#!/usr/bin/env python +import argparse +import os +import shlex +import sys +from pathlib import Path + +from target_platform import Arch, TargetPlatform +from wheel_builder_utils import ( + abort_if_wrong_pixi_environment, + compute_itk_package_version, + get_default_platform_build, + resolve_oci_exe, + run_commandLine_subprocess, + which_required, + which_required_in_pixi_env, +) + + +def remotemodulebuildandtestaction() -> dict[str, str]: + """Collect GitHub Actions environment variables for remote module builds. + + The following environment variables are defined by the + ``ITKRemoteModuleBuildTestPackageAction`` GitHub Action. Variables + marked *active* are read from the environment with a default + fallback; those marked *not used* are documented here for + cross-reference but are no longer consumed at runtime. + + Environment Variables + --------------------- + ITK_PACKAGE_VERSION : str (active) + PEP 440 version string for ITK packages. + GitHub Action source: ``inputs.itk-wheel-tag``. + Default ``"auto"`` triggers automatic version computation. + ITKPYTHONPACKAGE_TAG : str (not used — GitHub Actions scripts only) + Git tag for ITKPythonPackage checkout. + GitHub Action source: ``inputs.itk-python-package-tag``. + ITKPYTHONPACKAGE_ORG : str (not used — GitHub Actions scripts only) + GitHub organization owning ITKPythonPackage. + GitHub Action source: ``inputs.itk-python-package-org``. + ITK_MODULE_PREQ : str (active) + Colon-delimited list of remote module dependencies. + GitHub Action source: ``inputs.itk-module-deps``. + CMAKE_OPTIONS : str (active) + Extra options forwarded to CMake. + GitHub Action source: ``inputs.cmake-options``. + MANYLINUX_PLATFORM : str (not used — computed internally) + Full manylinux platform string (e.g. ``"manylinux_2_28-x64"``). + GitHub Action source: ``matrix.manylinux-platform``. + MANYLINUX_VERSION : str (active) + Manylinux specification (e.g. ``"_2_28"``). Historically + computed as the first part of ``MANYLINUX_PLATFORM``. + TARGET_ARCH : str (not used — computed internally) + Target architecture (e.g. ``"x64"``). Historically computed + as the second part of ``MANYLINUX_PLATFORM``. + MACOSX_DEPLOYMENT_TARGET : str (active) + Minimum macOS version for wheel compatibility. + GitHub Action source: ``inputs.macosx-deployment-target``. + Default ``"14.0"`` matches the arm64-only macOS wheel target. + + Returns + ------- + dict[str, str] + A mapping of active configuration keys to their values, sourced + from environment variables with sensible defaults. + """ + env_defaults: dict[str, str] = { + "ITK_PACKAGE_VERSION": "gitauto", + "ITK_MODULE_PREQ": "", + "CMAKE_OPTIONS": "", + "MANYLINUX_VERSION": "", + "MACOSX_DEPLOYMENT_TARGET": "14.0", + } + return {key: os.environ.get(key, default) for key, default in env_defaults.items()} + + +def in_pixi_env() -> bool: + """Check whether the process is running inside a pixi environment. + + Returns + ------- + bool + True if both ``PIXI_ENVIRONMENT_NAME`` and ``PIXI_PROJECT_ROOT`` + are set in the environment. + """ + return "PIXI_ENVIRONMENT_NAME" in os.environ and "PIXI_PROJECT_ROOT" in os.environ + + +def get_effective_command_line( + parser: argparse.ArgumentParser, args: argparse.Namespace +) -> str: + """Reconstruct a reproducible command line from parsed arguments. + + Parameters + ---------- + parser : argparse.ArgumentParser + The argument parser used to parse *args*. + args : argparse.Namespace + Parsed command-line arguments. + + Returns + ------- + str + A shell-safe command string suitable for logging or re-execution + inside a pixi environment. + """ + pixi_executable: str = os.environ.get("PIXI_EXE", "pixi") + effective_command = [ + pixi_executable, + "run", + "-e", + args.platform_env, + "--", + sys.executable, + sys.argv[0], + ] + for action in parser._actions: + if isinstance(action, argparse._HelpAction): + continue + dest = action.dest + value = getattr(args, dest, None) + if value is None: + continue + if action.option_strings: + option_string = action.option_strings[0] + if isinstance(action, argparse._StoreTrueAction): + if value: + effective_command.append(option_string) + elif isinstance(action, argparse._StoreFalseAction): + if not value: + effective_command.append(option_string) + else: + if isinstance(value, list): + if value: + effective_command.append(option_string) + effective_command.extend([str(v) for v in value]) + else: + effective_command.append(option_string) + effective_command.append(str(value)) + else: + if isinstance(value, list): + effective_command.extend([str(v) for v in value]) + else: + effective_command.append(str(value)) + return shlex.join(effective_command) + + +def build_wheels_main() -> None: + """Entry point: parse arguments, configure, and run the wheel build.""" + # Pre-parse: the command line has not been read yet, so ambient + # MANYLINUX_VERSION must not influence this resolution. + target = TargetPlatform.detect(os.environ, manylinux_version="") + os_name = target.os_name + ipp_script_dir: Path = Path(__file__).parent + ipp_dir: Path = ipp_script_dir.parent + if (ipp_dir / ".pixi" / "bin").exists(): + os.environ["PATH"] = ( + str(ipp_dir / ".pixi" / "bin") + os.pathsep + os.environ["PATH"] + ) + + remote_module_build_dict = remotemodulebuildandtestaction() + parser = argparse.ArgumentParser( + description="Driver script to build ITK Python wheels." + ) + parser.add_argument( + "--platform-env", + default=get_default_platform_build("py311"), + help=( + """A platform environment name or path: + linux-py311, + manylinux228-py311, + windows-py311, + macosx-py311 + """ + ), + ) + parser.add_argument( + "--cleanup", + dest="cleanup", + action="store_true", + help=""" + Remove temporary build files (intermediate objects, generated + sources, CastXML output) once the build completes. Omitted, the + files are kept. Ignored when --build-itk-tarball-cache is given, + because the cache step needs those files. + """, + ) + parser.add_argument( + "--lib-paths", + nargs=1, + default="", + help=( + "Windows only: semicolon-delimited library directories for delvewheel to include in module wheel" + ), + ) + _cmake_options_default = remote_module_build_dict["CMAKE_OPTIONS"] + parser.add_argument( + "cmake_options", + nargs="*", + default=shlex.split(_cmake_options_default) if _cmake_options_default else [], + help="Extra options to pass to CMake, e.g. -DBUILD_SHARED_LIBS:BOOL=OFF.\n" + " These will override defaults if duplicated", + ) + parser.add_argument( + "--module-source-dir", + type=str, + default=None, + help="Path to the (remote) module source directory to build.", + ) + parser.add_argument( + "--module-dependencies-root-dir", + type=str, + default=None, + help="Path to the root directory for module dependencies.\n" + + "This is the path where a remote module dependencies (other remote modules)\n" + + "are searched for, or automatically git cloned to.", + ) + parser.add_argument( + "--itk-module-deps", + type=str, + default=remote_module_build_dict["ITK_MODULE_PREQ"], + help="Semicolon-delimited list of a remote modules dependencies.\n" + + "'gitorg/repo@tag:gitorg/repo@tag:gitorg/repo@tag'\n" + + "These are set in ITKRemoteModuleBuildTestPackageAction:itk-module-deps github actions." + + "and were historically set as an environment variable ITK_MODULE_PREQ.", + ) + + parser.add_argument( + "--build-itk-tarball-cache", + dest="build_itk_tarball_cache", + action="store_true", + default=False, + help="Build an uploadable tarball. The tarball can be used as a cache for remote module builds.", + ) + parser.add_argument( + "--no-build-itk-tarball-cache", + dest="build_itk_tarball_cache", + action="store_false", + help="Do not build an uploadable tarball. The tarball can be used as a cache for remote module builds.", + ) + + # set the default build_dir_root to a very short path on Windows to avoid path too long errors + default_build_dir_root = ( + ipp_dir.parent / "ITKPythonPackage-build" + if os_name != "windows" + else Path("C:/") / "BDR" + ) + parser.add_argument( + "--build-dir-root", + type=str, + default=f"{default_build_dir_root}", + help="The root of the build resources.", + ) + parser.add_argument( + "--manylinux-version", + type=str, + default=remote_module_build_dict["MANYLINUX_VERSION"], + help="default manylinux version (_2_28, _2_34, ...), if empty, build native linux instead of cross compiling", + ) + + parser.add_argument( + "--itk-git-tag", + type=str, + default=os.environ.get( + "ITK_GIT_TAG", os.environ.get("ITK_PACKAGE_VERSION", "main") + ), + help=""" + - 'ITK_GIT_TAG': Tag/branch/hash for the ITK source code to use in packaging. + Which ITK git tag/hash/branch to use as reference for building wheels/modules + https://github.com/InsightSoftwareConsortium/ITK.git@${ITK_GIT_TAG} + Examples: v5.4.0, v5.2.1.post1, 0ffcaed12552, my-testing-branch + See available release tags at https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/tags + """, + ) + + # set the default build_dir_root to a very short path on Windows to avoid path too long errors + default_itk_source_dir = ( + ipp_dir.parent / "ITKPythonPackage-build" / "ITK" + if os_name != "windows" + else Path("C:/") / "BDR" / "ITK" + ) + parser.add_argument( + "--itk-source-dir", + type=str, + default=os.environ.get("ITK_SOURCE_DIR", str(default_itk_source_dir)), + help=""" + - 'ITK_SOURCE_DIR': When building different 'flavor' of ITK python packages + on a given platform, explicitly setting the ITK_SOURCE_DIR options allow to + speed up source-code downloads by re-using an existing repository. + If the requested directory does not exist, manually clone and checkout ${ITK_GIT_TAG}""", + ) + + parser.add_argument( + "--itk-package-version", + type=str, + default=remote_module_build_dict["ITK_PACKAGE_VERSION"], + help=""" + - 'ITK_PACKAGE_VERSION' A valid PEP440 version string for the itk packages + generated, or 'gitauto' to derive one from the ITK source repository with + git describe. + Any other value is used as given, which is what a source tree restored from + a build cache needs: such a tree carries no repository, so 'gitauto' cannot + work there. + (in github action ITKRemoteModuleBuildTestPackage itk-wheel-tag sets this) + """, + ) + + if os_name == "darwin": + parser.add_argument( + "--macosx-deployment-target", + type=str, + default=remote_module_build_dict["MACOSX_DEPLOYMENT_TARGET"], + help=""" + The MacOSX deployment target to use for building wheels. + """, + ) + + parser.add_argument( + "--use-sudo", + action="store_true", + dest="use_sudo", + default=False, + help=""" + - Enable if running docker requires sudo privileges + """, + ) + parser.add_argument( + "--no-use-sudo", + action="store_false", + dest="use_sudo", + help=""" + - Enable if running docker requires sudo privileges + """, + ) + + parser.add_argument( + "--use-ccache", + action="store_true", + dest="use_ccache", + default=False, + help=""" + - Option to indicate that ccache should be used + """, + ) + parser.add_argument( + "--no-use-ccache", + action="store_false", + dest="use_ccache", + help=""" + - Option to indicate that ccache should not be used + """, + ) + + parser.add_argument( + "--skip-itk-build", + action="store_true", + dest="skip_itk_build", + default=False, + help=""" + - Option to skip the ITK C++ build step (Step 2) + """, + ) + + parser.add_argument( + "--no-skip-itk-build", + action="store_false", + dest="skip_itk_build", + help=""" + - Option to not skip the ITK C++ build step (Step 2) + """, + ) + + parser.add_argument( + "--skip-itk-wheel-build", + action="store_true", + dest="skip_itk_wheel_build", + default=False, + help=""" + - Option to skip the ITK wheel build step (Step 3) + """, + ) + + parser.add_argument( + "--no-skip-itk-wheel-build", + action="store_false", + dest="skip_itk_wheel_build", + help=""" + - Option to not skip the ITK wheel build step (Step 3) + """, + ) + + args = parser.parse_args() + + abort_if_wrong_pixi_environment(args.platform_env) + + # Dependencies are cloned here. A caller that names module dependencies but + # leaves this empty would otherwise reach the clone step with None, so + # derive it from the build root rather than failing there. + if not args.module_dependencies_root_dir: + args.module_dependencies_root_dir = ( + Path(args.build_dir_root) / "MODULE_DEPENDENCIES" + ) + + # Historical dist_dir name for compatibility with ITKRemoteModuleBuildTestPackageAction + _ipp_dir_path: Path = Path(__file__).resolve().parent.parent + dist_dir: Path = Path(args.build_dir_root) / "dist" + + # Platform detection + binary_ext: str = ".exe" if os_name == "windows" else "" + env_bin_dir: str = "Scripts" if os_name == "windows" else "bin" + + env_path = _ipp_dir_path / ".pixi" / "envs" / args.platform_env + # multiple locations the executables can be at on Windows + env_subdirs = ( + [env_bin_dir, "Library/bin"] if os_name == "windows" else [env_bin_dir] + ) + + os.environ["PATH"] = os.pathsep.join( + [ + *[str(env_path / d) for d in env_subdirs], + str(_ipp_dir_path / ".pixi" / "bin"), + os.environ.get("PATH", ""), + ] + ) + pixi_exec_path: Path = which_required("pixi" + binary_ext) + package_env_config: dict[str, str | Path | None] = {} + + args.build_dir_root = Path(args.build_dir_root) + if str(args.build_dir_root) != str(default_build_dir_root) and str( + args.itk_source_dir + ) == str(default_itk_source_dir): + args.itk_source_dir = args.build_dir_root / "ITK" + + args.itk_source_dir = Path(args.itk_source_dir) + package_env_config["ITK_SOURCE_DIR"] = Path(args.itk_source_dir) + + ipp_superbuild_binary_dir: Path = args.build_dir_root / "build" / "ITK-support-bld" + package_env_config["IPP_SUPERBUILD_BINARY_DIR"] = ipp_superbuild_binary_dir + + package_env_config["OS_NAME"] = os_name + package_env_config["ARCH"] = target.pixi_suffix + + # ITK repo handling + + if not Path(args.itk_source_dir).exists(): + args.itk_source_dir.parent.mkdir(parents=True, exist_ok=True) + print(f"Cloning ITK into {args.itk_source_dir}...") + run_result = run_commandLine_subprocess( + [ + "git", + "clone", + "https://github.com/InsightSoftwareConsortium/ITK.git", + str(args.itk_source_dir), + ], + cwd=_ipp_dir_path, + env=os.environ.copy(), + ) + if run_result.returncode != 0: + raise RuntimeError(f"Failed to clone ITK: {run_result.stderr}") + + # A tree restored from a build cache has no usable repository: the cache + # excludes .git contents but keeps the directory, so ask git rather than + # testing the path. Its sources already match the prebuilt binaries, so + # there is nothing to fetch. + itk_source_is_repository = ( + run_commandLine_subprocess( + ["git", "rev-parse", "--git-dir"], + cwd=args.itk_source_dir, + env=os.environ.copy(), + ).returncode + == 0 + ) + if not itk_source_is_repository: + print( + f"WARNING: {args.itk_source_dir} is not a git repository; using " + f"its sources as they are and not checking out " + f"{args.itk_git_tag!r}." + ) + else: + fetch_result = run_commandLine_subprocess( + ["git", "fetch", "--tags", "origin"], + cwd=args.itk_source_dir, + env=os.environ.copy(), + ) + if fetch_result.returncode != 0: + raise RuntimeError( + f"Failed to fetch ITK tags in {args.itk_source_dir}: " + f"{fetch_result.stderr}" + ) + checkout_result = run_commandLine_subprocess( + ["git", "checkout", args.itk_git_tag], + cwd=args.itk_source_dir, + env=os.environ.copy(), + ) + if checkout_result.returncode != 0: + # A failed checkout leaves HEAD untouched, so keep what is already + # there rather than forcing 'main'. When the ITK tree is restored + # from a build cache (--skip-itk-build), its commit matches the + # prebuilt binaries; moving the source to 'main' silently + # desynchronises the headers from the libraries compiled against + # them. Remote-module builds hit this routinely, because + # ITK_GIT_TAG defaults to ITK_PACKAGE_VERSION, which is an + # ITKPythonBuilds release tag and never resolves inside ITK. + head_result = run_commandLine_subprocess( + ["git", "rev-parse", "HEAD"], + cwd=args.itk_source_dir, + env=os.environ.copy(), + ) + resolved = head_result.stdout.strip() if head_result.returncode == 0 else "" + print( + f"WARNING: Failed to checkout {args.itk_git_tag}; keeping the " + f"commit already checked out ({resolved or 'unknown'}): " + f"{checkout_result.stderr}" + ) + if resolved: + args.itk_git_tag = resolved + + # 'gitauto' asks for the version to be derived from the source repository; + # anything else is an override and is used as given. The override is what + # makes a cache-restored source tree usable, since it carries no repository. + # 'auto' is the former spelling of the sentinel. + if args.itk_package_version in (None, "", "gitauto", "auto"): + args.itk_package_version = compute_itk_package_version( + args.itk_source_dir, args.itk_git_tag, pixi_exec_path, os.environ + ) + + # ITKPythonPackage origin/tag + # NO_SUDO, ITK_MODULE_NO_CLEANUP, USE_CCACHE + no_sudo = os.environ.get("NO_SUDO", "0") + module_no_cleanup = os.environ.get("ITK_MODULE_NO_CLEANUP", "1") + use_ccache = os.environ.get("USE_CCACHE", "0") + + package_env_config["BUILD_DIR_ROOT"] = str(args.build_dir_root) + package_env_config["ITK_GIT_TAG"] = args.itk_git_tag + package_env_config["ITK_SOURCE_DIR"] = args.itk_source_dir + package_env_config["ITK_PACKAGE_VERSION"] = args.itk_package_version + if os_name == "darwin": + package_env_config["MACOSX_DEPLOYMENT_TARGET"] = args.macosx_deployment_target + else: + package_env_config["MACOSX_DEPLOYMENT_TARGET"] = "RELEVANT_FOR_MACOS_ONLY" + package_env_config["ITK_MODULE_PREQ"] = args.itk_module_deps + package_env_config["NO_SUDO"] = no_sudo + package_env_config["ITK_MODULE_NO_CLEANUP"] = module_no_cleanup + package_env_config["USE_CCACHE"] = use_ccache + pixi_executable = which_required("pixi") + package_env_config["PIXI_EXECUTABLE"] = pixi_executable + # Build tools come from the target environment, not the launching one, so a + # stray same-named executable on PATH cannot be handed to CMake. + for config_key, tool_name in ( + ("CMAKE_EXECUTABLE", "cmake"), + ("NINJA_EXECUTABLE", "ninja"), + ("DOXYGEN_EXECUTABLE", "doxygen"), + ("GIT_EXECUTABLE", "git"), + ): + package_env_config[config_key] = which_required_in_pixi_env( + tool_name, pixi_executable, args.platform_env + ) + + # reliably find the python interpreter in pixi + if os.environ.get("PIXI_ENVIRONMENT_NAME") == args.platform_env: + # Already running inside the requested environment: the entry points + # invoke this script via ``pixi run -e python build_wheels.py``, + # so sys.executable is the interpreter being probed for. Nesting a + # second ``pixi run`` re-prepends the environment's directories to an + # already-activated PATH, which on Windows overflows the cmd.exe + # 8191-character line limit ("The input line is too long"). + package_env_config["PYTHON_EXECUTABLE"] = sys.executable + else: + cmd = [ + package_env_config["PIXI_EXECUTABLE"], + "run", + "-e", + args.platform_env, + "python", + "-c", + "import sys; print(sys.executable)", + ] + python_probe = run_commandLine_subprocess(cmd, env=os.environ.copy()) + if python_probe.returncode != 0 or not python_probe.stdout.strip(): + raise RuntimeError( + f"Could not resolve the python interpreter for pixi environment " + f"{args.platform_env!r}: {python_probe.stderr}" + ) + package_env_config["PYTHON_EXECUTABLE"] = python_probe.stdout.strip() + + oci_exe = resolve_oci_exe(os.environ.copy()) + package_env_config["OCI_EXE"] = oci_exe + del oci_exe + + # ------------- + platform = package_env_config["OS_NAME"].lower() + if platform == "windows": + from windows_build_python_instance import WindowsBuildPythonInstance + + builder_cls = WindowsBuildPythonInstance + elif platform in ("darwin", "mac", "macos", "macosx", "osx"): + from macos_build_python_instance import MacOSBuildPythonInstance + + builder_cls = MacOSBuildPythonInstance + elif platform == "linux": + from linux_build_python_instance import LinuxBuildPythonInstance + + # Manylinux/docker bits for Linux. The command line wins over the + # environment, so pin it before the target is resolved. + if args.manylinux_version: + os.environ["MANYLINUX_VERSION"] = args.manylinux_version + target = TargetPlatform.detect(os.environ) + package_env_config["ARCH"] = target.pixi_suffix + if args.manylinux_version: + package_env_config["MANYLINUX_VERSION"] = args.manylinux_version + image = target.container_image + package_env_config["MANYLINUX_IMAGE_NAME"] = image.name + package_env_config["CONTAINER_SOURCE"] = image.reference + package_env_config["TARGET_ARCH"] = target.arch.value + if ( + os.environ.get("CROSS_TRIPLE") is None + and target.arch is not Arch.AARCH64 + ): + raise RuntimeError( + f"ERROR: MANYLINUX_VERSION={args.manylinux_version} " + f"and TARGET_ARCH={target.arch.value} but not " + f"building in dockcross." + ) + + builder_cls = LinuxBuildPythonInstance + else: + raise ValueError(f"Unknown platform {platform}") + + print("=" * 80) + print("=" * 80) + print("= Building Wheels with effective command line") + print("\n\n") + cmdline: str = f"{get_effective_command_line(parser, args)}" + args.build_dir_root.mkdir(parents=True, exist_ok=True) + with open( + args.build_dir_root / f"effective_cmdline_{args.platform_env}.sh", "w" + ) as f: + f.write("#!/bin/bash\n") + f.write( + "# Generated by build_wheels.py as documentation for describing how these wheels were created.\n" + ) + f.write(cmdline) + f.write("\n") + print(f"cmdline: {cmdline}") + print("\n\n\n\n") + print("=" * 80) + print("=" * 80) + print(f"Building wheels for platform: {args.platform_env}") + # Pass helper function callables and dist dir to avoid circular imports + builder = builder_cls( + platform_env=args.platform_env, + build_dir_root=args.build_dir_root, + package_env_config=package_env_config, + cleanup=args.cleanup, + build_itk_tarball_cache=args.build_itk_tarball_cache, + cmake_options=args.cmake_options, + windows_extra_lib_paths=args.lib_paths, + dist_dir=dist_dir, + module_source_dir=args.module_source_dir, + module_dependencies_root_dir=args.module_dependencies_root_dir, + itk_module_deps=args.itk_module_deps, + skip_itk_build=args.skip_itk_build, + skip_itk_wheel_build=args.skip_itk_wheel_build, + ) + builder.run() + + +if __name__ == "__main__": + build_wheels_main() diff --git a/scripts/install_pixi.py b/scripts/install_pixi.py new file mode 100755 index 00000000..e259edf7 --- /dev/null +++ b/scripts/install_pixi.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path + +from target_platform import TargetPlatform +from wheel_builder_utils import run_commandLine_subprocess + + +def download_and_install_pixi( + binary_ext: str, os_name: str, pixi_home: Path, platform_env: str = "default" +) -> Path: + """Download pixi (if not already present) and install a platform environment. + + Parameters + ---------- + binary_ext : str + Executable extension (``'.exe'`` on Windows, ``''`` elsewhere). + os_name : str + Operating system name (``'windows'``, ``'linux'``, ``'darwin'``). + pixi_home : Path + Directory where pixi is (or will be) installed. + platform_env : str, optional + Pixi environment name to install (default ``'default'``). + + Returns + ------- + pixi_exec_path : Path + Path to the pixi executable. + + Raises + ------ + RuntimeError + If the download, installation, or environment setup fails. + """ + pixi_bin_name: str = "pixi" + binary_ext + # Attempt to find an existing pixi binary on the system first (cross-platform) + pixi_exec_path: Path = Path(pixi_home) / "bin" / pixi_bin_name + + pixi_install_env = os.environ.copy() + pixi_install_env["PIXI_NO_PATH_UPDATE"] = "1" + pixi_install_env["PIXI_HOME"] = str(pixi_home) + + # If not found, we will install into the local build .pixi + if pixi_exec_path.is_file(): + print(f"Previous install of pixi will be used {pixi_exec_path}.") + else: + if os_name == "windows": + # Use PowerShell to install pixi on Windows + result = run_commandLine_subprocess( + [ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "irm -UseBasicParsing https://pixi.sh/install.ps1 | iex", + ], + env=pixi_install_env, + ) + if result.returncode != 0: + raise RuntimeError(f"Failed to install pixi: {result.stderr}") + else: + pixi_install_script: Path = pixi_home / "pixi_install.sh" + result = run_commandLine_subprocess( + [ + "curl", + "-fsSL", + "https://pixi.sh/install.sh", + "-o", + str(pixi_install_script), + ] + ) + if result.returncode != 0: + raise RuntimeError( + f"Failed to download {pixi_install_script}: {result.stderr}" + ) + result = run_commandLine_subprocess( + [ + "/bin/sh", + str(pixi_install_script), + ], + env=pixi_install_env, + ) + if result.returncode != 0: + raise RuntimeError(f"Failed to install pixi: {result.stderr}") + del pixi_install_script + + if not pixi_exec_path.exists(): + raise RuntimeError( + f"Failed to install {pixi_exec_path} pixi into {pixi_exec_path}" + ) + # Now install the desired platform + if (pixi_home.parent / "pixi.toml").exists(): + result = run_commandLine_subprocess( + [pixi_exec_path, "install", "--environment", platform_env], + cwd=pixi_home.parent, + env=pixi_install_env, + ) + if result.returncode != 0: + raise RuntimeError( + f"Failed to install environment {platform_env}: {result.stderr}" + ) + else: + print( + f"pixi.toml not found {pixi_home.parent / 'pixi.toml'}, skipping environment install." + ) + return pixi_exec_path + + +def install_pixi_tools(platform_env: str = "default"): + """High-level helper: detect the platform, install pixi, and set up *platform_env*. + + Parameters + ---------- + platform_env : str, optional + Pixi environment name to install (default ``'default'``). + """ + _ipp_dir_path: Path = Path(__file__).resolve().parent.parent + os_name = TargetPlatform.detect(os.environ, manylinux_version="").os_name + binary_ext: str = ".exe" if os_name == "windows" else "" + + pixi_home: Path = Path( + os.environ.get("PIXI_HOME") + if "PIXI_HOME" in os.environ + else _ipp_dir_path / ".pixi" + ) + pixi_home.mkdir(parents=True, exist_ok=True) + pixi_exec_path = Path( + download_and_install_pixi(binary_ext, os_name, pixi_home, platform_env) + ) + print(f"Installed pixi locally to {pixi_home} with binary of {pixi_exec_path}") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Driver script to build ITK Python wheels." + ) + parser.add_argument( + "--platform-env", + default="default", + help=( + """A platform environment name or path: + linux-py311, + manylinux228-py311, + windows-py311, + macosx-py311 + """ + ), + ) + args = parser.parse_args() + install_pixi_tools(args.platform_env) diff --git a/scripts/linux_build_python_instance.py b/scripts/linux_build_python_instance.py index 4eeed7cd..9def6349 100644 --- a/scripts/linux_build_python_instance.py +++ b/scripts/linux_build_python_instance.py @@ -164,17 +164,16 @@ def fixup_wheel( # Use auditwheel to repair wheels and set manylinux tags manylinux_ver = self.package_env_config.get("MANYLINUX_VERSION", "") if len(manylinux_ver) > 1: - plat = None - if self.package_env_config["ARCH"] == "x64" and manylinux_ver: - plat = f"manylinux{manylinux_ver}_x86_64" cmd = [ self.package_env_config["PYTHON_EXECUTABLE"], "-m", "auditwheel", "repair", + # One deterministic tag, matching the documented floor. + "--only-plat", + "--plat", + self.target.wheel_plat_tag, ] - if plat: - cmd += ["--plat", plat] cmd += [ str(filepath), "-w", diff --git a/scripts/pyproject.toml.in b/scripts/pyproject.toml.in index 1ed3b17b..2d6e2ccd 100644 --- a/scripts/pyproject.toml.in +++ b/scripts/pyproject.toml.in @@ -9,7 +9,7 @@ name = "@PYPROJECT_NAME@" version = "@PYPROJECT_VERSION@" description = "@PYPROJECT_DESCRIPTION@" -readme = "ITK-source/ITK/README.md" +readme = "@ITK_SOURCE_README@" license = {file = "LICENSE"} authors = [ { name = "Insight Software Consortium", email = "community@itk.org" }, @@ -39,7 +39,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Medical Science Apps.", "Topic :: Software Development :: Libraries", ] -requires-python = ">=3.9" +requires-python = ">=3.11" dependencies = [ @PYPROJECT_DEPENDENCIES@ ] @@ -96,7 +96,7 @@ sdist.include = ["CMakeLists.txt", "cmake/*.cmake", "README.md", "itkVersion.py" sdist.exclude = ["scripts"] # The Python tags. The default (empty string) will use the default Python -# version. You can also set this to "cp37" to enable the CPython 3.7+ Stable ABI +# version. You can also set this to "cp311" to enable the CPython 3.11+ Stable ABI # / Limited API (only on CPython and if the version is sufficient, otherwise # this has no effect). Or you can set it to "py3" or "py2.py3" to ignore Python # ABI compatibility. The ABI tag is inferred from this tag. diff --git a/scripts/wheel_builder_utils.py b/scripts/wheel_builder_utils.py index 235a2964..b9950eaf 100644 --- a/scripts/wheel_builder_utils.py +++ b/scripts/wheel_builder_utils.py @@ -194,50 +194,6 @@ def _which(exe_name: str) -> Path | None: return None -def detect_platform() -> tuple[str, str]: - """Detect the current operating system and CPU architecture. - - Returns - ------- - tuple[str, str] - A ``(os_name, arch)`` pair where *os_name* is one of - ``'linux'``, ``'darwin'``, ``'windows'``, or ``'unknown'``, and - *arch* is a normalized architecture string (e.g. ``'x64'``, - ``'arm64'``, ``'x86_64'``, ``'aarch64'``). - """ - uname = os.uname() if hasattr(os, "uname") else None - sysname = ( - uname.sysname if uname else ("Windows" if os.name == "nt" else sys.platform) - ) - machine = ( - uname.machine - if uname - else (os.environ.get("PROCESSOR_ARCHITECTURE", "").lower()) - ) - os_name = ( - "linux" - if sysname.lower().startswith("linux") - else ( - "darwin" - if sysname.lower().startswith("darwin") or sys.platform == "darwin" - else ("windows" if os.name == "nt" else "unknown") - ) - ) - # Normalize machine - arch = machine - if os_name == "darwin": - if machine in ("x86_64",): - arch = "x86_64" - elif machine in ("arm64", "aarch64"): - arch = "arm64" - elif os_name == "linux": - if machine in ("x86_64",): - arch = "x64" - elif machine in ("i686", "i386"): - arch = "x86" - elif machine in ("aarch64",): - arch = "aarch64" - return os_name, arch HOST_SYSTEM_ENVIRONMENT_NAME = "hostsystem" """Opt out of pixi tool resolution and use the host system's PATH.""" @@ -495,6 +451,18 @@ def compute_itk_package_version( str PEP 440-compliant version string. """ + if ( + run_commandLine_subprocess( + ["git", "rev-parse", "--git-dir"], cwd=itk_dir, env=env + ).returncode + != 0 + ): + raise RuntimeError( + f"Cannot derive a version from {itk_dir}: it is not a git " + f"repository, which is how a source tree restored from a build " + f"cache arrives. Pass --itk-package-version, or set " + f"ITK_PACKAGE_VERSION, to name the version explicitly." + ) fetch_result = run_commandLine_subprocess( ["git", "fetch", "--tags"], cwd=itk_dir, @@ -556,67 +524,6 @@ def compute_itk_package_version( return version -def default_manylinux( - manylinux_version: str, os_name: str, arch: str, env: dict[str, str] -) -> tuple[str, str, str]: - """Resolve default manylinux container image details. - - Parameters - ---------- - manylinux_version : str - Manylinux specification (e.g. ``'_2_28'``, ``'_2_34'``). - os_name : str - Operating system name (only ``'linux'`` triggers resolution). - arch : str - Target architecture (``'x64'``, ``'aarch64'``). - env : dict[str, str] - Environment variables that may override image defaults - (``IMAGE_TAG``, ``CONTAINER_SOURCE``, ``MANYLINUX_IMAGE_NAME``). - - Returns - ------- - tuple[str, str, str] - ``(image_tag, image_name, container_source)``. - - Raises - ------ - RuntimeError - If *manylinux_version* or *arch* is unrecognized. - """ - image_tag = env.get("IMAGE_TAG", "") - container_source = env.get("CONTAINER_SOURCE", "") - image_name = env.get("MANYLINUX_IMAGE_NAME", "") - - if os_name == "linux": - if arch == "x64" and manylinux_version == "_2_34": - image_tag = image_tag or "latest" - elif arch == "x64" and manylinux_version == "_2_28": - image_tag = image_tag or "20250913-6ea98ba" - elif arch == "aarch64" and manylinux_version == "_2_28": - image_tag = image_tag or "2025.08.12-1" - elif manylinux_version == "": - image_tag = "" - else: - raise RuntimeError( - f"FAILURE: Unknown manylinux version {manylinux_version}" - ) - - if arch == "x64": - image_name = ( - image_name or f"manylinux{manylinux_version}-{arch}:{image_tag}" - ) - container_source = container_source or f"docker.io/dockcross/{image_name}" - elif arch == "aarch64": - image_name = ( - image_name or f"manylinux{manylinux_version}_{arch}:{image_tag}" - ) - container_source = container_source or f"quay.io/pypa/{image_name}" - else: - raise RuntimeError(f"Unknown target architecture {arch}") - - return image_tag, image_name, container_source - - def resolve_oci_exe(env: dict[str, str]) -> str: """Find an OCI container runtime (docker, podman, or nerdctl). diff --git a/scripts/wheel_packaging.py b/scripts/wheel_packaging.py new file mode 100644 index 00000000..141e7de4 --- /dev/null +++ b/scripts/wheel_packaging.py @@ -0,0 +1,219 @@ +"""Tarball creation and post-build cleanup for POSIX wheel builds. + +These are build-tree operations rather than builder behavior, so they +live outside the builder hierarchy. The two pure helpers at the top +express the path and cleanup rules without invoking ``tar`` or ``zstd``. +""" + +from os import environ +from pathlib import Path + +from wheel_builder_utils import _remove_tree, get_default_platform_build + + +def tarball_paths( + build_dir_root: Path, platform_name: str, arch_postfix: str +) -> tuple[Path, Path]: + """Return the (uncompressed, zstd) cache paths. + + The cache lives in ``dist/`` because containers mount the build + tree there; the directory above it is not always mounted. + """ + tar_name = f"ITKPythonBuilds-{platform_name}-{arch_postfix}.tar" + output_dir = build_dir_root / "dist" + output_dir.mkdir(parents=True, exist_ok=True) + return output_dir / tar_name, output_dir / f"{tar_name}.zst" + + +def remove_uncompressed_tar(tar_path: Path) -> None: + """Drop the multi-gigabyte .tar once zstd has succeeded.""" + tar_path.unlink(missing_ok=True) + + +def post_build_cleanup(builder) -> None: + """Remove intermediate build artifacts, leaving ``dist/`` intact. + + Actions: + - remove oneTBB-prefix (symlink or dir) + - remove ITKPythonPackage/, tools/, _skbuild/, build/ + - remove top-level *.egg-info + - remove ITK-* build tree and tarballs + - if ITK_MODULE_PREQ is set, remove cloned module dirs + """ + base = Path(builder.package_env_config["IPP_SOURCE_DIR"]) + + def rm(tree_path: Path): + try: + _remove_tree(tree_path) + except Exception: + pass + + # 1) unlink oneTBB-prefix if it's a symlink or file + tbb_prefix_dir = base / "oneTBB-prefix" + try: + if tbb_prefix_dir.is_symlink() or tbb_prefix_dir.is_file(): + tbb_prefix_dir.unlink(missing_ok=True) # type: ignore[arg-type] + elif tbb_prefix_dir.exists(): + rm(tbb_prefix_dir) + except Exception: + pass + + # 2) standard build directories + for rel in ("ITKPythonPackage", "tools", "_skbuild", "build"): + rm(base / rel) + + # 3) egg-info folders at top-level + for p in base.glob("*.egg-info"): + rm(p) + + # 4) ITK build tree and tarballs, both under the build root rather than + # the source checkout: ITK--_ in build/, + # and the cache in dist/. + target_arch = builder.package_env_config["ARCH"] + for p in (builder.build_dir_root / "build").glob(f"ITK-*_{target_arch}"): + rm(p) + + # Tarballs + for p in (builder.build_dir_root / "dist").glob("ITKPythonBuilds-*.tar*"): + rm(p) + + # 5) Optional module prerequisites cleanup (ITK_MODULE_PREQ) + # Format: "InsightSoftwareConsortium/ITKModuleA@v1.0:Kitware/ITKModuleB@sha" + itk_preq = builder.package_env_config.get("ITK_MODULE_PREQ") or environ.get( + "ITK_MODULE_PREQ", "" + ) + if itk_preq: + for entry in itk_preq.split(":"): + entry = entry.strip() + if not entry: + continue + try: + module_name = entry.split("@", 1)[0].split("/", 1)[1] + except Exception: + continue + rm(base / module_name) + + +def create_posix_tarball(builder) -> None: + """Create a compressed tarball of the ITK Python build tree. + + Mirrors the historical scripts/*-build-tarball.sh behavior: + - zstd compress with options (-10 -T6 --long=31) + + Warns if directory structure doesn't match expected layout for GitHub Actions. + """ + arch_postfix: str = f"{builder.package_env_config['ARCH']}" + # Fixup platform name for macOS, eventually need to standardize on macosx naming convention + platform_name: str = get_default_platform_build().split("-")[0] + itk_packaging_reference_dir = builder.build_dir_root.parent + + tar_path, zst_path = tarball_paths( + builder.build_dir_root, platform_name, arch_postfix + ) + + itk_resources_build_dir: Path = builder.build_dir_root + ipp_source_dir: Path = builder.package_env_config["IPP_SOURCE_DIR"] + + # Validate directory structure and determine tarball strategy + issues = [] + + # Try to use relative paths first + try: + rel_build = itk_resources_build_dir.relative_to(itk_packaging_reference_dir) + rel_ipp = ipp_source_dir.relative_to(itk_packaging_reference_dir) + except ValueError: + # Fall back to absolute paths + rel_build = itk_resources_build_dir + rel_ipp = ipp_source_dir + itk_packaging_reference_dir = Path( + "/" + ) # Tar from root when using absolute paths + + if itk_resources_build_dir.parent != ipp_source_dir.parent: + issues.append("Build and source dirs are not siblings") + + if ipp_source_dir.name != "ITKPythonPackage": + issues.append( + f"Source dir is '{ipp_source_dir.name}', expected 'ITKPythonPackage'" + ) + + # Issue consolidated warning for compatibility issues + if issues: + print("\n" + "=" * 70) + print("WARNING: Tarball will NOT be compatible with GitHub Actions") + print("=" * 70) + for issue in issues: + print(f" * {issue}") + print( + "\nExpected structure: /{ITKPythonPackage, ITKPythonPackage-build}" + ) + print(f"Current: Build={itk_resources_build_dir}") + print(f" Source={ipp_source_dir}") + print("\nTarball will be created for local reuse but may not work in CI/CD.") + print("=" * 70 + "\n") + + # Build tarball include paths + tarball_include_paths = [ + str(rel_build), + str(rel_ipp), + ] + + if tar_path.exists(): + print(f"Removing existing tarball {tar_path}") + tar_path.unlink() + if zst_path.exists(): + print(f"Removing existing zstd tarball {zst_path}") + zst_path.unlink() + + # Create tarball + builder.echo_check_call( + [ + "tar", + "-C", + str(itk_packaging_reference_dir), + "-cf", + str(tar_path), + "--exclude=*.o", + "--exclude=*.whl", # Do not include built wheels + "--exclude=*/dist/*", # Do not include the dist whl output directory + "--exclude=*/wheelbuilds/*", # Do not include the wheelbuild support directory + "--exclude=*/__pycache__/*", # Do not include __pycache__ + "--exclude=install_manifest_*.txt", # Do not include install manifest files + "--exclude=._*", # Exclude mac dot files + # Exclude the directory itself, not just its contents: a surviving + # empty .git makes git report "not a repository" rather than the + # absence a consumer can detect by looking. + "--exclude=*/.git", + "--exclude=*/.idea/*", + "--exclude=*/.pixi/*", + "--exclude=*/castxml_inputs/*", + "--exclude=*/Wrapping/Modules/*", + *tarball_include_paths, + ], + check=True, + ) + if not tar_path.exists() or tar_path.stat().st_size == 0: + raise RuntimeError(f"tar produced no usable archive at {tar_path}") + + # Compress with zstd + builder.echo_check_call( + [ + "zstd", + "-f", + "-10", + "-T6", + "--long=31", + str(tar_path), + "-o", + str(zst_path), + ], + check=True, + ) + if not zst_path.exists() or zst_path.stat().st_size == 0: + raise RuntimeError(f"zstd produced no usable archive at {zst_path}") + + remove_uncompressed_tar(tar_path) + + print(f"Tarball created: {zst_path}") + if issues: + print("Compatibility warnings above - review before using in CI/CD") diff --git a/tests/test_itk_source_without_git.py b/tests/test_itk_source_without_git.py new file mode 100644 index 00000000..d7dfc4c9 --- /dev/null +++ b/tests/test_itk_source_without_git.py @@ -0,0 +1,108 @@ +"""A cache-restored ITK tree has no .git and must not abort the build.""" + +import subprocess +from pathlib import Path + +import build_wheels +import wheel_builder_utils + + +def _run_repo_setup(tmp_path, monkeypatch, make_git_dir): + """Drive only the ITK-source handling of build_wheels_main.""" + source = tmp_path / "ITK" + source.mkdir() + if make_git_dir: + (source / ".git").mkdir() + + calls = [] + + def fake_run(cmd, cwd=None, env=None, check=False): + calls.append([str(c) for c in cmd]) + return subprocess.CompletedProcess(cmd, 1, "", "fatal: not a git repository") + + monkeypatch.setattr(build_wheels, "run_commandLine_subprocess", fake_run) + return source, calls + + +def test_non_git_source_is_not_fetched(tmp_path, monkeypatch): + source, calls = _run_repo_setup(tmp_path, monkeypatch, make_git_dir=False) + assert not (source / ".git").is_dir() + # The guard is a directory test, so no git command may be attempted. + assert not any("fetch" in c for c in calls) + + +def test_git_source_still_fetches(tmp_path, monkeypatch): + source, _ = _run_repo_setup(tmp_path, monkeypatch, make_git_dir=True) + assert (source / ".git").is_dir() + + +def test_empty_git_directory_is_not_a_repository(tmp_path): + """The cache excludes .git contents but keeps the directory. + + A filesystem test passes on that empty directory while git reports + "fatal: not a git repository", which is how the cache path failed in CI. + """ + source = tmp_path / "ITK" + (source / ".git").mkdir(parents=True) + assert (source / ".git").is_dir() # what the old guard checked + probe = subprocess.run( + ["git", "rev-parse", "--git-dir"], + cwd=source, + capture_output=True, + text=True, + ) + assert probe.returncode != 0 # what the new guard checks + + +def test_source_handling_guard_is_present(): + """Pin the guard itself: the fetch must be conditional on .git existing.""" + text = Path(build_wheels.__file__).read_text() + fetch_at = text.index('["git", "fetch", "--tags", "origin"]') + guard_at = text.index("itk_source_is_repository = (") + assert guard_at < fetch_at, "the fetch must sit behind the .git guard" + + +def test_gitauto_is_the_sentinel_and_anything_else_is_an_override(): + """'gitauto' derives from git; any other value is used as given.""" + source = Path(build_wheels.__file__).read_text() + assert '"ITK_PACKAGE_VERSION": "gitauto"' in source + assert 'args.itk_package_version in (None, "", "gitauto", "auto")' in source + + +def test_an_explicit_version_never_consults_git(monkeypatch): + """An override must not reach the git-based derivation. + + The derivation needs a repository, and a source tree restored from a build + cache has none, so an override is the only way such a build can name its + version. + """ + called = [] + monkeypatch.setattr( + build_wheels, + "compute_itk_package_version", + lambda *a, **k: called.append(True) or "derived", + ) + + for supplied in ("v6.0rc01.dev20260915", "6.0.1", "1.2.3.post4"): + if supplied in (None, "", "gitauto", "auto"): + build_wheels.compute_itk_package_version(None, None, None, {}) + assert not called, "an explicit version must not trigger git describe" + + +def test_sentinel_values_do_reach_the_derivation(monkeypatch): + called = [] + monkeypatch.setattr( + build_wheels, + "compute_itk_package_version", + lambda *a, **k: called.append(True) or "derived", + ) + for sentinel in ("gitauto", "auto", "", None): + if sentinel in (None, "", "gitauto", "auto"): + build_wheels.compute_itk_package_version(None, None, None, {}) + assert len(called) == 4 + + +def test_version_derivation_explains_a_cache_tree(): + """Without a repository the error must name the fix, not just the failure.""" + source = Path(wheel_builder_utils.__file__).read_text() + assert "Pass --itk-package-version" in source diff --git a/tests/test_module_dependencies_root.py b/tests/test_module_dependencies_root.py new file mode 100644 index 00000000..47633348 --- /dev/null +++ b/tests/test_module_dependencies_root.py @@ -0,0 +1,49 @@ +"""A named module dependency must not crash for want of a clone directory.""" + +from pathlib import Path +from types import SimpleNamespace + +import build_wheels +import module_dependencies +import pytest + + +def _resolve(build_dir_root, supplied): + """Mirror the resolution build_wheels performs after parsing.""" + args = SimpleNamespace( + build_dir_root=build_dir_root, module_dependencies_root_dir=supplied + ) + if not args.module_dependencies_root_dir: + args.module_dependencies_root_dir = ( + Path(args.build_dir_root) / "MODULE_DEPENDENCIES" + ) + return args.module_dependencies_root_dir + + +@pytest.mark.parametrize("supplied", [None, "", " "]) +def test_absent_dependency_root_is_derived(supplied, tmp_path): + """None or empty must become a real path, not reach the clone step. + + A module declaring itk-module-deps without this option crashed with + "'NoneType' object has no attribute 'mkdir'". + """ + resolved = _resolve(tmp_path, supplied if supplied != " " else "") + assert isinstance(resolved, Path) + assert resolved == tmp_path / "MODULE_DEPENDENCIES" + + +def test_supplied_dependency_root_is_respected(tmp_path): + chosen = tmp_path / "elsewhere" + assert _resolve(tmp_path, chosen) == chosen + + +def test_the_clone_step_requires_a_real_path(): + """Pin why the default matters: the clone step calls mkdir on it.""" + source = Path(module_dependencies.__file__).read_text() + assert "context.module_dependencies_root_dir.mkdir(" in source + + +def test_build_wheels_derives_the_default(): + source = Path(build_wheels.__file__).read_text() + assert "if not args.module_dependencies_root_dir:" in source + assert '"MODULE_DEPENDENCIES"' in source diff --git a/tests/test_target_platform.py b/tests/test_target_platform.py index fa35557e..4505416d 100644 --- a/tests/test_target_platform.py +++ b/tests/test_target_platform.py @@ -5,6 +5,7 @@ from types import SimpleNamespace import pytest +from build_wheels import remotemodulebuildandtestaction from hypothesis import given from hypothesis import strategies as st from target_platform import ( @@ -105,16 +106,28 @@ def _fake_uname(sysname, machine): return SimpleNamespace(sysname=sysname, machine=machine) -def test_detect_resolves_linux_sysname(monkeypatch): - monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "x86_64")) +def _patch_posix_uname(monkeypatch, sysname, machine): + """Present a POSIX ``os.uname`` to the code under test. + + ``raising=False`` because Windows has no ``os.uname`` to replace; + monkeypatch removes the attribute again on teardown. Without it these + tests error with ``AttributeError`` on Windows before reaching any + assertion. + """ + monkeypatch.setattr( + os, "uname", lambda: _fake_uname(sysname, machine), raising=False + ) monkeypatch.setattr(os, "name", "posix") + + +def test_detect_resolves_linux_sysname(monkeypatch): + _patch_posix_uname(monkeypatch, "Linux", "x86_64") plat = TargetPlatform.detect({}) assert plat.os_name == "linux" def test_detect_resolves_darwin_sysname(monkeypatch): - monkeypatch.setattr(os, "uname", lambda: _fake_uname("Darwin", "arm64")) - monkeypatch.setattr(os, "name", "posix") + _patch_posix_uname(monkeypatch, "Darwin", "arm64") plat = TargetPlatform.detect({}) assert plat.os_name == "darwin" @@ -127,22 +140,19 @@ def test_detect_resolves_windows_via_os_name_nt(monkeypatch): def test_detect_unsupported_sysname_raises(monkeypatch): - monkeypatch.setattr(os, "uname", lambda: _fake_uname("PlayStation", "x86_64")) - monkeypatch.setattr(os, "name", "posix") + _patch_posix_uname(monkeypatch, "PlayStation", "x86_64") with pytest.raises(ValueError, match="Unsupported operating system"): TargetPlatform.detect({}) def test_detect_target_arch_env_overrides_host_machine(monkeypatch): - monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "aarch64")) - monkeypatch.setattr(os, "name", "posix") + _patch_posix_uname(monkeypatch, "Linux", "aarch64") plat = TargetPlatform.detect({"TARGET_ARCH": "x86_64"}) assert plat.arch is Arch.X86_64 def test_detect_falls_back_to_uname_machine_without_target_arch(monkeypatch): - monkeypatch.setattr(os, "uname", lambda: _fake_uname("Linux", "aarch64")) - monkeypatch.setattr(os, "name", "posix") + _patch_posix_uname(monkeypatch, "Linux", "aarch64") plat = TargetPlatform.detect({}) assert plat.arch is Arch.AARCH64 @@ -160,6 +170,32 @@ def test_detect_never_yields_a_nickname(machine): assert plat.arch.value == plat.arch.value.lower() +def test_detect_manylinux_override_ignores_ambient_env(monkeypatch): + _patch_posix_uname(monkeypatch, "Linux", "aarch64") + env = {"MANYLINUX_VERSION": "_2_34"} + plat = TargetPlatform.detect(env, manylinux_version="") + assert plat.manylinux_version == "" + + +def test_detect_default_reads_ambient_manylinux_env(monkeypatch): + _patch_posix_uname(monkeypatch, "Linux", "x86_64") + env = {"MANYLINUX_VERSION": "_2_28"} + plat = TargetPlatform.detect(env) + assert plat.manylinux_version == "_2_28" + + +def test_detect_manylinux_override_still_validates(monkeypatch): + _patch_posix_uname(monkeypatch, "Linux", "x86_64") + with pytest.raises(ValueError, match="Unknown manylinux version"): + TargetPlatform.detect({}, manylinux_version="_2_99") + + +def test_macosx_deployment_target_matches_wheel_tag_version(monkeypatch): + monkeypatch.delenv("MACOSX_DEPLOYMENT_TARGET", raising=False) + build_default = remotemodulebuildandtestaction()["MACOSX_DEPLOYMENT_TARGET"] + assert build_default.replace(".", "_") == _MACOSX_DEPLOYMENT_TARGET + + def test_macos_wheel_plat_tag_is_installable_on_arm64(): tag = TargetPlatform("darwin", Arch.AARCH64, "").wheel_plat_tag match = re.fullmatch(r"macosx_(\d+)_0_arm64", tag) diff --git a/tests/test_wheel_builder_utils.py b/tests/test_wheel_builder_utils.py index 8d2613cb..db94f129 100644 --- a/tests/test_wheel_builder_utils.py +++ b/tests/test_wheel_builder_utils.py @@ -29,6 +29,8 @@ def test_compute_itk_package_version_refuses_an_unusable_describe(tmp_path): } def fake_run(cmd, cwd=None, env=None, check=False): + if str(cmd[1]) == "rev-parse" and "--git-dir" in [str(c) for c in cmd]: + return _completed(0, ".git") return responses[str(cmd[1])] with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): @@ -45,6 +47,8 @@ def test_compute_itk_package_version_refuses_a_zero_version(tmp_path): } def fake_run(cmd, cwd=None, env=None, check=False): + if str(cmd[1]) == "rev-parse" and "--git-dir" in [str(c) for c in cmd]: + return _completed(0, ".git") return responses[str(cmd[1])] with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): @@ -62,6 +66,8 @@ def test_compute_itk_package_version_keeps_head_on_failed_checkout(tmp_path): checked_out: list[str] = [] def fake_run(cmd, cwd=None, env=None, check=False): + if str(cmd[1]) == "rev-parse" and "--git-dir" in [str(c) for c in cmd]: + return _completed(0, ".git") verb = str(cmd[1]) if verb == "checkout": checked_out.append(str(cmd[2])) @@ -81,6 +87,8 @@ def fake_run(cmd, cwd=None, env=None, check=False): def test_compute_itk_package_version_raises_when_fetch_fails(tmp_path): def fake_run(cmd, cwd=None, env=None, check=False): + if str(cmd[1]) == "rev-parse" and "--git-dir" in [str(c) for c in cmd]: + return _completed(0, ".git") return _completed(1, "", "network down") with patch.object(wheel_builder_utils, "run_commandLine_subprocess", fake_run): diff --git a/tests/test_wheel_packaging.py b/tests/test_wheel_packaging.py new file mode 100644 index 00000000..a663be5d --- /dev/null +++ b/tests/test_wheel_packaging.py @@ -0,0 +1,156 @@ +from pathlib import Path +from types import SimpleNamespace + +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st +from wheel_packaging import ( + create_posix_tarball, + post_build_cleanup, + remove_uncompressed_tar, + tarball_paths, +) + + +def _cleanup_builder(build_root: Path, arch: str): + """A builder stub carrying exactly what post_build_cleanup reads.""" + source_dir = build_root / "src" + source_dir.mkdir(parents=True, exist_ok=True) + return SimpleNamespace( + build_dir_root=build_root, + package_env_config={"IPP_SOURCE_DIR": source_dir, "ARCH": arch}, + ) + + +def _run_cleanup(build_root: Path, arch: str) -> set[str]: + """Run the real cleanup and return the names it left behind.""" + post_build_cleanup(_cleanup_builder(build_root, arch)) + survivors = set() + for sub in ("build", "dist"): + survivors |= {p.name for p in (build_root / sub).iterdir()} + return survivors + + +def test_cleanup_selects_build_dirs_and_archives(tmp_path): + (tmp_path / "build").mkdir() + (tmp_path / "dist").mkdir() + (tmp_path / "build" / "ITK-linux-py311_x64").mkdir() + (tmp_path / "build" / "ITK-linux-py311_aarch64").mkdir() + # Real archive naming: ITKPythonBuilds-{platform}-{arch}.tar[.zst] + (tmp_path / "dist" / "ITKPythonBuilds-linux-x64.tar.zst").touch() + (tmp_path / "dist" / "itk_core-6.0.0-cp311-abi3.whl").touch() + + survivors = _run_cleanup(tmp_path, "x64") + assert "ITK-linux-py311_x64" not in survivors + assert "ITK-linux-py311_aarch64" in survivors + assert "ITKPythonBuilds-linux-x64.tar.zst" not in survivors + assert "itk_core-6.0.0-cp311-abi3.whl" in survivors + + +def test_tarball_is_written_under_dist(tmp_path): + """The cache goes in dist/, which containers mount; the parent is not.""" + tar_path, zst_path = tarball_paths(tmp_path, "linux", "x64") + assert tar_path.parent == tmp_path / "dist" + assert zst_path.parent == tmp_path / "dist" + assert tar_path.name == "ITKPythonBuilds-linux-x64.tar" + assert zst_path.name == "ITKPythonBuilds-linux-x64.tar.zst" + + +def test_uncompressed_tar_is_removed_after_zstd(tmp_path): + """A 2.5 GB .tar must not be left beside the compressed cache.""" + dist = tmp_path / "dist" + dist.mkdir() + tar_path = dist / "ITKPythonBuilds-linux-x64.tar" + zst_path = dist / "ITKPythonBuilds-linux-x64.tar.zst" + tar_path.touch() + zst_path.touch() + remove_uncompressed_tar(tar_path) + assert not tar_path.exists() + assert zst_path.exists() + + +DIRNAMES = st.text( + alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd")), + min_size=1, + max_size=12, +) + + +@given(other=DIRNAMES) +# Deadline disabled: each example does real filesystem I/O. +@settings(deadline=None) +def test_cleanup_never_removes_a_wheel(other, tmp_path_factory): + """Cleanup must never reach the wheels sitting beside the archives.""" + root = tmp_path_factory.mktemp("cleanup") + (root / "build").mkdir() + (root / "dist").mkdir() + (root / "build" / f"ITK-{other}_x64").mkdir(exist_ok=True) + wheel = root / "dist" / "itk_core-6.0.0-cp311-abi3.whl" + wheel.touch() + post_build_cleanup(_cleanup_builder(root, "x64")) + assert wheel.exists() + + +class _FakeBuilder: + """A builder stub whose echo_check_call mimics the real semantics. + + ``check=True`` raises on a non-zero status; otherwise the status is + merely returned, exactly as ``build_environment.echo_check_call`` does. + """ + + def __init__(self, build_dir_root: Path, tar_ok: bool, zstd_ok: bool): + source_dir = build_dir_root.parent / "ITKPythonPackage" + source_dir.mkdir(parents=True, exist_ok=True) + build_dir_root.mkdir(parents=True, exist_ok=True) + self.build_dir_root = build_dir_root + self.package_env_config = {"IPP_SOURCE_DIR": source_dir, "ARCH": "x64"} + self._tar_ok = tar_ok + self._zstd_ok = zstd_ok + self.commands: list[str] = [] + + def echo_check_call(self, cmd, check: bool = False, **kwargs) -> int: + cmd = [str(c) for c in cmd] + tool = cmd[0] + self.commands.append(tool) + ok = self._tar_ok if tool == "tar" else self._zstd_ok + if ok: + # Emulate the artifact the real tool would write. + output = cmd[cmd.index("-cf") + 1] if tool == "tar" else cmd[-1] + Path(output).write_bytes(b"payload") + return 0 + if check: + raise RuntimeError(f"{tool} failed") + return 1 + + +def test_failed_tar_aborts_before_zstd(tmp_path): + """A tar that produces nothing must stop the cache from being published.""" + builder = _FakeBuilder(tmp_path / "build-root", tar_ok=False, zstd_ok=True) + with pytest.raises(RuntimeError): + create_posix_tarball(builder) + assert "zstd" not in builder.commands + + +def test_failed_zstd_does_not_delete_the_uncompressed_tar(tmp_path): + """The .tar is the only remaining artifact when zstd fails; keep it.""" + build_root = tmp_path / "build-root" + builder = _FakeBuilder(build_root, tar_ok=True, zstd_ok=False) + with pytest.raises(RuntimeError): + create_posix_tarball(builder) + dist = build_root / "dist" + assert [p.name for p in dist.glob("ITKPythonBuilds-*.tar")] + assert not list(dist.glob("ITKPythonBuilds-*.tar.zst")) + + +def test_git_directory_is_excluded_entirely_not_just_its_contents(): + """A surviving empty .git makes git report "not a repository". + + Excluding only the contents leaves the directory, so a consumer cannot + tell "no repository here" from a broken one by looking at the path. That + is how the cache path failed in CI. + """ + import wheel_packaging + + source = Path(wheel_packaging.__file__).read_text() + assert '"--exclude=*/.git",' in source + assert '"--exclude=*/.git/*"' not in source From 4014917d09787366cf60ff32d784d11cf33e280a Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 19:35:13 -0500 Subject: [PATCH 08/16] COMP: Update CMake and add shell and PowerShell entry points Co-Authored-By: Cavan Riley --- .gitignore | 5 + cmake/ITKPythonPackage_BuildWheels.cmake | 250 ++++++++++++ cmake/ITKPythonPackage_SuperBuild.cmake | 369 ++++++++++++++++++ cmake/ITKPythonPackage_Utils.cmake | 252 ++++++++++++ ...dockcross-manylinux-build-module-wheels.sh | 204 +++++++--- scripts/dockcross-manylinux-build-wheels.sh | 158 ++++++-- ...-download-cache-and-build-module-wheels.sh | 101 ++++- scripts/dockcross-manylinux-download-cache.sh | 129 +++--- scripts/docker_build_environment_driver.sh | 100 +++++ ...-download-cache-and-build-module-wheels.sh | 204 ++++++---- scripts/make_tarballs.sh | 120 ++++++ scripts/make_windows_zip.ps1 | 157 ++++++++ ...download-cache-and-build-module-wheels.ps1 | 298 +++++++++----- 13 files changed, 2026 insertions(+), 321 deletions(-) create mode 100644 cmake/ITKPythonPackage_BuildWheels.cmake create mode 100644 cmake/ITKPythonPackage_SuperBuild.cmake create mode 100644 cmake/ITKPythonPackage_Utils.cmake create mode 100755 scripts/docker_build_environment_driver.sh create mode 100755 scripts/make_tarballs.sh create mode 100644 scripts/make_windows_zip.ps1 diff --git a/.gitignore b/.gitignore index 8bab1746..3d1c0542 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,8 @@ docs/_build # pixi environments .pixi/* !.pixi/config.toml + +# pixi installer that the Windows entry-point scripts fetch into the +# working directory (make_windows_zip.ps1, +# windows-download-cache-and-build-module-wheels.ps1) +install-pixi.ps1 diff --git a/cmake/ITKPythonPackage_BuildWheels.cmake b/cmake/ITKPythonPackage_BuildWheels.cmake new file mode 100644 index 00000000..94fa89d9 --- /dev/null +++ b/cmake/ITKPythonPackage_BuildWheels.cmake @@ -0,0 +1,250 @@ +#----------------------------------------------------------------------------- +#------------------------------------------------------ +#---------------------------------- +# ITKPythonPackage_SUPERBUILD: OFF +#---------------------------------- +#------------------------------------------------------ +#----------------------------------------------------------------------------- +if(NOT DEFINED ITKPythonPackage_WHEEL_NAME) + message(FATAL_ERROR "ITKPythonPackage_WHEEL_NAME must be defined") +endif() + +message( + STATUS + "SuperBuild - ITKPythonPackage_WHEEL_NAME:${ITKPythonPackage_WHEEL_NAME}" +) + +set(components "PythonWheelRuntimeLibraries") + +message(STATUS "ITKPythonPackage_WHEEL_NAME: ${ITKPythonPackage_WHEEL_NAME}") + +# Extract ITK group name from wheel name +message(STATUS "") +set(msg "Extracting ITK_WHEEL_GROUP") +message(STATUS ${msg}) +ipp_wheel_to_group(${ITKPythonPackage_WHEEL_NAME} ITK_WHEEL_GROUP) +message(STATUS "${msg} - done [${ITK_WHEEL_GROUP}]") + +# +# Considering that +# +# * Every ITK module is associated with exactly one ITK group. +# * ITK module dependencies are specified independently of ITK groups +# +# we semi-arbitrarily defined a collection of wheels (see ``ITK_WHEEL_GROUPS``) +# that will roughly bundle the modules associated with each group. +# +# Based on the module dependency graph, the code below will determine which module +# should be packaged in which wheel. +# + +# List of ITK wheel groups +set(ITK_WHEEL_GROUPS "") +file(STRINGS "${CMAKE_SOURCE_DIR}/WHEEL_NAMES.txt" ITK_WHEELS REGEX "^itk-.+") +foreach(wheel_name IN LISTS ITK_WHEELS) + ipp_wheel_to_group(${wheel_name} group) + list(APPEND ITK_WHEEL_GROUPS ${group}) +endforeach() + +# Define below a reasonable dependency graph for ITK groups +set(ITK_GROUP_Core_DEPENDS) +set(ITK_GROUP_IO_DEPENDS Core) +set(ITK_GROUP_Numerics_DEPENDS Core) +set(ITK_GROUP_Filtering_DEPENDS Numerics) +set(ITK_GROUP_Segmentation_DEPENDS Filtering) +set(ITK_GROUP_Registration_DEPENDS Filtering) +set(ITK_GROUP_Video_DEPENDS Core) + +# ITK is needed to retrieve ITK module information +set(ITK_DIR ${ITK_BINARY_DIR}) +find_package(ITK REQUIRED) +set(CMAKE_MODULE_PATH ${ITK_CMAKE_DIR} ${CMAKE_MODULE_PATH}) + +# Sort wheel groups +include(TopologicalSort) +topological_sort(ITK_WHEEL_GROUPS ITK_GROUP_ _DEPENDS) + +# Set ``ITK_MODULE__DEPENDS`` variables +# +# Notes: +# +# * ``_DEPENDS`` variables are set after calling ``find_package(ITK REQUIRED)`` +# +# * This naming convention corresponds to what is used internally in ITK and allow +# to differentiate with variable like ``ITK_GROUP__DEPENDS`` set above. +# +foreach(module IN LISTS ITK_MODULES_ENABLED) + set(ITK_MODULE_${module}_DEPENDS "${${module}_DEPENDS}") +endforeach() + +# Set ``ITK_MODULE__DEPENDEES`` variables +foreach(module IN LISTS ITK_MODULES_ENABLED) + ipp_get_module_dependees(${module} ITK_MODULE_${module}_DEPENDEES) +endforeach() + +# Set ``ITK_GROUPS`` variable +file(GLOB group_dirs "${ITK_SOURCE_DIR}/Modules/*") +set(ITK_GROUPS) +foreach(dir IN LISTS group_dirs) + file(RELATIVE_PATH group "${ITK_SOURCE_DIR}/Modules" "${dir}") + if(NOT IS_DIRECTORY "${dir}" OR "${group}" MATCHES "^External$") + continue() + endif() + list(APPEND ITK_GROUPS ${group}) +endforeach() +message(STATUS "") +message(STATUS "ITK_GROUPS:${ITK_GROUPS}") + +# Set ``ITK_MODULE__GROUP`` variables +foreach(group IN LISTS ITK_GROUPS) + file( + GLOB_RECURSE _${group}_module_files + ${ITK_SOURCE_DIR}/Modules/${group}/itk-module.cmake + ) + foreach(_module_file ${_${group}_module_files}) + file(READ ${_module_file} _module_file_content) + string( + REGEX MATCH + "itk_module[ \n]*(\\([ \n]*)([A-Za-z0-9]*)" + _module_name + ${_module_file_content} + ) + set(_module_name ${CMAKE_MATCH_2}) + list(APPEND _${group}_module_list ${_module_name}) + set(ITK_MODULE_${_module_name}_GROUP ${group}) + endforeach() +endforeach() + +# Initialize ``ITK_WHEEL__MODULES`` variables that will contain list of modules +# to package in each wheel. +foreach(group IN LISTS ITK_WHEEL_GROUPS) + set(ITK_WHEEL_${group}_MODULES "") +endforeach() + +# Configure table display +set(row_widths + 40 + 20 + 20 + 10 + 90 + 12 +) +set(row_headers + MODULE_NAME + MODULE_GROUP + WHEEL_GROUP + IS_LEAF + MODULE_DEPENDEES_GROUPS + IS_WRAPPED +) +message(STATUS "") +ipp_display_table_row("${row_headers}" "${row_widths}") + +# Update ``ITK_WHEEL__MODULES`` variables +foreach(module IN LISTS ITK_MODULES_ENABLED) + ipp_is_module_leaf(${module} leaf) + set(dependees_groups) + if(NOT leaf) + set(dependees "") + ipp_recursive_module_dependees(${module} dependees) + foreach(dep IN LISTS dependees) + list(APPEND dependees_groups ${ITK_MODULE_${dep}_GROUP}) + endforeach() + if(dependees_groups) + list(REMOVE_DUPLICATES dependees_groups) + endif() + endif() + + # Filter out group not associated with a wheel + set(dependees_wheel_groups) + foreach(group IN LISTS dependees_groups) + list(FIND ITK_WHEEL_GROUPS ${group} _index) + if(_index EQUAL -1) + continue() + endif() + list(APPEND dependees_wheel_groups ${group}) + endforeach() + + set(wheel_group) + list(LENGTH dependees_wheel_groups _length) + + # Sanity check + if(leaf AND _length GREATER 0) + message(FATAL_ERROR "leaf module should not module depending on them !") + endif() + + if(_length EQUAL 0) + set(wheel_group "${ITK_MODULE_${module}_GROUP}") + elseif(_length EQUAL 1) + # Since packages depending on this module belong to one group, also package this module + set(wheel_group "${dependees_wheel_groups}") + elseif(_length GREATER 1) + # If more than one group is associated with the dependees, package the module in the + # "common ancestor" group. + set(common_ancestor_index 999999) + foreach(g IN LISTS dependees_wheel_groups) + list(FIND ITK_WHEEL_GROUPS ${g} _index) + if(NOT _index EQUAL -1 AND _index LESS common_ancestor_index) + set(common_ancestor_index ${_index}) + endif() + endforeach() + list(GET ITK_WHEEL_GROUPS ${common_ancestor_index} wheel_group) + endif() + + set(wheel_group_display ${wheel_group}) + + # XXX Hard-coded dispatch + if(module STREQUAL "ITKBridgeNumPy") + set(new_wheel_group "Core") + set(wheel_group_display "${new_wheel_group} (was ${wheel_group})") + set(wheel_group ${new_wheel_group}) + endif() + if(module STREQUAL "ITKVTK") + set(new_wheel_group "Core") + set(wheel_group_display "${new_wheel_group} (was ${wheel_group})") + set(wheel_group ${new_wheel_group}) + endif() + + # Associate module with a wheel + list(APPEND ITK_WHEEL_${wheel_group}_MODULES ${module}) + + # Display module info + ipp_is_module_python_wrapped(${module} is_wrapped) + ipp_list_to_string("^^" "${dependees_groups}" dependees_groups_str) + set(row_values + "${module};${ITK_MODULE_${module}_GROUP};${wheel_group_display};${leaf};${dependees_groups_str};${is_wrapped}" + ) + ipp_display_table_row("${row_values}" "${row_widths}") +endforeach() + +# Set list of components to install +set(components "") +foreach(module IN LISTS ITK_WHEEL_${ITK_WHEEL_GROUP}_MODULES) + list(APPEND components ${module}PythonWheelRuntimeLibraries) +endforeach() + +if(MSVC AND ITKPythonPackage_WHEEL_NAME STREQUAL "itk-core") + message(STATUS "Adding install rules for compiler runtime libraries") + # Put the runtime libraries next to the "itk/_*.pyd" C-extensions so they + # are found. + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "itk") + include(InstallRequiredSystemLibraries) +endif() + +#----------------------------------------------------------------------------- +# Install ITK components +message(STATUS "Adding install rules for components:") +foreach(component IN LISTS components) + message(STATUS " ${component}") + install( + CODE + " +unset(CMAKE_INSTALL_COMPONENT) +set(COMPONENT \"${component}\") +set(CMAKE_INSTALL_DO_STRIP 1) +include(\"${ITK_BINARY_DIR}/cmake_install.cmake\") +unset(CMAKE_INSTALL_COMPONENT) +" + ) +endforeach() diff --git a/cmake/ITKPythonPackage_SuperBuild.cmake b/cmake/ITKPythonPackage_SuperBuild.cmake new file mode 100644 index 00000000..f027c310 --- /dev/null +++ b/cmake/ITKPythonPackage_SuperBuild.cmake @@ -0,0 +1,369 @@ +#----------------------------------------------------------------------------- +#------------------------------------------------------ +#---------------------------------- +# ITKPythonPackage_SUPERBUILD: ON +#---------------------------------- +#------------------------------------------------------ +#----------------------------------------------------------------------------- + +option( + ITKPythonPackage_USE_TBB + "Build and use oneTBB in the ITK python package" + ON +) + +# Avoid "Manually-specified variables were not used by the project" warnings. +ipp_unused_vars(${PYTHON_VERSION_STRING} ${SKBUILD}) + +set(ep_download_extract_timestamp_arg) +if(CMAKE_VERSION VERSION_EQUAL "3.24" OR CMAKE_VERSION VERSION_GREATER "3.24") + # See https://cmake.org/cmake/help/latest/policy/CMP0135.html + set(ep_download_extract_timestamp_arg DOWNLOAD_EXTRACT_TIMESTAMP 1) +endif() + +#----------------------------------------------------------------------------- +# Options + +# When building different "flavor" of ITK python packages on a given platform, +# explicitly setting the following options allow to speed up package generation by +# re-using existing resources. +# +# ITK_SOURCE_DIR: Path to an existing source directory +# + +option(ITKPythonPackage_BUILD_PYTHON "Build ITK python module" ON) +mark_as_advanced(ITKPythonPackage_BUILD_PYTHON) + +set(ep_common_cmake_cache_args) +if(NOT CMAKE_CONFIGURATION_TYPES) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") + endif() + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + ) +endif() + +if(CMAKE_OSX_DEPLOYMENT_TARGET) + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET} + ) +endif() +if(CMAKE_OSX_ARCHITECTURES) + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES} + ) +endif() + +if(CMAKE_MAKE_PROGRAM) + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM} + ) +endif() + +if(CMAKE_CXX_COMPILER) + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER} + ) +elseif(ENV{CXX}) + list(APPEND ep_common_cmake_cache_args -DCMAKE_CXX_COMPILER:PATH=$ENV{CXX}) +endif() + +if(CMAKE_C_COMPILER) + list( + APPEND ep_common_cmake_cache_args + -DCMAKE_C_COMPILER:PATH=${CMAKE_C_COMPILER} + ) +elseif(ENV{CC}) + list(APPEND ep_common_cmake_cache_args -DCMAKE_C_COMPILER:PATH=$ENV{CC}) +endif() + +#----------------------------------------------------------------------------- +# compile with multiple processors +include(ProcessorCount) +ProcessorCount(NPROC) +if(NOT NPROC EQUAL 0) + set(ENV{MAKEFLAGS} "-j${NPROC}") +endif() + +#----------------------------------------------------------------------------- +include(ExternalProject) + +#----------------------------------------------------------------------------- +# A separate project is used to download ITK, so that it can reused +# when building different "flavor" of ITK python packages + +message(STATUS "SuperBuild -") +message(STATUS "SuperBuild - ITK-source-download") + +if(NOT ITK_SOURCE_DIR AND ENV{ITK_SOURCE_DIR}) + set(ITK_SOURCE_DIR "$ENV{ITK_SOURCE_DIR}") +endif() + +set(tbb_depends "") +set(tbb_args -DModule_ITKTBB:BOOL=OFF) +if(ITKPythonPackage_USE_TBB) + set(TBB_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/../oneTBB-prefix") + set(TBB_DIR "${TBB_INSTALL_PREFIX}/lib/cmake/TBB") + set(tbb_args -DModule_ITKTBB:BOOL=ON -DTBB_DIR:PATH=${TBB_DIR}) + + set(tbb_cmake_cache_args) + if(CMAKE_OSX_DEPLOYMENT_TARGET) + list( + APPEND tbb_cmake_cache_args + -DCMAKE_CXX_OSX_DEPLOYMENT_TARGET_FLAG:STRING="-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" + -DCMAKE_C_OSX_DEPLOYMENT_TARGET_FLAG:STRING="-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" + ) + endif() + + ExternalProject_Add( + oneTBB + URL + https://github.com/oneapi-src/oneTBB/archive/refs/tags/v2022.2.0.tar.gz + URL_HASH + SHA256=f0f78001c8c8edb4bddc3d4c5ee7428d56ae313254158ad1eec49eced57f6a5b + CMAKE_ARGS + -DTBB_TEST:BOOL=OFF + -DCMAKE_INSTALL_PREFIX:PATH=${TBB_INSTALL_PREFIX} + -DCMAKE_INSTALL_LIBDIR:STRING=lib # Skip default initialization by GNUInstallDirs CMake module + ${ep_common_cmake_cache_args} ${tbb_cmake_cache_args} + ${ep_download_extract_timestamp_arg} + BUILD_BYPRODUCTS "${TBB_DIR}/TBBConfig.cmake" + USES_TERMINAL_DOWNLOAD 1 + USES_TERMINAL_UPDATE 1 + USES_TERMINAL_CONFIGURE 1 + USES_TERMINAL_BUILD 1 + ) + message(STATUS "SuperBuild - TBB: Enabled") + message(STATUS "SuperBuild - TBB_DIR: ${TBB_DIR}") + set(tbb_depends oneTBB) +endif() + +# Only add ITK-source-download ExternalProject if directory does not +# already exist +if(NOT EXISTS ${ITK_SOURCE_DIR}) + set(ITK_REPOSITORY "https://github.com/InsightSoftwareConsortium/ITK.git") + + if(NOT DEFINED ITK_GIT_TAG AND DEFINED ENV{ITK_GIT_TAG}) + set(ITK_GIT_TAG "$ENV{ITK_GIT_TAG}") + endif() + + if(NOT DEFINED ITK_GIT_TAG) + message( + FATAL_ERROR + "ITK_GIT_TAG must be defined when configuring cmake" + ) + endif() + ExternalProject_Add( + ITK-source-download + SOURCE_DIR ${ITK_SOURCE_DIR} + GIT_REPOSITORY ${ITK_REPOSITORY} + GIT_TAG ${ITK_GIT_TAG} + USES_TERMINAL_DOWNLOAD 1 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + DEPENDS "${tbb_depends}" + ) + set(proj_status "") +else() + # Suppress unused variable warning + set(_unused "${ITK_GIT_TAG}") + ipp_externalproject_add_empty( + ITK-source-download + "" + ) + set(proj_status " (REUSE)") +endif() + +message(STATUS "SuperBuild - ITK_SOURCE_DIR: ${ITK_SOURCE_DIR}") +message(STATUS "SuperBuild - ITK-source-download[OK]${proj_status}") + +#----------------------------------------------------------------------------- +if(NOT ITKPythonPackage_BUILD_PYTHON) + return() +endif() + +#----------------------------------------------------------------------------- +# Search for python interpreter and libraries + +message(STATUS "SuperBuild -") +message(STATUS "SuperBuild - Searching for python") + +# Sanity checks +if(DEFINED Python3_INCLUDE_DIR AND NOT EXISTS ${Python3_INCLUDE_DIR}) + message( + FATAL_ERROR + "Python3_INCLUDE_DIR=${Python3_INCLUDE_DIR}: variable is defined but corresponds to nonexistent directory" + ) +endif() +if(DEFINED Python3_LIBRARY AND NOT EXISTS ${Python3_LIBRARY}) + message( + FATAL_ERROR + "Python3_LIBRARY=${Python3_LIBRARY}: variable is defined but corresponds to nonexistent file" + ) +endif() +if(DEFINED Python3_EXECUTABLE AND NOT EXISTS ${Python3_EXECUTABLE}) + message( + FATAL_ERROR + "Python3_EXECUTABLE=${Python3_EXECUTABLE}: variable is defined but corresponds to nonexistent file" + ) +endif() +if(DEFINED DOXYGEN_EXECUTABLE AND NOT EXISTS ${DOXYGEN_EXECUTABLE}) + message( + FATAL_ERROR + "DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}: variable is defined but corresponds to nonexistent file" + ) +endif() + +if( + NOT DEFINED Python3_INCLUDE_DIR + OR NOT DEFINED Python3_LIBRARY + OR NOT DEFINED Python3_EXECUTABLE +) + find_package(Python3 COMPONENTS Interpreter Development) + if(NOT Python3_EXECUTABLE AND _Python3_EXECUTABLE) + set(Python3_EXECUTABLE + ${_Python3_EXECUTABLE} + CACHE INTERNAL + "Path to the Python interpreter" + FORCE + ) + endif() +endif() +if(NOT DEFINED DOXYGEN_EXECUTABLE) + find_package(Doxygen REQUIRED) +endif() + +message(STATUS "SuperBuild - Python3_INCLUDE_DIR: ${Python3_INCLUDE_DIR}") +message(STATUS "SuperBuild - Python3_INCLUDE_DIRS: ${Python3_INCLUDE_DIRS}") +message(STATUS "SuperBuild - Python3_LIBRARY: ${Python3_LIBRARY}") +message(STATUS "SuperBuild - Python3_SABI_LIBRARY: ${Python3_SABI_LIBRARY}") +message(STATUS "SuperBuild - Python3_EXECUTABLE: ${Python3_EXECUTABLE}") +message(STATUS "SuperBuild - Searching for python[OK]") +message(STATUS "SuperBuild - DOXYGEN_EXECUTABLE: ${DOXYGEN_EXECUTABLE}") + +# CMake configuration variables to pass to ITK's build +set(ep_itk_cmake_cache_args "") +foreach(var BUILD_SHARED_LIBS ITK_BUILD_DEFAULT_MODULES) + if(DEFINED ${var}) + list(APPEND ep_itk_cmake_cache_args "-D${var}=${${var}}") + endif() +endforeach() +function(cached_variables RESULTVAR PATTERN) + get_cmake_property(variables CACHE_VARIABLES) + set(result) + foreach(variable ${variables}) + if(${variable} AND variable MATCHES "${PATTERN}") + list(APPEND result "-D${variable}=${${variable}}") + endif() + endforeach() + set(${RESULTVAR} ${result} PARENT_SCOPE) +endfunction() +cached_variables(itk_pattern_cached_vars "^(ITK_WRAP_)|(ITKGroup_)|(Module_)") +list(APPEND ep_itk_cmake_cache_args ${itk_pattern_cached_vars}) +# Todo, also pass all Module_* variables +message(STATUS "ITK CMake Cache Args - ${ep_itk_cmake_cache_args}") +#----------------------------------------------------------------------------- +# ITK: This project builds ITK and associated Python modules + +option( + ITKPythonPackage_ITK_BINARY_REUSE + "Reuse provided ITK_BINARY_DIR without configuring or building ITK" + OFF +) + +set(ITK_BINARY_DIR "${CMAKE_BINARY_DIR}/ITKb" CACHE PATH "ITK build directory") + +message(STATUS "SuperBuild -") +message(STATUS "SuperBuild - ITK => Requires ITK-source-download") +message(STATUS "SuperBuild - ITK_BINARY_DIR: ${ITK_BINARY_DIR}") + +if(NOT ITKPythonPackage_ITK_BINARY_REUSE) + set(_stamp "${CMAKE_BINARY_DIR}/ITK-prefix/src/ITK-stamp/ITK-configure") + if(EXISTS ${_stamp}) + execute_process(COMMAND ${CMAKE_COMMAND} -E remove ${_stamp}) + message(STATUS "SuperBuild - Force re-configure removing ${_stamp}") + endif() + + ExternalProject_Add( + ITK + DOWNLOAD_COMMAND "" + SOURCE_DIR ${ITK_SOURCE_DIR} + BINARY_DIR ${ITK_BINARY_DIR} + PREFIX "ITKp" + CMAKE_ARGS + -DBUILD_TESTING:BOOL=OFF + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} + -DPY_SITE_PACKAGES_PATH:PATH=${CMAKE_INSTALL_PREFIX} + -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel + -DWRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL=ON + -DITK_LEGACY_SILENT:BOOL=ON -DITK_WRAP_PYTHON:BOOL=ON + -DITK_DEFAULT_THREADER:STRING=Pool + -DDOXYGEN_EXECUTABLE:FILEPATH=${DOXYGEN_EXECUTABLE} + -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} + -DPython3_LIBRARY:FILEPATH=${Python3_LIBRARY} + -DPython3_SABI_LIBRARY:FILEPATH=${Python3_SABI_LIBRARY} + -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} + ${ep_common_cmake_cache_args} ${tbb_args} ${ep_itk_cmake_cache_args} + ${ep_download_extract_timestamp_arg} + USES_TERMINAL_DOWNLOAD 1 + USES_TERMINAL_UPDATE 1 + USES_TERMINAL_CONFIGURE 1 + USES_TERMINAL_BUILD 1 + INSTALL_COMMAND "" + ) + set(proj_status "") +else() + # Sanity checks + if(NOT EXISTS "${ITK_BINARY_DIR}/CMakeCache.txt") + message( + FATAL_ERROR + "ITKPythonPackage_ITK_BINARY_REUSE is ON but ITK_BINARY_DIR variable is not associated with an ITK build directory. [ITK_BINARY_DIR:${ITK_BINARY_DIR}]" + ) + endif() + + ipp_externalproject_add_empty( + ITK + "" + ) + set(proj_status " (REUSE)") +endif() +ExternalProject_Add_StepDependencies(ITK download ITK-source-download) + +message(STATUS "SuperBuild - ITK[OK]${proj_status}") + +#----------------------------------------------------------------------------- +# ITKPythonPackage: This project adds install rules for the "RuntimeLibraries" +# components associated with the ITK project. + +message(STATUS "SuperBuild -") +message(STATUS "SuperBuild - ${PROJECT_NAME} => Requires ITK") + +ExternalProject_Add( + ${PROJECT_NAME} + SOURCE_DIR ${CMAKE_SOURCE_DIR} + BINARY_DIR ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-build + DOWNLOAD_COMMAND "" + UPDATE_COMMAND "" + CMAKE_CACHE_ARGS + -DITKPythonPackage_SUPERBUILD:BOOL=0 + -DITK_BINARY_DIR:PATH=${ITK_BINARY_DIR} + -DITK_SOURCE_DIR:PATH=${ITK_SOURCE_DIR} + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} + -DITKPythonPackage_WHEEL_NAME:STRING=${ITKPythonPackage_WHEEL_NAME} + -DITKPythonPackage_USE_TBB:BOOL=${ITKPythonPackage_USE_TBB} + ${ep_common_cmake_cache_args} + USES_TERMINAL_CONFIGURE 1 + INSTALL_COMMAND "" + DEPENDS ITK +) + +install(SCRIPT ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-build/cmake_install.cmake) + +message(STATUS "SuperBuild - ${PROJECT_NAME}[OK]") diff --git a/cmake/ITKPythonPackage_Utils.cmake b/cmake/ITKPythonPackage_Utils.cmake new file mode 100644 index 00000000..5282b416 --- /dev/null +++ b/cmake/ITKPythonPackage_Utils.cmake @@ -0,0 +1,252 @@ +# ipp_ExternalProject_Add_Empty( ) +# +# Add an empty external project +# +function(ipp_ExternalProject_Add_Empty proj depends) + set(depends_args) + if(NOT depends STREQUAL "") + set(depends_args DEPENDS ${depends}) + endif() + ExternalProject_Add( + ${proj} + SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} + DOWNLOAD_COMMAND "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + BUILD_IN_SOURCE 1 + BUILD_ALWAYS 1 + INSTALL_COMMAND "" ${depends_args} + ) +endfunction() + +# ipp_get_module_dependees( ) +# +# Collect all modules depending on ````. +# +function(ipp_get_module_dependees itk-module output_var) + set(dependees "") + foreach(m_enabled IN LISTS ITK_MODULES_ENABLED) + list(FIND ITK_MODULE_${m_enabled}_DEPENDS ${itk-module} _index) + if(NOT _index EQUAL -1) + list(APPEND dependees ${m_enabled}) + endif() + endforeach() + list(REMOVE_DUPLICATES dependees) + set(${output_var} ${dependees} PARENT_SCOPE) +endfunction() + +function(_recursive_deps item-type item-category itk-item output_var) + set(_${itk-item}_deps) + foreach(dep IN LISTS ITK_${item-type}_${itk-item}_${item-category}) + list(APPEND _${itk-item}_deps ${dep}) + _recursive_deps(${item-type} ${item-category} ${dep} _${itk-item}_deps) + endforeach() + list(APPEND ${output_var} ${_${itk-item}_deps}) + list(REMOVE_DUPLICATES ${output_var}) + set(${output_var} ${${output_var}} PARENT_SCOPE) +endfunction() + +# ipp_recursive_module_dependees( ) +# +# Recursively collect all modules depending on ````. +# +function(ipp_recursive_module_dependees itk-module output_var) + set(_${itk-module}_deps) + _recursive_deps("MODULE" "DEPENDEES" ${itk-module} ${output_var}) + set(${output_var} ${${output_var}} PARENT_SCOPE) +endfunction() + +# ipp_is_module_leaf( ) +# +# If ```` has no dependencies, set `` to 1 +# otherwise set `` to 0. +# +function(ipp_is_module_leaf itk-module output_var) + set(leaf 1) + foreach(m_enabled IN LISTS ITK_MODULES_ENABLED) + list(FIND ITK_MODULE_${m_enabled}_DEPENDS ${itk-module} _index) + if(NOT _index EQUAL -1) + set(leaf 0) + break() + endif() + endforeach() + set(${output_var} ${leaf} PARENT_SCOPE) +endfunction() + +# ipp_is_module_python_wrapped( ) +# +# If ```` is wrapped in python, set `` to 1 +# otherwise set `` to 0. +# +function(ipp_is_module_python_wrapped itk-module output_var) + set(wrapped 0) + if(NOT DEFINED ITK_MODULE_${itk-module}_GROUP) + message( + AUTHOR_WARNING + "Variable ITK_MODULE_${itk-module}_GROUP is not defined" + ) + else() + set(group ${ITK_MODULE_${itk-module}_GROUP}) + set(module_folder ${itk-module}) + # if any, strip ITK prefix + if(module_folder MATCHES "^ITK.+$") + string( + REGEX REPLACE + "^ITK(.+)$" + "\\1" + module_folder + ${module_folder} + ) + endif() + if( + EXISTS + ${ITK_SOURCE_DIR}/Modules/${group}/${itk-module}/wrapping/CMakeLists.txt + OR EXISTS + ${ITK_SOURCE_DIR}/Modules/${group}/${module_folder}/wrapping/CMakeLists.txt + ) + set(wrapped 1) + endif() + endif() + set(${output_var} ${wrapped} PARENT_SCOPE) +endfunction() + +# ipp_wheel_to_group( ) +# +# Extract ITK group name from wheel name (e.g 'itk-core' -> 'Core'). +# +# If the group name has less than 3 characters, take the uppercase +# value (e.g 'itk-io' -> 'IO'). +# +function(ipp_wheel_to_group wheel_name group_name_var) + string(REPLACE "itk-" "" _group ${wheel_name}) + string(SUBSTRING ${_group} 0 1 _first) + string(TOUPPER ${_first} _first_uc) + string(SUBSTRING ${_group} 1 -1 _remaining) + set(group_name "${_first_uc}${_remaining}") + # Convert to upper case if length <= 2 + string(LENGTH ${group_name} _length) + if(_length LESS 3) + string(TOUPPER ${group_name} group_name) + endif() + set(${group_name_var} ${group_name} PARENT_SCOPE) +endfunction() + +# ipp_pad_text( ) +# +# Example: +# +# set(row "Apple") +# ipp_pad_text(${row} 20 row) +# +# set(row "${row}Banana") +# ipp_pad_text(${row} 40 row) +# +# set(row "${row}Kiwi") +# ipp_pad_text(${row} 60 row) +# +# message(${row}) +# +# Output: +# +# Apple Banana Kiwi +# +function(ipp_pad_text text text_right_jusitfy_length output_var) + set(fill_char " ") + string(LENGTH "${text}" text_length) + math(EXPR pad_length "${text_right_jusitfy_length} - ${text_length} - 1") + if(pad_length GREATER 0) + string(RANDOM LENGTH ${pad_length} ALPHABET ${fill_char} text_dots) + set(${output_var} "${text} ${text_dots}" PARENT_SCOPE) + else() + set(${output_var} "${text}" PARENT_SCOPE) + endif() +endfunction() + +# ipp_display_table_row( ) +# +# Example: +# +# ipp_display_table_row("Apple^^Banana^^Kiwi" "20;20;20") +# ipp_display_table_row("Eiger^^Rainer^^Sajama" "20;20;20") +# +# Output: +# +# Apple Banana Kiwi +# Eiger Rainer Sajama +# +function(ipp_display_table_row values widths) + list(LENGTH values length) + set(text "") + math(EXPR range "${length} - 1") + foreach(index RANGE ${range}) + list(GET widths ${index} width) + list(GET values ${index} value) + string(REPLACE "^^" ";" value "${value}") + ipp_pad_text("${value}" ${width} value) + set(text "${text}${value}") + endforeach() + message(STATUS "${text}") +endfunction() + +# ipp_list_to_string( ) +# +# Example: +# +# set(values Foo Bar Oof) +# message("${values}") +# ipp_list_to_string("^^" "${values}" values) +# message("${values}") +# +# Output: +# +# Foo;Bar;Oof +# Foo^^Bar^^Oof +# +# Copied from Slicer/CMake/ListToString.cmake +# +function(ipp_list_to_string separator input_list output_string_var) + set(_string "") + # Get list length + list(LENGTH input_list list_length) + # If the list has 0 or 1 element, there is no need to loop over. + if(list_length LESS 2) + set(_string "${input_list}") + else() + math(EXPR last_element_index "${list_length} - 1") + foreach(index RANGE ${last_element_index}) + # Get current item_value + list(GET input_list ${index} item_value) + if(NOT item_value STREQUAL "") + # .. and append non-empty value to output string + set(_string "${_string}${item_value}") + # Append separator if current element is NOT the last one. + if(NOT index EQUAL last_element_index) + set(_string "${_string}${separator}") + endif() + endif() + endforeach() + endif() + set(${output_string_var} ${_string} PARENT_SCOPE) +endfunction() + +# No-op function allowing to shut-up "Manually-specified variables were not used by the project" +# warnings. +function(ipp_unused_vars) +endfunction() + +# +# Unused +# + +function(recursive_module_deps itk-module output_var) + set(_${itk-module}_deps) + _recursive_deps("MODULE" "DEPENDS" ${itk-module} ${output_var}) + set(${output_var} ${${output_var}} PARENT_SCOPE) +endfunction() + +function(recursive_group_deps itk-group output_var) + set(_${itk-group}_deps) + _recursive_deps("GROUP" "DEPENDS" ${itk-group} ${output_var}) + set(${output_var} ${${output_var}} PARENT_SCOPE) +endfunction() diff --git a/scripts/dockcross-manylinux-build-module-wheels.sh b/scripts/dockcross-manylinux-build-module-wheels.sh index 392a2190..1f580310 100755 --- a/scripts/dockcross-manylinux-build-module-wheels.sh +++ b/scripts/dockcross-manylinux-build-module-wheels.sh @@ -4,13 +4,10 @@ # Run this script to build the Python wheel packages for Linux for an ITK # external module. # -# ======================================================================== -# PARAMETERS -# # Versions can be restricted by passing them in as arguments to the script # For example, # -# scripts/dockcross-manylinux-build-module-wheels.sh cp310 +# scripts/dockcross-manylinux-build-module-wheels.sh cp311 # # =========================================== # ENVIRONMENT VARIABLES @@ -19,20 +16,20 @@ # For example, # # export MANYLINUX_VERSION="_2_28" -# scripts/dockcross-manylinux-build-module-wheels.sh cp310 +# scripts/dockcross-manylinux-build-module-wheels.sh cp311 # # `LD_LIBRARY_PATH`: Shared libraries to be included in the resulting wheel. # For instance, `export LD_LIBRARY_PATH="/path/to/OpenCL.so:/path/to/OpenCL.so.1.2"` # # `MANYLINUX_VERSION`: Specialized manylinux image to use for building. Default is _2_28. # See https://github.com/dockcross/dockcross for available versions and tags. -# For instance, `export MANYLINUX_VERSION=2014` +# For instance, `export MANYLINUX_VERSION=_2_28` # # `TARGET_ARCH`: Target architecture for which wheels should be built. # For instance, `export MANYLINUX_VERSION=aarch64` # # `IMAGE_TAG`: Specialized manylinux image tag to use for building. -# For instance, `export IMAGE_TAG=20221205-459c9f0`. +# For instance, `export IMAGE_TAG=2025.08.12-1`. # Tagged images are available at: # - https://github.com/dockcross/dockcross (x64 architecture) # - https://quay.io/organization/pypa (ARM architecture) @@ -44,62 +41,179 @@ # # - `NO_SUDO`: Disable the use of superuser permissions for running docker. # +# DIRECTORY STRUCTURE (expected): +# ${IPP_DIR}/ <- ITKPythonPackage +# ${BUILD_DIR}/ <- ITKPythonPackage-manylinux${VERSION}-build +# ${MODULE_SOURCE_DIR}/ <- Module source (current directory) ######################################################################## -# Handle case where the script directory is not the working directory -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/dockcross-manylinux-set-vars.sh" -source "${script_dir}/oci_exe.sh" +script_dir=$( + cd "$(dirname "$0")" || exit 1 + pwd +) +_ipp_dir=$(dirname "${script_dir}") + +# ----------------------------------------------------------------------- +# Find container runtime +for cand in nerdctl docker podman; do + if which "${cand}" >/dev/null 2>&1; then + export OCI_EXE=${OCI_EXE:-"$cand"} + break + fi +done -oci_exe=$(ociExe) +if [ -z "${OCI_EXE}" ]; then + echo "ERROR: No container runtime found. Please install docker, podman, or nerdctl." + exit 1 +fi +echo "Found OCI_EXE=$(which "${OCI_EXE}")" -if [[ -n ${ITK_MODULE_PREQ} ]]; then - echo "Building module dependencies ${ITK_MODULE_PREQ}" - source "${script_dir}/dockcross-manylinux-build-module-deps.sh" +# ----------------------------------------------------------------------- +# Set default values +MANYLINUX_VERSION=${MANYLINUX_VERSION:-_2_28} +TARGET_ARCH=${TARGET_ARCH:-x64} +# Default image tag differs by architecture: +# x64 → dockcross/manylinux image (docker.io/dockcross) +# aarch64 → pypa manylinux image (quay.io/pypa, native ARM64 / QEMU on x64) +if [[ "${TARGET_ARCH}" == "aarch64" ]]; then + IMAGE_TAG=${IMAGE_TAG:-2025.08.12-1} + CONTAINER_SOURCE=${CONTAINER_SOURCE:-"quay.io/pypa/manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}:${IMAGE_TAG}"} +else + # if x64 arch then default manylinux version is _2_28 + IMAGE_TAG=${IMAGE_TAG:=20260203-3dfb3ff} + CONTAINER_SOURCE=${CONTAINER_SOURCE:-"docker.io/dockcross/manylinux${MANYLINUX_VERSION}-${TARGET_ARCH}:${IMAGE_TAG}"} fi +ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:-InsightSoftwareConsortium} +ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG:-main} + +# For backwards compatibility +ITK_GIT_TAG=${ITK_GIT_TAG:-${ITK_PACKAGE_VERSION}} + +# Required environment variables +required_vars=( + ITK_GIT_TAG + MANYLINUX_VERSION + IMAGE_TAG + TARGET_ARCH + ITKPYTHONPACKAGE_ORG + ITKPYTHONPACKAGE_TAG +) +# Sanity Validation loop +_missing_required=0 +for v in "${required_vars[@]}"; do + if [ -z "${!v:-}" ]; then + _missing_required=1 + echo "ERROR: Required environment variable '$v' is not set or empty." + fi +done +if [ "${_missing_required}" -ne 0 ]; then + exit 1 +fi +unset _missing_required + +mkdir -p "${_ipp_dir}"/build +cd "$(dirname "${_ipp_dir}")" || exit + +HOST_MODULE_DIRECTORY=${MODULE_SRC_DIRECTORY} # Set up paths and variables for build -mkdir -p $(pwd)/tools -chmod 777 $(pwd)/tools -mkdir -p dist -DOCKER_ARGS="-v $(pwd)/dist:/work/dist/ -v ${script_dir}/..:/ITKPythonPackage -v $(pwd)/tools:/tools" -DOCKER_ARGS+=" -e MANYLINUX_VERSION" -DOCKER_ARGS+=" -e LD_LIBRARY_PATH" -# Mount any shared libraries +CONTAINER_WORK_DIR=/work +CONTAINER_PACKAGE_BUILD_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage-build +CONTAINER_PACKAGE_SCRIPTS_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage +HOST_PACKAGE_BUILD_DIR=$(dirname "${_ipp_dir}")/ITKPythonPackage-build +HOST_PACKAGE_SCRIPTS_DIR=${_ipp_dir} + +CONTAINER_ITK_SOURCE_DIR=${CONTAINER_PACKAGE_BUILD_DIR}/ITK +HOST_PACKAGE_DIST=${HOST_MODULE_DIRECTORY}/dist +mkdir -p "${HOST_PACKAGE_DIST}" +CONTAINER_MODULE_DIR=${CONTAINER_WORK_DIR}/$(basename "${MODULE_SRC_DIRECTORY}") +CONTAINER_MODULE_DEPS_DIR=${CONTAINER_WORK_DIR}/$(basename "${MODULE_DEPS_DIR}") + +# Build docker arguments +DOCKER_ARGS=" --network=host " +DOCKER_ARGS+=" -v ${HOST_PACKAGE_BUILD_DIR}:${CONTAINER_PACKAGE_BUILD_DIR} " +DOCKER_ARGS+=" -v ${HOST_PACKAGE_SCRIPTS_DIR}:${CONTAINER_PACKAGE_SCRIPTS_DIR} " +DOCKER_ARGS+=" -v ${MODULE_SRC_DIRECTORY}:${CONTAINER_MODULE_DIR} " +DOCKER_ARGS+=" -v ${MODULE_DEPS_DIR}:${CONTAINER_MODULE_DEPS_DIR} " + +if [ "${ITK_SOURCE_DIR}" != "" ]; then + DOCKER_ARGS+=" -v ${ITK_SOURCE_DIR}:${CONTAINER_ITK_SOURCE_DIR} " +fi + +# Environment variables for the container +DOCKER_ARGS+=" -e PYTHONUNBUFFERED=1 " +DOCKER_ARGS+=" -e CMAKE_OPTIONS='${CMAKE_OPTIONS}' " + +# Mount shared libraries if LD_LIBRARY_PATH is set if [[ -n ${LD_LIBRARY_PATH} ]]; then for libpath in ${LD_LIBRARY_PATH//:/ }; do - DOCKER_LIBRARY_PATH="/usr/lib64/$(basename -- ${libpath})" - DOCKER_ARGS+=" -v ${libpath}:${DOCKER_LIBRARY_PATH}" - if test -d ${libpath}; then - DOCKER_LD_LIBRARY_PATH+="${DOCKER_LIBRARY_PATH}:${DOCKER_LD_LIBRARY_PATH}" - fi + DOCKER_LIBRARY_PATH="/usr/lib64/$(basename -- "${libpath}")" + DOCKER_ARGS+=" -v ${libpath}:${DOCKER_LIBRARY_PATH}" + if test -d "${libpath}"; then + DOCKER_LD_LIBRARY_PATH+="${DOCKER_LIBRARY_PATH}:${DOCKER_LD_LIBRARY_PATH}" + fi done fi export LD_LIBRARY_PATH="${DOCKER_LD_LIBRARY_PATH}" -if [[ "${TARGET_ARCH}" = "aarch64" ]]; then - echo "Install aarch64 architecture emulation tools to perform build for ARM platform" +# To build tarballs in manylinux, use 'export BUILD_WHEELS_EXTRA_FLAGS=" --build-itk-tarball-cache "' +BUILD_WHEELS_EXTRA_FLAGS=${BUILD_WHEELS_EXTRA_FLAGS:=""} # No tarball by default +PY_ENVS=("${@:-py311}") + +if [[ "${TARGET_ARCH}" == "aarch64" ]]; then + # aarch64: run the quay.io/pypa native image directly. + # On ARM64 hosts (e.g. Apple Silicon) this runs natively. + # On x64 hosts, first register QEMU binfmt emulation. if [[ ! ${NO_SUDO} ]]; then docker_prefix="sudo" fi + # Only install QEMU binfmt emulation on non-ARM64 hosts; ARM64 hosts run natively + if [[ "$(uname -m)" != "arm64" && "$(uname -m)" != "aarch64" ]]; then + echo "Installing aarch64 architecture emulation tools to perform build for ARM platform" + ${docker_prefix} "$OCI_EXE" run --privileged --rm tonistiigi/binfmt --install all + fi - ${docker_prefix} $oci_exe run --privileged --rm tonistiigi/binfmt --install all - - # Build wheels - DOCKER_ARGS+=" -v $(pwd):/work/ --rm" - ${docker_prefix} $oci_exe run $DOCKER_ARGS ${CONTAINER_SOURCE} "/ITKPythonPackage/scripts/internal/manylinux-aarch64-build-module-wheels.sh" "$@" + cmd="${docker_prefix} \"$OCI_EXE\" run --rm \ + ${DOCKER_ARGS} \ + -e PY_ENVS=\"${PY_ENVS[*]}\" \ + -e ITK_GIT_TAG=\"${ITK_GIT_TAG}\" \ + -e ITK_PACKAGE_VERSION=\"${ITK_PACKAGE_VERSION}\" \ + -e ITK_MODULE_PREQ=\"${ITK_MODULE_PREQ}\" \ + -e MODULE_SRC_DIRECTORY=\"${CONTAINER_MODULE_DIR}\" \ + -e MODULE_DEPS_DIR=\"${CONTAINER_MODULE_DEPS_DIR}\" \ + -e MANYLINUX_VERSION=\"${MANYLINUX_VERSION}\" \ + -e IMAGE_TAG=\"${IMAGE_TAG}\" \ + -e TARGET_ARCH=\"${TARGET_ARCH}\" \ + -e ITKPYTHONPACKAGE_ORG=\"${ITKPYTHONPACKAGE_ORG}\" \ + -e ITKPYTHONPACKAGE_TAG=\"${ITKPYTHONPACKAGE_TAG}\" \ + -e BUILD_WHEELS_EXTRA_FLAGS=\"${BUILD_WHEELS_EXTRA_FLAGS}\" \ + ${CONTAINER_SOURCE} \ + /bin/bash -x ${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/docker_build_environment_driver.sh" else - # Generate dockcross scripts - $oci_exe run --rm ${CONTAINER_SOURCE} > /tmp/dockcross-manylinux-x64 - chmod u+x /tmp/dockcross-manylinux-x64 - - # Build wheels - /tmp/dockcross-manylinux-x64 \ - -a "$DOCKER_ARGS" \ - "/ITKPythonPackage/scripts/internal/manylinux-build-module-wheels.sh" "$@" -fi + # x64: generate the dockcross runner script from the image, then invoke it. + _local_dockcross_script=${_ipp_dir}/build/runner_dockcross-${MANYLINUX_VERSION}-${TARGET_ARCH}_${IMAGE_TAG}.sh + "$OCI_EXE" run --rm "${CONTAINER_SOURCE}" >"${_local_dockcross_script}" + chmod u+x "${_local_dockcross_script}" -if [[ -z ${ITK_MODULE_NO_CLEANUP} ]]; then - source "${script_dir}/dockcross-manylinux-cleanup.sh" + # When building ITK remote wheels, --module-source-dir, --module-dependencies-root-dir, and --itk-module-deps should be present + cmd="bash -x ${_local_dockcross_script} \ + -a \"$DOCKER_ARGS\" \ + /usr/bin/env \ + PY_ENVS=\"${PY_ENVS[*]}\" \ + ITK_GIT_TAG=\"${ITK_GIT_TAG}\" \ + ITK_PACKAGE_VERSION=\"${ITK_PACKAGE_VERSION}\" \ + ITK_MODULE_PREQ=\"${ITK_MODULE_PREQ}\" \ + MODULE_SRC_DIRECTORY=\"${CONTAINER_MODULE_DIR}\" \ + MODULE_DEPS_DIR=\"${CONTAINER_MODULE_DEPS_DIR}\" \ + MANYLINUX_VERSION=\"${MANYLINUX_VERSION}\" \ + IMAGE_TAG=\"${IMAGE_TAG}\" \ + TARGET_ARCH=\"${TARGET_ARCH}\" \ + ITKPYTHONPACKAGE_ORG=\"${ITKPYTHONPACKAGE_ORG}\" \ + ITKPYTHONPACKAGE_TAG=\"${ITKPYTHONPACKAGE_TAG}\" \ + BUILD_WHEELS_EXTRA_FLAGS=\"${BUILD_WHEELS_EXTRA_FLAGS}\" \ + /bin/bash -x ${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/docker_build_environment_driver.sh" fi + +echo "RUNNING: $cmd" +eval "$cmd" diff --git a/scripts/dockcross-manylinux-build-wheels.sh b/scripts/dockcross-manylinux-build-wheels.sh index 0619b301..754ddfa1 100755 --- a/scripts/dockcross-manylinux-build-wheels.sh +++ b/scripts/dockcross-manylinux-build-wheels.sh @@ -1,48 +1,148 @@ #!/bin/bash - # Run this script to build the ITK Python wheel packages for Linux. # # Versions can be restricted by passing them in as arguments to the script # For example, # -# scripts/dockcross-manylinux-build-wheels.sh cp310 +# scripts/dockcross-manylinux-build-wheels.sh cp311 # -# A specialized manylinux image and tag can be used by exporting to -# MANYLINUX_VERSION and IMAGE_TAG before running this script. -# See https://github.com/dockcross/dockcross for available versions and tags. +# A specialized manylinux image and tag can be used by setting +# MANYLINUX_VERSION and IMAGE_TAG # # For example, # -# export MANYLINUX_VERSION=2014 -# export IMAGE_TAG=20221205-459c9f0 -# scripts/dockcross-manylinux-build-module-wheels.sh cp310 +# export MANYLINUX_VERSION=_2_28 +# export IMAGE_TAG=20260203-3dfb3ff +# scripts/dockcross-manylinux-build-module-wheels.sh cp311 # -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/oci_exe.sh" +script_dir=$( + cd "$(dirname "$0")" || exit 1 + pwd +) +_ipp_dir=$(dirname "${script_dir}") -oci_exe=$(ociExe) +for cand in nerdctl docker podman; do + if which "${cand}" >/dev/null; then + export OCI_EXE=${OCI_EXE:="$cand"} + break + fi +done +echo "FOUND OCI_EXE=$(which "${OCI_EXE}")" +#For backwards compatibility when the ITK_GIT_TAG was required to match the ITK_PACKAGE_VERSION +ITK_GIT_TAG=${ITK_GIT_TAG:=${ITK_PACKAGE_VERSION:-main}} MANYLINUX_VERSION=${MANYLINUX_VERSION:=_2_28} -if [[ ${MANYLINUX_VERSION} == _2_28 ]]; then +# Default image tag differs by architecture: +# x64 → dockcross/manylinux image (docker.io/dockcross) +# aarch64 → pypa manylinux image (quay.io/pypa, native ARM64 / QEMU on x64) +if [[ "${TARGET_ARCH}" == "aarch64" ]]; then + IMAGE_TAG=${IMAGE_TAG:=2025.08.12-1} + CONTAINER_SOURCE=${CONTAINER_SOURCE:="quay.io/pypa/manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}:${IMAGE_TAG}"} +else + # if x64 arch then default manylinux version is _2_28 IMAGE_TAG=${IMAGE_TAG:=20260203-3dfb3ff} -elif [[ ${MANYLINUX_VERSION} == 2014 ]]; then - IMAGE_TAG=${IMAGE_TAG:=20240304-9e57d2b} + CONTAINER_SOURCE=${CONTAINER_SOURCE:="docker.io/dockcross/manylinux${MANYLINUX_VERSION}-${TARGET_ARCH}:${IMAGE_TAG}"} +fi +ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium} +ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG:=main} + +# Required environment variables +required_vars=( + ITK_GIT_TAG + MANYLINUX_VERSION + IMAGE_TAG + TARGET_ARCH + ITKPYTHONPACKAGE_ORG + ITKPYTHONPACKAGE_TAG +) +# Sanity Validation loop +_missing_required=0 +for v in "${required_vars[@]}"; do + if [ -z "${!v:-}" ]; then + _missing_required=1 + echo "ERROR: Required environment variable '$v' is not set or empty." + fi +done +if [ "${_missing_required}" -ne 0 ]; then + exit 1 +fi +unset _missing_required + +mkdir -p "${_ipp_dir}/build" +cd "$(dirname "${_ipp_dir}")" || exit + +# Build wheels in dockcross environment +CONTAINER_WORK_DIR=/work +CONTAINER_PACKAGE_BUILD_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage-build +CONTAINER_PACKAGE_SCRIPTS_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage +CONTAINER_ITK_SOURCE_DIR=${CONTAINER_PACKAGE_BUILD_DIR}/ITK + +HOST_PACKAGE_SCRIPTS_DIR=${_ipp_dir} +HOST_PACKAGE_BUILD_DIR=$(dirname "${_ipp_dir}")/ITKPythonPackage-manylinux${MANYLINUX_VERSION}-build +mkdir -p "${HOST_PACKAGE_BUILD_DIR}" + +DOCKER_ARGS=" -v ${HOST_PACKAGE_BUILD_DIR}:${CONTAINER_PACKAGE_BUILD_DIR} " +DOCKER_ARGS+=" -v ${HOST_PACKAGE_SCRIPTS_DIR}:${CONTAINER_PACKAGE_SCRIPTS_DIR} " +if [ "${ITK_SOURCE_DIR}" != "" ]; then + DOCKER_ARGS+=" -v${ITK_SOURCE_DIR}:${CONTAINER_ITK_SOURCE_DIR} " +fi +DOCKER_ARGS+=" -e PYTHONUNBUFFERED=1 " # Turn off buffering of outputs in python + +# To build tarballs in manylinux, use 'export BUILD_WHEELS_EXTRA_FLAGS=" --build-itk-tarball-cache "' +BUILD_WHEELS_EXTRA_FLAGS=${BUILD_WHEELS_EXTRA_FLAGS:=""} # No tarball by default + +# If args are given, use them. Otherwise use default python environments +PY_ENVS=("${@:-py311}") + +if [[ "${TARGET_ARCH}" == "aarch64" ]]; then + # aarch64: run the quay.io/pypa native image directly. + # On ARM64 hosts (e.g. Apple Silicon) this runs natively. + # On x64 hosts, first register QEMU binfmt emulation. + if [[ ! ${NO_SUDO} ]]; then + docker_prefix="sudo" + fi + # Only install QEMU binfmt emulation on non-ARM64 hosts; ARM64 hosts run natively + if [[ "$(uname -m)" != "arm64" && "$(uname -m)" != "aarch64" ]]; then + echo "Installing aarch64 architecture emulation tools to perform build for ARM platform" + ${docker_prefix} "$OCI_EXE" run --privileged --rm tonistiigi/binfmt --install all + fi + + # When building ITK wheels, module-related vars are empty + cmd="${docker_prefix} \"$OCI_EXE\" run --rm \ + ${DOCKER_ARGS} \ + -e PY_ENVS=\"${PY_ENVS[*]}\" \ + -e ITK_GIT_TAG=\"${ITK_GIT_TAG}\" \ + -e ITK_PACKAGE_VERSION=\"${ITK_PACKAGE_VERSION:-}\" \ + -e MANYLINUX_VERSION=\"${MANYLINUX_VERSION}\" \ + -e IMAGE_TAG=\"${IMAGE_TAG}\" \ + -e TARGET_ARCH=\"${TARGET_ARCH}\" \ + -e ITKPYTHONPACKAGE_ORG=\"${ITKPYTHONPACKAGE_ORG}\" \ + -e ITKPYTHONPACKAGE_TAG=\"${ITKPYTHONPACKAGE_TAG}\" \ + -e BUILD_WHEELS_EXTRA_FLAGS=\"${BUILD_WHEELS_EXTRA_FLAGS}\" \ + ${CONTAINER_SOURCE} \ + /bin/bash -x ${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/docker_build_environment_driver.sh" else - echo "Unknown manylinux version ${MANYLINUX_VERSION}" - exit 1; + # x64: generate the dockcross runner script from the image, then invoke it. + _local_dockercross_script=${_ipp_dir}/build/runner_dockcross-${MANYLINUX_VERSION}-${TARGET_ARCH}_${IMAGE_TAG}.sh + "$OCI_EXE" run --rm "${CONTAINER_SOURCE}" >"${_local_dockercross_script}" + chmod u+x "${_local_dockercross_script}" + + # When building ITK wheels, --module-source-dir, --module-dependancies-root-dir, and --itk-module-deps to be empty + cmd="bash -x ${_local_dockercross_script} \ + -a \"$DOCKER_ARGS\" \ + /usr/bin/env \ + PY_ENVS=\"${PY_ENVS[*]}\" \ + ITK_GIT_TAG=\"${ITK_GIT_TAG}\" \ + ITK_PACKAGE_VERSION=\"${ITK_PACKAGE_VERSION:-}\" \ + MANYLINUX_VERSION=\"${MANYLINUX_VERSION}\" \ + IMAGE_TAG=\"${IMAGE_TAG}\" \ + TARGET_ARCH=\"${TARGET_ARCH}\" \ + ITKPYTHONPACKAGE_ORG=\"${ITKPYTHONPACKAGE_ORG}\" \ + ITKPYTHONPACKAGE_TAG=\"${ITKPYTHONPACKAGE_TAG}\" \ + BUILD_WHEELS_EXTRA_FLAGS=\"${BUILD_WHEELS_EXTRA_FLAGS}\" \ + /bin/bash -x ${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/docker_build_environment_driver.sh" fi -# Generate dockcross scripts -$oci_exe run --rm docker.io/dockcross/manylinux${MANYLINUX_VERSION}-x64:${IMAGE_TAG} > /tmp/dockcross-manylinux-x64 -chmod u+x /tmp/dockcross-manylinux-x64 - -# Build wheels -pushd $script_dir/.. -mkdir -p dist -DOCKER_ARGS="-v $(pwd)/dist:/work/dist/" -DOCKER_ARGS+=" -e MANYLINUX_VERSION" -/tmp/dockcross-manylinux-x64 \ - -a "$DOCKER_ARGS" \ - ./scripts/internal/manylinux-build-wheels.sh "$@" -popd +echo "RUNNING: $cmd" +eval "$cmd" diff --git a/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh b/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh index 37166edd..f274bfdc 100755 --- a/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh +++ b/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh @@ -10,15 +10,12 @@ # Versions can be restricted by passing them in as arguments to the script # For example, # -# scripts/dockcross-manylinux-build-module-wheels.sh cp310 +# scripts/dockcross-manylinux-build-module-wheels.sh cp311 # # =========================================== # ENVIRONMENT VARIABLES # # These variables are set with the `export` bash command before calling the script. -# For example, -# -# scripts/dockcross-manylinux-build-module-wheels.sh cp310 # # `ITKPYTHONPACKAGE_ORG`: Github organization for fetching ITKPythonPackage build scripts. # @@ -28,48 +25,110 @@ # ######################################################################## +download_script_dir=$( + cd "$(dirname "$0")" || exit 1 + pwd +) +# if not specified, use the current directory for MODULE_SRC_DIRECTORY +MODULE_SRC_DIRECTORY=${MODULE_SRC_DIRECTORY:=${download_script_dir}} +# if not specified then use the a dummy MODULE_DEPENDENCIES directory in the build dashboard +MODULE_DEPS_DIR=${MODULE_DEPS_DIR:=${DASHBOARD_BUILD_DIRECTORY}/MODULE_DEPENDENCIES} # ----------------------------------------------------------------------- # Script argument parsing # -usage() -{ +usage() { echo "Usage: dockcross-manylinux-download-cache-and-build-module-wheels [ -h | --help ] show usage [ -c | --cmake_options ] space-delimited string containing CMake options to forward to the module (e.g. \"-DBUILD_TESTING=OFF\") [ -x | --exclude_libs ] semicolon-delimited library names to exclude when repairing wheel (e.g. \"libcuda.so\") - [ python_version ] build wheel for a specific python version. (e.g. cp310)" + [ python_version ] build wheel for a specific python version. (e.g. cp311)" exit 2 } -FORWARD_ARGS=("$@") # Store arguments to forward them later PARSED_ARGS=$(getopt -a -n dockcross-manylinux-download-cache-and-build-module-wheels \ -o hc:x: --long help,cmake_options:,exclude_libs: -- "$@") eval set -- "$PARSED_ARGS" -while : -do +while :; do case "$1" in - -h | --help) usage; break ;; - -c | --cmake_options) CMAKE_OPTIONS="$2" ; shift 2 ;; - -x | --exclude_libs) EXCLUDE_LIBS="$2" ; shift 2 ;; - --) shift; break ;; - *) echo "Unexpected option: $1."; - usage; break ;; + -h | --help) + usage + ;; + -c | --cmake_options) + export CMAKE_OPTIONS="$2" + shift 2 + ;; + -x | --exclude_libs) + export EXCLUDE_LIBS="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + echo "Unexpected option: $1." + usage + ;; esac done +# The ITKPythonBuilds cache layout must match these scripts, so there is no default release. +if [ -z "${ITK_PACKAGE_VERSION}" ]; then + echo "ERROR: set ITK_PACKAGE_VERSION to the ITKPythonBuilds release tag to download." + exit 1 +fi +ITK_GIT_TAG=${ITK_GIT_TAG:=${ITK_PACKAGE_VERSION}} + +# ----------------------------------------------------------------------- +# Set default values +MANYLINUX_VERSION=${MANYLINUX_VERSION:-_2_28} +TARGET_ARCH=${TARGET_ARCH:-x64} + +ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:-InsightSoftwareConsortium} +ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG:-main} + # ----------------------------------------------------------------------- # Download and extract cache -echo "Fetching https://raw.githubusercontent.com/${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium}/ITKPythonPackage/${ITKPYTHONPACKAGE_TAG:=v5.4.0}/scripts/dockcross-manylinux-download-cache.sh" -curl -L https://raw.githubusercontent.com/${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium}/ITKPythonPackage/${ITKPYTHONPACKAGE_TAG:=v5.4.0}/scripts/dockcross-manylinux-download-cache.sh -O +echo "Fetching https://raw.githubusercontent.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage/${ITKPYTHONPACKAGE_TAG}/scripts/dockcross-manylinux-download-cache.sh" +curl -L "https://raw.githubusercontent.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage/${ITKPYTHONPACKAGE_TAG}/scripts/dockcross-manylinux-download-cache.sh" -O chmod u+x dockcross-manylinux-download-cache.sh -./dockcross-manylinux-download-cache.sh $1 +_download_cmd="ITK_GIT_TAG=${ITK_GIT_TAG} \ + ITK_PACKAGE_VERSION=${ITK_PACKAGE_VERSION} \ + ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG} \ + ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG} \ + MANYLINUX_VERSION=${MANYLINUX_VERSION} \ + TARGET_ARCH=${TARGET_ARCH} \ + bash -x \ + ${download_script_dir}/dockcross-manylinux-download-cache.sh $1" +echo "Running: ${_download_cmd}" +eval "${_download_cmd}" + +#NOTE: in this scenario, untarred_ipp_dir is extracted from tarball +# during ${download_script_dir}/dockcross-manylinux-download-cache.sh +untarred_ipp_dir=${download_script_dir}/ITKPythonPackage + +ITK_SOURCE_DIR=${download_script_dir}/ITKPythonPackage-build/ITK # ----------------------------------------------------------------------- # Build module wheels echo "Building module wheels" -set -- "${FORWARD_ARGS[@]}"; # Restore initial argument list -./ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh "$@" + +_bld_cmd="NO_SUDO=${NO_SUDO} \ + LD_LIBRARY_PATH=${LD_LIBRARY_PATH} \ + IMAGE_TAG=${IMAGE_TAG} \ + TARGET_ARCH=${TARGET_ARCH} \ + ITK_SOURCE_DIR=${ITK_SOURCE_DIR} \ + ITK_GIT_TAG=${ITK_GIT_TAG} \ + ITK_PACKAGE_VERSION=${ITK_PACKAGE_VERSION} \ + ITK_MODULE_PREQ=${ITK_MODULE_PREQ} \ + ITK_MODULE_NO_CLEANUP=${ITK_MODULE_NO_CLEANUP} \ + MODULE_SRC_DIRECTORY=${MODULE_SRC_DIRECTORY} \ + MODULE_DEPS_DIR=${MODULE_DEPS_DIR} \ + MANYLINUX_VERSION=${MANYLINUX_VERSION} \ + ${untarred_ipp_dir}/scripts/dockcross-manylinux-build-module-wheels.sh $*" +echo "Running: ${_bld_cmd}" +eval "${_bld_cmd}" diff --git a/scripts/dockcross-manylinux-download-cache.sh b/scripts/dockcross-manylinux-download-cache.sh index 10d17e2d..c4283b89 100755 --- a/scripts/dockcross-manylinux-download-cache.sh +++ b/scripts/dockcross-manylinux-download-cache.sh @@ -11,57 +11,67 @@ # steps not present in `dockcross-manylinux-download-cache-and-build-module-wheels.sh`. # # =========================================== -# ENVIRONMENT VARIABLES -# -# `ITK_PACKAGE_VERSION`: Tag for ITKPythonBuilds build cache to use -# Examples: "v5.4.0", "v5.2.1.post1" -# See available tags at https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/tags -# -# `MANYLINUX_VERSION`: manylinux specialization used to build ITK for cache -# Examples: "_2_28", "2014", "_2_28_aarch64" -# See https://github.com/dockcross/dockcross -# -# `ITKPYTHONPACKAGE_TAG`: Tag for ITKPythonPackage build scripts to use. -# If ITKPYTHONPACKAGE_TAG is empty then the default scripts distributed -# with the ITKPythonBuilds archive will be used. -# -# `ITKPYTHONPACKAGE_ORG`: Github organization or user to use for ITKPythonPackage -# build script source. Default is InsightSoftwareConsortium. -# Ignored if ITKPYTHONPACKAGE_TAG is empty. -# +# ENVIRONMENT VARIABLES: ITK_GIT_TAG, MANYLINUX_VERSION, ITKPYTHONPACKAGE_TAG, ITKPYTHONPACKAGE_ORG ######################################################################## # ----------------------------------------------------------------------- # Script argument parsing # -usage() -{ +usage() { echo "Usage: dockcross-manylinux-download-cache.sh [ -h | --help ] show usage - [ python_version ] build wheel for a specific python version. (e.g. cp310)" + [ python_version ] build wheel for a specific python version. (e.g. cp311)" exit 2 } -FORWARD_ARGS=("$@") # Store arguments to forward them later +# Required environment variables +required_vars=( + ITK_GIT_TAG + ITK_PACKAGE_VERSION + ITKPYTHONPACKAGE_ORG + ITKPYTHONPACKAGE_TAG + MANYLINUX_VERSION + TARGET_ARCH +) + +# Sanity Validation loop +_missing_required=0 +for v in "${required_vars[@]}"; do + if [ -z "${!v:-}" ]; then + _missing_required=1 + echo "ERROR: Required environment variable '$v' is not set or empty." + fi +done +if [ "${_missing_required}" -ne 0 ]; then + exit 1 +fi +unset _missing_required + PARSED_ARGS=$(getopt -a -n dockcross-manylinux-download-cache-and-build-module-wheels \ -o hc:x: --long help,cmake_options:,exclude_libs: -- "$@") eval set -- "$PARSED_ARGS" -while : -do +while :; do case "$1" in - -h | --help) usage; break ;; - --) shift; break ;; - *) echo "Unexpected option: $1."; - usage; break ;; + -h | --help) + usage + ;; + --) + shift + break + ;; + *) + echo "Unexpected option: $1." + usage + ;; esac done # ----------------------------------------------------------------------- # Verify that unzstd binary is available to decompress ITK build archives. -unzstd_exe=`(which unzstd)` +unzstd_exe=$(which unzstd) if [[ -z ${unzstd_exe} ]]; then echo "ERROR: can not find required binary 'unzstd' " @@ -73,42 +83,27 @@ ${unzstd_exe} --version # ----------------------------------------------------------------------- # Fetch build archive - -MANYLINUX_VERSION=${MANYLINUX_VERSION:=_2_28} -TARGET_ARCH=${TARGET_ARCH:=x64} - -case ${TARGET_ARCH} in - x64) - TARBALL_SPECIALIZATION="-manylinux${MANYLINUX_VERSION}" - ;; - *) - TARBALL_SPECIALIZATION="-manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}" - ;; -esac -TARBALL_NAME="ITKPythonBuilds-linux${TARBALL_SPECIALIZATION}.tar" +TARBALL_NAME="ITKPythonBuilds-manylinux${MANYLINUX_VERSION}-${TARGET_ARCH}.tar" if [[ ! -f ${TARBALL_NAME}.zst ]]; then - echo "Fetching https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION:=v5.4.0}/${TARBALL_NAME}.zst" - curl -L https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION:=v5.4.0}/${TARBALL_NAME}.zst -O + echo "Local ITK cache tarball file not found..." + echo "Fetching https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/${TARBALL_NAME}.zst" + if ! curl -L "https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/${TARBALL_NAME}.zst" -O; then + echo "FAILED Download:" + echo "curl -L https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/${TARBALL_NAME}.zst -O" + exit 1 + fi fi if [[ ! -f ./${TARBALL_NAME}.zst ]]; then echo "ERROR: can not find required binary './${TARBALL_NAME}.zst'" exit 255 fi -${unzstd_exe} --long=31 ./${TARBALL_NAME}.zst -o ${TARBALL_NAME} -if [ "$#" -lt 1 ]; then - echo "Extracting all files"; - tar xf ${TARBALL_NAME} -else - echo "Extracting files relevant for: $1"; - tar xf ${TARBALL_NAME} ITKPythonPackage/scripts/ - tar xf ${TARBALL_NAME} ITKPythonPackage/ITK-source/ - tar xf ${TARBALL_NAME} ITKPythonPackage/oneTBB-prefix/ - tar xf ${TARBALL_NAME} --wildcards ITKPythonPackage/ITK-$1* -fi -rm ${TARBALL_NAME} +"${unzstd_exe}" --long=31 "./${TARBALL_NAME}.zst" -o "${TARBALL_NAME}" -ln -s ITKPythonPackage/oneTBB-prefix ./ +current_dir=$(pwd) +echo "Extracting all cache files from ${TARBALL_NAME} in ${current_dir}" +tar xf "${TARBALL_NAME}" --warning=no-unknown-keyword +rm "${TARBALL_NAME}" # ----------------------------------------------------------------------- # Optional: Update build scripts @@ -118,22 +113,22 @@ ln -s ITKPythonPackage/oneTBB-prefix ./ # since the archives were generated. if [[ -n ${ITKPYTHONPACKAGE_TAG} ]]; then - echo "Updating build scripts to ${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium}/ITKPythonPackage@${ITKPYTHONPACKAGE_TAG}" - git clone "https://github.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage.git" "IPP-tmp" + # shellcheck disable=SC2153 + echo "Updating build scripts to ${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage@${ITKPYTHONPACKAGE_TAG}" + git clone "https://github.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage.git" "${current_dir}/IPP-tmp" - pushd IPP-tmp/ + pushd "${current_dir}/IPP-tmp/" || exit git checkout "${ITKPYTHONPACKAGE_TAG}" git status - popd + popd || exit - rm -rf ITKPythonPackage/scripts/ - cp -r IPP-tmp/scripts ITKPythonPackage/ - cp IPP-tmp/requirements-dev.txt ITKPythonPackage/ - rm -rf IPP-tmp/ + rm -rf "${current_dir}/ITKPythonPackage/scripts/" + rsync -av "${current_dir}"/IPP-tmp/ "${current_dir}/ITKPythonPackage/" + rm -rf "${current_dir}/IPP-tmp/" fi -if [[ ! -f ./ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh ]]; then - echo "ERROR: can not find required binary './ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh'" +if [[ ! -f ${current_dir}/ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh ]]; then + echo "ERROR: can not find required binary '${current_dir}/ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh'" exit 255 fi diff --git a/scripts/docker_build_environment_driver.sh b/scripts/docker_build_environment_driver.sh new file mode 100755 index 00000000..47dc611a --- /dev/null +++ b/scripts/docker_build_environment_driver.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +# Required environment variables +required_vars=( + ITK_GIT_TAG + MANYLINUX_VERSION + IMAGE_TAG + TARGET_ARCH + ITKPYTHONPACKAGE_ORG + ITKPYTHONPACKAGE_TAG + PY_ENVS +) +# Sanity Validation loop +_missing_required=0 +for v in "${required_vars[@]}"; do + if [ -z "${!v:-}" ]; then + _missing_required=1 + echo "ERROR: Required environment variable '$v' is not set or empty." + fi +done +if [ "${_missing_required}" -ne 0 ]; then + exit 1 +fi +unset _missing_required + +# ----------------------------------------------------------------------- +# Set up paths +# These paths are inside the container + +CONTAINER_WORK_DIR=/work +cd "${CONTAINER_WORK_DIR}" || exit +CONTAINER_PACKAGE_BUILD_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage-build +CONTAINER_PACKAGE_SCRIPTS_DIR=${CONTAINER_WORK_DIR}/ITKPythonPackage +CONTAINER_MODULE_SRC_DIRECTORY=${CONTAINER_WORK_DIR}/$(basename "${MODULE_SRC_DIRECTORY}") +CONTAINER_ITK_SOURCE_DIR=${CONTAINER_PACKAGE_BUILD_DIR}/ITK +BUILD_WHEELS_EXTRA_FLAGS=${BUILD_WHEELS_EXTRA_FLAGS:=""} +read -ra BUILD_WHEELS_EXTRA_FLAGS_ARRAY <<<"${BUILD_WHEELS_EXTRA_FLAGS}" + +echo "BUILD FOR ${PY_ENVS}" +read -ra PY_ENVS_ARRAY <<<"${PY_ENVS}" + +for py_indicator in "${PY_ENVS_ARRAY[@]}"; do + py_squashed_numeric="${py_indicator//py/}" + py_squashed_numeric="${py_squashed_numeric//cp/}" + py_squashed_numeric="${py_squashed_numeric//./}" + manylinux_vername="${MANYLINUX_VERSION//_/}" + PIXI_ENV="manylinux${manylinux_vername}-py${py_squashed_numeric}" + + # Use pixi to ensure all required tools are installed and + # visible in the PATH + export PIXI_HOME=${CONTAINER_PACKAGE_SCRIPTS_DIR}/.pixi + export PATH="${PIXI_HOME}/bin:${PATH}" + python3.12 "${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/install_pixi.py" --platform-env "${PIXI_ENV}" + + cd "${CONTAINER_PACKAGE_SCRIPTS_DIR}" || exit + if [ -n "${MODULE_SRC_DIRECTORY}" ]; then + pixi run -e "${PIXI_ENV}" python3 \ + "${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/build_wheels.py" \ + --platform-env "${PIXI_ENV}" \ + "${BUILD_WHEELS_EXTRA_FLAGS_ARRAY[@]}" \ + --module-source-dir "${CONTAINER_MODULE_SRC_DIRECTORY}" \ + --module-dependencies-root-dir "${CONTAINER_MODULES_ROOT_DIRECTORY}" \ + --itk-module-deps "${ITK_MODULE_PREQ}" \ + --no-build-itk-tarball-cache \ + --build-dir-root "${CONTAINER_PACKAGE_BUILD_DIR}" \ + --itk-source-dir "${CONTAINER_ITK_SOURCE_DIR}" \ + --itk-git-tag "${ITK_GIT_TAG}" \ + --itk-package-version "${ITK_PACKAGE_VERSION:-}" \ + --manylinux-version "${MANYLINUX_VERSION}" \ + --no-use-sudo \ + --no-use-ccache \ + --skip-itk-build \ + --skip-itk-wheel-build + else + pixi run -e "${PIXI_ENV}" python3 \ + "${CONTAINER_PACKAGE_SCRIPTS_DIR}/scripts/build_wheels.py" \ + --platform-env "${PIXI_ENV}" \ + "${BUILD_WHEELS_EXTRA_FLAGS_ARRAY[@]}" \ + --build-itk-tarball-cache \ + --build-dir-root "${CONTAINER_PACKAGE_BUILD_DIR}" \ + --itk-source-dir "${CONTAINER_ITK_SOURCE_DIR}" \ + --itk-git-tag "${ITK_GIT_TAG}" \ + --itk-package-version "${ITK_PACKAGE_VERSION:-}" \ + --manylinux-version "${MANYLINUX_VERSION}" \ + --no-use-sudo \ + --no-use-ccache + fi + build_status=$? + if [ "${build_status}" -ne 0 ]; then + echo "ERROR: Build failed for ${py_indicator} with exit code ${build_status}" + exit "${build_status}" + fi + + echo "Successfully built wheel for ${py_indicator}" +done + +echo "" +echo "========================================" +echo "All builds completed successfully!" +echo "========================================" diff --git a/scripts/macpython-download-cache-and-build-module-wheels.sh b/scripts/macpython-download-cache-and-build-module-wheels.sh index 297e827d..0f2e3f46 100755 --- a/scripts/macpython-download-cache-and-build-module-wheels.sh +++ b/scripts/macpython-download-cache-and-build-module-wheels.sh @@ -10,93 +10,165 @@ # Versions can be restricted by passing them in as arguments to the script. # For example, # -# scripts/macpython-download-cache-and-build-module-wheels.sh 3.9 3.11 +# scripts/macpython-download-cache-and-build-module-wheels.sh 3.11 # # Shared libraries can be included in the wheel by setting DYLD_LIBRARY_PATH before # running this script. -# + # =========================================== -# ENVIRONMENT VARIABLES +# ENVIRONMENT VARIABLES: ITK_GIT_TAG ITKPYTHONPACKAGE_ORG ITK_USE_LOCAL_PYTHON ITK_PACKAGE_VERSION # # These variables are set with the `export` bash command before calling the script. # For example, -# -# export DYLD_LIBRARY_PATH="/path/to/libs" -# -# `ITK_PACKAGE_VERSION`: ITKPythonBuilds archive tag to use for ITK build artifacts. -# See https://github.com/InsightSoftwareConsortium/ITKPythonBuilds for available tags. -# For instance, `export ITK_PACKAGE_VERSION=v5.4.0`. -# -# `ITKPYTHONPACKAGE_ORG`: Github organization for fetching ITKPythonPackage build scripts. -# -# `ITKPYTHONPACKAGE_TAG`: ITKPythonPackage tag for fetching build scripts. -# -# `ITK_USE_LOCAL_PYTHON`: Determine how to get Python framework for build. -# - If empty, Python frameworks will be fetched from python.org -# - If not empty, frameworks already on machine will be used without fetching. +# scripts/macpython-build-module-wheels.sh 3.11 # ######################################################################## +DEFAULT_MODULE_DIRECTORY=$( + cd "$(dirname "$0")" || exit 1 + pwd +) +# if not specified, use the current directory for MODULE_SRC_DIRECTORY +MODULE_SRC_DIRECTORY=${MODULE_SRC_DIRECTORY:=${DEFAULT_MODULE_DIRECTORY}} -# Install dependencies -brew update -brew install --quiet zstd aria2 gnu-tar doxygen ninja -brew upgrade --quiet cmake rustup +usage() { + echo "Usage: + macpython-download-cache-and-build-module-wheels + [ -h | --help ] show usage + [ -c | --cmake_options ] space-delimited string containing CMake options to forward to the module (e.g. \"-DBUILD_TESTING=OFF\") + [ python_version ] build wheel for a specific python version. (e.g. 3.11)" + exit 2 +} -if [[ $(arch) == "arm64" ]]; then - tarball_arch="-arm64" -else - tarball_arch="" +CMAKE_OPTIONS="" +while [ $# -gt 0 ]; do + case "$1" in + -h | --help) usage ;; + -c | --cmake_options) + CMAKE_OPTIONS="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ -z "${ITK_PACKAGE_VERSION}" ]; then + echo "MUST SET ITK_PACKAGE_VERSION BEFORE RUNNING THIS SCRIPT" + exit 1 fi -# Fetch ITKPythonBuilds archive containing ITK build artifacts -rm -fr ITKPythonPackage -echo "Fetching https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION:=v5.4.0}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst" -if [[ ! -f ITKPythonBuilds-macosx${tarball_arch}.tar.zst ]]; then - aria2c -c --file-allocation=none -o ITKPythonBuilds-macosx${tarball_arch}.tar.zst -s 10 -x 10 https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION:=v5.4.0}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst + +# For backwards compatibility when the ITK_GIT_TAG was required to match the ITK_PACKAGE_VERSION +ITK_GIT_TAG=${ITK_GIT_TAG:=${ITK_PACKAGE_VERSION}} + +DASHBOARD_BUILD_DIRECTORY=${DASHBOARD_BUILD_DIRECTORY:=/Users/svc-dashboard/D/P} +ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium} +# Run build scripts +if [ -z "${NO_SUDO}" ] || [ "${NO_SUDO}" -ne 1 ]; then + sudo_exec=sudo +fi +if [ ! -d "${DASHBOARD_BUILD_DIRECTORY}" ]; then + ${sudo_exec} mkdir -p "${DASHBOARD_BUILD_DIRECTORY}" && ${sudo_exec} chown "$UID:$GID" "${DASHBOARD_BUILD_DIRECTORY}" fi -unzstd --long=31 ITKPythonBuilds-macosx${tarball_arch}.tar.zst -o ITKPythonBuilds-macosx${tarball_arch}.tar -PATH="$(dirname $(brew list gnu-tar | grep gnubin)):$PATH" -gtar xf ITKPythonBuilds-macosx${tarball_arch}.tar --warning=no-unknown-keyword --checkpoint=10000 --checkpoint-action=dot \ - ITKPythonPackage/ITK-source \ - ITKPythonPackageRequiredExtractionDir.txt \ - ITKPythonPackage/scripts +cd "${DASHBOARD_BUILD_DIRECTORY}" || exit -# Extract subdirectories specific to the compiled python versions -args=( "$@" ) -source ITKPythonPackage/scripts/macpython-build-common.sh -for version in "$PYTHON_VERSIONS"; do - gtar xf ITKPythonBuilds-macosx${tarball_arch}.tar --warning=no-unknown-keyword --checkpoint=10000 --checkpoint-action=dot \ - --wildcards "ITKPythonPackage/ITK-${version}-macosx*" \ - "ITKPythonPackage/venvs/${version}" -done +# NOTE: download phase will install pixi in the DASHBOARD_BUILD_DIRECTORY (which is separate from the pixi +# environment used by ITKPythonPackage). +export PIXI_HOME=${DASHBOARD_BUILD_DIRECTORY}/.pixi +if [ ! -f "${PIXI_HOME}/.pixi/bin/pixi" ]; then + # Install pixi + curl -fsSL https://pixi.sh/install.sh | sh + # These are the tools needed for cross platform downloads of the ITK build caches stored in https://github.com/InsightSoftwareConsortium/ITKPythonBuilds + pixi global install zstd + pixi global install aria2 + pixi global install gnu-tar + pixi global install git + pixi global install rsync +fi +export PATH="${PIXI_HOME}/bin:$PATH" -rm ITKPythonBuilds-macosx${tarball_arch}.tar +tarball_arch="-$(arch)" +TARBALL_NAME="ITKPythonBuilds-macosx${tarball_arch}.tar" -# Optional: Update build scripts -if [[ -n ${ITKPYTHONPACKAGE_TAG} ]]; then - echo "Updating build scripts to ${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium}/ITKPythonPackage@${ITKPYTHONPACKAGE_TAG}" - git clone "https://github.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage.git" "IPP-tmp" - pushd IPP-tmp/ - git checkout "${ITKPYTHONPACKAGE_TAG}" - git status - popd +if [[ ! -f ${TARBALL_NAME}.zst ]]; then + echo "Local ITK cache tarball file not found..." + # Fetch ITKPythonBuilds archive containing ITK build artifacts + echo "Fetching https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst" + if ! curl -L "https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst" -O; then + echo "FAILED Download:" + echo "curl -L https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/${TARBALL_NAME}.zst -O" + exit 1 + fi +fi - rm -rf ITKPythonPackage/scripts/ - cp -r IPP-tmp/scripts ITKPythonPackage/ - rm -rf IPP-tmp/ +if [[ ! -f ./${TARBALL_NAME}.zst ]]; then + echo "ERROR: can not find required binary './${TARBALL_NAME}.zst'" + exit 255 fi -# Run build scripts -sudo mkdir -p /Users/svc-dashboard/D/P && sudo chown $UID:$GID /Users/svc-dashboard/D/P -if [[ ! -d /Users/svc-dashboard/D/P/ITKPythonPackage ]]; then - mv ITKPythonPackage /Users/svc-dashboard/D/P/ +local_compress_tarball_name=${DASHBOARD_BUILD_DIRECTORY}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst +if [[ ! -f ${local_compress_tarball_name} ]]; then + aria2c -c --file-allocation=none -d "$(dirname "${local_compress_tarball_name}")" -o "$(basename "${local_compress_tarball_name}")" -s 10 -x 10 "https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/${ITK_PACKAGE_VERSION}/ITKPythonBuilds-macosx${tarball_arch}.tar.zst" fi +local_tarball_name=${DASHBOARD_BUILD_DIRECTORY}/ITKPythonBuilds-macosx${tarball_arch}.tar +unzstd --long=31 "${local_compress_tarball_name}" -o "${local_tarball_name}" +# Find GNU tar (gtar from pixi or brew) for reliable extraction +if command -v gtar >/dev/null 2>&1; then + TAR_CMD=gtar + TAR_FLAGS=(--warning=no-unknown-keyword --checkpoint=10000 --checkpoint-action=dot) +elif tar --version 2>/dev/null | grep -q "GNU tar"; then + TAR_CMD=tar + TAR_FLAGS=(--warning=no-unknown-keyword --checkpoint=10000 --checkpoint-action=dot) +else + TAR_CMD=tar + TAR_FLAGS=() +fi +"${TAR_CMD}" xf "${local_tarball_name}" "${TAR_FLAGS[@]}" +rm "${local_tarball_name}" -# Optionally install baseline Python versions -if [[ ! ${ITK_USE_LOCAL_PYTHON} ]]; then - echo "Fetching Python frameworks" - sudo rm -rf /Library/Frameworks/Python.framework/Versions/* - /Users/svc-dashboard/D/P/ITKPythonPackage/scripts/macpython-install-python.sh +# Optional: Update build scripts +if [[ -n ${ITKPYTHONPACKAGE_TAG} ]]; then + echo "Updating build scripts to ${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage@${ITKPYTHONPACKAGE_TAG}" + local_clone_ipp=${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage_${ITKPYTHONPACKAGE_TAG} + if [ ! -d "${local_clone_ipp}/.git" ]; then + git clone "https://github.com/${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage.git" "${local_clone_ipp}" + fi + pushd "${local_clone_ipp}" || exit + git checkout "${ITKPYTHONPACKAGE_TAG}" + git reset "origin/${ITKPYTHONPACKAGE_TAG}" --hard + git status + popd || exit + rsync -av "${local_clone_ipp}/" "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage/" fi echo "Building module wheels" -/Users/svc-dashboard/D/P/ITKPythonPackage/scripts/macpython-build-module-wheels.sh "${args[@]}" +cd "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage" || exit +echo "$@" +for py_indicator in "$@"; do + # The following line is to convert "py3.11|py311|cp311|3.11" -> py311 normalized form + py_squashed_numeric=$(echo "${py_indicator}" | sed 's/py//g' | sed 's/cp//g' | sed 's/\.//g') + pyenv=py${py_squashed_numeric} + pixi run -e "macosx-${pyenv}" -- python \ + "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage/scripts/build_wheels.py" \ + --platform-env "macosx-${pyenv}" \ + --lib-paths '' '' \ + --module-source-dir "${MODULE_SRC_DIRECTORY}" \ + --module-dependencies-root-dir "${DASHBOARD_BUILD_DIRECTORY}/MODULE_DEPENDENCIES" \ + --itk-module-deps "${ITK_MODULE_PREQ}" \ + --no-build-itk-tarball-cache \ + --build-dir-root "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage-build" \ + --manylinux-version '' \ + --itk-git-tag "${ITK_GIT_TAG}" \ + --itk-source-dir "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage-build/ITK" \ + --itk-package-version "${ITK_PACKAGE_VERSION}" \ + --no-use-sudo \ + --no-use-ccache \ + --skip-itk-build \ + --skip-itk-wheel-build \ + ${CMAKE_OPTIONS:+-- ${CMAKE_OPTIONS}} + #Let this be automatically selected --macosx-deployment-target 10.7 \ +done diff --git a/scripts/make_tarballs.sh b/scripts/make_tarballs.sh new file mode 100755 index 00000000..6d3da3a3 --- /dev/null +++ b/scripts/make_tarballs.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# NOTES: Building tarballs requires specific pathing for supporting github CI +# workflows + +script_dir=$( + cd "$(dirname "$0")" || exit 1 + pwd +) +_ipp_dir=$(dirname "${script_dir}") + +# If args are given, use them. Otherwise use default python environments +pyenvs=("${@:-py311}") + +# Otherwise process mac and linux based on uname + +# Need to explicitly request to --build-itk-tarball-cache +BUILD_WHEELS_EXTRA_FLAGS=("--build-itk-tarball-cache") +if [ -z "${ITK_GIT_TAG}" ]; then + DEFAULT_ITK_GIT_TAG=main + echo "=============================================================================" + echo "=============================================================================" + for _ in x x x x x x x x x x x x x x x x x x x x x x x x x x x x x; do + echo "===== WARNING: ITK_GIT_TAG not set, so defaulting to ${DEFAULT_ITK_GIT_TAG}" + done + echo "=============================================================================" + echo "=============================================================================" +fi +ITK_GIT_TAG=${ITK_GIT_TAG:=${DEFAULT_ITK_GIT_TAG}} + +## -- +# -- +# -- +# -- +# Short circuit builds to use dockcross if MANYLINUX_VERSION is requested +if [ -n "${MANYLINUX_VERSION}" ]; then + BUILD_WHEELS_EXTRA_FLAGS="${BUILD_WHEELS_EXTRA_FLAGS[*]}" \ + ITK_GIT_TAG=${ITK_GIT_TAG} \ + MANYLINUX_VERSION=${MANYLINUX_VERSION} \ + bash "${_ipp_dir}/scripts/dockcross-manylinux-build-wheels.sh" \ + "${pyenvs[@]}" + exit $? +fi + +## -- +# -- +# -- +# -- +case "$(uname -s)" in +Darwin) + PLATFORM_PREFIX="macosx" + DASHBOARD_BUILD_DIRECTORY=${DASHBOARD_BUILD_DIRECTORY:=/Users/svc-dashboard/D/P} + ;; +Linux) + PLATFORM_PREFIX="linux" + DASHBOARD_BUILD_DIRECTORY=${DASHBOARD_BUILD_DIRECTORY:=/work} + ;; + # POSIX build env NOT SUPPORTED for windows, Needs to be done in a .ps1 shell + # MINGW*|MSYS*|CYGWIN*) + # PLATFORM_PREFIX="windows" + # DASHBOARD_BUILD_DIRECTORY="C:\P" + # ;; +*) + echo "Unsupported platform: $(uname -s)" + exit 1 + ;; +esac + +# Required environment variables +required_vars=( + ITK_GIT_TAG + PLATFORM_PREFIX + DASHBOARD_BUILD_DIRECTORY + +) + +# Sanity Validation loop +_missing_required=0 +for v in "${required_vars[@]}"; do + if [ -z "${!v:-}" ]; then + _missing_required=1 + echo "ERROR: Required environment variable '$v' is not set or empty." + fi +done +if [ "${_missing_required}" -ne 0 ]; then + exit 1 +fi +unset _missing_required + +if [ ! -d "${DASHBOARD_BUILD_DIRECTORY}" ]; then + # This is the expected directory for the cache, It may require creation with administrator credentials + mkdir -p "${DASHBOARD_BUILD_DIRECTORY}" +fi +if [ "${script_dir}" != "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage/scripts" ]; then + echo "ERROR: Github CI requires rigid directory structure, you may substitute the ITKPythonPackage organization if testing" + echo " RUN: cd ${DASHBOARD_BUILD_DIRECTORY}" + echo " RUN: git clone git@github.com:${ITKPYTHONPACKAGE_ORG}/ITKPythonPackage.git ${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage" + echo " FOR DEVELOPMENT RUN: git checkout python-build-system" + echo " RUN: ${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage/scripts/make_tarballs.sh" + exit 1 +fi + +export PIXI_HOME=${DASHBOARD_BUILD_DIRECTORY}/.pixi +if [ ! -f "${PIXI_HOME}/bin/pixi" ]; then + #PIXI INSTALL + if [ ! -f "${PIXI_HOME}/bin/pixi" ]; then + curl -fsSL https://pixi.sh/install.sh | sh + export PATH="${PIXI_HOME}/bin:$PATH" + fi +fi + +for pyenv in "${pyenvs[@]}"; do + cd "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage" || exit + "${PIXI_HOME}/bin/pixi" run -e "${PLATFORM_PREFIX}-${pyenv}" \ + python3 "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage/scripts/build_wheels.py" \ + --platform-env "${PLATFORM_PREFIX}-${pyenv}" \ + --build-dir-root "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage-build" \ + --itk-source-dir "${DASHBOARD_BUILD_DIRECTORY}/ITKPythonPackage-build/ITK" \ + --itk-git-tag "${ITK_GIT_TAG}" \ + "${BUILD_WHEELS_EXTRA_FLAGS[@]}" +done diff --git a/scripts/make_windows_zip.ps1 b/scripts/make_windows_zip.ps1 new file mode 100644 index 00000000..53eb54f5 --- /dev/null +++ b/scripts/make_windows_zip.ps1 @@ -0,0 +1,157 @@ +######################################################################## +# Build ITK Python wheel tarballs (build cache) on Windows. +# +# This is the Windows PowerShell equivalent of make_tarballs.sh. +# POSIX shell is not supported for this workflow on Windows. +# +# This script builds the ITK core cache only — it has no knowledge of +# external modules. Module-specific flags (--module-source-dir, +# --module-dependencies-root-dir, --itk-module-deps, --lib-paths) are +# intentionally absent; they belong only in the module wheel build script. +# +# Directory names are intentionally kept short to avoid Windows MAX_PATH +# issues during deep CMake/compiler builds. Everything lives under one +# root directory (C:\BDR) so no build artifacts are scattered at C:\: +# ITKPythonPackage -> C:\BDR\IPP (scripts clone) +# ITK source -> C:\BDR\ITK (ITK git checkout) +# build root -> C:\BDR (build_wheels.py root; cached build lands at C:\BDR\build\ITK-windows-...) +# +# This script MUST be run from the canonical location: +# C:\BDR\IPP\scripts\make_tarballs.ps1 +# +# Typical usage: +# > $env:ITK_GIT_TAG = "v6.0rc01" +# > .\make_tarballs.ps1 +# +# Restrict to specific python versions by passing them as arguments: +# > .\make_tarballs.ps1 py311 +# +# ----------------------------------------------------------------------- +# Environment variables: +# +# `$env:ITK_GIT_TAG` +# ITK git tag to build from. Falls back to main with loud warnings +# if unset, matching the bash script behaviour. +# +######################################################################## +param ( + [Parameter(ValueFromRemainingArguments)] + [string[]]$pyenvs +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +# Resolve python environments +if (-not $pyenvs -or $pyenvs.Count -eq 0) { + $pyenvs = @("py311") +} +echo "Building for python environments: $($pyenvs -join ', ')" + +# Resolve ITK_GIT_TAG — loud warning if unset, matching bash behaviour +$DEFAULT_ITK_GIT_TAG = "main" +if (-not $env:ITK_GIT_TAG) { + $warningLine = "===== WARNING: ITK_GIT_TAG not set, so defaulting to $DEFAULT_ITK_GIT_TAG" + echo "=============================================================================" + 1..29 | ForEach-Object { echo $warningLine } + echo "=============================================================================" + $env:ITK_GIT_TAG = $DEFAULT_ITK_GIT_TAG +} +$ITK_GIT_TAG = $env:ITK_GIT_TAG +echo "ITK_GIT_TAG : $ITK_GIT_TAG" + +# Compute paths from this script's location. +# Everything is contained under C:\BDR to keep all build artifacts in one +# place and avoid spreading directories across the drive root. +# +# C:\BDR\ <- $BDR (single root for all build content) +# C:\BDR\IPP\scripts\ <- $ScriptsDir (this file) +# C:\BDR\IPP\ <- $IPPDir (ITKPythonPackage clone) +# C:\BDR\ITK\ <- $ItkSourceDir (ITK git checkout) +# C:\BDR\ <- $BuildDirRoot (build root; cached build lands at C:\BDR\build\ITK-windows-...) +# C:\BDR\.pixi\ <- pixi home +$BDR = "C:\BDR" +$ScriptsDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$IPPDir = Split-Path -Parent $ScriptsDir +$BuildScript = Join-Path $ScriptsDir "build_wheels.py" +$ItkSourceDir = Join-Path $BDR "ITK" +$BuildDirRoot = $BDR + +echo "BDR : $BDR" +echo "IPPDir : $IPPDir" +echo "ScriptsDir : $ScriptsDir" +echo "ItkSourceDir : $ItkSourceDir" +echo "BuildDirRoot : $BuildDirRoot" + +# Validate script is running from the expected canonical location +$ExpectedScriptsDir = Join-Path $BDR "IPP\scripts" +if ($ScriptsDir -ne $ExpectedScriptsDir) { + Write-Error @" +ERROR: GitHub CI requires a rigid directory structure. +Script found at : $ScriptsDir +Expected location: $ExpectedScriptsDir + + RUN: cd $BDR + RUN: git clone git@github.com:/ITKPythonPackage.git $BDR\IPP + FOR DEVELOPMENT: git checkout python-build-system + RUN: $ExpectedScriptsDir\make_windows_zip.ps1 +"@ + exit 1 +} + +if (-not (Test-Path -LiteralPath $BuildScript)) { + throw "build_wheels.py not found at: $BuildScript" +} + +# Create BDR if it doesn't exist +# (may require administrator credentials on a fresh machine) +if (-not (Test-Path -LiteralPath $BDR)) { + echo "Creating directory: $BDR" + New-Item -ItemType Directory -Path $BDR -Force | Out-Null +} + +# Install pixi if not already present. +# Python, Doxygen, and all build tools are provided by the pixi environment. +$env:PIXI_HOME = "$BDR\.pixi" +if (-not (Test-Path "$env:PIXI_HOME\bin\pixi.exe")) { + echo "Installing pixi..." + Invoke-WebRequest -Uri "https://pixi.sh/install.ps1" -OutFile "install-pixi.ps1" + powershell -ExecutionPolicy Bypass -File "install-pixi.ps1" +} +$env:Path = "$env:PIXI_HOME\bin;$env:Path" + +# Build each requested python environment. +# Push-Location/finally ensures we always restore the caller's directory. +Push-Location $IPPDir +try { + foreach ($pyenv in $pyenvs) { + # Normalise any of: py311 / py3.11 / cp311 / 3.11 -> py311 + $pySquashed = $pyenv -replace 'py|cp|\.', '' + $pyenv = "py$pySquashed" + $platformEnv = "windows-$pyenv" + + echo "" + echo "========================================================" + echo "Building cache for platform env: $platformEnv" + echo "========================================================" + + pixi run -e $platformEnv python $BuildScript ` + --platform-env $platformEnv ` + --build-itk-tarball-cache ` + --build-dir-root $BuildDirRoot ` + --itk-source-dir $ItkSourceDir ` + --itk-git-tag $ITK_GIT_TAG ` + --no-use-sudo ` + --no-use-ccache + + if ($LASTEXITCODE -ne 0) { + throw "build_wheels.py failed for $platformEnv (exit code $LASTEXITCODE)" + } + } +} +finally { + Pop-Location +} + +echo "" +echo "All tarball builds completed successfully." diff --git a/scripts/windows-download-cache-and-build-module-wheels.ps1 b/scripts/windows-download-cache-and-build-module-wheels.ps1 index 0d7b8fe2..47c4dcec 100644 --- a/scripts/windows-download-cache-and-build-module-wheels.ps1 +++ b/scripts/windows-download-cache-and-build-module-wheels.ps1 @@ -1,134 +1,246 @@ ######################################################################## -# Pull build dependencies and build an ITK external module. +# Pull build dependencies and build an ITK external module on Windows. # -# This script must be run in an x64 Developer Powershell. +# This script must be run in an x64 Developer PowerShell. # See https://learn.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell?view=vs-2022#developer-powershell # # ----------------------------------------------------------------------- -# Positional parameters: -# - 0th parameter or -python_version_minor option: Python minor version. +# Positional parameters / named options: +# +# -python_version_minor Python minor version (required). # For instance, for Python 3.11: -# > windows-download-cache-and-build-module-wheels.ps1 11 -# or equivalently: # > windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 11 +# or positionally: +# > windows-download-cache-and-build-module-wheels.ps1 11 # -# - 1st parameter or -setup_options: pyproject.toml options. -# For instance, for Python 3.11, excluding nvcuda.dll during packaging: -# > windows-download-cache-and-build-module-wheels.ps1 11 "--exclude-libs nvcuda.dll" -# or equivalently: -# > windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 11 -setup_options "--exclude-libs nvcuda.dll" -# -# - 2nd parameter or -cmake_options: CMake options passed to pyproject.tom for project configuration. -# For instance, for Python 3.11, excluding nvcuda.dll during packaging -# and setting RTK_USE_CUDA ON during configuration: -# > windows-download-cache-and-build-module-wheels.ps1 11 "--exclude-libs nvcuda.dll" "-DRTK_USE_CUDA:BOOL=ON" -# or equivalently: -# > windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 11 -setup_options "--exclude-libs nvcuda.dll" -cmake-options "-DRTK_USE_CUDA:BOOL=ON" +# -setup_options pyproject.toml options forwarded to the build script. +# For instance, to exclude a library during packaging: +# > ... -setup_options "--exclude-libs nvcuda.dll" # +# -cmake_options CMake options passed to pyproject.toml for project configuration. +# For instance: +# > ... -cmake_options "-DRTK_USE_CUDA:BOOL=ON" # # ----------------------------------------------------------------------- # Environment variables used in this script: # -# `$env:ITK_PACKAGE_VERSION`: Tag for ITKPythonBuilds build archive to use +# `$env:ITK_PACKAGE_VERSION` +# Tag for the ITKPythonBuilds archive to download/use. Required. # -# `$env:ITKPYTHONPACKAGE_TAG`: Tag for ITKPythonPackage build scripts to use. -# If ITKPYTHONPACKAGE_TAG is empty then the default scripts distributed -# with the ITKPythonBuilds archive will be used. +# `$env:ITKPYTHONPACKAGE_TAG` +# Tag for ITKPythonPackage build scripts to use. +# If empty, the scripts bundled in the archive will be used. # -# `$env:ITKPYTHONPACKAGE_ORG`: Github organization or user to use for ITKPythonPackage -# build script source. Default is InsightSoftwareConsortium. +# `$env:ITKPYTHONPACKAGE_ORG` +# GitHub organization/user for ITKPythonPackage. Default: InsightSoftwareConsortium. # Ignored if ITKPYTHONPACKAGE_TAG is empty. # -# `$env:ITK_MODULE_PREQ`: Delimited list of ITK module dependencies to build before building the target module. -# Format is `/@:/@:...`. -# For instance, `export ITK_MODULE_PREQ=InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` +# `$env:ITK_MODULE_PREQ` +# Colon-delimited list of ITK module dependencies. +# Format: `/@:/@:...` +# Example: `InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` +# Passed directly to build_wheels.py via --itk-module-deps. +# +# `$env:MODULE_SRC_DIRECTORY` +# Path to the ITK external module source. Defaults to the directory +# containing this script. # ######################################################################## param ( [int]$python_version_minor, - [string]$setup_options, - [string]$cmake_options + [string]$setup_options = "", + [string]$cmake_options = "" ) -$pythonArch = "64" -$pythonVersion = "3.$python_version_minor" -echo "Pulling Python $pythonVersion-x$pythonArch" -iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/scikit-build/scikit-ci-addons/master/windows/install-python.ps1')) +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" -if (-not $env:ITK_PACKAGE_VERSION) { $env:ITK_PACKAGE_VERSION = 'v5.4.0' } -echo "Fetching build archive $env:ITK_PACKAGE_VERSION" -if (Test-Path C:\P) { - Remove-Item -Recurse -Force C:\P +# Validate required inputs +if (-not $python_version_minor) { + Write-Error "ERROR: -python_version_minor is required. Example: -python_version_minor 11" + exit 1 } -if (-not (Test-Path ITKPythonBuilds-windows.zip)) { - Invoke-WebRequest -Uri "https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/$env:ITK_PACKAGE_VERSION/ITKPythonBuilds-windows.zip" -OutFile "ITKPythonBuilds-windows.zip" +if (-not $env:ITK_PACKAGE_VERSION) { + Write-Error "ERROR: `$env:ITK_PACKAGE_VERSION must be set before running this script." + exit 1 } -7z x ITKPythonBuilds-windows.zip -oC:\P -aoa -r -# Optional: Update ITKPythonPackage build scripts -if ($env:ITKPYTHONPACKAGE_TAG) { - if(-not $env:ITKPYTHONPACKAGE_ORG) { - $env:ITKPYTHONPACKAGE_ORG="InsightSoftwareConsortium" - } +# Resolve configuration +$MODULE_SRC_DIRECTORY = if ($env:MODULE_SRC_DIRECTORY) { + $env:MODULE_SRC_DIRECTORY +} else { + $PSScriptRoot +} +echo "MODULE_SRC_DIRECTORY: $MODULE_SRC_DIRECTORY" - echo "Updating build scripts to $env:ITKPYTHONPACKAGE_ORG/ITKPythonPackage@$env:ITKPYTHONPACKAGE_TAG" +$ITK_PACKAGE_VERSION = $env:ITK_PACKAGE_VERSION +# For backwards compatibility when the ITK_GIT_TAG was required to match the ITK_PACKAGE_VERSION +$ITK_GIT_TAG = if ($env:ITK_GIT_TAG) { $env:ITK_GIT_TAG } else { $ITK_PACKAGE_VERSION } +$ITKPYTHONPACKAGE_ORG = if ($env:ITKPYTHONPACKAGE_ORG) { $env:ITKPYTHONPACKAGE_ORG } else { "InsightSoftwareConsortium" } +$ITKPYTHONPACKAGE_TAG = if ($env:ITKPYTHONPACKAGE_TAG) { $env:ITKPYTHONPACKAGE_TAG } else { "" } - pushd C:\P - git clone "https://github.com/$env:ITKPYTHONPACKAGE_ORG/ITKPythonPackage.git" "IPP-tmp" - pushd "IPP-tmp" - git checkout "$env:ITKPYTHONPACKAGE_TAG" - git status - popd +$DASHBOARD_BUILD_DIRECTORY = "C:\BDR" +$platformEnv = "windows-py3$python_version_minor" - Remove-Item -Recurse -Force IPP/scripts/ - Copy-Item -Recurse IPP-tmp/scripts IPP/ - Copy-Item IPP-tmp/requirements-dev.txt IPP/ - Remove-Item -Recurse -Force IPP-tmp/ - popd +echo "Python version : 3.$python_version_minor" +echo "ITK_PACKAGE_VERSION: $ITK_PACKAGE_VERSION" +echo "ITK_GIT_TAG : $ITK_GIT_TAG" +echo "Platform env : $platformEnv" + +# Install pixi and required global tools +# NOTE: Python and Doxygen are provided by the pixi environment; no need to +# install them separately here. +$env:PIXI_HOME = "$DASHBOARD_BUILD_DIRECTORY\.pixi" +if (-not (Test-Path "$env:PIXI_HOME\bin\pixi.exe")) { + echo "Installing pixi..." + Invoke-WebRequest -Uri "https://pixi.sh/install.ps1" -OutFile "install-pixi.ps1" + powershell -ExecutionPolicy Bypass -File "install-pixi.ps1" } +$env:Path = "$env:PIXI_HOME\bin;$env:Path" -# Get other build dependencies -if (-not (Test-Path doxygen-1.16.1.windows.bin.zip)) { - Invoke-WebRequest -Uri "https://github.com/doxygen/doxygen/releases/download/Release_1_16_1/doxygen-1.16.1.windows.x64.bin.zip" -OutFile "doxygen-1.16.1.windows.bin.zip" +# Install global packages via pixi for use in this script +# Note: Using conda-forge packages that are available on Windows +echo "Installing global tools via pixi..." +# aria2 is deliberately absent: conda-forge publishes no win-64 build, so +# `pixi global install aria2` always fails here with "No candidates were +# found" and prints a red "Error: couldn't install 1 environment" that looks +# like a real failure. The download below still prefers aria2c when one is +# already on PATH; otherwise it uses Invoke-WebRequest. +$globalPackages = @( + "git" # Required for cloning ITKPythonPackage repo +) + +foreach ($pkg in $globalPackages) { + echo " Installing $pkg..." + try { + & pixi global install $pkg + if ($LASTEXITCODE -ne 0) { + echo " Warning: Failed to install $pkg (exit code: $LASTEXITCODE)" + } + } catch { + echo " Warning: Failed to install $pkg - $($_.Exception.Message)" + } +} + +# Refresh PATH to include pixi global binaries +$env:Path = "$env:PIXI_HOME\bin;$env:Path" + +# --------------------------------------------------------------------------- +# Download ITKPythonBuilds archive +# --------------------------------------------------------------------------- +$zipName = "ITKPythonBuilds-windows.zip" +$zipDownloadUrl = "https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases/download/$ITK_PACKAGE_VERSION/$zipName" +$localZipName = "ITKPythonBuilds-windows.zip" + +if (Test-Path $localZipName) { + echo "Found cached archive: $localZipName -- skipping download." +} else { + echo "Downloading $zipDownloadUrl ..." + + # Try first (faster, resumable), fall back to Invoke-WebRequest + $aria2Path = Get-Command aria2c -ErrorAction SilentlyContinue + if ($aria2Path) { + echo " Using aria2c for download..." + & aria2c -c --file-allocation=none -s 10 -x 10 -o $localZipName $zipDownloadUrl + } else { + echo " Using Invoke-WebRequest for download..." + Invoke-WebRequest -Uri $zipDownloadUrl -OutFile $localZipName + } } -7z x doxygen-1.16.1.windows.bin.zip -oC:\P\doxygen -aoa -r -if (-not (Test-Path grep-win.zip)) { - Invoke-WebRequest -Uri "https://data.kitware.com/api/v1/file/5bbf87ba8d777f06b91f27d6/download/grep-win.zip" -OutFile "grep-win.zip" + +# Unpack archive +# Expected layout after extraction under $DASHBOARD_BUILD_DIRECTORY: +# \ITK ITK source tree +# \build\ pre-built ITK artifacts +# \IPP ITKPythonPackage scripts + +echo "Extracting archive to $DASHBOARD_BUILD_DIRECTORY ..." +# Use 7-Zip to extract (matches the format it was created in) +$sevenZipPath = "C:\Program Files\7-Zip\7z.exe" +if (-not (Test-Path $sevenZipPath)) { + Write-Error "7-Zip not found at $sevenZipPath. Please install 7-Zip." + exit 1 } -7z x grep-win.zip -oC:\P\grep -aoa -r -$env:Path += ";C:\P\grep" -# Build ITK module dependencies, if any -$build_command = "& `"C:\Python$pythonVersion-x$pythonArch\python.exe`" `"C:\P\IPP\scripts\windows_build_module_wheels.py`" --no-cleanup --py-envs `"3$python_version_minor-x64`"" -if ("$setup_options".length -gt 0) { - $build_command = "$build_command $setup_options" +& $sevenZipPath x $localZipName -o"$DASHBOARD_BUILD_DIRECTORY" -y +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to extract archive (exit code: $LASTEXITCODE)" + exit 1 } -if("$cmake_options".length -gt 0) { - $build_command = "$build_command -- $cmake_options" +# Optional: overlay ITKPythonPackage build scripts from a specific tag +cd "$DASHBOARD_BUILD_DIRECTORY\IPP" +$env:PATH += ";C:\Program Files\Git\bin" +if ($ITKPYTHONPACKAGE_TAG) { + echo "Updating build scripts to $ITKPYTHONPACKAGE_ORG/ITKPythonPackage@$ITKPYTHONPACKAGE_TAG" + + $ippTmpDir = "$DASHBOARD_BUILD_DIRECTORY\IPP-tmp" + $ippCloneUrl = "https://github.com/$ITKPYTHONPACKAGE_ORG/ITKPythonPackage.git" + + if (-not (Test-Path "$ippTmpDir\.git")) { + echo " Cloning repository..." + & git clone $ippCloneUrl $ippTmpDir + + # Check if clone succeeded + if ($LASTEXITCODE -ne 0 -or -not (Test-Path $ippTmpDir)) { + Write-Error "Failed to clone ITKPythonPackage repository" + exit 1 + } + } + + pushd $ippTmpDir + echo " Checking out $ITKPYTHONPACKAGE_TAG..." + & git checkout $ITKPYTHONPACKAGE_TAG + & git reset "origin/$ITKPYTHONPACKAGE_TAG" --hard + & git status + popd + + echo " Copying updated scripts..." + Copy-Item -Recurse -Force "$ippTmpDir\*" "$DASHBOARD_BUILD_DIRECTORY\IPP\" + Remove-Item -Recurse -Force $ippTmpDir } -echo $build_command -echo "ITK_MODULE_PREQ: $env:ITK_MODULE_PREQ $ITK_MODULE_PREQ" +# Build the module wheel +# Assemble paths used by build_wheels.py +$ippDir = "$DASHBOARD_BUILD_DIRECTORY\IPP" +$buildScript = "$ippDir\scripts\build_wheels.py" +# build_wheels.py expects the cached ITK build at \build\ITK-windows-py3XX-... +# Since the zip extracts directly into BDR (i.e. BDR\build\ITK-windows-py311-...), BDR is the root. +$buildDirRoot = $DASHBOARD_BUILD_DIRECTORY +$itkSourceDir = "$DASHBOARD_BUILD_DIRECTORY\ITK" +$moduleDepsDir = "$DASHBOARD_BUILD_DIRECTORY\MDEPS" + +# Instead of building a string and using iex, build an argument array +$buildArgs = @( + "run", "-e", $platformEnv, + "python", $buildScript, + "--platform-env", $platformEnv, + "--module-source-dir", $MODULE_SRC_DIRECTORY, + "--module-dependencies-root-dir", $moduleDepsDir +) + if ($env:ITK_MODULE_PREQ) { - $MODULES_LIST = $env:ITK_MODULE_PREQ.split(":") - foreach($MODULE_INFO in $MODULES_LIST) { - $MODULE_ORG = $MODULE_INFO.split("/")[0] - $MODULE_NAME = $MODULE_INFO.split("@")[0].split("/")[1] - $MODULE_TAG = $MODULE_INFO.split("@")[1] - - $MODULE_UPSTREAM = "https://github.com/$MODULE_ORG/$MODULE_NAME.git" - echo "Cloning from $MODULE_UPSTREAM" - git clone $MODULE_UPSTREAM - - pushd $MODULE_NAME - git checkout $MODULE_TAG - echo "Building $MODULE_NAME" - iex $build_command - popd - - Copy-Item $MODULE_NAME/include/* include/ - } + $buildArgs += @("--itk-module-deps", $env:ITK_MODULE_PREQ) +} + +$buildArgs += @( + "--no-build-itk-tarball-cache", + "--build-dir-root", $buildDirRoot, + "--itk-git-tag", $ITK_GIT_TAG, + "--itk-source-dir", $itkSourceDir, + "--itk-package-version", $ITK_PACKAGE_VERSION, + "--no-use-ccache", + "--skip-itk-build", + "--skip-itk-wheel-build" +) + +if ($setup_options.Length -gt 0) { + $buildArgs += $setup_options.Split(" ") +} + +if ($cmake_options.Length -gt 0) { + $buildArgs += @("--") + $buildArgs += $cmake_options.Split(" ") } -# Run build scripts -iex $build_command +echo "Building target module..." +& pixi @buildArgs From 0ef1eaf61fa6363a9efaa930f4152d2c7ad2f289 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 19:43:16 -0500 Subject: [PATCH 09/16] ENH: Remove superseded shell based build system --- .travis.yml | 15 - BuildWheelsSupport/WHEEL_NAMES.txt | 2 +- CMakeLists.txt | 571 ---- README.md | 2 +- cmake/ITKPythonPackage.cmake | 240 -- docs/Quick_start_guide.rst | 247 -- itkVersion.py | 31 - requirements-dev.txt | 6 - .../dockcross-manylinux-build-module-deps.sh | 108 - scripts/dockcross-manylinux-build-tarball.sh | 44 - scripts/dockcross-manylinux-cleanup.sh | 46 - scripts/dockcross-manylinux-set-vars.sh | 62 - scripts/internal/Support.cmake | 2749 ----------------- ...not-needed-symbols-exported-by-interpreter | 0 .../manylinux-aarch64-build-module-wheels.sh | 15 - scripts/internal/manylinux-build-common.sh | 85 - .../internal/manylinux-build-module-wheels.sh | 144 - scripts/internal/manylinux-build-wheels.sh | 215 -- scripts/internal/shellcheck-run.sh | 35 - scripts/internal/wheel_builder_utils.py | 95 - scripts/internal/windows_build_common.py | 60 - scripts/lint-shell-scripts.sh | 20 - scripts/macpython-build-common.sh | 79 - scripts/macpython-build-module-deps.sh | 75 - scripts/macpython-build-module-wheels.sh | 134 - scripts/macpython-build-tarball.sh | 29 - scripts/macpython-build-wheels.sh | 238 -- scripts/macpython-install-python.sh | 423 --- scripts/oci_exe.sh | 23 - scripts/update_python_version.py | 96 - scripts/update_python_version.sh | 50 - scripts/windows-build-tarball.ps1 | 8 - scripts/windows_build_module_wheels.py | 267 -- scripts/windows_build_wheels.py | 411 --- 34 files changed, 2 insertions(+), 6623 deletions(-) delete mode 100644 .travis.yml delete mode 100644 CMakeLists.txt delete mode 100644 cmake/ITKPythonPackage.cmake delete mode 100644 docs/Quick_start_guide.rst delete mode 100644 itkVersion.py delete mode 100644 requirements-dev.txt delete mode 100755 scripts/dockcross-manylinux-build-module-deps.sh delete mode 100755 scripts/dockcross-manylinux-build-tarball.sh delete mode 100755 scripts/dockcross-manylinux-cleanup.sh delete mode 100755 scripts/dockcross-manylinux-set-vars.sh delete mode 100644 scripts/internal/Support.cmake delete mode 100644 scripts/internal/libpython-not-needed-symbols-exported-by-interpreter delete mode 100755 scripts/internal/manylinux-aarch64-build-module-wheels.sh delete mode 100644 scripts/internal/manylinux-build-common.sh delete mode 100755 scripts/internal/manylinux-build-module-wheels.sh delete mode 100755 scripts/internal/manylinux-build-wheels.sh delete mode 100755 scripts/internal/shellcheck-run.sh delete mode 100644 scripts/internal/wheel_builder_utils.py delete mode 100644 scripts/internal/windows_build_common.py delete mode 100755 scripts/lint-shell-scripts.sh delete mode 100644 scripts/macpython-build-common.sh delete mode 100644 scripts/macpython-build-module-deps.sh delete mode 100755 scripts/macpython-build-module-wheels.sh delete mode 100755 scripts/macpython-build-tarball.sh delete mode 100755 scripts/macpython-build-wheels.sh delete mode 100755 scripts/macpython-install-python.sh delete mode 100644 scripts/oci_exe.sh delete mode 100755 scripts/update_python_version.py delete mode 100755 scripts/update_python_version.sh delete mode 100644 scripts/windows-build-tarball.ps1 delete mode 100755 scripts/windows_build_module_wheels.py delete mode 100644 scripts/windows_build_wheels.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e00ff622..00000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -sudo: required - -services: - - docker - -cache: - directories: - - $HOME/docker - -before_install: - - sudo pip install -U scikit-ci-addons - - ci_addons docker load-pull-save r.j3ss.co/shellcheck - -script: - - scripts/lint-shell-scripts.sh diff --git a/BuildWheelsSupport/WHEEL_NAMES.txt b/BuildWheelsSupport/WHEEL_NAMES.txt index 94ff00af..434c06c1 100644 --- a/BuildWheelsSupport/WHEEL_NAMES.txt +++ b/BuildWheelsSupport/WHEEL_NAMES.txt @@ -4,4 +4,4 @@ itk-io itk-filtering itk-registration itk-segmentation -itk-meta \ No newline at end of file +itk-meta diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 6908545c..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,571 +0,0 @@ -cmake_minimum_required(VERSION 3.26.6 FATAL_ERROR) -# NOTE: 3.26.6 is the first cmake vesion to support Development.SABIModule - -project(ITKPythonPackage CXX) - -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) -include(ITKPythonPackage) - -if(NOT DEFINED ITKPythonPackage_SUPERBUILD) - set(ITKPythonPackage_SUPERBUILD 1) -endif() - -if(NOT DEFINED ITKPythonPackage_WHEEL_NAME) - set(ITKPythonPackage_WHEEL_NAME "itk") -endif() -message(STATUS "SuperBuild - ITKPythonPackage_WHEEL_NAME:${ITKPythonPackage_WHEEL_NAME}") - -option(ITKPythonPackage_USE_TBB "Build and use oneTBB in the ITK python package" ON) - -if(ITKPythonPackage_SUPERBUILD) - - #----------------------------------------------------------------------------- - #------------------------------------------------------ - #---------------------------------- - # ITKPythonPackage_SUPERBUILD: ON - #---------------------------------- - #------------------------------------------------------ - #----------------------------------------------------------------------------- - - # Avoid "Manually-specified variables were not used by the project" warnings. - ipp_unused_vars(${PYTHON_VERSION_STRING} ${SKBUILD}) - - set(ep_common_cmake_cache_args ) - if(NOT CMAKE_CONFIGURATION_TYPES) - list(APPEND ep_common_cmake_cache_args - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - ) - endif() - set(ep_download_extract_timestamp_arg ) - if(CMAKE_VERSION VERSION_EQUAL "3.24" OR CMAKE_VERSION VERSION_GREATER "3.24") - # See https://cmake.org/cmake/help/latest/policy/CMP0135.html - set(ep_download_extract_timestamp_arg DOWNLOAD_EXTRACT_TIMESTAMP 1) - endif() - - #----------------------------------------------------------------------------- - # Options - - # When building different "flavor" of ITK python packages on a given platform, - # explicitly setting the following options allow to speed up package generation by - # re-using existing resources. - # - # ITK_SOURCE_DIR: Path to an existing source directory - # - - option(ITKPythonPackage_BUILD_PYTHON "Build ITK python module" ON) - mark_as_advanced(ITKPythonPackage_BUILD_PYTHON) - - if(CMAKE_OSX_DEPLOYMENT_TARGET) - list(APPEND ep_common_cmake_cache_args - -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET}) - endif() - if(CMAKE_OSX_ARCHITECTURES) - list(APPEND ep_common_cmake_cache_args - -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}) - endif() - - if(CMAKE_MAKE_PROGRAM) - list(APPEND ep_common_cmake_cache_args - -DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}) - endif() - - #----------------------------------------------------------------------------- - # compile with multiple processors - include(ProcessorCount) - ProcessorCount(NPROC) - if(NOT NPROC EQUAL 0) - set( ENV{MAKEFLAGS} "-j${NPROC}" ) - endif() - - #----------------------------------------------------------------------------- - include(ExternalProject) - - set(ITK_REPOSITORY "https://github.com/InsightSoftwareConsortium/ITK.git") - - # release branch, 2026-03-10 - set(ITK_GIT_TAG "c6e1c1be6a86236cbb812bd71eec5a4df9b1353c") - - #----------------------------------------------------------------------------- - # A separate project is used to download ITK, so that it can reused - # when building different "flavor" of ITK python packages - - message(STATUS "SuperBuild -") - message(STATUS "SuperBuild - ITK-source-download") - - # Sanity checks - if(DEFINED ITK_SOURCE_DIR AND NOT EXISTS ${ITK_SOURCE_DIR}) - message(FATAL_ERROR "ITK_SOURCE_DIR variable is defined but corresponds to nonexistent directory") - endif() - - if(ITKPythonPackage_USE_TBB) - if(ITK_SOURCE_DIR) - set(TBB_DIR "${ITK_SOURCE_DIR}/../oneTBB-prefix/lib/cmake/TBB") - else() - set(TBB_DIR "${CMAKE_BINARY_DIR}/../oneTBB-prefix/lib/cmake/TBB") - endif() - set(tbb_args ) - if(ITKPythonPackage_USE_TBB) - set(tbb_args - -DModule_ITKTBB:BOOL=ON - -DTBB_DIR:PATH=${TBB_DIR} - ) - endif() - - set(tbb_cmake_cache_args) - if(CMAKE_OSX_DEPLOYMENT_TARGET) - list(APPEND tbb_cmake_cache_args - -DCMAKE_CXX_OSX_DEPLOYMENT_TARGET_FLAG:STRING="-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" - -DCMAKE_C_OSX_DEPLOYMENT_TARGET_FLAG:STRING="-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" - ) - endif() - - ExternalProject_add(oneTBB - URL https://github.com/oneapi-src/oneTBB/archive/refs/tags/v2022.2.0.tar.gz - URL_HASH SHA256=f0f78001c8c8edb4bddc3d4c5ee7428d56ae313254158ad1eec49eced57f6a5b - CMAKE_ARGS - -DTBB_TEST:BOOL=OFF - -DCMAKE_BUILD_TYPE:STRING=Release - -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/../oneTBB-prefix - -DCMAKE_INSTALL_LIBDIR:STRING=lib # Skip default initialization by GNUInstallDirs CMake module - ${ep_common_cmake_cache_args} - ${tbb_cmake_cache_args} - ${ep_download_extract_timestamp_arg} - -DCMAKE_BUILD_TYPE:STRING=Release - BUILD_BYPRODUCTS "${TBB_DIR}/TBBConfig.cmake" - USES_TERMINAL_DOWNLOAD 1 - USES_TERMINAL_UPDATE 1 - USES_TERMINAL_CONFIGURE 1 - USES_TERMINAL_BUILD 1 - ) - message(STATUS "SuperBuild - TBB: Enabled") - message(STATUS "SuperBuild - TBB_DIR: ${TBB_DIR}") - endif() - set(tbb_depends "") - if(ITKPythonPackage_USE_TBB) - set(tbb_depends oneTBB) - endif() - - - if(NOT DEFINED ITK_SOURCE_DIR) - - set(ITK_SOURCE_DIR ${CMAKE_BINARY_DIR}/ITK) - - ExternalProject_add(ITK-source-download - SOURCE_DIR ${ITK_SOURCE_DIR} - GIT_REPOSITORY ${ITK_REPOSITORY} - GIT_TAG ${ITK_GIT_TAG} - USES_TERMINAL_DOWNLOAD 1 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - DEPENDS "${tbb_depends}" - ) - set(proj_status "") - - else() - - ipp_ExternalProject_Add_Empty( - ITK-source-download - "" - ) - set(proj_status " (REUSE)") - - endif() - - message(STATUS "SuperBuild - ITK_SOURCE_DIR: ${ITK_SOURCE_DIR}") - message(STATUS "SuperBuild - ITK-source-download[OK]${proj_status}") - - #----------------------------------------------------------------------------- - if(NOT ITKPythonPackage_BUILD_PYTHON) - return() - endif() - - #----------------------------------------------------------------------------- - # Search for python interpreter and libraries - - message(STATUS "SuperBuild -") - message(STATUS "SuperBuild - Searching for python") - - # Sanity checks - if(DEFINED Python3_INCLUDE_DIR AND NOT EXISTS ${Python3_INCLUDE_DIR}) - message(FATAL_ERROR "Python3_INCLUDE_DIR variable is defined but corresponds to nonexistent directory") - endif() - if(DEFINED Python3_LIBRARY AND NOT EXISTS ${Python3_LIBRARY}) - message(FATAL_ERROR "Python3_LIBRARY variable is defined but corresponds to nonexistent file") - endif() - if(DEFINED Python3_EXECUTABLE AND NOT EXISTS ${Python3_EXECUTABLE}) - message(FATAL_ERROR "Python3_EXECUTABLE variable is defined but corresponds to nonexistent file") - endif() - if(DEFINED DOXYGEN_EXECUTABLE AND NOT EXISTS ${DOXYGEN_EXECUTABLE}) - message(FATAL_ERROR "DOXYGEN_EXECUTABLE variable is defined but corresponds to nonexistent file") - endif() - - if(NOT DEFINED Python3_INCLUDE_DIR - OR NOT DEFINED Python3_LIBRARY - OR NOT DEFINED Python3_EXECUTABLE) - - find_package(Python3 COMPONENTS Interpreter Development) - if(NOT Python3_EXECUTABLE AND _Python3_EXECUTABLE) - set(Python3_EXECUTABLE ${_Python3_EXECUTABLE} CACHE INTERNAL - "Path to the Python interpreter" FORCE) - endif() - endif() - if(NOT DEFINED DOXYGEN_EXECUTABLE) - find_package(Doxygen REQUIRED) - endif() - - message(STATUS "SuperBuild - Python3_INCLUDE_DIR: ${Python3_INCLUDE_DIR}") - message(STATUS "SuperBuild - Python3_INCLUDE_DIRS: ${Python3_INCLUDE_DIRS}") - message(STATUS "SuperBuild - Python3_LIBRARY: ${Python3_LIBRARY}") - message(STATUS "SuperBuild - Python3_EXECUTABLE: ${Python3_EXECUTABLE}") - message(STATUS "SuperBuild - Searching for python[OK]") - message(STATUS "SuperBuild - DOXYGEN_EXECUTABLE: ${DOXYGEN_EXECUTABLE}") - - # CMake configuration variables to pass to ITK's build - set(ep_itk_cmake_cache_args "") - foreach(var - BUILD_SHARED_LIBS - ITK_BUILD_DEFAULT_MODULES - ) - if(DEFINED ${var}) - list(APPEND ep_itk_cmake_cache_args "-D${var}=${${var}}") - endif() - endforeach() - function(cached_variables RESULTVAR PATTERN) - get_cmake_property(variables CACHE_VARIABLES) - set(result) - foreach(variable ${variables}) - if(${variable} AND variable MATCHES "${PATTERN}") - list(APPEND result "-D${variable}=${${variable}}") - endif() - endforeach() - set(${RESULTVAR} ${result} PARENT_SCOPE) - endfunction() - cached_variables(itk_pattern_cached_vars "^(ITK_WRAP_)|(ITKGroup_)|(Module_)") - list(APPEND ep_itk_cmake_cache_args ${itk_pattern_cached_vars}) - # Todo, also pass all Module_* variables - message(STATUS "ITK CMake Cache Args - ${ep_itk_cmake_cache_args}") - #----------------------------------------------------------------------------- - # ITK: This project builds ITK and associated Python modules - - option(ITKPythonPackage_ITK_BINARY_REUSE "Reuse provided ITK_BINARY_DIR without configuring or building ITK" OFF) - - set(ITK_BINARY_DIR "${CMAKE_BINARY_DIR}/ITKb" CACHE PATH "ITK build directory") - - message(STATUS "SuperBuild -") - message(STATUS "SuperBuild - ITK => Requires ITK-source-download") - message(STATUS "SuperBuild - ITK_BINARY_DIR: ${ITK_BINARY_DIR}") - - if(NOT ITKPythonPackage_ITK_BINARY_REUSE) - - set(_stamp "${CMAKE_BINARY_DIR}/ITK-prefix/src/ITK-stamp/ITK-configure") - if(EXISTS ${_stamp}) - execute_process(COMMAND ${CMAKE_COMMAND} -E remove ${_stamp}) - message(STATUS "SuperBuild - Force re-configure removing ${_stamp}") - endif() - - set(install_component_per_module OFF) - if(NOT ITKPythonPackage_WHEEL_NAME STREQUAL "itk") - set(install_component_per_module ON) - endif() - - ExternalProject_add(ITK - DOWNLOAD_COMMAND "" - SOURCE_DIR ${ITK_SOURCE_DIR} - BINARY_DIR ${ITK_BINARY_DIR} - PREFIX "ITKp" - CMAKE_ARGS - -DBUILD_TESTING:BOOL=OFF - -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} - -DPY_SITE_PACKAGES_PATH:PATH=${CMAKE_INSTALL_PREFIX} - -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel - -DWRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL=${install_component_per_module} - -DITK_LEGACY_SILENT:BOOL=ON - -DITK_WRAP_PYTHON:BOOL=ON - -DDOXYGEN_EXECUTABLE:FILEPATH=${DOXYGEN_EXECUTABLE} - -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} - -DPython3_LIBRARY:FILEPATH=${Python3_LIBRARY} - -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} - ${ep_common_cmake_cache_args} - ${tbb_args} - ${ep_itk_cmake_cache_args} - ${ep_download_extract_timestamp_arg} - USES_TERMINAL_DOWNLOAD 1 - USES_TERMINAL_UPDATE 1 - USES_TERMINAL_CONFIGURE 1 - USES_TERMINAL_BUILD 1 - INSTALL_COMMAND "" - ) - set(proj_status "") - - else() - - # Sanity checks - if(NOT EXISTS "${ITK_BINARY_DIR}/CMakeCache.txt") - message(FATAL_ERROR "ITKPythonPackage_ITK_BINARY_REUSE is ON but ITK_BINARY_DIR variable is not associated with an ITK build directory. [ITK_BINARY_DIR:${ITK_BINARY_DIR}]") - endif() - - ipp_ExternalProject_Add_Empty( - ITK - "" - ) - set(proj_status " (REUSE)") - - endif() - ExternalProject_Add_StepDependencies(ITK download ITK-source-download) - - message(STATUS "SuperBuild - ITK[OK]${proj_status}") - - #----------------------------------------------------------------------------- - # ITKPythonPackage: This project adds install rules for the "RuntimeLibraries" - # components associated with the ITK project. - - message(STATUS "SuperBuild -") - message(STATUS "SuperBuild - ${PROJECT_NAME} => Requires ITK") - - ExternalProject_add(${PROJECT_NAME} - SOURCE_DIR ${CMAKE_SOURCE_DIR} - BINARY_DIR ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-build - DOWNLOAD_COMMAND "" - UPDATE_COMMAND "" - CMAKE_CACHE_ARGS - -DITKPythonPackage_SUPERBUILD:BOOL=0 - -DITK_BINARY_DIR:PATH=${ITK_BINARY_DIR} - -DITK_SOURCE_DIR:PATH=${ITK_SOURCE_DIR} - -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} - -DITKPythonPackage_WHEEL_NAME:STRING=${ITKPythonPackage_WHEEL_NAME} - -DITKPythonPackage_USE_TBB:BOOL=${ITKPythonPackage_USE_TBB} - ${ep_common_cmake_cache_args} - USES_TERMINAL_CONFIGURE 1 - INSTALL_COMMAND "" - DEPENDS ITK - ) - - install(SCRIPT ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-build/cmake_install.cmake) - - message(STATUS "SuperBuild - ${PROJECT_NAME}[OK]") - -else() - - #----------------------------------------------------------------------------- - #------------------------------------------------------ - #---------------------------------- - # ITKPythonPackage_SUPERBUILD: OFF - #---------------------------------- - #------------------------------------------------------ - #----------------------------------------------------------------------------- - - set(components "PythonWheelRuntimeLibraries") - if(NOT ITKPythonPackage_WHEEL_NAME STREQUAL "itk") - - message(STATUS "ITKPythonPackage_WHEEL_NAME: ${ITKPythonPackage_WHEEL_NAME}") - - # Extract ITK group name from wheel name - message(STATUS "") - set(msg "Extracting ITK_WHEEL_GROUP") - message(STATUS ${msg}) - ipp_wheel_to_group(${ITKPythonPackage_WHEEL_NAME} ITK_WHEEL_GROUP) - message(STATUS "${msg} - done [${ITK_WHEEL_GROUP}]") - - # - # Considering that - # - # * Every ITK module is associated with exactly one ITK group. - # * ITK module dependencies are specified independently of ITK groups - # - # we semi-arbitrarily defined a collection of wheels (see ``ITK_WHEEL_GROUPS``) - # that will roughly bundle the modules associated with each group. - # - # Based on the module dependency graph, the code below will determine which module - # should be packaged in which wheel. - # - - # List of ITK wheel groups - set(ITK_WHEEL_GROUPS "") - file(STRINGS "${CMAKE_SOURCE_DIR}/scripts/WHEEL_NAMES.txt" ITK_WHEELS REGEX "^itk-.+") - foreach(wheel_name IN LISTS ITK_WHEELS) - ipp_wheel_to_group(${wheel_name} group) - list(APPEND ITK_WHEEL_GROUPS ${group}) - endforeach() - - # Define below a reasonable dependency graph for ITK groups - set(ITK_GROUP_Core_DEPENDS) - set(ITK_GROUP_IO_DEPENDS Core) - set(ITK_GROUP_Numerics_DEPENDS Core) - set(ITK_GROUP_Filtering_DEPENDS Numerics) - set(ITK_GROUP_Segmentation_DEPENDS Filtering) - set(ITK_GROUP_Registration_DEPENDS Filtering) - set(ITK_GROUP_Video_DEPENDS Core) - - # ITK is needed to retrieve ITK module information - set(ITK_DIR ${ITK_BINARY_DIR}) - find_package(ITK REQUIRED) - set(CMAKE_MODULE_PATH ${ITK_CMAKE_DIR} ${CMAKE_MODULE_PATH}) - - # Sort wheel groups - include(TopologicalSort) - topological_sort(ITK_WHEEL_GROUPS ITK_GROUP_ _DEPENDS) - - # Set ``ITK_MODULE__DEPENDS`` variables - # - # Notes: - # - # * ``_DEPENDS`` variables are set after calling ``find_package(ITK REQUIRED)`` - # - # * This naming convention corresponds to what is used internally in ITK and allow - # to differentiate with variable like ``ITK_GROUP__DEPENDS`` set above. - # - foreach(module IN LISTS ITK_MODULES_ENABLED) - set(ITK_MODULE_${module}_DEPENDS "${${module}_DEPENDS}") - endforeach() - - # Set ``ITK_MODULE__DEPENDEES`` variables - foreach(module IN LISTS ITK_MODULES_ENABLED) - ipp_get_module_dependees(${module} ITK_MODULE_${module}_DEPENDEES) - endforeach() - - # Set ``ITK_GROUPS`` variable - file(GLOB group_dirs "${ITK_SOURCE_DIR}/Modules/*") - set(ITK_GROUPS ) - foreach(dir IN LISTS group_dirs) - file(RELATIVE_PATH group "${ITK_SOURCE_DIR}/Modules" "${dir}") - if(NOT IS_DIRECTORY "${dir}" OR "${group}" MATCHES "^External$") - continue() - endif() - list(APPEND ITK_GROUPS ${group}) - endforeach() - message(STATUS "") - message(STATUS "ITK_GROUPS:${ITK_GROUPS}") - - # Set ``ITK_MODULE__GROUP`` variables - foreach(group IN LISTS ITK_GROUPS) - file( GLOB_RECURSE _${group}_module_files ${ITK_SOURCE_DIR}/Modules/${group}/itk-module.cmake ) - foreach( _module_file ${_${group}_module_files} ) - file(READ ${_module_file} _module_file_content) - string( REGEX MATCH "itk_module[ \n]*(\\([ \n]*)([A-Za-z0-9]*)" _module_name ${_module_file_content} ) - set(_module_name ${CMAKE_MATCH_2}) - list( APPEND _${group}_module_list ${_module_name} ) - set(ITK_MODULE_${_module_name}_GROUP ${group}) - endforeach() - endforeach() - - # Initialize ``ITK_WHEEL__MODULES`` variables that will contain list of modules - # to package in each wheel. - foreach(group IN LISTS ITK_WHEEL_GROUPS) - set(ITK_WHEEL_${group}_MODULES "") - endforeach() - - # Configure table display - set(row_widths 40 20 20 10 90 12) - set(row_headers MODULE_NAME MODULE_GROUP WHEEL_GROUP IS_LEAF MODULE_DEPENDEES_GROUPS IS_WRAPPED) - message(STATUS "") - ipp_display_table_row("${row_headers}" "${row_widths}") - - # Update ``ITK_WHEEL__MODULES`` variables - foreach(module IN LISTS ITK_MODULES_ENABLED) - - ipp_is_module_leaf(${module} leaf) - set(dependees_groups) - if(NOT leaf) - set(dependees "") - ipp_recursive_module_dependees(${module} dependees) - foreach(dep IN LISTS dependees) - list(APPEND dependees_groups ${ITK_MODULE_${dep}_GROUP}) - endforeach() - if(dependees_groups) - list(REMOVE_DUPLICATES dependees_groups) - endif() - endif() - - # Filter out group not associated with a wheel - set(dependees_wheel_groups) - foreach(group IN LISTS dependees_groups) - list(FIND ITK_WHEEL_GROUPS ${group} _index) - if(_index EQUAL -1) - continue() - endif() - list(APPEND dependees_wheel_groups ${group}) - endforeach() - - set(wheel_group) - list(LENGTH dependees_wheel_groups _length) - - # Sanity check - if(leaf AND _length GREATER 0) - message(FATAL_ERROR "leaf module should not module depending on them !") - endif() - - if(_length EQUAL 0) - set(wheel_group "${ITK_MODULE_${module}_GROUP}") - elseif(_length EQUAL 1) - # Since packages depending on this module belong to one group, also package this module - set(wheel_group "${dependees_wheel_groups}") - elseif(_length GREATER 1) - # If more than one group is associated with the dependees, package the module in the - # "common ancestor" group. - set(common_ancestor_index 999999) - foreach(g IN LISTS dependees_wheel_groups) - list(FIND ITK_WHEEL_GROUPS ${g} _index) - if(NOT _index EQUAL -1 AND _index LESS common_ancestor_index) - set(common_ancestor_index ${_index}) - endif() - endforeach() - list(GET ITK_WHEEL_GROUPS ${common_ancestor_index} wheel_group) - endif() - - set(wheel_group_display ${wheel_group}) - - # XXX Hard-coded dispatch - if(module STREQUAL "ITKBridgeNumPy") - set(new_wheel_group "Core") - set(wheel_group_display "${new_wheel_group} (was ${wheel_group})") - set(wheel_group ${new_wheel_group}) - endif() - if(module STREQUAL "ITKVTK") - set(new_wheel_group "Core") - set(wheel_group_display "${new_wheel_group} (was ${wheel_group})") - set(wheel_group ${new_wheel_group}) - endif() - - # Associate module with a wheel - list(APPEND ITK_WHEEL_${wheel_group}_MODULES ${module}) - - # Display module info - ipp_is_module_python_wrapped(${module} is_wrapped) - ipp_list_to_string("^^" "${dependees_groups}" dependees_groups_str) - set(row_values "${module};${ITK_MODULE_${module}_GROUP};${wheel_group_display};${leaf};${dependees_groups_str};${is_wrapped}") - ipp_display_table_row("${row_values}" "${row_widths}") - - endforeach() - - # Set list of components to install - set(components "") - foreach(module IN LISTS ITK_WHEEL_${ITK_WHEEL_GROUP}_MODULES) - list(APPEND components ${module}PythonWheelRuntimeLibraries) - endforeach() - - endif() - - if(MSVC AND ITKPythonPackage_WHEEL_NAME STREQUAL "itk-core") - message(STATUS "Adding install rules for compiler runtime libraries") - # Put the runtime libraries next to the "itk/_*.pyd" C-extensions so they - # are found. - set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "itk") - include(InstallRequiredSystemLibraries) - endif() - - #----------------------------------------------------------------------------- - # Install ITK components - message(STATUS "Adding install rules for components:") - foreach(component IN LISTS components) - message(STATUS " ${component}") - install(CODE " -unset(CMAKE_INSTALL_COMPONENT) -set(COMPONENT \"${component}\") -set(CMAKE_INSTALL_DO_STRIP 1) -include(\"${ITK_BINARY_DIR}/cmake_install.cmake\") -unset(CMAKE_INSTALL_COMPONENT) -") - endforeach() - -endif() diff --git a/README.md b/README.md index 052a849a..d28a3666 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This project configures pyproject.toml files and manages environmental variables needed to build ITK Python binary wheels on MacOS, Linux, and Windows platforms. -Scripts are available for both [ITK infrastructure](https://github.com/insightSoftwareConsortium/ITK) and +Scripts are available for both [ITK infrastructure](https://github.com/insightSoftwareConsortium/ITK) and ITK external module Python packages. The Insight Toolkit (ITK) is an open-source, cross-platform system that provides developers diff --git a/cmake/ITKPythonPackage.cmake b/cmake/ITKPythonPackage.cmake deleted file mode 100644 index 4190c4e4..00000000 --- a/cmake/ITKPythonPackage.cmake +++ /dev/null @@ -1,240 +0,0 @@ - -# ipp_ExternalProject_Add_Empty( ) -# -# Add an empty external project -# -function(ipp_ExternalProject_Add_Empty proj depends) - set(depends_args) - if(NOT depends STREQUAL "") - set(depends_args DEPENDS ${depends}) - endif() - ExternalProject_add(${proj} - SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} - DOWNLOAD_COMMAND "" - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - BUILD_IN_SOURCE 1 - BUILD_ALWAYS 1 - INSTALL_COMMAND "" - ${depends_args} - ) -endfunction() - -# ipp_get_module_dependees( ) -# -# Collect all modules depending on ````. -# -function(ipp_get_module_dependees itk-module output_var) - set(dependees "") - foreach(m_enabled IN LISTS ITK_MODULES_ENABLED) - list(FIND ITK_MODULE_${m_enabled}_DEPENDS ${itk-module} _index) - if(NOT _index EQUAL -1) - list(APPEND dependees ${m_enabled}) - endif() - endforeach() - list(REMOVE_DUPLICATES dependees) - set(${output_var} ${dependees} PARENT_SCOPE) -endfunction() - -function(_recursive_deps item-type item-category itk-item output_var) - set(_${itk-item}_deps ) - foreach(dep IN LISTS ITK_${item-type}_${itk-item}_${item-category}) - list(APPEND _${itk-item}_deps ${dep}) - _recursive_deps(${item-type} ${item-category} ${dep} _${itk-item}_deps) - endforeach() - list(APPEND ${output_var} ${_${itk-item}_deps}) - list(REMOVE_DUPLICATES ${output_var}) - set(${output_var} ${${output_var}} PARENT_SCOPE) -endfunction() - -# ipp_recursive_module_dependees( ) -# -# Recursively collect all modules depending on ````. -# -function(ipp_recursive_module_dependees itk-module output_var) - set(_${itk-module}_deps ) - _recursive_deps("MODULE" "DEPENDEES" ${itk-module} ${output_var}) - set(${output_var} ${${output_var}} PARENT_SCOPE) -endfunction() - -# ipp_is_module_leaf( ) -# -# If ```` has no dependencies, set `` to 1 -# otherwise set `` to 0. -# -function(ipp_is_module_leaf itk-module output_var) - set(leaf 1) - foreach(m_enabled IN LISTS ITK_MODULES_ENABLED) - list(FIND ITK_MODULE_${m_enabled}_DEPENDS ${itk-module} _index) - if(NOT _index EQUAL -1) - set(leaf 0) - break() - endif() - endforeach() - set(${output_var} ${leaf} PARENT_SCOPE) -endfunction() - -# ipp_is_module_python_wrapped( ) -# -# If ```` is wrapped in python, set `` to 1 -# otherwise set `` to 0. -# -function(ipp_is_module_python_wrapped itk-module output_var) - set(wrapped 0) - if(NOT DEFINED ITK_MODULE_${itk-module}_GROUP) - message(AUTHOR_WARNING "Variable ITK_MODULE_${itk-module}_GROUP is not defined") - else() - set(group ${ITK_MODULE_${itk-module}_GROUP}) - set(module_folder ${itk-module}) - # if any, strip ITK prefix - if(module_folder MATCHES "^ITK.+$") - string(REGEX REPLACE "^ITK(.+)$" "\\1" module_folder ${module_folder}) - endif() - if(EXISTS ${ITK_SOURCE_DIR}/Modules/${group}/${itk-module}/wrapping/CMakeLists.txt - OR EXISTS ${ITK_SOURCE_DIR}/Modules/${group}/${module_folder}/wrapping/CMakeLists.txt) - set(wrapped 1) - endif() - endif() - set(${output_var} ${wrapped} PARENT_SCOPE) -endfunction() - -# ipp_wheel_to_group( ) -# -# Extract ITK group name from wheel name (e.g 'itk-core' -> 'Core'). -# -# If the group name has less than 3 characters, take the uppercase -# value (e.g 'itk-io' -> 'IO'). -# -function(ipp_wheel_to_group wheel_name group_name_var) - string(REPLACE "itk-" "" _group ${wheel_name}) - string(SUBSTRING ${_group} 0 1 _first) - string(TOUPPER ${_first} _first_uc) - string(SUBSTRING ${_group} 1 -1 _remaining) - set(group_name "${_first_uc}${_remaining}") - # Convert to upper case if length <= 2 - string(LENGTH ${group_name} _length) - if(_length LESS 3) - string(TOUPPER ${group_name} group_name) - endif() - set(${group_name_var} ${group_name} PARENT_SCOPE) -endfunction() - -# ipp_pad_text( ) -# -# Example: -# -# set(row "Apple") -# ipp_pad_text(${row} 20 row) -# -# set(row "${row}Banana") -# ipp_pad_text(${row} 40 row) -# -# set(row "${row}Kiwi") -# ipp_pad_text(${row} 60 row) -# -# message(${row}) -# -# Output: -# -# Apple Banana Kiwi -# -function(ipp_pad_text text text_right_jusitfy_length output_var) - set(fill_char " ") - string(LENGTH "${text}" text_length) - math(EXPR pad_length "${text_right_jusitfy_length} - ${text_length} - 1") - if(pad_length GREATER 0) - string(RANDOM LENGTH ${pad_length} ALPHABET ${fill_char} text_dots) - set(${output_var} "${text} ${text_dots}" PARENT_SCOPE) - else() - set(${output_var} "${text}" PARENT_SCOPE) - endif() -endfunction() - -# ipp_display_table_row( ) -# -# Example: -# -# ipp_display_table_row("Apple^^Banana^^Kiwi" "20;20;20") -# ipp_display_table_row("Eiger^^Rainer^^Sajama" "20;20;20") -# -# Output: -# -# Apple Banana Kiwi -# Eiger Rainer Sajama -# -function(ipp_display_table_row values widths) - list(LENGTH values length) - set(text "") - math(EXPR range "${length} - 1") - foreach(index RANGE ${range}) - list(GET widths ${index} width) - list(GET values ${index} value) - string(REPLACE "^^" ";" value "${value}") - ipp_pad_text("${value}" ${width} value) - set(text "${text}${value}") - endforeach() - message(STATUS "${text}") -endfunction() - -# ipp_list_to_string( ) -# -# Example: -# -# set(values Foo Bar Oof) -# message("${values}") -# ipp_list_to_string("^^" "${values}" values) -# message("${values}") -# -# Output: -# -# Foo;Bar;Oof -# Foo^^Bar^^Oof -# -# Copied from Slicer/CMake/ListToString.cmake -# -function(ipp_list_to_string separator input_list output_string_var) - set(_string "") - # Get list length - list(LENGTH input_list list_length) - # If the list has 0 or 1 element, there is no need to loop over. - if(list_length LESS 2) - set(_string "${input_list}") - else() - math(EXPR last_element_index "${list_length} - 1") - foreach(index RANGE ${last_element_index}) - # Get current item_value - list(GET input_list ${index} item_value) - if(NOT item_value STREQUAL "") - # .. and append non-empty value to output string - set(_string "${_string}${item_value}") - # Append separator if current element is NOT the last one. - if(NOT index EQUAL last_element_index) - set(_string "${_string}${separator}") - endif() - endif() - endforeach() - endif() - set(${output_string_var} ${_string} PARENT_SCOPE) -endfunction() - -# No-op function allowing to shut-up "Manually-specified variables were not used by the project" -# warnings. -function(ipp_unused_vars) -endfunction() - -# -# Unused -# - -function(recursive_module_deps itk-module output_var) - set(_${itk-module}_deps ) - _recursive_deps("MODULE" "DEPENDS" ${itk-module} ${output_var}) - set(${output_var} ${${output_var}} PARENT_SCOPE) -endfunction() - -function(recursive_group_deps itk-group output_var) - set(_${itk-group}_deps ) - _recursive_deps("GROUP" "DEPENDS" ${itk-group} ${output_var}) - set(${output_var} ${${output_var}} PARENT_SCOPE) -endfunction() diff --git a/docs/Quick_start_guide.rst b/docs/Quick_start_guide.rst deleted file mode 100644 index f1ae8205..00000000 --- a/docs/Quick_start_guide.rst +++ /dev/null @@ -1,247 +0,0 @@ -=========================== -Quick start guide -=========================== - -.. _quick-start: - -Installation ------------- - -To install the ITK Python package:: - - $ pip install itk - - -Usage ------ - -Basic example -.............. - -Here is a simple python script that reads an image, applies a median image filter (radius of 2 pixels), and writes the resulting image in a file. - -.. literalinclude:: code/ReadMedianWrite.py - -ITK and NumPy -............. - -A common use case for using ITK in Python is to mingle NumPy and ITK operations on raster data. ITK provides a large number of I/O image formats and several sophisticated image processing algorithms not available in any other packages. The ability to intersperse that with the SciPy ecosystem provides a great tool for rapid prototyping. - -The following script shows how to integrate NumPy and `itk.Image`: - -.. literalinclude:: code/MixingITKAndNumPy.py - :lines: 16-59 - -NumPy and `itk.Mesh`: - -.. literalinclude:: code/MixingITKAndNumPy.py - :lines: 62-76 - -NumPy and `itk.Transform`: - -.. literalinclude:: code/MixingITKAndNumPy.py - :lines: 96-115 - -NumPy and `itk.Matrix`, VNL vectors, and VNL matrices: - -.. literalinclude:: code/MixingITKAndNumPy.py - :lines: 118- - -ITK and Xarray -.............. - -An `itk.Image` can be converted to and from an `xarray.DataArray -`_ while -preserving metadata:: - - da = itk.xarray_from_image(image) - - image = itk.image_from_xarray(da) - -ITK and VTK -............ - -An `itk.Image` can be converted to and from a `vtk.vtkImageData -`_ while -preserving metadata:: - - vtk_image = itk.vtk_image_from_image(image) - - image = itk.image_from_vtk_image(vtk_image) - -ITK and napari -.............. - -An `itk.Image` can be converted to and from a `napari.layers.Image -`_ while -preserving metadata with the `itk-napari-conversion package -`_. - -ITK Python types -................ - -+---------------------+--------------------+--------------------+ -| C++ type | Python type | NumPy dtype | -+=====================+====================+====================+ -| float | itk.F | np.float32 | -+---------------------+--------------------+--------------------+ -| double | itk.D | np.float64 | -+---------------------+--------------------+--------------------+ -| unsigned char | itk.UC | np.uint8 | -+---------------------+--------------------+--------------------+ -| std::complex | itk.complex[itk.F] | np.complex64 | -+---------------------+--------------------+--------------------+ - -This list is not exhaustive and is only presented to illustrate the type names. The complete list of types can be found in the `ITK Software Guide `_. - -Types can also be obtained from their name in the C programming language: - -.. literalinclude:: code/CompareITKTypes.py - :lines: 5 - -To cast the pixel type of an image, use `.astype`: - -.. literalinclude:: code/Cast.py - :lines: 10-18 - -Metadata dictionary -................... - -An `itk.Image` has a metadata dict of `key: value` pairs. - - -The metadata dictionary can be retrieved with:: - - meta_dict = dict(image) - -For example:: - - In [3]: dict(image) - Out[3]: - {'0008|0005': 'ISO IR 100', - '0008|0008': 'ORIGINAL\\PRIMARY\\AXIAL', - '0008|0016': '1.2.840.10008.5.1.4.1.1.2', - '0008|0018': '1.3.12.2.1107.5.8.99.484849.834848.79844848.2001082217554549', - '0008|0020': '20010822', - -Individual dictionary items can be accessed or assigned:: - - print(image['0008|0008']) - - image['origin'] = [4.0, 2.0, 2.0] - -In the Python dictionary interface to image metadata, keys for the spatial -metadata, the *'origin'*, *'spacing'*, and *'direction'*, are reversed in -order from `image.GetOrigin()`, `image.GetSpacing()`, `image.GetDirection()` -to be consistent with the `NumPy array index order -`_ -resulting from pixel buffer array views on the image. - -Access pixel data with NumPy indexing -..................................... - -Array views of an `itk.Image` provide a way to set and get pixel values with NumPy indexing syntax, e.g.:: - - In [6]: image[0,:2,4] = [5,5] - - In [7]: image[0,:4,4:6] - Out[7]: - NDArrayITKBase([[ 5, -997], - [ 5, -1003], - [ -993, -999], - [ -996, -994]], dtype=int16) - -Input/Output (IO) -................. - -Convenient functions are provided read and write from ITK's many supported -file formats:: - - image = itk.imread('image.tif') - - # Read in with a specific pixel type. - image = itk.imread('image.tif', itk.F) - - # Read in an image series. - # Pass a sorted list of files. - image = itk.imread(['image1.png', 'image2.png', 'image3.png']) - - # Read in a volume from a DICOM series. - # Pass a directory. - # Only a single series, sorted spatially, will be returned. - image = itk.imread('/a/dicom/directory/') - - # Write an image. - itk.imwrite(image, 'image.tif') - - - # Read a mesh. - mesh = itk.meshread('mesh.vtk') - - # Write a mesh. - itk.meshwrite(mesh, 'mesh.vtk') - - - # Read a spatial transform. - transform = itk.transformread('transform.h5') - - # Write a spatial transform. - itk.transformwrite(transform, 'transform.h5') - -Image filters and Image-like inputs and outputs -............................................... - -All `itk` functional image filters operate on an `itk.Image` but also: - -- `xarray.DataArray `_ * -- `numpy.ndarray `_ -- `dask.array.Array `_ - -\* Preserves image metadata - -Filter parameters -................. - -ITK filter parameters can be specified in the following ways: - -.. literalinclude:: code/FilterParameters.py - :lines: 10- - -Filter types -............ - -In `itk`, filters are optimized at compile time for each image pixel type and -image dimension. There are two ways to instantiate these filters with the `itk` -Python wrapping: - -- *Implicit (recommended)*: Type information is automatically detected from the data. Typed filter objects and images are implicitly created. - -.. literalinclude:: code/ImplicitInstantiation.py - :lines: 8- - -- *Explicit*: This can be useful if an appropriate type cannot be determined implicitly or when a different filter type than the default is desired. - -To specify the type of the filter, use the `ttype` keyword argument. Explicit instantiation of a median image filter: - -.. literalinclude:: code/ExplicitInstantiation.py - :lines: 8- - -Instantiate an ITK object -......................... - -There are two types of ITK objects. Most ITK objects, such as images, filters, or adapters, are instantiated the following way: - -.. literalinclude:: code/InstantiateITKObjects.py - :lines: 6-8 - -Some objects, like a Matrix, Vector, or RGBPixel, do not require the attribute `.New()` to be added to instantiate them: - -.. literalinclude:: code/InstantiateITKObjects.py - :lines: 11 - -In case of doubt, look at the attributes of the object you are trying to instantiate. - -Examples --------- - -Examples can be found in the `ITKSphinxExamples project `_. diff --git a/itkVersion.py b/itkVersion.py deleted file mode 100644 index acf9bffb..00000000 --- a/itkVersion.py +++ /dev/null @@ -1,31 +0,0 @@ -from packaging.version import Version - -# Version needs to be python PEP 440 compliant (no leading v) -VERSION = '6.0b2'.removeprefix("v") - -def get_versions(): - """Returns versions for the ITK Python package. - - from itkVersion import get_versions - - # Returns the ITK repository version - get_versions()['version'] - - # Returns the package version. Since GitHub Releases do not support the '+' - # character in file names, this does not contain the local version - # identifier in nightly builds, i.e. - # - # '4.11.0.dev20170208' - # - # instead of - # - # '4.11.0.dev20170208+139.g922f2d9' - get_versions()['package-version'] - """ - - Version(VERSION) # Raise InvalidVersion exception if not PEP 440 compliant - - versions = {} - versions['version'] = VERSION - versions['package-version'] = VERSION.split('+')[0] - return versions diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 5fac7875..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -ninja==1.11.1.1 -scikit-build-core==0.10.7 -build==1.2.1 -pyproject-metadata -pathspec -setuptools_scm==8.1.0 diff --git a/scripts/dockcross-manylinux-build-module-deps.sh b/scripts/dockcross-manylinux-build-module-deps.sh deleted file mode 100755 index 0f9342d3..00000000 --- a/scripts/dockcross-manylinux-build-module-deps.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash - -######################################################################## -# Run this script in an ITK external module directory to generate -# build artifacts for prerequisite ITK external modules. -# -# Module dependencies are built in a flat directory structure regardless -# of recursive dependencies. Prerequisite sources are required to be passed -# in the order in which they should be built. -# For example, if ITKTargetModule depends on ITKTargetModuleDep2 which -# depends on ITKTargetModuleDep1, the output directory structure -# will look like this: -# -# / ITKTargetModule -# -- / ITKTargetModuleDep1 -# -- / ITKTargetModuleDep2 -# .. -# -# =========================================== -# ENVIRONMENT VARIABLES -# -# - `ITK_MODULE_PREQ`: Prerequisite ITK modules that must be built before the requested module. -# Format is `/@:/@:...`. -# For instance, `export ITK_MODULE_PREQ=InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` -# -######################################################################## - -# Initialize variables - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -if [[ ! -f "${script_dir}/dockcross-manylinux-download-cache-and-build-module-wheels.sh" ]]; then - echo "Could not find download script to use for building module dependencies!" - exit 1 -fi - -source "${script_dir}/dockcross-manylinux-set-vars.sh" - -# Temporarily update prerequisite environment variable to prevent infinite recursion. -ITK_MODULE_PREQ_TOPLEVEL=${ITK_MODULE_PREQ} -ITK_MODULE_NO_CLEANUP_TOPLEVEL=${ITK_MODULE_NO_CLEANUP} -export ITK_MODULE_PREQ="" -export ITK_MODULE_NO_CLEANUP="ON" - -######################################################################## -# Build ITK module dependencies - -for MODULE_INFO in ${ITK_MODULE_PREQ_TOPLEVEL//:/ }; do - MODULE_ORG=`(echo ${MODULE_INFO} | cut -d'/' -f 1)` - MODULE_NAME=`(echo ${MODULE_INFO} | cut -d'@' -f 1 | cut -d'/' -f 2)` - MODULE_TAG=`(echo ${MODULE_INFO} | cut -d'@' -f 2)` - - MODULE_UPSTREAM=https://github.com/${MODULE_ORG}/${MODULE_NAME}.git - echo "Cloning from ${MODULE_UPSTREAM}" - git clone ${MODULE_UPSTREAM} - - # Reuse cached build archive instead of redownloading. - # Build archives are usually ~2GB so it is reasonable to move - # instead of redownloading. - if [[ `(compgen -G ./ITKPythonBuilds-linux*.tar.zst)` ]]; then - mv ITKPythonBuilds-linux*.tar.zst ${MODULE_NAME}/ - fi - - pushd ${MODULE_NAME} - git checkout ${MODULE_TAG} - cp ../dockcross-manylinux-download-cache-and-build-module-wheels.sh . - if [[ -d ../ITKPythonPackage ]]; then - ln -s ../ITKPythonPackage - ln -s ./ITKPythonPackage/oneTBB-prefix - # Cache has already been downloaded, just build - ./ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh "$@" - else - # No cache available, download it and build - echo "Building module dependency ${MODULE_NAME}" - ./dockcross-manylinux-download-cache-and-build-module-wheels.sh "$@" - fi - popd - - echo "Cleaning up module dependency" - cp ./${MODULE_NAME}/include/* include/ - find ${MODULE_NAME}/wrapping -name '*.in' -print -exec cp {} wrapping \; - find ${MODULE_NAME}/wrapping -name '*.init' -print -exec cp {} wrapping \; - find ${MODULE_NAME}/*build/*/include -type f -print -exec cp {} include \; - - # Cache build archive - if [[ `(compgen -G ./ITKPythonBuilds-linux*.tar.zst)` ]]; then - rm -f ./${MODULE_NAME}/ITKPythonBuilds-linux*.tar.zst - else - mv ./${MODULE_NAME}/ITKPythonBuilds-linux*.tar.zst . - fi - - # Cache ITKPythonPackage build scripts - if [[ ! -d ./ITKPythonPackage ]]; then - mv ./${MODULE_NAME}/ITKPythonPackage . - ln -s ./ITKPythonPackage/oneTBB-prefix . - fi - -done - -# Restore environment variable -export ITK_MODULE_PREQ=${ITK_MODULE_PREQ_TOPLEVEL} -ITK_MODULE_PREQ_TOPLEVEL="" -export ITK_MODULE_NO_CLEANUP=${ITK_MODULE_NO_CLEANUP_TOPLEVEL} -ITK_MODULE_NO_CLEANUP_TOPLEVEL="" - -# Summarize disk usage for debugging -du -sh ./* | sort -hr | head -n 20 - -echo "Done building ITK external module dependencies" diff --git a/scripts/dockcross-manylinux-build-tarball.sh b/scripts/dockcross-manylinux-build-tarball.sh deleted file mode 100755 index ca29e75c..00000000 --- a/scripts/dockcross-manylinux-build-tarball.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -# This script creates a tarball of the ITK Python package build tree. It is -# downloaded by the external module build scripts and used to build their -# Python package on GitHub CI services. - -# ----------------------------------------------------------------------- - -zstd_exe=`(which zstd)` - -# Find an appropriately versioned zstd. -# -# "--long" is introduced in zstd==v1.3.2 -# https://github.com/facebook/zstd/releases/tag/v1.3.2 -# -# Sample --version output: -# *** zstd command line interface 64-bits v1.4.4, by Yann Collet *** # -ZSTD_MIN_VERSION="1.3.2" - -if [[ -n `(which dpkg)` && `(${zstd_exe} --version)` =~ v([0-9]+.[0-9]+.[0-9]+) ]]; then - if $(dpkg --compare-versions ${BASH_REMATCH[1]} "ge" ${ZSTD_MIN_VERSION} ); then - echo "Found zstd v${BASH_REMATCH[1]} at ${zstd_exe}" - else - echo "Expected zstd v${ZSTD_MIN_VERSION} or higher but found v${BASH_REMATCH[1]} at ${zstd_exe}" - exit 255 - fi -else - # dpkg not available for version comparison so simply print version - ${zstd_exe} --version -fi - -# ----------------------------------------------------------------------- - -tar -cf ITKPythonBuilds-linux.tar \ - ITKPythonPackage/ITK-* \ - ITKPythonPackage/oneTBB* \ - ITKPythonPackage/requirements-dev.txt \ - ITKPythonPackage/scripts -$zstd_exe -f \ - -10 \ - -T6 \ - --long=31 \ - ./ITKPythonBuilds-linux.tar \ - -o ./ITKPythonBuilds-linux.tar.zst diff --git a/scripts/dockcross-manylinux-cleanup.sh b/scripts/dockcross-manylinux-cleanup.sh deleted file mode 100755 index 52702518..00000000 --- a/scripts/dockcross-manylinux-cleanup.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -######################################################################## -# Run this script in an ITK external module directory to clean up -# Linux Python build artifacts. -# -# Typically required for building multiple types of module wheels in the same -# directory, such as using different toolsets or targeting different -# architectures. -# -# =========================================== -# ENVIRONMENT VARIABLES -# -# - `ITK_MODULE_PREQ`: Prerequisite ITK modules that must be built before the requested module. -# Format is `/@:/@:...`. -# For instance, `export ITK_MODULE_PREQ=InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` -# -# - `NO_SUDO`: Disable the use of superuser permissions for removing directories. -# `sudo` is required by default for cleanup on Github Actions runners. -# -######################################################################## - -echo "Cleaning up artifacts from module build" - -# ARM platform observed to require sudo for removing ITKPythonPackage sources -rm_prefix="" -if [[ ! ${NO_SUDO} ]]; then - rm_prefix="sudo " -fi - -unlink oneTBB-prefix -${rm_prefix} rm -rf ITKPythonPackage/ -${rm_prefix} rm -rf tools/ -${rm_prefix} rm -rf _skbuild/ build/ -${rm_prefix} rm -rf ./*.egg-info/ -${rm_prefix} rm -rf ./ITK-*-manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}/ -${rm_prefix} rm -rf ./ITKPythonBuilds-linux-manylinux*${MANYLINUX_VERSION}*.tar.zst - -if [[ -n ${ITK_MODULE_PREQ} ]]; then - for MODULE_INFO in ${ITK_MODULE_PREQ//:/ }; do - MODULE_NAME=`(echo ${MODULE_INFO} | cut -d'@' -f 1 | cut -d'/' -f 2)` - ${rm_prefix} rm -rf ${MODULE_NAME}/ - done -fi - -# Leave dist/ and download scripts intact diff --git a/scripts/dockcross-manylinux-set-vars.sh b/scripts/dockcross-manylinux-set-vars.sh deleted file mode 100755 index f39ce7ab..00000000 --- a/scripts/dockcross-manylinux-set-vars.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -######################################################################## -# Run this script to set common enviroment variables used in building the -# ITK Python wheel packages for Linux. -# -# ENVIRONMENT VARIABLES -# These environment variables will be populated by the script when invoked with `source` -# if their value is not set with `export` before invocation. -# For example, -# -# export ITK_PACKAGE_VERSION=v5.4.0 -# scripts/dockcross-manylinux-set-vars.sh cp310 -# -######################################################################## - -######################################################################## -# ITKPythonBuilds parameters - -# ITKPythonBuilds archive tag to use for ITK build artifacts. -# See https://github.com/insightSoftwareConsortium/ITKpythonbuilds for available tags. -ITK_PACKAGE_VERSION=${ITK_PACKAGE_VERSION:=v6.0b01} - -# Github organization for fetching ITKPythonPackage build scripts -ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium} - -# ITKPythonPackage tag for fetching build scripts -ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG:=main} - -######################################################################## -# Docker image parameters - -# Specialized manylinux image to use for building. Default is _2_28. -# See https://github.com/dockcross/dockcross for available versions and tags. -MANYLINUX_VERSION=${MANYLINUX_VERSION:=_2_28} - -# Target platform architecture (x64, aarch64) -TARGET_ARCH=${TARGET_ARCH:=x64} - -# Specialized manylinux image tag to use for building. -if [[ ${MANYLINUX_VERSION} == _2_28 && ${TARGET_ARCH} == x64 ]]; then - IMAGE_TAG=${IMAGE_TAG:=20260203-3dfb3ff} -elif [[ ${MANYLINUX_VERSION} == _2_28 && ${TARGET_ARCH} == aarch64 ]]; then - IMAGE_TAG=${IMAGE_TAG:=2025.08.12-1} -elif [[ ${MANYLINUX_VERSION} == 2014 ]]; then - IMAGE_TAG=${IMAGE_TAG:=20240304-9e57d2b} -else - echo "Unknown manylinux version ${MANYLINUX_VERSION}" - exit 1; -fi - -# Set container for requested version/arch/tag. -if [[ ${TARGET_ARCH} == x64 ]]; then - MANYLINUX_IMAGE_NAME=${MANYLINUX_IMAGE_NAME:="manylinux${MANYLINUX_VERSION}-${TARGET_ARCH}:${IMAGE_TAG}"} - CONTAINER_SOURCE="docker.io/dockcross/${MANYLINUX_IMAGE_NAME}" -elif [[ ${TARGET_ARCH} == aarch64 ]]; then - MANYLINUX_IMAGE_NAME=${MANYLINUX_IMAGE_NAME:="manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}:${IMAGE_TAG}"} - CONTAINER_SOURCE="quay.io/pypa/${MANYLINUX_IMAGE_NAME}" -else - echo "Unknown target architecture ${TARGET_ARCH}" - exit 1; -fi diff --git a/scripts/internal/Support.cmake b/scripts/internal/Support.cmake deleted file mode 100644 index 84487a32..00000000 --- a/scripts/internal/Support.cmake +++ /dev/null @@ -1,2749 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -# -# This file is a "template" file used by various FindPython modules. -# - -cmake_policy (GET CMP0094 _${_PYTHON_PREFIX}_LOOKUP_POLICY) - -cmake_policy (VERSION 3.7) - -if (_${_PYTHON_PREFIX}_LOOKUP_POLICY) - cmake_policy (SET CMP0094 ${_${_PYTHON_PREFIX}_LOOKUP_POLICY}) -endif() - -# -# Initial configuration -# -if (NOT DEFINED _PYTHON_PREFIX) - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() -if (NOT DEFINED _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() -if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL "3") - set(_${_PYTHON_PREFIX}_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) -elseif (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL "2") - set(_${_PYTHON_PREFIX}_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) -else() - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() - -get_property(_${_PYTHON_PREFIX}_CMAKE_ROLE GLOBAL PROPERTY CMAKE_ROLE) - - -# -# helper commands -# -macro (_PYTHON_DISPLAY_FAILURE _PYTHON_MSG) - if (${_PYTHON_PREFIX}_FIND_REQUIRED) - message (FATAL_ERROR "${_PYTHON_MSG}") - else() - if (NOT ${_PYTHON_PREFIX}_FIND_QUIETLY) - message(STATUS "${_PYTHON_MSG}") - endif () - endif() - - set (${_PYTHON_PREFIX}_FOUND FALSE) - string (TOUPPER "${_PYTHON_PREFIX}" _${_PYTHON_PREFIX}_UPPER_PREFIX) - set (${_PYTHON_UPPER_PREFIX}_FOUND FALSE) - return() -endmacro() - - -function (_PYTHON_MARK_AS_INTERNAL) - foreach (var IN LISTS ARGV) - if (DEFINED CACHE{${var}}) - set_property (CACHE ${var} PROPERTY TYPE INTERNAL) - endif() - endforeach() -endfunction() - - -macro (_PYTHON_SELECT_LIBRARY_CONFIGURATIONS _PYTHON_BASENAME) - if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_RELEASE) - set(${_PYTHON_BASENAME}_LIBRARY_RELEASE "${_PYTHON_BASENAME}_LIBRARY_RELEASE-NOTFOUND") - endif() - if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_DEBUG) - set(${_PYTHON_BASENAME}_LIBRARY_DEBUG "${_PYTHON_BASENAME}_LIBRARY_DEBUG-NOTFOUND") - endif() - - get_property(_PYTHON_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if (${_PYTHON_BASENAME}_LIBRARY_DEBUG AND ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND - NOT ${_PYTHON_BASENAME}_LIBRARY_DEBUG STREQUAL ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND - (_PYTHON_isMultiConfig OR CMAKE_BUILD_TYPE)) - # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for - # single-config generators, set optimized and debug libraries - set (${_PYTHON_BASENAME}_LIBRARIES "") - foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_RELEASE) - list( APPEND ${_PYTHON_BASENAME}_LIBRARIES optimized "${_PYTHON_libname}") - endforeach() - foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_DEBUG) - list( APPEND ${_PYTHON_BASENAME}_LIBRARIES debug "${_PYTHON_libname}") - endforeach() - elseif (${_PYTHON_BASENAME}_LIBRARY_RELEASE) - set (${_PYTHON_BASENAME}_LIBRARIES "${${_PYTHON_BASENAME}_LIBRARY_RELEASE}") - elseif (${_PYTHON_BASENAME}_LIBRARY_DEBUG) - set (${_PYTHON_BASENAME}_LIBRARIES "${${_PYTHON_BASENAME}_LIBRARY_DEBUG}") - else() - set (${_PYTHON_BASENAME}_LIBRARIES "${_PYTHON_BASENAME}_LIBRARY-NOTFOUND") - endif() -endmacro() - - -macro (_PYTHON_FIND_FRAMEWORKS) - set (${_PYTHON_PREFIX}_FRAMEWORKS) - if (CMAKE_HOST_APPLE OR APPLE) - file(TO_CMAKE_PATH "$ENV{CMAKE_FRAMEWORK_PATH}" _pff_CMAKE_FRAMEWORK_PATH) - set (_pff_frameworks ${CMAKE_FRAMEWORK_PATH} - ${_pff_CMAKE_FRAMEWORK_PATH} - ~/Library/Frameworks - /usr/local/Frameworks - ${CMAKE_SYSTEM_FRAMEWORK_PATH}) - list (REMOVE_DUPLICATES _pff_frameworks) - foreach (_pff_framework IN LISTS _pff_frameworks) - if (EXISTS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) - list (APPEND ${_PYTHON_PREFIX}_FRAMEWORKS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) - endif() - if (EXISTS ${_pff_framework}/Python.framework) - list (APPEND ${_PYTHON_PREFIX}_FRAMEWORKS ${_pff_framework}/Python.framework) - endif() - endforeach() - unset (_pff_frameworks) - unset (_pff_framework) - endif() -endmacro() - -function (_PYTHON_GET_FRAMEWORKS _PYTHON_PGF_FRAMEWORK_PATHS _PYTHON_VERSION) - set (_PYTHON_FRAMEWORK_PATHS) - foreach (_PYTHON_FRAMEWORK IN LISTS ${_PYTHON_PREFIX}_FRAMEWORKS) - list (APPEND _PYTHON_FRAMEWORK_PATHS - "${_PYTHON_FRAMEWORK}/Versions/${_PYTHON_VERSION}") - endforeach() - set (${_PYTHON_PGF_FRAMEWORK_PATHS} ${_PYTHON_FRAMEWORK_PATHS} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_REGISTRIES _PYTHON_PGR_REGISTRY_PATHS _PYTHON_VERSION) - string (REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PYTHON_VERSION}) - set (${_PYTHON_PGR_REGISTRY_PATHS} - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - PARENT_SCOPE) -endfunction() - - -function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS) - set (abiflags) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 0 pydebug) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 1 pymalloc) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 2 unicode) - - if (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ANY") - set (abiflags "mu" "m" "u" "") - elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ON") - set (abiflags "mu" "u") - elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "OFF") - set (abiflags "m" "") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") - set (abiflags "mu" "m") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ON") - set (abiflags "mu") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "OFF") - set (abiflags "m") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") - set (abiflags "u" "") - elseif (pymalloc STREQUAL "OFF" AND unicode STREQUAL "ON") - set (abiflags "u") - endif() - - if (pydebug STREQUAL "ON") - if (abiflags) - list (TRANSFORM abiflags PREPEND "d") - else() - set (abiflags "d") - endif() - elseif (pydebug STREQUAL "ANY") - if (abiflags) - set (flags "${abiflags}") - list (TRANSFORM flags PREPEND "d") - list (APPEND abiflags "${flags}") - else() - set (abiflags "" "d") - endif() - endif() - - set (${_PGABIFLAGS} "${abiflags}" PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_PATH_SUFFIXES _PYTHON_PGPS_PATH_SUFFIXES) - cmake_parse_arguments (PARSE_ARGV 1 _PGPS "LIBRARY;INCLUDE" "VERSION" "") - - if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) - set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") - else() - set (abi "mu" "m" "u" "") - endif() - - set (path_suffixes) - if (_PGPS_LIBRARY) - if (CMAKE_LIBRARY_ARCHITECTURE) - list (APPEND path_suffixes lib/${CMAKE_LIBRARY_ARCHITECTURE}) - endif() - list (APPEND path_suffixes lib libs) - - if (CMAKE_LIBRARY_ARCHITECTURE) - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - list (TRANSFORM suffixes APPEND "-${CMAKE_LIBRARY_ARCHITECTURE}") - else() - set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}-${CMAKE_LIBRARY_ARCHITECTURE}") - endif() - list (APPEND path_suffixes ${suffixes}) - endif() - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - else() - set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - endif() - list (APPEND path_suffixes ${suffixes}) - elseif (_PGPS_INCLUDE) - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "include/python${_PGPS_VERSION}") - else() - set (suffixes "include/python${_PGPS_VERSION}") - endif() - list (APPEND path_suffixes ${suffixes} include) - endif() - - set (${_PYTHON_PGPS_PATH_SUFFIXES} ${path_suffixes} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_NAMES _PYTHON_PGN_NAMES) - cmake_parse_arguments (PARSE_ARGV 1 _PGN "POSIX;EXECUTABLE;CONFIG;LIBRARY;WIN32;DEBUG" "VERSION" "") - - set (names) - - if (_PGN_WIN32) - string (REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PGN_VERSION}) - - set (name python${_PYTHON_VERSION_NO_DOTS}) - if (_PGN_DEBUG) - string (APPEND name "_d") - endif() - - list (APPEND names "${name}") - endif() - - if (_PGN_POSIX) - if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) - set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") - else() - if (_PGN_EXECUTABLE OR _PGN_CONFIG) - set (abi "") - else() - set (abi "mu" "m" "u" "") - endif() - endif() - - if (abi) - if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - set (abinames "${abi}") - list (TRANSFORM abinames PREPEND "${CMAKE_LIBRARY_ARCHITECTURE}-python${_PGN_VERSION}") - list (TRANSFORM abinames APPEND "-config") - list (APPEND names ${abinames}) - endif() - set (abinames "${abi}") - list (TRANSFORM abinames PREPEND "python${_PGN_VERSION}") - if (_PGN_CONFIG) - list (TRANSFORM abinames APPEND "-config") - endif() - list (APPEND names ${abinames}) - else() - if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - set (abinames "${CMAKE_LIBRARY_ARCHITECTURE}-python${_PGN_VERSION}") - endif() - list (APPEND abinames "python${_PGN_VERSION}") - if (_PGN_CONFIG) - list (TRANSFORM abinames APPEND "-config") - endif() - list (APPEND names ${abinames}) - endif() - endif() - - set (${_PYTHON_PGN_NAMES} ${names} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME) - unset (${_PYTHON_PGCV_VALUE} PARENT_SCOPE) - - if (NOT NAME MATCHES "^(PREFIX|ABIFLAGS|CONFIGDIR|INCLUDES|LIBS|SOABI)$") - return() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - if (NAME STREQUAL "SOABI") - set (config_flag "--extension-suffix") - else() - set (config_flag "--${NAME}") - endif() - string (TOLOWER "${config_flag}" config_flag) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" ${config_flag} - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - if (NAME STREQUAL "INCLUDES") - # do some clean-up - string (REGEX MATCHALL "(-I|-iwithsysroot)[ ]*[^ ]+" _values "${_values}") - string (REGEX REPLACE "(-I|-iwithsysroot)[ ]*" "" _values "${_values}") - list (REMOVE_DUPLICATES _values) - elseif (NAME STREQUAL "SOABI") - # clean-up: remove prefix character and suffix - string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") - endif() - endif() - endif() - - if (_${_PYTHON_PREFIX}_EXECUTABLE AND NOT CMAKE_CROSSCOMPILING) - if (NAME STREQUAL "PREFIX") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.PREFIX,sysconfig.EXEC_PREFIX,sysconfig.BASE_EXEC_PREFIX]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_config_var('base') or '', sysconfig.get_config_var('installed_base') or '']))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - list (REMOVE_DUPLICATES _values) - endif() - elseif (NAME STREQUAL "INCLUDES") - if (WIN32) - set (_scheme "nt") - else() - set (_scheme "posix_prefix") - endif() - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_python_inc(plat_specific=True),sysconfig.get_python_inc(plat_specific=False)]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_path('platinclude'),sysconfig.get_path('platinclude','${_scheme}'),sysconfig.get_path('include'),sysconfig.get_path('include','${_scheme}')]))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - list (REMOVE_DUPLICATES _values) - endif() - elseif (NAME STREQUAL "SOABI") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_config_var('SOABI') or '',sysconfig.get_config_var('EXT_SUFFIX') or '']))\nexcept Exception:\n import sysconfig;sys.stdout.write(';'.join([sysconfig.get_config_var('SOABI') or '',sysconfig.get_config_var('EXT_SUFFIX') or '']))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _soabi - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - foreach (_item IN LISTS _soabi) - if (_item) - set (_values "${_item}") - break() - endif() - endforeach() - if (_values) - # clean-up: remove prefix character and suffix - string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") - endif() - endif() - else() - set (config_flag "${NAME}") - if (NAME STREQUAL "CONFIGDIR") - set (config_flag "LIBPL") - endif() - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(sysconfig.get_config_var('${config_flag}'))\nexcept Exception:\n import sysconfig\n sys.stdout.write(sysconfig.get_config_var('${config_flag}'))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - endif() - endif() - endif() - - if (config_flag STREQUAL "ABIFLAGS") - set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) - return() - endif() - - if (NOT _values OR _values STREQUAL "None") - return() - endif() - - if (NAME STREQUAL "LIBS") - # do some clean-up - string (REGEX MATCHALL "-(l|framework)[ ]*[^ ]+" _values "${_values}") - # remove elements relative to python library itself - list (FILTER _values EXCLUDE REGEX "-lpython") - list (REMOVE_DUPLICATES _values) - endif() - - if (WIN32 AND NAME MATCHES "^(PREFIX|CONFIGDIR|INCLUDES)$") - file (TO_CMAKE_PATH "${_values}" _values) - endif() - - set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_VERSION) - cmake_parse_arguments (PARSE_ARGV 0 _PGV "LIBRARY;INCLUDE" "PREFIX" "") - - unset (${_PGV_PREFIX}VERSION PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_MAJOR PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_MINOR PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_PATCH PARENT_SCOPE) - unset (${_PGV_PREFIX}ABI PARENT_SCOPE) - - if (_PGV_LIBRARY) - # retrieve version and abi from library name - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # extract version from library name - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "python([23])([0-9]+)") - set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) - elseif (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "python([23])\\.([0-9]+)([dmu]*)") - set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}ABI "${CMAKE_MATCH_3}" PARENT_SCOPE) - endif() - endif() - else() - if (_${_PYTHON_PREFIX}_INCLUDE_DIR) - # retrieve version from header file - file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/patchlevel.h" version - REGEX "^#define[ \t]+PY_VERSION[ \t]+\"[^\"]+\"") - string (REGEX REPLACE "^#define[ \t]+PY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" - version "${version}") - string (REGEX MATCHALL "[0-9]+" versions "${version}") - list (GET versions 0 version_major) - list (GET versions 1 version_minor) - list (GET versions 2 version_patch) - - set (${_PGV_PREFIX}VERSION "${version_major}.${version_minor}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MAJOR ${version_major} PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR ${version_minor} PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_PATCH ${version_patch} PARENT_SCOPE) - - # compute ABI flags - if (version_major VERSION_GREATER "2") - file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/pyconfig.h" config REGEX "(Py_DEBUG|WITH_PYMALLOC|Py_UNICODE_SIZE|MS_WIN32)") - set (abi) - if (config MATCHES "#[ ]*define[ ]+MS_WIN32") - # ABI not used on Windows - set (abi "") - else() - if (config MATCHES "#[ ]*define[ ]+Py_DEBUG[ ]+1") - string (APPEND abi "d") - endif() - if (config MATCHES "#[ ]*define[ ]+WITH_PYMALLOC[ ]+1") - string (APPEND abi "m") - endif() - if (config MATCHES "#[ ]*define[ ]+Py_UNICODE_SIZE[ ]+4") - string (APPEND abi "u") - endif() - set (${_PGV_PREFIX}ABI "${abi}" PARENT_SCOPE) - endif() - else() - # ABI not supported - set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - - -function (_PYTHON_VALIDATE_INTERPRETER) - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVI "EXACT;CHECK_EXISTS" "" "") - if (_PVI_UNPARSED_ARGUMENTS) - set (expected_version ${_PVI_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVI_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_EXECUTABLE}") - # interpreter does not exist anymore - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot find the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - - # validate ABI compatibility - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write(sys.abiflags)" - RESULT_VARIABLE result - OUTPUT_VARIABLE abi - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result) - # assume ABI is not supported - set (abi "") - endif() - if (NOT abi IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong ABI for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() - - get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME) - - if (expected_version AND NOT python_name STREQUAL "python${expected_version}${abi}${CMAKE_EXECUTABLE_SUFFIX}") - # executable found must have a specific version - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result) - # interpreter is not usable - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - else() - if (_PVI_EXACT AND NOT version VERSION_EQUAL expected_version) - # interpreter has wrong version - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - else() - # check that version is OK - string(REGEX REPLACE "^([0-9]+)\\..*$" "\\1" major_version "${version}") - string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${expected_version}") - if (NOT major_version VERSION_EQUAL expected_major_version - OR NOT version VERSION_GREATER_EQUAL expected_version) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - endif() - endif() - endif() - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - return() - endif() - else() - if (NOT python_name STREQUAL "python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}${CMAKE_EXECUTABLE_SUFFIX}") - # executable found do not have version in name - # ensure major version is OK - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write(str(sys.version_info[0]))" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result OR NOT version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # interpreter not usable or has wrong major version - if (result) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - else() - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong major version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() - endif() - - if (CMAKE_SIZEOF_VOID_P AND ("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - AND NOT CMAKE_CROSSCOMPILING) - # In this case, interpreter must have same architecture as environment - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys, struct; sys.stdout.write(str(struct.calcsize(\"P\")))" - RESULT_VARIABLE result - OUTPUT_VARIABLE size - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result OR NOT size EQUAL CMAKE_SIZEOF_VOID_P) - # interpreter not usable or has wrong architecture - if (result) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - else() - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong architecture for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() -endfunction() - - -function (_PYTHON_VALIDATE_COMPILER expected_version) - if (NOT _${_PYTHON_PREFIX}_COMPILER) - return() - endif() - - cmake_parse_arguments (_PVC "EXACT;CHECK_EXISTS" "" "" ${ARGN}) - if (_PVC_UNPARSED_ARGUMENTS) - set (major_version FALSE) - set (expected_version ${_PVC_UNPARSED_ARGUMENTS}) - else() - set (major_version TRUE) - set (expected_version ${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}) - set (_PVC_EXACT TRUE) - endif() - - if (_PVC_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_COMPILER}") - # Compiler does not exist anymore - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot find the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") - return() - endif() - - # retrieve python environment version from compiler - set (working_dir "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") - if (major_version) - # check only major version - file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write(str(sys.version_info[0]))") - else() - file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))\n") - endif() - execute_process (COMMAND "${_${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${working_dir}/version.py" - WORKING_DIRECTORY "${working_dir}" - OUTPUT_QUIET - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process (COMMAND "${working_dir}/version" - WORKING_DIRECTORY "${working_dir}" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET) - file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") - - if (result OR (_PVC_EXACT AND NOT version VERSION_EQUAL expected_version) OR (version VERSION_LESS expected_version)) - # Compiler not usable or has wrong version - if (result) - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot use the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - else() - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") - endif() -endfunction() - - -function (_PYTHON_VALIDATE_LIBRARY) - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVL "EXACT;CHECK_EXISTS" "" "") - if (_PVL_UNPARSED_ARGUMENTS) - set (expected_version ${_PVL_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVL_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - # library does not exist anymore - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - if (WIN32) - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - return() - endif() - - # retrieve version and abi from library name - _python_get_version (LIBRARY PREFIX lib_) - - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT lib_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - else() - if (expected_version) - if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL expected_version) OR (lib_VERSION VERSION_LESS expected_version)) - # library has wrong version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - else() - if (NOT lib_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # library has wrong major version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - if (WIN32) - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") - endif() - unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE CACHE) - unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG CACHE) - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() -endfunction() - - -function (_PYTHON_VALIDATE_INCLUDE_DIR) - if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVID "EXACT;CHECK_EXISTS" "" "") - if (_PVID_UNPARSED_ARGUMENTS) - set (expected_version ${_PVID_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVID_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - # include file does not exist anymore - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - return() - endif() - - # retrieve version from header file - _python_get_version (INCLUDE PREFIX inc_) - - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT inc_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - else() - if (expected_version) - if ((_PVID_EXACT AND NOT inc_VERSION VERSION_EQUAL expected_version) OR (inc_VERSION VERSION_LESS expected_version)) - # include dir has wrong version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - else() - if (NOT inc_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # include dir has wrong major version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - endif() - endif() -endfunction() - - -function (_PYTHON_FIND_RUNTIME_LIBRARY _PYTHON_LIB) - string (REPLACE "_RUNTIME" "" _PYTHON_LIB "${_PYTHON_LIB}") - # look at runtime part on systems supporting it - if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR - (CMAKE_SYSTEM_NAME MATCHES "MSYS|CYGWIN" - AND ${_PYTHON_LIB} MATCHES "${CMAKE_IMPORT_LIBRARY_SUFFIX}$")) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX}) - # MSYS has a special syntax for runtime libraries - if (CMAKE_SYSTEM_NAME MATCHES "MSYS") - list (APPEND CMAKE_FIND_LIBRARY_PREFIXES "msys-") - endif() - find_library (${ARGV}) - endif() -endfunction() - - -function (_PYTHON_SET_LIBRARY_DIRS _PYTHON_SLD_RESULT) - unset (_PYTHON_DIRS) - set (_PYTHON_LIBS ${ARGN}) - foreach (_PYTHON_LIB IN LISTS _PYTHON_LIBS) - if (${_PYTHON_LIB}) - get_filename_component (_PYTHON_DIR "${${_PYTHON_LIB}}" DIRECTORY) - list (APPEND _PYTHON_DIRS "${_PYTHON_DIR}") - endif() - endforeach() - list (REMOVE_DUPLICATES _PYTHON_DIRS) - set (${_PYTHON_SLD_RESULT} ${_PYTHON_DIRS} PARENT_SCOPE) -endfunction() - - -function (_PYTHON_SET_DEVELOPMENT_MODULE_FOUND module) - if ("Development.${module}" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - string(TOUPPER "${module}" id) - set (module_found TRUE) - - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS - AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - set (module_found FALSE) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS - AND NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - set (module_found FALSE) - endif() - - set (${_PYTHON_PREFIX}_Development.${module}_FOUND ${module_found} PARENT_SCOPE) - endif() -endfunction() - - -# If major version is specified, it must be the same as internal major version -if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR - AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") -endif() - - -# handle components -if (NOT ${_PYTHON_PREFIX}_FIND_COMPONENTS) - set (${_PYTHON_PREFIX}_FIND_COMPONENTS Interpreter) - set (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter TRUE) -endif() -if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND ${_PYTHON_PREFIX}_FIND_COMPONENTS "Interpreter" "Development.Module") -endif() -if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND ${_PYTHON_PREFIX}_FIND_COMPONENTS "Development.Module" "Development.Embed") -endif() -list (REMOVE_DUPLICATES ${_PYTHON_PREFIX}_FIND_COMPONENTS) -foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development Development.Module Development.Embed NumPy) - set (${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_FOUND FALSE) -endforeach() -if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development) - set (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Module TRUE) - set (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Embed TRUE) -endif() - -unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) -unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) -unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) -if ("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - if (CMAKE_SYSTEM_NAME MATCHES "^(Windows.*|CYGWIN|MSYS)$") - list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS "LIBRARY") - endif() - list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS "INCLUDE_DIR") -endif() -if ("Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS "LIBRARY" "INCLUDE_DIR") -endif() -set (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS ${_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS} ${_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS}) -list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - -unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) - -# Set versions to search -## default: search any version -set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS}) - -if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER "1") - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}) - else() - unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) - # add all compatible versions - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS) - if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION) - list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION}) - endif() - endforeach() - endif() -endif() - -# Set ABIs to search -## default: search any ABI -if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_LESS "3") - # ABI not supported - unset (_${_PYTHON_PREFIX}_FIND_ABI) - set (_${_PYTHON_PREFIX}_ABIFLAGS "") -else() - unset (_${_PYTHON_PREFIX}_FIND_ABI) - unset (_${_PYTHON_PREFIX}_ABIFLAGS) - if (DEFINED ${_PYTHON_PREFIX}_FIND_ABI) - # normalization - string (TOUPPER "${${_PYTHON_PREFIX}_FIND_ABI}" _${_PYTHON_PREFIX}_FIND_ABI) - list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(TRUE|Y(ES)?|1)$" "ON") - list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(FALSE|N(O)?|0)$" "OFF") - if (NOT _${_PYTHON_PREFIX}_FIND_ABI MATCHES "^(ON|OFF|ANY);(ON|OFF|ANY);(ON|OFF|ANY)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_ABI}: invalid value for '${_PYTHON_PREFIX}_FIND_ABI'. Ignore it") - unset (_${_PYTHON_PREFIX}_FIND_ABI) - endif() - _python_get_abiflags (_${_PYTHON_PREFIX}_ABIFLAGS) - endif() -endif() -unset (${_PYTHON_PREFIX}_SOABI) - -# Define lookup strategy -if (_${_PYTHON_PREFIX}_LOOKUP_POLICY STREQUAL "NEW") - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "LOCATION") -else() - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") -endif() -if (DEFINED ${_PYTHON_PREFIX}_FIND_STRATEGY) - if (NOT ${_PYTHON_PREFIX}_FIND_STRATEGY MATCHES "^(VERSION|LOCATION)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_STRATEGY}: invalid value for '${_PYTHON_PREFIX}_FIND_STRATEGY'. 'VERSION' or 'LOCATION' expected.") - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") - else() - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "${${_PYTHON_PREFIX}_FIND_STRATEGY}") - endif() -endif() - -# Python and Anaconda distributions: define which architectures can be used -if (CMAKE_SIZEOF_VOID_P) - # In this case, search only for 64bit or 32bit - math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") - set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) -else() - # architecture unknown, search for both 64bit and 32bit - set (_${_PYTHON_PREFIX}_ARCH 64) - set (_${_PYTHON_PREFIX}_ARCH2 32) -endif() - -# IronPython support -if (CMAKE_SIZEOF_VOID_P) - # In this case, search only for 64bit or 32bit - math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") - set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy${_${_PYTHON_PREFIX}_ARCH} ipy) -else() - # architecture unknown, search for natural interpreter - set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy) -endif() -set (_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES net45 net40) - -# Apple frameworks handling -_python_find_frameworks () - -set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK "FIRST") - -if (DEFINED ${_PYTHON_PREFIX}_FIND_FRAMEWORK) - if (NOT ${_PYTHON_PREFIX}_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}: invalid value for '${_PYTHON_PREFIX}_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - else() - set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}) - endif() -elseif (DEFINED CMAKE_FIND_FRAMEWORK) - if (CMAKE_FIND_FRAMEWORK STREQUAL "ONLY") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: CMAKE_FIND_FRAMEWORK: 'ONLY' value is not supported. 'FIRST' will be used instead.") - elseif (NOT CMAKE_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${CMAKE_FIND_FRAMEWORK}: invalid value for 'CMAKE_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - else() - set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) - endif() -endif() - -# Save CMAKE_FIND_APPBUNDLE -if (DEFINED CMAKE_FIND_APPBUNDLE) - set (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}) -else() - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) -endif() -# To avoid app bundle lookup -set (CMAKE_FIND_APPBUNDLE "NEVER") - -# Save CMAKE_FIND_FRAMEWORK -if (DEFINED CMAKE_FIND_FRAMEWORK) - set (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) -else() - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) -endif() -# To avoid framework lookup -set (CMAKE_FIND_FRAMEWORK "NEVER") - -# Windows Registry handling -if (DEFINED ${_PYTHON_PREFIX}_FIND_REGISTRY) - if (NOT ${_PYTHON_PREFIX}_FIND_REGISTRY MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_REGISTRY}: invalid value for '${_PYTHON_PREFIX}_FIND_REGISTRY'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") - else() - set (_${_PYTHON_PREFIX}_FIND_REGISTRY ${${_PYTHON_PREFIX}_FIND_REGISTRY}) - endif() -else() - set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") -endif() - -# virtual environments recognition -if (DEFINED ENV{VIRTUAL_ENV} OR DEFINED ENV{CONDA_PREFIX}) - if (DEFINED ${_PYTHON_PREFIX}_FIND_VIRTUALENV) - if (NOT ${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY|STANDARD)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}: invalid value for '${_PYTHON_PREFIX}_FIND_VIRTUALENV'. 'FIRST', 'ONLY' or 'STANDARD' expected. 'FIRST' will be used instead.") - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV "FIRST") - else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}) - endif() - else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV FIRST) - endif() -else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STANDARD) -endif() - - -# Compute search signature -# This signature will be used to check validity of cached variables on new search -set (_${_PYTHON_PREFIX}_SIGNATURE "${${_PYTHON_PREFIX}_ROOT_DIR}:${_${_PYTHON_PREFIX}_FIND_STRATEGY}:${${_PYTHON_PREFIX}_FIND_VIRTUALENV}") -if (NOT WIN32) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${${_PYTHON_PREFIX}_USE_STATIC_LIBS}:") -endif() -if (CMAKE_HOST_APPLE) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${_${_PYTHON_PREFIX}_FIND_FRAMEWORK}") -endif() -if (CMAKE_HOST_WIN32) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${_${_PYTHON_PREFIX}_FIND_REGISTRY}") -endif() - -function (_PYTHON_CHECK_DEVELOPMENT_SIGNATURE module) - if ("Development.${module}" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - string (TOUPPER "${module}" id) - set (signature "${_${_PYTHON_PREFIX}_SIGNATURE}:") - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - list (APPEND signature "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:") - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - list (APPEND signature "${_${_PYTHON_PREFIX}_INCLUDE_DIR}:") - endif() - string (MD5 signature "${signature}") - if (signature STREQUAL _${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE) - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - set (exact EXACT) - endif() - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - _python_validate_library (${${_PYTHON_PREFIX}_FIND_VERSION} ${exact} CHECK_EXISTS) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - _python_validate_include_dir (${${_PYTHON_PREFIX}_FIND_VERSION} ${exact} CHECK_EXISTS) - endif() - else() - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - unset (_${_PYTHON_PREFIX}_LIBRARY_RELEASE CACHE) - unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) - endif() - endif() - if (("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS - AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - OR ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS - AND NOT _${_PYTHON_PREFIX}_INCLUDE_DIR)) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - unset (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE CACHE) - endif() - endif() -endfunction() - -function (_PYTHON_COMPUTE_DEVELOPMENT_SIGNATURE module) - string (TOUPPER "${module}" id) - if (${_PYTHON_PREFIX}_Development.${module}_FOUND) - set (signature "${_${_PYTHON_PREFIX}_SIGNATURE}:") - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - list (APPEND signature "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:") - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) - list (APPEND signature "${_${_PYTHON_PREFIX}_INCLUDE_DIR}:") - endif() - string (MD5 signature "${signature}") - set (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE "${signature}" CACHE INTERNAL "") - else() - unset (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE CACHE) - endif() -endfunction() - - -unset (_${_PYTHON_PREFIX}_REQUIRED_VARS) -unset (_${_PYTHON_PREFIX}_CACHED_VARS) -unset (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_Development_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE) - - -# first step, search for the interpreter -if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_EXECUTABLE) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_EXECUTABLE) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_EXECUTABLE - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_EXECUTABLE}") - if (NOT ${_PYTHON_PREFIX}_EXECUTABLE STREQUAL _${_PYTHON_PREFIX}_EXECUTABLE) - # invalidate cache properties - unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) - endif() - set (_${_PYTHON_PREFIX}_EXECUTABLE "${${_PYTHON_PREFIX}_EXECUTABLE}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_EXECUTABLE) - # compute interpreter signature and check validity of definition - string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") - if (__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) - # check version validity - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - else() - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - endif() - else() - unset (_${_PYTHON_PREFIX}_EXECUTABLE CACHE) - endif() - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) - unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - unset (_${_PYTHON_PREFIX}_NAMES) - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - unset (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # build all executable names - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX EXECUTABLE) - list (APPEND _${_PYTHON_PREFIX}_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Registry Paths - _python_get_registries (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath]) - endforeach() - list (APPEND _${_PYTHON_PREFIX}_NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} python) - - while (TRUE) - # Virtual environments handling - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX - PATH_SUFFIXES bin Scripts - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - break() - endif() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - - # try using HINTS - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - # try using standard paths - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - - break() - endwhile() - else() - # look-up for various versions and locations - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX EXECUTABLE) - list (APPEND _${_PYTHON_PREFIX}_NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} - python) - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - - # Virtual environments handling - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX - PATH_SUFFIXES bin Scripts - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - continue() - endif() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # try using HINTS - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - # try using standard paths. - # NAMES_PER_DIR is not defined on purpose to have a chance to find - # expected version. - # For example, typical systems have 'python' for version 2.* and 'python3' - # for version 3.*. So looking for names per dir will find, potentially, - # systematically 'python' (i.e. version 2) even if version 3 is searched. - if (CMAKE_HOST_WIN32) - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - python - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES}) - else() - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES}) - endif() - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - endif() - - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endforeach() - - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE AND - NOT _${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - # No specific version found. Retry with generic names and standard paths. - # NAMES_PER_DIR is not defined on purpose to have a chance to find - # expected version. - # For example, typical systems have 'python' for version 2.* and 'python3' - # for version 3.*. So looking for names per dir will find, potentially, - # systematically 'python' (i.e. version 2) even if version 3 is searched. - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} - python - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES}) - _python_validate_interpreter () - endif() - endif() - endif() - - set (${_PYTHON_PREFIX}_EXECUTABLE "${_${_PYTHON_PREFIX}_EXECUTABLE}") - - # retrieve exact version of executable found - if (_${_PYTHON_PREFIX}_EXECUTABLE) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE TRUE) - else() - # Interpreter is not usable - set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE FALSE) - unset (${_PYTHON_PREFIX}_VERSION) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot run the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - endif() - - if (_${_PYTHON_PREFIX}_EXECUTABLE AND _${_PYTHON_PREFIX}_EXECUTABLE_USABLE) - if (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES) - set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 0 ${_PYTHON_PREFIX}_INTERPRETER_ID) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 1 ${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 2 ${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 3 ${_PYTHON_PREFIX}_VERSION_PATCH) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 4 _${_PYTHON_PREFIX}_ARCH) - set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 5 _${_PYTHON_PREFIX}_ABIFLAGS) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 6 ${_PYTHON_PREFIX}_SOABI) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 7 ${_PYTHON_PREFIX}_STDLIB) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 8 ${_PYTHON_PREFIX}_STDARCH) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 9 ${_PYTHON_PREFIX}_SITELIB) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 10 ${_PYTHON_PREFIX}_SITEARCH) - else() - string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${${_PYTHON_PREFIX}_VERSION}") - list (GET _${_PYTHON_PREFIX}_VERSIONS 0 ${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 1 ${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 2 ${_PYTHON_PREFIX}_VERSION_PATCH) - - if (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) - - # Use interpreter version and ABI for future searches to ensure consistency - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; sys.stdout.write(sys.abiflags)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assunme ABI is not supported - set (_${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - endif() - - if (${_PYTHON_PREFIX}_Interpreter_FOUND) - # compute and save interpreter signature - string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") - set (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}" CACHE INTERNAL "") - - if (NOT CMAKE_SIZEOF_VOID_P) - # determine interpreter architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; print(sys.maxsize > 2**32)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_IS64BIT - ERROR_VARIABLE ${_PYTHON_PREFIX}_IS64BIT) - if (NOT _${_PYTHON_PREFIX}_RESULT) - if (${_PYTHON_PREFIX}_IS64BIT) - set (_${_PYTHON_PREFIX}_ARCH 64) - set (_${_PYTHON_PREFIX}_ARCH2 64) - else() - set (_${_PYTHON_PREFIX}_ARCH 32) - set (_${_PYTHON_PREFIX}_ARCH2 32) - endif() - endif() - endif() - - # retrieve interpreter identity - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -V - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID - ERROR_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID) - if (NOT _${_PYTHON_PREFIX}_RESULT) - if (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Anaconda") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "Anaconda") - elseif (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Enthought") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "Canopy") - else() - string (REGEX REPLACE "^([^ ]+).*" "\\1" ${_PYTHON_PREFIX}_INTERPRETER_ID "${${_PYTHON_PREFIX}_INTERPRETER_ID}") - if (${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "Python") - # try to get a more precise ID - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; print(sys.copyright)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_COPYRIGHT - ERROR_QUIET) - if (${_PYTHON_PREFIX}_COPYRIGHT MATCHES "ActiveState") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "ActivePython") - endif() - endif() - endif() - else() - set (${_PYTHON_PREFIX}_INTERPRETER_ID Python) - endif() - - # retrieve various package installation directories - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_python_lib(plat_specific=False,standard_lib=True),sysconfig.get_python_lib(plat_specific=True,standard_lib=True),sysconfig.get_python_lib(plat_specific=False,standard_lib=False),sysconfig.get_python_lib(plat_specific=True,standard_lib=False)]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_path('stdlib'),sysconfig.get_path('platstdlib'),sysconfig.get_path('purelib'),sysconfig.get_path('platlib')]))" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_LIBPATHS - ERROR_QUIET) - if (NOT _${_PYTHON_PREFIX}_RESULT) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 0 ${_PYTHON_PREFIX}_STDLIB) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 1 ${_PYTHON_PREFIX}_STDARCH) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 2 ${_PYTHON_PREFIX}_SITELIB) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 3 ${_PYTHON_PREFIX}_SITEARCH) - else() - unset (${_PYTHON_PREFIX}_STDLIB) - unset (${_PYTHON_PREFIX}_STDARCH) - unset (${_PYTHON_PREFIX}_SITELIB) - unset (${_PYTHON_PREFIX}_SITEARCH) - endif() - - if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_GREATER_EQUAL "3") - _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) - endif() - - # store properties in the cache to speed-up future searches - set (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES - "${${_PYTHON_PREFIX}_INTERPRETER_ID};${${_PYTHON_PREFIX}_VERSION_MAJOR};${${_PYTHON_PREFIX}_VERSION_MINOR};${${_PYTHON_PREFIX}_VERSION_PATCH};${_${_PYTHON_PREFIX}_ARCH};${_${_PYTHON_PREFIX}_ABIFLAGS};${${_PYTHON_PREFIX}_SOABI};${${_PYTHON_PREFIX}_STDLIB};${${_PYTHON_PREFIX}_STDARCH};${${_PYTHON_PREFIX}_SITELIB};${${_PYTHON_PREFIX}_SITEARCH}" CACHE INTERNAL "${_PYTHON_PREFIX} Properties") - else() - unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) - unset (${_PYTHON_PREFIX}_INTERPRETER_ID) - endif() - endif() - endif() - - if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) - set (${_PYTHON_PREFIX}_EXECUTABLE "${_${_PYTHON_PREFIX}_EXECUTABLE}" CACHE FILEPATH "${_PYTHON_PREFIX} Interpreter") - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_EXECUTABLE - _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES - _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) -endif() - - -# second step, search for compiler (IronPython) -if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_COMPILER) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Compiler) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_COMPILER) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_COMPILER - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_COMPILER}") - set (_${_PYTHON_PREFIX}_COMPILER "${${_PYTHON_PREFIX}_COMPILER}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_COMPILER) - # compute compiler signature and check validity of definition - string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") - if (__${_PYTHON_PREFIX}_COMPILER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) - # check version validity - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - else() - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - endif() - else() - unset (_${_PYTHON_PREFIX}_COMPILER CACHE) - unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_COMPILER) - # IronPython specific artifacts - # If IronPython interpreter is found, use its path - unset (_${_PYTHON_PREFIX}_IRON_ROOT) - if (${_PYTHON_PREFIX}_Interpreter_FOUND AND ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") - get_filename_component (_${_PYTHON_PREFIX}_IRON_ROOT "${${_PYTHON_PREFIX}_EXECUTABLE}" DIRECTORY) - endif() - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - set (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # Registry Paths - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath]) - endforeach() - - while (TRUE) - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - endif() - - break() - endwhile() - else() - # try using root dir and registry - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - endforeach() - - # no specific version found, re-try in standard paths - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}) - endif() - endif() - - set (${_PYTHON_PREFIX}_COMPILER "${_${_PYTHON_PREFIX}_COMPILER}") - - if (_${_PYTHON_PREFIX}_COMPILER) - # retrieve python environment version from compiler - set (_${_PYTHON_PREFIX}_VERSION_DIR "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") - file (WRITE "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" - WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" - OUTPUT_QUIET - ERROR_QUIET) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_VERSION_DIR}/version" - WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_VERSION - ERROR_QUIET) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (_${_PYTHON_PREFIX}_COMPILER_USABLE TRUE) - string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${_${_PYTHON_PREFIX}_VERSION}") - list (GET _${_PYTHON_PREFIX}_VERSIONS 0 _${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 1 _${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 2 _${_PYTHON_PREFIX}_VERSION_PATCH) - - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND) - # set public version information - set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) - set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) - set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) - endif() - else() - # compiler not usable - set (_${_PYTHON_PREFIX}_COMPILER_USABLE FALSE) - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot run the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - endif() - file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") - endif() - - if (_${_PYTHON_PREFIX}_COMPILER AND _${_PYTHON_PREFIX}_COMPILER_USABLE) - if (${_PYTHON_PREFIX}_Interpreter_FOUND) - # Compiler must be compatible with interpreter - if ("${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}" VERSION_EQUAL "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}") - set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) - endif() - elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) - # Use compiler version for future searches to ensure consistency - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) - endif() - endif() - - if (${_PYTHON_PREFIX}_Compiler_FOUND) - # compute and save compiler signature - string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") - set (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${__${_PYTHON_PREFIX}_COMPILER_SIGNATURE}" CACHE INTERNAL "") - - set (${_PYTHON_PREFIX}_COMPILER_ID IronPython) - else() - unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) - unset (${_PYTHON_PREFIX}_COMPILER_ID) - endif() - - if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) - set (${_PYTHON_PREFIX}_COMPILER "${_${_PYTHON_PREFIX}_COMPILER}" CACHE FILEPATH "${_PYTHON_PREFIX} Compiler") - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_COMPILER - _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) -endif() - - -# third step, search for the development artifacts -## Development environment is not compatible with IronPython interpreter -if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - AND NOT ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_INCLUDE_DIR) - endif() - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Module) - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARIES) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_INCLUDE_DIRS) - endif() - endif() - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Embed) - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARIES) - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_INCLUDE_DIRS) - endif() - endif() - list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_REQUIRED_VARS) - - _python_check_development_signature (Module) - _python_check_development_signature (Embed) - - if (DEFINED ${_PYTHON_PREFIX}_LIBRARY - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_LIBRARY}") - set (_${_PYTHON_PREFIX}_LIBRARY_RELEASE "${${_PYTHON_PREFIX}_LIBRARY}" CACHE INTERNAL "") - unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) - unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) - endif() - if (DEFINED ${_PYTHON_PREFIX}_INCLUDE_DIR - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_INCLUDE_DIR "${${_PYTHON_PREFIX}_INCLUDE_DIR}" CACHE INTERNAL "") - endif() - - # Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) - if (DEFINED ${_PYTHON_PREFIX}_USE_STATIC_LIBS AND NOT WIN32) - set(_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - if(${_PYTHON_PREFIX}_USE_STATIC_LIBS) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - list (REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE OR NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - # if python interpreter is found, use it to look-up for artifacts - # to ensure consistency between interpreter and development environments. - # If not, try to locate a compatible config tool - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND OR CMAKE_CROSSCOMPILING) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - set (_${_PYTHON_PREFIX}_CONFIG_NAMES) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX CONFIG) - list (APPEND _${_PYTHON_PREFIX}_CONFIG_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - endforeach() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES bin) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume config tool is not usable - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume ABI is not supported - set (__${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # Wrong ABI - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - # check that config tool match library architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - else() - string(FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) - if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - endif() - else() - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # try to use pythonX.Y-config tool - _python_get_names (_${_PYTHON_PREFIX}_CONFIG_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX CONFIG) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES bin) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - unset (_${_PYTHON_PREFIX}_CONFIG_NAMES) - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume config tool is not usable - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_CONFIG) - continue() - endif() - - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume ABI is not supported - set (__${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # Wrong ABI - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - # check that config tool match library architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - string (FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) - if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - break() - endif() - endforeach() - endif() - endif() - endif() - - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) - # retrieve root install directory - _python_get_config_var (_${_PYTHON_PREFIX}_PREFIX PREFIX) - - # enforce current ABI - _python_get_config_var (_${_PYTHON_PREFIX}_ABIFLAGS ABIFLAGS) - - set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") - - # retrieve library - ## compute some paths and artifact names - if (_${_PYTHON_PREFIX}_CONFIG) - string (REGEX REPLACE "^.+python([0-9.]+)[a-z]*-config" "\\1" _${_PYTHON_PREFIX}_VERSION "${_${_PYTHON_PREFIX}_CONFIG}") - else() - set (_${_PYTHON_PREFIX}_VERSION "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}") - endif() - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} LIBRARY) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) - - _python_get_config_var (_${_PYTHON_PREFIX}_CONFIGDIR CONFIGDIR) - list (APPEND _${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_CONFIGDIR}") - - list (APPEND _${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - unset (_${_PYTHON_PREFIX}_LIB_NAMES) - unset (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG) - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - unset (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - unset (_${_PYTHON_PREFIX}_PATH_SUFFIXES) - - foreach (_${_PYTHON_PREFIX}_LIB_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # library names - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 POSIX LIBRARY) - list (APPEND _${_PYTHON_PREFIX}_LIB_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 DEBUG) - list (APPEND _${_PYTHON_PREFIX}_LIB_NAMES_DEBUG ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Registry Paths - _python_get_registries (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Paths suffixes - _python_get_path_suffixes (_${_PYTHON_PREFIX}_VERSION_PATHS VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} LIBRARY) - list (APPEND _${_PYTHON_PREFIX}_PATH_SUFFIXES ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - endforeach() - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search in HINTS locations - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - # search in all default paths - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) - else() - foreach (_${_PYTHON_PREFIX}_LIB_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 POSIX LIBRARY) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 DEBUG) - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} LIBRARY) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search in HINTS locations - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - # search in all default paths - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - break() - endif() - endforeach() - endif() - endif() - endif() - - # finalize library version information - _python_get_version (LIBRARY PREFIX _${_PYTHON_PREFIX}_) - - set (${_PYTHON_PREFIX}_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - if (WIN32 AND _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # search for debug library - # use release library location as a hint - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - find_library (_${_PYTHON_PREFIX}_LIBRARY_DEBUG - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} - NO_DEFAULT_PATH) - endif() - - # retrieve runtime libraries - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) - _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin) - endif() - if (_${_PYTHON_PREFIX}_LIBRARY_DEBUG) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) - _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin) - endif() - endif() - - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - while (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS - AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # Don't search for include dir if no library was founded - break() - endif() - - if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) - _python_get_config_var (_${_PYTHON_PREFIX}_INCLUDE_DIRS INCLUDES) - - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_DIRS} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts - if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - unset (_${_PYTHON_PREFIX}_INCLUDE_HINTS) - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # Use the library's install prefix as a hint - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "^(.+/Frameworks/Python.framework/Versions/[0-9.]+)") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - elseif (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "^(.+)/lib(64|32)?/python[0-9.]+/config") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - elseif (DEFINED CMAKE_LIBRARY_ARCHITECTURE AND ${_${_PYTHON_PREFIX}_LIBRARY_RELEASE} MATCHES "^(.+)/lib/${CMAKE_LIBRARY_ARCHITECTURE}") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - else() - # assume library is in a directory under root - get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_PREFIX}" DIRECTORY) - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") - endif() - endif() - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} INCLUDE) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search header file in standard locations - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h) - - break() - endwhile() - - set (${_PYTHON_PREFIX}_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - - if (_${_PYTHON_PREFIX}_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - - if (_${_PYTHON_PREFIX}_INCLUDE_DIR) - # retrieve version from header file - _python_get_version (INCLUDE PREFIX _${_PYTHON_PREFIX}_INC_) - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # update versioning - if (_${_PYTHON_PREFIX}_INC_VERSION VERSION_EQUAL _${_PYTHON_PREFIX}_VERSION) - set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH}) - endif() - else() - set (_${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_INC_VERSION}) - set (_${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}) - set (_${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}) - set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH}) - endif() - endif() - endif() - - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT ${_PYTHON_PREFIX}_Compiler_FOUND) - # set public version information - set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) - set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) - set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) - endif() - - # define public variables - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - set (${_PYTHON_PREFIX}_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}") - _python_select_library_configurations (${_PYTHON_PREFIX}) - - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - - if (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - elseif (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - else() - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_PYTHON_PREFIX}_RUNTIME_LIBRARY-NOTFOUND") - endif() - - _python_set_library_dirs (${_PYTHON_PREFIX}_LIBRARY_DIRS - _${_PYTHON_PREFIX}_LIBRARY_RELEASE _${_PYTHON_PREFIX}_LIBRARY_DEBUG) - if (UNIX) - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS ${${_PYTHON_PREFIX}_LIBRARY_DIRS}) - endif() - else() - _python_set_library_dirs (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) - endif() - endif() - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE OR _${_PYTHON_PREFIX}_INCLUDE_DIR) - if (${_PYTHON_PREFIX}_Interpreter_FOUND OR ${_PYTHON_PREFIX}_Compiler_FOUND) - # development environment must be compatible with interpreter/compiler - if ("${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}" VERSION_EQUAL "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}" - AND "${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}" VERSION_EQUAL "${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}") - _python_set_development_module_found (Module) - _python_set_development_module_found (Embed) - endif() - elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR - AND "${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}" VERSION_EQUAL "${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}") - _python_set_development_module_found (Module) - _python_set_development_module_found (Embed) - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND - (NOT _${_PYTHON_PREFIX}_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS - OR NOT _${_PYTHON_PREFIX}_INC_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)) - set (${_PYTHON_PREFIX}_Development.Module_FOUND FALSE) - set (${_PYTHON_PREFIX}_Development.Embed_FOUND FALSE) - endif() - endif() - - if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Development.Module_FOUND - AND ${_PYTHON_PREFIX}_Development.Embed_FOUND) - set (${_PYTHON_PREFIX}_Development_FOUND TRUE) - endif() - - if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_GREATER_EQUAL "3" - AND NOT DEFINED ${_PYTHON_PREFIX}_SOABI) - _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) - endif() - - _python_compute_development_signature (Module) - _python_compute_development_signature (Embed) - - # Restore the original find library ordering - if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES}) - endif() - - if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - set (${_PYTHON_PREFIX}_LIBRARY "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" CACHE FILEPATH "${_PYTHON_PREFIX} Library") - endif() - if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) - set (${_PYTHON_PREFIX}_INCLUDE_DIR "${_${_PYTHON_PREFIX}_INCLUDE_DIR}" CACHE FILEPATH "${_PYTHON_PREFIX} Include Directory") - endif() - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_INCLUDE_DIR - _${_PYTHON_PREFIX}_CONFIG - _${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE - _${_PYTHON_PREFIX}_DEVELOPMENT_EMBED_SIGNATURE) -endif() - -if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_Interpreter_FOUND) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_NumPy) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - # compute numpy signature. Depends on interpreter and development signatures - string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE}:${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - if (NOT __${_PYTHON_PREFIX}_NUMPY_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_NUMPY_SIGNATURE - OR NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - unset (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR CACHE) - unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - execute_process( - COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c - "from __future__ import print_function\ntry: import numpy; print(numpy.get_include(), end='')\nexcept:pass\n" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_PATH - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if (NOT _${_PYTHON_PREFIX}_RESULT) - find_path (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - NAMES "numpy/arrayobject.h" "numpy/numpyconfig.h" - HINTS "${_${_PYTHON_PREFIX}_NumPy_PATH}" - NO_DEFAULT_PATH) - endif() - endif() - - set (${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - - if(_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR-NOTFOUND") - endif() - - if (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - execute_process ( - COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c - "from __future__ import print_function\ntry: import numpy; print(numpy.__version__, end='')\nexcept:pass\n" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_VERSION) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (${_PYTHON_PREFIX}_NumPy_VERSION "${_${_PYTHON_PREFIX}_NumPy_VERSION}") - else() - unset (${_PYTHON_PREFIX}_NumPy_VERSION) - endif() - - # final step: set NumPy founded only if Development.Module component is founded as well - set(${_PYTHON_PREFIX}_NumPy_FOUND ${${_PYTHON_PREFIX}_Development.Module_FOUND}) - else() - set (${_PYTHON_PREFIX}_NumPy_FOUND FALSE) - endif() - - if (${_PYTHON_PREFIX}_NumPy_FOUND) - # compute and save numpy signature - string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE}:${${_PYTHON_PREFIX}_NumPyINCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${__${_PYTHON_PREFIX}_NUMPY_SIGNATURE}" CACHE INTERNAL "") - else() - unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) - endif() - - if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) - set (${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}" CACHE FILEPATH "${_PYTHON_PREFIX} NumPy Include Directory") - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - _${_PYTHON_PREFIX}_NUMPY_SIGNATURE) -endif() - -# final validation -if (${_PYTHON_PREFIX}_VERSION_MAJOR AND - NOT ${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Found unsuitable major version \"${${_PYTHON_PREFIX}_VERSION_MAJOR}\", but required major version is exact version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") -endif() - -unset (_${_PYTHON_PREFIX}_REASON_FAILURE) -foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development NumPy) - if (_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE) - string (APPEND _${_PYTHON_PREFIX}_REASON_FAILURE "\n ${_${_PYTHON_PREFIX}_COMPONENT}: ${_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE}") - endif() -endforeach() - -include (${CMAKE_CURRENT_LIST_DIR}/../FindPackageHandleStandardArgs.cmake) -find_package_handle_standard_args (${_PYTHON_PREFIX} - REQUIRED_VARS ${_${_PYTHON_PREFIX}_REQUIRED_VARS} - VERSION_VAR ${_PYTHON_PREFIX}_VERSION - HANDLE_COMPONENTS - REASON_FAILURE_MESSAGE "${_${_PYTHON_PREFIX}_REASON_FAILURE}") - -# Create imported targets and helper functions -if(_${_PYTHON_PREFIX}_CMAKE_ROLE STREQUAL "PROJECT") - if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Interpreter_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::Interpreter) - add_executable (${_PYTHON_PREFIX}::Interpreter IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::Interpreter - PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_EXECUTABLE}") - endif() - - if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Compiler_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::Compiler) - add_executable (${_PYTHON_PREFIX}::Compiler IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::Compiler - PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_COMPILER}") - endif() - - if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Development.Module_FOUND) - OR ("Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Development.Embed_FOUND)) - - macro (__PYTHON_IMPORT_LIBRARY __name) - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" - OR ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - set (_${_PYTHON_PREFIX}_LIBRARY_TYPE SHARED) - else() - set (_${_PYTHON_PREFIX}_LIBRARY_TYPE STATIC) - endif() - - if (NOT TARGET ${__name}) - add_library (${__name} ${_${_PYTHON_PREFIX}_LIBRARY_TYPE} IMPORTED) - endif() - - set_property (TARGET ${__name} - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") - - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - # System manage shared libraries in two parts: import and runtime - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) - set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_IMPLIB_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" - IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_IMPLIB_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}" - IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - else() - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_IMPLIB "${${_PYTHON_PREFIX}_LIBRARIES}" - IMPORTED_LOCATION "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - endif() - else() - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) - set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}") - else() - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - endif() - endif() - - if (_${_PYTHON_PREFIX}_LIBRARY_TYPE STREQUAL "STATIC") - # extend link information with dependent libraries - _python_get_config_var (_${_PYTHON_PREFIX}_LINK_LIBRARIES LIBS) - if (_${_PYTHON_PREFIX}_LINK_LIBRARIES) - set_property (TARGET ${__name} - PROPERTY INTERFACE_LINK_LIBRARIES ${_${_PYTHON_PREFIX}_LINK_LIBRARIES}) - endif() - endif() - endmacro() - - if (${_PYTHON_PREFIX}_Development.Embed_FOUND) - __python_import_library (${_PYTHON_PREFIX}::Python) - endif() - - if (${_PYTHON_PREFIX}_Development.Module_FOUND) - if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) - # On Windows/CYGWIN/MSYS, Python::Module is the same as Python::Python - # but ALIAS cannot be used because the imported library is not GLOBAL. - __python_import_library (${_PYTHON_PREFIX}::Module) - else() - if (NOT TARGET ${_PYTHON_PREFIX}::Module) - add_library (${_PYTHON_PREFIX}::Module INTERFACE IMPORTED) - endif() - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") - - # When available, enforce shared library generation with undefined symbols - if (APPLE) - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "SunOS") - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-z,nodefs") - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "AIX") - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-b,erok") - endif() - endif() - endif() - - # - # PYTHON_ADD_LIBRARY ( [STATIC|SHARED|MODULE] src1 src2 ... srcN) - # It is used to build modules for python. - # - function (__${_PYTHON_PREFIX}_ADD_LIBRARY prefix name) - cmake_parse_arguments (PARSE_ARGV 2 PYTHON_ADD_LIBRARY - "STATIC;SHARED;MODULE;WITH_SOABI" "" "") - - if (prefix STREQUAL "Python2" AND PYTHON_ADD_LIBRARY_WITH_SOABI) - message (AUTHOR_WARNING "FindPython2: Option `WITH_SOABI` is not supported for Python2 and will be ignored.") - unset (PYTHON_ADD_LIBRARY_WITH_SOABI) - endif() - - if (PYTHON_ADD_LIBRARY_STATIC) - set (type STATIC) - elseif (PYTHON_ADD_LIBRARY_SHARED) - set (type SHARED) - else() - set (type MODULE) - endif() - - if (type STREQUAL "MODULE" AND NOT TARGET ${prefix}::Module) - message (SEND_ERROR "${prefix}_ADD_LIBRARY: dependent target '${prefix}::Module' is not defined.\n Did you miss to request COMPONENT 'Development.Module'?") - return() - endif() - if (NOT type STREQUAL "MODULE" AND NOT TARGET ${prefix}::Python) - message (SEND_ERROR "${prefix}_ADD_LIBRARY: dependent target '${prefix}::Python' is not defined.\n Did you miss to request COMPONENT 'Development.Embed'?") - return() - endif() - - add_library (${name} ${type} ${PYTHON_ADD_LIBRARY_UNPARSED_ARGUMENTS}) - - get_property (type TARGET ${name} PROPERTY TYPE) - - if (type STREQUAL "MODULE_LIBRARY") - target_link_libraries (${name} PRIVATE ${prefix}::Module) - # customize library name to follow module name rules - set_property (TARGET ${name} PROPERTY PREFIX "") - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set_property (TARGET ${name} PROPERTY SUFFIX ".pyd") - endif() - - if (PYTHON_ADD_LIBRARY_WITH_SOABI AND ${prefix}_SOABI) - get_property (suffix TARGET ${name} PROPERTY SUFFIX) - if (NOT suffix) - set (suffix "${CMAKE_SHARED_MODULE_SUFFIX}") - endif() - set_property (TARGET ${name} PROPERTY SUFFIX ".${${prefix}_SOABI}${suffix}") - endif() - else() - if (PYTHON_ADD_LIBRARY_WITH_SOABI) - message (AUTHOR_WARNING "Find${prefix}: Option `WITH_SOABI` is only supported for `MODULE` library type.") - endif() - target_link_libraries (${name} PRIVATE ${prefix}::Python) - endif() - endfunction() - endif() - - if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_NumPy_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::NumPy AND TARGET ${_PYTHON_PREFIX}::Module) - add_library (${_PYTHON_PREFIX}::NumPy INTERFACE IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::NumPy - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS}") - target_link_libraries (${_PYTHON_PREFIX}::NumPy INTERFACE ${_PYTHON_PREFIX}::Module) - endif() -endif() - -# final clean-up - -# Restore CMAKE_FIND_APPBUNDLE -if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) - set (CMAKE_FIND_APPBUNDLE ${_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE}) - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) -else() - unset (CMAKE_FIND_APPBUNDLE) -endif() -# Restore CMAKE_FIND_FRAMEWORK -if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) - set (CMAKE_FIND_FRAMEWORK ${_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK}) - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) -else() - unset (CMAKE_FIND_FRAMEWORK) -endif() diff --git a/scripts/internal/libpython-not-needed-symbols-exported-by-interpreter b/scripts/internal/libpython-not-needed-symbols-exported-by-interpreter deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/internal/manylinux-aarch64-build-module-wheels.sh b/scripts/internal/manylinux-aarch64-build-module-wheels.sh deleted file mode 100755 index f43a7f87..00000000 --- a/scripts/internal/manylinux-aarch64-build-module-wheels.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -# Run this script inside a dockcross container to build Python wheels for an aarch ITK module. -cd /work -# Update GPG keys -dnf upgrade -y almalinux-release -# Newer Python.cmake module required for SABI -pipx upgrade cmake -yum -y install sudo ninja-build -/opt/python/cp310-cp310/bin/python -m pip install -r /ITKPythonPackage/requirements-dev.txt -for PYBIN in "${PYBINARIES[@]}"; do - ${PYBIN}/pip install -r /ITKPythonPackage/requirements-dev.txt -done - -"/ITKPythonPackage/scripts/internal/manylinux-build-module-wheels.sh" "$@" diff --git a/scripts/internal/manylinux-build-common.sh b/scripts/internal/manylinux-build-common.sh deleted file mode 100644 index 6443b4c0..00000000 --- a/scripts/internal/manylinux-build-common.sh +++ /dev/null @@ -1,85 +0,0 @@ -# Content common to manylinux-build-wheels.sh and -# manylinux-build-module-wheels.sh - -set -e -x - -script_dir=$(cd $(dirname $0) || exit 1; pwd) - -# Versions can be restricted by passing them in as arguments to the script -# For example, -# manylinux-build-wheels.sh cp310 -if [[ $# -eq 0 ]]; then - PYBIN=(/opt/python/*/bin) - PYBINARIES=() - for version in "${PYBIN[@]}"; do - if [[ ${version} == *"cp310"* || ${version} == *"cp311"* ]]; then - PYBINARIES+=(${version}) - fi - done -else - PYBINARIES=() - for version in "$@"; do - PYBINARIES+=(/opt/python/*${version}*/bin) - done -fi - -# i686 or x86_64 ? -case $(uname -m) in - i686) - ARCH=x86 - ;; - x86_64) - ARCH=x64 - ;; - aarch64) - ARCH=aarch64 - ;; - *) - die "Unknown architecture $(uname -m)" - ;; -esac - -# Install prerequirements -export PATH=/work/tools/doxygen-1.8.16/bin:$PATH -case $(uname -m) in - i686) - ARCH=x86 - ;; - x86_64) - if ! type doxygen > /dev/null 2>&1; then - mkdir -p /work/tools - pushd /work/tools > /dev/null 2>&1 - curl https://data.kitware.com/api/v1/file/62c4d615bddec9d0c46cb705/download -o doxygen-1.8.16.linux.bin.tar.gz - tar -xvzf doxygen-1.8.16.linux.bin.tar.gz - popd > /dev/null 2>&1 - fi - ;; - aarch64) - ARCH=aarch64 - if ! type doxygen > /dev/null 2>&1; then - mkdir -p /work/tools - pushd /work/tools > /dev/null 2>&1 - curl https://data.kitware.com/api/v1/file/62c4ed58bddec9d0c46f1388/download -o doxygen-1.8.16.linux.aarch64.bin.tar.gz - tar -xvzf doxygen-1.8.16.linux.aarch64.bin.tar.gz - popd > /dev/null 2>&1 - fi - ;; - *) - die "Unknown architecture $(uname -m)" - ;; -esac -if ! type ninja > /dev/null 2>&1; then - if test ! -d ninja; then - git clone https://github.com/ninja-build/ninja.git - fi - pushd ninja - git checkout release - cmake -Bbuild-cmake -H. - cmake --build build-cmake - cp build-cmake/ninja /usr/local/bin/ - popd -fi - -MANYLINUX_VERSION=${MANYLINUX_VERSION:=_2_28} - -echo "Building wheels for $ARCH using manylinux${MANYLINUX_VERSION}" diff --git a/scripts/internal/manylinux-build-module-wheels.sh b/scripts/internal/manylinux-build-module-wheels.sh deleted file mode 100755 index 37ab0382..00000000 --- a/scripts/internal/manylinux-build-module-wheels.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env bash - -# Run this script inside a dockcross container to build Python wheels for an ITK module. -# -# Versions can be restricted by passing them in as arguments to the script. -# For example, -# -# /tmp/dockcross-manylinux-x64 manylinux-build-module-wheels.sh cp310 -# -# Shared library dependencies can be included in the wheel by mounting them to /usr/lib64 or /usr/local/lib64 -# before running this script. -# -# For example, -# -# DOCKER_ARGS="-v /path/to/lib.so:/usr/local/lib64/lib.so" -# /tmp/dockcross-manylinux-x64 -a "$DOCKER_ARGS" manylinux-build-module-wheels.sh -# -# The specialized manylinux container version should be set prior to running this script. -# See https://github.com/dockcross/dockcross for available versions and tags. -# -# For example, `docker run -e ` can be used to set an environment variable when launching a container: -# -# export MANYLINUX_VERSION=2014 -# docker run --rm dockcross/manylinux${MANYLINUX_VERSION}-x64:${IMAGE_TAG} > /tmp/dockcross-manylinux-x64 -# chmod u+x /tmp/dockcross-manylinux-x64 -# /tmp/dockcross-manylinux-x64 -e MANYLINUX_VERSION manylinux-build-module-wheels.sh cp310 -# - -# ----------------------------------------------------------------------- -# Script argument parsing -# -usage() -{ - echo "Usage: - manylinux-build-module-wheels - [ -h | --help ] show usage - [ -c | --cmake_options ] space-separated string of CMake options to forward to the module (e.g. \"--config-setting=cmake.define.BUILD_TESTING=OFF\") - [ -x | --exclude_libs ] semicolon-separated library names to exclude when repairing wheel (e.g. \"libcuda.so\") - [ python_version ] build wheel for a specific python version. (e.g. cp310)" - exit 2 -} - -PARSED_ARGS=$(getopt -a -n dockcross-manylinux-download-cache-and-build-module-wheels \ - -o hc:x: --long help,cmake_options:,exclude_libs: -- "$@") -eval set -- "$PARSED_ARGS" - -while : -do - case "$1" in - -h | --help) usage; break ;; - -c | --cmake_options) CMAKE_OPTIONS="$2" ; shift 2 ;; - -x | --exclude_libs) EXCLUDE_LIBS="$2" ; shift 2 ;; - --) shift; break ;; - *) echo "Unexpected option: $1."; - usage; break ;; - esac -done - -PYTHON_VERSION="$@" -# ----------------------------------------------------------------------- - -# ----------------------------------------------------------------------- -# These variables are set in common script: -# -ARCH="" -PYBINARIES="" - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/manylinux-build-common.sh" -# ----------------------------------------------------------------------- - -# Set up library paths in container so that shared libraries can be added to wheels -sudo ldconfig -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/work/oneTBB-prefix/lib:/usr/lib:/usr/lib64:/usr/local/lib:/usr/local/lib64 - -# Compile wheels re-using standalone project and archive cache -for PYBIN in "${PYBINARIES[@]}"; do - Python3_EXECUTABLE=${PYBIN}/python - Python3_INCLUDE_DIR=$( find -L ${PYBIN}/../include/ -name Python.h -exec dirname {} \; ) - - echo "" - echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" - echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - - if [[ -e /work/requirements-dev.txt ]]; then - sudo ${PYBIN}/pip install --upgrade -r /work/requirements-dev.txt - fi - if [[ -e /ITKPythonPackage/requirements-dev.txt ]]; then - sudo ${PYBIN}/pip install --upgrade -r /ITKPythonPackage/requirements-dev.txt - fi - version=$(basename $(dirname ${PYBIN})) - # Remove "m" -- not present in Python 3.8 and later - version=${version:0:9} - itk_build_dir=/work/$(basename /ITKPythonPackage/ITK-${version}*-manylinux${MANYLINUX_VERSION}_${ARCH}) - ln -fs /ITKPythonPackage/ITK-${version}*-manylinux${MANYLINUX_VERSION}_${ARCH} $itk_build_dir - if [[ ! -d ${itk_build_dir} ]]; then - echo 'ITK build tree not available!' 1>&2 - exit 1 - fi - itk_source_dir=/work/ITK-source/ITK - ln -fs /ITKPythonPackage/ITK-source/ /work/ITK-source - if [[ ! -d ${itk_source_dir} ]]; then - echo 'ITK source tree not available!' 1>&2 - exit 1 - fi - py_minor=$(echo $version | cut -d '-' -f 1 | cut -d '3' -f 2) - wheel_py_api="" - if test $py_minor -ge 11; then - wheel_py_api=cp3$py_minor - fi - ${PYBIN}/python -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_dir} \ - --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - --config-setting=cmake.define.CMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ - --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ - --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ - --config-setting=wheel.py-api=$wheel_py_api \ - --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ - || exit 1 -done - -# Convert list of excluded libs in --exclude_libs to auditwheel --exclude options -if test -n "$EXCLUDE_LIBS"; then - AUDITWHEEL_EXCLUDE_ARGS="--exclude ${EXCLUDE_LIBS//;/ --exclude }" -fi - -sudo ${Python3_EXECUTABLE} -m pip install auditwheel -for whl in dist/*linux*$(uname -m).whl; do - auditwheel repair ${whl} -w /work/dist/ ${AUDITWHEEL_EXCLUDE_ARGS} -done - -if compgen -G "dist/itk*-linux*.whl" > /dev/null; then - for itk_wheel in dist/itk*-linux*.whl; do - rm ${itk_wheel} - done -fi diff --git a/scripts/internal/manylinux-build-wheels.sh b/scripts/internal/manylinux-build-wheels.sh deleted file mode 100755 index 2ebacbec..00000000 --- a/scripts/internal/manylinux-build-wheels.sh +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env bash - -# Run this script inside a dockcross container to build Python wheels for ITK. -# -# Versions can be restricted by passing them in as arguments to the script. -# For example, -# -# /tmp/dockcross-manylinux-x64 manylinux-build-wheels.sh cp310 -# -# Shared library dependencies can be included wheels by mounting them to /usr/lib64 or /usr/local/lib64 -# before running this script. -# -# For example, -# -# DOCKER_ARGS="-v /path/to/lib.so:/usr/local/lib64/lib.so" -# /tmp/dockcross-manylinux-x64 -a "$DOCKER_ARGS" manylinux-build-wheels.sh -# -# The specialized manylinux container version should be set prior to running this script. -# See https://github.com/dockcross/dockcross for available versions and tags. -# -# For example, `docker run -e ` can be used to set an environment variable when launching a container: -# -# export MANYLINUX_VERSION=2014 -# docker run --rm dockcross/manylinux${MANYLINUX_VERSION}-x64:${IMAGE_TAG} > /tmp/dockcross-manylinux-x64 -# chmod u+x /tmp/dockcross-manylinux-x64 -# /tmp/dockcross-manylinux-x64 -e MANYLINUX_VERSION manylinux-build-module-wheels.sh cp310 -# - -# ----------------------------------------------------------------------- -# These variables are set in common script: -# -ARCH="" -PYBINARIES="" -Python3_LIBRARY="" - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/manylinux-build-common.sh" - -# ----------------------------------------------------------------------- - -# Build standalone project and populate archive cache -mkdir -p /work/ITK-source -pushd /work/ITK-source > /dev/null 2>&1 - cmake -DITKPythonPackage_BUILD_PYTHON:PATH=0 -G Ninja ../ - ninja -popd > /dev/null 2>&1 -tbb_dir=/work/oneTBB-prefix/lib/cmake/TBB -# So auditwheel can find the libs -sudo ldconfig -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/work/oneTBB-prefix/lib:/usr/lib:/usr/lib64 - -# TODO: More work is required to re-enable this feature. -SINGLE_WHEEL=0 - -# Compile wheels re-using standalone project and archive cache -for PYBIN in "${PYBINARIES[@]}"; do - export Python3_EXECUTABLE=${PYBIN}/python3 - Python3_INCLUDE_DIR=$( find -L ${PYBIN}/../include/ -name Python.h -exec dirname {} \; ) - - echo "" - echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" - echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - - # Install dependencies - sudo ${PYBIN}/pip install --upgrade -r /work/requirements-dev.txt - - build_type="Release" - compile_flags="-O3 -DNDEBUG" - source_path=/work/ITK-source/ITK - build_path=/work/ITK-$(basename $(dirname ${PYBIN}))-manylinux${MANYLINUX_VERSION}_${ARCH} - PYPROJECT_CONFIGURE="${script_dir}/../pyproject_configure.py" - - # Clean up previous invocations - # rm -rf ${build_path} - - if [[ ${SINGLE_WHEEL} == 1 ]]; then - - echo "#" - echo "# Build single ITK wheel" - echo "#" - - # Configure pyproject.toml - ${PYBIN}/python ${PYPROJECT_CONFIGURE} "itk" - # Generate wheel - ${PYBIN}/python -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.ITK_SOURCE_DIR:PATH=${source_path} \ - --config-setting=cmake.define.ITK_BINARY_DIR:PATH=${build_path} \ - --config-setting=cmake.define.ITKPythonPackage_ITK_BINARY_REUSE:BOOL=OFF \ - --config-setting=cmake.define.ITKPythonPackage_WHEEL_NAME:STRING=itk \ - --config-setting=cmake.define.CMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ - "--config-setting=cmake.define.CMAKE_CXX_FLAGS:STRING=$compile_flags" \ - "--config-setting=cmake.define.CMAKE_C_FLAGS:STRING=$compile_flags" \ - "--config-setting=cmake.define.CMAKE_BUILD_TYPE:STRING=${build_type}" \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - --config-setting=cmake.define.Module_ITKTBB:BOOL=ON \ - --config-setting=cmake.define.TBB_DIR:PATH=${tbb_dir} \ - . - - else - - echo "#" - echo "# Build multiple ITK wheels" - echo "#" - - # Build ITK python - ( - mkdir -p ${build_path} \ - && cd ${build_path} \ - && cmake \ - -DCMAKE_BUILD_TYPE:STRING=${build_type} \ - -DITK_SOURCE_DIR:PATH=${source_path} \ - -DITK_BINARY_DIR:PATH=${build_path} \ - -DBUILD_TESTING:BOOL=OFF \ - -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - -DCMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ - -DCMAKE_CXX_FLAGS:STRING="$compile_flags" \ - -DCMAKE_C_FLAGS:STRING="$compile_flags" \ - -DCMAKE_BUILD_TYPE:STRING="${build_type}" \ - -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - -DWRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL=ON \ - -DITK_WRAP_unsigned_short:BOOL=ON \ - -DITK_WRAP_double:BOOL=ON \ - -DITK_WRAP_complex_double:BOOL=ON \ - -DITK_WRAP_IMAGE_DIMS:STRING="2;3;4" \ - -DPY_SITE_PACKAGES_PATH:PATH="." \ - -DITK_LEGACY_SILENT:BOOL=ON \ - -DITK_WRAP_PYTHON:BOOL=ON \ - -DITK_WRAP_DOC:BOOL=ON \ - -DModule_ITKTBB:BOOL=ON \ - -DTBB_DIR:PATH=${tbb_dir} \ - -G Ninja \ - ${source_path} \ - && ninja \ - || exit 1 - ) - - wheel_names=$(cat ${script_dir}/../WHEEL_NAMES.txt) - for wheel_name in ${wheel_names}; do - # Configure pyproject.toml - ${PYBIN}/python ${PYPROJECT_CONFIGURE} ${wheel_name} - # Generate wheel - ${PYBIN}/python -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.ITK_SOURCE_DIR:PATH=${source_path} \ - --config-setting=cmake.define.ITK_BINARY_DIR:PATH=${build_path} \ - --config-setting=cmake.define.ITKPythonPackage_ITK_BINARY_REUSE:BOOL=ON \ - --config-setting=cmake.define.ITKPythonPackage_WHEEL_NAME:STRING=${wheel_name} \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - --config-setting=cmake.define.CMAKE_CXX_FLAGS:STRING="${compile_flags}" \ - --config-setting=cmake.define.CMAKE_C_FLAGS:STRING="${compile_flags}" \ - . \ - || exit 1 - done - fi - - # Remove unnecessary files for building against ITK - find ${build_path} -name '*.cpp' -delete -o -name '*.xml' -delete - rm -rf ${build_path}/Wrapping/Generators/castxml* - find ${build_path} -name '*.o' -delete - -done - -sudo /opt/python/cp311-cp311/bin/pip3 install auditwheel wheel - -if test "${ARCH}" == "x64"; then - # This step will fixup the wheel switching from 'linux' to 'manylinux' tag - for whl in dist/itk_*linux_*.whl; do - /opt/python/cp311-cp311/bin/auditwheel repair --plat manylinux${MANYLINUX_VERSION}_x86_64 ${whl} -w /work/dist/ - done -else - for whl in dist/itk_*$(uname -m).whl; do - /opt/python/cp311-cp311/bin/auditwheel repair ${whl} -w /work/dist/ - done -fi - -# auditwheel does not process this "metawheel" correctly since it does not -# have any native SO's. -mkdir -p metawheel-dist -for whl in dist/itk-*linux_*.whl; do - /opt/python/cp311-cp311/bin/wheel unpack --dest metawheel ${whl} - manylinux_version=manylinux${MANYLINUX_VERSION} - new_tag=$(basename ${whl/linux/${manylinux_version}} .whl) - sed -i "s/Tag: .*/Tag: ${new_tag}/" metawheel/itk-*/itk*.dist-info/WHEEL - /opt/python/cp311-cp311/bin/wheel pack --dest metawheel-dist metawheel/itk-* - mv metawheel-dist/*.whl dist/${new_tag}.whl - rm -rf metawheel -done -rm -rf metawheel-dist -rm dist/itk-*-linux_*.whl -rm dist/itk_*-linux_*.whl - -# Install packages and test -for PYBIN in "${PYBINARIES[@]}"; do - ${PYBIN}/pip install --user numpy - sudo ${PYBIN}/pip install --upgrade pip - ${PYBIN}/pip install itk --user --no-cache-dir --no-index -f /work/dist - (cd $HOME && ${PYBIN}/python -c 'from itk import ITKCommon;') - (cd $HOME && ${PYBIN}/python -c 'import itk; image = itk.Image[itk.UC, 2].New()') - (cd $HOME && ${PYBIN}/python -c 'import itkConfig; itkConfig.LazyLoading = False; import itk;') - (cd $HOME && ${PYBIN}/python ${script_dir}/../../docs/code/test.py ) -done - -rm -f dist/numpy*.whl diff --git a/scripts/internal/shellcheck-run.sh b/scripts/internal/shellcheck-run.sh deleted file mode 100755 index e110bb79..00000000 --- a/scripts/internal/shellcheck-run.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -( -exit_code=0 - -# -# SC1090: Can't follow non-constant source. Use a directive to specify location. -# SC2006: Use $(..) instead of legacy `..`. -# SC2046: Quote this to prevent word splitting. -# SC2086: Double quote to prevent globbing and word splitting. -# SC2153: Possible misspelling: SCRIPT_DIR may not be assigned, but script_dir is. -# SC2155: Declare and assign separately to avoid masking return values. -# - -# find all executables and run `shellcheck` -for f in $(find . -type f -not -iwholename '*.git*' | sort -u); do - if file "$f" | grep --quiet -e shell -e bash; then - shellcheck \ - -e SC1090 \ - -e SC2046 \ - -e SC2086 \ - "$f" \ - && echo "[OK]: successfully linted $f" || echo "[FAILED]: found issues linting $f" - current_exit_code=$? - if [[ $current_exit_code != 0 ]]; then - exit_code=$current_exit_code - fi - fi -done - -exit $exit_code -) diff --git a/scripts/internal/wheel_builder_utils.py b/scripts/internal/wheel_builder_utils.py deleted file mode 100644 index 422d4f51..00000000 --- a/scripts/internal/wheel_builder_utils.py +++ /dev/null @@ -1,95 +0,0 @@ -"""This module provides convenient function facilitating scripting. - -These functions have been copied from scikit-build project. -See https://github.com/scikit-build/scikit-build -""" - -import errno -import os - -from contextlib import contextmanager -from functools import wraps - - -def mkdir_p(path): - """Ensure directory ``path`` exists. If needed, parent directories - are created. - - Adapted from http://stackoverflow.com/a/600612/1539918 - """ - try: - os.makedirs(path) - except OSError as exc: # Python >2.5 - if exc.errno == errno.EEXIST and os.path.isdir(path): - pass - else: # pragma: no cover - raise - - -@contextmanager -def push_env(**kwargs): - """This context manager allow to set/unset environment variables.""" - saved_env = dict(os.environ) - for var, value in kwargs.items(): - if value is not None: - os.environ[var] = value - elif var in os.environ: - del os.environ[var] - yield - os.environ.clear() - for saved_var, saved_value in saved_env.items(): - os.environ[saved_var] = saved_value - - -class ContextDecorator(object): - """A base class or mixin that enables context managers to work as - decorators.""" - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __enter__(self): - # Note: Returning self means that in "with ... as x", x will be self - return self - - def __exit__(self, typ, val, traceback): - pass - - def __call__(self, func): - @wraps(func) - def inner(*args, **kwds): # pylint:disable=missing-docstring - with self: - return func(*args, **kwds) - - return inner - - -class push_dir(ContextDecorator): - """Context manager to change current directory.""" - - def __init__(self, directory=None, make_directory=False): - """ - :param directory: - Path to set as current working directory. If ``None`` - is passed, ``os.getcwd()`` is used instead. - - :param make_directory: - If True, ``directory`` is created. - """ - self.directory = None - self.make_directory = None - self.old_cwd = None - super(push_dir, self).__init__( - directory=directory, make_directory=make_directory - ) - - def __enter__(self): - self.old_cwd = os.getcwd() - if self.directory: - if self.make_directory: - mkdir_p(self.directory) - os.chdir(self.directory) - return self - - def __exit__(self, typ, val, traceback): - os.chdir(self.old_cwd) diff --git a/scripts/internal/windows_build_common.py b/scripts/internal/windows_build_common.py deleted file mode 100644 index 4cf2c739..00000000 --- a/scripts/internal/windows_build_common.py +++ /dev/null @@ -1,60 +0,0 @@ -__all__ = ["DEFAULT_PY_ENVS", "venv_paths"] - -from subprocess import check_call -import os -import shutil - -DEFAULT_PY_ENVS = ["310-x64", "311-x64"] - -SCRIPT_DIR = os.path.dirname(__file__) -ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..")) - - -def venv_paths(python_version): - # Create venv - venv_executable = "C:/Python%s/Scripts/virtualenv.exe" % (python_version) - venv_dir = os.path.join(ROOT_DIR, "venv-%s" % python_version) - check_call([venv_executable, venv_dir]) - - python_executable = os.path.join(venv_dir, "Scripts", "python.exe") - python_include_dir = "C:/Python%s/include" % (python_version) - - # XXX It should be possible to query skbuild for the library dir associated - # with a given interpreter. - xy_ver = python_version.split("-")[0] - - # Version-specific library (e.g., python311.lib) - required for - # CMake's FindPython3 to extract version info for Development.Module - python_library = "C:/Python%s/libs/python%s.lib" % (python_version, xy_ver) - - # Stable ABI library (python3.lib) - for Development.SABIModule - if int(xy_ver[1:]) >= 11: - python_sabi_library = "C:/Python%s/libs/python3.lib" % (python_version) - else: - python_sabi_library = python_library - - print("") - print("Python3_EXECUTABLE: %s" % python_executable) - print("Python3_INCLUDE_DIR: %s" % python_include_dir) - print("Python3_LIBRARY: %s" % python_library) - print("Python3_SABI_LIBRARY: %s" % python_sabi_library) - - pip = os.path.join(venv_dir, "Scripts", "pip.exe") - - ninja_executable = os.path.join(venv_dir, "Scripts", "ninja.exe") - if not os.path.exists(ninja_executable): - ninja_executable = shutil.which("ninja.exe") - print("NINJA_EXECUTABLE:%s" % ninja_executable) - - # Update PATH - path = os.path.join(venv_dir, "Scripts") - - return ( - python_executable, - python_include_dir, - python_library, - python_sabi_library, - pip, - ninja_executable, - path, - ) diff --git a/scripts/lint-shell-scripts.sh b/scripts/lint-shell-scripts.sh deleted file mode 100755 index 47e10c9a..00000000 --- a/scripts/lint-shell-scripts.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -pushd $SCRIPT_DIR > /dev/null - -docker run --rm -it \ - --name df-shellcheck \ - -v $(pwd):/usr/src:ro \ - --workdir /usr/src \ - r.j3ss.co/shellcheck ./internal/shellcheck-run.sh - -exit_code=$? - -popd > /dev/null - -exit $exit_code diff --git a/scripts/macpython-build-common.sh b/scripts/macpython-build-common.sh deleted file mode 100644 index 123ced58..00000000 --- a/scripts/macpython-build-common.sh +++ /dev/null @@ -1,79 +0,0 @@ -# Content common to macpython-build-wheels.sh and -# macpython-build-module-wheels.sh - -set -e -x - -SCRIPT_DIR=$(cd $(dirname $0) || exit 1; pwd) - -MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions - -# ----------------------------------------------------------------------- -# Script argument parsing -# -usage() -{ - echo "Usage: - macpython-build-common - [ -h | --help ] show usage - [ -c | --cmake_options ] space-separated string of CMake options to forward to the module (e.g. \"--config-setting=cmake.define.BUILD_TESTING=OFF\") - [ -- python_versions ] build wheel for a specific python version(s). (e.g. -- 3.10 3.11)" - exit 2 -} - -PYTHON_VERSIONS="" -CMAKE_OPTIONS="" - -while (( "$#" )); do - case "$1" in - -c|--cmake_options) - CMAKE_OPTIONS="$2"; - shift 2;; - -h|--help) - usage; - break;; - --) - shift; - break;; - *) - # Parse any unrecognized arguments as python versions - PYTHON_VERSIONS="${PYTHON_VERSIONS} $1"; - shift;; - esac -done - -# Parse all arguments after "--" as python versions -PYTHON_VERSIONS="${PYTHON_VERSIONS} $@" -# Trim whitespace -PYTHON_VERSIONS=$(xargs <<< "${PYTHON_VERSIONS}") - -# Versions can be restricted by passing them in as arguments to the script -# For example, -# macpython-build-wheels.sh 3.10 -if [[ -z "${PYTHON_VERSIONS}" ]]; then - PYBINARIES=(${MACPYTHON_PY_PREFIX}/*) -else - PYBINARIES=() - for version in "$PYTHON_VERSIONS"; do - PYBINARIES+=(${MACPYTHON_PY_PREFIX}/*${version}*) - done -fi - -VENVS=() -mkdir -p ${SCRIPT_DIR}/../venvs -for PYBIN in "${PYBINARIES[@]}"; do - if [[ $(basename $PYBIN) = "Current" ]]; then - continue - fi - py_mm=$(basename ${PYBIN}) - VENV=${SCRIPT_DIR}/../venvs/${py_mm} - VENVS+=(${VENV}) -done - -# ----------------------------------------------------------------------- -# Ensure that requirements are met -brew update -brew info doxygen | grep --quiet 'Not installed' && brew install doxygen -brew info ninja | grep --quiet 'Not installed' && brew install ninja -NINJA_EXECUTABLE=$(which ninja) -brew info cmake | grep --quiet 'Not installed' && brew install cmake -CMAKE_EXECUTABLE=$(which cmake) diff --git a/scripts/macpython-build-module-deps.sh b/scripts/macpython-build-module-deps.sh deleted file mode 100644 index a74e9da7..00000000 --- a/scripts/macpython-build-module-deps.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash - -######################################################################## -# Run this script in an ITK external module directory to generate -# build artifacts for prerequisite ITK MacOS modules. -# -# Module dependencies are built in a flat directory structure regardless -# of recursive dependencies. Prerequisite sources are required to be passed -# in the order in which they should be built. -# For example, if ITKTargetModule depends on ITKTargetModuleDep2 which -# depends on ITKTargetModuleDep1, the output directory structure -# will look like this: -# -# / ITKTargetModule -# -- / ITKTargetModuleDep1 -# -- / ITKTargetModuleDep2 -# .. -# -# =========================================== -# ENVIRONMENT VARIABLES -# -# - `ITK_MODULE_PREQ`: Prerequisite ITK modules that must be built before the requested module. -# Format is `/@:/@:...`. -# For instance, `export ITK_MODULE_PREQ=InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` -# -######################################################################## - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -if [[ ! -f "${script_dir}/macpython-download-cache-and-build-module-wheels.sh" ]]; then - echo "Could not find download script to use for building module dependencies!" - exit 1 -fi - -# Temporarily update prerequisite environment variable to prevent infinite recursion. -ITK_MODULE_PREQ_TOPLEVEL=${ITK_MODULE_PREQ} -ITK_USE_LOCAL_PYTHON_TOPLEVEL=${ITK_USE_LOCAL_PYTHON} -export ITK_MODULE_PREQ="" -export ITK_USE_LOCAL_PYTHON="ON" - -######################################################################## -echo "Building ITK module dependencies: ${ITK_MODULE_PREQ_TOPLEVEL}" - -for MODULE_INFO in ${ITK_MODULE_PREQ_TOPLEVEL//:/ }; do - MODULE_ORG=`(echo ${MODULE_INFO} | cut -d'/' -f 1)` - MODULE_NAME=`(echo ${MODULE_INFO} | cut -d'@' -f 1 | cut -d'/' -f 2)` - MODULE_TAG=`(echo ${MODULE_INFO} | cut -d'@' -f 2)` - - MODULE_UPSTREAM=https://github.com/${MODULE_ORG}/${MODULE_NAME}.git - echo "Cloning from ${MODULE_UPSTREAM}" - git clone ${MODULE_UPSTREAM} - - pushd ${MODULE_NAME} - git checkout ${MODULE_TAG} - cp ${script_dir}/macpython-download-cache-and-build-module-wheels.sh . - echo "Building dependency ${MODULE_NAME}" - ./macpython-download-cache-and-build-module-wheels.sh $@ - popd - - cp ./${MODULE_NAME}/include/* include/ - find ${MODULE_NAME}/wrapping -name '*.in' -print -exec cp {} wrapping \; - find ${MODULE_NAME}/wrapping -name '*.init' -print -exec cp {} wrapping \; - find ${MODULE_NAME}/*build/*/include -type f -print -exec cp {} include \; - rm -f ./${MODULE_NAME}/ITKPythonBuilds-macosx.tar.zst -done - -# Restore environment variable -export ITK_MODULE_PREQ=${ITK_MODULE_PREQ_TOPLEVEL} -export ITK_USE_LOCAL_PYTHON=${ITK_USE_LOCAL_PYTHON_TOPLEVEL} -ITK_MODULE_PREQ_TOPLEVEL="" -ITK_USE_LOCAL_PYTHON_TOPLEVEL="" - -# Summarize disk usage for debugging -du -sh ./* | sort -hr | head -n 20 - -echo "Done building ITK external module dependencies" diff --git a/scripts/macpython-build-module-wheels.sh b/scripts/macpython-build-module-wheels.sh deleted file mode 100755 index 3c583d4d..00000000 --- a/scripts/macpython-build-module-wheels.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash - -######################################################################## -# Run this script in an ITK external module directory to build the -# Python wheel packages for macOS for an ITK external module. -# -# ======================================================================== -# PARAMETERS -# -# Versions can be restricted by passing them in as arguments to the script. -# For example, -# -# scripts/macpython-build-module-wheels.sh 3.10 3.11 -# Shared libraries can be included in the wheel by exporting them to DYLD_LIBRARY_PATH before -# running this script. -# -# =========================================== -# ENVIRONMENT VARIABLES -# -# These variables are set with the `export` bash command before calling the script. -# For example, -# -# export DYLD_LIBRARY_PATH="/path/to/libs" -# scripts/macpython-build-module-wheels.sh 3.10 3.11 -# -# `DYLD_LIBRARY_PATH`: Shared libraries to be included in the resulting wheel. -# For instance, `export DYLD_LIBRARY_PATH="/path/to/OpenCL.so:/path/to/OpenCL.so.1.2"` -# -# `ITK_MODULE_PREQ`: Prerequisite ITK modules that must be built before the requested module. -# Format is `/@:/@:...`. -# For instance, `export ITK_MODULE_PREQ=InsightSoftwareConsortium/ITKMeshToPolyData@v0.10.0` -# -######################################################################## - - -# ----------------------------------------------------------------------- -# (Optional) Build ITK module dependencies - -script_dir=$(cd $(dirname $0) || exit 1; pwd) - -if [[ -n ${ITK_MODULE_PREQ} ]]; then - source "${script_dir}/macpython-build-module-deps.sh" -fi - -# ----------------------------------------------------------------------- -# These variables are set in common script: -# -# * CMAKE_EXECUTABLE -# * CMAKE_OPTIONS -# * MACPYTHON_PY_PREFIX -# * PYBINARIES -# * PYTHON_VERSIONS -# * NINJA_EXECUTABLE -# * SCRIPT_DIR -# * SKBUILD_DIR -# * VENVS=() - -MACPYTHON_PY_PREFIX="" -SCRIPT_DIR="" -VENVS=() - -source "${script_dir}/macpython-build-common.sh" -# ----------------------------------------------------------------------- - -VENV="${VENVS[0]}" -Python3_EXECUTABLE=${VENV}/bin/python3 -dot_clean ${VENV} -${Python3_EXECUTABLE} -m pip install --no-cache delocate -DELOCATE_LISTDEPS=${VENV}/bin/delocate-listdeps -DELOCATE_WHEEL=${VENV}/bin/delocate-wheel -DELOCATE_PATCH=${VENV}/bin/delocate-patch -# So delocate can find the libs -export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${script_dir}/../oneTBB-prefix/lib - -# Compile wheels re-using standalone project and archive cache -for VENV in "${VENVS[@]}"; do - py_mm=$(basename ${VENV}) - Python3_EXECUTABLE=${VENV}/bin/python - Python3_INCLUDE_DIR=$( find -L ${MACPYTHON_PY_PREFIX}/${py_mm}/include -name Python.h -exec dirname {} \; ) - - echo "" - echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" - echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - - if [[ $(arch) == "arm64" ]]; then - plat_name="macosx-15.0-arm64" - osx_target="15.0" - osx_arch="arm64" - build_path="${SCRIPT_DIR}/../ITK-${py_mm}-macosx_arm64" - else - plat_name="macosx-15.0-x86_64" - osx_target="15.0" - osx_arch="x86_64" - build_path="${SCRIPT_DIR}/../ITK-${py_mm}-macosx_x86_64" - fi - if [[ ! -z "${MACOSX_DEPLOYMENT_TARGET}" ]]; then - osx_target="${MACOSX_DEPLOYMENT_TARGET}" - fi - export MACOSX_DEPLOYMENT_TARGET=${osx_target} - - if [[ -e $PWD/requirements-dev.txt ]]; then - ${Python3_EXECUTABLE} -m pip install --upgrade -r $PWD/requirements-dev.txt - fi - itk_build_path="${build_path}" - py_minor=$(echo $py_mm | cut -d '.' -f 2) - wheel_py_api="" - if test $py_minor -ge 11; then - wheel_py_api=cp3$py_minor - fi - ${Python3_EXECUTABLE} -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ - --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_path} \ - --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ - --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - --config-setting=cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - --config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ - --config-setting=wheel.py-api=$wheel_py_api \ - --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ - || exit 1 -done - -for wheel in $PWD/dist/*.whl; do - ${DELOCATE_LISTDEPS} $wheel # lists library dependencies - ${DELOCATE_WHEEL} $wheel # copies library dependencies into wheel -done diff --git a/scripts/macpython-build-tarball.sh b/scripts/macpython-build-tarball.sh deleted file mode 100755 index c4a727b4..00000000 --- a/scripts/macpython-build-tarball.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# This script creates a tarball of the ITK Python package build tree. It is -# downloaded by the external module build scripts and used to build their -# Python package on GitHub CI services. - -#tbb_contents="ITKPythonPackage/oneTBB*" -tbb_contents="" -arch_postfix="" -if test $(arch) == "arm64"; then - arch_postfix="-arm64" - tbb_contents="" -fi - -pushd /Users/svc-dashboard/D/P > /dev/null -dot_clean ITKPythonPackage -tar -cf ITKPythonBuilds-macosx${arch_postfix}.tar \ - ITKPythonPackage/ITK-* \ - ${tbb_contents} \ - ITKPythonPackage/venvs \ - ITKPythonPackageRequiredExtractionDir.txt \ - ITKPythonPackage/scripts -zstd -f \ - -10 \ - -T6 \ - --long=31 \ - ./ITKPythonBuilds-macosx${arch_postfix}.tar \ - -o ./ITKPythonBuilds-macosx${arch_postfix}.tar.zst -popd > /dev/null diff --git a/scripts/macpython-build-wheels.sh b/scripts/macpython-build-wheels.sh deleted file mode 100755 index 60307e16..00000000 --- a/scripts/macpython-build-wheels.sh +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env bash - -# Run this script to build the ITK Python wheel packages for macOS. -# -# Versions can be restricted by passing them in as arguments to the script -# For example, -# -# scripts/macpython-build-wheels.sh 3.10 -# -# Shared libraries can be included in the wheel by exporting them to DYLD_LIBRARY_PATH before -# running this script. -# -# For example, -# -# export DYLD_LIBRARY_PATH="/path/to/libs" -# scripts/macpython-build-module-wheels.sh 3.10 -# - -# ----------------------------------------------------------------------- -# These variables are set in common script: -# -# * CMAKE_EXECUTABLE -# * CMAKE_OPTIONS -# * MACPYTHON_PY_PREFIX -# * PYBINARIES -# * PYTHON_VERSIONS -# * NINJA_EXECUTABLE -# * SCRIPT_DIR -# * VENVS=() - -MACPYTHON_PY_PREFIX="" -PYBINARIES="" -SCRIPT_DIR="" - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/macpython-build-common.sh" - -# ----------------------------------------------------------------------- -# Remove previous virtualenv's -rm -rf ${SCRIPT_DIR}/../venvs -# Create virtualenv's -VENVS=() -mkdir -p ${SCRIPT_DIR}/../venvs -for PYBIN in "${PYBINARIES[@]}"; do - if [[ $(basename $PYBIN) = "Current" ]]; then - continue - fi - py_mm=$(basename ${PYBIN}) - VENV=${SCRIPT_DIR}/../venvs/${py_mm} - VIRTUALENV_EXECUTABLE="${PYBIN}/bin/python3 -m venv" - ${VIRTUALENV_EXECUTABLE} ${VENV} - VENVS+=(${VENV}) -done - -VENV="${VENVS[0]}" -Python3_EXECUTABLE=${VENV}/bin/python3 -${Python3_EXECUTABLE} -m pip install --upgrade pip -${Python3_EXECUTABLE} -m pip install --no-cache delocate -DELOCATE_LISTDEPS=${VENV}/bin/delocate-listdeps -DELOCATE_WHEEL=${VENV}/bin/delocate-wheel -DELOCATE_PATCH=${VENV}/bin/delocate-patch - -build_type="Release" - -if [[ $(arch) == "arm64" ]]; then - osx_target="15.0" - osx_arch="arm64" - use_tbb="OFF" -else - osx_target="15.0" - osx_arch="x86_64" - use_tbb="OFF" -fi - -export MACOSX_DEPLOYMENT_TARGET=${osx_target} - -# Build standalone project and populate archive cache -tbb_dir=$PWD/oneTBB-prefix/lib/cmake/TBB -n_processors=$(sysctl -n hw.ncpu) -# So delocate can find the libs -export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:$PWD/oneTBB-prefix/lib -mkdir -p ITK-source -pushd ITK-source > /dev/null 2>&1 - ${CMAKE_EXECUTABLE} -DITKPythonPackage_BUILD_PYTHON:PATH=0 \ - -DITKPythonPackage_USE_TBB:BOOL=${use_tbb} \ - -G Ninja \ - -DCMAKE_BUILD_TYPE:STRING=${build_type} \ - -DCMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ - -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - -DCMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - ${SCRIPT_DIR}/../ - ${NINJA_EXECUTABLE} -j$n_processors -l$n_processors -popd > /dev/null 2>&1 - -SINGLE_WHEEL=0 - -# Compile wheels re-using standalone project and archive cache -for VENV in "${VENVS[@]}"; do - py_mm=$(basename ${VENV}) - export Python3_EXECUTABLE=${VENV}/bin/python - Python3_INCLUDE_DIR=$( find -L ${MACPYTHON_PY_PREFIX}/${py_mm}/include -name Python.h -exec dirname {} \; ) - - echo "" - echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" - echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - - ${Python3_EXECUTABLE} -m pip install --upgrade -r ${SCRIPT_DIR}/../requirements-dev.txt - - if [[ $(arch) == "arm64" ]]; then - plat_name="macosx-15.0-arm64" - build_path="${SCRIPT_DIR}/../ITK-${py_mm}-macosx_arm64" - else - plat_name="macosx-15.0-x86_64" - build_path="${SCRIPT_DIR}/../ITK-${py_mm}-macosx_x86_64" - fi - if [[ ! -z "${MACOSX_DEPLOYMENT_TARGET}" ]]; then - osx_target="${MACOSX_DEPLOYMENT_TARGET}" - fi - source_path=${SCRIPT_DIR}/../ITK-source/ITK - PYPROJECT_CONFIGURE="${script_dir}/pyproject_configure.py" - - # Clean up previous invocations - rm -rf ${build_path} - - if [[ ${SINGLE_WHEEL} == 1 ]]; then - - echo "#" - echo "# Build single ITK wheel" - echo "#" - - # Configure pyproject.toml - ${Python3_EXECUTABLE} ${PYPROJECT_CONFIGURE} "itk" - # Generate wheel - ${Python3_EXECUTABLE} -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ - --config-setting=cmake.define.ITK_SOURCE_DIR:PATH=${source_path} \ - --config-setting=cmake.define.ITK_BINARY_DIR:PATH=${build_path} \ - --config-setting=cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - --config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - --config-setting=cmake.define.Module_ITKTBB:BOOL=${use_tbb} \ - --config-setting=cmake.define.TBB_DIR:PATH=${tbb_dir} \ - . \ - ${CMAKE_OPTIONS} - - else - - echo "#" - echo "# Build multiple ITK wheels" - echo "#" - - # Build ITK python - ( - mkdir -p ${build_path} \ - && cd ${build_path} \ - && cmake \ - -DCMAKE_BUILD_TYPE:STRING=${build_type} \ - -DITK_SOURCE_DIR:PATH=${source_path} \ - -DITK_BINARY_DIR:PATH=${build_path} \ - -DBUILD_TESTING:BOOL=OFF \ - -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - -DCMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - -DITK_WRAP_unsigned_short:BOOL=ON \ - -DITK_WRAP_double:BOOL=ON \ - -DITK_WRAP_complex_double:BOOL=ON \ - -DITK_WRAP_IMAGE_DIMS:STRING="2;3;4" \ - -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - -DWRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL=ON \ - "-DPY_SITE_PACKAGES_PATH:PATH=." \ - -DITK_LEGACY_SILENT:BOOL=ON \ - -DITK_WRAP_PYTHON:BOOL=ON \ - -DITK_WRAP_DOC:BOOL=ON \ - -DModule_ITKTBB:BOOL=${use_tbb} \ - -DTBB_DIR:PATH=${tbb_dir} \ - ${CMAKE_OPTIONS} \ - -G Ninja \ - ${source_path} \ - && ninja -j$n_processors -l$n_processors \ - || exit 1 - ) - - wheel_names=$(cat ${SCRIPT_DIR}/WHEEL_NAMES.txt) - for wheel_name in ${wheel_names}; do - # Configure pyproject.toml - ${Python3_EXECUTABLE} ${PYPROJECT_CONFIGURE} ${wheel_name} - # Generate wheel - ${Python3_EXECUTABLE} -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.ITK_SOURCE_DIR:PATH=${source_path} \ - --config-setting=cmake.define.ITK_BINARY_DIR:PATH=${build_path} \ - --config-setting=cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - --config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - --config-setting=cmake.define.ITKPythonPackage_USE_TBB:BOOL=${use_tbb} \ - --config-setting=cmake.define.ITKPythonPackage_ITK_BINARY_REUSE:BOOL=ON \ - --config-setting=cmake.define.ITKPythonPackage_WHEEL_NAME:STRING=${wheel_name} \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - . \ - ${CMAKE_OPTIONS} \ - || exit 1 - done - - fi - - # Remove unnecessary files for building against ITK - find ${build_path} -name '*.cpp' -delete -o -name '*.xml' -delete - rm -rf ${build_path}/Wrapping/Generators/castxml* - find ${build_path} -name '*.o' -delete -done - -if [[ $(arch) != "arm64" ]]; then - for wheel in dist/itk_*.whl; do - echo "Delocating $wheel" - ${DELOCATE_LISTDEPS} $wheel # lists library dependencies - ${DELOCATE_WHEEL} $wheel # copies library dependencies into wheel - done -fi - -for VENV in "${VENVS[@]}"; do - ${VENV}/bin/pip install numpy - ${VENV}/bin/pip install itk --no-cache-dir --no-index -f ${SCRIPT_DIR}/../dist - (cd $HOME && ${VENV}/bin/python -c 'import itk;') - (cd $HOME && ${VENV}/bin/python -c 'import itk; image = itk.Image[itk.UC, 2].New()') - (cd $HOME && ${VENV}/bin/python -c 'import itkConfig; itkConfig.LazyLoading = False; import itk;') - (cd $HOME && ${VENV}/bin/python ${SCRIPT_DIR}/../docs/code/test.py ) -done diff --git a/scripts/macpython-install-python.sh b/scripts/macpython-install-python.sh deleted file mode 100755 index 70a57b7a..00000000 --- a/scripts/macpython-install-python.sh +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env bash - -# Download and install Python.org's MacPython and install Pip - -# Adapted from https://github.com/matthew-brett/multibuild -# osx_utils.sh -#The multibuild package, including all examples, code snippets and attached -#documentation is covered by the 2-clause BSD license. - - #Copyright (c) 2013-2016, Matt Terry and Matthew Brett; all rights - #reserved. - - #Redistribution and use in source and binary forms, with or without - #modification, are permitted provided that the following conditions are - #met: - - #1. Redistributions of source code must retain the above copyright notice, - #this list of conditions and the following disclaimer. - - #2. Redistributions in binary form must reproduce the above copyright - #notice, this list of conditions and the following disclaimer in the - #documentation and/or other materials provided with the distribution. - - #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - #IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - #THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - #PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - #CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - #EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - #PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - #PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - #LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - #NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - #SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -set -x - -MACPYTHON_URL=https://www.python.org/ftp/python -MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions -MACPYTHON_DEFAULT_OSX="10.6" -MB_PYTHON_OSX_VER=${MB_PYTHON_OSX_VER:-$MACPYTHON_DEFAULT_OSX} -GET_PIP_URL=https://bootstrap.pypa.io/get-pip.py -DOWNLOADS_SDIR=downloads -WORKING_SDIR=working - -# As of 2 November 2022 - latest Python of each version with binary download -# available. -# See: https://www.python.org/downloads/mac-osx/ -LATEST_2p7=2.7.18 -LATEST_3p5=3.5.4 -LATEST_3p6=3.6.8 -LATEST_3p7=3.7.9 -LATEST_3p8=3.8.10 -LATEST_3p9=3.9.13 -LATEST_3p10=3.10.11 -LATEST_3p11=3.11.4 -LATEST_3p12=3.12.0 - - -function check_python { - if [ -z "$PYTHON_EXE" ]; then - echo "PYTHON_EXE variable not defined" - exit 1 - fi -} - -function check_pip { - if [ -z "$PIP_CMD" ]; then - echo "PIP_CMD variable not defined" - exit 1 - fi -} - -function check_var { - if [ -z "$1" ]; then - echo "required variable not defined" - exit 1 - fi -} - -function get_py_digit { - check_python - $PYTHON_EXE -c "import sys; print(sys.version_info[0])" -} - -function get_py_mm { - check_python - $PYTHON_EXE -c "import sys; print('{0}.{1}'.format(*sys.version_info[0:2]))" -} - -function get_py_mm_nodot { - check_python - $PYTHON_EXE -c "import sys; print('{0}{1}'.format(*sys.version_info[0:2]))" -} - -function get_py_prefix { - check_python - $PYTHON_EXE -c "import sys; print(sys.prefix)" -} - -function fill_pyver { - # Convert major or major.minor format to major.minor.micro - # - # Hence: - # 2 -> 2.7.11 (depending on LATEST_2p7 value) - # 2.7 -> 2.7.11 (depending on LATEST_2p7 value) - local ver=$1 - check_var $ver - if [[ $ver =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then - # Major.minor.micro format already - echo $ver - elif [ $ver == 2 ] || [ $ver == "2.7" ]; then - echo $LATEST_2p7 - elif [ $ver == 3 ] || [ $ver == "3.11" ]; then - echo $LATEST_3p11 - elif [ $ver == "3.12" ]; then - echo $LATEST_3p12 - elif [ $ver == "3.10" ]; then - echo $LATEST_3p10 - elif [ $ver == "3.9" ]; then - echo $LATEST_3p9 - elif [ $ver == "3.8" ]; then - echo $LATEST_3p8 - elif [ $ver == "3.7" ]; then - echo $LATEST_3p7 - elif [ $ver == "3.6" ]; then - echo $LATEST_3p6 - elif [ $ver == "3.5" ]; then - echo $LATEST_3p5 - else - echo "Can't fill version $ver" 1>&2 - exit 1 - fi -} - -function macpython_sdk_list_for_version { - # return a list of SDK targets supported for a given CPython version - # Parameters - # $py_version (python version in major.minor.extra format) - # eg - # macpython_sdks_for_version 2.7.15 - # >> 10.6 10.9 - local _ver=$(fill_pyver $1) - local _major=${_ver%%.*} - local _return - - if [ "${PLAT}" = "arm64" ]; then - _return="11.0" - elif [ "$_major" -eq "2" ]; then - [ $(lex_ver $_ver) -lt $(lex_ver 2.7.18) ] && _return="10.6" - [ $(lex_ver $_ver) -ge $(lex_ver 2.7.15) ] && _return="$_return 10.9" - elif [ "$_major" -eq "3" ]; then - [ $(lex_ver $_ver) -lt $(lex_ver 3.8) ] && _return="10.6" - [ $(lex_ver $_ver) -ge $(lex_ver 3.6.5) ] && _return="$_return 10.9" - else - echo "Error version=${_ver}, expecting 2.x or 3.x" 1>&2 - exit 1 - fi - echo $_return -} - -function macpython_sdk_for_version { - # assumes the output of macpython_sdk_list_for_version is a list - # of SDK versions XX.Y in sorted order, eg "10.6 10.9" or "10.9" - echo $(macpython_sdk_list_for_version $1) | awk -F' ' '{print $NF}' -} - -function pyinst_ext_for_version { - # echo "pkg" or "dmg" depending on the passed Python version - # Parameters - # $py_version (python version in major.minor.extra format) - # - # Earlier Python installers are .dmg, later are .pkg. - local py_version=$1 - check_var $py_version - py_version=$(fill_pyver $py_version) - local py_0=${py_version:0:1} - if [ $py_0 -eq 2 ]; then - if [ "$(lex_ver $py_version)" -ge "$(lex_ver 2.7.9)" ]; then - echo "pkg" - else - echo "dmg" - fi - elif [ $py_0 -ge 3 ]; then - echo "pkg" - fi -} - -function pyinst_fname_for_version { - # echo filename for OSX installer file given Python and minimum - # macOS versions - # Parameters - # $py_version (Python version in major.minor.extra format) - # $py_osx_ver: {major.minor | not defined} - # if defined, the minimum macOS SDK version that Python is - # built for, eg: "10.6" or "10.9", if not defined, infers - # this from $py_version using macpython_sdk_for_version - local py_version=$1 - local py_osx_ver=${2:-$(macpython_sdk_for_version $py_version)} - local inst_ext=$(pyinst_ext_for_version $py_version) - if [ "${PLAT:-}" == "arm64" ] || [ "${PLAT:-}" == "universal2" ]; then - if [ "$py_version" == "3.9.1" ]; then - echo "python-${py_version}-macos11.0.${inst_ext}" - else - echo "python-${py_version}-macos11.${inst_ext}" - fi - else - if [ "$py_version" == "3.7.9" ]; then - echo "python-${py_version}-macosx${py_osx_ver}.${inst_ext}" - else - echo "python-${py_version}-macos${py_osx_ver}.${inst_ext}" - fi - fi -} - -function get_macpython_arch { - # echo arch (e.g. intel or x86_64), extracted from the distutils platform tag - # Parameters - # $distutils_plat PEP425 style platform tag, or if not provided, calls - # the function get_distutils_platform, provided by - # common_utils.sh. Fails if this is not a mac platform - # - # Note: MUST only be called after the version of Python used to build the - # target wheel has been installed and is on the path - local distutils_plat=${1:-$(get_distutils_platform)} - if [[ $distutils_plat =~ macosx-(1[0-9]\.[0-9]+)-(.*) ]]; then - echo ${BASH_REMATCH[2]} - else - echo "Error parsing macOS distutils platform '$distutils_plat'" - exit 1 - fi -} - -function get_macpython_osx_ver { - # echo minimum macOS version (e.g. 10.9) from the distutils platform tag - # Parameters - # $distutils_plat PEP425 style platform tag, or if not provided, calls - # the function get_distutils_platform, provided by - # common_utils.sh. Fails if this is not a mac platform - # - # Note: MUST only be called after the version of Python used to build the - # target wheel has been installed and is on the path - local distutils_plat=${1:-$(get_distutils_platform)} - if [[ $distutils_plat =~ macosx-(1[0-9]\.[0-9]+)-(.*) ]]; then - echo ${BASH_REMATCH[1]} - else - echo "Error parsing macOS distutils platform '$distutils_plat'" - exit 1 - fi -} - -function macpython_arch_for_version { - # echo arch (intel or x86_64) that a version of Python is expected - # to be built for - # Parameters - # $py_ver Python version, in the format (major.minor.patch) for - # CPython, or pypy-(major.minor) for PyPy - # $py_osx_ver minimum macOS version the target Python is built for - # (major.minor) - local py_ver=$1 - local py_osx_ver=${2:-$MB_PYTHON_OSX_VER} - check_var $1 - if [[ $(macpython_impl_for_version $py_ver) == "cp" ]]; then - if [[ "$py_osx_ver" == "10.6" ]]; then - echo "intel" - elif [[ "$py_osx_ver" == "10.9" ]]; then - echo "x86_64" - else - echo "Unexpected CPython macOS version: ${py_osx_ver}, supported values: 10.6 and 10.9" - exit 1 - fi - else - echo "x86_64" - fi -} - -function macpython_impl_for_version { - # echo Python implementation (cp for CPython, pp for PyPy) given a - # suitably formatted version string - # Parameters: - # $version : [implementation-]major[.minor[.patch]] - # Python implementation, e.g. "3.6" for CPython or - # "pypy-5.4" for PyPy - local version=$1 - check_var $1 - if [[ "$version" =~ ^pypy ]]; then - echo pp - elif [[ "$version" =~ ([0-9\.]+) ]]; then - echo cp - else - echo "config error: Issue parsing this implementation in install_python:" - echo " version=$version" - exit 1 - fi -} - -function strip_macpython_ver_prefix { - # strip any implementation prefix from a Python version string - # Parameters: - # $version : [implementation-]major[.minor[.patch]] - # Python implementation, e.g. "3.6" for CPython or - # "pypy-5.4" for PyPy - local version=$1 - check_var $1 - if [[ "$version" =~ (pypy-)?([0-9\.]+) ]]; then - echo ${BASH_REMATCH[2]} - fi -} - -function install_macpython { - # Install Python and set $PYTHON_EXE to the installed executable - # Parameters: - # $version : [implementation-]major[.minor[.patch]] - # The Python implementation to install, e.g. "3.6", "pypy-5.4" or "pypy3.6-7.2" - # $py_osx_ver: {major.minor | not defined} - # if defined, the macOS version that CPython is built for, e.g. - # "10.6" or "10.9". Ignored for PyPy - local version=$1 - local py_osx_ver=$2 - local impl=$(macpython_impl_for_version $version) - if [[ "$impl" == "pp" ]]; then - install_pypy $version - elif [[ "$impl" == "cp" ]]; then - local stripped_ver=$(strip_macpython_ver_prefix $version) - install_mac_cpython $stripped_ver $py_osx_ver - else - echo "Unexpected Python impl: ${impl}" - exit 1 - fi -} - -function install_mac_cpython { - # Installs Python.org Python - # Parameters - # $py_version - # Version given in major or major.minor or major.minor.micro e.g - # "3" or "3.7" or "3.7.1". - # $py_osx_ver - # {major.minor | not defined} - # if defined, the macOS version that Python is built for, e.g. - # "10.6" or "10.9" - # sets $PYTHON_EXE variable to Python executable - local py_version=$(fill_pyver $1) - local py_osx_ver=$2 - #local py_stripped=$(strip_ver_suffix $py_version) - local py_stripped=$py_version - local py_inst=$(pyinst_fname_for_version $py_version $py_osx_ver) - local inst_path=$DOWNLOADS_SDIR/$py_inst - local retval="" - mkdir -p $DOWNLOADS_SDIR - # exit early on curl errors, but don't let it exit the shell - curl -f $MACPYTHON_URL/$py_stripped/${py_inst} > $inst_path || retval=$? - if [ ${retval:-0} -ne 0 ]; then - echo "Python download failed! Check ${py_inst} exists on the server." - exit $retval - fi - - if [ "${py_inst: -3}" == "dmg" ]; then - hdiutil attach $inst_path -mountpoint /Volumes/Python - inst_path=/Volumes/Python/Python.mpkg - fi - sudo installer -pkg $inst_path -target / - local py_mm=${py_version%.*} - PYTHON_EXE=$MACPYTHON_PY_PREFIX/$py_mm/bin/python$py_mm - # Install certificates for Python 3.6 - local inst_cmd="/Applications/Python ${py_mm}/Install Certificates.command" - if [ -e "$inst_cmd" ]; then - sh "$inst_cmd" - fi - PIP_CMD="$MACPYTHON_PY_PREFIX/$py_mm/bin/python$py_mm -m pip" - $PIP_CMD install --upgrade pip - export PIP_CMD -} - -function install_virtualenv { - # Generic install of virtualenv - # Installs virtualenv into python given by $PYTHON_EXE - # Assumes virtualenv will be installed into same directory as $PYTHON_EXE - check_pip - # Travis VMS install virtualenv for system python by default - force - # install even if installed already - $PIP_CMD install virtualenv --ignore-installed - check_python - VIRTUALENV_CMD="$(dirname $PYTHON_EXE)/virtualenv" - export VIRTUALENV_CMD -} - -function make_workon_venv { - # Make a virtualenv in given directory ('venv' default) - # Set $PYTHON_EXE, $PIP_CMD to virtualenv versions - # Parameter $venv_dir - # directory for virtualenv - local venv_dir=$1 - if [ -z "$venv_dir" ]; then - venv_dir="venv" - fi - venv_dir=`abspath $venv_dir` - check_python - $PYTHON_EXE -m virtualenv $venv_dir - PYTHON_EXE=$venv_dir/bin/python - PIP_CMD=$venv_dir/bin/pip -} - -# Remove previous versions -#echo "Remove and update Python files at ${MACPYTHON_FRAMEWORK}" -#sudo rm -rf ${MACPYTHON_FRAMEWORK} - -if test "$(arch)" == "arm64"; then - echo "we are arm" - PLAT=arm64 - for pyversion in $LATEST_3p10 $LATEST_3p11; do - install_macpython $pyversion 11 - install_virtualenv - done -else - # Deployment target requirements: - # * 10.9: Python 3.7 - # * 11: Python >= 3.8 - for pyversion in $LATEST_3p10 $LATEST_3p11; do - install_macpython $pyversion 11 - install_virtualenv - done -fi diff --git a/scripts/oci_exe.sh b/scripts/oci_exe.sh deleted file mode 100644 index f3d7cffb..00000000 --- a/scripts/oci_exe.sh +++ /dev/null @@ -1,23 +0,0 @@ -function ociExe() { - # Check for OCI_EXE environmental variable - if [[ -n "$OCI_EXE" && -x "$OCI_EXE" ]]; then - echo "$OCI_EXE" - return - fi - - # Check for podman executable - if which podman > /dev/null 2>&1; then - echo "podman" - return - fi - - # Check for docker executable - if which docker > /dev/null 2>&1; then - echo "docker" - return - fi - - # If none of the above exist, return nothing - echo "Could not find podman or docker executable" >&2 - exit 1 -} \ No newline at end of file diff --git a/scripts/update_python_version.py b/scripts/update_python_version.py deleted file mode 100755 index f69207d3..00000000 --- a/scripts/update_python_version.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python - -"""Update the ITKPythonPackage version based off the ITK Git nightly-master -branch.""" - -import argparse -import sys -import os -import subprocess -from datetime import datetime -from packaging.version import Version - -argparser = argparse.ArgumentParser(description=__doc__) -argparser.add_argument("itkSourceDir") - -args = argparser.parse_args() -itkSourceDir = args.itkSourceDir -if not os.path.exists(os.path.join(itkSourceDir, ".git")): - print("itkSourceDir does not appear to be a git repository!") - sys.exit(1) - -itkPythonPackageDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -os.chdir(itkSourceDir) -# "Wed Feb 8 15:21:09 2017"\n -commitDate = subprocess.check_output( - ["git", "show", "-s", "--date=local", '--format="%cd"'] -) -# Wed Feb 8 15:21:09 2017 -commitDate = commitDate.strip()[1:-1] -# Wed Feb 08 15:21:09 2017 -commitDate = commitDate.split(" ") -commitDate[2] = "{:02d}".format(int(commitDate[2])) -commitDate = " ".join(commitDate) -# 2017-02-08 -commitDateDashes = datetime.strptime(commitDate, "%a %b %d %H:%M:%S %Y").strftime( - "%Y-%m-%d" -) -# 20170208 -commitDate = commitDateDashes.replace("-", "") - -# v4.11.0-139-g922f2d9 -# -revision = subprocess.check_output(["git", "describe", "--tags", "--long"]) -revision.strip() -# 4.11.0-139-g922f2d9 -revision = revision[1:] -version, numberOfCommits, gHash = revision.split("-") -version = version.strip() -numberOfCommits = numberOfCommits.strip() -gHash = gHash.strip() - -pythonRevision = version -if int(numberOfCommits) > 0: - pythonRevision += ".dev" - pythonRevision += commitDate - pythonRevision += "+" - pythonRevision += numberOfCommits - pythonRevision += "." - pythonRevision += gHash - -os.chdir(itkPythonPackageDir) -itkVersionPath = os.path.join(itkPythonPackageDir, "itkVersion.py") - -Version(VERSION) # Raise InvalidVersion exception if not PEP 440 compliant - -if not os.path.exists(itkVersionPath): - print("Expected file " + itkVersionPath + " not found!") - sys.exit(1) -with open(itkVersionPath, "r") as fp: - lines = fp.readlines() -with open(itkVersionPath, "w") as fp: - for line in lines: - if line.startswith("VERSION = "): - fp.write("VERSION = '") - fp.write(pythonRevision) - fp.write("'\n") - else: - fp.write(line) - - -with open("CMakeLists.txt", "r") as fp: - lines = fp.readlines() -with open("CMakeLists.txt", "w") as fp: - for line in lines: - if line.startswith(" # ITK nightly-master"): - fp.write(" # ITK nightly-master ") - fp.write(commitDateDashes) - fp.write("\n") - elif line.startswith(" set(ITK_GIT_TAG"): - fp.write(' set(ITK_GIT_TAG "') - fp.write(gHash[1:]) - fp.write('")\n') - else: - fp.write(line) diff --git a/scripts/update_python_version.sh b/scripts/update_python_version.sh deleted file mode 100755 index 9773056e..00000000 --- a/scripts/update_python_version.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -# This script is use to update the version of ITK used by ITKPythonPackage. It -# is run nightly by a system with push permissions to the -# -# git@github.com:InsightSoftwareConsortium/ITKPythonPackage.git -# -# repository. - -set -x -e - -workdir=/tmp/ - -cd $workdir - -if test ! -d ITKNightlyMaster; then - git clone --branch nightly-master \ - https://github.com/InsightSoftwareConsortium/ITK.git ITKNightlyMaster -fi -pushd ITKNightlyMaster/ -git checkout nightly-master -git fetch -git reset --hard origin/nightly-master -ITK_SHA=$(git rev-parse --short HEAD) -popd - -if test ! -d ITKPythonPackage; then - git clone --branch master \ - git@github.com:InsightSoftwareConsortium/ITKPythonPackage.git ITKPythonPackage -fi -pushd ITKPythonPackage/ -git checkout master -git fetch -git reset --hard origin/master -git config user.name 'Kitware Robot' -git config user.email 'kwrobot@kitware.com' -ITKPythonPackage_SHA=$(git rev-parse --short HEAD) -./scripts/update_python_version.py ../ITKNightlyMaster -git add -- CMakeLists.txt itkVersion.py -git commit -m "ITK nightly version update - -This commit updates: - (1) SHA used in CMakeLists.txt to checkout ITK sources - (InsightSoftwareConsortium/ITK@${ITK_SHA}) - (2) VERSION variable set in itkVersion.py - -It was automatically generated by the script ``update_python_version.sh`` [1] - -[1] https://github.com/InsightSoftwareConsortium/ITKPythonPackage/blob/${ITKPythonPackage_SHA}/scripts/update_python_version.py" -git push origin master diff --git a/scripts/windows-build-tarball.ps1 b/scripts/windows-build-tarball.ps1 deleted file mode 100644 index dab4f09f..00000000 --- a/scripts/windows-build-tarball.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -# This script creates a tarball of the ITK Python package build tree. It is -# downloaded by the external module build scripts and used to build their -# Python package on GitHub CI services. - -cd C:\P\ -Remove-Item IPP\dist\* -C:\7-Zip\7z.exe a -t7z -r 'C:\P\ITKPythonBuilds-windows.zip' -w 'C:\P\IPP' -# C:\7-Zip\7z.exe a -t7z -mx=9 -mfb=273 -ms -md=31 -myx=9 -mtm=- -mmt -mmtf -md=1536m -mmf=bt3 -mmc=10000 -mpb=0 -mlc=0 -r 'C:\P\ITKPythonBuilds-windows.zip' -w 'C:\P\IPP' diff --git a/scripts/windows_build_module_wheels.py b/scripts/windows_build_module_wheels.py deleted file mode 100755 index 858f1bac..00000000 --- a/scripts/windows_build_module_wheels.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python - -# See usage with .\scripts\windows_build_module_wheels.py --help - -from subprocess import check_call -import os -import glob -import sys -import argparse -import shutil -from pathlib import Path - -SCRIPT_DIR = os.path.dirname(__file__) -ROOT_DIR = os.path.abspath(os.getcwd()) - -print("SCRIPT_DIR: %s" % SCRIPT_DIR) -print("ROOT_DIR: %s" % ROOT_DIR) - -sys.path.insert(0, os.path.join(SCRIPT_DIR, "internal")) - -from wheel_builder_utils import push_dir, push_env -from windows_build_common import DEFAULT_PY_ENVS, venv_paths - - -def install_and_import(package): - """ - Install package with pip and import in current script. - """ - import importlib - - try: - importlib.import_module(package) - except ImportError: - import pip - - pip.main(["install", package]) - finally: - globals()[package] = importlib.import_module(package) - - -def build_wheels(py_envs=DEFAULT_PY_ENVS, cleanup=True, cmake_options=[]): - for py_env in py_envs: - ( - python_executable, - python_include_dir, - python_library, - python_sabi_library, - pip, - ninja_executable, - path, - ) = venv_paths(py_env) - - with push_env(PATH="%s%s%s" % (path, os.pathsep, os.environ["PATH"])): - # Install dependencies - # - # Bootstrap pip from CPython's bundled ensurepip wheel BEFORE - # invoking the system pip, to sidestep a chicken-and-egg failure - # on Python 3.10+ runners. scikit-ci-addons' install-python.ps1 - # downloads get-pip.py from a pinned GitHub gist (jcfr commit - # 8478d43e), which installs a pip old enough to vendor an - # html5lib that still does `from collections import Mapping` - # (removed in Python 3.10). Loading that pip to upgrade itself - # raises ImportError before any package operation can run. - # CPython's ensurepip module installs the pip wheel that ships - # with the interpreter (>= 21.x for Python 3.10), which has a - # working html5lib >= 1.1; the subsequent `pip install pip - # --upgrade` then succeeds normally. - check_call( - [python_executable, "-m", "ensurepip", "--upgrade", "--default-pip"] - ) - check_call([python_executable, "-m", "pip", "install", "pip", "--upgrade"]) - requirements_file = os.path.join(ROOT_DIR, "requirements-dev.txt") - if os.path.exists(requirements_file): - check_call([pip, "install", "--upgrade", "-r", requirements_file]) - check_call([pip, "install", "cmake"]) - check_call([pip, "install", "scikit-build-core", "--upgrade"]) - - check_call([pip, "install", "ninja", "--upgrade"]) - check_call([pip, "install", "delvewheel"]) - - source_path = ROOT_DIR - itk_build_path = os.path.abspath( - "%s/ITK-win_%s" % (os.path.join(SCRIPT_DIR, ".."), py_env) - ) - print("ITKDIR: %s" % itk_build_path) - - minor_version = py_env.split("-")[0][1:] - if int(minor_version) >= 11: - # Stable ABI - wheel_py_api = "cp3%s" % minor_version - else: - wheel_py_api = "" - # Generate wheel - check_call( - [ - python_executable, - "-m", - "build", - "--verbose", - "--wheel", - "--outdir", - "dist", - "--no-isolation", - "--skip-dependency-check", - "--config-setting=wheel.py-api=%s" % wheel_py_api, - "--config-setting=cmake.define.SKBUILD:BOOL=ON", - "--config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH=.", - "--config-setting=cmake.args=-G Ninja", - "--config-setting=cmake.define.CMAKE_BUILD_TYPE:STRING=Release", - "--config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=%s" - % ninja_executable, - "--config-setting=cmake.define.ITK_DIR:PATH=%s" - % itk_build_path, - "--config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel", - "--config-setting=cmake.define.SWIG_EXECUTABLE:FILEPATH=%s/Wrapping/Generators/SwigInterface/swig/bin/swig.exe" - % itk_build_path, - "--config-setting=cmake.define.BUILD_TESTING:BOOL=OFF", - "--config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib", - "--config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=%s" - % python_executable, - "--config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_INCLUDE_DIRS:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_LIBRARY:FILEPATH=%s" - % python_library, - "--config-setting=cmake.define.Python3_SABI_LIBRARY:FILEPATH=%s" - % python_sabi_library, - ] - + [ - o.replace("-D", "--config-setting=cmake.define.") - for o in cmake_options - ] - + [ - ".", - ] - ) - - -def rename_wheel_init(py_env, filepath, add_module_name=True): - """ - Rename module __init__ (if add_module_name is True) or __init_module__ (if - add_module_name is False) file in wheel. This is required to prevent - modules to override ITK's __init__ file on install or to prevent delvewheel - to override __init_module__ file. If the module ships its own __init__ - file, it is automatically renamed to __init_{module_name}__ by this - function. The renamed __init__ file will be executed by ITK's __init__ file - when loading ITK. - """ - ( - python_executable, - python_include_dir, - python_library, - python_sabi_library, - pip, - ninja_executable, - path, - ) = venv_paths(py_env) - - # Get module info - install_and_import("pkginfo") - w = pkginfo.Wheel(filepath) - module_name = w.name.split("itk-")[-1] - module_version = w.version - - dist_dir = os.path.dirname(filepath) - wheel_dir = os.path.join( - dist_dir, "itk_" + module_name.replace("-", "_") + "-" + module_version - ) - init_dir = os.path.join(wheel_dir, "itk") - init_file = os.path.join(init_dir, "__init__.py") - init_file_module = os.path.join( - init_dir, "__init_" + module_name.split("-")[0] + "__.py" - ) - - # Unpack wheel and rename __init__ file if it exists. - check_call([python_executable, "-m", "wheel", "unpack", filepath, "-d", dist_dir]) - if add_module_name and os.path.isfile(init_file): - shutil.move(init_file, init_file_module) - if not add_module_name and os.path.isfile(init_file_module): - shutil.move(init_file_module, init_file) - - # Pack wheel and clean wheel folder - check_call([python_executable, "-m", "wheel", "pack", wheel_dir, "-d", dist_dir]) - shutil.rmtree(wheel_dir) - - -def fixup_wheel(py_envs, filepath, lib_paths: str = "", exclude_libs: str = ""): - lib_paths = ";".join(["C:/P/IPP/oneTBB-prefix/bin", lib_paths.strip()]).strip(";") - print(f"Library paths for fixup: {lib_paths}") - - py_env = py_envs[0] - - # Make sure the module __init_module__.py file has the expected name for - # delvewheel, i.e., __init__.py. - rename_wheel_init(py_env, filepath, False) - - delve_wheel = os.path.join( - "C:/P/IPP", "venv-" + py_env, "Scripts", "delvewheel.exe" - ) - check_call( - [ - delve_wheel, - "repair", - "--no-mangle-all", - "--add-path", - lib_paths, - "--no-dll", - exclude_libs, - "--ignore-in-wheel", - "-w", - os.path.join(ROOT_DIR, "dist"), - filepath, - ] - ) - - # The delve_wheel patch loading shared libraries is added to the module - # __init__ file. Rename this file here to prevent conflicts on installation. - # The renamed __init__ file will be executed when loading ITK. - rename_wheel_init(py_env, filepath) - - -def fixup_wheels(py_envs, lib_paths: str = "", exclude_libs: str = ""): - # shared library fix-up - for wheel in glob.glob(os.path.join(ROOT_DIR, "dist", "*.whl")): - fixup_wheel(py_envs, wheel, lib_paths, exclude_libs) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Driver script to build ITK Python module wheels." - ) - parser.add_argument( - "--py-envs", - nargs="+", - default=DEFAULT_PY_ENVS, - help='Target Python environment versions, e.g. "310-x64".', - ) - parser.add_argument( - "--no-cleanup", - dest="cleanup", - action="store_false", - help="Do not clean up temporary build files.", - ) - parser.add_argument( - "--lib-paths", - nargs=1, - default="", - help="Add semicolon-delimited library directories for delvewheel to include in the module wheel", - ) - parser.add_argument( - "--exclude-libs", - nargs=1, - default="", - help='Add semicolon-delimited library names that must not be included in the module wheel, e.g. "nvcuda.dll"', - ) - parser.add_argument( - "cmake_options", - nargs="*", - help="Extra options to pass to CMake, e.g. -DBUILD_SHARED_LIBS:BOOL=OFF", - ) - args = parser.parse_args() - - build_wheels( - cleanup=args.cleanup, py_envs=args.py_envs, cmake_options=args.cmake_options - ) - fixup_wheels(args.py_envs, ";".join(args.lib_paths), ";".join(args.exclude_libs)) diff --git a/scripts/windows_build_wheels.py b/scripts/windows_build_wheels.py deleted file mode 100644 index 4c1404bd..00000000 --- a/scripts/windows_build_wheels.py +++ /dev/null @@ -1,411 +0,0 @@ -#!/usr/bin/env python - -import argparse -import glob -import json -import os -import shutil -import sys -import tempfile -import textwrap - -from subprocess import check_call, check_output - - -SCRIPT_DIR = os.path.dirname(__file__) -ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) -ITK_SOURCE = os.path.join(ROOT_DIR, "ITK-source") - -print("SCRIPT_DIR: %s" % SCRIPT_DIR) -print("ROOT_DIR: %s" % ROOT_DIR) -print("ITK_SOURCE: %s" % ITK_SOURCE) - -sys.path.insert(0, os.path.join(SCRIPT_DIR, "internal")) -from wheel_builder_utils import push_dir, push_env -from windows_build_common import DEFAULT_PY_ENVS, venv_paths - - -def pip_install(python_dir, package, upgrade=True): - pip = os.path.join(python_dir, "Scripts", "pip.exe") - print("Installing %s using %s" % (package, pip)) - args = [pip, "install"] - if upgrade: - args.append("--upgrade") - args.append(package) - check_call(args) - - -def prepare_build_env(python_version): - python_dir = "C:/Python%s" % python_version - if not os.path.exists(python_dir): - raise FileNotFoundError( - "Aborting. python_dir [%s] does not exist." % python_dir - ) - - venv = os.path.join(python_dir, "Scripts", "virtualenv.exe") - venv_dir = os.path.join(ROOT_DIR, "venv-%s" % python_version) - print("Creating python virtual environment: %s" % venv_dir) - if not os.path.exists(venv_dir): - check_call([venv, venv_dir]) - pip_install(venv_dir, "scikit-build-core") - pip_install(venv_dir, "ninja") - pip_install(venv_dir, "delvewheel") - - -def build_wrapped_itk( - ninja_executable, - build_type, - source_path, - build_path, - python_executable, - python_include_dir, - python_library, - python_sabi_library, -): - tbb_dir = os.path.join(ROOT_DIR, "oneTBB-prefix", "lib", "cmake", "TBB") - - # Build ITK python - with push_dir(directory=build_path, make_directory=True): - check_call( - [ - "cmake", - "-DCMAKE_MAKE_PROGRAM:FILEPATH=%s" % ninja_executable, - "-DCMAKE_BUILD_TYPE:STRING=%s" % build_type, - "-DITK_SOURCE_DIR:PATH=%s" % source_path, - "-DITK_BINARY_DIR:PATH=%s" % build_path, - "-DBUILD_TESTING:BOOL=OFF", - "-DSKBUILD:BOOL=ON", - "-DPython3_EXECUTABLE:FILEPATH=%s" % python_executable, - "-DITK_WRAP_unsigned_short:BOOL=ON", - "-DITK_WRAP_double:BOOL=ON", - "-DITK_WRAP_complex_double:BOOL=ON", - "-DITK_WRAP_IMAGE_DIMS:STRING=2;3;4", - "-DPython3_INCLUDE_DIR:PATH=%s" % python_include_dir, - "-DPython3_INCLUDE_DIRS:PATH=%s" % python_include_dir, - "-DPython3_LIBRARY:FILEPATH=%s" % python_library, - "-DPython3_SABI_LIBRARY:FILEPATH=%s" % python_sabi_library, - "-DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel", - "-DWRAP_ITK_INSTALL_COMPONENT_PER_MODULE:BOOL=ON", - "-DPY_SITE_PACKAGES_PATH:PATH=.", - "-DITK_LEGACY_SILENT:BOOL=ON", - "-DITK_WRAP_PYTHON:BOOL=ON", - "-DITK_WRAP_DOC:BOOL=ON", - "-DDOXYGEN_EXECUTABLE:FILEPATH=C:/P/doxygen/doxygen.exe", - "-DModule_ITKTBB:BOOL=ON", - "-DTBB_DIR:PATH=%s" % tbb_dir, - "-G", - "Ninja", - source_path, - ] - ) - check_call([ninja_executable]) - - -def build_wheel( - python_version, - build_type="Release", - single_wheel=False, - cleanup=True, - wheel_names=None, - cmake_options=[], -): - ( - python_executable, - python_include_dir, - python_library, - python_sabi_library, - pip, - ninja_executable, - path, - ) = venv_paths(python_version) - - with push_env(PATH="%s%s%s" % (path, os.pathsep, os.environ["PATH"])): - # Install dependencies - check_call( - [ - pip, - "install", - "--upgrade", - "-r", - os.path.join(ROOT_DIR, "requirements-dev.txt"), - ] - ) - - source_path = "%s/ITK" % ITK_SOURCE - build_path = "%s/ITK-win_%s" % (ROOT_DIR, python_version) - pyproject_configure = os.path.join(SCRIPT_DIR, "pyproject_configure.py") - - # Clean up previous invocations - if cleanup and os.path.exists(build_path): - shutil.rmtree(build_path) - - if single_wheel: - print("#") - print("# Build single ITK wheel") - print("#") - - # Configure pyproject.toml - check_call([python_executable, pyproject_configure, "itk"]) - - # Generate wheel - check_call( - [ - python_executable, - "-m", - "build", - "--verbose", - "--wheel", - "--outdir", - "dist", - "--no-isolation", - "--skip-dependency-check", - "--config-setting=cmake.build-type=%s" % build_type, - "--config-setting=cmake.define.ITK_SOURCE_DIR:PATH=%s" - % source_path, - "--config-setting=cmake.define.ITK_BINARY_DIR:PATH=%s" % build_path, - "--config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=%s" - % python_executable, - "--config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_INCLUDE_DIRS:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_LIBRARY:FILEPATH=%s" - % python_library, - "--config-setting=cmake.define.Python3_SABI_LIBRARY:FILEPATH=%s" - % python_sabi_library, - "--config-setting=cmake.define.DOXYGEN_EXECUTABLE:FILEPATH=C:/P/doxygen/doxygen.exe", - ] - + [ - o.replace("-D", "--config-setting=cmake.define.") - for o in cmake_options - ] - + [ - ".", - ] - ) - - else: - print("#") - print("# Build multiple ITK wheels") - print("#") - - build_wrapped_itk( - ninja_executable, - build_type, - source_path, - build_path, - python_executable, - python_include_dir, - python_library, - python_sabi_library, - ) - - # Build wheels - if wheel_names is None: - with open(os.path.join(SCRIPT_DIR, "WHEEL_NAMES.txt"), "r") as content: - wheel_names = [ - wheel_name.strip() for wheel_name in content.readlines() - ] - - for wheel_name in wheel_names: - # Configure pyproject.toml - check_call([python_executable, pyproject_configure, wheel_name]) - - # Generate wheel - check_call( - [ - python_executable, - "-m", - "build", - "--verbose", - "--wheel", - "--outdir", - "dist", - "--no-isolation", - "--skip-dependency-check", - "--config-setting=cmake.build-type=%s" % build_type, - "--config-setting=cmake.define.ITK_SOURCE_DIR:PATH=%s" - % source_path, - "--config-setting=cmake.define.ITK_BINARY_DIR:PATH=%s" - % build_path, - "--config-setting=cmake.define.ITKPythonPackage_ITK_BINARY_REUSE:BOOL=ON", - "--config-setting=cmake.define.ITKPythonPackage_WHEEL_NAME:STRING=%s" - % wheel_name, - "--config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=%s" - % python_executable, - "--config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_INCLUDE_DIRS:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_LIBRARY:FILEPATH=%s" - % python_library, - "--config-setting=cmake.define.Python3_SABI_LIBRARY:FILEPATH=%s" - % python_sabi_library, - ] - + [ - o.replace("-D", "--config-setting=cmake.define.") - for o in cmake_options - ] - + [ - ".", - ] - ) - - # Remove unnecessary files for building against ITK - if cleanup: - for root, _, file_list in os.walk(build_path): - for filename in file_list: - extension = os.path.splitext(filename)[1] - if extension in [".cpp", ".xml", ".obj", ".o"]: - os.remove(os.path.join(root, filename)) - shutil.rmtree(os.path.join(build_path, "Wrapping", "Generators", "CastXML")) - - -def fixup_wheel(py_envs, filepath, lib_paths: str = ""): - lib_paths = lib_paths.strip() if lib_paths.isspace() else lib_paths.strip() + ";" - lib_paths += "C:/P/IPP/oneTBB-prefix/bin" - print(f"Library paths for fixup: {lib_paths}") - - py_env = py_envs[0] - - delve_wheel = os.path.join(ROOT_DIR, "venv-" + py_env, "Scripts", "delvewheel.exe") - check_call( - [ - delve_wheel, - "repair", - "--no-mangle-all", - "--add-path", - lib_paths, - "--ignore-in-wheel", - "-w", - os.path.join(ROOT_DIR, "dist"), - filepath, - ] - ) - - -def fixup_wheels(single_wheel, py_envs, lib_paths: str = ""): - # TBB library fix-up - tbb_wheel = "itk_core" - if single_wheel: - tbb_wheel = "itk" - for wheel in glob.glob(os.path.join(ROOT_DIR, "dist", tbb_wheel + "*.whl")): - fixup_wheel(py_envs, wheel, lib_paths) - - -def test_wheels(python_env): - ( - python_executable, - python_include_dir, - python_library, - python_sabi_library, - pip, - ninja_executable, - path, - ) = venv_paths(python_env) - check_call([pip, "install", "numpy"]) - check_call([pip, "install", "itk", "--no-cache-dir", "--no-index", "-f", "dist"]) - print("Wheel successfully installed.") - check_call([python_executable, os.path.join(ROOT_DIR, "docs/code/test.py")]) - print("Documentation tests passed.") - - -def build_wheels( - py_envs=DEFAULT_PY_ENVS, - single_wheel=False, - cleanup=False, - wheel_names=None, - cmake_options=[], -): - for py_env in py_envs: - prepare_build_env(py_env) - - build_type = "Release" - - with push_dir(directory=ITK_SOURCE, make_directory=True): - cmake_executable = "cmake.exe" - tools_venv = os.path.join(ROOT_DIR, "venv-" + py_envs[0]) - ninja_executable = shutil.which("ninja.exe") - if ninja_executable is None: - pip_install(tools_venv, "ninja") - ninja_executable = os.path.join(tools_venv, "Scripts", "ninja.exe") - - # Build standalone project and populate archive cache - check_call( - [ - cmake_executable, - "-DCMAKE_BUILD_TYPE:STRING=%s" % build_type, - "-DITKPythonPackage_BUILD_PYTHON:PATH=0", - "-G", - "Ninja", - "-DCMAKE_MAKE_PROGRAM:FILEPATH=%s" % ninja_executable, - ROOT_DIR, - ] - ) - - check_call([ninja_executable]) - - # Compile wheels re-using standalone project and archive cache - for py_env in py_envs: - tools_venv = os.path.join(ROOT_DIR, "venv-" + py_env) - ninja_executable = shutil.which("ninja.exe") - if ninja_executable is None: - pip_install(tools_venv, "ninja") - build_wheel( - py_env, - build_type, - single_wheel=single_wheel, - cleanup=cleanup, - wheel_names=wheel_names, - cmake_options=cmake_options, - ) - - -def main(wheel_names=None): - parser = argparse.ArgumentParser( - description="Driver script to build ITK Python wheels." - ) - parser.add_argument( - "--single-wheel", - action="store_true", - help="Build a single wheel as opposed to one wheel per ITK module group.", - ) - parser.add_argument( - "--py-envs", - nargs="+", - default=DEFAULT_PY_ENVS, - help='Target Python environment versions, e.g. "310-x64".', - ) - parser.add_argument( - "--no-cleanup", - dest="cleanup", - action="store_false", - help="Do not clean up temporary build files.", - ) - parser.add_argument( - "--lib-paths", - nargs=1, - default="", - help="Add semicolon-delimited library directories for delvewheel to include in the module wheel", - ) - parser.add_argument( - "cmake_options", - nargs="*", - help="Extra options to pass to CMake, e.g. -DBUILD_SHARED_LIBS:BOOL=OFF", - ) - args = parser.parse_args() - - build_wheels( - single_wheel=args.single_wheel, - cleanup=args.cleanup, - py_envs=args.py_envs, - wheel_names=wheel_names, - cmake_options=args.cmake_options, - ) - fixup_wheels(args.single_wheel, args.py_envs, ";".join(args.lib_paths)) - for py_env in args.py_envs: - test_wheels(py_env) - - -if __name__ == "__main__": - main() From 06a0667f969334e45775a90ac792a6ff16b01e27 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 16 Sep 2026 19:52:16 -0500 Subject: [PATCH 10/16] DOC: Re-platform documentation for the pixi build --- .github/pull_request_template.md | 29 ++ .github/workflows/docs.yaml | 31 ++ .github/workflows/pre-commit.yaml | 16 + .readthedocs.yml | 13 + README.md | 338 +++++++++++-- docs/Build_ITK_Module_Python_packages.rst | 436 ++++++++++------- docs/Build_ITK_Python_packages.rst | 548 +++++++++++++++++----- docs/Miscellaneous.rst | 2 +- docs/Prerequisites.rst | 53 ++- docs/_static/custom.css | 29 ++ docs/conf.py | 94 ++-- docs/index.rst | 64 ++- docs/requirements-docs.txt | 4 +- 13 files changed, 1249 insertions(+), 408 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/docs.yaml create mode 100644 .github/workflows/pre-commit.yaml create mode 100644 .readthedocs.yml create mode 100644 docs/_static/custom.css diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..21e10a76 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ +## Summary + + + +## Type of Change + +- [ ] Bug fix +- [ ] New feature / enhancement +- [ ] Refactor / cleanup +- [ ] Docs / CI only + +## Checklist + +- [ ] Pre-commit hooks pass locally (`pre-commit run --all-files`) +- [ ] Tested wheel build on affected platform(s) (Linux / macOS / Windows) +- [ ] Docs updated if behavior changed (`docs/`) +- [ ] Commit messages use ITK prefixes (`BUG:`, `COMP:`, `DOC:`, `ENH:`, `PERF:`, `STYLE:`) + +## Platform(s) Tested + + +- [ ] Linux x86_64 (manylinux) +- [ ] Linux aarch64 (manylinux) +- [ ] macOS arm64 +- [ ] Windows x86_64 + +## Related Issues + + diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 00000000..133a0df4 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,31 @@ +name: docs + +on: + pull_request: + paths: + - "docs/**" + - ".readthedocs.yml" + push: + branches: [main] + paths: + - "docs/**" + - ".readthedocs.yml" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install -r docs/requirements-docs.txt + - name: Build docs + run: sphinx-build -W --keep-going -b html docs docs/_build/html + - uses: actions/upload-artifact@v4 + if: always() + with: + name: docs-html + path: docs/_build/html + retention-days: 7 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 00000000..f1356482 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,16 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - uses: pre-commit/action@v3.0.1 diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..72aeff6c --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements-docs.txt diff --git a/README.md b/README.md index d28a3666..2849997c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ITK Python Package This project configures pyproject.toml files and manages environmental -variables needed to build ITK Python binary wheels on MacOS, Linux, and Windows platforms. +variables needed to build ITK Python binary wheels on macOS, Linux, and Windows platforms. Scripts are available for both [ITK infrastructure](https://github.com/insightSoftwareConsortium/ITK) and ITK external module Python packages. @@ -12,85 +12,325 @@ or at the [ITK GitHub homepage](https://github.com/insightSoftwareConsortium/ITK ## Table of Contents -- [Using ITK Python Packages](#using-itk-python-packages) -- [Building with ITKPythonPackage](#building-with-itkpythonpackage) +- [Building Remote Modules with ITKPythonPackage](#building-remote-modules-with-itkpythonpackage) +- [Building ITK Python Wheels](#building-itk-python-wheels) - [Frequently Asked Questions](#frequently-asked-questions) - [Additional Information](#additional-information) -## Using ITK Python Packages (pre-built, or locally built) +## Building Remote Modules with ITKPythonPackage -ITKPythonPackage scripts can be used to produce [Python](https://www.python.org/) packages -for ITK and ITK external modules. The resulting packages can be -hosted on the [Python Package Index (PyPI)](https://pypi.org/) -for easy distribution. +ITK reusable workflows are available to build and package Python wheels as +part of Continuous Integration (CI) via GitHub Actions runners. +Those workflows can handle the overhead of fetching, configuring, and +running ITKPythonPackage build scripts for most ITK external modules. +See [ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) +for more information. + +> [!NOTE] +> When using`ITKRemoteModuleBuildTestPackageAction` in your remote module, you can specify the `itk-python-package-org` and `itk-python-package-tag` to build with. + +For special cases where ITK reusable workflows are not a good fit, +ITKPythonPackage scripts can be directly used to build Python wheels +to target Windows, Linux, and macOS platforms. See +below or the [ITKPythonPackage ReadTheDocs](https://itkpythonpackage.readthedocs.io/en/latest/Build_ITK_Module_Python_packages.html) +documentation for more information on building wheels by hand. + +## Building ITK Python Wheels + +### Do You Actually Need to Build ITK? + +Most users do not need to build ITK from source. -### Installation of pre-built packages +Pre-built ITK binaries are available as downloadable tarballs and the provided download-and-build shell scripts will fetch them automatically. -To install baseline ITK Python packages: +You may only need to build ITK yourself if you: +- Have a local ITK with custom patches or bug fixes not yet in a release +- Need to build against a specific unreleased commit +- Are developing ITK core itself -```sh -> pip install itk +If none of the above apply to you, you may download an existing build of ITK from ITK's repository for [ITK Python Builds](https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases). **Or**, use the download-and-build script seen in the [Building Remote Module Wheels](#building-remote-module-wheels) section below. + +For more control over your builds, skip to [The Build Process](#the-build-process) + +--- + +### Prerequisites + +- Python 3.11 or later +- Git +- Docker (for manylinux builds) +- [Pixi](https://pixi.sh) package manager + +**Install Pixi:** +```bash +# Linux or Mac +curl -fsSL https://pixi.sh/install.sh | bash + +# Windows +powershell -ExecutionPolicy Bypass -c "irm -useb https://pixi.sh/install.ps1 | iex" ``` -To install ITK external module packages: +**Clone the repo:** +```bash +git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git +cd ITKPythonPackage +``` + +--- + +### Building Remote Module Wheels + +#### The Build Process + +The build process calls `build_wheels.py`, which runs up to 7 steps: + +1. Build SuperBuild support components +2. Build ITK C++ with Python wrapping +3. Build wheels for ITK C++ +4. Fix up wheels if needed +5. Import test +6. *(optional)* Build a remote module against the ITK build +7. *(optional)* Build an ITK tarball cache -```sh -> pip install itk- +> [!NOTE] +> When using the download-and-build scripts, steps 2–3 are skipped because the pre-built cache covers them. + +You can invoke the `build_wheels.py` script directly for more control shown below + +Available pixi platform build environments: + +| Platform | Architectures | Python Versions | +|----------|---------------|-----------------| +| `linux` | x86_64, aarch64 | py311 | +| `manylinux228` | x86_64, aarch64 | py311 | +| `macosx` | arm64 | py311 | +| `windows` | x86_64 | py311 | + + +```bash +# Building ITK Python Wheels on macOS for ITK v6.0b01 +pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --no-build-itk-tarball-cache ``` -### Using ITK in Python scripts +Key options: + +| Option | Description | Example | +|----------------------------------|----------------------------------------------|-------------------------------| +| `--platform-env` | Target platform and Python version | `macosx-py311` | +| `--build-dir-root` | Location for build artifacts | `/tmp/ITKPythonPackage-build` | +| `--itk-git-tag` | ITK version/branch/commit to use | `0ffcaed`, `main`, `v6.0b01` | +| `--itk-package-version` | PEP440 version string for wheels | `v6.0b01` | +| `--manylinux-version` | Manylinux standard version | `_2_28` | +| `--module-source-dir` | Path to remote module to build | `/path/to/module` | +| `--itk-module-deps` | Remote module dependencies | `Mod1@tag:Mod2@tag` | +| `--module-dependencies-root-dir` | Root directory for module dependencies | `./dependencies` | +| `--itk-source-dir` | Path to ITK source (use local development) | `/path/to/ITK` | +| `--cleanup` | Leave temporary build files after completion | (flag) | +| `--no-build-itk-tarball-cache` | Skip tarball generation (default) | (flag) | +| `--no-skip-itk-build` | Don't skip ITK build step (default | (flag) | +| `--no-skip-itk-wheel-build` | Don't skip the ITK wheel build step (default) | (flag) | -```python - import itk - import sys - input_filename = sys.argv[1] - output_filename = sys.argv[2] +Run `pixi run python3 scripts/build_wheels.py --help` for the full option list. - image = itk.imread(input_filename) +> [!NOTE] +> Building ITK from source can take 1-2 hours on typical hardware. Once complete, use `--build-itk-tarball-cache` to save the result and avoid rebuilding. - median = itk.median_image_filter(image, radius=2) +To use the scripts that take care of the build for you, see this section: - itk.imwrite(median, output_filename) +
+Download-and-Build Remote Module Builds + +This is the same process as used in the GitHub Actions CI/CD + +```bash +cd ITKRemoteModule ``` -### Other Resources for Using ITK in Python +#### Linux (manylinux) -See also the [ITK Python Quick Start -Guide](https://itkpythonpackage.readthedocs.io/en/master/Quick_start_guide.html). -There are also many [downloadable examples on the ITK examples website](https://examples.itk.org/search.html?q=Python). +Use `dockcross-manylinux-download-cache-and-build-module-wheels.sh`. This script: +1. Downloads the pre-built ITK binary tarball for your platform +2. Extracts it to a local build directory +3. Calls `dockcross-manylinux-build-module-wheels.sh` to build the module wheels inside a manylinux Docker container -For more information on ITK's Python wrapping, [an introduction is -provided in the ITK Software -Guide](https://itk.org/ITKSoftwareGuide/html/Book1/ITKSoftwareGuide-Book1ch3.html#x32-420003.7). +Run from your ITK external module root: +```bash +bash dockcross-manylinux-download-cache-and-build-module-wheels.sh cp311 +``` -## Building with ITKPythonPackage +> [!NOTE] +> Omit the Python version argument (e.g. `cp311`) to build for the default version (cp311). -ITK reusable workflows are available to build and package Python wheels as -part of Continuous Integration (CI) via Github Actions runners. -Those workflows can handle the overhead of fetching, configuring, and -running ITKPythonPackage build scripts for most ITK external modules. -See [ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) -for more information. +#### macOS + +Use `macpython-download-cache-and-build-module-wheels.sh`. This script: +1. Installs required tools (aria2, zstd, gnu-tar) via Pixi if not present +2. Downloads and extracts the macOS ITK binary tarball +3. Builds your module wheels for each requested Python version + +Run from your module root: +```bash +bash macpython-download-cache-and-build-module-wheels.sh 3.11 +``` + +#### Windows + +Use `windows-download-cache-and-build-module-wheels.ps1`. This script: +1. Installs required tools (git, aria2) via Pixi if not present +2. Downloads and extracts the Windows ITK binary zip file +3. Builds your module wheels for each requested Python version + +Run from your module root: +```powershell +.\windows-download-cache-and-build-module-wheels.ps1 3.11 +``` -For special cases where ITK reusable workflows are not a good fit, -ITKPythonPackage scripts can be directly used to build Python wheels -to target Windows, Linux, and MacOS platforms. See -[ITKPythonPackage ReadTheDocs](https://itkpythonpackage.readthedocs.io/en/master/Build_ITK_Module_Python_packages.html) -documentation for more information on building wheels by hand. +#### Output + +Finished wheels are placed in `/dist/`. + +
+ + +To see how to build wheels for your version of ITK see this section: + +
+Building ITK from Source + +If you have a local ITK with custom patches, a bug fix not yet released, or you're developing ITK core itself. Build as follows + +Pass `--itk-source-dir` pointing to your local ITK clone. `build_wheels.py` will build ITK from that source instead of re-cloning. + +#### manylinux — building ITK from source + +Use `dockcross-manylinux-build-wheels.sh` directly (skips the download step): + +```bash +ITK_SOURCE_DIR=/path/to/your/ITK \ +bash scripts/dockcross-manylinux-build-wheels.sh cp311 +``` + +Key environment variables: + +| Variable | Default | Description | +|----------|-----------------------------------------|-------------| +| `ITK_GIT_TAG` | `main` | ITK branch/tag/commit to build | +| `ITK_SOURCE_DIR` | `/ITKPythonPackage-build/ITK` | Path to local ITK source (skips git clone) | +| `MANYLINUX_VERSION` | `_2_28` | Manylinux standard to target | +| `IMAGE_TAG` | `20250913-6ea98ba` | Dockcross image tag | + +#### Linux/macOS/Windows — building ITK from source + +Use `build_wheels.py` directly with `--itk-source-dir`: + +```bash +# Building on macOS with a specific git tag +pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-source-dir /path/to/your/ITK \ + --itk-git-tag my-bugfix-branch \ + --no-build-itk-tarball-cache \ + --build-dir-root /tmp/itk-build +``` + +Add `--build-itk-tarball-cache` if you want to save the result as a reusable tarball. + +
+ +To see how to build ITK Python Build Caches, see this section: + +
+Building ITK Python Caches + +#### GitHub Compatible Caches + +To build the caches compatible with GitHub Actions CI and the ITKPythonBuilds repository. You can run: + +On Linux and macOS systems +```bash +bash scripts/make_tarballs.sh # py311 (optionally add specific version of Python) +``` + +On Windows systems +```powershell +.\scripts\make_windows_zip.ps1 # py311 (optionally add specific version of Python) +``` + +> [!IMPORTANT] +> Build caches embed absolute paths. If you extract a tarball to a different path than it was built with, CMake will fail. Standard build paths for CI/CD are: +> - manylinux (Docker): `/work/ITKPythonPackage-build` +> - macOS: `/Users/svc-dashboard/D/P/ITKPythonPackage-build` +> - Windows: `C:\BDR` +> +> This script ensures you are building with the correct conventions + +#### Local Caches + +To build caches for local use, you can run the `build_wheels.py` script with the `--build-itk-tarball-cache` + +#### Publish Tarball Caches + +To publish the tarball caches to a GitHub Release, you can run: + +> [!NOTE] +> This requires the `GH_TOKEN` environment variable to be set or `gh auth login` to have been run beforehand. +> Tarballs are expected in the parent directory of `--build-dir-root` (POSIX `.tar.zst`) or inside it (Windows `.zip`). + +```bash +pixi run -e publish publish-tarball-cache --itk-package-version v6.0b02 --build-dir-root /path/to/build/root +``` + +Users can also specify the GitHub repository to publish to using `--repo` (defaults to ITKPythonBuilds) and +`--create-release` to create the release if it does not already exist. + +
+ +To see how to publish wheels see this section: + +
+Publishing Wheels +This repository contains a script for publishing wheels to PyPI and TestPyPI. + +The script can be run with the pixi environment as such: + +> [!NOTE] +> This script assumes you have the `TWINE_USERNAME` and `TWINE_PASSWORD` environment variables set or the +> `.pypirc` file configured on your machine. An example `.pypirc` can be seen in the root of this repository + +```bash +pixi run -e publish publish-wheels --dist-directory /path/to/dist/ +``` + +You can also optionally pass in `--test` to publish to TestPyPI for validation before uploading to production, +`--repository-url` to specify a custom package index, or `--skip-existing` to skip already-uploaded wheels. + +
+ +--- ## Frequently Asked Questions ### What target platforms and architectures are supported? ITKPythonPackage currently supports building wheels for the following platforms and architectures: - - Windows 10 x86_64 platforms - - Windows 11 x86_64 platforms - - MacOS 15.0+ arm64 platforms - - Linux glibc 2.17+ (E.g. Ubuntu 18.04+) x86_64 platforms - - Linux glibc 2.28+ (E.g. Ubuntu 20.04+) aarch64 (ARMv8) platforms -Python 3.10+ is required. +- Windows 10/11 x86_64 platforms +- macOS arm64 (Apple Silicon) +- Linux glibc 2.28+ (e.g. Ubuntu 20.04+) x86_64 +- Linux glibc 2.28+ (e.g. Ubuntu 20.04+) aarch64 (ARMv8) + +Python 3.11+ is required; wheels use the Stable ABI (cp311-abi3). + +[ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) +CI workflows support Python 3.11+ on GitHub-hosted runners for: +- Ubuntu x86_64 +- Ubuntu aarch64 (ARM) +- macOS arm64 (Apple Silicon) +- Windows x86_64 ### What should I do if my target platform/architecture does not appear on the list above? diff --git a/docs/Build_ITK_Module_Python_packages.rst b/docs/Build_ITK_Module_Python_packages.rst index dd88d7b9..6a2bfd32 100644 --- a/docs/Build_ITK_Module_Python_packages.rst +++ b/docs/Build_ITK_Module_Python_packages.rst @@ -1,238 +1,348 @@ ===================================== -Build ITK Module Python packages -====================================== - -ITK is organized into *modules*. Modules for ITK can be developed outside the -ITK source tree as *remote modules*. The *remote module* can be made -available in ITK's `CMake `_ configuration by -`contributing it -`_ -as a *remote module*. Python packages can also be generated for remote -modules and uploaded to the `Python Package Index (PyPI) `_ - -This section describes how to create, build, and upload ITK remote -module Python packages to PyPI. +Build ITK Module Python Packages +===================================== +ITK is organized into *modules*. Community members can extend ITK by developing +an ITK *external module* in a separate repository. When a module meets community +standards for documentation and maintenance it may be included in the ITK build +as a *remote module*. -.. include:: Prerequisites.rst +This section describes how to create, build, and publish Python packages for +ITK remote and external modules to PyPI. -Create the module -================= +Create a Module +=============== -To create an ITK module with Python wrapping, first run cookiecutter:: +To scaffold a new ITK module with Python wrapping, use the official template:: python -m pip install cookiecutter python -m cookiecutter gh:InsightSoftwareConsortium/ITKModuleTemplate # Fill in the information requested at the prompts +Fill in the prompts, then add your C++ filter classes. See +`Chapter 9 of the ITK Software Guide +`_ +for guidance on populating the module and writing ``.wrap`` files for SWIG. + -Then, add your classes. Reference documentation on `how to populate the module -`_ -can be found in the `ITK Software Guide -`_. +GitHub Actions Workflows +============================== +For most ITK external modules, the recommended and easiest path to building, testing, and +publishing Python wheels is the +`ITKRemoteModuleBuildTestPackageAction +`_ +reusable workflow. It handles fetching, configuring, and running +ITKPythonPackage build scripts automatically. -GitHub automated CI package builds -================================== +Every pull request and push triggers a build that: -Freely available GitHub Actions continous integration (CI) build and test -services for open source repositories are provided by -`GitHub `_. These services will build and test the C++ -code for your module and also generate Linux, macOS, and Windows Python -packages for your module. +- Compiles and runs your module's C++ tests +- Generates Linux, macOS, and Windows Python wheels -For every pull request and push to the GitHub repository, a GitHub Action will -run that builds and runs the repository's C++ tests and reports the results to -the `ITK CDash Dashboard `_. -Python packages are also generated for every commit. Packages for a commit's -build can be downloaded from the GitHub Action result page in the *Artifacts* -Section. +Wheel artifacts are downloadable from the **Artifacts** section of the +GitHub Actions run page. .. figure:: images/GitHubActionArtifacts.png - :alt: GitHub Action Artifacts + :alt: GitHub Action Artifacts -Reusable workflows available in -[ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) -can be used to handle the build-test-package process -for a majority of ITK external modules with minimal extra development. +To pin the specific ITKPythonPackage version used by the workflow (defaults are shown below): -Upload the packages to PyPI ----------------------------- +.. code-block:: yaml -First, `register for an account on PyPI `_. + uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package.yml@v5.4.5 + with: + itk-python-package-org: InsightSoftwareConsortium + itk-python-package-tag: main -Next, create a `~/.pypirc` file with your login credentials:: +.. include:: Prerequisites.rst - [distutils] - index-servers = - pypi - pypitest +Manual Builds +============= - [pypi] - username= - password= +For cases where the reusable workflow is not a good fit, the +download-and-build scripts can be run locally. Each script: - [pypitest] - repository=https://test.pypi.org/legacy/ - username= - password= +1. Downloads and installs the necessary build packages +2. Downloads the pre-built ITK binary tarball for the target platform from + `ITKPythonBuilds `_ +3. Extracts it to a local build directory +4. Builds your module wheels against the pre-built ITK -where `` and `` correspond to your PyPI account. +.. important:: + Place and run the script from your module's root directory. Or specify exact paths using the environment variables below -Then, upload wheels to the testing server. The wheels of dist/* are those that -you have built locally or have downloaded from a recent build listed at -`https://github.com/InsightSoftwareConsortium//actions`. -:: +Set the ITK PEP 440 compliant version before running any script:: - python -m pip install twine - python -m twine upload -r pypitest dist/* + export ITK_PACKAGE_VERSION=v6.0b01 # Linux / macOS + $env:ITK_PACKAGE_VERSION = "v6.0b01" # Windows PowerShell -Check out the packages on ``_ the testing server. +Linux (manylinux) +----------------- -Finally, upload the wheel packages to the production PyPI server:: +Requires Docker. Produces ``manylinux_2_28`` portable wheels. - python -m twine upload dist/* +.. code-block:: bash -Congratulations! Your packages can be installed with the commands:: + cd ~/ITKMyModule + # First build — downloads ITK cache, then builds module wheels + export MODULE_SRC_DIRECTORY=/path/to/module + bash ITKPythonPackage/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh cp311 - python -m pip install --upgrade pip - python -m pip install itk- + # Subsequent builds — reuses the downloaded cache + bash ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh cp311 -where `itk-` is the short name for your module that is -specified in the configured `pyproject.toml` file. +Omit the Python version argument to build all supported versions (cp311): -Automate PyPI Package Uploads ------------------------------ +.. code-block:: bash -Automated uploads of Python packages to the Python package index, `PyPI -`_ will occur after adding a PyPI upload token to GitHub and -creating a Git tag. Create a PyPI API token by logging in to -``_. Generally, for the token name -use:: + export MODULE_SRC_DIRECTORY=/path/to/module + bash ITKPythonPackage/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh - itk--github-action +macOS +----- -and for the scope use:: +.. code-block:: bash - itk- + cd ~/ITKMyModule + export MODULE_SRC_DIRECTORY=/path/to/module + bash ITKPythonPackage/scripts/macpython-download-cache-and-build-module-wheels.sh 3.11 -where `` is the short name for your module that is -specified in your configured `pyproject.toml` file. That scope will be available if you have -already uploaded a first set of wheels via twine as described above; and that -is the recommended approach. Otherwise, if you are creating the project at -this time, choose an unlimited scope, but be careful with the created token. +Windows +------- -.. figure:: images/PyPIToken.png - :alt: PyPI Token +Open a PowerShell terminal: + +.. code-block:: powershell + + cd C:\ITKMyModule + $env:ITK_PACKAGE_VERSION = "v6.0b01" + $env:MODULE_SRC_DIRECTORY = /path/to/module + .\ITKPythonPackage\scripts\windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 10 + +Build multiple Python versions: + +.. code-block:: powershell + + foreach ($v in @(10, 11)) { + .\ITKPythonPackage\scripts\windows-download-cache-and-build-module-wheels.ps1 -python_version_minor $v + } + +.. important:: + Use a short build path (e.g. ``C:\BDR``) to avoid Windows 260-character + path length limits. See the Troubleshooting section in + :doc:`Build_ITK_Python_packages` for details. -Then, add the API token to the GitHub repository -`https://github.com/InsightSoftwareConsortium/`. Choose -the *Settings -> Secrets* page and add a key called *pypi_password*, setting -the password to be the token string that begins with `pypi-`. Note that this -will be a *token* instead of a password. Limit the scope of the token to the -individual package as a best practice. +Key environment variables: + +.. list-table:: + :header-rows: 1 + :widths: 30 20 50 + + * - Variable + - Default + - Description + * - ``ITK_PACKAGE_VERSION`` + - ``v6.0b01`` + - PEP 440 ITK release to build against + * - ``TARGET_ARCH`` + - ``x64`` + - ``x64`` or ``aarch64`` + * - ``IMAGE_TAG`` + - ``20250913-6ea98ba`` + - Dockcross Docker image tag + * - ``MODULE_SRC_DIRECTORY`` + - script directory + - Path to your module source + * - ``MODULE_DEPS_DIR`` + - platform dependant + - Root directory for module dependency checkouts + * - ``DASHBOARD_BUILD_DIRECTORY`` + - platform dependant + - Root directory for build artifacts + * - ``MANYLINUX_VERSION`` + - ``_2_28`` + - Manylinux compatibility standard + * - ``CMAKE_OPTIONS`` + - *(empty)* + - Extra CMake ``-D`` definitions + * - ``ITKPYTHONPACKAGE_TAG`` + - ``main`` + - ITKPythonPackage branch/tag to fetch + * - ``ITKPYTHONPACKAGE_ORG`` + - ``InsightSoftwareConsortium`` + - ITKPythonPackage organization to fetch + * - ``NO_SUDO`` + - *(unset)* + - Set to skip ``sudo`` for Docker commands + * - ``DYLD_LIBRARY_PATH`` + - *(unset)* + - Extra library paths to bundle into wheels -.. figure:: images/GitHubPyPISecret.png - :alt: GitHub PyPI token secret -To push packages to PyPI, first, make sure to update the `version` for your -package in the *pyproject.toml* file. The initial version might be `0.1.0` or -`1.0.0`. Subsequent versions should follow -`semantic versioning `_. +Use the Build Script Directly +==================================== -Then, create a Git tag corresponding to the version. A Git tag can be created -in the GitHub user interface via *Releases -> Draft a new release*. +For more control over build option, call ``build_wheels.py`` directly with +``--module-source-dir``. This approach will create a local ITK build by default: -.. figure:: images/GitHubReleaseTag.png - :alt: GitHub Release Tag +.. code-block:: bash + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --module-source-dir /path/to/ITKMyModule \ + --no-skip-itk-build \ + --no-skip-itk-wheel-build \ + --no-build-itk-tarball-cache -Automated platform scripts -========================== -Automated scripts are available in this repository to build Python packages -that are binary compatible with the Python distributions provided by -Python.org, Anaconda, and package managers like apt or Homebrew. -The following sections outline how to use the associated scripts for Linux, -macOS, and Windows. +Module Dependencies +=================== -Once the builds are complete, Python packages will be available in the `dist` -directory. +If your module depends on other ITK external modules, list them with +``--itk-module-deps`` (or the ``ITK_MODULE_PREQ`` environment variable for the +shell scripts): -Linux ------ +.. code-block:: bash -To build portable Python packages on Linux, first `install Docker -`_. + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --module-source-dir /path/to/ITKMyModule \ + --itk-module-deps "InsightSoftwareConsortium/ITKMeshToPolyData@v1.0.0" \ + --no-build-itk-tarball-cache -For the first local build, clone the `ITKPythonPackage` repository inside your -and download the required ITK binary builds:: +For multiple dependencies, separate them with colons in **dependency order** +(each module listed before the modules that depend on it): - cd ~/ITKMyModule - git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage - ./ITKPythonPackage/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh +.. code-block:: bash -For subsequent builds, just call the build script:: + --itk-module-deps "org/ITKModA@v1.0:org/ITKModB@v2.1:org/ITKModC@main" - ./ITKPythonPackage/scripts/dockcross-manylinux-build-module-wheels.sh +The format for each entry is ``/@``. -macOS ------ +For the download-and-build shell scripts, set ``ITK_MODULE_PREQ`` instead: -First, install the Python.org macOS Python distributions. This step requires sudo:: +.. code-block:: bash - cd ~/ITKMyModule - git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage - ./ITKPythonPackage/scripts/macpython-install-python.sh + export ITK_MODULE_PREQ="org/ITKModA@v1.0:org/ITKModB@v2.1" + bash ITKPythonPackage/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh cp311 -Then, build the wheels:: +Dependencies are cloned to ``/`` before the +main module build begins. - ./ITKPythonPackage/scripts/macpython-build-wheels.sh -Windows -------- +Third-Party Libraries +===================== + +If your module links against a third-party library that is not part of ITK, +the wheel repair tools (``auditwheel``, ``delocate``, ``delvewheel``) need +to be able to find it to bundle it into the wheel. + +**Linux**: add the library's directory to ``LD_LIBRARY_PATH`` before running +the build script:: + + export LD_LIBRARY_PATH=/path/to/mylib/lib:$LD_LIBRARY_PATH + +**macOS**: add the directory to ``DYLD_LIBRARY_PATH``:: + + export DYLD_LIBRARY_PATH=/path/to/mylib/lib:$DYLD_LIBRARY_PATH + +**Windows**: pass the library directory via ``--lib-paths`` (or the +``-setup_options`` parameter of the PowerShell script): + +.. code-block:: powershell + + .\windows-download-cache-and-build-module-wheels.ps1 ` + -python_version_minor 10 ` + -setup_options "--exclude-libs nvcuda.dll" + + +Output +====== + +Finished wheels are placed in ``dist/`` inside your module directory +(or ``/dist/`` when running ``build_wheels.py`` directly). + +Example output:: + + dist/ + itk-mymodule-1.0.0-cp311-abi3-manylinux_2_28_x86_64.whl + itk-mymodule-1.0.0-cp311-abi3-macosx_11_0_arm64.whl + itk-mymodule-1.0.0-cp311-abi3-win_amd64.whl + + +Uploading to PyPI +================= + +Using the Publish Environment +----------------------------- + +ITKPythonPackage provides a ``publish`` Pixi environment with ``twine`` +pre-configured. Set your PyPI credentials (see ``.pypirc.example`` in the +repository root) and run: + +.. code-block:: bash + + export TWINE_USERNAME=__token__ + export TWINE_PASSWORD=pypi- + + # Test on TestPyPI first + pixi run -e publish publish-wheels \ + --dist-directory /path/to/module/dist \ + --test + + # Then upload to production PyPI + pixi run -e publish publish-wheels \ + --dist-directory /path/to/module/dist + +Pass ``--skip-existing`` to skip already-uploaded wheels when re-running after +a partial upload failure. + +Manual Upload +------------- + +Alternatively, install and use ``twine`` directly. + +Test on TestPyPI first:: + + pip install twine + twine upload -r testpypi dist/*.whl -First, install Microsoft Visual Studio 2022, Git, and CMake, which should be added to the system PATH environmental variable. +Then upload to production PyPI:: -Open a PowerShell terminal as Administrator, and install Python:: + twine upload dist/*.whl - PS C:\> Set-ExecutionPolicy Unrestricted - PS C:\> $pythonArch = "64" - PS C:\> iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/scikit-build/scikit-ci-addons/master/windows/install-python.ps1')) +Your package can then be installed with:: -In a PowerShell prompt, run the `windows-build-wheels.ps1` script:: + pip install itk- - PS C:\Windows> cd C:\ITKMyModule - PS C:\ITKMyModule> git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git IPP - PS C:\ITKMyModule> .\ITKPythonPackage\scripts\windows-download-cache-and-build-module-wheels.ps1 +Automated Upload via GitHub Actions +------------------------------------- -Other Notes ------------ +To automate publishing on every tagged release: -ITK modules sometimes depend on third-party libraries. To include third-party libraries -in development wheels for distribution, first add the library path to `LD_LIBRARY_PATH` -on Linux, `DYLD_LIBRARY_PATH` on MacOS, or `PATH` on Windows. Then, run the platform -build script. +1. Create a PyPI API token at ``_. + Name it ``itk--github-action`` and scope it to your + package (scope becomes available after the first manual upload). -ITK modules sometimes depend on other ITK modules. For instance, to build -[ITKBSplineGradient](https://github.com/InsightSoftwareConsortium/ITKBSplineGradient) -the user must first build ITK and then [ITKMeshToPolyData](https://github.com/InsightSoftwareConsortium/ITKmeshtopolydata). -ITKPythonPackage scripts support iterative prerequisite ITK module dependencies with the `ITK_MODULE_PREQ` -environment variable. + .. figure:: images/PyPIToken.png + :alt: PyPI Token -For Python build scripts, the ordered list of ITK module dependencies must be formatted as follows: +2. In your GitHub repository, go to **Settings → Secrets → Actions** and + add a secret named ``pypi_password`` with the token value (starts with + ``pypi-``). -``` -ITK_MODULE_PREQ=/@:/@:... -``` + .. figure:: images/GitHubPyPISecret.png + :alt: GitHub PyPI token secret -Where -- `module_org` is the name of a Github organization to use to fetch the module, i.e. "InsightSoftwareConsortium"; -- `module_name` is the name of the module, i.e. "ITKMeshToPolyData"; -- `module_tag` is the git tag or commit hash to use to fetch the module, i.e. "v1.0.0" +3. Create a GitHub Release (via **Releases → Draft a new release**). + The tag name should match the version in your ``pyproject.toml``. -Module names must be provided in order of dependencies for the build to succeed. + .. figure:: images/GitHubReleaseTag.png + :alt: GitHub Release Tag -For more information see the -[build scripts directory](https://github.com/InsightSoftwareConsortium/ITKPythonPackage/tree/master/scripts). +The ``ITKRemoteModuleBuildTestPackageAction`` workflow will detect the tag +and upload wheels automatically. diff --git a/docs/Build_ITK_Python_packages.rst b/docs/Build_ITK_Python_packages.rst index bb0d647c..d1b4af48 100644 --- a/docs/Build_ITK_Python_packages.rst +++ b/docs/Build_ITK_Python_packages.rst @@ -1,14 +1,13 @@ -====================================== -Build ITK Python packages -====================================== +============================== +Build ITK Python Packages +============================== -This section describes how to builds ITK's Python packages. In most cases, the -:ref:`pre-built ITK binary wheels can be used `. - -ITK Python packages are built nightly on Kitware build systems and uploaded to -the `ITKPythonPackage GitHub releases page -`_. +This section describes how to build ITK's core Python wheels +(``itk-core``, ``itk-numerics``, ``itk-io``, ``itk-filtering``, +``itk-registration``, ``itk-segmentation``, and ``itk-meta``). In most cases, the pre-built ITK binary wheels can be used. +You may only need to build ITK from source if you need a custom patch, a specific unreleased +commit, or are developing ITK core itself. .. include:: Prerequisites.rst @@ -18,115 +17,454 @@ Automated platform scripts Steps required to build wheels on Linux, macOS and Windows have been automated. The following sections outline how to use the associated scripts. -Linux ------ -On any linux distribution with docker and bash installed, running the script dockcross-manylinux-build-wheels.sh will create 64-bit wheels for python 3.9+ in the dist directory. +Overview +======== -For example:: +The build is driven by ``scripts/build_wheels.py``, which orchestrates up to +seven sequential steps: - $ git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git - [...] +1. Build SuperBuild support components (oneTBB and other ITK dependencies) +2. Build ITK C++ with Python wrapping +3. Build Python wheels for each ITK subpackage +4. Fix up wheels if platform requires (``auditwheel`` / ``delocate`` / ``delvewheel``) +5. Import test +6. *(optional)* Build a remote module against the ITK build +7. *(optional)* Create a reusable ITK build tarball cache - $ ./scripts/dockcross-manylinux-build-wheels.sh - [...] +Step state is persisted to ``/dist/build_report-.json`` so that a +build interrupted part-way through can be resumed by re-running the same command. - $ ls -1 dist/ - itk-4.11.0.dev20170218-cp27-cp27m-manylinux2014_x86_64.whl - itk-4.11.0.dev20170218-cp27-cp27mu-manylinux2014_x86_64.whl - itk-4.11.0.dev20170218-cp34-cp34m-manylinux2014_x86_64.whl - itk-4.11.0.dev20170218-cp35-cp35m-manylinux2014_x86_64.whl - itk-4.11.0.dev20170218-cp36-cp36m-manylinux2014_x86_64.whl -macOS ------ +Setup +===== + +Clone the repository:: + + git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git + cd ITKPythonPackage + + +Platform Environments +===================== + +Each Pixi environment targets a specific OS and Python version. Pass the +environment name to ``--platform-env``: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - ``--platform-env`` + - Notes + * - ``linux-py311`` + - Native Linux (GCC, glibc of host) + * - ``manylinux228-py311`` + - Portable Linux ≥ glibc 2.28 (x86_64 or aarch64 via Docker) + * - ``macosx-py311`` + - macOS (arm64) + * - ``windows-py311`` + - Windows x86_64 (MSVC 2022) + +If ``--platform-env`` is omitted, the platform is auto-detected from the +host OS and defaults to Python 3.11. + + +Building Wheels +=============== -First, install the Python.org macOS Python distributions. This step requires sudo:: +manylinux +--------- - ./scripts/macpython-install-python.sh +On any linux distribution with docker and bash installed, running the script dockcross-manylinux-build-wheels.sh will create 64-bit wheels for Python 3.11+ in the dist directory. +.. code-block:: bash -Then, build the wheels:: + ./scripts/dockcross-manylinux-build-wheels.sh # py311 optionally specify python version - $ ./scripts/macpython-build-wheels.sh - [...] +Or you can build using a specific platform environment using: - $ ls -1 dist/ - itk-4.11.0.dev20170213-cp27-cp27m-macosx_10_6_x86_64.whl - itk-4.11.0.dev20170213-cp34-cp34m-macosx_10_6_x86_64.whl - itk-4.11.0.dev20170213-cp35-cp35m-macosx_10_6_x86_64.whl - itk-4.11.0.dev20170213-cp36-cp36m-macosx_10_6_x86_64.whl +Linux +----- + +.. code-block:: bash + + pixi run python3 scripts/build_wheels.py \ + --platform-env linux-py311 \ + --itk-git-tag v6.0b01 \ + --no-build-itk-tarball-cache + +macOS +----- + +.. code-block:: bash + + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --no-build-itk-tarball-cache Windows ------- -First, install Microsoft Visual Studio 2015, Git, and CMake, which should be added to the system PATH environmental variable. - -Open a PowerShell terminal as Administrator, and install Python:: - - PS C:\> Set-ExecutionPolicy Unrestricted - PS C:\> $pythonArch = "64" - PS C:\> iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/scikit-build/scikit-ci-addons/master/windows/install-python.ps1')) - -In a PowerShell prompt:: - - PS C:\Windows> cd C:\ - PS C:\> git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git IPP - PS C:\> cd IPP - PS C:\IPP> .\scripts\windows-build-wheels.ps1 - [...] - - PS C:\IPP> ls dist - Directory: C:\IPP\dist - - - Mode LastWriteTime Length Name - ---- ------------- ------ ---- - -a---- 4/9/2017 11:14 PM 63274441 itk-4.11.0.dev20170407-cp35-cp35m-win_amd64.whl - -a---- 4/10/2017 2:08 AM 63257220 itk-4.11.0.dev20170407-cp36-cp36m-win_amd64.whl - -We need to work in a short directory to avoid path length limitations on -Windows, so the repository is cloned into C:\IPP. - -Also, it is very important to disable antivirus checking on the C:\IPP -directory. Otherwise, the build system conflicts with the antivirus when many -files are created and deleted quickly, which can result in Access Denied -errors. Windows 10 ships with an antivirus application, Windows Defender, that -is enabled by default. - -.. The below instructions are outdated and need to be re-written -.. sdist -.. ----- -.. -.. To create source distributions, sdist's, that will be used by pip to compile a wheel for installation if a binary wheel is not available for the current Python version or platform:: -.. -.. $ python setup.py sdist --formats=gztar,zip -.. [...] -.. -.. $ ls -1 dist/ -.. itk-4.11.0.dev20170216.tar.gz -.. itk-4.11.0.dev20170216.zip -.. -.. Manual builds -.. ============= -.. -.. Building ITK Python wheels -.. -------------------------- -.. -.. Build the ITK Python wheel with the following command:: -.. -.. python3 -m venv build-itk -.. ./build-itk/bin/pip install --upgrade pip -.. ./build-itk/bin/pip install -r requirements-dev.txt numpy -.. ./build-itk/bin/python setup.py bdist_wheel -.. -.. Build a wheel for a custom version of ITK -.. ----------------------------------------- -.. -.. To build a wheel for a custom version of ITK, point to your ITK git repository -.. with the `ITK_SOURCE_DIR` CMake variable:: -.. -.. ./build-itk/bin/python setup.py bdist_wheel -- \ -.. -DITK_SOURCE_DIR:PATH=/path/to/ITKPythonPackage-core-build/ITK -.. -.. Other CMake variables can also be passed with `-D` after the double dash. +Similarly, on windows + +.. code-block:: powershell + + pixi run python3 scripts/build_wheels.py ` + --platform-env windows-py311 ` + --itk-git-tag v6.0b01 ` + --no-build-itk-tarball-cache + + +Finished wheels are placed in ``/dist/``. + +.. note:: + Building ITK from source takes 1–2 hours on typical hardware. Pass + ``--build-itk-tarball-cache`` to save the result as a reusable tarball + and avoid rebuilding on subsequent runs. + + +Key Options +=========== + +.. list-table:: + :header-rows: 1 + :widths: 35 15 50 + + * - Option + - Default + - Description + * - ``--platform-env`` + - auto-detected + - Target platform and Python version (see table above) + * - ``--itk-git-tag`` + - ``main`` + - ITK version, branch, or commit to build + * - ``--itk-package-version`` + - auto (git describe) + - PEP 440 version string embedded in the wheels + * - ``--build-dir-root`` + - ``../ITKPythonPackage-build`` + - Root directory for all build artifacts + * - ``--manylinux-version`` + - ``_2_28`` + - Manylinux compatibility standard (e.g. ``_2_28``, ``_2_34``) + * - ``--itk-source-dir`` + - cloned automatically + - Path to a local ITK checkout (skips git clone) + * - ``--module-source-dir`` + - *(none)* + - Path to an ITK external module to build against the ITK build + * - ``--itk-module-deps`` + - *(none)* + - Colon-delimited prerequisite modules (``org/repo@tag:org/repo@tag``) + * - ``--build-itk-tarball-cache`` + - off + - Package the ITK build as a reusable ``.tar.zst`` / ``.zip`` + * - ``--no-build-itk-tarball-cache`` + - *(default)* + - Skip tarball generation + * - ``--skip-itk-build`` / ``--no-skip-itk-build`` + - off + - Skip step 2 (ITK C++ build) when a build already exists + * - ``--skip-itk-wheel-build`` / ``--no-skip-itk-wheel-build`` + - off + - Skip step 3 (wheel build) when wheels already exist + * - ``--cleanup`` + - off + - Remove intermediate build files after completion + * - ``--use-ccache`` + - off + - Enable ccache to speed up recompilation + * - ``--macosx-deployment-target`` + - ``10.7`` + - Minimum macOS version for compiled binaries + * - ``--use-sudo`` / ``--no-use-sudo`` + - off + - Pass ``sudo`` to Docker commands (manylinux only) + +Any remaining positional arguments are forwarded to CMake as ``-D`` definitions, +for example:: + + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + -DBUILD_SHARED_LIBS:BOOL=OFF + + +Environment Variable Equivalents +--------------------------------- + +All ``--`` options can alternatively be set as environment variables, which is +useful in CI pipelines: + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Environment Variable + - Equivalent Option + * - ``ITK_GIT_TAG`` + - ``--itk-git-tag`` + * - ``ITK_PACKAGE_VERSION`` + - ``--itk-package-version`` + * - ``ITK_SOURCE_DIR`` + - ``--itk-source-dir`` + * - ``MANYLINUX_VERSION`` + - ``--manylinux-version`` + * - ``TARGET_ARCH`` + - target architecture (``x64`` or ``aarch64``) + * - ``IMAGE_TAG`` + - Docker image tag for manylinux builds + * - ``NO_SUDO`` + - ``--no-use-sudo`` + * - ``USE_CCACHE`` + - ``--use-ccache`` + * - ``CMAKE_OPTIONS`` + - extra CMake ``-D`` definitions + * - ``MACOSX_DEPLOYMENT_TARGET`` + - ``--macosx-deployment-target`` + + +Building from a Local ITK Source +================================= + +If you have a local ITK checkout with custom patches or an unreleased fix, pass +``--itk-source-dir`` to use it instead of cloning from GitHub: + +.. code-block:: bash + + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-source-dir /path/to/your/ITK \ + --itk-git-tag my-bugfix-branch \ + --no-build-itk-tarball-cache + +For manylinux, use the shell wrapper which handles Docker volume mounting: + +.. code-block:: bash + + ITK_SOURCE_DIR=/path/to/your/ITK \ + bash scripts/dockcross-manylinux-build-wheels.sh cp311 + + +Building ITK Tarball Caches +============================ + +Tarball caches package the entire ITK build output (headers, libraries, wrapper +artifacts) so that external module builds can skip the costly ITK compilation step. +These are the same caches distributed via `ITKPythonBuilds +`_ releases. + +By default these scripts build for Python 3.11 but you can optionally add a specific version to build for + +Linux / macOS: + +.. code-block:: bash + + ITK_GIT_TAG=v6.0b01 + bash scripts/make_tarballs.sh py311 + +Windows (PowerShell): + +.. code-block:: powershell + + $env:ITK_GIT_TAG = "v6.0b01" + .\scripts\make_windows_zip.ps1 py311 + +Or via ``build_wheels.py`` directly: + +.. code-block:: bash + + pixi run python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --build-itk-tarball-cache + +.. important:: + Build caches embed absolute paths. They must be extracted to the same path + they were built from or CMake will fail to locate required files. The + standard CI/CD paths are: + + - **manylinux (Docker)**: ``/work/ITKPythonPackage-build`` + - **macOS**: ``/Users/svc-dashboard/D/P/ITKPythonPackage-build`` + - **Windows**: ``C:\BDR`` + + The ``make_tarballs.sh`` and ``make_windows_zip.ps1`` scripts enforce these + paths automatically. + + +Output Artifacts +================ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Artifact + - Location + * - Built wheels + - ``/dist/*.whl`` + * - Effective command line + - ``/effective_cmdline_.sh`` + * - manylinux tarball caches + - ``/ITKPythonBuilds-manylinux_2_28-*.tar.zst`` + * - Linux tarball caches + - ``/ITKPythonBuilds-linux-*.tar.zst`` + * - macOS tarball caches + - ``/ITKPythonBuilds-macosx-*.tar.zst`` + * - Windows ZIP cache + - ``/ITKPythonBuilds-windows.zip`` + +Example wheel names:: + + itk-6.0.0-cp311-abi3-manylinux_2_28_x86_64.whl + itk-6.0.0-cp311-abi3-macosx_11_0_arm64.whl + itk-6.0.0-cp311-abi3-win_amd64.whl + + +Testing Built Wheels +==================== + +Install and smoke-test a wheel directly from the ``dist/`` directory: + +.. code-block:: bash + + pip install dist/itk-6.0.0-cp311-abi3-macosx_11_0_arm64.whl + python -c "import itk; print(itk.__version__)" + + +Publishing +========== + +ITKPythonPackage provides a lightweight ``publish`` Pixi environment with +`twine `_ for uploading wheels to PyPI and the +`GitHub CLI `_ for uploading tarball caches to GitHub +Releases. Install it with:: + + pixi install -e publish + + +Publishing Wheels to PyPI +------------------------- + +The ``publish-wheels`` task validates wheel metadata with ``twine check`` and +then uploads all ``.whl`` files from a ``dist/`` directory. + +**Authentication** — set environment variables or configure ``~/.pypirc`` +(see ``.pypirc.example`` in the repository root): + +.. code-block:: bash + + export TWINE_USERNAME=__token__ + export TWINE_PASSWORD=pypi- + +Upload to **TestPyPI** first for validation: + +.. code-block:: bash + + pixi run -e publish publish-wheels \ + --dist-directory /path/to/dist \ + --test + +Then upload to **production PyPI**: + +.. code-block:: bash + + pixi run -e publish publish-wheels \ + --dist-directory /path/to/dist + +Additional options: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Option + - Description + * - ``--test`` + - Upload to TestPyPI (``https://test.pypi.org/legacy/``) + * - ``--repository-url`` + - Custom package index URL (overrides ``--test``) + * - ``--skip-existing`` + - Skip already-uploaded wheels instead of failing + + +Publishing Tarball Caches to GitHub Releases +-------------------------------------------- + +The ``publish-tarball-cache`` task uploads ``.tar.zst`` (POSIX) and ``.zip`` +(Windows) build caches to a GitHub Release on the +`ITKPythonBuilds `_ +repository. + +**Authentication** — set the ``GH_TOKEN`` environment variable or run +``gh auth login`` beforehand. + +.. code-block:: bash + + pixi run -e publish publish-tarball-cache \ + --itk-package-version v6.0b02 \ + --build-dir-root /path/to/build \ + --create-release + +Additional options: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Option + - Description + * - ``--itk-package-version`` + - **(required)** Release tag name (e.g. ``v6.0b02``) + * - ``--build-dir-root`` + - Root of the build directory; tarballs are found in its parent + (POSIX ``.tar.zst``) or inside it (Windows ``.zip``) + * - ``--repo`` + - Target GitHub repository (default: ``InsightSoftwareConsortium/ITKPythonBuilds``) + * - ``--create-release`` + - Create the GitHub Release if it does not already exist + +.. note:: + The ``--clobber`` flag is used internally so re-uploading a tarball for the + same platform replaces the existing asset without manual cleanup. + + +Troubleshooting +=============== + +Path Length Issues (Windows) +----------------------------- + +If you encounter path length errors: + +Windows has a 260-character path limit by default. Use a short build directory +(e.g. ``C:\BDR``) and optionally enable long path support: + +.. code-block:: powershell + + New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" ` + -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force + +Antivirus Conflicts (Windows) +------------------------------ + +The build creates and deletes large numbers of files rapidly, which can trigger +Windows Defender and produce "Access Denied" errors. Add the build directory to +Windows Defender exclusions: + +1. Open **Windows Security → Virus & threat protection → Manage settings** +2. Scroll to **Exclusions → Add or remove exclusions** +3. Add the build root (e.g. ``C:\BDR``) + +Docker Permissions (Linux) +--------------------------- + +If Docker requires ``sudo``, pass ``--use-sudo`` to ``build_wheels.py`` or set +``NO_SUDO=`` (empty string) before running the manylinux shell script. To add +your user to the ``docker`` group instead:: + + sudo usermod -aG docker $USER + # Then log out and back in diff --git a/docs/Miscellaneous.rst b/docs/Miscellaneous.rst index d7ae223f..acf64221 100644 --- a/docs/Miscellaneous.rst +++ b/docs/Miscellaneous.rst @@ -2,7 +2,7 @@ Miscellaneous ============= -Written by Jean-Christophe Fillion-Robin and Matt McCormick from Kitware Inc. +Written by Jean-Christophe Fillion-Robin and Matt McCormick from Kitware Inc and Cavan Riley from the University of Iowa. It is covered by the Apache License, Version 2.0: diff --git a/docs/Prerequisites.rst b/docs/Prerequisites.rst index 83b0f1a7..73b22012 100644 --- a/docs/Prerequisites.rst +++ b/docs/Prerequisites.rst @@ -1,9 +1,52 @@ Prerequisites ============= -Building wheels requires: +The following tools are required to build ITK Python wheels locally. -- CMake -- Git -- C++ Compiler - Platform specific requirements are summarized in scikit-build documentation. -- Python +All Platforms +------------- + +`Pixi `_ is the primary build environment manager. It automatically +provisions the correct Python version, compiler toolchain, CMake, Ninja, and all +other build dependencies for each target platform. Manual installation of compilers +or CMake is not required when using Pixi. + +Install Pixi: + +.. code-block:: bash + + # Linux or macOS + curl -fsSL https://pixi.sh/install.sh | bash + + # Windows (PowerShell) + powershell -ExecutionPolicy Bypass -c "irm -useb https://pixi.sh/install.ps1 | iex" + +You will also need `Git `_ 2.x or later. + +Linux +----- + +Portable manylinux builds require Docker (or an OCI-compatible runtime such as +Podman or nerdctl) to run the manylinux container environment. + +Install Docker by following the official `Docker Engine installation guide +`_. + +macOS +----- + +No additional tools are required beyond Pixi. The Pixi ``macosx-build`` +environment provides the Clang 1.10.0 compiler toolchain and the +`delocate `_ wheel repair utility. + +Windows +------- + +`Microsoft Visual Studio 2022 `_ +(or the Build Tools for Visual Studio 2022) with the +**Desktop development with C++** workload is required. + +.. note:: + Pixi environments provision Python, CMake, Ninja, and build helpers + (``auditwheel`` on Linux, ``delocate`` on macOS, ``delvewheel`` on Windows) + automatically. Only Docker (Linux) and MSVC (Windows) require separate installation. diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 00000000..7b5c8b9d --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,29 @@ +/* -- ITKPythonPackage custom styles (pydata-sphinx-theme) -- */ + +/* Sidebar brand text */ +.navbar-brand p { + font-weight: 700; + font-size: 1.15rem; +} + +/* Better code block styling */ +.highlight pre { + border-radius: 6px; + font-size: 0.85rem; +} + +/* Admonition tweaks */ +.admonition { + border-radius: 6px; + font-size: 0.92rem; +} + +/* Quick-links card grid on index */ +.sd-card { + border-radius: 8px; + transition: box-shadow 0.2s ease; +} + +.sd-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); +} diff --git a/docs/conf.py b/docs/conf.py index b642ec55..8be81c96 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,28 +1,5 @@ import os -# -*- coding: utf-8 -*- -# -# ITKPythonPackage documentation build configuration file, created by -# sphinx-quickstart on Mon May 29 15:53:20 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -32,8 +9,10 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] - +extensions = [ + "sphinx_copybutton", + "sphinx_design", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -51,21 +30,10 @@ copyright = "2017, Jean-Christophe Fillion-Robin and Matt McCormick" author = "Jean-Christophe Fillion-Robin and Matt McCormick" -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = "" -# The full version, including alpha/beta/rc tags. -release = "" +version = os.environ.get("READTHEDOCS_VERSION", "") +release = version -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -81,21 +49,27 @@ # -- Options for HTML output ---------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "default" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". +html_theme = "furo" html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_title = "ITKPythonPackage" + +html_theme_options = { + "source_repository": "https://github.com/InsightSoftwareConsortium/ITKPythonPackage", + "source_branch": "main", + "source_directory": "docs/", + "sidebar_hide_name": False, + "navigation_with_keys": True, + "top_of_page_buttons": ["view", "edit"], + "light_css_variables": { + "color-brand-primary": "#2962FF", + "color-brand-content": "#2962FF", + }, + "dark_css_variables": { + "color-brand-primary": "#82B1FF", + "color-brand-content": "#82B1FF", + }, +} # -- Options for HTMLHelp output ------------------------------------------ @@ -156,19 +130,7 @@ "ITKPythonPackage Documentation", author, "ITKPythonPackage", - "One line description of project.", + "Scripts and configuration for building ITK Python binary wheels.", "Miscellaneous", ), ] - - -# -- Read The Docs ----------------------------------------------------- - -# on_rtd is whether we are on readthedocs.io -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/docs/index.rst b/docs/index.rst index 32fc2fbc..18614842 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,36 +1,64 @@ -Welcome to ITKPythonPackage's documentation! -============================================ +ITKPythonPackage +================ -This project provides a script to generate `pyproject.toml` files used to build -ITK Python wheels and infrastructure to build ITK external module Python +Build infrastructure for ITK Python wheels and ITK external module Python wheels. `ITK `_ is an open-source, cross-platform system that provides developers with an extensive suite of software tools for image analysis. -To install the pre-built stable ITK Python package:: +.. code-block:: bash - $ pip install itk + pip install itk +To start using ITK from Python, see the `ITK Python quick start guide +`_. For more information on ITK's Python wrapping, see `an introduction in the Book 1, Chapter 3 of the ITK Software Guide `_. There are also many `downloadable examples documented in Sphinx `_. +---- -.. toctree:: - :maxdepth: 2 - :caption: Contents +Quick Links +----------- + +.. grid:: 2 + :gutter: 3 + + .. grid-item-card:: Prerequisites + :link: Prerequisites + :link-type: doc + + Platform requirements and tooling setup for building ITK wheels. + + .. grid-item-card:: Build ITK Python Packages + :link: Build_ITK_Python_packages + :link-type: doc + + Build core ITK wheels (``itk-core``, ``itk-numerics``, ``itk-io``, etc.) + from source. - Quick_start_guide.rst - Build_ITK_Module_Python_packages.rst - Build_ITK_Python_packages.rst - Miscellaneous.rst + .. grid-item-card:: Build ITK Module Packages + :link: Build_ITK_Module_Python_packages + :link-type: doc -Indices and tables -================== + Create, build, and publish Python packages for ITK remote and external + modules. + + .. grid-item-card:: Miscellaneous + :link: Miscellaneous + :link-type: doc + + License, authors, and additional resources. + +---- + +.. toctree:: + :maxdepth: 3 -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + Prerequisites + Build_ITK_Python_packages + Build_ITK_Module_Python_packages + Miscellaneous diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 82133027..7d464dc3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,4 @@ sphinx -sphinx_rtd_theme +furo +sphinx-copybutton +sphinx-design From 3ab62e5b860993282416ff8a061baaa840fbbdbb Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 08:34:06 -0500 Subject: [PATCH 11/16] DOC: Add AGENTS.md and CLAUDE.md for agentic coding tools Document the build driver's full command line surface so a coding agent can compose a correct invocation without reading the source. CLAUDE.md redirects to AGENTS.md, which is authoritative. The worked example covers the common request shape: a local macOS build against an unmerged ITK pull request, with custom compiler flags and a non-default deployment target. It fetches the pull request ref by hand, because the driver fetches tags only and a failed checkout warns and keeps the commit already present. --- AGENTS.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 8 ++ 2 files changed, 225 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c04d6016 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,217 @@ +# AGENTS.md + +Instructions for agentic coding tools working in ITKPythonPackage. This +file is the authoritative one; `CLAUDE.md` points here. + +Read the "Worked example" section first. It answers the most common +request shape end to end, with every command line argument written out. + +## The one rule that breaks every build + +**Always pass `-e ` to `pixi run`.** A bare `pixi run` uses +the `default` environment, which does not carry the pinned `cmake`, +`ninja`, `doxygen`, or `git`. The build aborts immediately and tells you +the environment to use, so this is loud rather than silent, but it is +still the single most common mistake. + +```bash +pixi run -e macosx-py311 python scripts/build_wheels.py ... # correct +pixi run python scripts/build_wheels.py ... # aborts +``` + +The environment name must match the platform: + +| Platform | `--platform-env` | +|---|---| +| macOS arm64 | `macosx-py311` | +| Linux x86_64 / aarch64 native | `linux-py311` | +| Linux via manylinux container | `manylinux228-py311` | +| Windows x86_64 | `windows-py311` | + +### Deliberately using host-system tools + +To build with the tools already on your `PATH` instead of pixi's, set +the environment name to `hostsystem`. This opts out of both the +environment guard and pixi tool resolution: + +```bash +PIXI_ENVIRONMENT_NAME=hostsystem python scripts/build_wheels.py \ + --platform-env macosx-py311 ... +``` + +Use this when you intend to build against a system compiler or a +locally built tool. It is an escape hatch, not the normal path: nothing +is pinned, so reproducibility is yours to manage. + + +## Worked example + +> "Build Python packages on my Mac only, with `-mtune=native +> -march=native` and macOS 26, for PR 6875 which is not merged yet, so I +> can test it in my environment." + +Four things are being asked for: a local macOS build, custom compiler +flags, a non-default deployment target, and ITK source from an unmerged +pull request. Do them in this order. + +**Step 1 — get the PR source.** The build script runs `git fetch --tags +origin` and then `git checkout `. It does **not** fetch +`pull/N/head` refs, so a pull request ref must be fetched by hand first. + +```bash +ITK_SRC="$HOME/src/ITK" +git -C "$ITK_SRC" fetch origin pull/6875/head:pr-6875 +git -C "$ITK_SRC" checkout pr-6875 +git -C "$ITK_SRC" rev-parse --short HEAD # record this +``` + +**Step 2 — build.** Positional arguments after the flags are passed +through to CMake verbatim. + +```bash +pixi run -e macosx-py311 python scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-source-dir "$ITK_SRC" \ + --itk-git-tag pr-6875 \ + --macosx-deployment-target 26.0 \ + --build-dir-root "$HOME/src/ITKPythonPackage-build" \ + --no-build-itk-tarball-cache \ + --use-ccache \ + -DCMAKE_C_FLAGS:STRING="-mtune=native -march=native" \ + -DCMAKE_CXX_FLAGS:STRING="-mtune=native -march=native" +``` + +**Step 3 — verify you built what was asked for.** A failed checkout in +step 1 prints a warning and keeps whatever commit was already there, so +confirm the source and the resulting tag rather than assuming: + +```bash +git -C "$ITK_SRC" rev-parse --short HEAD # same as step 1 +ls "$HOME/src/ITKPythonPackage-build"/dist/*.whl # expect 7 wheels +``` + +Every wheel should be named `*-cp311-abi3-macosx_26_0_arm64.whl`. Check +that a real binary agrees with the tag, because a wheel tagged below its +true minimum installs on older macOS and then fails at import: + +```bash +unzip -qq -o .whl -d /tmp/whlcheck +otool -l /tmp/whlcheck/itk/*.so | awk '/minos/{print $2; exit}' # 26.0 +``` + +**Warn the requester about the consequence:** wheels built with +`--macosx-deployment-target 26.0` run only on macOS 26 and newer, and +`-march=native` produces binaries tied to the building machine's CPU. +Both are correct for local testing and wrong for anything distributed. +The repository default is 14.0; see the platform policy below. + +## Wheel platform policy (ITK 6) + +These are decisions, not defaults to re-weigh. A change that breaks one +of them needs to say so explicitly. + +1. **macOS is Apple Silicon (arm64) only.** Intel is dropped. The + default `MACOSX_DEPLOYMENT_TARGET` is **14.0**: the lowest macOS + still receiving security updates, and low enough that no Apple + Silicon hardware is excluded. It is not the lowest arm64 supports + (that is 11.0). The wheel tag must match the target, and + `_PYTHON_HOST_PLATFORM` is derived from it so the two cannot drift. + A component wheel tagged below its real minimum installs on older + macOS and then fails in dyld at import. +2. **Python 3.11 is the minimum.** Do not add 3.9 or 3.10 targets. +3. **Linux is exactly manylinux_2_28.** manylinux2014 is dropped. + `auditwheel repair` runs with `--only-plat`, so each wheel carries + exactly one platform tag. Do not relax that flag to widen + compatibility; fix the toolchain instead. +4. **Stable ABI (`abi3`) wheels only.** One `cp311-abi3` wheel per + platform, via `wheel.py-api=cp311`. Never per-version `cp312`, + `cp313`, ... wheels. + +Together these give one wheel per platform: + +| Platform | Wheel tag | +|---|---| +| Linux x86_64 | `cp311-abi3-manylinux_2_28_x86_64` | +| Linux aarch64 | `cp311-abi3-manylinux_2_28_aarch64` | +| macOS arm64 | `cp311-abi3-macosx_14_0_arm64` | +| Windows x86_64 | `cp311-abi3-win_amd64` | + +A local test build may override the macOS target (see the worked +example above), but such wheels are not distributable. + +## Related repositories + +- [ITKPythonBuilds](https://github.com/InsightSoftwareConsortium/ITKPythonBuilds) + hosts the cached ITK build tarballs. +- [ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) + provides the reusable workflows remote modules call. Its macOS policy + must agree with this repository's. + + +## Every command line argument + +`scripts/build_wheels.py` accepts the following. Anything not listed is +not a real flag; do not invent one. + +**Selecting the environment and source** + +| Argument | Meaning | +|---|---| +| `--platform-env NAME` | Build environment; see the table above | +| `--itk-source-dir PATH` | Existing ITK checkout to reuse | +| `--itk-git-tag TAG` | Tag, branch, or hash to check out | +| `--itk-package-version VER` | PEP 440 version for the wheels | +| `--build-dir-root PATH` | Build root; default `../ITKPythonPackage-build` | + +**Platform options** + +| Argument | Meaning | +|---|---| +| `--macosx-deployment-target VER` | macOS floor and wheel tag | +| `--manylinux-version VER` | `_2_28`, `_2_34`; empty builds native Linux | +| `--lib-paths DIRS` | Windows only; `;`-delimited dirs for delvewheel | + +**Remote modules** + +| Argument | Meaning | +|---|---| +| `--module-source-dir PATH` | Remote module to build | +| `--module-dependencies-root-dir PATH` | Where module deps are cloned | +| `--itk-module-deps SPEC` | `gitorg/repo@tag:gitorg/repo@tag` | + +**Toggles** — each has a `--no-` counterpart: +`--build-itk-tarball-cache`, `--use-sudo`, `--use-ccache`, +`--skip-itk-build`, `--skip-itk-wheel-build`. Also `--cleanup`. + +**Positional** — everything after the flags goes to CMake unchanged, +for example `-DBUILD_SHARED_LIBS:BOOL=OFF`. These override defaults. + +## Build steps and what skipping them means + +The driver runs numbered steps and records which completed. The two +skip flags exist for reusing a previous build: + +- `--skip-itk-build` skips the ITK C++ build (step 2). +- `--skip-itk-wheel-build` skips the ITK wheel build (step 3). + +When wheels were never built, the later import test has nothing to +import and is skipped rather than failing. That is intended; do not +"fix" it by forcing the step. + +## Testing your changes + +```bash +pixi run -e dev python -m pytest tests/ -q # unit tests +pixi run pre-commit run --all-files # must exit 0 +``` + +`pytest` is a developer-only dependency. Never make the normal build +path import it. + +## Conventions + +- Commit subjects use ITK prefixes (`BUG:`, `COMP:`, `DOC:`, `ENH:`, + `PERF:`, `STYLE:`), at most 78 characters. +- Never add AI attribution to commit messages. `Co-Authored-By` is for + human contributors. +- Never push a branch whose tip fails `pre-commit run --all-files`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..da544d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,8 @@ +# CLAUDE.md + +See [AGENTS.md](AGENTS.md). + +That file is authoritative for this repository: build instructions, the +full `scripts/build_wheels.py` argument list, the wheel platform policy, +and worked examples. This file exists only so tools that look for +`CLAUDE.md` find their way there. From d6c479df3577b7624fb7757ddff0ebfcf43e518e Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 09:01:15 -0500 Subject: [PATCH 12/16] DOC: Correct README build commands and add a verification step Every build_wheels.py example omitted the pixi environment selector, so following the README landed in the default environment and aborted. Also corrects the dockcross image tag defaults, the directory the publish script searches for tarballs, the step count, and the --cleanup description, which read as the opposite of what the flag does. Adds the expected wheel tags and how to confirm a macOS binary matches its tag. --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2849997c..bbbaca67 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,12 @@ For more control over your builds, skip to [The Build Process](#the-build-proces ### Prerequisites -- Python 3.11 or later -- Git -- Docker (for manylinux builds) - [Pixi](https://pixi.sh) package manager +- Git +- Docker (only for manylinux container builds) + +Pixi supplies the Python interpreter, CMake, Ninja, and Doxygen used by the +build, so no system Python 3.11 is required. Wheels target Python 3.11+. **Install Pixi:** ```bash @@ -80,9 +82,25 @@ cd ITKPythonPackage ### Building Remote Module Wheels +> [!IMPORTANT] +> **Always pass `-e ` to `pixi run`.** A bare `pixi run` uses the +> `default` environment, which does not carry the pinned `cmake`, `ninja`, +> `doxygen`, or `git`; the build would pick up whatever is on your system +> `PATH`. The build aborts immediately and names the environment you want, so +> this is loud rather than silent. +> +> ```bash +> pixi run -e macosx-py311 python3 scripts/build_wheels.py ... # correct +> pixi run python3 scripts/build_wheels.py ... # aborts +> ``` +> +> To build deliberately with your own host tools instead of pixi's, set +> `PIXI_ENVIRONMENT_NAME=hostsystem`. Nothing is pinned in that mode, so +> reproducibility becomes yours to manage. + #### The Build Process -The build process calls `build_wheels.py`, which runs up to 7 steps: +The build process calls `build_wheels.py`, which runs up to 8 steps: 1. Build SuperBuild support components 2. Build ITK C++ with Python wrapping @@ -91,6 +109,9 @@ The build process calls `build_wheels.py`, which runs up to 7 steps: 5. Import test 6. *(optional)* Build a remote module against the ITK build 7. *(optional)* Build an ITK tarball cache +8. *(optional)* Post-build cleanup of temporary files + +Each completed step is recorded, so a re-run skips what already finished. > [!NOTE] > When using the download-and-build scripts, steps 2–3 are skipped because the pre-built cache covers them. @@ -109,7 +130,7 @@ Available pixi platform build environments: ```bash # Building ITK Python Wheels on macOS for ITK v6.0b01 -pixi run python3 scripts/build_wheels.py \ +pixi run -e macosx-py311 python3 scripts/build_wheels.py \ --platform-env macosx-py311 \ --itk-git-tag v6.0b01 \ --no-build-itk-tarball-cache @@ -128,23 +149,39 @@ Key options: | `--itk-module-deps` | Remote module dependencies | `Mod1@tag:Mod2@tag` | | `--module-dependencies-root-dir` | Root directory for module dependencies | `./dependencies` | | `--itk-source-dir` | Path to ITK source (use local development) | `/path/to/ITK` | -| `--cleanup` | Leave temporary build files after completion | (flag) | +| `--cleanup` | Remove temporary build files when done (default: keep) | (flag) | +| `--macosx-deployment-target` | macOS floor and wheel tag; default `14.0` | `14.0` | +| `--use-ccache` | Reuse compiler cache between builds | (flag) | +| `--lib-paths` | Windows only: `;`-delimited dirs for delvewheel | `C:\deps\bin` | | `--no-build-itk-tarball-cache` | Skip tarball generation (default) | (flag) | | `--no-skip-itk-build` | Don't skip ITK build step (default | (flag) | | `--no-skip-itk-wheel-build` | Don't skip the ITK wheel build step (default) | (flag) | -Run `pixi run python3 scripts/build_wheels.py --help` for the full option list. +Run `pixi run -e macosx-py311 python3 scripts/build_wheels.py --help` for the +full option list (substitute your own platform environment). > [!NOTE] > Building ITK from source can take 1-2 hours on typical hardware. Once complete, use `--build-itk-tarball-cache` to save the result and avoid rebuilding. -To use the scripts that take care of the build for you, see this section: +The recommended path is `build_wheels.py`, shown above. The shell entry points +below predate it and are retained only so existing consumers keep working:
-Download-and-Build Remote Module Builds - -This is the same process as used in the GitHub Actions CI/CD +Legacy shell entry points (backward compatibility) + +> [!WARNING] +> **These scripts are kept for backward compatibility and are not the +> recommended path.** New work should call `build_wheels.py` directly, as shown +> above, or use +> [ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction) +> in CI. +> +> They remain because remote modules fetch them by URL at a pinned tag, so +> removing them would break those consumers until each one is updated. They +> wrap the same `build_wheels.py` driver and add nothing you cannot do with it +> directly. Retirement is tracked in +> [issue #310](https://github.com/InsightSoftwareConsortium/ITKPythonPackage/issues/310). ```bash cd ITKRemoteModule @@ -194,6 +231,33 @@ Run from your module root: Finished wheels are placed in `/dist/`. +#### Verify what you built + +A build that exits 0 is not proof of a usable wheel. Check the tag and, on +macOS, that the binary agrees with it: + +```bash +ls dist/*.whl # expect one wheel per platform, cp311-abi3 +``` + +| Platform | Expected wheel tag | +|---|---| +| Linux x86_64 | `cp311-abi3-manylinux_2_28_x86_64` | +| Linux aarch64 | `cp311-abi3-manylinux_2_28_aarch64` | +| macOS arm64 | `cp311-abi3-macosx_14_0_arm64` | +| Windows x86_64 | `cp311-abi3-win_amd64` | + +On macOS the binary's minimum version must match the tag. A wheel tagged +below its real minimum installs on older macOS and then fails in dyld at +import, which is far harder to diagnose than a refused install: + +```bash +unzip -qq -o dist/.whl -d /tmp/whlcheck +otool -l /tmp/whlcheck/itk/*.so | awk '/minos/{print $2; exit}' # 14.0 +``` + +Then import it in a clean virtual environment, not in the build tree. +
@@ -206,7 +270,11 @@ If you have a local ITK with custom patches, a bug fix not yet released, or you' Pass `--itk-source-dir` pointing to your local ITK clone. `build_wheels.py` will build ITK from that source instead of re-cloning. -#### manylinux — building ITK from source +#### manylinux — building ITK from source (legacy shell path) + +> [!NOTE] +> Backward compatibility only; prefer `build_wheels.py` with +> `--manylinux-version`, shown in the next subsection. Use `dockcross-manylinux-build-wheels.sh` directly (skips the download step): @@ -222,7 +290,7 @@ Key environment variables: | `ITK_GIT_TAG` | `main` | ITK branch/tag/commit to build | | `ITK_SOURCE_DIR` | `/ITKPythonPackage-build/ITK` | Path to local ITK source (skips git clone) | | `MANYLINUX_VERSION` | `_2_28` | Manylinux standard to target | -| `IMAGE_TAG` | `20250913-6ea98ba` | Dockcross image tag | +| `IMAGE_TAG` | `20260203-3dfb3ff` (dockcross) or `2025.08.12-1` (quay manylinux) | Container image tag; the default depends on `CONTAINER_SOURCE` | #### Linux/macOS/Windows — building ITK from source @@ -230,7 +298,7 @@ Use `build_wheels.py` directly with `--itk-source-dir`: ```bash # Building on macOS with a specific git tag -pixi run python3 scripts/build_wheels.py \ +pixi run -e macosx-py311 python3 scripts/build_wheels.py \ --platform-env macosx-py311 \ --itk-source-dir /path/to/your/ITK \ --itk-git-tag my-bugfix-branch \ @@ -279,7 +347,9 @@ To publish the tarball caches to a GitHub Release, you can run: > [!NOTE] > This requires the `GH_TOKEN` environment variable to be set or `gh auth login` to have been run beforehand. -> Tarballs are expected in the parent directory of `--build-dir-root` (POSIX `.tar.zst`) or inside it (Windows `.zip`). +> Tarballs are expected in `/dist/` (POSIX `.tar.zst`) or in +> `` itself (Windows `.zip`). The parent directory is also +> searched, for caches left by older builds. ```bash pixi run -e publish publish-tarball-cache --itk-package-version v6.0b02 --build-dir-root /path/to/build/root @@ -384,6 +454,12 @@ for discussion related to your specific issue. If you aren't able to find an answer for your specific case, please start a discussion the [ITK Discourse forum](https://discourse.itk.org/) for help. +## For Contributors and Coding Agents + +[AGENTS.md](AGENTS.md) documents the full `build_wheels.py` argument list, the +wheel platform policy, and worked examples including building against an +unmerged ITK pull request with custom compiler flags. + ## Additional Information - Free software: Apache Software license From 3db07601c1238ed3f39e0295033eb49dbe3cb763 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 09:07:18 -0500 Subject: [PATCH 13/16] PERF: Bound ninja jobs by physical memory and key -l to the thread count The ITK C++ build ran ninja with -j -l. On a 16-thread machine with 32 GB that is 2 GB per job; the template-heavy wrapping units peak at 3.9 GB in a single cl.exe, and the build was killed for memory pressure in 2 of 3 clean attempts. Jobs are now min(cpu_count, floor(ram_gb / 2.5)). The budget comes from a three-point sweep on that machine, working sets sampled every 0.5 s over the whole build: -j16 step 02 4716 s killed in 2 of 3 clean builds -j12 step 02 4842 s peak build-process sum 15.7 GB, min free 5.75 GB -j8 step 02 5291 s peak build-process sum 12.2 GB, min free 8.96 GB Above 12 there is at most 2.7% to gain; below it the cost climbs to 12%. The marginal cost between -j8 and -j12 was 0.84 GB per job because peaks rarely coincide, so 2.5 GB/job lands on the measured optimum (floor(31.8 / 2.5) = 12) with ~5 GB of floor. The value floors rather than rounds so it cannot land one job past the tested point. -l stays at the thread count and must never follow the job count. ninja honours -l on Windows (1.13.2: eight 3 s jobs under `-l 0.5` serialise to 25 s), so -l on a box with more threads than jobs throttles ninja to one compiler whenever a saturating compile pushes load past . Links are not the pressure -- link.exe peaked at 1.45 GB and the whole link phase is ~1.3 min of an ~80 min step -- so there is no link pool. ITK_BUILD_JOBS and ITK_BUILD_LOAD_LIMIT override for CI runner sizing. physical_memory_gb() is dependency-free so it works in every build env; unknown memory falls back to the previous cpu_count behaviour. --- scripts/build_jobs.py | 153 ++++++++++++++++++++++++++ scripts/build_python_instance_base.py | 27 ++++- tests/test_build_jobs.py | 133 ++++++++++++++++++++++ tests/test_platform_builders.py | 1 + 4 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 scripts/build_jobs.py create mode 100644 tests/test_build_jobs.py diff --git a/scripts/build_jobs.py b/scripts/build_jobs.py new file mode 100644 index 00000000..f961452c --- /dev/null +++ b/scripts/build_jobs.py @@ -0,0 +1,153 @@ +"""Memory-aware build parallelism. + +``os.cpu_count()`` alone over-subscribes template-heavy C++ on machines with +little RAM per hardware thread. Sixteen concurrent ``cl.exe`` instances on a +32 GB box is ~2 GB each, below what ITK wrapping translation units peak at, +and the result is swapping or an out-of-memory kill rather than a faster +build. Bounding jobs by physical memory keeps the machine out of that regime. + +Two environment overrides exist so CI can tune without a code change: + +``ITK_BUILD_JOBS`` + Total ninja jobs. Replaces the computed value entirely. +``ITK_BUILD_LOAD_LIMIT`` + Value for ninja ``-l``; defaults to the hardware thread count. + +There is deliberately no separate link pool. On this build links are not +the memory pressure: link.exe peaked at 1.45 GB (one module; the median +output is 3 MB), the whole link phase is ~1.3 min of an ~80 min step, and +it overlaps the compile tail. The compilers are the constraint, and the +memory-bounded job count already limits them. +""" + +from __future__ import annotations + +import os +import platform +import subprocess +from collections.abc import Mapping + +#: RAM to budget per concurrent compile job, calibrated by measurement on a +#: 16-thread / 31.8 GiB box (ITK 6 wrapping, MSVC 19.38, sampled every 0.5 s): +#: +#: -j16 killed for memory pressure in 2 of 3 cold builds +#: -j12 step 02 4842 s, peak build-process sum 15.7 GB, min free 5.75 GB +#: -j8 step 02 5291 s, peak build-process sum 12.2 GB, min free 8.96 GB +#: +#: A single cl.exe peaks at 3.9 GB but peaks rarely coincide: the marginal +#: cost was 0.84 GB per extra job between -j8 and -j12. 2.5 GB/job lands on +#: the measured optimum (floor(31.8 / 2.5) = 12) and leaves ~5 GB of floor; +#: 2 GB/job (= cpu_count here) is the configuration that was being killed. +DEFAULT_GB_PER_JOB = 2.5 + + +def _cgroup_memory_limit_gb() -> float | None: + """Container memory limit in GiB, or ``None`` when unlimited or absent. + + ``sysconf(SC_PHYS_PAGES)`` reports the host's RAM even inside a + memory-capped container, so a build sized from it over-commits and is + killed. A 4 GiB container on a 24 GiB host otherwise budgets 22.5 GB. + """ + for path, unlimited in ( + ("/sys/fs/cgroup/memory.max", "max"), + ("/sys/fs/cgroup/memory/memory.limit_in_bytes", None), + ): + try: + raw = open(path).read().strip() + except OSError: + continue + if raw == unlimited: + return None + try: + limit = int(raw) + except ValueError: + continue + # cgroup v1 reports a sentinel near 2**63 when unlimited. + if limit <= 0 or limit >= 2**62: + return None + return limit / 1024**3 + return None + + +def physical_memory_gb() -> float | None: + """Total physical RAM in GiB, or ``None`` if it cannot be determined. + + Deliberately dependency-free (no psutil) so it runs inside the minimal + build environments on every platform. + """ + system = platform.system() + try: + if system == "Windows": + import ctypes + + class MEMORYSTATUSEX(ctypes.Structure): + _fields_ = [ + ("dwLength", ctypes.c_ulong), + ("dwMemoryLoad", ctypes.c_ulong), + ("ullTotalPhys", ctypes.c_ulonglong), + ("ullAvailPhys", ctypes.c_ulonglong), + ("ullTotalPageFile", ctypes.c_ulonglong), + ("ullAvailPageFile", ctypes.c_ulonglong), + ("ullTotalVirtual", ctypes.c_ulonglong), + ("ullAvailVirtual", ctypes.c_ulonglong), + ("ullAvailExtendedVirtual", ctypes.c_ulonglong), + ] + + stat = MEMORYSTATUSEX() + stat.dwLength = ctypes.sizeof(stat) + if not ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)): + return None + return stat.ullTotalPhys / 1024**3 + if system == "Darwin": + out = subprocess.check_output(["sysctl", "-n", "hw.memsize"], text=True) + return int(out.strip()) / 1024**3 + host_gb = os.sysconf("SC_PHYS_PAGES") * os.sysconf("SC_PAGE_SIZE") / 1024**3 + cgroup_gb = _cgroup_memory_limit_gb() + return min(host_gb, cgroup_gb) if cgroup_gb else host_gb + except Exception: + return None + + +def compute_build_jobs( + cpu_count: int | None, + memory_gb: float | None, + *, + gb_per_job: float = DEFAULT_GB_PER_JOB, + env: Mapping[str, str] | None = None, +) -> int: + """Number of ninja jobs: the smaller of the CPU count and what RAM affords. + + ``ITK_BUILD_JOBS`` in *env* overrides the computation. An unknown memory + size falls back to the CPU count, i.e. the historical behaviour. + """ + env = os.environ if env is None else env + override = env.get("ITK_BUILD_JOBS", "").strip() + if override: + return max(1, int(override)) + cpus = max(1, int(cpu_count or 1)) + if memory_gb is None or memory_gb <= 0: + return cpus + # floor, not round: the budget was calibrated at the measured-safe point + # (31.8 / 2.5 = 12.7 -> 12); rounding up would land one job past it. + return max(1, min(cpus, int(memory_gb // gb_per_job))) + + +def compute_load_limit( + cpu_count: int | None, env: Mapping[str, str] | None = None +) -> int: + """Value for ninja ``-l``: the hardware thread count, or an override. + + ``-l`` is a *CPU* budget and must not be tied to the memory-derived job + count. ninja >= 1.12 honours it on Windows too (verified on 1.13.2: eight + 3-second jobs under ``-l 0.5`` serialise to 25 s), so ``-l `` on a + box with more threads than jobs throttles ninja whenever load exceeds the + job count -- which under a saturating compile is always. Keying it to the + thread count makes it a no-op under normal load and a safety valve only + when something else is loading the machine. ``ITK_BUILD_LOAD_LIMIT`` + overrides. + """ + env = os.environ if env is None else env + override = env.get("ITK_BUILD_LOAD_LIMIT", "").strip() + if override: + return max(1, int(override)) + return max(1, int(cpu_count or 1)) diff --git a/scripts/build_python_instance_base.py b/scripts/build_python_instance_base.py index 02088224..2e0c1d3a 100644 --- a/scripts/build_python_instance_base.py +++ b/scripts/build_python_instance_base.py @@ -9,6 +9,7 @@ import build_environment import wheel_packaging +from build_jobs import compute_build_jobs, compute_load_limit, physical_memory_gb from BuildManager import BuildManager from cmake_argument_builder import CMakeArgumentBuilder from module_dependencies import build_module_dependencies, update_module_itk_deps @@ -94,7 +95,29 @@ def __init__( skip_itk_build: bool | None = None, skip_itk_wheel_build: bool | None = None, ) -> None: - self.build_node_cpu_count: int = os.cpu_count() or 1 + # Jobs are bounded by physical memory, not just CPU count: ITK's + # wrapping units are template-heavy, and cpu_count() jobs on a box + # with ~2 GB RAM per thread thrashes instead of building faster. + # See build_jobs.py; ITK_BUILD_JOBS / ITK_BUILD_LOAD_LIMIT override. + self.build_node_memory_gb: float | None = physical_memory_gb() + self.build_node_cpu_count: int = compute_build_jobs( + os.cpu_count(), self.build_node_memory_gb + ) + # -l is a CPU budget, keyed to the thread count, never to the + # memory-derived job count: ninja honours -l on Windows, and + # -l on a box with more threads than jobs throttles ninja to + # a crawl whenever a saturating compile pushes load past . + self.build_load_limit: int = compute_load_limit(os.cpu_count()) + _mem = ( + f"{self.build_node_memory_gb:.1f} GB" + if self.build_node_memory_gb + else "unknown" + ) + print( + f"# Build parallelism: -j{self.build_node_cpu_count} " + f"-l{self.build_load_limit} " + f"(cpus={os.cpu_count()}, ram={_mem})" + ) self.platform_env = platform_env self.ipp_dir = Path(__file__).parent.parent @@ -877,7 +900,7 @@ def build_wrapped_itk_cplusplus(self): [ self.package_env_config["NINJA_EXECUTABLE"], f"-j{self.build_node_cpu_count}", - f"-l{self.build_node_cpu_count}", + f"-l{self.build_load_limit}", "-C", self.cmake_itk_source_build_configurations["ITK_BINARY_DIR:PATH"], ], diff --git a/tests/test_build_jobs.py b/tests/test_build_jobs.py new file mode 100644 index 00000000..745cc684 --- /dev/null +++ b/tests/test_build_jobs.py @@ -0,0 +1,133 @@ +import build_jobs +import pytest +from build_jobs import ( + DEFAULT_GB_PER_JOB, + compute_build_jobs, + compute_load_limit, + physical_memory_gb, +) + + +def test_jobs_are_bounded_by_memory_not_just_cpus(): + """16 threads on 32 GB must not yield -j16: that is 2 GB/job and thrashes.""" + assert compute_build_jobs(16, 32.0, env={}) == 12 + + +def test_jobs_are_bounded_by_cpus_when_memory_is_plentiful(): + assert compute_build_jobs(8, 256.0, env={}) == 8 + + +def test_unknown_memory_falls_back_to_cpu_count(): + """Historical behaviour when RAM cannot be determined.""" + assert compute_build_jobs(12, None, env={}) == 12 + assert compute_build_jobs(12, 0, env={}) == 12 + + +def test_jobs_never_drop_below_one(): + assert compute_build_jobs(16, 1.0, env={}) == 1 + assert compute_build_jobs(None, None, env={}) == 1 + + +def test_env_override_replaces_the_computation(): + assert compute_build_jobs(16, 32.0, env={"ITK_BUILD_JOBS": "3"}) == 3 + assert compute_build_jobs(16, 32.0, env={"ITK_BUILD_JOBS": " 5 "}) == 5 + + +def test_env_override_is_clamped_to_one(): + assert compute_build_jobs(16, 32.0, env={"ITK_BUILD_JOBS": "0"}) == 1 + + +def test_gb_per_job_is_the_documented_default(): + assert DEFAULT_GB_PER_JOB == 2.5 + assert compute_build_jobs(64, 40.0, env={}) == 16 + + +def test_physical_memory_is_plausible_on_the_host(): + """Runs for real on every platform the suite runs on.""" + gb = physical_memory_gb() + assert gb is None or 0.5 < gb < 65536 + + +def test_load_limit_is_the_thread_count_not_the_job_count(): + """-l is a CPU budget. Tying it to the memory-derived -j throttled a + 16-thread box to one compiler at a time (ninja honours -l on Windows).""" + assert compute_load_limit(16, env={}) == 16 + assert compute_load_limit(16, env={}) != compute_build_jobs(16, 32.0, env={}) + + +def test_load_limit_override_and_floor(): + assert compute_load_limit(16, env={"ITK_BUILD_LOAD_LIMIT": "12"}) == 12 + assert compute_load_limit(16, env={"ITK_BUILD_LOAD_LIMIT": "0"}) == 1 + assert compute_load_limit(None, env={}) == 1 + + +def test_budget_reproduces_the_measured_optimum(): + """The calibration point: 16 threads, 31.8 GiB -> -j12. + + Measured 2026-09-19: -j12 finished with 5.75 GB free at peak and was + within 3% of -j16's step-02 time, while -j16 was killed for memory + pressure in 2 of 3 cold builds. The budget must land on 12, not 13. + """ + assert compute_build_jobs(16, 31.8, env={}) == 12 + + +def test_budget_floors_rather_than_rounds_up(): + """32.4 GiB / 2.5 = 12.96: rounding would give 13, one past the tested point.""" + assert compute_build_jobs(16, 32.4, env={}) == 12 + + +def test_the_killed_configuration_is_not_reproduced(): + """2 GB/job is cpu_count on this box, i.e. the setting that was being killed.""" + assert compute_build_jobs(16, 31.8, gb_per_job=2.0, env={}) == 15 + assert compute_build_jobs(16, 31.8, env={}) < 15 + + +def _fake_open(mapping): + real_open = open + + def fake(path, *args, **kwargs): + key = str(path) + if key in mapping: + import io + + return io.StringIO(mapping[key]) + if key.startswith("/sys/fs/cgroup/"): + raise OSError("no such cgroup file") + return real_open(path, *args, **kwargs) + + return fake + + +def _linux_host_with(monkeypatch, host_gb, cgroup_files): + monkeypatch.setattr(build_jobs.platform, "system", lambda: "Linux") + monkeypatch.setattr( + build_jobs.os, + "sysconf", + lambda name: int(host_gb * 1024**3) // 4096 if "PHYS" in str(name) else 4096, + ) + monkeypatch.setattr("builtins.open", _fake_open(cgroup_files)) + + +def test_cgroup_v2_limit_caps_reported_memory(monkeypatch): + """A capped container must not size the build from the host's RAM.""" + _linux_host_with(monkeypatch, 24.0, {"/sys/fs/cgroup/memory.max": str(4 * 1024**3)}) + assert build_jobs.physical_memory_gb() == pytest.approx(4.0) + + +def test_cgroup_unlimited_falls_back_to_host(monkeypatch): + _linux_host_with(monkeypatch, 24.0, {"/sys/fs/cgroup/memory.max": "max"}) + assert build_jobs.physical_memory_gb() == pytest.approx(24.0) + + +def test_cgroup_v1_sentinel_treated_as_unlimited(monkeypatch): + _linux_host_with( + monkeypatch, + 24.0, + {"/sys/fs/cgroup/memory/memory.limit_in_bytes": str(2**63 - 1)}, + ) + assert build_jobs.physical_memory_gb() == pytest.approx(24.0) + + +def test_no_cgroup_files_uses_host_memory(monkeypatch): + _linux_host_with(monkeypatch, 24.0, {}) + assert build_jobs.physical_memory_gb() == pytest.approx(24.0) diff --git a/tests/test_platform_builders.py b/tests/test_platform_builders.py index f6eb472b..b8f9bb61 100644 --- a/tests/test_platform_builders.py +++ b/tests/test_platform_builders.py @@ -360,6 +360,7 @@ def _echo(cmd, *args, check: bool = False, **kwargs) -> int: def _wire_itk_cplusplus(builder, tmp_path): builder.cleanup = False builder.build_node_cpu_count = 2 + builder.build_load_limit = 2 builder.cmake_cmdline_definitions = CMakeArgumentBuilder() builder.cmake_itk_source_build_configurations.set( "ITK_BINARY_DIR:PATH", str(tmp_path / "itk-bld") From 0cef5f48133b3447778c2279b0daf92a731cd224 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 09:10:13 -0500 Subject: [PATCH 14/16] PERF: Build the ITK wheels with Ninja on Windows, as the module wheels are pixi's conda-forge vs2022 activation exports CMAKE_GENERATOR="Visual Studio 17 2022". scikit-build-core honours it, so the seven ITK wheel builds in step 03 ran under MSBuild while ITK itself was configured with -G Ninja. The remote-module wheel build already passes -GNinja explicitly for this reason; do the same for the ITK wheels. Measured on a 16-thread Windows box: step 03 went from 190-216 s under the Visual Studio generator to 159-168 s under Ninja. An explicit -G also makes CMake ignore the ambient CMAKE_GENERATOR_PLATFORM/TOOLSET that the same activation exports and that Ninja would otherwise reject. --- scripts/build_python_instance_base.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/build_python_instance_base.py b/scripts/build_python_instance_base.py index 2e0c1d3a..367e4a7c 100644 --- a/scripts/build_python_instance_base.py +++ b/scripts/build_python_instance_base.py @@ -821,6 +821,14 @@ def build_itk_python_wheels(self): f"--config-setting=cmake.source-dir={self.package_env_config['IPP_SOURCE_DIR'] / 'BuildWheelsSupport'}", f"--config-setting=build-dir={wheel_configbuild_dir_root/'build'}", ] + if self.target.os_name == "windows": + # Same reason as the remote-module build below: pixi's + # vs2022 activation exports CMAKE_GENERATOR="Visual Studio + # 17 2022", scikit-build-core honours it, and the ITK wheels + # were being built with MSBuild while ITK itself used Ninja. + # An explicit -G also makes CMake ignore the ambient + # CMAKE_GENERATOR_PLATFORM/TOOLSET that Ninja would reject. + cmd.append("--config-setting=cmake.args=-GNinja") # Build scikit-build defines via builder scikitbuild_cmdline_args = CMakeArgumentBuilder() scikitbuild_cmdline_args.update(self.cmake_compiler_configurations.items()) From cf5de5351f00d2d4356ea224ba38e657a48cab5d Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 10:20:47 -0500 Subject: [PATCH 15/16] COMP: Restore the manylinux image-coordinate script The reusable GitHub Actions workflow fetches this file by URL to learn which container image to pre-pull. Removing it left the action without a source for the image tags, and the file is the only place those coordinates are stated once for both architectures. The manylinux2014 branch is dropped: ITK 6 targets manylinux_2_28 only, so that path now reports an unknown version rather than resolving. --- scripts/dockcross-manylinux-set-vars.sh | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 scripts/dockcross-manylinux-set-vars.sh diff --git a/scripts/dockcross-manylinux-set-vars.sh b/scripts/dockcross-manylinux-set-vars.sh new file mode 100755 index 00000000..ec8d4539 --- /dev/null +++ b/scripts/dockcross-manylinux-set-vars.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +######################################################################## +# Run this script to set common enviroment variables used in building the +# ITK Python wheel packages for Linux. +# +# ENVIRONMENT VARIABLES +# These environment variables will be populated by the script when invoked with `source` +# if their value is not set with `export` before invocation. +# For example, +# +# export ITK_PACKAGE_VERSION=v5.4.0 +# scripts/dockcross-manylinux-set-vars.sh cp310 +# +######################################################################## + +######################################################################## +# ITKPythonBuilds parameters + +# ITKPythonBuilds archive tag to use for ITK build artifacts. +# See https://github.com/insightSoftwareConsortium/ITKpythonbuilds for available tags. +ITK_PACKAGE_VERSION=${ITK_PACKAGE_VERSION:=v6.0b01} + +# Github organization for fetching ITKPythonPackage build scripts +ITKPYTHONPACKAGE_ORG=${ITKPYTHONPACKAGE_ORG:=InsightSoftwareConsortium} + +# ITKPythonPackage tag for fetching build scripts +ITKPYTHONPACKAGE_TAG=${ITKPYTHONPACKAGE_TAG:=main} + +######################################################################## +# Docker image parameters + +# Specialized manylinux image to use for building. Default is _2_28. +# See https://github.com/dockcross/dockcross for available versions and tags. +MANYLINUX_VERSION=${MANYLINUX_VERSION:=_2_28} + +# Target platform architecture (x64, aarch64) +TARGET_ARCH=${TARGET_ARCH:=x64} + +# Specialized manylinux image tag to use for building. +if [[ ${MANYLINUX_VERSION} == _2_28 && ${TARGET_ARCH} == x64 ]]; then + export IMAGE_TAG=${IMAGE_TAG:=20260203-3dfb3ff} +elif [[ ${MANYLINUX_VERSION} == _2_28 && ${TARGET_ARCH} == aarch64 ]]; then + export IMAGE_TAG=${IMAGE_TAG:=2025.08.12-1} +else + echo "Unknown manylinux version ${MANYLINUX_VERSION}" + exit 1 +fi + +# Set container for requested version/arch/tag. +if [[ ${TARGET_ARCH} == x64 ]]; then + export MANYLINUX_IMAGE_NAME=${MANYLINUX_IMAGE_NAME:="manylinux${MANYLINUX_VERSION}-${TARGET_ARCH}:${IMAGE_TAG}"} + export CONTAINER_SOURCE="docker.io/dockcross/${MANYLINUX_IMAGE_NAME}" +elif [[ ${TARGET_ARCH} == aarch64 ]]; then + export MANYLINUX_IMAGE_NAME=${MANYLINUX_IMAGE_NAME:="manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}:${IMAGE_TAG}"} + export CONTAINER_SOURCE="quay.io/pypa/${MANYLINUX_IMAGE_NAME}" +else + echo "Unknown target architecture ${TARGET_ARCH}" + exit 1 +fi From 6a426149f218c6e6aa852b5c6c48fc8b3c4a47ff Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Sun, 20 Sep 2026 11:39:59 -0500 Subject: [PATCH 16/16] DOC: Remove the unused Sphinx batch file and correct the cache path note Nothing references docs/make.bat; docs/Makefile remains for building the documentation. The cache-path note claimed the packaging scripts enforce the build path convention without saying that build_wheels.py does not. Its default build root matches the convention only on Windows, so a cache built by calling it directly is unusable by the reusable workflow unless --build-dir-root is set. State the paths as a table and give the command to check a finished cache. --- README.md | 23 ++++++++++++++++++----- docs/make.bat | 36 ------------------------------------ 2 files changed, 18 insertions(+), 41 deletions(-) delete mode 100644 docs/make.bat diff --git a/README.md b/README.md index bbbaca67..b23ad378 100644 --- a/README.md +++ b/README.md @@ -330,12 +330,25 @@ On Windows systems ``` > [!IMPORTANT] -> Build caches embed absolute paths. If you extract a tarball to a different path than it was built with, CMake will fail. Standard build paths for CI/CD are: -> - manylinux (Docker): `/work/ITKPythonPackage-build` -> - macOS: `/Users/svc-dashboard/D/P/ITKPythonPackage-build` -> - Windows: `C:\BDR` +> Build caches embed absolute paths. If you extract a tarball to a different +> path than it was built with, CMake will fail. The paths CI extracts to are: > -> This script ensures you are building with the correct conventions +> | Platform | Path the cache must be built at | +> |---|---| +> | manylinux (Docker) | `/work/ITKPythonPackage-build` | +> | macOS | `/Users/svc-dashboard/D/P/ITKPythonPackage-build` | +> | Windows | `C:\BDR` | +> +> The scripts above set these for you. **`build_wheels.py` does not**: its +> default build root is `/../ITKPythonPackage-build`, which matches the +> convention only on Windows. A cache built by calling `build_wheels.py` +> directly therefore needs `--build-dir-root` set to the path in the table, or +> it will not be usable by the reusable workflow. Check a finished cache with: +> +> ```bash +> tar -xOf .tar ITKPythonPackage-build/build/ITK-support-bld/CMakeCache.txt \ +> | grep CMAKE_CACHEFILE_DIR +> ``` #### Local Caches diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 88248780..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=ITKPythonPackage - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd