From 3ea0a6c2f0219d97ff8387b87bd2448dcfb7452c Mon Sep 17 00:00:00 2001 From: Matthieu Darbois Date: Fri, 10 May 2024 21:33:56 +0200 Subject: [PATCH] fix: respect constraints when building with pip (#1818) When building a project that has a `pyproject.toml`, constraints are not respected when building with the `pip` frontend. This commit fixes this by using the same tricks as for the `build` frontend. --- cibuildwheel/macos.py | 27 ++++++------- cibuildwheel/windows.py | 68 ++++++++++++++++---------------- test/test_dependency_versions.py | 49 ++++++++--------------- 3 files changed, 63 insertions(+), 81 deletions(-) diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index d72d50427..44f9ecf0e 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -381,6 +381,18 @@ def build(options: Options, tmp_path: Path) -> None: ) extra_flags += build_frontend.args + build_env = env.copy() + build_env["VIRTUALENV_PIP"] = get_pip_version(env) + if build_options.dependency_constraints: + constraint_path = build_options.dependency_constraints.get_for_python_version( + config.version + ) + user_constraints = build_env.get("PIP_CONSTRAINT") + our_constraints = constraint_path.as_uri() + build_env["PIP_CONSTRAINT"] = " ".join( + c for c in [user_constraints, our_constraints] if c + ) + if build_frontend.name == "pip": extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org @@ -394,25 +406,12 @@ def build(options: Options, tmp_path: Path) -> None: f"--wheel-dir={built_wheel_dir}", "--no-deps", *extra_flags, - env=env, + env=build_env, ) elif build_frontend.name == "build": if not 0 <= build_options.build_verbosity < 2: msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring." log.warning(msg) - build_env = env.copy() - if build_options.dependency_constraints: - constraint_path = ( - build_options.dependency_constraints.get_for_python_version( - config.version - ) - ) - user_constraints = build_env.get("PIP_CONSTRAINT") - our_constraints = constraint_path.as_uri() - build_env["PIP_CONSTRAINT"] = " ".join( - c for c in [user_constraints, our_constraints] if c - ) - build_env["VIRTUALENV_PIP"] = get_pip_version(env) call( "python", "-m", diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index d2be65783..13d3a1857 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -407,6 +407,28 @@ def build(options: Options, tmp_path: Path) -> None: ) extra_flags += build_frontend.args + build_env = env.copy() + build_env["VIRTUALENV_PIP"] = get_pip_version(env) + if build_options.dependency_constraints: + constraints_path = build_options.dependency_constraints.get_for_python_version( + config.version + ) + # Bug in pip <= 21.1.3 - we can't have a space in the + # constraints file, and pip doesn't support drive letters + # in uhi. After probably pip 21.2, we can use uri. For + # now, use a temporary file. + if " " in str(constraints_path): + assert " " not in str(identifier_tmp_dir) + tmp_file = identifier_tmp_dir / "constraints.txt" + tmp_file.write_bytes(constraints_path.read_bytes()) + constraints_path = tmp_file + + our_constraints = str(constraints_path) + user_constraints = build_env.get("PIP_CONSTRAINT") + build_env["PIP_CONSTRAINT"] = " ".join( + c for c in [our_constraints, user_constraints] if c + ) + if build_frontend.name == "pip": extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org @@ -420,46 +442,22 @@ def build(options: Options, tmp_path: Path) -> None: f"--wheel-dir={built_wheel_dir}", "--no-deps", *extra_flags, - env=env, + env=build_env, ) elif build_frontend.name == "build": if not 0 <= build_options.build_verbosity < 2: msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring." log.warning(msg) - build_env = env.copy() - if build_options.dependency_constraints: - constraints_path = ( - build_options.dependency_constraints.get_for_python_version( - config.version - ) - ) - # Bug in pip <= 21.1.3 - we can't have a space in the - # constraints file, and pip doesn't support drive letters - # in uhi. After probably pip 21.2, we can use uri. For - # now, use a temporary file. - if " " in str(constraints_path): - assert " " not in str(identifier_tmp_dir) - tmp_file = identifier_tmp_dir / "constraints.txt" - tmp_file.write_bytes(constraints_path.read_bytes()) - constraints_path = tmp_file - - our_constraints = str(constraints_path) - user_constraints = build_env.get("PIP_CONSTRAINT") - build_env["PIP_CONSTRAINT"] = " ".join( - c for c in [our_constraints, user_constraints] if c - ) - - build_env["VIRTUALENV_PIP"] = get_pip_version(env) - call( - "python", - "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - *extra_flags, - env=build_env, - ) + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) else: assert_never(build_frontend) diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index 739e8d0ac..760675d74 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -15,9 +15,10 @@ r""" import subprocess import os + import sys versions_output_text = subprocess.check_output( - ['pip', 'freeze', '--all', '-qq'], + [sys.executable, '-m', 'pip', 'freeze', '--all', '-qq'], universal_newlines=True, ) versions = versions_output_text.strip().splitlines() @@ -36,6 +37,11 @@ ) ) +project_with_expected_version_checks.files["pyproject.toml"] = r""" +[build-system] +requires = ["setuptools", "pip"] +build-backend = "setuptools.build_meta" +""" VERSION_REGEX = r"([\w-]+)==([^\s]+)" @@ -50,27 +56,16 @@ def get_versions_from_constraint_file(constraint_file): def test_pinned_versions(tmp_path, python_version, build_frontend_env): if utils.platform == "linux": pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead") + if python_version == "3.6" and utils.platform == "macos" and platform.machine() == "arm64": + pytest.skip("macOS arm64 does not support Python 3.6") project_dir = tmp_path / "project" project_with_expected_version_checks.generate(project_dir) + version_no_dot = python_version.replace(".", "") build_environment = {} - - if python_version == "3.6": - if utils.platform == "macos" and platform.machine() == "arm64": - pytest.skip("macOS arm64 does not support Python 3.6") - constraint_filename = "constraints-python36.txt" - build_pattern = "[cp]p36-*" - elif python_version == "3.7": - constraint_filename = "constraints-python37.txt" - build_pattern = "[cp]p37-*" - elif python_version == "3.8": - constraint_filename = "constraints-python38.txt" - build_pattern = "[cp]p38-*" - else: - constraint_filename = "constraints-python310.txt" - build_pattern = "[cp]p310-*" - + build_pattern = f"[cp]p{version_no_dot}-*" + constraint_filename = f"constraints-python{version_no_dot}.txt" constraint_file = cibuildwheel.util.resources_dir / constraint_filename constraint_versions = get_versions_from_constraint_file(constraint_file) @@ -89,21 +84,11 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env): ) # also check that we got the right wheels - if python_version == "3.6": - expected_wheels = [ - w for w in utils.expected_wheels("spam", "0.1.0") if "-cp36" in w or "-pp36" in w - ] - elif python_version == "3.8": - expected_wheels = [ - w for w in utils.expected_wheels("spam", "0.1.0") if "-cp38" in w or "-pp38" in w - ] - elif python_version == "3.10": - expected_wheels = [ - w for w in utils.expected_wheels("spam", "0.1.0") if "-cp310" in w or "-pp310" in w - ] - else: - msg = "unhandled python version" - raise ValueError(msg) + expected_wheels = [ + w + for w in utils.expected_wheels("spam", "0.1.0") + if f"-cp{version_no_dot}" in w or f"-pp{version_no_dot}" in w + ] assert set(actual_wheels) == set(expected_wheels)