feet: general approach to auditing wheels with abi3audit default (#2805)
* WIP - initial punt at audit command * Add `abi3audit` as a dependency * Add helper functions to check stable ABI wheels * Run `abi3audit` for macOS and Windows wheels * Copy out of container for repairing? * Add some notes that `cibuildwheel` runs `abi3audit` * Add basic unit tests * Add a basic C extension with `Py_LIMITED_API` * Add a test project that violates Stable ABI * Fix linux test * Skip abi3 wheel tests for Pyodide * Patch the correct subprocess module * wrap cleanup of abi3audit dir * Write the docs for the new options * Move to above testing in docs * Implement audit-requires and audit-command * Some cleanups after self-review * Add default value * fix type errors * the key is `audit-command`, not `audit` * Add a variety of tests for audit requires options * Add `test_audit_requires` similar to `test_test_requires` * Add some configurability-related audit tests * Fix parsing error with options docs leaving out commands * Better way to extract version (maybe helps Pyodide?) * Fix a case of unbound `use_uv` * Standardise: rename to `abi3_wheel` * Fix audit command run message * Simplify custom audit command a bit * Remove unnecessary skip for Pyodide * Pyodide should have no default audit command * More accurate skip messages for Pyodide skips * Wheels are audited after they are repaired * Regenerate constraints to include `abi3audit` * Fix typos * Some attempts for Windows fixes * Check `pyvenv.cfg` instead of directory existence * Add validation for lack of wheel placeholders * Try yet another Windows `uv` fix * Regenerate diagram and re-trigger Azure CI * Add missing `import sys` for abi3 C extension tests * Remove audit-command at the global level * Clarify `abi3audit` pinning a little bit * Regen constraints * Discard changes to cibuildwheel/resources/constraints-pyodide312.txt * Discard changes to cibuildwheel/resources/constraints-pyodide313.txt * try opt-in uv again * fix issue on windows on Python 3.13 related to nested venvs On win / python 3.13, virtualenv creates a venv where the 'home' points back to the venv that sys.executable was running in, rather than the root install. that seemingly leads to problems with package resolution, where pip.exe couldn't find the pip python package. this appears to fix it! * Update constraints * chore: revert python-discovery bump Assisted-by: OpenCode:glm-5.1 Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * fix: restore workaround for graalpy Assisted-by: OpenCode:glm-5.1 Signed-off-by: Henry Schreiner <henryfs@princeton.edu> --------- Signed-off-by: Henry Schreiner <henryfs@princeton.edu> Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Co-authored-by: Henry Schreiner <henryfs@princeton.edu>
This commit is contained in:
co-authored by
Agriya Khetarpal
Henry Schreiner
parent
e04baff444
commit
79244d366c
@@ -155,6 +155,8 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
|
||||
| | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels |
|
||||
| | [`dependency-versions`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Control the versions of the tools cibuildwheel uses |
|
||||
| | [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) | Specify the Pyodide version to use for `pyodide` platform builds |
|
||||
| **Auditing** | [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) | Install Python dependencies for the audit step |
|
||||
| | [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) | Use a tool to check wheels before the end of the run |
|
||||
| **Testing** | [`test-command`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | The command to test each built wheel |
|
||||
| | [`before-test`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel |
|
||||
| | [`test-sources`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Paths that are copied into the working directory of the tests |
|
||||
@@ -169,7 +171,7 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
|
||||
| | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build |
|
||||
|
||||
|
||||
<!--[[[end]]] (sum: dbfwOkj/k/) -->
|
||||
<!--[[[end]]] (sum: b7YIjCyCkf) -->
|
||||
|
||||
These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/).
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ additionalProperties: false
|
||||
description: cibuildwheel's settings.
|
||||
type: object
|
||||
properties:
|
||||
audit-command:
|
||||
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
|
||||
type: string_array
|
||||
audit-requires:
|
||||
description: Install Python dependencies for the audit step.
|
||||
type: string_array
|
||||
archs:
|
||||
description: Change the architectures built on your machine by default.
|
||||
type: string_array
|
||||
@@ -308,6 +314,8 @@ items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
audit-command: {"$ref": "#/$defs/inherit"}
|
||||
audit-requires: {"$ref": "#/$defs/inherit"}
|
||||
before-all: {"$ref": "#/$defs/inherit"}
|
||||
before-build: {"$ref": "#/$defs/inherit"}
|
||||
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.logger import log
|
||||
from cibuildwheel.options import BuildOptions
|
||||
from cibuildwheel.util.cmd import call, shell
|
||||
from cibuildwheel.util.helpers import prepare_command
|
||||
from cibuildwheel.util.packaging import is_abi3_wheel
|
||||
from cibuildwheel.venv import activate_virtualenv, find_uv, virtualenv
|
||||
|
||||
|
||||
def run_audit(
|
||||
*,
|
||||
tmp_dir: Path,
|
||||
build_options: BuildOptions,
|
||||
wheel: Path,
|
||||
) -> None:
|
||||
"""
|
||||
Run the audit commands on a single wheel.
|
||||
|
||||
Creates a virtualenv (or reuses an existing one) and installs any
|
||||
audit requirements, then runs each audit command template against
|
||||
the wheel. Commands containing {abi3_wheel} are skipped for
|
||||
non-abi3 wheels.
|
||||
"""
|
||||
|
||||
if not needs_audit(build_options.audit_command, wheel.name):
|
||||
return
|
||||
|
||||
log.step("Auditing wheel...")
|
||||
|
||||
use_uv = build_options.build_frontend.name in {"build[uv]", "uv"}
|
||||
version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
||||
dependency_constraint = build_options.dependency_constraints.get_for_python_version(
|
||||
version=version, tmp_dir=tmp_dir
|
||||
)
|
||||
|
||||
# Use the base interpreter, not the venv python, to avoid nested-venv
|
||||
# issues where pip can't be found (seen on Windows + Python 3.13).
|
||||
host_python = Path(getattr(sys, "_base_executable", sys.executable))
|
||||
|
||||
audit_venv_dir = tmp_dir / "audit_venv"
|
||||
if not (audit_venv_dir / "pyvenv.cfg").exists():
|
||||
env = virtualenv(
|
||||
version,
|
||||
host_python,
|
||||
audit_venv_dir,
|
||||
dependency_constraint=dependency_constraint,
|
||||
use_uv=use_uv,
|
||||
)
|
||||
else:
|
||||
env = activate_virtualenv(audit_venv_dir)
|
||||
|
||||
# install audit requirements. This is run every time in case the user has
|
||||
# defined overrides.
|
||||
audit_requires = build_options.audit_requires
|
||||
if audit_requires:
|
||||
print(f"Installing audit dependencies: {', '.join(audit_requires)}")
|
||||
|
||||
pip: list[str]
|
||||
if use_uv:
|
||||
uv_path = find_uv()
|
||||
assert uv_path is not None
|
||||
pip = [str(uv_path), "pip"]
|
||||
else:
|
||||
pip = ["pip"]
|
||||
# we pin if the audit-requires is left as the default "abi3audit"
|
||||
should_pin = audit_requires == ["abi3audit"] and dependency_constraint
|
||||
|
||||
call(
|
||||
*pip,
|
||||
"install",
|
||||
*(["--constraint", str(dependency_constraint)] if should_pin else []),
|
||||
*audit_requires,
|
||||
env=env,
|
||||
)
|
||||
|
||||
audit_command = build_options.audit_command
|
||||
|
||||
for command_template in audit_command:
|
||||
if "{abi3_wheel}" in command_template and "{wheel}" in command_template:
|
||||
msg = (
|
||||
f"Invalid audit command {command_template!r}: cannot contain both {{abi3_wheel}} "
|
||||
"and {{wheel}} placeholders"
|
||||
)
|
||||
raise errors.ConfigurationError(msg)
|
||||
|
||||
if "{abi3_wheel}" in command_template and not is_abi3_wheel(wheel.name):
|
||||
continue
|
||||
|
||||
prepared_command = prepare_command(
|
||||
command_template,
|
||||
abi3_wheel=wheel,
|
||||
wheel=wheel,
|
||||
project=".",
|
||||
package=build_options.package_dir,
|
||||
)
|
||||
|
||||
print(f"Running audit command: {prepared_command}")
|
||||
try:
|
||||
shell(prepared_command, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Audit command failed with exit code {e.returncode}")
|
||||
msg = f"Audit command failed: {prepared_command}"
|
||||
raise errors.AuditCommandFailedError(msg) from e
|
||||
|
||||
|
||||
def needs_audit(audit_commands: list[str], wheel_name: str) -> bool:
|
||||
saw_abi3_placeholder = False
|
||||
for audit_command in audit_commands:
|
||||
if "{abi3_wheel}" not in audit_command and "{wheel}" not in audit_command:
|
||||
msg = (
|
||||
f"Invalid audit command {audit_command!r}: must contain either "
|
||||
"{{abi3_wheel}} or {{wheel}} placeholder"
|
||||
)
|
||||
raise errors.ConfigurationError(msg)
|
||||
|
||||
if "{abi3_wheel}" in audit_command:
|
||||
saw_abi3_placeholder = True
|
||||
if is_abi3_wheel(wheel_name):
|
||||
return True
|
||||
elif "{wheel}" in audit_command:
|
||||
return True
|
||||
|
||||
if saw_abi3_placeholder:
|
||||
print("No audit required for this wheel, as it is not abi3")
|
||||
else:
|
||||
print("No audit configured")
|
||||
|
||||
return False
|
||||
@@ -103,3 +103,9 @@ class RepairStepProducedMultipleWheelsError(FatalError):
|
||||
)
|
||||
super().__init__(message)
|
||||
self.return_code = 8
|
||||
|
||||
|
||||
class AuditCommandFailedError(FatalError):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.return_code = 9
|
||||
|
||||
@@ -125,6 +125,8 @@ class BuildOptions:
|
||||
test_groups: list[str]
|
||||
test_environment: ParsedEnvironment
|
||||
test_runtime: TestRuntimeConfig
|
||||
audit_requires: list[str]
|
||||
audit_command: list[str]
|
||||
build_verbosity: int
|
||||
build_frontend: BuildFrontendConfig
|
||||
config_settings: str
|
||||
@@ -892,6 +894,15 @@ class Options:
|
||||
|
||||
pyodide_version = self.reader.get("pyodide-version", env_plat=False)
|
||||
|
||||
audit_command_str = self.reader.get(
|
||||
"audit-command", option_format=ListFormat(sep=" && ")
|
||||
)
|
||||
audit_command = audit_command_str.split(" && ") if audit_command_str else []
|
||||
|
||||
audit_requires = self.reader.get(
|
||||
"audit-requires", option_format=ListFormat(sep=" ")
|
||||
).split()
|
||||
|
||||
return BuildOptions(
|
||||
globals=self.globals,
|
||||
test_command=test_command,
|
||||
@@ -915,6 +926,8 @@ class Options:
|
||||
config_settings=config_settings,
|
||||
container_engine=container_engine,
|
||||
pyodide_version=pyodide_version or None,
|
||||
audit_command=audit_command,
|
||||
audit_requires=audit_requires,
|
||||
)
|
||||
|
||||
def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None:
|
||||
|
||||
@@ -24,6 +24,7 @@ from filelock import FileLock
|
||||
|
||||
from cibuildwheel import errors, platforms # pylint: disable=cyclic-import
|
||||
from cibuildwheel.architecture import Architecture, arch_synonym
|
||||
from cibuildwheel.audit import run_audit
|
||||
from cibuildwheel.frontend import (
|
||||
get_build_frontend_extra_flags,
|
||||
parse_config_settings,
|
||||
@@ -154,6 +155,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
before_build(state)
|
||||
built_wheel = build_wheel(state)
|
||||
repaired_wheel = repair_wheel(state, built_wheel)
|
||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||
|
||||
test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from filelock import FileLock
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.architecture import Architecture
|
||||
from cibuildwheel.audit import run_audit
|
||||
from cibuildwheel.environment import ParsedEnvironment
|
||||
from cibuildwheel.frontend import (
|
||||
BuildFrontendName,
|
||||
@@ -546,10 +547,12 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
|
||||
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
|
||||
|
||||
test_wheel = repaired_wheel
|
||||
|
||||
log.step_end()
|
||||
|
||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||
|
||||
test_wheel = repaired_wheel
|
||||
|
||||
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||
if not config.is_simulator:
|
||||
log.step("Skipping tests on non-simulator SDK")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
@@ -10,6 +11,7 @@ from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.architecture import Architecture
|
||||
from cibuildwheel.audit import needs_audit, run_audit
|
||||
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
|
||||
from cibuildwheel.logger import log
|
||||
from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
|
||||
@@ -372,6 +374,18 @@ def build_in_container(
|
||||
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
|
||||
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
|
||||
|
||||
log.step_end()
|
||||
|
||||
if needs_audit(build_options.audit_command, repaired_wheel.name):
|
||||
local_abi3audit_dir = local_identifier_tmp_dir / "audit"
|
||||
local_abi3audit_dir.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
container.copy_out(repaired_wheel_dir, local_abi3audit_dir)
|
||||
local_wheel = local_abi3audit_dir / repaired_wheel.name
|
||||
run_audit(tmp_dir=local_tmp_dir, build_options=build_options, wheel=local_wheel)
|
||||
finally:
|
||||
shutil.rmtree(local_abi3audit_dir, ignore_errors=True)
|
||||
|
||||
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||
log.step("Testing wheel...")
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from packaging.version import Version
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.architecture import Architecture
|
||||
from cibuildwheel.audit import run_audit
|
||||
from cibuildwheel.ci import detect_ci_provider
|
||||
from cibuildwheel.environment import ParsedEnvironment
|
||||
from cibuildwheel.frontend import (
|
||||
@@ -576,6 +577,8 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
|
||||
log.step_end()
|
||||
|
||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||
|
||||
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||
machine_arch = platform.machine()
|
||||
testing_archs: list[Literal["x86_64", "arm64"]]
|
||||
|
||||
@@ -16,6 +16,7 @@ from filelock import FileLock
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.architecture import Architecture
|
||||
from cibuildwheel.audit import run_audit
|
||||
from cibuildwheel.environment import ParsedEnvironment
|
||||
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
|
||||
from cibuildwheel.logger import log
|
||||
@@ -463,6 +464,8 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
|
||||
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
|
||||
|
||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||
|
||||
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||
log.step("Testing wheel...")
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from filelock import FileLock
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.architecture import Architecture
|
||||
from cibuildwheel.audit import run_audit
|
||||
from cibuildwheel.environment import ParsedEnvironment
|
||||
from cibuildwheel.frontend import (
|
||||
BuildFrontendName,
|
||||
@@ -585,6 +586,8 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
|
||||
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
|
||||
|
||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||
|
||||
test_selected = options.globals.test_selector(config.identifier)
|
||||
if test_selected and config.arch == "ARM64" != platform_module.machine():
|
||||
log.warning(
|
||||
|
||||
@@ -26,6 +26,36 @@
|
||||
"description": "cibuildwheel's settings.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "CIBW_AUDIT_COMMAND"
|
||||
},
|
||||
"audit-requires": {
|
||||
"description": "Install Python dependencies for the audit step.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "CIBW_AUDIT_REQUIRES"
|
||||
},
|
||||
"archs": {
|
||||
"description": "Change the architectures built on your machine by default.",
|
||||
"oneOf": [
|
||||
@@ -634,6 +664,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
"before-all": {
|
||||
"$ref": "#/$defs/inherit"
|
||||
},
|
||||
@@ -681,6 +717,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"before-all": {
|
||||
"$ref": "#/properties/before-all"
|
||||
},
|
||||
@@ -799,6 +841,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
@@ -929,6 +977,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
@@ -992,6 +1046,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
@@ -1068,6 +1128,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
@@ -1131,6 +1197,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
@@ -1194,6 +1266,12 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"audit-command": {
|
||||
"$ref": "#/properties/audit-command"
|
||||
},
|
||||
"audit-requires": {
|
||||
"$ref": "#/properties/audit-requires"
|
||||
},
|
||||
"archs": {
|
||||
"$ref": "#/properties/archs"
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.14
|
||||
idna==3.15
|
||||
# via requests
|
||||
leb128==1.0.9
|
||||
# via auditwheel-emscripten
|
||||
@@ -37,8 +37,7 @@ packaging==26.2
|
||||
# build
|
||||
# pyodide-build
|
||||
# wheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
pip==26.1.1
|
||||
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
@@ -63,9 +62,9 @@ pyodide-lock==0.1.3
|
||||
# via pyodide-build
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.0
|
||||
requests==2.34.1
|
||||
# via pyodide-build
|
||||
rich==15.0.0
|
||||
# via
|
||||
@@ -82,7 +81,7 @@ typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
urllib3==2.7.0
|
||||
# via requests
|
||||
virtualenv==21.3.1
|
||||
virtualenv==21.3.3
|
||||
# via
|
||||
# build
|
||||
# pyodide-build
|
||||
|
||||
@@ -23,7 +23,7 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.14
|
||||
idna==3.15
|
||||
# via requests
|
||||
leb128==1.0.9
|
||||
# via auditwheel-emscripten
|
||||
@@ -62,9 +62,9 @@ pyodide-lock==0.1.3
|
||||
# via pyodide-build
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.0
|
||||
requests==2.34.1
|
||||
# via pyodide-build
|
||||
rich==15.0.0
|
||||
# via
|
||||
@@ -81,7 +81,7 @@ typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
urllib3==2.7.0
|
||||
# via requests
|
||||
virtualenv==21.3.1
|
||||
virtualenv==21.3.3
|
||||
# via
|
||||
# build
|
||||
# pyodide-build
|
||||
|
||||
@@ -23,7 +23,7 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.14
|
||||
idna==3.15
|
||||
# via requests
|
||||
leb128==1.0.9
|
||||
# via auditwheel-emscripten
|
||||
@@ -62,9 +62,9 @@ pyodide-lock==0.1.3
|
||||
# via pyodide-build
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.0
|
||||
requests==2.34.1
|
||||
# via pyodide-build
|
||||
rich==15.0.0
|
||||
# via
|
||||
@@ -81,7 +81,7 @@ typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
urllib3==2.7.0
|
||||
# via requests
|
||||
virtualenv==21.3.1
|
||||
virtualenv==21.3.3
|
||||
# via
|
||||
# build
|
||||
# pyodide-build
|
||||
|
||||
@@ -1,47 +1,96 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
exceptiongroup==1.3.1
|
||||
# via cattrs
|
||||
filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
importlib-metadata==9.0.0
|
||||
# via build
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
tomli==2.4.1
|
||||
# via build
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
# exceptiongroup
|
||||
# virtualenv
|
||||
virtualenv==21.3.1
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
zipp==3.23.1
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,60 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,60 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,59 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,59 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,59 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -1,46 +1,96 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.25
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.4.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.4.4
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==25.3.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
exceptiongroup==1.3.1
|
||||
# via cattrs
|
||||
filelock==3.19.1
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
importlib-metadata==8.7.1
|
||||
# via build
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
packaging==26.2
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==25.0
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.0.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.4.0
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.32.5
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.2.1
|
||||
# via abi3audit
|
||||
rich==14.2.0
|
||||
# via abi3audit
|
||||
tomli==2.4.1
|
||||
# via build
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
# exceptiongroup
|
||||
# virtualenv
|
||||
virtualenv==21.3.1
|
||||
url-normalize==2.2.1
|
||||
# via requests-cache
|
||||
urllib3==2.6.3
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
zipp==3.23.1
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -3,3 +3,4 @@ build
|
||||
delocate
|
||||
delvewheel
|
||||
virtualenv
|
||||
abi3audit
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# nox -s update_constraints
|
||||
abi3audit==0.0.26
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
abi3info==2025.11.29
|
||||
# via abi3audit
|
||||
altgraph==0.17.5
|
||||
# via macholib
|
||||
attrs==26.1.0
|
||||
# via
|
||||
# cattrs
|
||||
# requests-cache
|
||||
build==1.5.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
cattrs==26.1.0
|
||||
# via requests-cache
|
||||
certifi==2026.4.22
|
||||
# via requests
|
||||
charset-normalizer==3.4.7
|
||||
# via requests
|
||||
delocate==0.13.0
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
delvewheel==1.12.1
|
||||
@@ -14,26 +28,59 @@ filelock==3.29.0
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
idna==3.15
|
||||
# via
|
||||
# requests
|
||||
# url-normalize
|
||||
kaitaistruct==0.11
|
||||
# via abi3audit
|
||||
macholib==1.16.4
|
||||
# via delocate
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
packaging==26.2
|
||||
# via
|
||||
# abi3audit
|
||||
# build
|
||||
# delocate
|
||||
pefile==2024.8.26
|
||||
# via delvewheel
|
||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||
# via
|
||||
# abi3audit
|
||||
# delvewheel
|
||||
pip==26.1.1
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
platformdirs==4.9.6
|
||||
# via
|
||||
# python-discovery
|
||||
# requests-cache
|
||||
# virtualenv
|
||||
pyelftools==0.32
|
||||
# via abi3audit
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
python-discovery==1.3.0
|
||||
python-discovery==1.3.1
|
||||
# via virtualenv
|
||||
requests==2.34.1
|
||||
# via
|
||||
# abi3audit
|
||||
# requests-cache
|
||||
requests-cache==1.3.2
|
||||
# via abi3audit
|
||||
rich==15.0.0
|
||||
# via abi3audit
|
||||
typing-extensions==4.15.0
|
||||
# via delocate
|
||||
virtualenv==21.3.1
|
||||
# via
|
||||
# cattrs
|
||||
# delocate
|
||||
url-normalize==3.0.0
|
||||
# via requests-cache
|
||||
urllib3==2.7.0
|
||||
# via
|
||||
# requests
|
||||
# requests-cache
|
||||
virtualenv==21.3.3
|
||||
# via -r cibuildwheel/resources/constraints.in
|
||||
|
||||
@@ -5,6 +5,8 @@ test-skip = ""
|
||||
enable = []
|
||||
|
||||
archs = ["auto"]
|
||||
audit-requires = ["abi3audit"]
|
||||
audit-command = "abi3audit --strict --report {abi3_wheel}"
|
||||
build-frontend = "default"
|
||||
config-settings = {}
|
||||
dependency-versions = "pinned"
|
||||
@@ -65,3 +67,4 @@ repair-wheel-command = "delvewheel repair -w {dest_dir} -v {wheel}"
|
||||
[tool.cibuildwheel.ios]
|
||||
|
||||
[tool.cibuildwheel.pyodide]
|
||||
audit-command = ""
|
||||
|
||||
@@ -177,3 +177,9 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None:
|
||||
return wheel
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_abi3_wheel(wheel_name: str) -> bool:
|
||||
"""Check if a wheel uses the abi3 stable ABI based on its filename."""
|
||||
_, _, _, tags = parse_wheel_filename(wheel_name)
|
||||
return any(tag.abi == "abi3" for tag in tags)
|
||||
|
||||
+15
-4
@@ -166,10 +166,7 @@ def virtualenv(
|
||||
python,
|
||||
venv_path,
|
||||
)
|
||||
paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")]
|
||||
venv_env = os.environ.copy() if env is None else env.copy()
|
||||
venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]])
|
||||
venv_env["VIRTUAL_ENV"] = str(venv_path)
|
||||
venv_env = activate_virtualenv(venv_path, env=env)
|
||||
if not use_uv and pip_version == "embed":
|
||||
call(
|
||||
"python",
|
||||
@@ -185,6 +182,20 @@ def virtualenv(
|
||||
return venv_env
|
||||
|
||||
|
||||
def activate_virtualenv(
|
||||
venv_path: Path,
|
||||
env: dict[str, str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
Return a copy of the environment with the virtualenv at `venv_path` activated.
|
||||
"""
|
||||
paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")]
|
||||
venv_env = os.environ.copy() if env is None else env.copy()
|
||||
venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]])
|
||||
venv_env["VIRTUAL_ENV"] = str(venv_path)
|
||||
return venv_env
|
||||
|
||||
|
||||
def find_uv() -> Path | None:
|
||||
# Prefer uv in our environment
|
||||
with contextlib.suppress(ImportError, FileNotFoundError):
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 96 KiB |
+16
-2
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<div class="grid-column-label"
|
||||
style="grid-row: 2 / span 1;
|
||||
grid-column: 9 / -3;
|
||||
grid-column: 10 / -3;
|
||||
margin-bottom: 0.5em;">
|
||||
<div class="label">If tests are configured</div>
|
||||
</div>
|
||||
@@ -200,6 +200,20 @@
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
env: "CIBW_AUDIT_COMMAND",
|
||||
href: 'options/#audit-command',
|
||||
label: 'audit wheel',
|
||||
platforms: ['linux', 'macos', 'windows'],
|
||||
style: 'dot',
|
||||
tooltip: {
|
||||
title: 'CIBW_AUDIT_COMMAND',
|
||||
tag: 'Optional step',
|
||||
description: 'Runs a shell command to check each built wheel. By default this runs abi3audit if produced wheels are abi3.'
|
||||
},
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
href: 'options/#before-test',
|
||||
@@ -481,7 +495,7 @@
|
||||
grid-column: 3 / -2;
|
||||
}
|
||||
.grid-outline.testVenv {
|
||||
grid-column: 9 / span 3;
|
||||
grid-column: 10 / span 3;
|
||||
}
|
||||
.grid-outline .outline {
|
||||
position: absolute;
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ The CPython Limited API is a subset of the Python C Extension API that's declare
|
||||
|
||||
To create a package that builds ABI3 wheels, you'll need to configure your build backend to compile libraries correctly create wheels with the right tags. [Check this repo](https://github.com/joerick/python-abi3-package-sample) for an example of how to do this with setuptools.
|
||||
|
||||
You could also consider running [abi3audit](https://github.com/trailofbits/abi3audit) against the produced wheels in order to check for abi3 violations or inconsistencies. You can run it alongside the default in your [repair-wheel-command](options.md#repair-wheel-command).
|
||||
cibuildwheel automatically runs [abi3audit](https://github.com/trailofbits/abi3audit) on any abi3 wheel after the repair step to check for stable ABI violations or inconsistencies. If abi3audit detects any issues, the build will fail with a detailed report.
|
||||
|
||||
### Packages with optional C extensions {: #optional-extensions}
|
||||
|
||||
|
||||
+117
-46
@@ -923,7 +923,6 @@ Default:
|
||||
|
||||
- on Linux: `'auditwheel repair -w {dest_dir} {wheel}'`
|
||||
- on macOS: `'delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}'`
|
||||
- on Windows: `'delvewheel repair -w {dest_dir} -v {wheel}'`
|
||||
- on Android: There is no default command, but cibuildwheel will add `libc++` to the
|
||||
wheel if anything links against it. Setting a command will replace this behavior.
|
||||
- on Pyodide: You can use `pyodide auditwheel repair --libdir /path/to/libraries --output-dir {dest_dir} {wheel}` command to repair the wheel.
|
||||
@@ -948,35 +947,29 @@ The command is run in a shell, so you can run multiple commands like `cmd1 && cm
|
||||
Platform-specific environment variables are also available:<br/>
|
||||
`CIBW_REPAIR_WHEEL_COMMAND_MACOS` | `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS` | `CIBW_REPAIR_WHEEL_COMMAND_LINUX` | `CIBW_REPAIR_WHEEL_COMMAND_ANDROID` | `CIBW_REPAIR_WHEEL_COMMAND_IOS` | `CIBW_REPAIR_WHEEL_COMMAND_PYODIDE`
|
||||
|
||||
!!! note "Windows: telling delvewheel where to find DLLs"
|
||||
On Windows, delvewheel searches the directories on `PATH` for external DLL dependencies. If your DLLs are already discoverable via `PATH`, (say, installed by a package manager that adds itself and the relevant directories to `PATH`), the default repair command should be sufficient.
|
||||
!!! tip
|
||||
cibuildwheel doesn't yet ship a default repair command for Windows.
|
||||
|
||||
If your project links against DLLs in a custom location – such as a [vcpkg](https://vcpkg.io/) or [Conan](https://conan.io/) install tree, or a manually built library directory, you may pass `--add-path` to tell delvewheel where to look. The flag can be used multiple times for more than one directory:
|
||||
**If that's an issue for you, check out [delvewheel]** - a new package that aims to do the same as auditwheel or delocate for Windows.
|
||||
|
||||
```toml
|
||||
[tool.cibuildwheel.windows]
|
||||
repair-wheel-command = "delvewheel repair --add-path C:/vcpkg/installed/x64-windows/bin --add-path C:/mylibs/bin -w {dest_dir} -v {wheel}"
|
||||
```
|
||||
Because delvewheel is still relatively early-stage, cibuildwheel does not yet run it by default. However, we'd recommend giving it a try! See the examples below for usage.
|
||||
|
||||
You can also reference environment variables expanded by the shell at build time, for example if the path is set during `before-build`:
|
||||
|
||||
```yaml
|
||||
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair --add-path %VCPKG_INSTALLED_DIR%\\x64-windows\\bin -w {dest_dir} -v {wheel}"
|
||||
```
|
||||
[Delvewheel]: https://github.com/adang1345/delvewheel
|
||||
|
||||
#### Examples
|
||||
|
||||
!!! tab examples "pyproject.toml"
|
||||
|
||||
```toml
|
||||
# Use delvewheel on windows
|
||||
[tool.cibuildwheel.windows]
|
||||
before-build = "pip install delvewheel"
|
||||
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"
|
||||
|
||||
# Don't repair macOS wheels
|
||||
[tool.cibuildwheel.macos]
|
||||
repair-wheel-command = ""
|
||||
|
||||
# Don't repair Windows wheels
|
||||
[tool.cibuildwheel.windows]
|
||||
repair-wheel-command = ""
|
||||
|
||||
# Pass the `--lib-sdir .` flag to auditwheel on Linux
|
||||
[tool.cibuildwheel.linux]
|
||||
repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
|
||||
@@ -988,36 +981,24 @@ Platform-specific environment variables are also available:<br/>
|
||||
'python scripts/check_repaired_wheel.py -w {dest_dir} {wheel}',
|
||||
]
|
||||
|
||||
# Use abi3audit to catch issues with Limited API wheels
|
||||
[tool.cibuildwheel.linux]
|
||||
repair-wheel-command = [
|
||||
"auditwheel repair -w {dest_dir} {wheel}",
|
||||
"pipx run abi3audit --strict --report {wheel}",
|
||||
]
|
||||
[tool.cibuildwheel.macos]
|
||||
repair-wheel-command = [
|
||||
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
|
||||
"pipx run abi3audit --strict --report {wheel}",
|
||||
]
|
||||
[tool.cibuildwheel.windows]
|
||||
repair-wheel-command = [
|
||||
"delvewheel repair -w {dest_dir} -v {wheel}",
|
||||
"pipx run abi3audit --strict --report {wheel}",
|
||||
]
|
||||
```
|
||||
|
||||
!!! note
|
||||
cibuildwheel automatically runs [abi3audit](https://github.com/trailofbits/abi3audit) on abi3 wheels after the repair step. You no longer need to add it to your repair command manually.
|
||||
|
||||
In configuration files, you can use an inline array, and the items will be joined with `&&`.
|
||||
|
||||
|
||||
!!! tab examples "Environment variables"
|
||||
|
||||
```yaml
|
||||
# Use delvewheel on windows
|
||||
CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel"
|
||||
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}"
|
||||
|
||||
# Don't repair macOS wheels
|
||||
CIBW_REPAIR_WHEEL_COMMAND_MACOS: ""
|
||||
|
||||
# Don't repair Windows wheels
|
||||
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: ""
|
||||
|
||||
# Pass the `--lib-sdir .` flag to auditwheel on Linux
|
||||
CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
|
||||
|
||||
@@ -1026,16 +1007,6 @@ Platform-specific environment variables are also available:<br/>
|
||||
python scripts/repair_wheel.py -w {dest_dir} {wheel} &&
|
||||
python scripts/check_repaired_wheel.py -w {dest_dir} {wheel}
|
||||
|
||||
# Use abi3audit to catch issues with Limited API wheels
|
||||
CIBW_REPAIR_WHEEL_COMMAND_LINUX: >
|
||||
auditwheel repair -w {dest_dir} {wheel} &&
|
||||
pipx run abi3audit --strict --report {wheel}
|
||||
CIBW_REPAIR_WHEEL_COMMAND_MACOS: >
|
||||
delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} &&
|
||||
pipx run abi3audit --strict --report {wheel}
|
||||
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >
|
||||
delvewheel repair -w {dest_dir} -v {wheel} &&
|
||||
pipx run abi3audit --strict --report {wheel}
|
||||
```
|
||||
|
||||
|
||||
@@ -1255,6 +1226,10 @@ Platform-specific environment variables are also available:<br/>
|
||||
dependency versions on Linux, use the [`manylinux-*` / `musllinux-*`](#linux-image)
|
||||
options.
|
||||
|
||||
There is one exception to this rule - when `audit-requires` is left as the
|
||||
default `["abi3audit"]`, the `abi3audit` version is governed by this option,
|
||||
because audits take place outside of the build container.
|
||||
|
||||
#### Examples
|
||||
|
||||
!!! tab examples "pyproject.toml"
|
||||
@@ -1349,6 +1324,102 @@ The available Pyodide versions are determined by the version of `pyodide-build`
|
||||
```
|
||||
|
||||
|
||||
## Auditing
|
||||
|
||||
### `audit-requires` {: #audit-requires toml env-var }
|
||||
|
||||
> Install Python dependencies for the audit step
|
||||
|
||||
Default: `abi3audit`
|
||||
|
||||
Space-separated list of package dependencies required for the audit command.
|
||||
These are installed into an isolated environment before running the
|
||||
[`audit-command`](#audit-command).
|
||||
|
||||
If no audit command is specified, or no audit is required (i.e. your project builds non-abi3 wheels and the command refers only to abi3 wheels), then the audit environment won't be created and this option is ignored.
|
||||
|
||||
If you leave this as the default, the versions of abi3audit and libraries are pinned according to [`dependency-versions`](#dependency-versions), even on Linux.
|
||||
|
||||
#### Examples
|
||||
|
||||
!!! tab examples "pyproject.toml"
|
||||
|
||||
```toml
|
||||
# Install twine for wheel metadata checks
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = "twine"
|
||||
|
||||
# Install specific versions of audit dependencies
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = ["twine==6.1.0", "abi3audit==0.0.17"]
|
||||
```
|
||||
|
||||
In configuration files, you can use an array, and the items will be joined with a space.
|
||||
|
||||
!!! tab examples "Environment variables"
|
||||
|
||||
```yaml
|
||||
# Install twine for wheel metadata checks
|
||||
CIBW_AUDIT_REQUIRES: twine
|
||||
|
||||
# Install specific versions of audit dependencies
|
||||
CIBW_AUDIT_REQUIRES: twine==6.1.0 abi3audit==0.0.17
|
||||
```
|
||||
|
||||
### `audit-command` {: #audit-command toml env-var }
|
||||
|
||||
> Use a tool to check wheels before the end of the run
|
||||
|
||||
Default: `abi3audit --strict --report {abi3_wheel}`
|
||||
|
||||
Run shell commands to verify your wheels once they are built. Multiple commands can be passed, they should be separated with ` && `. In each command, you must use one of the following placeholders:
|
||||
|
||||
- `{abi3_wheel}`: if your build produces an [ABI3 wheel](https://docs.python.org/3/c-api/stable.html#limited-c-api), as determined by the presence of an ABI3 tag in the filename, the command is run and this placeholder is substituted for the wheel path.
|
||||
- `{wheel}`: inserts the wheel path for all wheels that were built.
|
||||
|
||||
#### Examples
|
||||
|
||||
!!! tab examples "pyproject.toml"
|
||||
|
||||
```toml
|
||||
# Run a custom audit tool on all wheels
|
||||
[tool.cibuildwheel]
|
||||
audit-command = "my-audit-tool --check {wheel}"
|
||||
|
||||
# Run multiple audit commands, one for abi3 wheels only and one for all wheels
|
||||
[tool.cibuildwheel]
|
||||
audit-command = [
|
||||
"./my-audit-tool --check-abi3 {abi3_wheel}",
|
||||
"./my-audit-tool --check {wheel}",
|
||||
]
|
||||
|
||||
# Use twine check to validate wheel metadata
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = ["twine"]
|
||||
audit-command = "twine check {wheel}"
|
||||
|
||||
# Add an additional audit command using overrides, keeping the default abi3audit check
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
select = "*"
|
||||
inherit.audit-command = "append"
|
||||
audit-command = "twine check {wheel}"
|
||||
```
|
||||
|
||||
!!! tab examples "Environment variables"
|
||||
|
||||
```yaml
|
||||
# Run a custom audit tool on all wheels
|
||||
CIBW_AUDIT_COMMAND: "my-audit-tool --check {wheel}"
|
||||
|
||||
# Run multiple audit commands
|
||||
CIBW_AUDIT_COMMAND: "./my-audit-tool --check-abi3 {abi3_wheel} && ./my-audit-tool --check {wheel}"
|
||||
|
||||
# Use twine check to validate wheel metadata
|
||||
CIBW_AUDIT_REQUIRES: "twine"
|
||||
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
### `test-command` {: #test-command env-var toml}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from . import test_projects, utils
|
||||
|
||||
pyproject_toml = r"""
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"""
|
||||
|
||||
limited_api_project = test_projects.new_c_project(
|
||||
setup_py_add=textwrap.dedent(
|
||||
r"""
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
IS_CPYTHON = sys.implementation.name == "cpython"
|
||||
Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED")
|
||||
CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED
|
||||
setup_options = {}
|
||||
extension_kwargs = {}
|
||||
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10):
|
||||
extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x030A0000")]
|
||||
extension_kwargs["py_limited_api"] = True
|
||||
setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}}
|
||||
"""
|
||||
),
|
||||
setup_py_extension_args_add="**extension_kwargs",
|
||||
setup_py_setup_args_add="options=setup_options",
|
||||
)
|
||||
|
||||
limited_api_project.files["pyproject.toml"] = pyproject_toml
|
||||
|
||||
# Project that claims abi3 but violates the stable ABI by calling
|
||||
# PyUnicode_AsUTF8 (not in stable ABI until 3.13) without defining
|
||||
# Py_LIMITED_API in the C code.
|
||||
violating_abi3_project = test_projects.new_c_project(
|
||||
setup_py_add=textwrap.dedent(
|
||||
r"""
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
IS_CPYTHON = sys.implementation.name == "cpython"
|
||||
Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED")
|
||||
CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED
|
||||
setup_options = {}
|
||||
extension_kwargs = {}
|
||||
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10):
|
||||
# Intentionally NOT defining Py_LIMITED_API as a C macro,
|
||||
# but still tagging the wheel as abi3.
|
||||
extension_kwargs["py_limited_api"] = True
|
||||
setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}}
|
||||
"""
|
||||
),
|
||||
spam_c_function_add=textwrap.dedent(
|
||||
r"""
|
||||
// Call a function not in the stable ABI until Python 3.13.
|
||||
// Without Py_LIMITED_API defined, the compiler allows it.
|
||||
PyObject *str_obj = PyUnicode_FromString(content);
|
||||
const char *utf8 = PyUnicode_AsUTF8(str_obj);
|
||||
(void)utf8;
|
||||
Py_DECREF(str_obj);
|
||||
"""
|
||||
),
|
||||
setup_py_extension_args_add="**extension_kwargs",
|
||||
setup_py_setup_args_add="options=setup_options",
|
||||
)
|
||||
|
||||
violating_abi3_project.files["pyproject.toml"] = pyproject_toml
|
||||
|
||||
|
||||
@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)")
|
||||
def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||
"""Test that abi3audit runs automatically on abi3 wheels."""
|
||||
project_dir = tmp_path / "project"
|
||||
limited_api_project.generate(project_dir)
|
||||
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
# Let's only build one cpython version to keep the test fast.
|
||||
"CIBW_BUILD": "cp310-*",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
)
|
||||
|
||||
assert len(actual_wheels) >= 1
|
||||
|
||||
captured = capfd.readouterr()
|
||||
assert "Running audit command: abi3audit" in captured.out
|
||||
|
||||
|
||||
def test_abi3audit_skipped_for_non_abi3_wheel(
|
||||
tmp_path: Path, capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that abi3audit does not run for non-abi3 wheels."""
|
||||
project_dir = tmp_path / "project"
|
||||
basic_project = test_projects.new_c_project()
|
||||
basic_project.generate(project_dir)
|
||||
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
single_python=True,
|
||||
)
|
||||
|
||||
assert len(actual_wheels) >= 1
|
||||
|
||||
captured = capfd.readouterr()
|
||||
assert "Running audit command: abi3audit" not in captured.out
|
||||
|
||||
|
||||
@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)")
|
||||
def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||
"""Test that abi3audit catches stable ABI violations and fails the build.
|
||||
|
||||
This project tags the wheel as cp310-abi3 but uses PyUnicode_AsUTF8,
|
||||
which was not part of the stable ABI until Python 3.13.
|
||||
"""
|
||||
project_dir = tmp_path / "project"
|
||||
violating_abi3_project.generate(project_dir)
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_BUILD": "cp310-*",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
)
|
||||
|
||||
captured = capfd.readouterr()
|
||||
assert "Running audit command: abi3audit" in captured.out
|
||||
|
||||
|
||||
def test_custom_audit_command(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||
project_dir = tmp_path / "project"
|
||||
test_projects.new_c_project().generate(project_dir)
|
||||
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_AUDIT_COMMAND": "echo custom-audit-ok {wheel}",
|
||||
"CIBW_AUDIT_REQUIRES": "",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
single_python=True,
|
||||
)
|
||||
|
||||
assert len(actual_wheels) >= 1
|
||||
captured = capfd.readouterr()
|
||||
assert "Auditing wheel" in captured.out
|
||||
assert "custom-audit-ok" in captured.out
|
||||
|
||||
|
||||
def test_custom_audit_requires(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||
project_dir = tmp_path / "project"
|
||||
test_projects.new_c_project().generate(project_dir)
|
||||
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_AUDIT_REQUIRES": "pycowsay",
|
||||
"CIBW_AUDIT_COMMAND": "pycowsay moo {wheel}",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
single_python=True,
|
||||
)
|
||||
|
||||
assert len(actual_wheels) >= 1
|
||||
captured = capfd.readouterr()
|
||||
assert "Installing audit dependencies: pycowsay" in captured.out
|
||||
assert "moo" in captured.out
|
||||
|
||||
|
||||
def test_empty_audit_command_disables_audit(
|
||||
tmp_path: Path, capfd: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
project_dir = tmp_path / "project"
|
||||
test_projects.new_c_project().generate(project_dir)
|
||||
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_AUDIT_COMMAND": "",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
single_python=True,
|
||||
)
|
||||
|
||||
assert len(actual_wheels) >= 1
|
||||
captured = capfd.readouterr()
|
||||
assert "Auditing wheel" not in captured.out
|
||||
|
||||
|
||||
def test_custom_audit_command_failure(tmp_path: Path) -> None:
|
||||
project_dir = tmp_path / "project"
|
||||
test_projects.new_c_project().generate(project_dir)
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env={
|
||||
"CIBW_AUDIT_COMMAND": 'python -c "import sys; sys.exit(1)" {wheel}',
|
||||
"CIBW_AUDIT_REQUIRES": "",
|
||||
"CIBW_ARCHS": "native",
|
||||
},
|
||||
single_python=True,
|
||||
)
|
||||
@@ -14,6 +14,7 @@ build-backend = "setuptools.build_meta"
|
||||
limited_api_project = test_projects.new_c_project(
|
||||
setup_py_add=textwrap.dedent(
|
||||
r"""
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
IS_CPYTHON = sys.implementation.name == "cpython"
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import contextlib
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from cibuildwheel import errors
|
||||
from cibuildwheel.audit import needs_audit, run_audit
|
||||
|
||||
|
||||
def mock_virtualenv() -> contextlib.AbstractContextManager[Mock]:
|
||||
return patch(
|
||||
"cibuildwheel.audit.virtualenv",
|
||||
return_value={
|
||||
"PATH": "/bin",
|
||||
"VIRTUAL_ENV": "/tmp/v",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestNeedsAudit:
|
||||
def test_empty_commands(self) -> None:
|
||||
assert needs_audit([], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") is False
|
||||
|
||||
def test_wheel_placeholder_matches_any_wheel(self) -> None:
|
||||
assert needs_audit(
|
||||
["my-tool {wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
)
|
||||
|
||||
def test_abi3_placeholder_skips_non_abi3(self) -> None:
|
||||
assert (
|
||||
needs_audit(
|
||||
["abi3audit {abi3_wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
def test_abi3_placeholder_matches_abi3(self) -> None:
|
||||
assert needs_audit(
|
||||
["abi3audit {abi3_wheel}"], "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
||||
)
|
||||
|
||||
def test_mixed_commands_matches_if_any_applies(self) -> None:
|
||||
commands = ["abi3audit {abi3_wheel}", "twine check {wheel}"]
|
||||
# non-abi3 wheel still needs audit because of the {wheel} command
|
||||
assert needs_audit(commands, "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")
|
||||
|
||||
def test_command_without_placeholder_raises(self) -> None:
|
||||
with pytest.raises(errors.ConfigurationError, match="must contain either"):
|
||||
needs_audit(["my-tool"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")
|
||||
|
||||
|
||||
class TestRunAudit:
|
||||
@pytest.fixture
|
||||
def mock_build_options(self) -> Mock:
|
||||
opts = Mock()
|
||||
opts.audit_command = []
|
||||
opts.audit_requires = []
|
||||
opts.package_dir = Path("/fake/package")
|
||||
opts.build_frontend.name = "build"
|
||||
opts.dependency_constraints.get_for_python_version.return_value = None
|
||||
return opts
|
||||
|
||||
def test_no_commands_does_nothing(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = []
|
||||
|
||||
with patch("cibuildwheel.audit.shell") as mock_shell:
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
mock_shell.assert_not_called()
|
||||
|
||||
def test_runs_wheel_command(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["my-tool {wheel}"]
|
||||
|
||||
with mock_virtualenv(), patch("cibuildwheel.audit.shell") as mock_shell:
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
mock_shell.assert_called_once()
|
||||
cmd = mock_shell.call_args[0][0]
|
||||
assert str(wheel) in cmd
|
||||
|
||||
def test_abi3_command_skipped_for_non_abi3(
|
||||
self, tmp_path: Path, mock_build_options: Mock
|
||||
) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["abi3audit {abi3_wheel}"]
|
||||
|
||||
with patch("cibuildwheel.audit.shell") as mock_shell:
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
mock_shell.assert_not_called()
|
||||
|
||||
def test_abi3_command_runs_for_abi3(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["abi3audit {abi3_wheel}"]
|
||||
|
||||
with (
|
||||
mock_virtualenv(),
|
||||
patch("cibuildwheel.audit.shell") as mock_shell,
|
||||
):
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
mock_shell.assert_called_once()
|
||||
cmd = mock_shell.call_args[0][0]
|
||||
assert str(wheel) in cmd
|
||||
|
||||
def test_raises_on_command_failure(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["failing-tool {wheel}"]
|
||||
|
||||
with (
|
||||
mock_virtualenv(),
|
||||
patch(
|
||||
"cibuildwheel.audit.shell",
|
||||
side_effect=subprocess.CalledProcessError(1, "failing-tool"),
|
||||
),
|
||||
pytest.raises(errors.AuditCommandFailedError),
|
||||
):
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
|
||||
def test_multiple_commands_all_run(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["tool-a {wheel}", "tool-b {wheel}"]
|
||||
|
||||
with (
|
||||
mock_virtualenv(),
|
||||
patch("cibuildwheel.audit.shell") as mock_shell,
|
||||
):
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
assert mock_shell.call_count == 2
|
||||
|
||||
def test_both_placeholders_raises(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
||||
wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
||||
mock_build_options.audit_command = ["my-tool {wheel} {abi3_wheel}"]
|
||||
|
||||
with (
|
||||
mock_virtualenv(),
|
||||
pytest.raises(errors.ConfigurationError, match="cannot contain both"),
|
||||
):
|
||||
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
||||
@@ -301,6 +301,30 @@ def test_test_requires(
|
||||
assert build_options.test_requires == (test_requires or "").split()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("audit_requires", [None, "abi3audit", "abi3audit custom-audit-tool"])
|
||||
@pytest.mark.parametrize("platform_specific", [False, True])
|
||||
def test_audit_requires(
|
||||
audit_requires: str | None,
|
||||
platform_specific: bool,
|
||||
platform: str,
|
||||
intercepted_build_args: "ArgsInterceptor",
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
if audit_requires is not None:
|
||||
if platform_specific:
|
||||
monkeypatch.setenv("CIBW_AUDIT_REQUIRES_" + platform.upper(), audit_requires)
|
||||
monkeypatch.setenv("CIBW_AUDIT_REQUIRES", "overwritten")
|
||||
else:
|
||||
monkeypatch.setenv("CIBW_AUDIT_REQUIRES", audit_requires)
|
||||
|
||||
main()
|
||||
|
||||
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||
|
||||
expected = (audit_requires or "abi3audit").split()
|
||||
assert build_options.audit_requires == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_extras", [None, "extras"])
|
||||
@pytest.mark.parametrize("platform_specific", [False, True])
|
||||
def test_test_extras(
|
||||
|
||||
@@ -541,3 +541,162 @@ before-all = ["override2"]
|
||||
options_reader.get("config-settings", option_format=ShlexTableFormat())
|
||||
== "key1=value1 key2=override2 empty='' key3=value3"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_command_option(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-command = "abi3audit {abi3_wheel}"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||
assert (
|
||||
options_reader.get("audit-command", option_format=ListFormat(" && "))
|
||||
== "abi3audit {abi3_wheel}"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_command_option_list(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-command = ["first command", "second command"]
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||
assert (
|
||||
options_reader.get("audit-command", option_format=ListFormat(" && "))
|
||||
== "first command && second command"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_command_option_env(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml, platform=platform, env={"CIBW_AUDIT_COMMAND": "my-audit-tool {wheel}"}
|
||||
)
|
||||
assert (
|
||||
options_reader.get("audit-command", option_format=ListFormat(" && "))
|
||||
== "my-audit-tool {wheel}"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_requires_option(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = "abi3audit"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||
assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit"
|
||||
|
||||
|
||||
def test_audit_requires_option_list(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = ["abi3audit", "twine"]
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||
assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit twine"
|
||||
|
||||
|
||||
def test_audit_requires_option_env(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml, platform=platform, env={"CIBW_AUDIT_REQUIRES": "custom-audit-tool"}
|
||||
)
|
||||
assert (
|
||||
options_reader.get("audit-requires", option_format=ListFormat(" ")) == "custom-audit-tool"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_requires_option_env_override(tmp_path: Path, platform: PlatformName) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = "abi3audit"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml, platform=platform, env={"CIBW_AUDIT_REQUIRES": "custom-audit-tool"}
|
||||
)
|
||||
assert (
|
||||
options_reader.get("audit-requires", option_format=ListFormat(" ")) == "custom-audit-tool"
|
||||
)
|
||||
|
||||
|
||||
def test_audit_requires_platform_specific(tmp_path: Path) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = "abi3audit"
|
||||
|
||||
[tool.cibuildwheel.linux]
|
||||
audit-requires = ["abi3audit", "check-wheel-contents"] # whatever
|
||||
|
||||
[tool.cibuildwheel.macos]
|
||||
audit-requires = ["check-wheel-contents", "pydistcheck"] # whatever
|
||||
"""
|
||||
)
|
||||
|
||||
linux_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||
assert (
|
||||
linux_reader.get("audit-requires", option_format=ListFormat(" "))
|
||||
== "abi3audit check-wheel-contents"
|
||||
)
|
||||
|
||||
macos_reader = OptionsReader(pyproject_toml, platform="macos", env={})
|
||||
assert (
|
||||
macos_reader.get("audit-requires", option_format=ListFormat(" "))
|
||||
== "check-wheel-contents pydistcheck"
|
||||
)
|
||||
|
||||
windows_reader = OptionsReader(pyproject_toml, platform="windows", env={})
|
||||
assert windows_reader.get("audit-requires", option_format=ListFormat(" ")) == "abi3audit"
|
||||
|
||||
|
||||
def test_audit_requires_platform_env_override(tmp_path: Path) -> None:
|
||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||
pyproject_toml.write_text(
|
||||
"""
|
||||
[tool.cibuildwheel]
|
||||
audit-requires = "abi3audit"
|
||||
"""
|
||||
)
|
||||
|
||||
options_reader = OptionsReader(
|
||||
pyproject_toml,
|
||||
platform="linux",
|
||||
env={
|
||||
"CIBW_AUDIT_REQUIRES": "some-fallback-tool",
|
||||
"CIBW_AUDIT_REQUIRES_LINUX": "linux-audit-tool",
|
||||
},
|
||||
)
|
||||
assert options_reader.get("audit-requires", option_format=ListFormat(" ")) == "linux-audit-tool"
|
||||
|
||||
+21
-1
@@ -15,7 +15,7 @@ from cibuildwheel.util.helpers import (
|
||||
unwrap,
|
||||
unwrap_preserving_paragraphs,
|
||||
)
|
||||
from cibuildwheel.util.packaging import find_compatible_wheel
|
||||
from cibuildwheel.util.packaging import find_compatible_wheel, is_abi3_wheel
|
||||
|
||||
|
||||
def test_format_safe() -> None:
|
||||
@@ -401,3 +401,23 @@ def test_unwrap_preserving_paragraphs() -> None:
|
||||
""")
|
||||
== "paragraph one\n\nparagraph two"
|
||||
)
|
||||
|
||||
|
||||
class TestIsAbi3Wheel:
|
||||
def test_abi3_wheel(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-cp310-abi3-manylinux_2_28_x86_64.whl") is True
|
||||
|
||||
def test_abi3_wheel_macos(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-cp311-abi3-macosx_11_0_arm64.whl") is True
|
||||
|
||||
def test_abi3_wheel_windows(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-cp310-abi3-win_amd64.whl") is True
|
||||
|
||||
def test_cpython_wheel(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-cp310-cp310-manylinux_2_28_x86_64.whl") is False
|
||||
|
||||
def test_none_any_wheel(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-py3-none-any.whl") is False
|
||||
|
||||
def test_none_platform_wheel(self) -> None:
|
||||
assert is_abi3_wheel("foo-1.0-cp310-none-win_amd64.whl") is False
|
||||
|
||||
Reference in New Issue
Block a user