From 9d34e2b63197f6c0cea3ddb77898107c9a19a753 Mon Sep 17 00:00:00 2001 From: Ezra Brooks Date: Mon, 9 Nov 2026 14:26:03 -0700 Subject: [PATCH 1/3] Read the skip-existing channels with py-rattler get_repodata fetched //repodata.json by hand: its own protocol handling, an MD5-keyed cache in the working directory with a fixed 27-hour lifetime, and callers that each merged "packages" and "packages.conda" themselves. get_channel_records reads the channel with rattler.fetch_repo_data instead, which caches by HTTP semantics and handles zstd-compressed repodata, and returns the records of both artifact types. As before, a channel without (valid) repodata for the platform has no records and an unreachable one raises. vinca-migrate now also sees .conda artifacts. --- pixi.lock | 294 +++++++++++++++++++++-------- pyproject.toml | 1 + vinca/generate_gha.py | 25 ++- vinca/main.py | 10 +- vinca/migrate.py | 6 +- vinca/pipeline.py | 10 +- vinca/skip_existing.py | 16 +- vinca/test_skip_existing_builds.py | 20 +- vinca/test_utils.py | 113 ++++++++--- vinca/utils.py | 92 ++++----- 10 files changed, 371 insertions(+), 216 deletions(-) diff --git a/pixi.lock b/pixi.lock index 9548bfc..5de9c00 100644 --- a/pixi.lock +++ b/pixi.lock @@ -46,6 +46,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.19.2-hee9eb32_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/py-rattler-0.28.1-py311hc91d8b8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/pyrefly-1.2.0-hb17b654_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda @@ -57,6 +58,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda - conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.25.0-py314hfe1a184_3.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda @@ -64,6 +66,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/cpython-3.14.10-py314hd8ed1ab_100.conda - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/docutils-0.23-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 @@ -86,6 +89,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python-gil-3.14.10-h4df99d1_100.conda - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -98,8 +102,9 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda_source: vinca[c2d8a98d] @ . + - conda_source: vinca[239d55bf] @ . osx-64: + - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda @@ -107,6 +112,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/cpython-3.14.10-py314hd8ed1ab_100.conda - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/docutils-0.23-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 @@ -129,6 +135,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python-gil-3.14.10-h4df99d1_100.conda - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -154,6 +161,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/markupsafe-3.0.3-py314h77fa6c7_1.conda - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/py-rattler-0.28.1-py311h85c30bf_0.conda - conda: https://prefix.dev/conda-forge/osx-64/pyrefly-1.2.0-h19f9e61_0.conda - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda - conda: https://prefix.dev/conda-forge/osx-64/pyyaml-6.0.3-py314h10d0514_1.conda @@ -165,8 +173,9 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-had63097_3.conda - conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.25.0-py314h17af80e_3.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - - conda_source: vinca[e0187b5e] @ . + - conda_source: vinca[5eece575] @ . osx-arm64: + - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda @@ -174,6 +183,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/cpython-3.14.10-py314hd8ed1ab_100.conda - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/docutils-0.23-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 @@ -196,6 +206,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python-gil-3.14.10-h4df99d1_100.conda - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -222,6 +233,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/py-rattler-0.28.1-py311h1e0e963_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyrefly-1.2.0-h6fdd925_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.0-h40d2674_102_cp314.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda @@ -233,8 +245,9 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.25.0-py314hd98292b_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - - conda_source: vinca[1c223f3d] @ . + - conda_source: vinca[5d4b4b66] @ . win-64: + - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda @@ -242,6 +255,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/cpython-3.14.10-py314hd8ed1ab_100.conda - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/docutils-0.23-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 @@ -264,6 +278,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python-gil-3.14.10-h4df99d1_100.conda - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://prefix.dev/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -288,6 +303,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + - conda: https://prefix.dev/conda-forge/win-64/py-rattler-0.28.1-py311h66afae6_0.conda - conda: https://prefix.dev/conda-forge/win-64/pyrefly-1.2.0-h18a1a76_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda @@ -302,7 +318,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_3.conda - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - - conda_source: vinca[bbc4be42] @ . + - conda_source: vinca[67615039] @ . packages: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -775,6 +791,27 @@ packages: run_exports: {} size: 152126 timestamp: 1790866799280 +- conda: https://prefix.dev/conda-forge/linux-64/py-rattler-0.28.1-py311hc91d8b8_0.conda + noarch: python + sha256: a205cc0cbdc40f7ca84aec0741ca241a8d007f46130898bfb6b4648769ddb222 + md5: 5372f7af3bd02484bf49d68f42f1bae6 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - openssl >=3.5.9,<4.0a0 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __glibc >=2.17 + extra_depends: + networkx: + - networkx + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 15049429 + timestamp: 1793309611253 - conda: https://prefix.dev/conda-forge/linux-64/pyrefly-1.2.0-hb17b654_0.conda sha256: 937a57a855f4a7f0994a561fbee5a089d509faf9b0668ae2fd02a71dc606dc4f md5: 2c8a7d2685e9b1aff86433c47a883431 @@ -1027,6 +1064,17 @@ packages: purls: [] size: 567578 timestamp: 1742433379869 +- conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: 3845f3d75991bae0fb90884662f4327c + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + run_exports: {} + size: 8144 + timestamp: 1784221492234 - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda noarch: generic sha256: 19514d89d1e725e44b0650d31a4d43da8e070e4e93e4131e3b16bd139404f2b2 @@ -1128,6 +1176,17 @@ packages: - pkg:pypi/colorama?source=hash-mapping size: 27011 timestamp: 1733218222191 +- conda: https://prefix.dev/conda-forge/noarch/cpython-3.14.10-py314hd8ed1ab_100.conda + noarch: generic + sha256: 80c395601d9cff6d342d3dc81b7c19fabf00a3644b7df053cff14783077e6d15 + md5: 81608e10400d700890e3782b9b8d3987 + depends: + - python >=3.14,<3.15.0a0 + - python_abi * *_cp314 + license: Python-2.0 + run_exports: {} + size: 52072 + timestamp: 1793014778194 - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda sha256: 5603c7d0321963bb9b4030eadabc3fd7ca6103a38475b4e0ed13ed6d97c86f4e md5: 0a2014fd9860f8b1eaa0b1f3d3771a08 @@ -1456,6 +1515,16 @@ packages: run_exports: {} size: 233310 timestamp: 1751104122689 +- conda: https://prefix.dev/conda-forge/noarch/python-gil-3.14.10-h4df99d1_100.conda + sha256: d3d2134ec9f8c42095bb823260499352cfc14d78d97d12d4abb84fbe06f7f84d + md5: 834210c44f2cb3f1fb07373f62b21cbf + depends: + - cpython 3.14.10.* + - python_abi * *_cp314 + license: Python-2.0 + run_exports: {} + size: 52037 + timestamp: 1793014785617 - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 @@ -1921,6 +1990,25 @@ packages: - openssl >=3.6.4,<4.0a0 size: 2780873 timestamp: 1787700098779 +- conda: https://prefix.dev/conda-forge/osx-64/py-rattler-0.28.1-py311h85c30bf_0.conda + noarch: python + sha256: 20e3660e16ad7994a8a69bf3e67bf3495b27b949cebb921a227cdfb47f123ba6 + md5: 0e2ba8eb0a436efb8af0628aae74f49b + depends: + - python + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __osx >=11.0 + extra_depends: + networkx: + - networkx + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 14590908 + timestamp: 1793309708900 - conda: https://prefix.dev/conda-forge/osx-64/pyrefly-1.2.0-h19f9e61_0.conda sha256: 5c5802b4fb5d051ee6e666e4b8adec6de0733a619fb7f507ec374e45984003d8 md5: c7de32d660332c51ec5fd4421b1486cd @@ -2442,6 +2530,25 @@ packages: - openssl >=3.6.4,<4.0a0 size: 3110142 timestamp: 1787698648639 +- conda: https://prefix.dev/conda-forge/osx-arm64/py-rattler-0.28.1-py311h1e0e963_0.conda + noarch: python + sha256: 32239dd953c2e6ecce9d1d79cd1ca145c0088a7261ba703c181adc88891d88c3 + md5: 8f5df37090edaca13f6cf60fbf2d8ff5 + depends: + - python + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.11 + constrains: + - __osx >=11.0 + extra_depends: + networkx: + - networkx + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 13242554 + timestamp: 1793309637666 - conda: https://prefix.dev/conda-forge/osx-arm64/pyrefly-1.2.0-h6fdd925_0.conda sha256: ecb46769c4b863b3a5aaa3932e06972907439d83a612253de6155f9d47d1e6ab md5: 958874b3b5800b69299c402002608b84 @@ -2943,6 +3050,25 @@ packages: - openssl >=3.6.4,<4.0a0 size: 9474879 timestamp: 1787699876495 +- conda: https://prefix.dev/conda-forge/win-64/py-rattler-0.28.1-py311h66afae6_0.conda + noarch: python + sha256: 3e15f8f31d02178aeaba8c6250f832fa82eac1f2c5e762130084c36b9c8bc6ac + md5: 927cbf1a57f34b7693a6f05f845b8532 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - _python_abi3_support 1.* + - cpython >=3.11 + extra_depends: + networkx: + - networkx + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 16468002 + timestamp: 1793309604859 - conda: https://prefix.dev/conda-forge/win-64/pyrefly-1.2.0-h18a1a76_0.conda sha256: e61ebfd255a77f090d8093fc50e048df111afce7222407eb41003185b2ac63ae md5: 281d3c3c1d8a12e19c5e3f4fa2a248f9 @@ -3249,7 +3375,7 @@ packages: purls: [] size: 354697 timestamp: 1742433568506 -- conda_source: vinca[1c223f3d] @ . +- conda_source: vinca[239d55bf] @ . variants: target_platform: noarch depends: @@ -3266,8 +3392,31 @@ packages: - license-expression >=30.0.0 - packaging >=23.0 - zstandard >=0.19.0 + - py-rattler >=0.28.1,<0.29 license: MIT host_packages: + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://prefix.dev/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libpython-3.14.7-hdc7f604_104_cp314.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.7-hcd007b5_104_cp314.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/uv-0.12.8-h86a270d_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda @@ -3279,24 +3428,7 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda -- conda_source: vinca[bbc4be42] @ . +- conda_source: vinca[5d4b4b66] @ . variants: target_platform: noarch depends: @@ -3313,9 +3445,10 @@ packages: - license-expression >=30.0.0 - packaging >=23.0 - zstandard >=0.19.0 + - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3326,24 +3459,24 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda - - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda - - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda - - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda - - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda - - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda -- conda_source: vinca[c2d8a98d] @ . + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda +- conda_source: vinca[5eece575] @ . variants: target_platform: noarch depends: @@ -3360,30 +3493,9 @@ packages: - license-expression >=30.0.0 - packaging >=23.0 - zstandard >=0.19.0 + - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda - - conda: https://prefix.dev/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda - - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/libpython-3.14.7-hdc7f604_104_cp314.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.7-hcd007b5_104_cp314.conda - - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/uv-0.12.8-h86a270d_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda @@ -3395,7 +3507,23 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda -- conda_source: vinca[e0187b5e] @ . + - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda + - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.8.1-hcc62823_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.7.0-h6b3a05b_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-ha1e9b39_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/libpython-3.14.7-hfe304d0_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.53.4-ha5db789_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.6.4-h332eb6d_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.7-hafa0b4b_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-64/readline-8.3-h000c2f7_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda + - conda: https://prefix.dev/conda-forge/osx-64/uv-0.12.7-h6c1caad_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda +- conda_source: vinca[67615039] @ . variants: target_platform: noarch depends: @@ -3412,9 +3540,10 @@ packages: - license-expression >=30.0.0 - packaging >=23.0 - zstandard >=0.19.0 + - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3425,19 +3554,20 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda - - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.8.1-hcc62823_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.7.0-h6b3a05b_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-ha1e9b39_2.conda - - conda: https://prefix.dev/conda-forge/osx-64/libpython-3.14.7-hfe304d0_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.53.4-ha5db789_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda - - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.6.4-h332eb6d_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.7-hafa0b4b_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-64/readline-8.3-h000c2f7_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - - conda: https://prefix.dev/conda-forge/osx-64/uv-0.12.7-h6c1caad_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda diff --git a/pyproject.toml b/pyproject.toml index a68f09b..934710a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "license-expression >=30.0.0", "packaging >=23.0", "zstandard >=0.19.0", + "py-rattler >=0.28.1,<0.29", ] [project.urls] diff --git a/vinca/generate_gha.py b/vinca/generate_gha.py index ae99fb2..a15aee7 100644 --- a/vinca/generate_gha.py +++ b/vinca/generate_gha.py @@ -132,7 +132,7 @@ def add_additional_recipes(args: Namespace): return [] vinca_conf = read_vinca_yaml("vinca.yaml") - repodatas = get_skip_existing(vinca_conf, args.platform) + records = get_skip_existing(vinca_conf, args.platform) additional_recipes = [] for recipe_path in glob.glob(additional_recipes_path + "/**/recipe.yaml"): @@ -146,19 +146,16 @@ def add_additional_recipes(args: Namespace): ) print("Checking if ", name, version, bnumber, " exists") skip = False - for repo in repodatas: - repo_pkgs = repo.get("packages", {}) - repo_pkgs.update(repo.get("packages.conda", {})) - for _, pkg in repo_pkgs.items(): - if ( - pkg["name"] == name - and pkg["version"] == version - and pkg["build_number"] == bnumber - and built_for_distro(pkg, vinca_conf["ros_distro"]) - ): - skip = True - print(f"{name}=={version}=={bnumber} already exists. Skipping.") - break + for pkg in records: + if ( + pkg["name"] == name + and pkg["version"] == version + and pkg["build_number"] == bnumber + and built_for_distro(pkg, vinca_conf["ros_distro"]) + ): + skip = True + print(f"{name}=={version}=={bnumber} already exists. Skipping.") + break if not skip: print("Adding ", os.path.dirname(recipe_path)) diff --git a/vinca/main.py b/vinca/main.py index 9bf0f34..6b76ea6 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -14,7 +14,7 @@ import ruamel.yaml from vinca import __version__, config -from vinca.utils import built_for_distro, get_pkg_build_number, get_repodata +from vinca.utils import built_for_distro, get_channel_records, get_pkg_build_number from .configuration import read_snapshot as read_snapshot from .configuration import read_vinca_yaml as load_vinca_yaml @@ -751,19 +751,17 @@ def main() -> None: selected_bn = None print(f"Fetching repodata: {fn}") - repodata = get_repodata(fn, get_conda_subdir()) + records = get_channel_records(fn, get_conda_subdir()) if arguments.multiple_file: # compared by exact build string once the recipes are written - published |= published_builds([repodata], vinca_conf["ros_distro"]) + published |= published_builds(records, vinca_conf["ros_distro"]) continue # currently we don't check the build numbers of local repodatas, # only URLs if "://" in fn: selected_bn = vinca_conf.get("build_number", 0) - all_pkgs = repodata.get("packages", {}) - all_pkgs.update(repodata.get("packages.conda", {})) - for _, pkg in all_pkgs.items(): + for pkg in records: is_built = False if not built_for_distro(pkg, vinca_conf["ros_distro"]): # e.g. a channel shared by several distributions diff --git a/vinca/migrate.py b/vinca/migrate.py index d6a0940..ebc51c9 100644 --- a/vinca/migrate.py +++ b/vinca/migrate.py @@ -14,7 +14,7 @@ from vinca.distro import Distro from .naming import PackageNameMode, get_package_name_mode, get_package_prefix -from .utils import get_repodata +from .utils import get_channel_records distro_version = None ros_prefix = None @@ -53,9 +53,7 @@ def create_migration_instructions(arch, packages_to_migrate, trigger_branch) -> legacy_prefix = distro.get_legacy_package_prefix() package_name_mode = get_package_name_mode(vinca_conf) - repodata = get_repodata(url, arch) - - packages = repodata["packages"] + packages = {record["fn"]: record for record in get_channel_records(url, arch)} to_migrate = set() ros_pkgs = set() for pkey in packages: diff --git a/vinca/pipeline.py b/vinca/pipeline.py index 4820721..b209cde 100644 --- a/vinca/pipeline.py +++ b/vinca/pipeline.py @@ -17,7 +17,7 @@ from ruamel.yaml import YAML -from vinca.utils import get_repodata +from vinca.utils import get_channel_records # a stage this short is one job, shared with the short stages next to it SHARED_JOB_SECONDS = 120.0 @@ -251,13 +251,13 @@ def more(index: int) -> tuple[float, list[list[str]]] | None: def get_skip_existing(vinca_conf: Mapping[str, Any], platform: str) -> list[dict]: - """Fetch repodata files configured for skip-existing checks.""" + """Fetch the package records of the channels configured for skip-existing checks.""" repositories = vinca_conf.get("skip_existing") or [] - repodatas = [] + records = [] for repository in repositories: print(f"Fetching repodata: {repository}") - repodatas.append(get_repodata(repository, platform)) - return repodatas + records += get_channel_records(repository, platform) + return records def get_all_ancestors(graph: Mapping[str, Iterable[str]], node: str) -> set[str]: diff --git a/vinca/skip_existing.py b/vinca/skip_existing.py index 8e5d0a8..cf897a7 100644 --- a/vinca/skip_existing.py +++ b/vinca/skip_existing.py @@ -23,17 +23,13 @@ Build = tuple[str, str, str] -def published_builds( - repodatas: Iterable[Mapping[str, Any]], ros_distro: str -) -> set[Build]: +def published_builds(records: Iterable[dict[str, Any]], ros_distro: str) -> set[Build]: """(name, version, build string) of every record built for *ros_distro*.""" - builds = set() - for repodata in repodatas: - records = {**repodata.get("packages", {}), **repodata.get("packages.conda", {})} - for record in records.values(): - if built_for_distro(record, ros_distro): - builds.add((record["name"], record["version"], record["build"])) - return builds + return { + (record["name"], record["version"], record["build"]) + for record in records + if built_for_distro(record, ros_distro) + } def render_outputs( diff --git a/vinca/test_skip_existing_builds.py b/vinca/test_skip_existing_builds.py index 8780d7d..07f3ad1 100644 --- a/vinca/test_skip_existing_builds.py +++ b/vinca/test_skip_existing_builds.py @@ -23,27 +23,21 @@ def _record(name, version, build, mutex=LYRICAL_MUTEX): def test_published_builds_are_name_version_and_build_string(): - repodata = { - "packages": {"a.tar.bz2": _record("ros2-rcl", "10.2.0", "hb2aaf94_30")}, - "packages.conda": { - "b.conda": _record("ros2-rclpy", "10.0.1", "np2py314h40712d6_30") - }, - } + records = [ + _record("ros2-rcl", "10.2.0", "hb2aaf94_30"), + _record("ros2-rclpy", "10.0.1", "np2py314h40712d6_30"), + ] - assert published_builds([repodata], "lyrical") == { + assert published_builds(records, "lyrical") == { ("ros2-rcl", "10.2.0", "hb2aaf94_30"), ("ros2-rclpy", "10.0.1", "np2py314h40712d6_30"), } def test_published_builds_leave_out_other_distributions(): - repodata = { - "packages.conda": { - "a.conda": _record("ros2-rcl", "10.2.0", "h1_30", ROLLING_MUTEX) - } - } + records = [_record("ros2-rcl", "10.2.0", "h1_30", ROLLING_MUTEX)] - assert published_builds([repodata], "lyrical") == set() + assert published_builds(records, "lyrical") == set() def test_recipe_with_every_output_published_is_built(): diff --git a/vinca/test_utils.py b/vinca/test_utils.py index 7dc01c3..2cf2900 100644 --- a/vinca/test_utils.py +++ b/vinca/test_utils.py @@ -1,20 +1,23 @@ +import functools import json -from unittest.mock import Mock, patch +import socket +import threading +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer import pytest +from rattler.exceptions import FetchRepoDataError +from vinca import utils from vinca.utils import ( CyclicTestRequirement, add_test_requirements, build_requirement_graph, extract_dependency_names, + get_channel_records, get_pkg_additional_info, get_pkg_build_number, - get_repodata, ) -EMPTY_REPODATA = {"packages": {}, "packages.conda": {}} - def test_extract_dependency_names_handles_conditional_requirements(): requirements = [ @@ -86,42 +89,90 @@ def test_build_requirement_graph_rejects_cyclic_test_requirements(): assert "ros2-b" in str(excinfo.value) -def test_get_repodata_returns_empty_for_missing_local_repodata(tmp_path): - assert get_repodata(str(tmp_path), "osx-arm64") == EMPTY_REPODATA +@pytest.fixture +def channel(tmp_path, monkeypatch): + monkeypatch.setattr(utils, "REPODATA_CACHE", tmp_path / "cache") + return tmp_path / "channel" -def test_get_repodata_returns_empty_for_missing_remote_repodata(tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - response = Mock(status_code=404, content=b"Not Found") +@pytest.fixture +def served_channel(channel): + channel.mkdir() + handler = functools.partial(SimpleHTTPRequestHandler, directory=channel) + server = ThreadingHTTPServer(("127.0.0.1", 0), handler) + threading.Thread(target=server.serve_forever, daemon=True).start() + yield channel, f"http://127.0.0.1:{server.server_port}/" + server.shutdown() + server.server_close() - with patch("vinca.utils.requests.get", return_value=response): - assert ( - get_repodata("https://example.com/channel", "osx-arm64") == EMPTY_REPODATA - ) +def _write_repodata(channel, subdir, content): + (channel / subdir).mkdir(parents=True) + (channel / subdir / "repodata.json").write_text(content) -def test_get_repodata_returns_empty_for_non_json_remote_repodata(tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - response = Mock(status_code=200, content=b"Not Found") - response.raise_for_status.return_value = None - with patch("vinca.utils.requests.get", return_value=response): - assert ( - get_repodata("https://example.com/channel", "osx-arm64") == EMPTY_REPODATA - ) +def _repodata(*records): + return json.dumps( + { + "packages": {}, + "packages.conda": { + f"{r['name']}-{r['version']}-{r['build']}.conda": r for r in records + }, + } + ) + + +FOO = {"name": "ros2-foo", "version": "1.0", "build": "h1_31", "build_number": 31} + + +def _names_and_builds(records): + return [(r["name"], r["version"], r["build"], r["build_number"]) for r in records] + + +def test_get_channel_records_of_a_local_channel(channel): + _write_repodata(channel, "linux-64", _repodata(FOO)) + + for location in (str(channel), channel.as_uri()): + records = get_channel_records(location, "linux-64") + assert _names_and_builds(records) == [("ros2-foo", "1.0", "h1_31", 31)] + assert records[0]["fn"] == "ros2-foo-1.0-h1_31.conda" + + +def test_get_channel_records_of_a_remote_channel(served_channel): + channel, url = served_channel + _write_repodata(channel, "linux-64", _repodata(FOO)) + + records = get_channel_records(url, "linux-64") + assert _names_and_builds(records) == [("ros2-foo", "1.0", "h1_31", 31)] + + +def test_get_channel_records_returns_no_records_for_missing_local_repodata(channel): + assert get_channel_records(str(channel), "osx-arm64") == [] + + +def test_get_channel_records_returns_no_records_for_missing_remote_repodata( + served_channel, +): + _, url = served_channel + assert get_channel_records(url, "osx-arm64") == [] + + +@pytest.mark.parametrize("content", ["Not Found", ""]) +def test_get_channel_records_returns_no_records_for_invalid_remote_repodata( + served_channel, content +): + channel, url = served_channel + _write_repodata(channel, "osx-arm64", content) + assert get_channel_records(url, "osx-arm64") == [] -def test_get_repodata_ignores_invalid_cached_repodata(tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - url = "https://example.com/channel/osx-arm64/repodata.json" - cache_name = "vinca_d7e1ca2423.json" - (tmp_path / cache_name).write_text("Not Found") - repodata = {"packages": {"pkg.tar.bz2": {"name": "pkg"}}, "packages.conda": {}} - response = Mock(status_code=200, content=json.dumps(repodata).encode("utf-8")) - response.raise_for_status.return_value = None +def test_get_channel_records_raises_when_the_channel_is_unreachable(channel): + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] - with patch("vinca.utils.requests.get", return_value=response): - assert get_repodata(url) == repodata + with pytest.raises(FetchRepoDataError): + get_channel_records(f"http://127.0.0.1:{port}/", "linux-64") ADDITIONAL_INFO_CONF = { diff --git a/vinca/utils.py b/vinca/utils.py index 3a954d2..3238bf7 100644 --- a/vinca/utils.py +++ b/vinca/utils.py @@ -1,12 +1,16 @@ -import hashlib +import asyncio import json -import os -import time +import tempfile +from pathlib import Path +from urllib.parse import urlsplit import networkx as nx -import requests +import rattler +from rattler.exceptions import FetchRepoDataError from ruamel.yaml.representer import SafeRepresenter +REPODATA_CACHE = Path(tempfile.gettempdir()) / "vinca-repodata" + class folded_unicode(str): pass @@ -37,56 +41,42 @@ def ignore_aliases(self, data) -> bool: NoAliasRepresenter.add_representer(literal_unicode, literal_unicode_representer) -def get_repodata(url_or_path, platform=None): - if platform: - if not url_or_path.endswith("/"): - url_or_path += "/" - url_or_path += f"{platform}/repodata.json" - - if "://" not in url_or_path: - if not os.path.exists(url_or_path): - print(f"No repodata found at {url_or_path}, assuming no existing packages") - return {"packages": {}, "packages.conda": {}} - with open(url_or_path, encoding="utf-8") as fi: - return json.load(fi) - print("Downloading repodata from ", url_or_path) - - m = hashlib.md5(url_or_path.encode("utf-8")).hexdigest()[:10] - # print(tempfile.gettempdir()) - fn = f"vinca_{m}.json" - if os.path.exists(fn): - st = os.stat(fn) - age = time.time() - st.st_mtime - print(f"Found cached repodata, age: {age}") - max_age = 100_000 # seconds == 27 hours - if age < max_age: - try: - with open(fn, encoding="utf-8") as fi: - return json.load(fi) - except json.JSONDecodeError: - print(f"Ignoring invalid cached repodata at {fn}") - os.remove(fn) - - repodata = requests.get(url_or_path) - if repodata.status_code == 404: - print(f"No repodata found at {url_or_path}, assuming no existing packages") - return {"packages": {}, "packages.conda": {}} - repodata.raise_for_status() - content = repodata.content - if not content.strip(): - print(f"No repodata found at {url_or_path}, assuming no existing packages") - return {"packages": {}, "packages.conda": {}} +def _channel(location): + # rattler reads a bare name as an anaconda.org channel; a single-letter scheme + # is a Windows drive + if len(urlsplit(location).scheme) > 1: + return rattler.Channel(location) + return rattler.Channel(str(Path(location).resolve())) + + +def get_channel_records(channel, platform): + """The package records of *channel*, a URL or local path, for *platform*, as + repodata.json records; none if the channel has no valid repodata for it.""" try: - parsed_repodata = json.loads(content) - except json.JSONDecodeError: + [repodata] = asyncio.run( + rattler.fetch_repo_data( + channels=[_channel(channel)], + platforms=[rattler.Subdir(platform)], + cache_path=REPODATA_CACHE, + callback=None, + ) + ) + records = repodata.load_all_records() + except FetchRepoDataError as exc: + # network failures raise the same type + if not str(exc).startswith("repodata not found"): + raise print( - f"No valid repodata found at {url_or_path}, assuming no existing packages" + f"No {platform} repodata found at {channel}, assuming no existing packages" ) - return {"packages": {}, "packages.conda": {}} - - with open(fn, "w", encoding="utf-8") as fcache: - fcache.write(content.decode("utf-8")) - return parsed_repodata + return [] + except OSError: + print( + f"No valid {platform} repodata found at {channel}, " + "assuming no existing packages" + ) + return [] + return [json.loads(record.to_json()) for record in records] def ensure_name_is_without_distro_prefix_and_with_underscores(name, vinca_conf): From 990cc67f74dab90213686e4faddc8ea568a5da1c Mon Sep 17 00:00:00 2001 From: Ezra Brooks Date: Mon, 9 Nov 2026 14:28:48 -0700 Subject: [PATCH 2/3] Fetch the conda-forge pinning package with py-rattler The newest conda-forge-pinning was found by downloading and decompressing conda-forge's noarch current_repodata.json.zst, an exact version through anaconda.org's release API, and the artifact was unpacked by hand (.conda: zip of tar.zst; .tar.bz2). A rattler.Gateway query for the package reads only its shard of conda-forge's repodata, and rattler.package_streaming.download_and_extract unpacks either format, so vinca no longer depends on zstandard. --- pixi.lock | 233 ++++++++---------------------------------- pyproject.toml | 1 - vinca/pinning.py | 158 +++++++--------------------- vinca/test_pinning.py | 42 +++++--- 4 files changed, 108 insertions(+), 326 deletions(-) diff --git a/pixi.lock b/pixi.lock index 5de9c00..c22f620 100644 --- a/pixi.lock +++ b/pixi.lock @@ -30,7 +30,6 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py314hcd2bdb6_3.conda - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://prefix.dev/conda-forge/linux-64/cffi-2.1.1-py314h4a8dc5f_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda @@ -56,7 +55,6 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ruff-0.14.2-ha3a3aed_0.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda - - conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.25.0-py314hfe1a184_3.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -83,7 +81,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda @@ -102,7 +99,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda_source: vinca[239d55bf] @ . + - conda_source: vinca[71f43a44] @ . osx-64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -129,7 +126,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda @@ -150,7 +146,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.2.0-py314h21a1a37_3.conda - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://prefix.dev/conda-forge/osx-64/cffi-2.1.1-py314hb60be56_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda @@ -171,9 +166,8 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/ruff-0.14.2-hba89d1c_0.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-had63097_3.conda - - conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.25.0-py314h17af80e_3.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - - conda_source: vinca[5eece575] @ . + - conda_source: vinca[a87fd365] @ . osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -200,7 +194,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda @@ -221,7 +214,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py314hee34562_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/cffi-2.1.1-py314h7bede21_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda @@ -243,9 +235,8 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.14.2-h492a034_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.25.0-py314hd98292b_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - - conda_source: vinca[5d4b4b66] @ . + - conda_source: vinca[ab136790] @ . win-64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -272,7 +263,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda @@ -294,7 +284,6 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py314h85cf176_3.conda - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://prefix.dev/conda-forge/win-64/cffi-2.1.1-py314h5a2d7ad_2.conda - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda - conda: https://prefix.dev/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda @@ -316,9 +305,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_3.conda - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - - conda_source: vinca[67615039] @ . + - conda_source: vinca[93445889] @ . packages: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -397,21 +385,6 @@ packages: purls: [] size: 260341 timestamp: 1757437258798 -- conda: https://prefix.dev/conda-forge/linux-64/cffi-2.1.1-py314h4a8dc5f_0.conda - sha256: eec96c89f9f445da65cfc315e9c8572968335e127a18f17104bc9a90df103058 - md5: f10f311ad713701678c05ecc19c22784 - 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 - run_exports: {} - size: 306515 - timestamp: 1785810913099 - conda: https://prefix.dev/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda sha256: 9f07834f0c546ab14d885ce0366285f61f44e326c0edd1fc63b8294e113ae432 md5: 72a381cbad04f24b1c2a43ef707f45b4 @@ -1023,21 +996,6 @@ packages: - yaml >=0.2.5,<0.3.0a0 size: 84936 timestamp: 1787228426393 -- conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.25.0-py314hfe1a184_3.conda - sha256: 8c73ee662ea1446a322b1f0e8ddbb3cd631b9e6abf9b2dbe70662fe73b0c196b - md5: 7fe9ffe3e04a39dc12669cc0e4f23f05 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=15 - - python_abi 3.14.* *_cp314 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - run_exports: {} - size: 472944 - timestamp: 1787896524818 - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda sha256: 47d682b9f6d6ec9eb1a6e6c3e75ea6273e899e78fb7fc59f81d39745009fbc60 md5: aa459086047c0e5e27023ab19f8cb86a @@ -1427,17 +1385,6 @@ packages: run_exports: {} size: 25877 timestamp: 1764896838868 -- conda: https://prefix.dev/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 - run_exports: {} - size: 55886 - timestamp: 1779293633166 - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a md5: 6b6ece66ebcae2d5f326c77ef2c5a066 @@ -1759,20 +1706,6 @@ packages: purls: [] size: 132607 timestamp: 1757437730085 -- conda: https://prefix.dev/conda-forge/osx-64/cffi-2.1.1-py314hb60be56_0.conda - sha256: 4adecbfb8212fe44caaa135099b70dc00adbe14658d75f235c317d76b5944974 - md5: 92c2088c1c4b021d0f62cfe59b63465f - depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pycparser - - python >=3.14,<3.15.0a0 - - python_abi 3.14.* *_cp314 - license: MIT - license_family: MIT - run_exports: {} - size: 299378 - timestamp: 1785811225267 - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda sha256: da5b60cbdaf8d931e2534f3b17a0d9b5432ebc855509d36618d61a61f1f2909d md5: 925382e3a8a530f862be5be3b3aaf68b @@ -2198,20 +2131,6 @@ packages: - yaml >=0.2.5,<0.3.0a0 size: 75645 timestamp: 1787228502493 -- conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.25.0-py314h17af80e_3.conda - sha256: 3c6296a1456a386e00d11f54d25cbc9ddabbacfc0aaf4fb124d35399e6cb2acb - md5: 1b902b65bc2c87ab337e3d35d0a78097 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - python_abi 3.14.* *_cp314 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - run_exports: {} - size: 459782 - timestamp: 1787896584507 - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca md5: cd60a4a5a8d6a476b30d8aa4bb49251a @@ -2273,21 +2192,6 @@ packages: purls: [] size: 125061 timestamp: 1757437486465 -- conda: https://prefix.dev/conda-forge/osx-arm64/cffi-2.1.1-py314h7bede21_0.conda - sha256: b6c9358ee7cabfb417e6a8d2758c2241fe6b24f9fd316561a449ccac1959f8e1 - md5: 4648b9514e3cd095788963779cebcc7a - 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 - run_exports: {} - size: 297593 - timestamp: 1785811327918 - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 md5: 5eb22c1d7b3fc4abb50d92d621583137 @@ -2739,20 +2643,6 @@ packages: - yaml >=0.2.5,<0.3.0a0 size: 77530 timestamp: 1787228556857 -- conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.25.0-py314hd98292b_3.conda - sha256: e790bd7c15afa1d59d2d72b86825932338147cd7c832e7e54b8dc29adfb61be5 - md5: a8e20554ed5fbd1f81f1b098bfe03eb5 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.14.* *_cp314 - license: BSD-3-Clause - run_exports: {} - size: 383104 - timestamp: 1787896531255 - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda sha256: 0d02046f57f7a1a3feae3e9d1aa2113788311f3cf37a3244c71e61a93177ba67 md5: e6f69c7bcccdefa417f056fa593b40f0 @@ -2819,21 +2709,6 @@ packages: purls: [] size: 55977 timestamp: 1757437738856 -- conda: https://prefix.dev/conda-forge/win-64/cffi-2.1.1-py314h5a2d7ad_2.conda - sha256: e0e10d676eb67a8a4c8991cec89fb6f150f919eb1266a1b39253a857b3dc0454 - md5: 672d6ff72c6265b25eeef94ce21e71ac - 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 - run_exports: {} - size: 346657 - timestamp: 1786775160153 - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845 md5: 3608ffde260281fa641e70d6e34b1b96 @@ -3331,22 +3206,6 @@ packages: - yaml >=0.2.5,<0.3.0a0 size: 63944 timestamp: 1753484092156 -- conda: https://prefix.dev/conda-forge/win-64/zstandard-0.25.0-py314hc5dbbe4_3.conda - sha256: ba59719687df01c0e846b0308e59d9bbd8215a55532d228d35c14d0ced6bcba4 - md5: 099ca4a359dd7b5cf5140758a433cd58 - 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 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.14.* *_cp314 - license: BSD-3-Clause - run_exports: {} - size: 380551 - timestamp: 1787896524598 - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda sha256: ca7daae4f218a11fab82cc2857f0ea518ec3f46acec60490485347a4c22c6b3e md5: e4ac308c39d6d0e131154976da67cf3b @@ -3375,7 +3234,7 @@ packages: purls: [] size: 354697 timestamp: 1742433568506 -- conda_source: vinca[239d55bf] @ . +- conda_source: vinca[71f43a44] @ . variants: target_platform: noarch depends: @@ -3391,7 +3250,6 @@ packages: - jinja2 >=3.0.0 - license-expression >=30.0.0 - packaging >=23.0 - - zstandard >=0.19.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: @@ -3428,7 +3286,7 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda -- conda_source: vinca[5d4b4b66] @ . +- conda_source: vinca[93445889] @ . variants: target_platform: noarch depends: @@ -3444,11 +3302,10 @@ packages: - jinja2 >=3.0.0 - license-expression >=30.0.0 - packaging >=23.0 - - zstandard >=0.19.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3459,24 +3316,24 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda -- conda_source: vinca[5eece575] @ . + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda +- conda_source: vinca[a87fd365] @ . variants: target_platform: noarch depends: @@ -3492,7 +3349,6 @@ packages: - jinja2 >=3.0.0 - license-expression >=30.0.0 - packaging >=23.0 - - zstandard >=0.19.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: @@ -3523,7 +3379,7 @@ packages: - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://prefix.dev/conda-forge/osx-64/uv-0.12.7-h6c1caad_0.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda -- conda_source: vinca[67615039] @ . +- conda_source: vinca[ab136790] @ . variants: target_platform: noarch depends: @@ -3539,11 +3395,10 @@ packages: - jinja2 >=3.0.0 - license-expression >=30.0.0 - packaging >=23.0 - - zstandard >=0.19.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3554,20 +3409,20 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda - - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda - - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda - - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda - - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda - - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda diff --git a/pyproject.toml b/pyproject.toml index 934710a..4026644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ dependencies = [ "jinja2 >=3.0.0", "license-expression >=30.0.0", "packaging >=23.0", - "zstandard >=0.19.0", "py-rattler >=0.28.1,<0.29", ] diff --git a/vinca/pinning.py b/vinca/pinning.py index 9d6506b..8ab8580 100644 --- a/vinca/pinning.py +++ b/vinca/pinning.py @@ -9,33 +9,28 @@ from __future__ import annotations import argparse +import asyncio import io -import json import os import re import sys -import tarfile -import zipfile +import tempfile from concurrent.futures import ThreadPoolExecutor, as_completed from contextlib import contextmanager from pathlib import Path -from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, Union, cast +from typing import Any, Iterator, Mapping, Optional, Sequence, Union, cast from urllib.parse import quote +import rattler import requests import ruamel.yaml -import zstandard +from rattler.exceptions import ExtractError, GatewayError +from rattler.package_streaming import download_and_extract from vinca.v1_selectors import _eval_condition, _platform_flags from vinca.variant_algebra import variant_add PINNING_PACKAGE = "conda-forge-pinning" -ANACONDA_RELEASE_URL = ( - "https://api.anaconda.org/release/conda-forge/conda-forge-pinning/{version}" -) -CURRENT_REPODATA_URL = ( - "https://conda.anaconda.org/conda-forge/noarch/current_repodata.json.zst" -) MIGRATION_STATUS_URL = ( "https://raw.githubusercontent.com/regro/cf-graph-countyfair/master/" "status/migration_json/{migration}.json" @@ -66,129 +61,54 @@ def _yaml() -> Any: return yaml -def _request(url: str, *, timeout: int = 120) -> Any: +def _newest_pinning_record(spec: str) -> Any: + """The newest noarch conda-forge record matching *spec*.""" try: - response = requests.get(url, timeout=timeout) - response.raise_for_status() - except requests.RequestException as exc: - raise PinningError(f"Could not download {url}: {exc}") from exc - return response - - -def _load_zstd_json(payload: bytes) -> Any: - try: - decompressed = zstandard.ZstdDecompressor().decompress(payload) - except zstandard.ZstdError as exc: - raise PinningError("Could not decompress conda-forge repodata") from exc - return json.loads(decompressed) + [records] = asyncio.run( + rattler.Gateway().query( + [rattler.Channel("conda-forge")], ["noarch"], [spec], recursive=False + ) + ) + except GatewayError as exc: + raise PinningError(f"Could not query conda-forge for {spec}: {exc}") from exc + if not records: + raise PinningError(f"No {spec} package found on conda-forge") + return max(records, key=lambda record: (record.version, record.build_number)) def get_latest_pinning_distribution() -> tuple[str, str]: """Return the newest conda-forge-pinning version and artifact URL.""" - repodata = _load_zstd_json(_request(CURRENT_REPODATA_URL).content) - records = [] - for section in ("packages", "packages.conda"): - for filename, record in repodata.get(section, {}).items(): - if record.get("name") == PINNING_PACKAGE: - records.append((record.get("timestamp", 0), filename, record)) - if not records: - raise PinningError( - f"No {PINNING_PACKAGE} package found in conda-forge current repodata" - ) - _, filename, record = max(records) - url = f"https://conda.anaconda.org/conda-forge/noarch/{quote(filename)}" - return str(record["version"]), url + record = _newest_pinning_record(PINNING_PACKAGE) + return str(record.version), record.url def get_pinning_distribution(version: str) -> str: """Return the artifact URL for an exact conda-forge-pinning version.""" - release = _request(ANACONDA_RELEASE_URL.format(version=quote(str(version)))).json() - distributions = [ - item - for item in release.get("distributions", []) - if item.get("basename", "").startswith("noarch/") - and item.get("basename", "").endswith((".conda", ".tar.bz2")) - ] - if not distributions: - raise PinningError(f"No noarch artifact found for {PINNING_PACKAGE} {version}") - distribution = max( - distributions, - key=lambda item: ( - item.get("attrs", {}).get("build_number", 0), - item.get("attrs", {}).get("timestamp", 0), - ), - ) - url = distribution.get("download_url") - if not url: - basename = distribution["basename"].split("/", 1)[1] - url = "https://conda.anaconda.org/conda-forge/noarch/" + quote(basename) - elif url.startswith("//"): - url = "https:" + url - return url - - -def _read_tar_members( - fileobj: Any, *, mode: Literal["r|", "r:bz2"] -) -> dict[str, bytes]: - """Extract only the base config and migration YAML files from a package tarball.""" - files = {} - with tarfile.open(fileobj=fileobj, mode=mode) as archive: - for member in archive: - if not member.isfile(): - continue - name = member.name.lstrip("./") - if name == "conda_build_config.yaml" or ( - name.startswith("share/conda-forge/migrations/") - and name.endswith(".yaml") - ): - extracted = archive.extractfile(member) - if extracted is not None: - files[name] = extracted.read() - return files - - -def _read_pinning_package(payload: bytes) -> tuple[bytes, dict[str, bytes]]: - """Parse supported pinning artifacts into base-config bytes and named migrations. - - Raises ``PinningError`` when the artifact lacks the required package members. - """ - stream = io.BytesIO(payload) - if zipfile.is_zipfile(stream): - with zipfile.ZipFile(stream) as archive: - package_members = [ - name - for name in archive.namelist() - if name.startswith("pkg-") and name.endswith(".tar.zst") - ] - if len(package_members) != 1: - raise PinningError("Invalid .conda artifact: package archive not found") - compressed = archive.read(package_members[0]) - reader = zstandard.ZstdDecompressor().stream_reader(io.BytesIO(compressed)) - with reader: - files = _read_tar_members(reader, mode="r|") - else: - try: - files = _read_tar_members(io.BytesIO(payload), mode="r:bz2") - except tarfile.TarError as exc: - raise PinningError("Unsupported conda-forge-pinning artifact") from exc - - base = files.pop("conda_build_config.yaml", None) - if base is None: - raise PinningError("conda_build_config.yaml is missing from pinning artifact") - migrations = { - Path(name).stem: content - for name, content in files.items() - if Path(name).suffix == ".yaml" - } - return base, migrations + return _newest_pinning_record(f"{PINNING_PACKAGE}=={version}").url def download_pinning_package( version: str, *, artifact_url: Optional[str] = None ) -> tuple[bytes, dict[str, bytes]]: - """Download and parse a versioned pinning artifact, or the supplied artifact URL.""" + """Download a versioned pinning artifact, or the supplied artifact URL, into its + base config and named migrations.""" url = artifact_url or get_pinning_distribution(version) - return _read_pinning_package(_request(url).content) + with tempfile.TemporaryDirectory() as tmp: + dest = Path(tmp) + try: + asyncio.run(download_and_extract(rattler.Client(), url, dest)) + except (ExtractError, OSError) as exc: + raise PinningError(f"Could not download {url}: {exc}") from exc + base = dest / "conda_build_config.yaml" + if not base.is_file(): + raise PinningError( + "conda_build_config.yaml is missing from pinning artifact" + ) + migrations = { + path.stem: path.read_bytes() + for path in (dest / "share/conda-forge/migrations").glob("*.yaml") + } + return base.read_bytes(), migrations def _pinning_spec_fields( diff --git a/vinca/test_pinning.py b/vinca/test_pinning.py index aad52f6..cfc3f08 100644 --- a/vinca/test_pinning.py +++ b/vinca/test_pinning.py @@ -1,19 +1,17 @@ import io import re import tarfile -import zipfile from unittest.mock import patch import pytest import ruamel.yaml -import zstandard from vinca.pinning import ( PinningError, _migration_applies_to_platforms, - _read_pinning_package, dependencies_from_recipes, dependencies_from_vinca, + download_pinning_package, render_pinning, select_completed_migrations, update_pinning, @@ -38,37 +36,47 @@ """ -def _conda_package(files): - tar_payload = io.BytesIO() - with tarfile.open(fileobj=tar_payload, mode="w") as archive: +def _pinning_package(path, files): + with tarfile.open(path, mode="w:bz2") as archive: for name, content in files.items(): info = tarfile.TarInfo(name) info.size = len(content) archive.addfile(info, io.BytesIO(content)) - compressed = zstandard.ZstdCompressor().compress(tar_payload.getvalue()) - conda_payload = io.BytesIO() - with zipfile.ZipFile(conda_payload, mode="w") as archive: - archive.writestr("metadata.json", '{"conda_pkg_format_version": 2}') - archive.writestr("pkg-test.tar.zst", compressed) - archive.writestr("info-test.tar.zst", b"") - return conda_payload.getvalue() + return path.as_uri() -def test_read_pinning_conda_package(): - payload = _conda_package( +def test_download_pinning_package(tmp_path): + url = _pinning_package( + tmp_path / "conda-forge-pinning-1-0.tar.bz2", { "conda_build_config.yaml": BASE_CONFIG, "share/conda-forge/migrations/libboost190.yaml": BOOST_MIGRATION, "ignored.txt": b"not part of the pinning inputs", - } + }, ) - base, migrations = _read_pinning_package(payload) + base, migrations = download_pinning_package("1", artifact_url=url) assert base == BASE_CONFIG assert migrations == {"libboost190": BOOST_MIGRATION} +def test_download_pinning_package_without_a_base_config(tmp_path): + url = _pinning_package( + tmp_path / "conda-forge-pinning-1-0.tar.bz2", {"ignored.txt": b""} + ) + + with pytest.raises(PinningError, match="conda_build_config.yaml is missing"): + download_pinning_package("1", artifact_url=url) + + +def test_download_pinning_package_that_does_not_exist(tmp_path): + url = (tmp_path / "conda-forge-pinning-1-0.tar.bz2").as_uri() + + with pytest.raises(PinningError, match="Could not download"): + download_pinning_package("1", artifact_url=url) + + def test_render_applies_base_migration_then_override(tmp_path): config_path = tmp_path / "vinca_pinning.yaml" config_path.write_text( From e8689738666fb10514e6c0f1dd1bceed74ecd896 Mon Sep 17 00:00:00 2001 From: Ezra Brooks Date: Mon, 9 Nov 2026 14:30:43 -0700 Subject: [PATCH 3/3] Order versions as conda does, with rattler.Version The CFEP-9 variant algebra, adapted from conda-smithy, compares pins with conda's VersionOrder upstream but with PEP 440's packaging.Version here. PEP 440 can't parse many pins, which then compare as strings: "3.9.* *_cpython" sorted after "3.10.* *_cpython". vinca-migrate parsed versions by hand instead. rattler.Version orders both as conda does, and vinca no longer depends on packaging. --- pixi.lock | 168 +++++++++++++++++++-------------------- pyproject.toml | 1 - vinca/migrate.py | 16 +--- vinca/test_pinning.py | 11 +++ vinca/variant_algebra.py | 5 +- 5 files changed, 99 insertions(+), 102 deletions(-) diff --git a/pixi.lock b/pixi.lock index c22f620..d712690 100644 --- a/pixi.lock +++ b/pixi.lock @@ -99,7 +99,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - - conda_source: vinca[71f43a44] @ . + - conda_source: vinca[6a5a7c42] @ . osx-64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -167,7 +167,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-had63097_3.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - - conda_source: vinca[a87fd365] @ . + - conda_source: vinca[67e1c53a] @ . osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -236,7 +236,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - - conda_source: vinca[ab136790] @ . + - conda_source: vinca[3f434182] @ . win-64: - conda: https://prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda @@ -306,7 +306,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - - conda_source: vinca[93445889] @ . + - conda_source: vinca[a943a887] @ . packages: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -3234,7 +3234,7 @@ packages: purls: [] size: 354697 timestamp: 1742433568506 -- conda_source: vinca[71f43a44] @ . +- conda_source: vinca[3f434182] @ . variants: target_platform: noarch depends: @@ -3249,32 +3249,9 @@ packages: - rich >=10 - jinja2 >=3.0.0 - license-expression >=30.0.0 - - packaging >=23.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda - - conda: https://prefix.dev/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda - - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/libpython-3.14.7-hdc7f604_104_cp314.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.7-hcd007b5_104_cp314.conda - - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/uv-0.12.8-h86a270d_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda @@ -3286,7 +3263,24 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda -- conda_source: vinca[93445889] @ . + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda +- conda_source: vinca[67e1c53a] @ . variants: target_platform: noarch depends: @@ -3301,11 +3295,10 @@ packages: - rich >=10 - jinja2 >=3.0.0 - license-expression >=30.0.0 - - packaging >=23.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3316,24 +3309,23 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda - - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda - - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda - - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda - - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda - - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda - - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda -- conda_source: vinca[a87fd365] @ . + - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda + - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.8.1-hcc62823_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.7.0-h6b3a05b_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-ha1e9b39_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/libpython-3.14.7-hfe304d0_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.53.4-ha5db789_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.6.4-h332eb6d_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.7-hafa0b4b_104_cp314.conda + - conda: https://prefix.dev/conda-forge/osx-64/readline-8.3-h000c2f7_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda + - conda: https://prefix.dev/conda-forge/osx-64/uv-0.12.7-h6c1caad_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda +- conda_source: vinca[6a5a7c42] @ . variants: target_platform: noarch depends: @@ -3348,10 +3340,31 @@ packages: - rich >=10 - jinja2 >=3.0.0 - license-expression >=30.0.0 - - packaging >=23.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://prefix.dev/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libpython-3.14.7-hdc7f604_104_cp314.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.7-hcd007b5_104_cp314.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/uv-0.12.8-h86a270d_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda @@ -3363,23 +3376,7 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda - - conda: https://prefix.dev/conda-forge/osx-64/libcxx-23.1.0-h19cb2f5_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.8.1-hcc62823_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.7.0-h6b3a05b_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-ha1e9b39_2.conda - - conda: https://prefix.dev/conda-forge/osx-64/libpython-3.14.7-hfe304d0_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.53.4-ha5db789_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda - - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.6.4-h332eb6d_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.7-hafa0b4b_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-64/readline-8.3-h000c2f7_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - - conda: https://prefix.dev/conda-forge/osx-64/uv-0.12.7-h6c1caad_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda -- conda_source: vinca[ab136790] @ . +- conda_source: vinca[a943a887] @ . variants: target_platform: noarch depends: @@ -3394,11 +3391,10 @@ packages: - rich >=10 - jinja2 >=3.0.0 - license-expression >=30.0.0 - - packaging >=23.0 - py-rattler >=0.28.1,<0.29 license: MIT host_packages: - - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda - conda: https://prefix.dev/conda-forge/noarch/editables-0.6-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/hatchling-1.32.0-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda @@ -3409,20 +3405,20 @@ packages: - conda: https://prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2026.6.1.19-pyhcf101f3_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-23.1.0-h55c6f16_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libpython-3.14.7-h4311e70_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.7-h0ae1c2c_104_cp314.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/uv-0.12.8-h0e72303_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + - conda: https://prefix.dev/conda-forge/win-64/libpython-3.14.7-h4f90d01_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.14.7-h53f6dd8_104_cp314.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/uv-0.12.8-hc31ddbc_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda diff --git a/pyproject.toml b/pyproject.toml index 4026644..1ac16a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dependencies = [ "rich >=10", "jinja2 >=3.0.0", "license-expression >=30.0.0", - "packaging >=23.0", "py-rattler >=0.28.1,<0.29", ] diff --git a/vinca/migrate.py b/vinca/migrate.py index ebc51c9..7d6cff2 100644 --- a/vinca/migrate.py +++ b/vinca/migrate.py @@ -1,6 +1,5 @@ import argparse import os -import re import shutil import subprocess import sys @@ -9,6 +8,7 @@ import networkx as nx import ruamel.yaml +from rattler import Version from vinca import config from vinca.distro import Distro @@ -77,20 +77,10 @@ def create_migration_instructions(arch, packages_to_migrate, trigger_branch) -> latest = {} for pkg in ros_pkgs: current = None - current_version: tuple[int, ...] | None = None + current_version: Version | None = None for pkey in packages: if packages[pkey]["name"] == pkg: - parts = packages[pkey]["version"].split(".") - version = [] - for element in parts: - if element.isdecimal(): - version.append(int(element)) - else: - match = re.search(r"[^0-9]", element) - if match is not None: - version.append(int(element[: match.start()])) - - parsed_version = tuple(version) + parsed_version = Version(packages[pkey]["version"]) if current_version is None or parsed_version > current_version: current_version = parsed_version diff --git a/vinca/test_pinning.py b/vinca/test_pinning.py index cfc3f08..941ffae 100644 --- a/vinca/test_pinning.py +++ b/vinca/test_pinning.py @@ -267,6 +267,17 @@ def test_cfep9_key_add_updates_zip_group(): assert merged["is_python_min"] == [True, False, False] +def test_cfep9_key_add_orders_values_as_conda_versions(): + migration = { + "__migrator": {"operation": "key_add", "primary_key": "python"}, + "python": ["3.9.* *_cpython"], + } + + merged = variant_add({"python": ["3.10.* *_cpython"]}, migration) + + assert merged["python"] == ["3.9.* *_cpython", "3.10.* *_cpython"] + + def test_cfep9_merges_pin_run_as_build_and_zip_keys(): merged = variant_add( { diff --git a/vinca/variant_algebra.py b/vinca/variant_algebra.py index f7437ea..b02eea9 100644 --- a/vinca/variant_algebra.py +++ b/vinca/variant_algebra.py @@ -12,7 +12,8 @@ from copy import deepcopy from typing import Any, MutableMapping, Optional -from packaging.version import InvalidVersion, Version +from rattler import Version +from rattler.exceptions import InvalidVersionError from ruamel.yaml.comments import CommentedSeq _SELECTOR_RE = re.compile(r"#\s*\[(.+)]") @@ -37,7 +38,7 @@ def _version_order(value: Any, ordering: Any = None) -> tuple[int, Any]: normalized = str(value).replace(" ", ".").replace("*", "1") try: return (1, Version(normalized)) - except InvalidVersion: + except InvalidVersionError: return (0, normalized)