Skip to content

Commit

Permalink
fix: macos deployment target selection (#1552)
Browse files Browse the repository at this point in the history
  • Loading branch information
henryiii committed Jun 5, 2024
1 parent 861bdc8 commit 64005a2
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 21 deletions.
58 changes: 58 additions & 0 deletions backend/src/hatchling/builders/macos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

import os
import platform
import re

__all__ = ['process_macos_plat_tag']


def process_macos_plat_tag(plat: str, /, *, compat: bool) -> str:
"""
Process the macOS platform tag. This will normalize the macOS version to
10.16 if compat=True. If the MACOSX_DEPLOYMENT_TARGET environment variable
is set, then it will be used instead for the target version. If archflags
is set, then the archs will be respected, including a universal build.
"""
# Default to a native build
current_arch = platform.machine()
arm = current_arch == 'arm64'

# Look for cross-compiles
archflags = os.environ.get('ARCHFLAGS', '')
if archflags and (archs := re.findall(r'-arch (\S+)', archflags)):
new_arch = 'universal2' if set(archs) == {'x86_64', 'arm64'} else archs[0]
arm = archs == ['arm64']
plat = f'{plat[: plat.rfind(current_arch)]}{new_arch}'

# Process macOS version
if sdk_match := re.search(r'macosx_(\d+_\d+)', plat):
macos_version = sdk_match.group(1)
target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', None)

try:
new_version = normalize_macos_version(target or macos_version, arm=arm, compat=compat)
except ValueError:
new_version = normalize_macos_version(macos_version, arm=arm, compat=compat)

return plat.replace(macos_version, new_version, 1)

return plat


def normalize_macos_version(version: str, *, arm: bool, compat: bool) -> str:
"""
Set minor version to 0 if major is 11+. Enforces 11+ if arm=True. 11+ is
converted to 10.16 if compat=True. Version is always returned in
"major_minor" format.
"""
version = version.replace('.', '_')
if '_' not in version:
version = f'{version}_0'
major, minor = (int(d) for d in version.split('_')[:2])
major = max(major, 11) if arm else major
minor = 0 if major >= 11 else minor # noqa: PLR2004
if compat and major >= 11: # noqa: PLR2004
major = 10
minor = 16
return f'{major}_{minor}'
24 changes: 3 additions & 21 deletions backend/src/hatchling/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,28 +783,10 @@ def get_best_matching_tag(self) -> str:
tag = next(iter(t for t in sys_tags() if 'manylinux' not in t.platform and 'musllinux' not in t.platform))
tag_parts = [tag.interpreter, tag.abi, tag.platform]

archflags = os.environ.get('ARCHFLAGS', '')
if sys.platform == 'darwin':
if archflags and sys.version_info[:2] >= (3, 8):
import platform
import re

archs = re.findall(r'-arch (\S+)', archflags)
if archs:
plat = tag_parts[2]
current_arch = platform.mac_ver()[2]
new_arch = 'universal2' if set(archs) == {'x86_64', 'arm64'} else archs[0]
tag_parts[2] = f'{plat[: plat.rfind(current_arch)]}{new_arch}'

if self.config.macos_max_compat:
import re

plat = tag_parts[2]
sdk_match = re.search(r'macosx_(\d+_\d+)', plat)
if sdk_match:
sdk_version_part = sdk_match.group(1)
if tuple(map(int, sdk_version_part.split('_'))) >= (11, 0):
tag_parts[2] = plat.replace(sdk_version_part, '10_16', 1)
from hatchling.builders.macos import process_macos_plat_tag

tag_parts[2] = process_macos_plat_tag(tag_parts[2], compat=self.config.macos_max_compat)

return '-'.join(tag_parts)

Expand Down
56 changes: 56 additions & 0 deletions tests/backend/utils/test_macos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import annotations

import platform

import pytest

from hatchling.builders.macos import normalize_macos_version, process_macos_plat_tag


@pytest.mark.parametrize(
('plat', 'arch', 'compat', 'archflags', 'deptarget', 'expected'),
[
('macosx_10_9_x86_64', 'x86_64', False, '', '', 'macosx_10_9_x86_64'),
('macosx_11_9_x86_64', 'x86_64', False, '', '', 'macosx_11_0_x86_64'),
('macosx_12_0_x86_64', 'x86_64', True, '', '', 'macosx_10_16_x86_64'),
('macosx_10_9_arm64', 'arm64', False, '', '', 'macosx_11_0_arm64'),
('macosx_10_9_arm64', 'arm64', False, '-arch x86_64 -arch arm64', '', 'macosx_10_9_universal2'),
('macosx_10_9_x86_64', 'x86_64', False, '-arch x86_64 -arch arm64', '', 'macosx_10_9_universal2'),
('macosx_10_9_x86_64', 'x86_64', False, '-arch x86_64 -arch arm64', '12', 'macosx_12_0_universal2'),
('macosx_10_9_x86_64', 'x86_64', False, '-arch arm64', '12.4', 'macosx_12_0_arm64'),
('macosx_10_9_x86_64', 'x86_64', False, '-arch arm64', '10.12', 'macosx_11_0_arm64'),
('macosx_10_9_x86_64', 'x86_64', True, '-arch arm64', '10.12', 'macosx_10_16_arm64'),
],
)
def test_process_macos_plat_tag(
monkeypatch: pytest.MonkeyPatch,
*,
plat: str,
arch: str,
compat: bool,
archflags: str,
deptarget: str,
expected: str,
) -> None:
monkeypatch.setenv('ARCHFLAGS', archflags)
monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', deptarget)
monkeypatch.setattr(platform, 'machine', lambda: arch)

assert process_macos_plat_tag(plat, compat=compat) == expected


@pytest.mark.parametrize(
('version', 'arm', 'compat', 'expected'),
[
('10_9', False, False, '10_9'),
('10_9', False, True, '10_9'),
('10_9', True, False, '11_0'),
('10_9', True, True, '10_9'),
('11_3', False, False, '11_0'),
('12_3', True, False, '12_0'),
('12_3', False, True, '10_16'),
('12_3', True, True, '10_16'),
],
)
def check_normalization(*, version: str, arm: bool, compat: bool, expected: str) -> None:
assert normalize_macos_version(version, arm=arm, compat=compat) == expected

0 comments on commit 64005a2

Please sign in to comment.