From e1ee424f7ca5624c0233d3e58b2fde6a30775b39 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Mon, 8 Sep 2025 15:41:58 +0100 Subject: [PATCH 1/4] Don't constrain build-system.requires with dependency-versions --- cibuildwheel/platforms/ios.py | 3 --- cibuildwheel/platforms/macos.py | 6 +----- cibuildwheel/platforms/pyodide.py | 4 +--- cibuildwheel/platforms/windows.py | 5 +---- cibuildwheel/util/packaging.py | 29 +---------------------------- 5 files changed, 4 insertions(+), 43 deletions(-) diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index eba04c35d..4a3a0e775 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -33,7 +33,6 @@ ) from ..util.helpers import prepare_command, unwrap_preserving_paragraphs from ..util.packaging import ( - combine_constraints, find_compatible_wheel, get_pip_version, ) @@ -491,8 +490,6 @@ def build(options: Options, tmp_path: Path) -> None: build_env = env.copy() build_env["VIRTUALENV_PIP"] = pip_version - if constraints_path: - combine_constraints(build_env, constraints_path, None) match build_frontend.name: case "pip": diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 05d5d1623..292f58808 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -32,7 +32,7 @@ move_file, ) from ..util.helpers import prepare_command, unwrap -from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version +from ..util.packaging import find_compatible_wheel, get_pip_version from ..venv import constraint_flags, find_uv, virtualenv @@ -463,10 +463,6 @@ def build(options: Options, tmp_path: Path) -> None: build_env = env.copy() if pip_version is not None: build_env["VIRTUALENV_PIP"] = pip_version - if constraints_path: - combine_constraints( - build_env, constraints_path, identifier_tmp_dir if use_uv else None - ) match build_frontend.name: case "pip": diff --git a/cibuildwheel/platforms/pyodide.py b/cibuildwheel/platforms/pyodide.py index 60d707078..47cce9677 100644 --- a/cibuildwheel/platforms/pyodide.py +++ b/cibuildwheel/platforms/pyodide.py @@ -32,7 +32,7 @@ move_file, ) from ..util.helpers import prepare_command, unwrap, unwrap_preserving_paragraphs -from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version +from ..util.packaging import find_compatible_wheel, get_pip_version from ..util.python_build_standalone import ( PythonBuildStandaloneError, create_python_build_standalone_environment, @@ -420,8 +420,6 @@ def build(options: Options, tmp_path: Path) -> None: ) build_env = env.copy() - if constraints_path: - combine_constraints(build_env, constraints_path, identifier_tmp_dir) build_env["VIRTUALENV_PIP"] = pip_version call( "pyodide", diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index f9c85be89..692733a4a 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -23,7 +23,7 @@ from ..util.cmd import call, shell from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file from ..util.helpers import prepare_command, unwrap -from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version +from ..util.packaging import find_compatible_wheel, get_pip_version from ..venv import constraint_flags, find_uv, virtualenv @@ -465,9 +465,6 @@ def build(options: Options, tmp_path: Path) -> None: if pip_version is not None: build_env["VIRTUALENV_PIP"] = pip_version - if constraints_path: - combine_constraints(build_env, constraints_path, identifier_tmp_dir) - match build_frontend.name: case "pip": # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org diff --git a/cibuildwheel/util/packaging.py b/cibuildwheel/util/packaging.py index 578f82e44..c3d8c21dd 100644 --- a/cibuildwheel/util/packaging.py +++ b/cibuildwheel/util/packaging.py @@ -1,5 +1,5 @@ import shlex -from collections.abc import Mapping, MutableMapping, Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass, field from pathlib import Path, PurePath from typing import Any, Literal, Self, TypeVar @@ -178,30 +178,3 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None: return wheel return None - - -def combine_constraints( - env: MutableMapping[str, str], /, constraints_path: Path, tmp_dir: Path | None -) -> None: - """ - This will workaround a bug in pip<=21.1.1 or uv<=0.2.0 if a tmp_dir is given. - If set to None, this will use the modern URI method. - """ - - if tmp_dir: - if " " in str(constraints_path): - assert " " not in str(tmp_dir) - tmp_file = tmp_dir / "constraints.txt" - tmp_file.write_bytes(constraints_path.read_bytes()) - constraints_path = tmp_file - our_constraints = str(constraints_path) - else: - our_constraints = ( - constraints_path.as_uri() if " " in str(constraints_path) else str(constraints_path) - ) - - user_constraints = env.get("PIP_CONSTRAINT") - - env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join( - c for c in [our_constraints, user_constraints] if c - ) From 476a6221c56833c719c7056420495e82d813cdb0 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 20 Sep 2025 23:26:09 +0100 Subject: [PATCH 2/4] Remove setting of VIRTUALENV_PIP (it doesn't appear to have any effect) --- cibuildwheel/platforms/ios.py | 13 +++---------- cibuildwheel/platforms/macos.py | 2 -- cibuildwheel/platforms/pyodide.py | 4 +--- cibuildwheel/platforms/windows.py | 8 ++------ 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index 4a3a0e775..d649547c0 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -34,7 +34,6 @@ from ..util.helpers import prepare_command, unwrap_preserving_paragraphs from ..util.packaging import ( find_compatible_wheel, - get_pip_version, ) from ..venv import constraint_flags, virtualenv from .macos import install_cpython as install_build_cpython @@ -460,7 +459,6 @@ def build(options: Options, tmp_path: Path) -> None: build_frontend=build_frontend.name, xbuild_tools=build_options.xbuild_tools, ) - pip_version = get_pip_version(env) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) if compatible_wheel: @@ -488,9 +486,6 @@ def build(options: Options, tmp_path: Path) -> None: build_frontend, build_options.build_verbosity, build_options.config_settings ) - build_env = env.copy() - build_env["VIRTUALENV_PIP"] = pip_version - match build_frontend.name: case "pip": # Path.resolve() is needed. Without it pip wheel may try to @@ -505,7 +500,7 @@ def build(options: Options, tmp_path: Path) -> None: f"--wheel-dir={built_wheel_dir}", "--no-deps", *extra_flags, - env=build_env, + env=env, ) case "build": call( @@ -516,7 +511,7 @@ def build(options: Options, tmp_path: Path) -> None: "--wheel", f"--outdir={built_wheel_dir}", *extra_flags, - env=build_env, + env=env, ) case _: assert_never(build_frontend) @@ -534,9 +529,7 @@ def build(options: Options, tmp_path: Path) -> None: elif config.arch != os.uname().machine: log.step("Skipping tests on non-native simulator architecture") else: - test_env = build_options.test_environment.as_dictionary( - prev_environment=build_env - ) + test_env = build_options.test_environment.as_dictionary(prev_environment=env) if build_options.before_test: before_test_prepared = prepare_command( diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 292f58808..5f47bee63 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -461,8 +461,6 @@ def build(options: Options, tmp_path: Path) -> None: ) build_env = env.copy() - if pip_version is not None: - build_env["VIRTUALENV_PIP"] = pip_version match build_frontend.name: case "pip": diff --git a/cibuildwheel/platforms/pyodide.py b/cibuildwheel/platforms/pyodide.py index 47cce9677..09de6a441 100644 --- a/cibuildwheel/platforms/pyodide.py +++ b/cibuildwheel/platforms/pyodide.py @@ -419,15 +419,13 @@ def build(options: Options, tmp_path: Path) -> None: build_frontend, build_options.build_verbosity, build_options.config_settings ) - build_env = env.copy() - build_env["VIRTUALENV_PIP"] = pip_version call( "pyodide", "build", build_options.package_dir, f"--outdir={built_wheel_dir}", *extra_flags, - env=build_env, + env=env, ) built_wheel = next(built_wheel_dir.glob("*.whl")) diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index 692733a4a..2bc20efc8 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -461,10 +461,6 @@ def build(options: Options, tmp_path: Path) -> None: shell("graalpy -m pip install setuptools wheel", env=env) extra_flags = [*extra_flags, "-n"] - build_env = env.copy() - if pip_version is not None: - build_env["VIRTUALENV_PIP"] = pip_version - match build_frontend.name: case "pip": # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org @@ -478,7 +474,7 @@ def build(options: Options, tmp_path: Path) -> None: f"--wheel-dir={built_wheel_dir}", "--no-deps", *extra_flags, - env=build_env, + env=env, ) case "build" | "build[uv]": if ( @@ -496,7 +492,7 @@ def build(options: Options, tmp_path: Path) -> None: "--wheel", f"--outdir={built_wheel_dir}", *extra_flags, - env=build_env, + env=env, ) case _: assert_never(build_frontend) From 2102b97f99ffc16d26826d5cfa4029cd61bbd5c7 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 20 Sep 2025 23:26:43 +0100 Subject: [PATCH 3/4] Alter tests to assert the versions in before_build, not setup.py --- test/test_dependency_versions.py | 127 ++++++++++++++++++------------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index 5aa84a506..a4e766394 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -1,5 +1,7 @@ +import json import platform import re +import subprocess import textwrap from pathlib import Path @@ -9,40 +11,59 @@ from . import test_projects, utils -project_with_expected_version_checks = test_projects.new_c_project( - setup_py_add=textwrap.dedent( - r""" - import subprocess - import os - import sys - - versions_output_text = subprocess.check_output( - [sys.executable, '-m', 'pip', 'freeze', '--all', '-qq'], - universal_newlines=True, - ) - versions = versions_output_text.strip().splitlines() +VERSION_REGEX = r"([\w-]+)==([^\s]+)" - # `versions` now looks like: - # ['pip==x.x.x', 'setuptools==x.x.x', 'wheel==x.x.x'] +CHECK_VERSIONS_SCRIPT = """\ +''' +Checks that the versions in the env var EXPECTED_VERSIONS match those +installed in the active venv. +''' +import os, subprocess, sys, json + +versions_raw = json.loads( + subprocess.check_output([ + sys.executable, '-m', 'pip', 'list', '--format=json', + ], text=True) +) +versions = {item['name']: item['version'] for item in versions_raw} +expected_versions = json.loads(os.environ['EXPECTED_VERSIONS']) + +for name, expected_version in expected_versions.items(): + if name not in versions: + continue + if versions[name] != expected_version: + raise SystemExit(f'error: {name} version should equal {expected_version}. Versions: {versions}') +""" - print('Gathered versions', versions) - expected_version = os.environ['EXPECTED_PIP_VERSION'] +def test_check_versions_script(tmp_path, build_frontend_env_nouv, capfd): + # sanity check that the CHECK_VERSIONS_SCRIPT fails when it should + project_dir = tmp_path / "project" + test_projects.new_c_project().generate(project_dir) - assert f'pip=={expected_version}' in versions, ( - f'error: pip version should equal {expected_version}' + expected_versions = { + "pip": "0.0.1", + "build": "0.0.2", + } + script = project_dir / "check_versions.py" + script.write_text(CHECK_VERSIONS_SCRIPT) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_BEFORE_BUILD": f"python {script.name}", + "EXPECTED_VERSIONS": json.dumps(expected_versions), + **build_frontend_env_nouv, + }, ) - """ - ) -) -project_with_expected_version_checks.files["pyproject.toml"] = r""" -[build-system] -requires = ["setuptools", "pip"] -build-backend = "setuptools.build_meta" -""" + captured = capfd.readouterr() -VERSION_REGEX = r"([\w-]+)==([^\s]+)" + assert ( + "error: pip version should equal 0.0.1" in captured.err + or "error: build version should equal 0.0.2" in captured.err + ) def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]: @@ -65,11 +86,15 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv): pytest.skip(f"Windows ARM64 does not support Python {python_version}") project_dir = tmp_path / "project" - project_with_expected_version_checks.generate(project_dir) + test_projects.new_c_project().generate(project_dir) + # read the expected versions from the appropriate constraint file version_no_dot = python_version.replace(".", "") - build_environment = {} - build_pattern = f"[cp]p{version_no_dot}-*" + + # create cross-platform Python before-build script to verify versions pre-build + before_build_script = project_dir / "check_versions.py" + before_build_script.write_text(CHECK_VERSIONS_SCRIPT) + if utils.get_platform() == "pyodide": constraint_filename = f"constraints-pyodide{version_no_dot}.txt" else: @@ -77,16 +102,13 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv): constraint_file = resources.PATH / constraint_filename constraint_versions = get_versions_from_constraint_file(constraint_file) - build_environment["EXPECTED_PIP_VERSION"] = constraint_versions["pip"] - - cibw_environment_option = " ".join(f"{k}={v}" for k, v in build_environment.items()) - - # build and test the wheels + # build and test the wheels (dependency version check occurs before-build) actual_wheels = utils.cibuildwheel_run( project_dir, add_env={ - "CIBW_BUILD": build_pattern, - "CIBW_ENVIRONMENT": cibw_environment_option, + "CIBW_BUILD": f"[cp]p{version_no_dot}-*", + "CIBW_BEFORE_BUILD": f"python {before_build_script.name}", + "EXPECTED_VERSIONS": json.dumps(constraint_versions), **build_frontend_env_nouv, }, ) @@ -107,10 +129,11 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead") project_dir = tmp_path / "project" - project_with_expected_version_checks.generate(project_dir) + test_projects.new_c_project().generate(project_dir) tool_versions = { "pip": "23.1.2", + "build": "1.2.2", "delocate": "0.10.3", } @@ -120,6 +143,7 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): textwrap.dedent( """ pip=={pip} + build=={build} delocate=={delocate} """.format(**tool_versions) ) @@ -133,7 +157,7 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): msg = f"Unknown method: {method}" raise ValueError(msg) - build_environment = {} + skip = "" if ( utils.get_platform() == "windows" @@ -144,32 +168,29 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): # from a virtualenv seeded executable. See # https://github.com/oracle/graalpython/issues/491 and remove this once # fixed upstream. - build_frontend_env_nouv["CIBW_SKIP"] = "gp*" - - for package_name, version in tool_versions.items(): - env_name = f"EXPECTED_{package_name.upper()}_VERSION" - build_environment[env_name] = version + skip = "gp*" - cibw_environment_option = " ".join(f"{k}={v}" for k, v in build_environment.items()) + # cross-platform Python script for dependency constraint checks + before_build_script = project_dir / "check_versions.py" + before_build_script.write_text(CHECK_VERSIONS_SCRIPT) - # build and test the wheels + # build and test the wheels (dependency version check occurs pre-build) actual_wheels = utils.cibuildwheel_run( project_dir, add_env={ - "CIBW_ENVIRONMENT": cibw_environment_option, + "CIBW_SKIP": skip, "CIBW_DEPENDENCY_VERSIONS": dependency_version_option, + "CIBW_BEFORE_BUILD": f"python {before_build_script.name}", + "EXPECTED_VERSIONS": json.dumps(tool_versions), **build_frontend_env_nouv, }, + single_python=True, ) # also check that we got the right wheels - expected_wheels = utils.expected_wheels("spam", "0.1.0") + expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True) - if ( - utils.get_platform() == "windows" - and method == "file" - and build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] == "build" - ): + if skip == "gp*": # See reference to https://github.com/oracle/graalpython/issues/491 # above expected_wheels = [w for w in expected_wheels if "graalpy" not in w] From 0ba0ac493067ebfecac9278a1a36b94f263c78d3 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sun, 21 Sep 2025 00:01:04 +0100 Subject: [PATCH 4/4] Skip the util-test on linux --- test/test_dependency_versions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index a4e766394..bec96cb3b 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -37,6 +37,9 @@ def test_check_versions_script(tmp_path, build_frontend_env_nouv, capfd): + if utils.get_platform() == "linux": + pytest.skip("we don't test dependency versions on linux, refer to other tests") + # sanity check that the CHECK_VERSIONS_SCRIPT fails when it should project_dir = tmp_path / "project" test_projects.new_c_project().generate(project_dir)