* 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>
133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
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
|