Skip to content

Commit

Permalink
setup: Add support for macOS arm64/universal2 wheels. (#5583)
Browse files Browse the repository at this point in the history
Now `python setup.py wheel_darwin_64bit` will generate a wheel whose
platform (architecture and macOS deployment target) matches those of
the bootloaders currently present in `PyInstaller/bootloader/Darwin-64bit`.

An unfortunate, but minor, side-effect of this change is that PyInstaller
itself must be installed to run `setup.py wheel_darwin_64bit`. As
this command isn't needed to install PyInstaller, we avoid any need
for bootstrap-installing.
  • Loading branch information
bwoodsend committed Jun 3, 2021
1 parent 80936c6 commit 357d33a
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 8 deletions.
28 changes: 25 additions & 3 deletions PyInstaller/utils/osx.py
Expand Up @@ -101,13 +101,35 @@ def get_macos_sdk_version(filename):
header = binary.headers[0]
# Find version command using helper
version_cmd = _find_version_cmd(header)
return _hex_triplet(version_cmd[1].sdk)


def _hex_triplet(version):
# Parse SDK version number
major = (version_cmd[1].sdk & 0xFF0000) >> 16
minor = (version_cmd[1].sdk & 0xFF00) >> 8
revision = (version_cmd[1].sdk & 0xFF)
major = (version & 0xFF0000) >> 16
minor = (version & 0xFF00) >> 8
revision = (version & 0xFF)
return major, minor, revision


def macosx_version_min(filename: str) -> tuple:
"""Get the -macosx-version-min used to compile a macOS binary.
For fat binaries, the minimum version is selected.
"""
versions = []
for header in MachO(filename).headers:
cmd = _find_version_cmd(header)
if cmd[0].cmd == LC_VERSION_MIN_MACOSX:
versions.append(cmd[1].version)
else:
# macOS >= 10.14 uses LC_BUILD_VERSION instead.
versions.append(cmd[1].minos)

return min(map(_hex_triplet, versions))


def set_macos_sdk_version(filename, major, minor, revision):
"""
Overwrite the macOS SDK version declared in the given binary with
Expand Down
50 changes: 45 additions & 5 deletions setup.py
Expand Up @@ -147,11 +147,8 @@ def has_bootloaders(cls):
# must check/update this tag.
"Linux-64bit": "manylinux2014_x86_64",
"Linux-32bit": "manylinux2014_i686",
# The macOS version must be kept in sync with the -mmacosx-version-min in
# the waf build script.
# TODO: Once we start shipping universal2 bootloaders and PyPA have
# decided what the wheel tag should be, we will need to set it here.
"Darwin-64bit": "macosx_10_13_x86_64",
# macOS needs special handling. This gets done dynamically later.
"Darwin-64bit": None,
}

# Create a subclass of Wheel() for each platform.
Expand All @@ -168,6 +165,49 @@ def has_bootloaders(cls):
wheel_commands[command_name] = command


class bdist_macos(wheel_commands["wheel_darwin_64bit"]):
def finalize_options(self):
"""Choose a platform tag that reflects the platform of the bootloaders.
Namely:
* The minimum supported macOS version should mirror that of the
bootloaders.
* The architecture should similarly mirror the bootloader
architecture(s).
"""
try:
from PyInstaller.utils.osx import get_binary_architectures,\
macosx_version_min
except ImportError:
raise SystemExit(
"Building wheels for macOS requires that PyInstaller and "
"macholib be installed. Please run:\n"
" pip install -e . macholib")

bootloader = os.path.join(self.bootloaders_dir(), "run")
is_fat, architectures = get_binary_architectures(bootloader)

if is_fat and sorted(architectures) == ["arm64", "x86_64"]:
# An arm64 + x86_64 dual architecture binary gets the special name
# universal2.
architectures = "universal2"
else:
# It's unlikely that there will be other multi-architecture types
# but if one crops up, the syntax is to join them with underscores.
architectures = "_".join(architectures)

# Fetch the macOS deployment target the bootloaders are compiled with
# and set that in the tag too.
version = "_".join(map(str, macosx_version_min(bootloader)[:2]))

self.PLAT_NAME = f"macosx_{version}_{architectures}"
super().finalize_options()


wheel_commands["wheel_darwin_64bit"] = bdist_macos


class bdist_wheels(Command):
"""Build a wheel for every platform listed in the PLATFORMS dict which has
bootloaders available in `PyInstaller/bootloaders/[platform-name]`.
Expand Down

0 comments on commit 357d33a

Please sign in to comment.