From da983a7e544187fbb4efef0e9468acd36ee2f35a Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Sun, 26 Jul 2026 11:34:40 -0700 Subject: [PATCH 1/3] test(e2e): add pycross tests Port of rules_pycross's e2e suite (build_setuptools, patches_and_hooks, build_pure_python and the distutils probe) to rules_py's uv extension and pep517 rules. (cherry picked from commit 4869b33a259c864285491e116fd0938a9508462c, adapted to keep main's //tools:linux_x86_64 platform) --- e2e/cases/MODULE.bazel | 4 + e2e/cases/pycross-distutils-probe/BUILD.bazel | 18 ++ e2e/cases/pycross-distutils-probe/__test__.py | 105 +++++++ .../pycross-distutils-probe/pyproject.toml | 11 + .../setup.MODULE.bazel | 22 ++ e2e/cases/pycross-distutils-probe/uv.lock | 82 +++++ e2e/cases/pycross-patches/BUILD.bazel | 22 ++ .../patches/setproctitle-postinstall.patch | 13 + .../patches/setproctitle-prebuild.patch | 7 + e2e/cases/pycross-patches/pyproject.toml | 13 + e2e/cases/pycross-patches/setup.MODULE.bazel | 39 +++ .../test_setproctitle_patched.py | 17 ++ e2e/cases/pycross-patches/uv.lock | 104 +++++++ e2e/cases/pycross-pure-python/BUILD.bazel | 104 +++++++ e2e/cases/pycross-pure-python/pyproject.toml | 27 ++ .../pycross-pure-python/setup.MODULE.bazel | 39 +++ .../pycross-pure-python/test_filelock.py | 13 + .../test_pyproject_metadata.py | 12 + e2e/cases/pycross-pure-python/test_tomli.py | 13 + .../test_top_level_packages.py | 55 ++++ e2e/cases/pycross-pure-python/uv.lock | 230 ++++++++++++++ e2e/cases/pycross-setuptools/BUILD.bazel | 109 +++++++ e2e/cases/pycross-setuptools/pyproject.toml | 23 ++ .../require_extension.patch | 18 ++ .../pycross-setuptools/setup.MODULE.bazel | 58 ++++ e2e/cases/pycross-setuptools/test_pyyaml.py | 23 ++ .../pycross-setuptools/test_setproctitle.py | 13 + .../pycross-setuptools/test_zstandard.py | 46 +++ e2e/cases/pycross-setuptools/uv.lock | 286 ++++++++++++++++++ e2e/cases/tools/BUILD.bazel | 11 + e2e/cases/tools/check_wheel_native.py | 92 ++++++ e2e/cases/tools/check_wheel_tags.py | 32 ++ e2e/cases/tools/collect_wheels.bzl | 69 +++++ e2e/cases/tools/collect_wheels.py | 46 +++ 34 files changed, 1776 insertions(+) create mode 100644 e2e/cases/pycross-distutils-probe/BUILD.bazel create mode 100644 e2e/cases/pycross-distutils-probe/__test__.py create mode 100644 e2e/cases/pycross-distutils-probe/pyproject.toml create mode 100644 e2e/cases/pycross-distutils-probe/setup.MODULE.bazel create mode 100644 e2e/cases/pycross-distutils-probe/uv.lock create mode 100644 e2e/cases/pycross-patches/BUILD.bazel create mode 100644 e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch create mode 100644 e2e/cases/pycross-patches/patches/setproctitle-prebuild.patch create mode 100644 e2e/cases/pycross-patches/pyproject.toml create mode 100644 e2e/cases/pycross-patches/setup.MODULE.bazel create mode 100644 e2e/cases/pycross-patches/test_setproctitle_patched.py create mode 100644 e2e/cases/pycross-patches/uv.lock create mode 100644 e2e/cases/pycross-pure-python/BUILD.bazel create mode 100644 e2e/cases/pycross-pure-python/pyproject.toml create mode 100644 e2e/cases/pycross-pure-python/setup.MODULE.bazel create mode 100644 e2e/cases/pycross-pure-python/test_filelock.py create mode 100644 e2e/cases/pycross-pure-python/test_pyproject_metadata.py create mode 100644 e2e/cases/pycross-pure-python/test_tomli.py create mode 100644 e2e/cases/pycross-pure-python/test_top_level_packages.py create mode 100644 e2e/cases/pycross-pure-python/uv.lock create mode 100644 e2e/cases/pycross-setuptools/BUILD.bazel create mode 100644 e2e/cases/pycross-setuptools/pyproject.toml create mode 100644 e2e/cases/pycross-setuptools/require_extension.patch create mode 100644 e2e/cases/pycross-setuptools/setup.MODULE.bazel create mode 100644 e2e/cases/pycross-setuptools/test_pyyaml.py create mode 100644 e2e/cases/pycross-setuptools/test_setproctitle.py create mode 100644 e2e/cases/pycross-setuptools/test_zstandard.py create mode 100644 e2e/cases/pycross-setuptools/uv.lock create mode 100644 e2e/cases/tools/check_wheel_native.py create mode 100644 e2e/cases/tools/check_wheel_tags.py create mode 100644 e2e/cases/tools/collect_wheels.bzl create mode 100644 e2e/cases/tools/collect_wheels.py diff --git a/e2e/cases/MODULE.bazel b/e2e/cases/MODULE.bazel index 12829f48f..61dc887c7 100644 --- a/e2e/cases/MODULE.bazel +++ b/e2e/cases/MODULE.bazel @@ -104,6 +104,10 @@ include("//multi-project-hub:setup.MODULE.bazel") include("//oci:setup.MODULE.bazel") include("//pbs-cc-toolchain:setup.MODULE.bazel") include("//pth-namespace-547:setup.MODULE.bazel") +include("//pycross-distutils-probe:setup.MODULE.bazel") +include("//pycross-patches:setup.MODULE.bazel") +include("//pycross-pure-python:setup.MODULE.bazel") +include("//pycross-setuptools:setup.MODULE.bazel") include("//pytest-anyio-plugin:setup.MODULE.bazel") include("//pytest-main-867:setup.MODULE.bazel") include("//pytest-mock-530:setup.MODULE.bazel") diff --git a/e2e/cases/pycross-distutils-probe/BUILD.bazel b/e2e/cases/pycross-distutils-probe/BUILD.bazel new file mode 100644 index 000000000..99534b307 --- /dev/null +++ b/e2e/cases/pycross-distutils-probe/BUILD.bazel @@ -0,0 +1,18 @@ +# Ported from rules_pycross tests/e2e/build_setuptools's distutils probe. +# See __test__.py for what the probe asserts. + +load("@aspect_rules_py//py:defs.bzl", "py_test") + +py_test( + name = "test", + srcs = ["__test__.py"], + data = ["@aspect_rules_py//uv/private/pep517_whl:build_helper.py"], + dep_group = "pycross_distutils_probe", + main = "__test__.py", + python_version = "3.12", + deps = [ + "@pypi_pycross_distutils_probe//build", + "@pypi_pycross_distutils_probe//setuptools", + "@pypi_pycross_distutils_probe//wheel", + ], +) diff --git a/e2e/cases/pycross-distutils-probe/__test__.py b/e2e/cases/pycross-distutils-probe/__test__.py new file mode 100644 index 000000000..c2c35940e --- /dev/null +++ b/e2e/cases/pycross-distutils-probe/__test__.py @@ -0,0 +1,105 @@ +"""Drive build_helper.py against rules_pycross's distutils probe sdist. + +Ported from rules_pycross tests/e2e/build_setuptools (`distutils_probe/`, +`build_distutils_probe_sdist.py`, `test_distutils_probe.py`). The sdist's +`build_py` command shells out to a *fresh* interpreter and imports +`distutils.util`; on Python 3.12+ that only resolves when setuptools' +`_distutils_hack` is reachable from the child. A build action that exported a +stale `PYTHONPATH`/`PYTHONHOME` — the case build_helper.py's +`_INHERITED_PYTHON_ENV` filter exists for — breaks the child, not the parent, +so the failure is invisible to a plain "does it build" check. + +Structured like `uv-sdist-mpicc`: the sdist is assembled in-process and +build_helper.py is invoked directly, so nothing is fetched from the network. +""" + +import os +import subprocess +import sys +import tarfile +import tempfile +import textwrap + +PYPROJECT = textwrap.dedent( + """\ + [build-system] + requires = ["setuptools", "wheel"] + build-backend = "setuptools.build_meta" + """ +) + +SETUP_PY = textwrap.dedent( + """\ + import subprocess + import sys + + from setuptools import setup + from setuptools.command.build_py import build_py as _build_py + + + class build_py(_build_py): + def run(self): + subprocess.check_call([sys.executable, "-c", "from distutils.util import byte_compile"]) + super().run() + + + setup( + name="distutils_probe", + version="0.1", + packages=["distutils_probe_pkg"], + cmdclass={"build_py": build_py}, + ) + """ +) + + +def find_build_helper() -> str: + srcdir = os.environ["TEST_SRCDIR"] + for root, _, files in os.walk(srcdir, followlinks=True): + if "build_helper.py" in files and root.endswith(os.path.join("uv", "private", "pep517_whl")): + return os.path.join(root, "build_helper.py") + raise AssertionError("build_helper.py not found under TEST_SRCDIR") + + +def make_sdist(workdir: str) -> str: + pkgdir = os.path.join(workdir, "distutils_probe-0.1") + os.makedirs(os.path.join(pkgdir, "distutils_probe_pkg")) + with open(os.path.join(pkgdir, "pyproject.toml"), "w") as f: + f.write(PYPROJECT) + with open(os.path.join(pkgdir, "setup.py"), "w") as f: + f.write(SETUP_PY) + with open(os.path.join(pkgdir, "distutils_probe_pkg", "__init__.py"), "w") as f: + f.write("") + sdist = os.path.join(workdir, "distutils_probe-0.1.tar.gz") + with tarfile.open(sdist, "w:gz") as tar: + tar.add(pkgdir, arcname="distutils_probe-0.1") + return sdist + + +def main() -> None: + helper = find_build_helper() + tmp = tempfile.mkdtemp(dir=os.environ.get("TEST_TMPDIR")) + sdist = make_sdist(tmp) + outdir = os.path.join(tmp, "out") + + result = subprocess.run( + [sys.executable, helper, "--validate-anyarch", sdist, outdir], + capture_output=True, + text=True, + env={ + "PATH": os.pathsep.join(["/usr/bin", "/bin"]), + "HOME": os.environ.get("TEST_TMPDIR", "/tmp"), + }, + ) + if result.returncode != 0: + raise AssertionError( + "build_helper failed:\nstdout:\n{}\nstderr:\n{}".format(result.stdout, result.stderr) + ) + + wheels = [f for f in os.listdir(outdir) if f.endswith(".whl")] + assert wheels, "no wheel produced" + print("OK: {}".format(wheels)) + + +if __name__ == "__main__": + main() diff --git a/e2e/cases/pycross-distutils-probe/pyproject.toml b/e2e/cases/pycross-distutils-probe/pyproject.toml new file mode 100644 index 000000000..2c635b7a9 --- /dev/null +++ b/e2e/cases/pycross-distutils-probe/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "pycross_distutils_probe" +version = "0.0.0" +requires-python = ">=3.11" +dependencies = [ + # The test drives build_helper.py directly, which shells out to + # `python -m build` with setuptools as the backend. + "build", + "setuptools", + "wheel", +] diff --git a/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel b/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel new file mode 100644 index 000000000..5cffad2d5 --- /dev/null +++ b/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel @@ -0,0 +1,22 @@ +"""Port of rules_pycross's `tests/e2e/build_setuptools` distutils probe. + +rules_pycross ships a synthetic sdist whose `build_py` command shells out to a +fresh interpreter and imports `distutils.util`, guarding against a build +environment that leaves the backend's child processes unable to find distutils +(`SETUPTOOLS_USE_DISTUTILS`, a stale `PYTHONPATH`, or a stripped `PATH`). +build_helper.py filters `PYTHONHOME`/`PYTHONPATH`/`PYTHONPLATLIBDIR` out of the +action env for exactly this reason, so the probe is worth keeping. + +Driven the same way as `uv-sdist-mpicc`: the test invokes build_helper.py +directly against a sdist it builds in-process, so no lockfile entry or +network fetch is involved. +""" + +uv = use_extension("@aspect_rules_py//uv:extensions.bzl", "uv") +uv.declare_hub(hub_name = "pypi_pycross_distutils_probe") +uv.project( + hub_name = "pypi_pycross_distutils_probe", + lock = "//pycross-distutils-probe:uv.lock", + pyproject = "//pycross-distutils-probe:pyproject.toml", +) +use_repo(uv, "pypi_pycross_distutils_probe") diff --git a/e2e/cases/pycross-distutils-probe/uv.lock b/e2e/cases/pycross-distutils-probe/uv.lock new file mode 100644 index 000000000..8f4c248f8 --- /dev/null +++ b/e2e/cases/pycross-distutils-probe/uv.lock @@ -0,0 +1,82 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pycross-distutils-probe" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "build" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "build" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "wheel" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, +] diff --git a/e2e/cases/pycross-patches/BUILD.bazel b/e2e/cases/pycross-patches/BUILD.bazel new file mode 100644 index 000000000..62b8ca202 --- /dev/null +++ b/e2e/cases/pycross-patches/BUILD.bazel @@ -0,0 +1,22 @@ +# Ported from rules_pycross tests/e2e/patches_and_hooks. +# +# `PATCHED` comes from the pre-build patch (applied to the sdist source tree +# before the PEP 517 backend runs), `POST_PATCHED` from the post-install patch +# (applied to the unpacked wheel). The post-install hunk's context includes the +# pre-build hunk's line, so the two must apply in that order. + +load("@aspect_rules_py//py:defs.bzl", "py_test") + +exports_files([ + "patches/setproctitle-prebuild.patch", + "patches/setproctitle-postinstall.patch", +]) + +py_test( + name = "test_setproctitle_patched", + srcs = ["test_setproctitle_patched.py"], + dep_group = "pycross_patches", + main = "test_setproctitle_patched.py", + python_version = "3.12", + deps = ["@pypi_pycross_patches//setproctitle"], +) diff --git a/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch b/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch new file mode 100644 index 000000000..e242ee4ae --- /dev/null +++ b/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch @@ -0,0 +1,13 @@ +Ported from rules_pycross tests/e2e/patches_and_hooks. Retargeted at the +installed venv layout: rules_py applies post-install patches with `patch -d` +rooted at the install tree, so paths are `lib/python/site-packages/...` +rather than the wheel-relative paths rules_pycross uses. The `PATCHED = True` +context line comes from the pre-build patch, so this hunk only applies when +the two phases ran in order. +--- a/lib/python3.12/site-packages/setproctitle/__init__.py ++++ b/lib/python3.12/site-packages/setproctitle/__init__.py +@@ -56,3 +56,4 @@ + if sys.platform == "darwin": + getproctitle() + PATCHED = True ++POST_PATCHED = True diff --git a/e2e/cases/pycross-patches/patches/setproctitle-prebuild.patch b/e2e/cases/pycross-patches/patches/setproctitle-prebuild.patch new file mode 100644 index 000000000..789e73426 --- /dev/null +++ b/e2e/cases/pycross-patches/patches/setproctitle-prebuild.patch @@ -0,0 +1,7 @@ +--- a/pkg/setproctitle/__init__.py 2026-06-04 13:22:25.044466888 +0000 ++++ b/pkg/setproctitle/__init__.py 2026-06-04 13:23:03.082422796 +0000 +@@ -55,3 +55,4 @@ + # by fork() on macOS (see #113). + if sys.platform == "darwin": + getproctitle() ++PATCHED = True diff --git a/e2e/cases/pycross-patches/pyproject.toml b/e2e/cases/pycross-patches/pyproject.toml new file mode 100644 index 000000000..33be4d36b --- /dev/null +++ b/e2e/cases/pycross-patches/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "pycross_patches" +version = "0.0.0" +requires-python = ">=3.11" +dependencies = [ + "build", + "setuptools", + "wheel", + "setproctitle==1.3.2", +] + +[tool.uv] +no-binary-package = ["setproctitle"] diff --git a/e2e/cases/pycross-patches/setup.MODULE.bazel b/e2e/cases/pycross-patches/setup.MODULE.bazel new file mode 100644 index 000000000..62b442065 --- /dev/null +++ b/e2e/cases/pycross-patches/setup.MODULE.bazel @@ -0,0 +1,39 @@ +"""Port of rules_pycross's `tests/e2e/patches_and_hooks` workspace. + +setproctitle is forced to build from its sdist with both patch phases applied: +a pre-build patch that lands in the source tree before the PEP 517 backend +runs, and a post-install patch that rewrites the already-unpacked wheel. The +two patches stack — the post-install one applies against the pre-build +patch's output — so an ordering regression fails the runtime test. + +Uses its own hub: the patched setproctitle must not be shared with +`pycross-setuptools`, which builds the same version unpatched. + +Not ported: rules_pycross's `pre_build_hooks` / `post_build_hooks` / +`path_tools` / `site_hooks` and its `repair_wheel` hook have no rules_py +equivalent. `uv.override_package(toolchains = ..., env = ...)` covers the +"inject something into the build environment" case those hooks serve; see +`uv-sdist-jdk-build`. +""" + +uv = use_extension("@aspect_rules_py//uv:extensions.bzl", "uv") +uv.declare_hub(hub_name = "pypi_pycross_patches") +uv.project( + default_build_dependencies = [ + "build", + "setuptools", + "wheel", + ], + hub_name = "pypi_pycross_patches", + lock = "//pycross-patches:uv.lock", + pyproject = "//pycross-patches:pyproject.toml", +) +uv.override_package( + name = "setproctitle", + lock = "//pycross-patches:uv.lock", + post_install_patch_strip = 1, + post_install_patches = ["//pycross-patches:patches/setproctitle-postinstall.patch"], + pre_build_patch_strip = 1, + pre_build_patches = ["//pycross-patches:patches/setproctitle-prebuild.patch"], +) +use_repo(uv, "pypi_pycross_patches") diff --git a/e2e/cases/pycross-patches/test_setproctitle_patched.py b/e2e/cases/pycross-patches/test_setproctitle_patched.py new file mode 100644 index 000000000..a32362199 --- /dev/null +++ b/e2e/cases/pycross-patches/test_setproctitle_patched.py @@ -0,0 +1,17 @@ +# Ported from rules_pycross tests/e2e/patches_and_hooks/tests/test_setproctitle.py. +# `SITE_HOOK_RAN` is dropped: rules_py has no `site_hooks` equivalent. +import unittest + +import setproctitle + + +class TestSetproctitle(unittest.TestCase): + def test_import(self): + title = setproctitle.getproctitle() + self.assertIsNotNone(title) + self.assertTrue(setproctitle.PATCHED) + self.assertTrue(setproctitle.POST_PATCHED) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-patches/uv.lock b/e2e/cases/pycross-patches/uv.lock new file mode 100644 index 000000000..6c0c9d660 --- /dev/null +++ b/e2e/cases/pycross-patches/uv.lock @@ -0,0 +1,104 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pycross-patches" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "build" }, + { name = "setproctitle" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "build" }, + { name = "setproctitle", specifier = "==1.3.2" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "setproctitle" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/47/ac709629ddb9779fee29b7d10ae9580f60a4b37e49bce72360ddf9a79cdc/setproctitle-1.3.2.tar.gz", hash = "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd", size = 27173, upload-time = "2022-08-11T11:13:25.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/f9/f4e96c6c95d5e5e958405292ddb9dd932ec083c6f76ba55458b6caa4db02/setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa", size = 16936, upload-time = "2022-12-17T04:40:43.223Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/1b2c04a95da8e6c0951223bfbb0d4b56876ba35567455b88bbc9e48b7052/setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077", size = 11292, upload-time = "2022-12-17T04:40:45.225Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8d/4ad25c2e80e81f9c698add6c7a96e547c7194412ce85bce6c2c75eb8d2f8/setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de", size = 31575, upload-time = "2022-12-17T04:40:47.282Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/c1ef1118bcb756fd10bee57a2748240b033168501c77aec80d0eb9874f64/setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e", size = 32915, upload-time = "2022-12-17T04:40:49.347Z" }, + { url = "https://files.pythonhosted.org/packages/d7/76/46e536e87e0e46309f5664ebecebbbc541315d81ad301e93204d0248beed/setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf", size = 30080, upload-time = "2022-12-17T04:40:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/890da8a5790fa733a9fbf47d92e8226c1ff4bf1853dbdbabbdaa3aa6dffc/setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3", size = 31071, upload-time = "2022-12-17T04:40:53.078Z" }, + { url = "https://files.pythonhosted.org/packages/0e/08/a1fa4d4a3077604e71eb6b76795814b44a8a1fec874b06bca853157b2313/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4", size = 41228, upload-time = "2022-12-17T04:40:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7d/9c8371cde990ecce6d263c9b482bae0e75d49505589f1f7ef1ad4f756bbd/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77", size = 39720, upload-time = "2022-12-17T04:40:56.824Z" }, + { url = "https://files.pythonhosted.org/packages/26/a8/e406c98df9ff7a7481b8bb9ab4b410405a39751ec8b54ac8108bd0c80b4d/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80", size = 42765, upload-time = "2022-12-17T04:40:58.681Z" }, + { url = "https://files.pythonhosted.org/packages/96/e7/e409f944c8d22667f725eaba9d6c505ce6c44d91ff5922acc8347447ac66/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7", size = 41084, upload-time = "2022-12-17T04:41:00.616Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a4/931fb6e85ea5ea7569e563b8a97b43c695f97bb3fa88ee7d70492329679d/setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc", size = 11051, upload-time = "2022-12-17T04:41:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/7b/93/69ee2f7a651ca6a01b05aab3c55b9db3a44ff50125120cfacd46ced239b5/setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff", size = 11803, upload-time = "2022-12-17T04:41:03.966Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "wheel" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, +] diff --git a/e2e/cases/pycross-pure-python/BUILD.bazel b/e2e/cases/pycross-pure-python/BUILD.bazel new file mode 100644 index 000000000..71ca35f5c --- /dev/null +++ b/e2e/cases/pycross-pure-python/BUILD.bazel @@ -0,0 +1,104 @@ +# Ported from rules_pycross tests/e2e/build_pure_python. +# +# Three anyarch sdists built by `pep517_whl`, each importing at runtime, plus +# rules_pycross's `collect_wheels` matrix rebuilding the same wheels for two +# non-host target platforms. + +load("@aspect_rules_py//py:defs.bzl", "py_test") +load("//tools:collect_wheels.bzl", "collect_wheels") + +_PY = "3.12" + +_DEP_GROUP = "pycross_pure_python" + +# The wheel targets under `collect_wheels` are reached through a platform +# transition rather than a py_venv, so nothing sets the dep_group flag for +# them — the platform carries it alongside the libc/version flags the wheel +# resolver needs. +_PLATFORM_FLAGS = [ + "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", + "--@aspect_rules_py//uv/private/constraints/platform:platform_version=2.39", + "--@aspect_rules_py//uv/private/constraints/dep_group:dep_group=" + _DEP_GROUP, + "--@aspect_rules_py//py/private/interpreter:python_version=" + _PY, +] + +platform( + name = "amd64_linux", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + flags = _PLATFORM_FLAGS, +) + +platform( + name = "arm64_linux", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], + flags = _PLATFORM_FLAGS, +) + +py_test( + name = "test_tomli", + srcs = ["test_tomli.py"], + dep_group = _DEP_GROUP, + main = "test_tomli.py", + python_version = _PY, + deps = ["@pypi_pycross_pure_python//tomli"], +) + +py_test( + name = "test_filelock", + srcs = ["test_filelock.py"], + dep_group = _DEP_GROUP, + main = "test_filelock.py", + python_version = _PY, + deps = ["@pypi_pycross_pure_python//filelock"], +) + +py_test( + name = "test_pyproject_metadata", + srcs = ["test_pyproject_metadata.py"], + dep_group = _DEP_GROUP, + main = "test_pyproject_metadata.py", + python_version = _PY, + deps = ["@pypi_pycross_pure_python//pyproject_metadata"], +) + +# rules_pycross's shared top-level-packages check: a source-built wheel must +# land its top-level package in site-packages, and no dist-info directory may +# end up on sys.path. +py_test( + name = "test_top_level_packages", + srcs = ["test_top_level_packages.py"], + dep_group = _DEP_GROUP, + env = {"PYCROSS_TEST_PACKAGES": "tomli,filelock,pyproject_metadata"}, + main = "test_top_level_packages.py", + python_version = _PY, + deps = [ + "@pypi_pycross_pure_python//filelock", + "@pypi_pycross_pure_python//pyproject_metadata", + "@pypi_pycross_pure_python//tomli", + ], +) + +# An anyarch build uses no CC toolchain, so what differs per platform here is +# the configuration the build *tool* is resolved in: `pep517_whl`'s +# `exec_transition` has to pin the frontend and its wheels to the exec +# platform. If it leaked the target configuration, these builds would try to +# run a Linux interpreter on the exec host. +collect_wheels( + name = "pure_python_wheels", + expected_tags = ["-none-any.whl"], + platforms = [ + ":amd64_linux", + ":arm64_linux", + ], + wheels = [ + "@sdist_build__pycross_pure_python__filelock__3_17_0//:whl", + "@sdist_build__pycross_pure_python__pyproject_metadata__0_8_1//:whl", + "@sdist_build__pycross_pure_python__tomli__2_2_1//:whl", + ], +) diff --git a/e2e/cases/pycross-pure-python/pyproject.toml b/e2e/cases/pycross-pure-python/pyproject.toml new file mode 100644 index 000000000..075a72500 --- /dev/null +++ b/e2e/cases/pycross-pure-python/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "pycross_pure_python" +version = "0.0.0" +requires-python = ">=3.11" +dependencies = [ + "build", + "setuptools", + # Build backends the three sdists below declare in [build-system]. + # The configure tool resolves them out of this lock, so they have to be + # reachable from the project's dependency graph. + "flit-core", + "hatchling", + "hatch-vcs", + "filelock==3.17.0", + "pyproject-metadata==0.8.1", + "tomli==2.2.1", +] + +# All three ship `-none-any` wheels; forcing the source build exercises +# pep517_whl's `--validate-anyarch` path for three different PEP 517 +# backends (hatchling, flit-core). +[tool.uv] +no-binary-package = [ + "filelock", + "pyproject-metadata", + "tomli", +] diff --git a/e2e/cases/pycross-pure-python/setup.MODULE.bazel b/e2e/cases/pycross-pure-python/setup.MODULE.bazel new file mode 100644 index 000000000..cffc227a6 --- /dev/null +++ b/e2e/cases/pycross-pure-python/setup.MODULE.bazel @@ -0,0 +1,39 @@ +"""Port of rules_pycross's `tests/e2e/build_pure_python` workspace. + +Three pure-Python packages are forced to build from their sdists so the +`pep517_whl` (anyarch) path runs against two different PEP 517 backends — +hatchling (filelock) and flit-core (tomli, pyproject-metadata). Each package +still has to import from site-packages afterwards. + +The `sdist_build` repos are exposed so BUILD.bazel can rebuild the same wheels +under non-host target platforms: an anyarch build carries no CC toolchain, so +what that matrix exercises is `pep517_whl`'s exec transition — build tooling +(`build`, the backend) must resolve for the exec platform even when the +configuration targets another one. +""" + +uv = use_extension("@aspect_rules_py//uv:extensions.bzl", "uv") +uv.declare_hub(hub_name = "pypi_pycross_pure_python") +uv.project( + default_build_dependencies = [ + "build", + "setuptools", + # The backends have to come through the project repo, not through the + # configure tool's `[build-system] requires` resolution: that path + # yields bare `whl_install` targets, and hatchling needs its own + # transitive closure (pathspec, pluggy, trove-classifiers, …). + "flit-core", + "hatch-vcs", + "hatchling", + ], + hub_name = "pypi_pycross_pure_python", + lock = "//pycross-pure-python:uv.lock", + pyproject = "//pycross-pure-python:pyproject.toml", +) +use_repo( + uv, + "pypi_pycross_pure_python", + "sdist_build__pycross_pure_python__filelock__3_17_0", + "sdist_build__pycross_pure_python__pyproject_metadata__0_8_1", + "sdist_build__pycross_pure_python__tomli__2_2_1", +) diff --git a/e2e/cases/pycross-pure-python/test_filelock.py b/e2e/cases/pycross-pure-python/test_filelock.py new file mode 100644 index 000000000..cffa86ad3 --- /dev/null +++ b/e2e/cases/pycross-pure-python/test_filelock.py @@ -0,0 +1,13 @@ +import unittest + +import filelock + + +class TestFileLock(unittest.TestCase): + def test_import(self): + lock = filelock.FileLock("dummy.lock") + self.assertIsNotNone(lock) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-pure-python/test_pyproject_metadata.py b/e2e/cases/pycross-pure-python/test_pyproject_metadata.py new file mode 100644 index 000000000..739d34ff7 --- /dev/null +++ b/e2e/cases/pycross-pure-python/test_pyproject_metadata.py @@ -0,0 +1,12 @@ +import unittest + +import pyproject_metadata + + +class TestPyprojectMetadata(unittest.TestCase): + def test_import(self): + self.assertIsNotNone(pyproject_metadata.__version__) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-pure-python/test_tomli.py b/e2e/cases/pycross-pure-python/test_tomli.py new file mode 100644 index 000000000..48bdea076 --- /dev/null +++ b/e2e/cases/pycross-pure-python/test_tomli.py @@ -0,0 +1,13 @@ +import unittest + +import tomli + + +class TestTomli(unittest.TestCase): + def test_parse(self): + d = tomli.loads("a = 1") + self.assertEqual(d["a"], 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-pure-python/test_top_level_packages.py b/e2e/cases/pycross-pure-python/test_top_level_packages.py new file mode 100644 index 000000000..d77fc3d65 --- /dev/null +++ b/e2e/cases/pycross-pure-python/test_top_level_packages.py @@ -0,0 +1,55 @@ +"""Verify that installed wheels have their top-level packages in site-packages. + +This test works in two modes: +1. If PYCROSS_TEST_PACKAGES env var is set, it tests those packages (comma-separated). +2. Otherwise, it tests hardcoded packages (regex, IPython) for + the requirements e2e workspace. + +Packages are tested for importability and correct site-packages placement. +""" + +import importlib +import importlib.util +import os +import sys +import unittest + + +class TopLevelPackagesTest(unittest.TestCase): + """Check that top_level_paths from wheel inspection actually show up.""" + + def _get_packages(self): + return [p.strip() for p in os.environ.get("PYCROSS_TEST_PACKAGES", "regex,IPython").split(",") if p.strip()] + + def test_packages_importable(self): + """All expected packages should be importable.""" + for pkg in self._get_packages(): + with self.subTest(package=pkg): + mod = importlib.import_module(pkg) + self.assertIsNotNone(mod, f"{pkg} imported as None") + + def test_packages_resolve_to_site_packages(self): + """All expected packages should resolve to files within site-packages.""" + for pkg in self._get_packages(): + with self.subTest(package=pkg): + spec = importlib.util.find_spec(pkg) + self.assertIsNotNone(spec, f"{pkg} module spec not found") + self.assertIsNotNone(spec.origin, f"{pkg} has no origin (namespace package?)") + self.assertIn( + "site-packages", + spec.origin, + f"{pkg} origin not in site-packages: {spec.origin}", + ) + + def test_no_dist_info_on_sys_path(self): + """dist-info directories should not be added as sys.path entries.""" + for path in sys.path: + basename = os.path.basename(path) + self.assertFalse( + basename.endswith(".dist-info"), + f"dist-info directory should not be on sys.path: {path}", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-pure-python/uv.lock b/e2e/cases/pycross-pure-python/uv.lock new file mode 100644 index 000000000..385317dde --- /dev/null +++ b/e2e/cases/pycross-pure-python/uv.lock @@ -0,0 +1,230 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027, upload-time = "2025-01-21T20:04:49.099Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164, upload-time = "2025-01-21T20:04:47.734Z" }, +] + +[[package]] +name = "flit-core" +version = "3.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/59/b6fc2188dfc7ea4f936cd12b49d707f66a1cb7a1d2c16172963534db741b/flit_core-3.12.0.tar.gz", hash = "sha256:18f63100d6f94385c6ed57a72073443e1a71a4acb4339491615d0f16d6ff01b2", size = 53690, upload-time = "2025-03-25T08:03:23.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/65/b6ba90634c984a4fcc02c7e3afe523fef500c4980fec67cc27536ee50acf/flit_core-3.12.0-py3-none-any.whl", hash = "sha256:e7a0304069ea895172e3c7bb703292e992c5d1555dd1233ab7b5621b5b69e62c", size = 45594, upload-time = "2025-03-25T08:03:20.772Z" }, +] + +[[package]] +name = "hatch-vcs" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hatchling" }, + { name = "setuptools-scm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/4cc743d38adbee9d57d786fa496ed1daadb17e48589b6da8fa55717a0746/hatch_vcs-0.5.0.tar.gz", hash = "sha256:0395fa126940340215090c344a2bf4e2a77bcbe7daab16f41b37b98c95809ff9", size = 11424, upload-time = "2025-05-27T05:16:04.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/48/1f85cee4b7b4f40b9b814b1febbc661bda6ced9649e410a0b74f6e415dd0/hatch_vcs-0.5.0-py3-none-any.whl", hash = "sha256:b49677dbdc597460cc22d01b27ab3696f5e16a21ecf2700fb01bc28e1f2a04a7", size = 8507, upload-time = "2025-05-27T05:16:03.184Z" }, +] + +[[package]] +name = "hatchling" +version = "1.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/e2/dfa73fe78f773018dcaebc6d09b819bc10d328ff5a6b4a66efa1e3d71f52/hatchling-1.31.0.tar.gz", hash = "sha256:6b48ad4068a482ed7239b3a8215bc55b47aad3345d58dfc94e553c5d2d46211b", size = 57208, upload-time = "2026-07-08T01:48:32.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/e2/2c0af0a52d16be74a4f194564fcdc417521ed863e9b65e4bc9052dacba6f/hatchling-1.31.0-py3-none-any.whl", hash = "sha256:aac80bec8b6fe35e8480f1c335be8910fa210a0e6f735a139be205dadcacb544", size = 77747, upload-time = "2026-07-08T01:48:31.024Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycross-pure-python" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "build" }, + { name = "filelock" }, + { name = "flit-core" }, + { name = "hatch-vcs" }, + { name = "hatchling" }, + { name = "pyproject-metadata" }, + { name = "setuptools" }, + { name = "tomli" }, +] + +[package.metadata] +requires-dist = [ + { name = "build" }, + { name = "filelock", specifier = "==3.17.0" }, + { name = "flit-core" }, + { name = "hatch-vcs" }, + { name = "hatchling" }, + { name = "pyproject-metadata", specifier = "==0.8.1" }, + { name = "setuptools" }, + { name = "tomli", specifier = "==2.2.1" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pyproject-metadata" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/24/47dc876eacddcf7125fe59cd26b064530c7e58655dae87f6928bf47aabaf/pyproject_metadata-0.8.1.tar.gz", hash = "sha256:e6b494d4fbac8007ed0efd3a08df067bb28a299fb113b885ce77e492ad1f0600", size = 15404, upload-time = "2024-10-07T18:41:22.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/81/42aaafbff27ca340eef777a4e3e8a509941e75fc0eeb9da2be5ee4159041/pyproject_metadata-0.8.1-py3-none-any.whl", hash = "sha256:adf593fa478b787c90cc77fcea4114f19a3a1335532bdcba2851be9459a6c39e", size = 7810, upload-time = "2024-10-07T18:41:20.495Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "setuptools-scm" +version = "10.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "vcs-versioning" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/b1/d0b97ffd2856a7d19c63024a89fb84813cb9d2ed7fa8fdbedf9e2f13a9ab/setuptools_scm-10.2.1.tar.gz", hash = "sha256:4fa7dd82cf8c800df59c9a288c90299b1657ff1ecfc3f5cc00287c5dbf5e27a9", size = 154237, upload-time = "2026-07-21T08:08:04.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/b4/02ac1d9833b882c87b3a4e82703e70eac4ab33d0d15fb123e60bb01f3bc1/setuptools_scm-10.2.1-py3-none-any.whl", hash = "sha256:b7c82f4102d389ee57dc66ccdb4f9b4bca3c40ba83b43f1f63d68ccd72db2580", size = 29078, upload-time = "2026-07-21T08:08:03.293Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2026.6.1.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/e3/7ca82ee24c82d344584abd5b8637b3bd056f2900226e8d82fc22f1184b92/trove_classifiers-2026.6.1.19.tar.gz", hash = "sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745", size = 17059, upload-time = "2026-06-01T19:41:34.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl", hash = "sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3", size = 14211, upload-time = "2026-06-01T19:41:33.434Z" }, +] + +[[package]] +name = "vcs-versioning" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/95/c95bb74950763a163defcf4cedf6c5edfca1d623fd5031b76516ece85076/vcs_versioning-2.2.2.tar.gz", hash = "sha256:4ac4ded78720cdb4d0291ae58ace87e1e9201912e1023f3029c6cce5c9152cfb", size = 143135, upload-time = "2026-06-29T13:26:06.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/9e/1f06f4ddc3a74ccfe0877490caaf4a5233e65401160afc8cb13207815f5e/vcs_versioning-2.2.2-py3-none-any.whl", hash = "sha256:fe7fb216f8780a5516e7864a9333aacb1b70015b087a9be3918da76ef2760808", size = 108014, upload-time = "2026-06-29T13:26:05.367Z" }, +] diff --git a/e2e/cases/pycross-setuptools/BUILD.bazel b/e2e/cases/pycross-setuptools/BUILD.bazel new file mode 100644 index 000000000..df7c2a63b --- /dev/null +++ b/e2e/cases/pycross-setuptools/BUILD.bazel @@ -0,0 +1,109 @@ +# Ported from rules_pycross tests/e2e/build_setuptools. +# +# Three setuptools C-extension sdists built by `pep517_native_whl`, imported at +# runtime for the host, then rebuilt through rules_pycross's `collect_wheels` +# matrix for two non-host Linux platforms — the cross path this branch adds. + +load("@aspect_rules_py//py:defs.bzl", "py_test") +load("//tools:collect_wheels.bzl", "collect_wheels") + +exports_files(["require_extension.patch"]) + +_PY = "3.12" + +_DEP_GROUP = "pycross_setuptools" + +# The libc/version flags are what the wheel resolver keys platform-specific +# wheel selection off; a bare constraint_values platform would leave build +# dependencies unresolvable. Same shape as //uv-deps-650/crossbuild. The +# dep_group flag rides along because the wheel targets under `collect_wheels` +# are reached through a platform transition, not through a py_venv. +_PLATFORM_FLAGS = [ + "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", + "--@aspect_rules_py//uv/private/constraints/platform:platform_version=2.39", + "--@aspect_rules_py//uv/private/constraints/dep_group:dep_group=" + _DEP_GROUP, + "--@aspect_rules_py//py/private/interpreter:python_version=" + _PY, +] + +platform( + name = "amd64_linux", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + flags = _PLATFORM_FLAGS, +) + +platform( + name = "arm64_linux", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], + flags = _PLATFORM_FLAGS, +) + +py_test( + name = "test_setproctitle", + srcs = ["test_setproctitle.py"], + dep_group = _DEP_GROUP, + main = "test_setproctitle.py", + python_version = _PY, + deps = ["@pypi_pycross_setuptools//setproctitle"], +) + +py_test( + name = "test_zstandard", + srcs = ["test_zstandard.py"], + dep_group = _DEP_GROUP, + main = "test_zstandard.py", + python_version = _PY, + deps = ["@pypi_pycross_setuptools//zstandard"], +) + +py_test( + name = "test_pyyaml", + srcs = ["test_pyyaml.py"], + dep_group = _DEP_GROUP, + main = "test_pyyaml.py", + python_version = _PY, + deps = ["@pypi_pycross_setuptools//pyyaml"], +) + +# rules_pycross's cross matrix, restricted to zstandard. +# +# pyyaml is excluded because `PYYAML_FORCE_LIBYAML=0` makes it anyarch — there +# is nothing platform-specific left to assert. +# +# setproctitle is excluded because it cannot cross-build here at all: its +# setup.py picks sources and macros off `sys.platform`, which reports the +# *exec* interpreter's platform. Cross mode overrides `_PYTHON_HOST_PLATFORM` +# and `_PYTHON_SYSCONFIGDATA_NAME`, neither of which moves `sys.platform`, so a +# darwin exec host compiles `src/darwin_set_process_name.c` with +# `-D__darwin__=1` against a linux target and fails on +# `CoreFoundation/CoreFoundation.h`. rules_pycross handles this with a real +# crossenv (`pycross/private/build/tools/crossenv/`) that patches `sys.platform` +# in the build interpreter. `require_extension.patch` keeps that failure loud +# rather than letting setproctitle fall back to an anyarch wheel. +collect_wheels( + name = "native_wheels", + expected_tags = [ + "linux_x86_64", + "linux_aarch64", + ], + platforms = [ + ":amd64_linux", + ":arm64_linux", + ], + wheels = ["@sdist_build__pycross_setuptools__zstandard__0_22_0//:whl"], +) + +py_test( + name = "native_wheels_elf_test", + srcs = ["//tools:check_wheel_native.py"], + args = ["$(rootpath :native_wheels)"], + data = [":native_wheels"], + dep_group = _DEP_GROUP, + main = "//tools:check_wheel_native.py", + python_version = _PY, +) diff --git a/e2e/cases/pycross-setuptools/pyproject.toml b/e2e/cases/pycross-setuptools/pyproject.toml new file mode 100644 index 000000000..c4e3838f3 --- /dev/null +++ b/e2e/cases/pycross-setuptools/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "pycross_setuptools" +version = "0.0.0" +requires-python = ">=3.11" +dependencies = [ + "build", + "setuptools", + "wheel", + # zstandard's setup.py builds a cffi backend when cffi is importable. + "cffi", + # PyYAML 6.0.x cannot be cythonized by Cython 3. + "cython<3.0", + "pyyaml==6.0.1", + "setproctitle==1.3.2", + "zstandard==0.22.0", +] + +[tool.uv] +no-binary-package = [ + "pyyaml", + "setproctitle", + "zstandard", +] diff --git a/e2e/cases/pycross-setuptools/require_extension.patch b/e2e/cases/pycross-setuptools/require_extension.patch new file mode 100644 index 000000000..6f232b529 --- /dev/null +++ b/e2e/cases/pycross-setuptools/require_extension.patch @@ -0,0 +1,18 @@ +setproctitle's setup.py swallows any extension build failure and re-runs the +build without the C extension, producing a `py3-none-any` wheel. That fallback +makes a broken native build indistinguishable from a successful one — and +`pep517_native_whl`'s cross-mode wheel-tag validation skips `-none-any` wheels, +so nothing downstream notices either. Removing the fallback makes the extension +mandatory, the same way `//uv-sdist-native-build:require_cxx_driver.patch` +forces python-geohash to exercise the C++ driver. +--- a/setup.py ++++ b/setup.py +@@ -129,7 +129,4 @@ + ) + + +-try: +- do_build(with_extension=True) +-except BuildError: +- do_build(with_extension=False) ++do_build(with_extension=True) diff --git a/e2e/cases/pycross-setuptools/setup.MODULE.bazel b/e2e/cases/pycross-setuptools/setup.MODULE.bazel new file mode 100644 index 000000000..012261ca9 --- /dev/null +++ b/e2e/cases/pycross-setuptools/setup.MODULE.bazel @@ -0,0 +1,58 @@ +"""Port of rules_pycross's `tests/e2e/build_setuptools` workspace. + +Three setuptools-backed C-extension packages are forced to build from their +sdists: PyYAML (Cython-generated extension), setproctitle (plain C) and +zstandard (vendored libzstd). Together they cover the `pep517_native_whl` +path for a Cython pipeline, a trivial extension and a large vendored-C build. + +`env` on PyYAML ports rules_pycross's per-package `build_env` override +(`setuptools.override(name = "pyyaml", build_env = ...)`), and is observable +at runtime: with `PYYAML_FORCE_LIBYAML=0` the built wheel must not carry the +libyaml-backed `CLoader`. + +Not ported: rules_pycross builds psycopg2 with a `pg_config` tool dep and +zstandard against a Bazel-built system libzstd. Both need `rules_foreign_cc` +plus PostgreSQL/OpenSSL/zlib/zstd source builds in this workspace; the JDK +equivalent of that plumbing is already covered by `uv-sdist-jdk-build`. +""" + +uv = use_extension("@aspect_rules_py//uv:extensions.bzl", "uv") +uv.declare_hub(hub_name = "pypi_pycross_setuptools") +uv.project( + default_build_dependencies = [ + "build", + "setuptools", + "wheel", + # zstandard's cffi backend imports pycparser through cffi. Naming cffi + # here routes it through the project repo's py_library, which carries + # that transitive closure; the configure tool's `[build-system] + # requires` resolution yields a bare `whl_install` target instead. + "cffi", + ], + hub_name = "pypi_pycross_setuptools", + lock = "//pycross-setuptools:uv.lock", + pyproject = "//pycross-setuptools:pyproject.toml", +) +uv.override_package( + name = "pyyaml", + env = {"PYYAML_FORCE_LIBYAML": "0"}, + lock = "//pycross-setuptools:uv.lock", +) +uv.override_package( + name = "setproctitle", + lock = "//pycross-setuptools:uv.lock", + pre_build_patch_strip = 1, + pre_build_patches = ["//pycross-setuptools:require_extension.patch"], +) +uv.override_package( + name = "zstandard", + lock = "//pycross-setuptools:uv.lock", + resource_set = "mem_4g", +) +use_repo( + uv, + "pypi_pycross_setuptools", + "sdist_build__pycross_setuptools__pyyaml__6_0_1", + "sdist_build__pycross_setuptools__setproctitle__1_3_2", + "sdist_build__pycross_setuptools__zstandard__0_22_0", +) diff --git a/e2e/cases/pycross-setuptools/test_pyyaml.py b/e2e/cases/pycross-setuptools/test_pyyaml.py new file mode 100644 index 000000000..1853f0771 --- /dev/null +++ b/e2e/cases/pycross-setuptools/test_pyyaml.py @@ -0,0 +1,23 @@ +# Ported from rules_pycross tests/e2e/build_setuptools/tests/test_pyyaml.py. +# The `CLoader` assertion is added: rules_pycross's version tolerates either +# backend, which leaves its `build_env` override untested. The build sets +# PYYAML_FORCE_LIBYAML=0, so the source build must have produced the pure-Python +# backend — if the override never reached the build action, CLoader is present. +import yaml + + +def test_pyyaml_import(): + try: + from yaml import CLoader as Loader + except ImportError: + from yaml import Loader + else: + raise AssertionError("PYYAML_FORCE_LIBYAML=0 did not reach the build: CLoader exists") + + data = yaml.load("hello: world\nlist:\n - 1\n - 2\n", Loader=Loader) + assert data == {"hello": "world", "list": [1, 2]} + print("PyYAML loaded successfully!") + + +if __name__ == "__main__": + test_pyyaml_import() diff --git a/e2e/cases/pycross-setuptools/test_setproctitle.py b/e2e/cases/pycross-setuptools/test_setproctitle.py new file mode 100644 index 000000000..44541f3a7 --- /dev/null +++ b/e2e/cases/pycross-setuptools/test_setproctitle.py @@ -0,0 +1,13 @@ +import unittest + +import setproctitle + + +class TestSetproctitle(unittest.TestCase): + def test_import(self): + title = setproctitle.getproctitle() + self.assertIsNotNone(title) + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-setuptools/test_zstandard.py b/e2e/cases/pycross-setuptools/test_zstandard.py new file mode 100644 index 000000000..c944a74d9 --- /dev/null +++ b/e2e/cases/pycross-setuptools/test_zstandard.py @@ -0,0 +1,46 @@ +import unittest + +import zstandard + +ZEN = b"""\ +The Zen of Python, by Tim Peters + +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +""" + + +class TestZstandard(unittest.TestCase): + def test_roundtrip(self): + cctx = zstandard.ZstdCompressor() + compressed = cctx.compress(ZEN) + + # Make sure we actually compressed + assert len(compressed) < len(ZEN) + + dctx = zstandard.ZstdDecompressor() + decompressed = dctx.decompress(compressed) + + # Check the round trip + assert decompressed == ZEN + + +if __name__ == "__main__": + unittest.main() diff --git a/e2e/cases/pycross-setuptools/uv.lock b/e2e/cases/pycross-setuptools/uv.lock new file mode 100644 index 000000000..441684d9a --- /dev/null +++ b/e2e/cases/pycross-setuptools/uv.lock @@ -0,0 +1,286 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cython" +version = "0.29.37" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/97/8cc3fe7c6de4796921236a64d00ca8a95565772e57f0d3caae68d880b592/Cython-0.29.37.tar.gz", hash = "sha256:f813d4a6dd94adee5d4ff266191d1d95bf6d4164a4facc535422c021b2504cfb", size = 2099621, upload-time = "2023-12-19T09:22:39.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/ba/eddee5c048c621607023f7438e27f9e559d4c34149d758049d50150c2b2f/Cython-0.29.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b048354fd380278f2fa096e7526973beb6e0491a9d44d7e4e29df52612d25776", size = 1808964, upload-time = "2023-12-19T09:23:54.059Z" }, + { url = "https://files.pythonhosted.org/packages/22/99/2b01e0164ff1ea592e3515e473517674780f0f3d49f48af30324b85ac94d/Cython-0.29.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea6d208be1906c5df25b674777d5905c6d8e9ef0b201b830849e0729ba08caba", size = 1910527, upload-time = "2023-12-19T09:23:57.538Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2f/b3f3694aa2ea48a39c2ef1b218b6a25f4b0d62836495f9a65495f060969a/Cython-0.29.37-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:af03854571738307a5f30cc6b724081d72db12f907699e7fdfc04c12c839158e", size = 1910933, upload-time = "2023-12-19T09:24:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7f/f1a8ec07e0e7e2af84940c0155e6f8bb383671da34a785f441a19f2cff4e/Cython-0.29.37-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c33508ede9172a6f6f99d5a6dadc7fee23c840423b411ef8b5a403c04e530297", size = 2006522, upload-time = "2023-12-19T09:24:04.4Z" }, + { url = "https://files.pythonhosted.org/packages/c9/aa/99a0eac01136c0c75feb3210d107c49f93d49d5cb97f19e99318b9ecefdd/Cython-0.29.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8af5975ecfae254d8c0051204fca995dda8f93cf9f0bbf7571e3cda2b0cef4d", size = 1951525, upload-time = "2023-12-19T09:57:00.464Z" }, + { url = "https://files.pythonhosted.org/packages/14/5f/f5efba4409474892d650ba7e57349afa5d4c41d06f5ba3356c32891c75a6/Cython-0.29.37-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29415d8eb2fdc1ea518ca4810c50a2d062b387d4c9fbcfb3352346e93db22c6d", size = 1950936, upload-time = "2023-12-19T09:24:07.949Z" }, + { url = "https://files.pythonhosted.org/packages/7e/26/9d8de10005fedb1eceabe713348d43bae1dbab1786042ca0751a2e2b0f8c/Cython-0.29.37-py2.py3-none-any.whl", hash = "sha256:95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c", size = 989503, upload-time = "2023-12-19T09:22:32.861Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pycross-setuptools" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "build" }, + { name = "cffi" }, + { name = "cython" }, + { name = "pyyaml" }, + { name = "setproctitle" }, + { name = "setuptools" }, + { name = "wheel" }, + { name = "zstandard" }, +] + +[package.metadata] +requires-dist = [ + { name = "build" }, + { name = "cffi" }, + { name = "cython", specifier = "<3.0" }, + { name = "pyyaml", specifier = "==6.0.1" }, + { name = "setproctitle", specifier = "==1.3.2" }, + { name = "setuptools" }, + { name = "wheel" }, + { name = "zstandard", specifier = "==0.22.0" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201, upload-time = "2023-07-18T00:00:23.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", size = 187867, upload-time = "2023-07-17T23:57:34.35Z" }, + { url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", size = 167530, upload-time = "2023-07-17T23:57:36.975Z" }, + { url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", size = 732244, upload-time = "2023-07-17T23:57:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", size = 752871, upload-time = "2023-07-17T23:57:51.921Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", size = 757729, upload-time = "2023-07-17T23:57:59.865Z" }, + { url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", size = 748528, upload-time = "2023-08-28T18:43:23.207Z" }, + { url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", size = 130286, upload-time = "2023-07-17T23:58:02.964Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", size = 144699, upload-time = "2023-07-17T23:58:05.586Z" }, + { url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692, upload-time = "2023-08-28T18:43:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622, upload-time = "2023-08-28T18:43:26.54Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937, upload-time = "2024-01-18T20:40:22.92Z" }, + { url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969, upload-time = "2023-08-28T18:43:28.56Z" }, + { url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604, upload-time = "2023-08-28T18:43:30.206Z" }, + { url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098, upload-time = "2023-08-28T18:43:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675, upload-time = "2023-08-28T18:43:33.613Z" }, +] + +[[package]] +name = "setproctitle" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/47/ac709629ddb9779fee29b7d10ae9580f60a4b37e49bce72360ddf9a79cdc/setproctitle-1.3.2.tar.gz", hash = "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd", size = 27173, upload-time = "2022-08-11T11:13:25.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/f9/f4e96c6c95d5e5e958405292ddb9dd932ec083c6f76ba55458b6caa4db02/setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa", size = 16936, upload-time = "2022-12-17T04:40:43.223Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/1b2c04a95da8e6c0951223bfbb0d4b56876ba35567455b88bbc9e48b7052/setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077", size = 11292, upload-time = "2022-12-17T04:40:45.225Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8d/4ad25c2e80e81f9c698add6c7a96e547c7194412ce85bce6c2c75eb8d2f8/setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de", size = 31575, upload-time = "2022-12-17T04:40:47.282Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/c1ef1118bcb756fd10bee57a2748240b033168501c77aec80d0eb9874f64/setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e", size = 32915, upload-time = "2022-12-17T04:40:49.347Z" }, + { url = "https://files.pythonhosted.org/packages/d7/76/46e536e87e0e46309f5664ebecebbbc541315d81ad301e93204d0248beed/setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf", size = 30080, upload-time = "2022-12-17T04:40:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/890da8a5790fa733a9fbf47d92e8226c1ff4bf1853dbdbabbdaa3aa6dffc/setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3", size = 31071, upload-time = "2022-12-17T04:40:53.078Z" }, + { url = "https://files.pythonhosted.org/packages/0e/08/a1fa4d4a3077604e71eb6b76795814b44a8a1fec874b06bca853157b2313/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4", size = 41228, upload-time = "2022-12-17T04:40:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7d/9c8371cde990ecce6d263c9b482bae0e75d49505589f1f7ef1ad4f756bbd/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77", size = 39720, upload-time = "2022-12-17T04:40:56.824Z" }, + { url = "https://files.pythonhosted.org/packages/26/a8/e406c98df9ff7a7481b8bb9ab4b410405a39751ec8b54ac8108bd0c80b4d/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80", size = 42765, upload-time = "2022-12-17T04:40:58.681Z" }, + { url = "https://files.pythonhosted.org/packages/96/e7/e409f944c8d22667f725eaba9d6c505ce6c44d91ff5922acc8347447ac66/setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7", size = 41084, upload-time = "2022-12-17T04:41:00.616Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a4/931fb6e85ea5ea7569e563b8a97b43c695f97bb3fa88ee7d70492329679d/setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc", size = 11051, upload-time = "2022-12-17T04:41:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/7b/93/69ee2f7a651ca6a01b05aab3c55b9db3a44ff50125120cfacd46ced239b5/setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff", size = 11803, upload-time = "2022-12-17T04:41:03.966Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "wheel" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, +] + +[[package]] +name = "zstandard" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/91/2162ab4239b3bd6743e8e407bc2442fca0d326e2d77b3f4a88d90ad5a1fa/zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70", size = 660738, upload-time = "2023-11-01T06:57:31.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/41/80fc08ed96e68df920d28592710f5ed96fb288fda1fbb4b6aee5fdbaa5f6/zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08", size = 921016, upload-time = "2023-11-01T06:49:32.023Z" }, + { url = "https://files.pythonhosted.org/packages/54/fc/c1b1a1e140451f3362789f546731b3ef36c78668be19d7fc6fbd4326b535/zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26", size = 703352, upload-time = "2023-11-01T06:49:36.263Z" }, + { url = "https://files.pythonhosted.org/packages/68/fb/0a9389ee8ccc532ac4567562c7746bd7537d16bc5b079b2696fe3c510c37/zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09", size = 4918211, upload-time = "2023-11-01T06:49:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/80/6a/f8a618f84aafb9c373a959e7e51ad34bda73f1d99cd856c05c8f0b78e87f/zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775", size = 5433363, upload-time = "2023-11-01T06:50:14.145Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e1/0b29be2d3a8d86053f284add5a0b4174c820fefc96183b01d5cbcedd498d/zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8", size = 4847234, upload-time = "2023-11-01T06:50:35.646Z" }, + { url = "https://files.pythonhosted.org/packages/55/0b/b23b1a6e4d4525f663162344d4896f396267e7f65607f16f7a62e1862a23/zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94", size = 4923512, upload-time = "2023-11-01T06:50:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/9f/da/e16b2a5613141aaa719e4b09cdadf5eb3fb099311ffc8c0b13785bdcbe1a/zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88", size = 5447292, upload-time = "2023-11-01T06:51:16.79Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1d/ecca5dee2d173b3b70fa8d6bdeec17f9a875aa6fdac4a6bfaadf7c9668a1/zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440", size = 442566, upload-time = "2023-11-01T06:51:20.56Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bc/878a5b8f413d5fe902842fde08ecf317d3979c9728135034a519c409ce3f/zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd", size = 511638, upload-time = "2023-11-01T06:51:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f9/6b531e83f2e61bb9d66508113fd68557d990639be3dd37116af272932614/zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a", size = 920922, upload-time = "2023-11-01T06:51:29.476Z" }, + { url = "https://files.pythonhosted.org/packages/7d/80/9e40e57ba17dbbf6c55bcf0ac4ee533c367285209f309bdd9ab290c40536/zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912", size = 703142, upload-time = "2023-11-01T06:51:33.594Z" }, + { url = "https://files.pythonhosted.org/packages/47/44/be2d67304ba9bf4694ffcaa99d146814d70c4cb0bfc00e0e86fdfae66a31/zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f", size = 4918778, upload-time = "2023-11-01T06:51:53.663Z" }, + { url = "https://files.pythonhosted.org/packages/e5/01/080939755ca12ebbb7fc38b6f4ddecd5e8c416d571d4927ece1360baba2c/zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c", size = 5426393, upload-time = "2023-11-01T06:52:14.784Z" }, + { url = "https://files.pythonhosted.org/packages/e1/68/66f1edfa9760213e9a85cf70a6c78a9db1a9d9bf0a36c7fdf9ed117bf163/zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4", size = 4845899, upload-time = "2023-11-01T06:52:35.365Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c5/d5f4521c5444f3d1e8bd0dc32b40766b0de1950eee46c734a3a98ddc8f96/zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc", size = 4920875, upload-time = "2023-11-01T06:52:55.355Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7a/2702eb29c3851a04f42e63443420520b392e432bdb30cb21c11dac1f5eb3/zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45", size = 5451783, upload-time = "2023-11-01T06:53:15.066Z" }, + { url = "https://files.pythonhosted.org/packages/08/3e/4b78e8378dfdb25f51a1123ec4e44f98a490d58ccf51104a3a9c6f821eb1/zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2", size = 442885, upload-time = "2023-11-01T06:53:19.076Z" }, + { url = "https://files.pythonhosted.org/packages/b6/91/15feaf0d389f7d696ed0fbd75b51c6cbc04e9b8b474292a3bb2b5157e093/zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c", size = 511809, upload-time = "2023-11-01T06:53:22.745Z" }, +] diff --git a/e2e/cases/tools/BUILD.bazel b/e2e/cases/tools/BUILD.bazel index 64efef29b..65184ccbf 100644 --- a/e2e/cases/tools/BUILD.bazel +++ b/e2e/cases/tools/BUILD.bazel @@ -1,10 +1,14 @@ # Shared helpers consumed by //... via load("//tools:..."). +load("@aspect_rules_py//py:defs.bzl", "py_binary") + # Consumed cross-package by the `assert_tar_listing` macro (//tools:asserts.bzl), # which declares a py_test with this script as `main` in the caller's package. exports_files([ "assert_absent.py", "assert_ownership.py", + "check_wheel_native.py", + "check_wheel_tags.py", ]) platform( @@ -15,3 +19,10 @@ platform( ], visibility = ["//visibility:public"], ) + +py_binary( + name = "collect_wheels_tool", + srcs = ["collect_wheels.py"], + main = "collect_wheels.py", + visibility = ["//visibility:public"], +) diff --git a/e2e/cases/tools/check_wheel_native.py b/e2e/cases/tools/check_wheel_native.py new file mode 100644 index 000000000..0bc1211d5 --- /dev/null +++ b/e2e/cases/tools/check_wheel_native.py @@ -0,0 +1,92 @@ +"""Assert cross-built wheels really contain extensions for the tagged platform. + +Usage: check_wheel_native.py + +For every Linux-tagged wheel in the directory, every extension module inside it +must be an ELF object whose `e_machine` matches the wheel's architecture tag, +and whose filename ABI tag names the same architecture. A cross build that +fell back to the exec platform's compiler or sysconfig produces a correctly +named wheel holding host-architecture objects — which the wheel-tag check +alone cannot see. + +rules_pycross's cross coverage stops at "the wheel builds for both platforms" +(tests/e2e/shared/collect_wheels.bzl feeding a build target); this is the +assertion that makes that matrix meaningful. +""" + +import os +import struct +import sys +import zipfile + +# ELF e_machine values, from elf.h. +_EM_X86_64 = 0x3E +_EM_AARCH64 = 0xB7 + +_ARCH_TAGS = { + "x86_64": _EM_X86_64, + "aarch64": _EM_AARCH64, +} + + +def _wheel_arch(name: str) -> str: + for arch in _ARCH_TAGS: + if arch in name: + return arch + raise AssertionError("cannot determine architecture from wheel name {}".format(name)) + + +def _elf_machine(data: bytes) -> int: + assert data[:4] == b"\x7fELF", "not an ELF object" + little_endian = data[5] == 1 + return struct.unpack_from("H", data, 18)[0] + + +def _check(wheel_path: str) -> int: + name = os.path.basename(wheel_path) + arch = _wheel_arch(name) + expected_machine = _ARCH_TAGS[arch] + checked = 0 + + with zipfile.ZipFile(wheel_path) as zf: + for entry in zf.namelist(): + if not entry.endswith(".so"): + continue + machine = _elf_machine(zf.read(entry)) + if machine != expected_machine: + raise AssertionError( + "{}: {} is ELF machine {:#x}, expected {:#x} for '{}'".format( + name, entry, machine, expected_machine, arch + ) + ) + base = os.path.basename(entry) + if "." in base[:-3] and arch not in base: + raise AssertionError( + "{}: extension filename '{}' does not name architecture '{}'".format( + name, base, arch + ) + ) + print(" {} -> {} (ELF {:#x})".format(name, base, machine)) + checked += 1 + + return checked + + +def main() -> None: + wheel_dir = sys.argv[1] + wheels = sorted( + f + for f in os.listdir(wheel_dir) + if f.endswith(".whl") and "linux" in f and not f.endswith("-none-any.whl") + ) + assert wheels, "no Linux-tagged native wheels collected under {}".format(wheel_dir) + + total = 0 + for wheel in wheels: + total += _check(os.path.join(wheel_dir, wheel)) + + assert total, "collected native wheels contain no extension modules: {}".format(wheels) + + +if __name__ == "__main__": + main() diff --git a/e2e/cases/tools/check_wheel_tags.py b/e2e/cases/tools/check_wheel_tags.py new file mode 100644 index 000000000..a7dcfa00b --- /dev/null +++ b/e2e/cases/tools/check_wheel_tags.py @@ -0,0 +1,32 @@ +"""Assert that a collected-wheel directory contains every expected platform tag. + +Usage: check_wheel_tags.py ... + +Each expected tag is a substring that must appear in at least one wheel +filename. A cross build that leaked the exec platform into its wheel tag — +the failure `pep517_native_whl`'s `_validate_wheel_platform` guards against — +shows up here as a missing tag. +""" + +import os +import sys + + +def main() -> None: + wheel_dir, expected = sys.argv[1], sys.argv[2:] + names = sorted(f for f in os.listdir(wheel_dir) if f.endswith(".whl")) + assert names, "no wheels collected under {}".format(wheel_dir) + + print("collected wheels:") + for name in names: + print(" " + name) + + missing = [tag for tag in expected if not any(tag in name for name in names)] + if missing: + raise AssertionError( + "no collected wheel carries these tags: {}\ncollected: {}".format(missing, names) + ) + + +if __name__ == "__main__": + main() diff --git a/e2e/cases/tools/collect_wheels.bzl b/e2e/cases/tools/collect_wheels.bzl new file mode 100644 index 000000000..defa15f69 --- /dev/null +++ b/e2e/cases/tools/collect_wheels.bzl @@ -0,0 +1,69 @@ +"""Build source-built wheels for several target platforms and assert their tags. + +Ported from rules_pycross (tests/e2e/shared/collect_wheels.bzl), which builds +each wheel under a `platform_transition_filegroup` per target platform and +gathers the results into one directory. rules_pycross stops at "it builds"; +this version adds a runtime assertion over the collected wheel filenames so a +wheel tagged for the wrong platform fails the test instead of passing silently. +""" + +load("@aspect_rules_py//py:defs.bzl", "py_test") +load("@bazel_lib//lib:run_binary.bzl", "run_binary") +load("@bazel_lib//lib:transitions.bzl", "platform_transition_filegroup") + +def collect_wheels(name, wheels, platforms, expected_tags = None, **kwargs): + """Builds `wheels` once per entry in `platforms` and collects them. + + Args: + name: Name of the collected-directory target. + wheels: Wheel labels, e.g. `["@sdist_build__x__y__1_0//:whl"]`. + platforms: Platform labels to build each wheel for. + expected_tags: Substrings that must each appear in at least one + collected wheel filename. When set, declares `_tags_test`. + **kwargs: Forwarded to the collecting `run_binary`. + """ + + # Manual: only reachable through the platform transitions below, which are + # what supply the dep_group and libc flags the wheel targets select on. + native.filegroup( + name = name + "_wheels", + srcs = wheels, + tags = ["manual"], + ) + + all_srcs = [] + for platform in platforms: + transition_name = "_{}_{}".format(name, platform.split(":")[-1]) + platform_transition_filegroup( + name = transition_name, + srcs = [name + "_wheels"], + target_platform = platform, + ) + all_srcs.append(":" + transition_name) + + native.filegroup( + name = name + "_all_transitions", + srcs = all_srcs, + ) + + run_binary( + name = name, + srcs = [name + "_all_transitions"], + args = [ + "--out-dir", + "$(@D)", + "$(execpaths :{}_all_transitions)".format(name), + ], + out_dirs = [name], + tool = "//tools:collect_wheels_tool", + **kwargs + ) + + if expected_tags: + py_test( + name = name + "_tags_test", + srcs = ["//tools:check_wheel_tags.py"], + main = "//tools:check_wheel_tags.py", + args = ["$(rootpath :{})".format(name)] + expected_tags, + data = [name], + ) diff --git a/e2e/cases/tools/collect_wheels.py b/e2e/cases/tools/collect_wheels.py new file mode 100644 index 000000000..9dc094881 --- /dev/null +++ b/e2e/cases/tools/collect_wheels.py @@ -0,0 +1,46 @@ +"""Copy every .whl reachable from the given paths into one output directory. + +Ported from rules_pycross (tests/e2e/shared/collect_wheels.py). Inputs are a +mix of plain wheel files and tree artifacts holding wheels, because +pep517_whl/pep517_native_whl declare a directory output. +""" + +import argparse +import shutil +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--out-dir", required=True) + parser.add_argument("wheel", nargs="*", default=[]) + args = parser.parse_args() + + out_dir = Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + + for wheel_str in args.wheel: + for p in wheel_str.split(" "): + wheel_path = Path(p) + + if wheel_path.is_dir(): + for whl in wheel_path.glob("*.whl"): + real_path = whl.resolve() + target_path = out_dir / real_path.name + if not target_path.exists(): + shutil.copy2(real_path, target_path) + continue + + if not wheel_path.name.endswith(".whl"): + continue + + real_path = wheel_path.resolve() + target_path = out_dir / real_path.name + if target_path.exists(): + continue + + shutil.copy2(real_path, target_path) + + +if __name__ == "__main__": + main() From 87612be5d95143aa1d3f6bf261be3ace3660f688 Mon Sep 17 00:00:00 2001 From: xangcastle Date: Mon, 10 Aug 2026 18:37:49 -0600 Subject: [PATCH 2/3] test(e2e): scope pycross cases to the host sdist-build pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refinements over the original port so the suite runs green against the existing rules: - drop pycross-setuptools' cross matrix (native_wheels targets, per-case platforms, check_wheel_native.py): building a C extension for a non-host platform needs native_build_toolchain_type resolution for that platform, which lands with the cross-compilation branch. The pure-python collect_wheels matrix stays — anyarch builds exercise the exec-platform resolution without a native toolchain. - keep //tools:linux_x86_64, referenced by venv-namespace-collapse docs - require PYCROSS_TEST_PACKAGES in test_top_level_packages instead of falling back to packages this workspace doesn't ship - move design rationale from inline comments into module docstrings; strip the prose preamble from the post-install patch --- e2e/cases/pycross-distutils-probe/__test__.py | 13 ++- .../pycross-distutils-probe/pyproject.toml | 2 - .../setup.MODULE.bazel | 2 +- .../patches/setproctitle-postinstall.patch | 6 -- e2e/cases/pycross-patches/setup.MODULE.bazel | 11 ++- .../test_setproctitle_patched.py | 12 ++- e2e/cases/pycross-pure-python/BUILD.bazel | 8 +- e2e/cases/pycross-pure-python/pyproject.toml | 6 -- .../pycross-pure-python/setup.MODULE.bazel | 27 +++--- .../pycross-pure-python/test_filelock.py | 2 +- .../test_pyproject_metadata.py | 2 +- e2e/cases/pycross-pure-python/test_tomli.py | 2 +- .../test_top_level_packages.py | 28 +++--- e2e/cases/pycross-setuptools/BUILD.bazel | 74 +-------------- e2e/cases/pycross-setuptools/pyproject.toml | 2 - .../pycross-setuptools/setup.MODULE.bazel | 48 ++++++---- e2e/cases/pycross-setuptools/test_pyyaml.py | 17 ++-- .../pycross-setuptools/test_setproctitle.py | 10 +- .../pycross-setuptools/test_zstandard.py | 13 ++- e2e/cases/tools/BUILD.bazel | 1 - e2e/cases/tools/check_wheel_native.py | 92 ------------------- e2e/cases/tools/collect_wheels.bzl | 6 +- e2e/cases/tools/collect_wheels.py | 2 +- 23 files changed, 125 insertions(+), 261 deletions(-) delete mode 100644 e2e/cases/tools/check_wheel_native.py diff --git a/e2e/cases/pycross-distutils-probe/__test__.py b/e2e/cases/pycross-distutils-probe/__test__.py index c2c35940e..9504d667c 100644 --- a/e2e/cases/pycross-distutils-probe/__test__.py +++ b/e2e/cases/pycross-distutils-probe/__test__.py @@ -4,13 +4,16 @@ `build_distutils_probe_sdist.py`, `test_distutils_probe.py`). The sdist's `build_py` command shells out to a *fresh* interpreter and imports `distutils.util`; on Python 3.12+ that only resolves when setuptools' -`_distutils_hack` is reachable from the child. A build action that exported a -stale `PYTHONPATH`/`PYTHONHOME` — the case build_helper.py's -`_INHERITED_PYTHON_ENV` filter exists for — breaks the child, not the parent, -so the failure is invisible to a plain "does it build" check. +`_distutils_hack` is reachable from the child. A build action that leaks a +stale `PYTHONPATH`/`PYTHONHOME` — exactly what rule.bzl's +`_INHERITED_PYTHON_ENV` filter strips — poisons the child while the parent +backend keeps working, so the failure is invisible to a plain "does it +build" check. Structured like `uv-sdist-mpicc`: the sdist is assembled in-process and -build_helper.py is invoked directly, so nothing is fetched from the network. +build_helper.py runs as a subprocess with a minimal env (bare PATH, no +PYTHON* variables), so nothing is fetched from the network and nothing +leaks in from the test's own environment. """ import os diff --git a/e2e/cases/pycross-distutils-probe/pyproject.toml b/e2e/cases/pycross-distutils-probe/pyproject.toml index 2c635b7a9..e452dbd88 100644 --- a/e2e/cases/pycross-distutils-probe/pyproject.toml +++ b/e2e/cases/pycross-distutils-probe/pyproject.toml @@ -3,8 +3,6 @@ name = "pycross_distutils_probe" version = "0.0.0" requires-python = ">=3.11" dependencies = [ - # The test drives build_helper.py directly, which shells out to - # `python -m build` with setuptools as the backend. "build", "setuptools", "wheel", diff --git a/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel b/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel index 5cffad2d5..d017c53aa 100644 --- a/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel +++ b/e2e/cases/pycross-distutils-probe/setup.MODULE.bazel @@ -4,7 +4,7 @@ rules_pycross ships a synthetic sdist whose `build_py` command shells out to a fresh interpreter and imports `distutils.util`, guarding against a build environment that leaves the backend's child processes unable to find distutils (`SETUPTOOLS_USE_DISTUTILS`, a stale `PYTHONPATH`, or a stripped `PATH`). -build_helper.py filters `PYTHONHOME`/`PYTHONPATH`/`PYTHONPLATLIBDIR` out of the +rule.bzl filters `PYTHONHOME`/`PYTHONPATH`/`PYTHONPLATLIBDIR` out of the action env for exactly this reason, so the probe is worth keeping. Driven the same way as `uv-sdist-mpicc`: the test invokes build_helper.py diff --git a/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch b/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch index e242ee4ae..62714ffa8 100644 --- a/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch +++ b/e2e/cases/pycross-patches/patches/setproctitle-postinstall.patch @@ -1,9 +1,3 @@ -Ported from rules_pycross tests/e2e/patches_and_hooks. Retargeted at the -installed venv layout: rules_py applies post-install patches with `patch -d` -rooted at the install tree, so paths are `lib/python/site-packages/...` -rather than the wheel-relative paths rules_pycross uses. The `PATCHED = True` -context line comes from the pre-build patch, so this hunk only applies when -the two phases ran in order. --- a/lib/python3.12/site-packages/setproctitle/__init__.py +++ b/lib/python3.12/site-packages/setproctitle/__init__.py @@ -56,3 +56,4 @@ diff --git a/e2e/cases/pycross-patches/setup.MODULE.bazel b/e2e/cases/pycross-patches/setup.MODULE.bazel index 62b442065..018c5151c 100644 --- a/e2e/cases/pycross-patches/setup.MODULE.bazel +++ b/e2e/cases/pycross-patches/setup.MODULE.bazel @@ -3,8 +3,15 @@ setproctitle is forced to build from its sdist with both patch phases applied: a pre-build patch that lands in the source tree before the PEP 517 backend runs, and a post-install patch that rewrites the already-unpacked wheel. The -two patches stack — the post-install one applies against the pre-build -patch's output — so an ordering regression fails the runtime test. +two patches stack — the post-install hunk carries the pre-build patch's +`PATCHED = True` line as context, so it only applies when the phases ran in +order and an ordering regression fails the build itself, not just the +runtime test. + +The post-install patch is written against the installed venv layout +(`lib/python/site-packages/...`): rules_py applies post-install patches +with `patch -d` rooted at the install tree, unlike rules_pycross's +wheel-relative paths. Uses its own hub: the patched setproctitle must not be shared with `pycross-setuptools`, which builds the same version unpatched. diff --git a/e2e/cases/pycross-patches/test_setproctitle_patched.py b/e2e/cases/pycross-patches/test_setproctitle_patched.py index a32362199..c05a2152b 100644 --- a/e2e/cases/pycross-patches/test_setproctitle_patched.py +++ b/e2e/cases/pycross-patches/test_setproctitle_patched.py @@ -1,12 +1,18 @@ -# Ported from rules_pycross tests/e2e/patches_and_hooks/tests/test_setproctitle.py. -# `SITE_HOOK_RAN` is dropped: rules_py has no `site_hooks` equivalent. +"""Assert both patch phases reached the installed setproctitle. + +Ported from rules_pycross tests/e2e/patches_and_hooks/tests/test_setproctitle.py. +`PATCHED` comes from the pre-build patch, `POST_PATCHED` from the post-install +patch. The upstream `SITE_HOOK_RAN` assertion is dropped: rules_py has no +`site_hooks` equivalent. +""" + import unittest import setproctitle class TestSetproctitle(unittest.TestCase): - def test_import(self): + def test_import(self) -> None: title = setproctitle.getproctitle() self.assertIsNotNone(title) self.assertTrue(setproctitle.PATCHED) diff --git a/e2e/cases/pycross-pure-python/BUILD.bazel b/e2e/cases/pycross-pure-python/BUILD.bazel index 71ca35f5c..868bfe668 100644 --- a/e2e/cases/pycross-pure-python/BUILD.bazel +++ b/e2e/cases/pycross-pure-python/BUILD.bazel @@ -85,10 +85,10 @@ py_test( ) # An anyarch build uses no CC toolchain, so what differs per platform here is -# the configuration the build *tool* is resolved in: `pep517_whl`'s -# `exec_transition` has to pin the frontend and its wheels to the exec -# platform. If it leaked the target configuration, these builds would try to -# run a Linux interpreter on the exec host. +# the configuration the build *tool* is resolved in: the PEP 517 frontend and +# its wheels must resolve for the exec platform. If the target configuration +# leaked into them, these builds would try to run a non-host interpreter on +# the exec host. collect_wheels( name = "pure_python_wheels", expected_tags = ["-none-any.whl"], diff --git a/e2e/cases/pycross-pure-python/pyproject.toml b/e2e/cases/pycross-pure-python/pyproject.toml index 075a72500..4681134ee 100644 --- a/e2e/cases/pycross-pure-python/pyproject.toml +++ b/e2e/cases/pycross-pure-python/pyproject.toml @@ -5,9 +5,6 @@ requires-python = ">=3.11" dependencies = [ "build", "setuptools", - # Build backends the three sdists below declare in [build-system]. - # The configure tool resolves them out of this lock, so they have to be - # reachable from the project's dependency graph. "flit-core", "hatchling", "hatch-vcs", @@ -16,9 +13,6 @@ dependencies = [ "tomli==2.2.1", ] -# All three ship `-none-any` wheels; forcing the source build exercises -# pep517_whl's `--validate-anyarch` path for three different PEP 517 -# backends (hatchling, flit-core). [tool.uv] no-binary-package = [ "filelock", diff --git a/e2e/cases/pycross-pure-python/setup.MODULE.bazel b/e2e/cases/pycross-pure-python/setup.MODULE.bazel index cffc227a6..eb9350165 100644 --- a/e2e/cases/pycross-pure-python/setup.MODULE.bazel +++ b/e2e/cases/pycross-pure-python/setup.MODULE.bazel @@ -1,14 +1,21 @@ """Port of rules_pycross's `tests/e2e/build_pure_python` workspace. -Three pure-Python packages are forced to build from their sdists so the -`pep517_whl` (anyarch) path runs against two different PEP 517 backends — -hatchling (filelock) and flit-core (tomli, pyproject-metadata). Each package -still has to import from site-packages afterwards. +filelock, tomli and pyproject-metadata are pinned as `no-binary-package`, so +the anyarch `pep517_whl` path builds each sdist with a PEP 517 backend other +than setuptools — hatchling for filelock, flit-core for tomli and +pyproject-metadata — and every resulting wheel must import from +site-packages at runtime. -The `sdist_build` repos are exposed so BUILD.bazel can rebuild the same wheels -under non-host target platforms: an anyarch build carries no CC toolchain, so -what that matrix exercises is `pep517_whl`'s exec transition — build tooling -(`build`, the backend) must resolve for the exec platform even when the +The backends are named in `default_build_dependencies` so they resolve +through the project repo's `py_library` targets, which carry their transitive +closure (hatchling alone needs pathspec, pluggy, trove-classifiers, …); the +configure tool's `[build-system] requires` resolution would yield bare +`whl_install` targets instead. + +The `sdist_build` repos are exposed so BUILD.bazel can rebuild the same +wheels under non-host target platforms. An anyarch build uses no CC +toolchain, so that matrix isolates a single property: the build tooling +(frontend and backend) must resolve for the exec platform even when the configuration targets another one. """ @@ -18,10 +25,6 @@ uv.project( default_build_dependencies = [ "build", "setuptools", - # The backends have to come through the project repo, not through the - # configure tool's `[build-system] requires` resolution: that path - # yields bare `whl_install` targets, and hatchling needs its own - # transitive closure (pathspec, pluggy, trove-classifiers, …). "flit-core", "hatch-vcs", "hatchling", diff --git a/e2e/cases/pycross-pure-python/test_filelock.py b/e2e/cases/pycross-pure-python/test_filelock.py index cffa86ad3..388192caa 100644 --- a/e2e/cases/pycross-pure-python/test_filelock.py +++ b/e2e/cases/pycross-pure-python/test_filelock.py @@ -4,7 +4,7 @@ class TestFileLock(unittest.TestCase): - def test_import(self): + def test_import(self) -> None: lock = filelock.FileLock("dummy.lock") self.assertIsNotNone(lock) diff --git a/e2e/cases/pycross-pure-python/test_pyproject_metadata.py b/e2e/cases/pycross-pure-python/test_pyproject_metadata.py index 739d34ff7..c388bc95f 100644 --- a/e2e/cases/pycross-pure-python/test_pyproject_metadata.py +++ b/e2e/cases/pycross-pure-python/test_pyproject_metadata.py @@ -4,7 +4,7 @@ class TestPyprojectMetadata(unittest.TestCase): - def test_import(self): + def test_import(self) -> None: self.assertIsNotNone(pyproject_metadata.__version__) diff --git a/e2e/cases/pycross-pure-python/test_tomli.py b/e2e/cases/pycross-pure-python/test_tomli.py index 48bdea076..7d48c36ed 100644 --- a/e2e/cases/pycross-pure-python/test_tomli.py +++ b/e2e/cases/pycross-pure-python/test_tomli.py @@ -4,7 +4,7 @@ class TestTomli(unittest.TestCase): - def test_parse(self): + def test_parse(self) -> None: d = tomli.loads("a = 1") self.assertEqual(d["a"], 1) diff --git a/e2e/cases/pycross-pure-python/test_top_level_packages.py b/e2e/cases/pycross-pure-python/test_top_level_packages.py index d77fc3d65..f26573a1c 100644 --- a/e2e/cases/pycross-pure-python/test_top_level_packages.py +++ b/e2e/cases/pycross-pure-python/test_top_level_packages.py @@ -1,11 +1,10 @@ -"""Verify that installed wheels have their top-level packages in site-packages. +"""Assert that source-built wheels land their top-level packages in site-packages. -This test works in two modes: -1. If PYCROSS_TEST_PACKAGES env var is set, it tests those packages (comma-separated). -2. Otherwise, it tests hardcoded packages (regex, IPython) for - the requirements e2e workspace. - -Packages are tested for importability and correct site-packages placement. +Ported from rules_pycross's shared top-level-packages check. The packages to +verify come from the comma-separated `PYCROSS_TEST_PACKAGES` env var, set by +the consuming py_test. Three invariants per package: it imports, its module +spec resolves inside site-packages rather than a leaked source tree or +`PYTHONPATH` entry, and no dist-info directory ends up on `sys.path`. """ import importlib @@ -16,20 +15,16 @@ class TopLevelPackagesTest(unittest.TestCase): - """Check that top_level_paths from wheel inspection actually show up.""" - - def _get_packages(self): - return [p.strip() for p in os.environ.get("PYCROSS_TEST_PACKAGES", "regex,IPython").split(",") if p.strip()] + def _get_packages(self) -> list[str]: + return [p.strip() for p in os.environ["PYCROSS_TEST_PACKAGES"].split(",") if p.strip()] - def test_packages_importable(self): - """All expected packages should be importable.""" + def test_packages_importable(self) -> None: for pkg in self._get_packages(): with self.subTest(package=pkg): mod = importlib.import_module(pkg) self.assertIsNotNone(mod, f"{pkg} imported as None") - def test_packages_resolve_to_site_packages(self): - """All expected packages should resolve to files within site-packages.""" + def test_packages_resolve_to_site_packages(self) -> None: for pkg in self._get_packages(): with self.subTest(package=pkg): spec = importlib.util.find_spec(pkg) @@ -41,8 +36,7 @@ def test_packages_resolve_to_site_packages(self): f"{pkg} origin not in site-packages: {spec.origin}", ) - def test_no_dist_info_on_sys_path(self): - """dist-info directories should not be added as sys.path entries.""" + def test_no_dist_info_on_sys_path(self) -> None: for path in sys.path: basename = os.path.basename(path) self.assertFalse( diff --git a/e2e/cases/pycross-setuptools/BUILD.bazel b/e2e/cases/pycross-setuptools/BUILD.bazel index df7c2a63b..e2c54e19c 100644 --- a/e2e/cases/pycross-setuptools/BUILD.bazel +++ b/e2e/cases/pycross-setuptools/BUILD.bazel @@ -1,11 +1,9 @@ # Ported from rules_pycross tests/e2e/build_setuptools. # -# Three setuptools C-extension sdists built by `pep517_native_whl`, imported at -# runtime for the host, then rebuilt through rules_pycross's `collect_wheels` -# matrix for two non-host Linux platforms — the cross path this branch adds. +# Three setuptools C-extension sdists built by `pep517_native_whl` and +# imported at runtime for the host. load("@aspect_rules_py//py:defs.bzl", "py_test") -load("//tools:collect_wheels.bzl", "collect_wheels") exports_files(["require_extension.patch"]) @@ -13,36 +11,6 @@ _PY = "3.12" _DEP_GROUP = "pycross_setuptools" -# The libc/version flags are what the wheel resolver keys platform-specific -# wheel selection off; a bare constraint_values platform would leave build -# dependencies unresolvable. Same shape as //uv-deps-650/crossbuild. The -# dep_group flag rides along because the wheel targets under `collect_wheels` -# are reached through a platform transition, not through a py_venv. -_PLATFORM_FLAGS = [ - "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", - "--@aspect_rules_py//uv/private/constraints/platform:platform_version=2.39", - "--@aspect_rules_py//uv/private/constraints/dep_group:dep_group=" + _DEP_GROUP, - "--@aspect_rules_py//py/private/interpreter:python_version=" + _PY, -] - -platform( - name = "amd64_linux", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - flags = _PLATFORM_FLAGS, -) - -platform( - name = "arm64_linux", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - ], - flags = _PLATFORM_FLAGS, -) - py_test( name = "test_setproctitle", srcs = ["test_setproctitle.py"], @@ -69,41 +37,3 @@ py_test( python_version = _PY, deps = ["@pypi_pycross_setuptools//pyyaml"], ) - -# rules_pycross's cross matrix, restricted to zstandard. -# -# pyyaml is excluded because `PYYAML_FORCE_LIBYAML=0` makes it anyarch — there -# is nothing platform-specific left to assert. -# -# setproctitle is excluded because it cannot cross-build here at all: its -# setup.py picks sources and macros off `sys.platform`, which reports the -# *exec* interpreter's platform. Cross mode overrides `_PYTHON_HOST_PLATFORM` -# and `_PYTHON_SYSCONFIGDATA_NAME`, neither of which moves `sys.platform`, so a -# darwin exec host compiles `src/darwin_set_process_name.c` with -# `-D__darwin__=1` against a linux target and fails on -# `CoreFoundation/CoreFoundation.h`. rules_pycross handles this with a real -# crossenv (`pycross/private/build/tools/crossenv/`) that patches `sys.platform` -# in the build interpreter. `require_extension.patch` keeps that failure loud -# rather than letting setproctitle fall back to an anyarch wheel. -collect_wheels( - name = "native_wheels", - expected_tags = [ - "linux_x86_64", - "linux_aarch64", - ], - platforms = [ - ":amd64_linux", - ":arm64_linux", - ], - wheels = ["@sdist_build__pycross_setuptools__zstandard__0_22_0//:whl"], -) - -py_test( - name = "native_wheels_elf_test", - srcs = ["//tools:check_wheel_native.py"], - args = ["$(rootpath :native_wheels)"], - data = [":native_wheels"], - dep_group = _DEP_GROUP, - main = "//tools:check_wheel_native.py", - python_version = _PY, -) diff --git a/e2e/cases/pycross-setuptools/pyproject.toml b/e2e/cases/pycross-setuptools/pyproject.toml index c4e3838f3..292a3927f 100644 --- a/e2e/cases/pycross-setuptools/pyproject.toml +++ b/e2e/cases/pycross-setuptools/pyproject.toml @@ -6,9 +6,7 @@ dependencies = [ "build", "setuptools", "wheel", - # zstandard's setup.py builds a cffi backend when cffi is importable. "cffi", - # PyYAML 6.0.x cannot be cythonized by Cython 3. "cython<3.0", "pyyaml==6.0.1", "setproctitle==1.3.2", diff --git a/e2e/cases/pycross-setuptools/setup.MODULE.bazel b/e2e/cases/pycross-setuptools/setup.MODULE.bazel index 012261ca9..6a4ba7ee6 100644 --- a/e2e/cases/pycross-setuptools/setup.MODULE.bazel +++ b/e2e/cases/pycross-setuptools/setup.MODULE.bazel @@ -1,14 +1,36 @@ """Port of rules_pycross's `tests/e2e/build_setuptools` workspace. Three setuptools-backed C-extension packages are forced to build from their -sdists: PyYAML (Cython-generated extension), setproctitle (plain C) and -zstandard (vendored libzstd). Together they cover the `pep517_native_whl` -path for a Cython pipeline, a trivial extension and a large vendored-C build. +sdists (`[tool.uv] no-binary-package` in pyproject.toml), covering the +`pep517_native_whl` path for three distinct build shapes: -`env` on PyYAML ports rules_pycross's per-package `build_env` override -(`setuptools.override(name = "pyyaml", build_env = ...)`), and is observable -at runtime: with `PYYAML_FORCE_LIBYAML=0` the built wheel must not carry the -libyaml-backed `CLoader`. +- PyYAML 6.0.1: Cython-generated extension (sdist → cythonize → C → .so). + The lock pins `cython<3.0` because PyYAML 6.0.x cannot be cythonized by + Cython 3. +- setproctitle 1.3.2: a plain C extension, the minimal native case. +- zstandard 0.22.0: a large vendored-libzstd build, plus an optional cffi + backend its setup.py compiles whenever cffi is importable at build time. + +Each override exercises a different `uv.override_package` feature: + +- `env` on PyYAML ports rules_pycross's per-package `build_env` override + (`setuptools.override(name = "pyyaml", build_env = ...)`) and is observable + at runtime: with `PYYAML_FORCE_LIBYAML=0` the built wheel must not carry + the libyaml-backed `CLoader` (asserted by test_pyyaml.py). +- `pre_build_patches` on setproctitle removes its silent fallback to a pure + `py3-none-any` wheel when the extension fails to compile; see + require_extension.patch for why the fallback would mask a broken build. +- `resource_set` on zstandard declares the vendored-C compile's real memory + footprint so Bazel does not over-parallelize it. + +`default_build_dependencies` names cffi explicitly: zstandard's cffi backend +imports pycparser through cffi, and only the project repo's py_library carries +that transitive closure — the configure tool's `[build-system] requires` +resolution would yield a bare `whl_install` target without it. + +No cross-platform `collect_wheels` matrix here (unlike pycross-pure-python): +rebuilding these wheels for non-host targets would require a cross CC +toolchain this workspace does not set up. Not ported: rules_pycross builds psycopg2 with a `pg_config` tool dep and zstandard against a Bazel-built system libzstd. Both need `rules_foreign_cc` @@ -23,10 +45,6 @@ uv.project( "build", "setuptools", "wheel", - # zstandard's cffi backend imports pycparser through cffi. Naming cffi - # here routes it through the project repo's py_library, which carries - # that transitive closure; the configure tool's `[build-system] - # requires` resolution yields a bare `whl_install` target instead. "cffi", ], hub_name = "pypi_pycross_setuptools", @@ -49,10 +67,4 @@ uv.override_package( lock = "//pycross-setuptools:uv.lock", resource_set = "mem_4g", ) -use_repo( - uv, - "pypi_pycross_setuptools", - "sdist_build__pycross_setuptools__pyyaml__6_0_1", - "sdist_build__pycross_setuptools__setproctitle__1_3_2", - "sdist_build__pycross_setuptools__zstandard__0_22_0", -) +use_repo(uv, "pypi_pycross_setuptools") diff --git a/e2e/cases/pycross-setuptools/test_pyyaml.py b/e2e/cases/pycross-setuptools/test_pyyaml.py index 1853f0771..b6460a4e1 100644 --- a/e2e/cases/pycross-setuptools/test_pyyaml.py +++ b/e2e/cases/pycross-setuptools/test_pyyaml.py @@ -1,12 +1,17 @@ -# Ported from rules_pycross tests/e2e/build_setuptools/tests/test_pyyaml.py. -# The `CLoader` assertion is added: rules_pycross's version tolerates either -# backend, which leaves its `build_env` override untested. The build sets -# PYYAML_FORCE_LIBYAML=0, so the source build must have produced the pure-Python -# backend — if the override never reached the build action, CLoader is present. +"""Assert PyYAML built from sdist without the libyaml `CLoader` backend. + +Ported from rules_pycross tests/e2e/build_setuptools/tests/test_pyyaml.py, +with the `CLoader` assertion added: rules_pycross's version tolerates either +backend, which leaves its `build_env` override untested. The build sets +`PYYAML_FORCE_LIBYAML=0` via `uv.override_package(env = ...)`, so the source +build must have produced the pure-Python backend — if the override never +reached the build action, `CLoader` is importable and this test fails. +""" + import yaml -def test_pyyaml_import(): +def test_pyyaml_import() -> None: try: from yaml import CLoader as Loader except ImportError: diff --git a/e2e/cases/pycross-setuptools/test_setproctitle.py b/e2e/cases/pycross-setuptools/test_setproctitle.py index 44541f3a7..cb2cef063 100644 --- a/e2e/cases/pycross-setuptools/test_setproctitle.py +++ b/e2e/cases/pycross-setuptools/test_setproctitle.py @@ -1,10 +1,18 @@ +"""Smoke test for setproctitle built from its sdist. + +Ported from rules_pycross tests/e2e/build_setuptools/tests/test_setproctitle.py. +require_extension.patch makes the C extension mandatory, so a passing import +here means the native build ran — the upstream pure-Python fallback cannot +have papered over a compile failure. +""" + import unittest import setproctitle class TestSetproctitle(unittest.TestCase): - def test_import(self): + def test_import(self) -> None: title = setproctitle.getproctitle() self.assertIsNotNone(title) diff --git a/e2e/cases/pycross-setuptools/test_zstandard.py b/e2e/cases/pycross-setuptools/test_zstandard.py index c944a74d9..691192062 100644 --- a/e2e/cases/pycross-setuptools/test_zstandard.py +++ b/e2e/cases/pycross-setuptools/test_zstandard.py @@ -1,3 +1,10 @@ +"""Round-trip compress/decompress through zstandard built from its sdist. + +Ported from rules_pycross tests/e2e/build_setuptools/tests/test_zstandard.py. +A successful round trip proves the extension compiled against the vendored +libzstd actually works, not merely that the module imports. +""" + import unittest import zstandard @@ -28,17 +35,13 @@ class TestZstandard(unittest.TestCase): - def test_roundtrip(self): + def test_roundtrip(self) -> None: cctx = zstandard.ZstdCompressor() compressed = cctx.compress(ZEN) - - # Make sure we actually compressed assert len(compressed) < len(ZEN) dctx = zstandard.ZstdDecompressor() decompressed = dctx.decompress(compressed) - - # Check the round trip assert decompressed == ZEN diff --git a/e2e/cases/tools/BUILD.bazel b/e2e/cases/tools/BUILD.bazel index 65184ccbf..90a9266a8 100644 --- a/e2e/cases/tools/BUILD.bazel +++ b/e2e/cases/tools/BUILD.bazel @@ -7,7 +7,6 @@ load("@aspect_rules_py//py:defs.bzl", "py_binary") exports_files([ "assert_absent.py", "assert_ownership.py", - "check_wheel_native.py", "check_wheel_tags.py", ]) diff --git a/e2e/cases/tools/check_wheel_native.py b/e2e/cases/tools/check_wheel_native.py deleted file mode 100644 index 0bc1211d5..000000000 --- a/e2e/cases/tools/check_wheel_native.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Assert cross-built wheels really contain extensions for the tagged platform. - -Usage: check_wheel_native.py - -For every Linux-tagged wheel in the directory, every extension module inside it -must be an ELF object whose `e_machine` matches the wheel's architecture tag, -and whose filename ABI tag names the same architecture. A cross build that -fell back to the exec platform's compiler or sysconfig produces a correctly -named wheel holding host-architecture objects — which the wheel-tag check -alone cannot see. - -rules_pycross's cross coverage stops at "the wheel builds for both platforms" -(tests/e2e/shared/collect_wheels.bzl feeding a build target); this is the -assertion that makes that matrix meaningful. -""" - -import os -import struct -import sys -import zipfile - -# ELF e_machine values, from elf.h. -_EM_X86_64 = 0x3E -_EM_AARCH64 = 0xB7 - -_ARCH_TAGS = { - "x86_64": _EM_X86_64, - "aarch64": _EM_AARCH64, -} - - -def _wheel_arch(name: str) -> str: - for arch in _ARCH_TAGS: - if arch in name: - return arch - raise AssertionError("cannot determine architecture from wheel name {}".format(name)) - - -def _elf_machine(data: bytes) -> int: - assert data[:4] == b"\x7fELF", "not an ELF object" - little_endian = data[5] == 1 - return struct.unpack_from("H", data, 18)[0] - - -def _check(wheel_path: str) -> int: - name = os.path.basename(wheel_path) - arch = _wheel_arch(name) - expected_machine = _ARCH_TAGS[arch] - checked = 0 - - with zipfile.ZipFile(wheel_path) as zf: - for entry in zf.namelist(): - if not entry.endswith(".so"): - continue - machine = _elf_machine(zf.read(entry)) - if machine != expected_machine: - raise AssertionError( - "{}: {} is ELF machine {:#x}, expected {:#x} for '{}'".format( - name, entry, machine, expected_machine, arch - ) - ) - base = os.path.basename(entry) - if "." in base[:-3] and arch not in base: - raise AssertionError( - "{}: extension filename '{}' does not name architecture '{}'".format( - name, base, arch - ) - ) - print(" {} -> {} (ELF {:#x})".format(name, base, machine)) - checked += 1 - - return checked - - -def main() -> None: - wheel_dir = sys.argv[1] - wheels = sorted( - f - for f in os.listdir(wheel_dir) - if f.endswith(".whl") and "linux" in f and not f.endswith("-none-any.whl") - ) - assert wheels, "no Linux-tagged native wheels collected under {}".format(wheel_dir) - - total = 0 - for wheel in wheels: - total += _check(os.path.join(wheel_dir, wheel)) - - assert total, "collected native wheels contain no extension modules: {}".format(wheels) - - -if __name__ == "__main__": - main() diff --git a/e2e/cases/tools/collect_wheels.bzl b/e2e/cases/tools/collect_wheels.bzl index defa15f69..58749f1bb 100644 --- a/e2e/cases/tools/collect_wheels.bzl +++ b/e2e/cases/tools/collect_wheels.bzl @@ -21,10 +21,12 @@ def collect_wheels(name, wheels, platforms, expected_tags = None, **kwargs): expected_tags: Substrings that must each appear in at least one collected wheel filename. When set, declares `_tags_test`. **kwargs: Forwarded to the collecting `run_binary`. + + The inner `_wheels` filegroup is tagged manual: it is only reachable + through the platform transitions declared here, which are what supply the + dep_group and libc flags the wheel targets select on. """ - # Manual: only reachable through the platform transitions below, which are - # what supply the dep_group and libc flags the wheel targets select on. native.filegroup( name = name + "_wheels", srcs = wheels, diff --git a/e2e/cases/tools/collect_wheels.py b/e2e/cases/tools/collect_wheels.py index 9dc094881..7674a9bca 100644 --- a/e2e/cases/tools/collect_wheels.py +++ b/e2e/cases/tools/collect_wheels.py @@ -10,7 +10,7 @@ from pathlib import Path -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--out-dir", required=True) parser.add_argument("wheel", nargs="*", default=[]) From 86e0368d224187b456a51c659f31cdb002367edf Mon Sep 17 00:00:00 2001 From: xangcastle Date: Mon, 10 Aug 2026 21:09:38 -0600 Subject: [PATCH 3/3] test(e2e): drop dead paths from collect_wheels expected_tags is now required: every caller asserts tags, and a collect_wheels without the tags test would reintroduce exactly the silent wrong-platform pass the macro exists to prevent. The unused **kwargs forwarding goes with it. collect_wheels.py keeps only the tree-artifact branch: wheels always arrive as pep517_whl/pep517_native_whl directory outputs, so the plain-file handling inherited from rules_pycross never ran. --- e2e/cases/tools/collect_wheels.bzl | 21 ++++++++----------- e2e/cases/tools/collect_wheels.py | 33 +++++++++--------------------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/e2e/cases/tools/collect_wheels.bzl b/e2e/cases/tools/collect_wheels.bzl index 58749f1bb..53d19df76 100644 --- a/e2e/cases/tools/collect_wheels.bzl +++ b/e2e/cases/tools/collect_wheels.bzl @@ -11,7 +11,7 @@ load("@aspect_rules_py//py:defs.bzl", "py_test") load("@bazel_lib//lib:run_binary.bzl", "run_binary") load("@bazel_lib//lib:transitions.bzl", "platform_transition_filegroup") -def collect_wheels(name, wheels, platforms, expected_tags = None, **kwargs): +def collect_wheels(name, wheels, platforms, expected_tags): """Builds `wheels` once per entry in `platforms` and collects them. Args: @@ -19,8 +19,7 @@ def collect_wheels(name, wheels, platforms, expected_tags = None, **kwargs): wheels: Wheel labels, e.g. `["@sdist_build__x__y__1_0//:whl"]`. platforms: Platform labels to build each wheel for. expected_tags: Substrings that must each appear in at least one - collected wheel filename. When set, declares `_tags_test`. - **kwargs: Forwarded to the collecting `run_binary`. + collected wheel filename, asserted by `_tags_test`. The inner `_wheels` filegroup is tagged manual: it is only reachable through the platform transitions declared here, which are what supply the @@ -58,14 +57,12 @@ def collect_wheels(name, wheels, platforms, expected_tags = None, **kwargs): ], out_dirs = [name], tool = "//tools:collect_wheels_tool", - **kwargs ) - if expected_tags: - py_test( - name = name + "_tags_test", - srcs = ["//tools:check_wheel_tags.py"], - main = "//tools:check_wheel_tags.py", - args = ["$(rootpath :{})".format(name)] + expected_tags, - data = [name], - ) + py_test( + name = name + "_tags_test", + srcs = ["//tools:check_wheel_tags.py"], + main = "//tools:check_wheel_tags.py", + args = ["$(rootpath :{})".format(name)] + expected_tags, + data = [name], + ) diff --git a/e2e/cases/tools/collect_wheels.py b/e2e/cases/tools/collect_wheels.py index 7674a9bca..a834aa256 100644 --- a/e2e/cases/tools/collect_wheels.py +++ b/e2e/cases/tools/collect_wheels.py @@ -1,8 +1,9 @@ """Copy every .whl reachable from the given paths into one output directory. -Ported from rules_pycross (tests/e2e/shared/collect_wheels.py). Inputs are a -mix of plain wheel files and tree artifacts holding wheels, because -pep517_whl/pep517_native_whl declare a directory output. +Ported from rules_pycross (tests/e2e/shared/collect_wheels.py). Inputs are +tree artifacts holding wheels — pep517_whl/pep517_native_whl declare a +directory output. The same anyarch wheel can arrive through more than one +platform transition, so first copy wins. """ import argparse @@ -13,7 +14,7 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--out-dir", required=True) - parser.add_argument("wheel", nargs="*", default=[]) + parser.add_argument("wheel", nargs="*") args = parser.parse_args() out_dir = Path(args.out_dir) @@ -21,25 +22,11 @@ def main() -> None: for wheel_str in args.wheel: for p in wheel_str.split(" "): - wheel_path = Path(p) - - if wheel_path.is_dir(): - for whl in wheel_path.glob("*.whl"): - real_path = whl.resolve() - target_path = out_dir / real_path.name - if not target_path.exists(): - shutil.copy2(real_path, target_path) - continue - - if not wheel_path.name.endswith(".whl"): - continue - - real_path = wheel_path.resolve() - target_path = out_dir / real_path.name - if target_path.exists(): - continue - - shutil.copy2(real_path, target_path) + for whl in Path(p).glob("*.whl"): + real_path = whl.resolve() + target_path = out_dir / real_path.name + if not target_path.exists(): + shutil.copy2(real_path, target_path) if __name__ == "__main__":