Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/auditwheel/main_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import argparse
import logging
import shutil
import zlib
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -124,6 +125,13 @@ def configure_parser(sub_parsers: Any) -> None: # noqa: ANN401
help="Do not check for extended ISA compatibility (e.g. x86_64_v2)",
default=False,
)
parser.add_argument(
"--allow-pure-python-wheel",
dest="ALLOW_PURE_PY_WHEEL",
action="store_true",
help="Allow processing of pure Python wheels (no platform-specific binaries) without error",
default=False,
)
parser.set_defaults(func=execute)


Expand Down Expand Up @@ -151,6 +159,7 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:

wheel_filename = wheel_file.name
arch = requested_architecture
is_pure_python = False
try:
arch = get_wheel_architecture(wheel_filename)
if requested_architecture is not None and requested_architecture != arch:
Expand All @@ -159,10 +168,11 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
f"wheel targeting {requested_architecture.value}"
)
parser.error(msg)
except (WheelToolsError, NonPlatformWheelError):
except (WheelToolsError, NonPlatformWheelError) as e:
logger.warning(
"The architecture could not be deduced from the wheel filename",
)
is_pure_python = isinstance(e, NonPlatformWheelError)

try:
libc = get_wheel_libc(wheel_filename)
Expand Down Expand Up @@ -206,6 +216,12 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
)
except NonPlatformWheelError as e:
logger.info(e.message)
if is_pure_python and args.ALLOW_PURE_PY_WHEEL:
dest_fname = wheel_dir / wheel_file.name
if not dest_fname.is_file() or not dest_fname.samefile(wheel_file):
shutil.copy2(wheel_file, dest_fname)
# process next wheel
continue
return 1

policies = wheel_abi.policies
Expand Down
13 changes: 12 additions & 1 deletion src/auditwheel/main_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ def configure_parser(sub_parsers: Any) -> None: # noqa: ANN401
help="Do not check for extended ISA compatibility (e.g. x86_64_v2)",
default=False,
)
p.add_argument(
"--allow-pure-python-wheel",
dest="ALLOW_PURE_PY_WHEEL",
action="store_true",
help="Allow processing of pure Python wheels (no platform-specific binaries) without error",
default=False,
)
p.set_defaults(func=execute)


Expand All @@ -44,10 +51,12 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
parser.error(f"cannot access {wheel_file}. No such file")

fn = wheel_file.name
is_pure_python = False
try:
arch = get_wheel_architecture(fn)
except (WheelToolsError, NonPlatformWheelError):
except (WheelToolsError, NonPlatformWheelError) as e:
logger.warning("The architecture could not be deduced from the wheel filename")
is_pure_python = isinstance(e, NonPlatformWheelError)
arch = None

try:
Expand All @@ -67,6 +76,8 @@ def execute(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
)
except NonPlatformWheelError as e:
logger.info("%s", e.message)
if is_pure_python and args.ALLOW_PURE_PY_WHEEL:
return 0
return 1

policies = winfo.policies
Expand Down
81 changes: 75 additions & 6 deletions tests/integration/test_nonplatform_wheel.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from __future__ import annotations

import pathlib
import shutil
import subprocess
from pathlib import Path

import pytest

from auditwheel.architecture import Architecture

HERE = pathlib.Path(__file__).parent.resolve()
HERE = Path(__file__).parent.resolve()


@pytest.mark.parametrize("mode", ["repair", "show"])
Expand All @@ -24,15 +25,79 @@ def test_non_platform_wheel_pure(mode):
assert "AttributeError" not in proc.stderr


