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/.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/.gitignore b/.gitignore index cffba1ed..3d1c0542 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,15 @@ docs/_build # IDE junk .idea/* *.swp +/itkVersion.py +# PyPI credentials +.pypirc + +# 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/.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/.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/.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/.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/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/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 89% rename from scripts/WHEEL_NAMES.txt rename to BuildWheelsSupport/WHEEL_NAMES.txt index 94ff00af..434c06c1 100644 --- a/scripts/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/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. 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/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/README.md b/README.md index 052a849a..b23ad378 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # 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. -Scripts are available for both [ITK infrastructure](https://github.com/insightSoftwareConsortium/ITK) and +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. The Insight Toolkit (ITK) is an open-source, cross-platform system that provides developers @@ -12,85 +12,408 @@ 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. + +Pre-built ITK binaries are available as downloadable tarballs and the provided download-and-build shell scripts will fetch them automatically. + +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 + +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 + +- [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 +# Linux or Mac +curl -fsSL https://pixi.sh/install.sh | bash -### Installation of pre-built packages +# Windows +powershell -ExecutionPolicy Bypass -c "irm -useb https://pixi.sh/install.ps1 | iex" +``` + +**Clone the repo:** +```bash +git clone https://github.com/InsightSoftwareConsortium/ITKPythonPackage.git +cd ITKPythonPackage +``` -To install baseline ITK Python packages: +--- + +### 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 8 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 +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. + +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 -e macosx-py311 python3 scripts/build_wheels.py \ + --platform-env macosx-py311 \ + --itk-git-tag v6.0b01 \ + --no-build-itk-tarball-cache +``` -```sh -> pip install itk +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` | 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 -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. + +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: + +
+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 ``` -To install ITK external module packages: +#### Linux (manylinux) + +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 -```sh -> pip install itk- +Run from your ITK external module root: +```bash +bash dockcross-manylinux-download-cache-and-build-module-wheels.sh cp311 ``` -### Using ITK in Python scripts +> [!NOTE] +> Omit the Python version argument (e.g. `cp311`) to build for the default version (cp311). -```python - import itk - import sys +#### macOS - input_filename = sys.argv[1] - output_filename = sys.argv[2] +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 - image = itk.imread(input_filename) +Run from your module root: +```bash +bash macpython-download-cache-and-build-module-wheels.sh 3.11 +``` - median = itk.median_image_filter(image, radius=2) +#### Windows - itk.imwrite(median, output_filename) +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 ``` -### Other Resources for Using ITK in Python -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). +#### Output -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). +Finished wheels are placed in `/dist/`. -## Building with ITKPythonPackage +#### Verify what you built -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. +A build that exits 0 is not proof of a usable wheel. Check the tag and, on +macOS, that the binary agrees with it: -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. +```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. + +
+ +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 (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): + +```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` | `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 + +Use `build_wheels.py` directly with `--itk-source-dir`: + +```bash +# Building on macOS with a specific git tag +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 \ + --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. The paths CI extracts to are: +> +> | 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 + +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 `/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 +``` + +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? @@ -144,6 +467,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 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/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/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/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/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/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/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/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/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 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 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/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/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/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_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 new file mode 100644 index 00000000..367e4a7c --- /dev/null +++ b/scripts/build_python_instance_base.py @@ -0,0 +1,939 @@ +import copy +import os +import shutil +import subprocess +from abc import ABC, abstractmethod +from collections import OrderedDict +from collections.abc import Callable +from pathlib import Path + +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 +from pyproject_configure import STABLE_ABI_TAG, configure_one_pyproject_file +from target_platform import TargetPlatform +from wheel_builder_utils import ( + _remove_tree, + _which, +) + + +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: + # 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 + + 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.""" + wheel_packaging.post_build_cleanup(self) + + 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'}", + ] + 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()) + 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_load_limit}", + "-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.""" + wheel_packaging.create_posix_tarball(self) + + 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/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/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/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-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-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-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-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-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/dockcross-manylinux-set-vars.sh b/scripts/dockcross-manylinux-set-vars.sh index f39ce7ab..ec8d4539 100755 --- a/scripts/dockcross-manylinux-set-vars.sh +++ b/scripts/dockcross-manylinux-set-vars.sh @@ -39,24 +39,22 @@ 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} + export 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} + export IMAGE_TAG=${IMAGE_TAG:=2025.08.12-1} else echo "Unknown manylinux version ${MANYLINUX_VERSION}" - exit 1; + 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}" + 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 - MANYLINUX_IMAGE_NAME=${MANYLINUX_IMAGE_NAME:="manylinux${MANYLINUX_VERSION}_${TARGET_ARCH}:${IMAGE_TAG}"} - CONTAINER_SOURCE="quay.io/pypa/${MANYLINUX_IMAGE_NAME}" + 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; + exit 1 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/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/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/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/linux_build_python_instance.py b/scripts/linux_build_python_instance.py new file mode 100644 index 00000000..9def6349 --- /dev/null +++ b/scripts/linux_build_python_instance.py @@ -0,0 +1,253 @@ +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: + 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, + ] + 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/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-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/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/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/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/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/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.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/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/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/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/wheel_builder_utils.py b/scripts/wheel_builder_utils.py new file mode 100644 index 00000000..b9950eaf --- /dev/null +++ b/scripts/wheel_builder_utils.py @@ -0,0 +1,617 @@ +"""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 + + +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. + """ + 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, + 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 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/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/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-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 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_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/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() diff --git a/scripts/internal/libpython-not-needed-symbols-exported-by-interpreter b/tests/__init__.py similarity index 100% rename from scripts/internal/libpython-not-needed-symbols-exported-by-interpreter rename to tests/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..e750fe65 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +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 + + +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 = 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_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_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_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_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_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.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) 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_platform_builders.py b/tests/test_platform_builders.py new file mode 100644 index 00000000..b8f9bb61 --- /dev/null +++ b/tests/test_platform_builders.py @@ -0,0 +1,493 @@ +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_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"), + ): + 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: + 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 _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: + 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) + 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.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_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.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") + ) + 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) 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_target_platform.py b/tests/test_target_platform.py new file mode 100644 index 00000000..4505416d --- /dev/null +++ b/tests/test_target_platform.py @@ -0,0 +1,219 @@ +import os +import re +import tomllib +from pathlib import Path +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 ( + _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") + + +def _fake_uname(sysname, machine): + return SimpleNamespace(sysname=sysname, machine=machine) + + +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): + _patch_posix_uname(monkeypatch, "Darwin", "arm64") + 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): + _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): + _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): + _patch_posix_uname(monkeypatch, "Linux", "aarch64") + 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.""" + 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_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) + 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 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..db94f129 --- /dev/null +++ b/tests/test_wheel_builder_utils.py @@ -0,0 +1,96 @@ +"""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): + 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): + 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): + 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): + 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): + 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])) + 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): + 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): + with pytest.raises(RuntimeError, match="git fetch"): + compute_itk_package_version(tmp_path, "v6.0.0", None, {}) 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 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