221 lines
7.4 KiB
Python
221 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
from cibuildwheel.util import resources
|
|
|
|
from . import test_projects, utils
|
|
|
|
TYPE_CHECKING = False
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
VERSION_REGEX = r"([\w-]+)==([^;\s]+)"
|
|
|
|
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}')
|
|
"""
|
|
|
|
|
|
def test_check_versions_script(
|
|
tmp_path: Path, build_frontend_env_nouv: dict[str, str], capfd: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
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)
|
|
|
|
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,
|
|
},
|
|
)
|
|
|
|
captured = capfd.readouterr()
|
|
|
|
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]:
|
|
constraint_file_text = constraint_file.read_text(encoding="utf-8")
|
|
|
|
# Use the first occurrence of each package name. The marker-bearing lines
|
|
# (such as the GraalPy-specific pip pin) come after the default pin, so the
|
|
# first match is the one that applies to the majority of builds.
|
|
result: dict[str, str] = {}
|
|
for name, version in re.findall(VERSION_REGEX, constraint_file_text):
|
|
result.setdefault(name, version)
|
|
return result
|
|
|
|
|
|
@pytest.mark.parametrize("python_version", ["3.9", "3.13"])
|
|
def test_pinned_versions(
|
|
tmp_path: Path, python_version: str, build_frontend_env_nouv: dict[str, str]
|
|
) -> None:
|
|
if utils.get_platform() == "linux":
|
|
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
|
|
if python_version != "3.13" and utils.get_platform() == "pyodide":
|
|
pytest.skip(f"pyodide does not support Python {python_version}")
|
|
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
# read the expected versions from the appropriate constraint file
|
|
version_no_dot = python_version.replace(".", "")
|
|
|
|
# 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:
|
|
constraint_filename = f"constraints-python{version_no_dot}.txt"
|
|
constraint_file = resources.PATH / constraint_filename
|
|
constraint_versions = get_versions_from_constraint_file(constraint_file)
|
|
|
|
# build and test the wheels (dependency version check occurs before-build)
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"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,
|
|
},
|
|
)
|
|
|
|
# also check that we got the right wheels
|
|
expected_wheels = [
|
|
w
|
|
for w in utils.expected_wheels("spam", "0.1.0")
|
|
if f"-cp{version_no_dot}-cp{version_no_dot}-" in w or f"-pp{version_no_dot}-" in w
|
|
]
|
|
|
|
assert set(actual_wheels) == set(expected_wheels)
|
|
|
|
|
|
@pytest.mark.parametrize("method", ["inline", "file"])
|
|
def test_dependency_constraints(
|
|
method: str, tmp_path: Path, build_frontend_env_nouv: dict[str, str]
|
|
) -> None:
|
|
if utils.get_platform() == "linux":
|
|
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
|
|
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
if utils.get_platform() == "pyodide":
|
|
# pyodide-build 0.34+ requires build~=1.4.0, so we must use a
|
|
# compatible version here. delocate is macOS-only and not used on pyodide.
|
|
tool_versions = {
|
|
"pip": "23.1.2",
|
|
"build": "1.4.2",
|
|
"delocate": "0.10.3",
|
|
}
|
|
else:
|
|
tool_versions = {
|
|
"pip": "23.1.2",
|
|
"build": "1.2.2",
|
|
"delocate": "0.10.3",
|
|
}
|
|
|
|
if method == "file":
|
|
constraints_file = tmp_path / "constraints file.txt"
|
|
constraints_file.write_text(
|
|
textwrap.dedent(
|
|
"""
|
|
pip=={pip}
|
|
build=={build}
|
|
delocate=={delocate}
|
|
""".format(**tool_versions)
|
|
)
|
|
)
|
|
dependency_version_option = str(constraints_file)
|
|
elif method == "inline":
|
|
dependency_version_option = "packages: " + " ".join(
|
|
f"{k}=={v}" for k, v in tool_versions.items()
|
|
)
|
|
else:
|
|
msg = f"Unknown method: {method}"
|
|
raise ValueError(msg)
|
|
|
|
skip = ""
|
|
|
|
if (
|
|
utils.get_platform() == "windows"
|
|
and method == "file"
|
|
and build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] == "build"
|
|
):
|
|
# GraalPy 24 fails to discover its standard library when a venv is created
|
|
# from a virtualenv seeded executable. See
|
|
# https://github.com/oracle/graalpython/issues/491 and remove this once
|
|
# GraalPy 24 is dropped
|
|
skip = "gp311*"
|
|
|
|
# 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 (dependency version check occurs pre-build)
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"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", single_python=True)
|
|
|
|
if skip == "gp*":
|
|
# See reference to https://github.com/oracle/graalpython/issues/491
|
|
# above
|
|
expected_wheels = [w for w in expected_wheels if "graalpy311" not in w]
|
|
|
|
assert set(actual_wheels) == set(expected_wheels)
|