@pytest.mark.parametrize(
("mode", "samefile"),
[("repair", False), ("repair", True), ("show", False)],
)
def test_non_platform_wheel_pure_allow(mode: str, samefile: bool, tmp_path: Path) -> None:
wheel = HERE / "plumbum-1.6.8-py2.py3-none-any.whl"
dest_wheel = tmp_path / wheel.name
if samefile:
shutil.copy2(wheel, dest_wheel)
wheel = dest_wheel
args = ["auditwheel", mode, "--allow-pure-python-wheel", str(wheel)]
if mode == "repair":
args.extend(["-w", str(tmp_path)])
proc = subprocess.run(
args,
stderr=subprocess.PIPE,
text=True,
check=True,
)
assert "This does not look like a platform wheel" in proc.stderr
assert "AttributeError" not in proc.stderr
if mode == "repair":
assert dest_wheel.is_file()
assert dest_wheel.read_bytes() == wheel.read_bytes()
Comment thread
mayeut marked this conversation as resolved.


def test_non_platform_wheel_pure_allow_multiple(tmp_path: Path) -> None:
wheel = HERE / "plumbum-1.6.8-py2.py3-none-any.whl"
src_wheels = tmp_path / "src"
src_wheels.mkdir()
src_wheel1 = src_wheels / "plumbum-1.6.8-py2-none-any.whl"
src_wheel2 = src_wheels / "plumbum-1.6.8-py3-none-any.whl"
shutil.copy2(wheel, src_wheel1)
shutil.copy2(wheel, src_wheel2)
dst_wheels = tmp_path / "dst"
dst_wheel1 = dst_wheels / src_wheel1.name
dst_wheel2 = dst_wheels / src_wheel2.name

args = [
"auditwheel",
"repair",
"-w",
str(dst_wheels),
"--allow-pure-python-wheel",
str(src_wheel1),
str(src_wheel2),
]
proc = subprocess.run(
args,
stderr=subprocess.PIPE,
text=True,
check=True,
)
assert "This does not look like a platform wheel" in proc.stderr
assert "AttributeError" not in proc.stderr
for dst_wheel in [dst_wheel1, dst_wheel2]:
assert dst_wheel.is_file()
assert dst_wheel.read_bytes() == wheel.read_bytes()


@pytest.mark.parametrize("mode", ["repair", "show"])
@pytest.mark.parametrize("arch", ["armv5l", "mips64"])
def test_non_platform_wheel_unknown_arch(mode, arch, tmp_path):
@pytest.mark.parametrize("allow_pure_python", [True, False])
def test_non_platform_wheel_unknown_arch(mode, arch, allow_pure_python, tmp_path):
wheel_name = f"testsimple-0.0.1-cp313-cp313-linux_{arch}.whl"
wheel_path = HERE / "arch-wheels" / "glibc" / wheel_name
wheel_x86_64 = tmp_path / f"{wheel_path.stem}_x86_64.whl"
wheel_x86_64.symlink_to(wheel_path)
args = ["auditwheel", mode, str(wheel_x86_64)]
if allow_pure_python:
args.append("--allow-pure-python-wheel")
proc = subprocess.run(
["auditwheel", mode, str(wheel_x86_64)],
args,
stderr=subprocess.PIPE,
text=True,
check=False,
Expand All @@ -48,16 +113,20 @@ def test_non_platform_wheel_unknown_arch(mode, arch, tmp_path):
"arch",
["aarch64", "armv7l", "i686", "x86_64", "ppc64le", "s390x"],
)
def test_non_platform_wheel_bad_arch(mode, arch, tmp_path):
@pytest.mark.parametrize("allow_pure_python", [True, False])
def test_non_platform_wheel_bad_arch(mode, arch, allow_pure_python, tmp_path):
host_arch = Architecture.detect().value
if host_arch == arch:
pytest.skip("host architecture")
wheel_name = f"testsimple-0.0.1-cp313-cp313-linux_{arch}.whl"
wheel_path = HERE / "arch-wheels" / "glibc" / wheel_name
wheel_host = tmp_path / f"{wheel_path.stem}_{host_arch}.whl"
wheel_host.symlink_to(wheel_path)
args = ["auditwheel", mode, str(wheel_host)]
if allow_pure_python:
args.append("--allow-pure-python-wheel")
proc = subprocess.run(
["auditwheel", mode, str(wheel_host)],
args,
stderr=subprocess.PIPE,
text=True,
check=False,
Expand Down