Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to repair wheels for other architectures #512

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
36 changes: 36 additions & 0 deletions src/auditwheel/elfutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from typing import Iterator

from elftools.common.exceptions import ELFError
from elftools.elf.dynamic import DynamicSegment
from elftools.elf.elffile import ELFFile

from .lddtree import parse_ld_paths
from .libc import Libc


def elf_read_dt_needed(fn: str) -> list[str]:
Expand Down Expand Up @@ -161,3 +163,37 @@ def filter_undefined_symbols(
if intersection:
result[lib] = sorted(intersection)
return result


def elf_get_platform_info(path: str) -> tuple[Libc | None, str | None]:
with open(path, "rb") as f:
try:
elf = ELFFile(f)
except ELFError:
return (None, None)
arch = {
"x64": "x86_64",
"x86": "i686",
"AArch64": "aarch64",
"64-bit PowerPC": "ppc64",
"IBM S/390": "s390x",
"ARM": "armv7l",
"RISC-V": "riscv64",
}[elf.get_machine_arch()]
if arch == "ppc64" and elf.header.e_ident.EI_DATA == "ELFDATA2LSB":
arch = "ppc64le"

libc = None
for seg in elf.iter_segments():
if not isinstance(seg, DynamicSegment):
continue
for tag in seg.iter_tags():
if tag.entry.d_tag == "DT_NEEDED":
if tag.needed == "libc.so.6":
libc = Libc.GLIBC
break
if tag.needed.startswith("libc.musl-"):
libc = Libc.MUSL
break
break
return (libc, arch)
8 changes: 2 additions & 6 deletions src/auditwheel/libc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
from enum import IntEnum

from .error import InvalidLibc
from .musllinux import find_musl_libc

logger = logging.getLogger(__name__)
Expand All @@ -15,10 +14,7 @@ class Libc(IntEnum):


def get_libc() -> Libc:
try:
find_musl_libc()
if find_musl_libc() is not None:
logger.debug("Detected musl libc")
return Libc.MUSL
except InvalidLibc:
logger.debug("Falling back to GNU libc")
return Libc.GLIBC
return Libc.GLIBC
32 changes: 26 additions & 6 deletions src/auditwheel/main_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,20 @@ def configure_parser(sub_parsers):
p.add_argument(
"--plat",
action=EnvironmentDefault,
required=False,
metavar="PLATFORM",
env="AUDITWHEEL_PLAT",
dest="PLAT",
help="Desired target platform. See the available platforms under the "
f'PLATFORMS section below. (default: "{highest_policy}")',
f'PLATFORMS section below. (default on current arch: "{highest_policy}")',
choices=policy_names,
default=highest_policy,
default=None,
)
p.add_argument(
"--best-plat",
action="store_true",
dest="BEST_PLAT",
help="Automatically determine the best target platform.",
)
p.add_argument(
"-L",
Expand Down Expand Up @@ -115,26 +122,36 @@ def execute(args, p):
for wheel_file in args.WHEEL_FILE:
if not isfile(wheel_file):
p.error("cannot access %s. No such file" % wheel_file)
wheel_policy.set_platform_from_wheel(wheel_file)

logger.info("Repairing %s", basename(wheel_file))

if not exists(args.WHEEL_DIR):
os.makedirs(args.WHEEL_DIR)

try:
wheel_abi = analyze_wheel_abi(wheel_policy, wheel_file, exclude)
except NonPlatformWheel:
logger.info(NonPlatformWheel.LOG_MESSAGE)
return 1

if args.BEST_PLAT:
if args.PLAT:
p.error("Cannot specify both --best-plat and --plat")
args.PLAT = wheel_abi.overall_tag

if not exists(args.WHEEL_DIR):
os.makedirs(args.WHEEL_DIR)

highest_policy = wheel_policy.get_policy_name(wheel_policy.priority_highest)
if args.PLAT is None:
args.PLAT = highest_policy
policy = wheel_policy.get_policy_by_name(args.PLAT)
reqd_tag = policy["priority"]

if reqd_tag > wheel_policy.get_priority_by_name(wheel_abi.sym_tag):
msg = (
'cannot repair "%s" to "%s" ABI because of the presence '
"of too-recent versioned symbols. You'll need to compile "
"the wheel on an older toolchain." % (wheel_file, args.PLAT)
"the wheel on an older toolchain or pick a newer platform."
% (wheel_file, args.PLAT)
)
p.error(msg)

Expand Down Expand Up @@ -184,3 +201,6 @@ def execute(args, p):

if out_wheel is not None:
logger.info("\nFixed-up wheel written to %s", out_wheel)

if args.BEST_PLAT:
args.PLAT = None
2 changes: 2 additions & 0 deletions src/auditwheel/main_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def execute(args, p):
if not isfile(args.WHEEL_FILE):
p.error("cannot access %s. No such file" % args.WHEEL_FILE)

wheel_policy.set_platform_from_wheel(args.WHEEL_FILE)

try:
winfo = analyze_wheel_abi(wheel_policy, args.WHEEL_FILE, frozenset())
except NonPlatformWheel:
Expand Down
41 changes: 18 additions & 23 deletions src/auditwheel/musllinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import logging
import pathlib
import re
import subprocess
from typing import NamedTuple

from auditwheel.error import InvalidLibc

LOG = logging.getLogger(__name__)
VERSION_RE = re.compile(b"[^.](?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)\0")


class MuslVersion(NamedTuple):
Expand All @@ -17,31 +15,28 @@ class MuslVersion(NamedTuple):
patch: int


def find_musl_libc() -> pathlib.Path:
def find_musl_libc(library_path: str | None = None) -> pathlib.Path | None:
try:
(dl_path,) = list(pathlib.Path("/lib").glob("libc.musl-*.so.1"))
(dl_path,) = list(pathlib.Path(library_path or "/lib").glob("libc.musl-*.so.1"))
except ValueError:
LOG.debug("musl libc not detected")
raise InvalidLibc
return None

return dl_path


def get_musl_version(ld_path: pathlib.Path) -> MuslVersion:
def get_musl_version(ld_path: pathlib.Path) -> MuslVersion | None:
try:
ld = subprocess.run(
[ld_path], check=False, errors="strict", stderr=subprocess.PIPE
).stderr
with open(ld_path, "rb") as fp:
text = fp.read()
except FileNotFoundError:
LOG.error("Failed to determine musl version", exc_info=True)
raise InvalidLibc

match = re.search(
r"Version " r"(?P<major>\d+)." r"(?P<minor>\d+)." r"(?P<patch>\d+)", ld
)
if not match:
raise InvalidLibc

return MuslVersion(
int(match.group("major")), int(match.group("minor")), int(match.group("patch"))
)
return None

for match in VERSION_RE.finditer(text):
return MuslVersion(
int(match.group("major")),
int(match.group("minor")),
int(match.group("patch")),
)

LOG.error("Failed to determine musl version", exc_info=True)
return None
Loading