Skip to content

Commit

Permalink
Suppress macholib header parse error
Browse files Browse the repository at this point in the history
Macholib raises ValueError when failing to parse files which can be used
to detect if the file is a Mach-O object.
It is also raised on Mach-O objects it doesn't know how to handle.

Checking for this error means `_is_macho_file` is redundant.

Add min version tests for various file types.
These tests clarify that static libraries don't have min version info.
  • Loading branch information
HexDecimal committed Jan 16, 2025
1 parent 881a4cb commit 6b809ff
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ rules on making a good Changelog.
- `delocate-merge` now supports libraries with missing or unusual extensions.
[#228](https://github.com/matthew-brett/delocate/issues/228)
- Now supports library files ending in parentheses.
- Fixed `Unknown Mach-O header` error when encountering a fat static library.
[#229](https://github.com/matthew-brett/delocate/issues/229)

### Removed

Expand Down
22 changes: 16 additions & 6 deletions delocate/delocating.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
from .pkginfo import read_pkg_info, write_pkg_info
from .tmpdirs import TemporaryDirectory
from .tools import (
_is_macho_file,
_remove_absolute_rpaths,
dir2zip,
find_package_dirs,
Expand Down Expand Up @@ -584,12 +583,14 @@ def _make_install_name_ids_unique(
validate_signature(lib)


def _get_macos_min_version(dylib_path: Path) -> Iterator[tuple[str, Version]]:
def _get_macos_min_version(
dylib_path: str | os.PathLike[str],
) -> Iterator[tuple[str, Version]]:
"""Get the minimum macOS version from a dylib file.
Parameters
----------
dylib_path : Path
dylib_path : str or PathLike
The path to the dylib file.
Yields
Expand All @@ -599,9 +600,16 @@ def _get_macos_min_version(dylib_path: Path) -> Iterator[tuple[str, Version]]:
Version
The minimum macOS version.
"""
if not _is_macho_file(dylib_path):
return
for header in MachO(dylib_path).headers:
try:
macho = MachO(dylib_path)
except ValueError as exc:
if str(exc.args[0]).startswith(
("Unknown fat header magic", "Unknown Mach-O header")
):
return # Not a recognised Mach-O object file
raise # pragma: no cover # Unexpected error

for header in macho.headers:
for cmd in header.commands:
if cmd[0].cmd == LC_BUILD_VERSION:
version = cmd[1].minos
Expand Down Expand Up @@ -798,6 +806,8 @@ def _calculate_minimum_wheel_name(
all_library_versions: dict[str, dict[Version, list[Path]]] = {}

for lib in wheel_dir.glob("**/*"):
if lib.is_dir():
continue
for arch, version in _get_macos_min_version(lib):
all_library_versions.setdefault(arch.lower(), {}).setdefault(
version, []
Expand Down
42 changes: 41 additions & 1 deletion delocate/tests/test_delocating.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Tests for relocating libraries."""

from __future__ import annotations

import os
import shutil
import subprocess
Expand All @@ -8,6 +10,7 @@
from collections.abc import Iterable
from os.path import basename, dirname, realpath, relpath, splitext
from os.path import join as pjoin
from pathlib import Path
from typing import Any, Callable

import pytest
Expand All @@ -16,6 +19,7 @@

from ..delocating import (
_get_archs_and_version_from_wheel_name,
_get_macos_min_version,
bads_report,
check_archs,
copy_recurse,
Expand All @@ -33,7 +37,18 @@
from ..tools import get_install_names, set_install_name
from .env_tools import TempDirWithoutEnvVars
from .pytest_tools import assert_equal, assert_raises
from .test_install_names import EXT_LIBS, LIBA, LIBB, LIBC, TEST_LIB, _copy_libs
from .test_install_names import (
A_OBJECT,
DATA_PATH,
EXT_LIBS,
ICO_FILE,
LIBA,
LIBA_STATIC,
LIBB,
LIBC,
TEST_LIB,
_copy_libs,
)
from .test_tools import (
ARCH_32,
ARCH_64,
Expand Down Expand Up @@ -747,3 +762,28 @@ def test_get_archs_and_version_from_wheel_name() -> None:
_get_archs_and_version_from_wheel_name(
"foo-1.0-py310-abi3-manylinux1.whl"
)


@pytest.mark.parametrize(
"file,expected_min_version",
[
# Dylib files
(LIBBOTH, {"ARM64": Version("11.0"), "x86_64": Version("10.9")}),
(LIBA, {"x86_64": Version("10.9")}),
# Shared objects
(
Path(DATA_PATH, "np-1.6.0_intel_lib__compiled_base.so"),
{"i386": Version("10.6"), "x86_64": Version("10.6")},
),
# Object file
(A_OBJECT, {"x86_64": Version("10.9")}),
# Static file
(LIBA_STATIC, {}),
# Non library
(ICO_FILE, {}),
],
)
def test_get_macos_min_version(
file: str | Path, expected_min_version: dict[str, Version]
) -> None:
assert dict(_get_macos_min_version(file)) == expected_min_version

0 comments on commit 6b809ff

Please sign in to comment.