Skip to content

Commit 64005a2

Browse files
authored
fix: macos deployment target selection (#1552)
1 parent 861bdc8 commit 64005a2

File tree

3 files changed

+117
-21
lines changed

3 files changed

+117
-21
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import platform
5+
import re
6+
7+
__all__ = ['process_macos_plat_tag']
8+
9+
10+
def process_macos_plat_tag(plat: str, /, *, compat: bool) -> str:
11+
"""
12+
Process the macOS platform tag. This will normalize the macOS version to
13+
10.16 if compat=True. If the MACOSX_DEPLOYMENT_TARGET environment variable
14+
is set, then it will be used instead for the target version. If archflags
15+
is set, then the archs will be respected, including a universal build.
16+
"""
17+
# Default to a native build
18+
current_arch = platform.machine()
19+
arm = current_arch == 'arm64'
20+
21+
# Look for cross-compiles
22+
archflags = os.environ.get('ARCHFLAGS', '')
23+
if archflags and (archs := re.findall(r'-arch (\S+)', archflags)):
24+
new_arch = 'universal2' if set(archs) == {'x86_64', 'arm64'} else archs[0]
25+
arm = archs == ['arm64']
26+
plat = f'{plat[: plat.rfind(current_arch)]}{new_arch}'
27+
28+
# Process macOS version
29+
if sdk_match := re.search(r'macosx_(\d+_\d+)', plat):
30+
macos_version = sdk_match.group(1)
31+
target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', None)
32+
33+
try:
34+
new_version = normalize_macos_version(target or macos_version, arm=arm, compat=compat)
35+
except ValueError:
36+
new_version = normalize_macos_version(macos_version, arm=arm, compat=compat)
37+
38+
return plat.replace(macos_version, new_version, 1)
39+
40+
return plat
41+
42+
43+
def normalize_macos_version(version: str, *, arm: bool, compat: bool) -> str:
44+
"""
45+
Set minor version to 0 if major is 11+. Enforces 11+ if arm=True. 11+ is
46+
converted to 10.16 if compat=True. Version is always returned in
47+
"major_minor" format.
48+
"""
49+
version = version.replace('.', '_')
50+
if '_' not in version:
51+
version = f'{version}_0'
52+
major, minor = (int(d) for d in version.split('_')[:2])
53+
major = max(major, 11) if arm else major
54+
minor = 0 if major >= 11 else minor # noqa: PLR2004
55+
if compat and major >= 11: # noqa: PLR2004
56+
major = 10
57+
minor = 16
58+
return f'{major}_{minor}'

backend/src/hatchling/builders/wheel.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -783,28 +783,10 @@ def get_best_matching_tag(self) -> str:
783783
tag = next(iter(t for t in sys_tags() if 'manylinux' not in t.platform and 'musllinux' not in t.platform))
784784
tag_parts = [tag.interpreter, tag.abi, tag.platform]
785785

786-
archflags = os.environ.get('ARCHFLAGS', '')
787786
if sys.platform == 'darwin':
788-
if archflags and sys.version_info[:2] >= (3, 8):
789-
import platform
790-
import re
791-
792-
archs = re.findall(r'-arch (\S+)', archflags)
793-
if archs:
794-
plat = tag_parts[2]
795-
current_arch = platform.mac_ver()[2]
796-
new_arch = 'universal2' if set(archs) == {'x86_64', 'arm64'} else archs[0]
797-
tag_parts[2] = f'{plat[: plat.rfind(current_arch)]}{new_arch}'
798-
799-
if self.config.macos_max_compat:
800-
import re
801-
802-
plat = tag_parts[2]
803-
sdk_match = re.search(r'macosx_(\d+_\d+)', plat)
804-
if sdk_match:
805-
sdk_version_part = sdk_match.group(1)
806-
if tuple(map(int, sdk_version_part.split('_'))) >= (11, 0):
807-
tag_parts[2] = plat.replace(sdk_version_part, '10_16', 1)
787+
from hatchling.builders.macos import process_macos_plat_tag
788+
789+
tag_parts[2] = process_macos_plat_tag(tag_parts[2], compat=self.config.macos_max_compat)
808790

809791
return '-'.join(tag_parts)
810792

tests/backend/utils/test_macos.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from __future__ import annotations
2+
3+
import platform
4+
5+
import pytest
6+
7+
from hatchling.builders.macos import normalize_macos_version, process_macos_plat_tag
8+
9+
10+
@pytest.mark.parametrize(
11+
('plat', 'arch', 'compat', 'archflags', 'deptarget', 'expected'),
12+
[
13+
('macosx_10_9_x86_64', 'x86_64', False, '', '', 'macosx_10_9_x86_64'),
14+
('macosx_11_9_x86_64', 'x86_64', False, '', '', 'macosx_11_0_x86_64'),
15+
('macosx_12_0_x86_64', 'x86_64', True, '', '', 'macosx_10_16_x86_64'),
16+
('macosx_10_9_arm64', 'arm64', False, '', '', 'macosx_11_0_arm64'),
17+
('macosx_10_9_arm64', 'arm64', False, '-arch x86_64 -arch arm64', '', 'macosx_10_9_universal2'),
18+
('macosx_10_9_x86_64', 'x86_64', False, '-arch x86_64 -arch arm64', '', 'macosx_10_9_universal2'),
19+
('macosx_10_9_x86_64', 'x86_64', False, '-arch x86_64 -arch arm64', '12', 'macosx_12_0_universal2'),
20+
('macosx_10_9_x86_64', 'x86_64', False, '-arch arm64', '12.4', 'macosx_12_0_arm64'),
21+
('macosx_10_9_x86_64', 'x86_64', False, '-arch arm64', '10.12', 'macosx_11_0_arm64'),
22+
('macosx_10_9_x86_64', 'x86_64', True, '-arch arm64', '10.12', 'macosx_10_16_arm64'),
23+
],
24+
)
25+
def test_process_macos_plat_tag(
26+
monkeypatch: pytest.MonkeyPatch,
27+
*,
28+
plat: str,
29+
arch: str,
30+
compat: bool,
31+
archflags: str,
32+
deptarget: str,
33+
expected: str,
34+
) -> None:
35+
monkeypatch.setenv('ARCHFLAGS', archflags)
36+
monkeypatch.setenv('MACOSX_DEPLOYMENT_TARGET', deptarget)
37+
monkeypatch.setattr(platform, 'machine', lambda: arch)
38+
39+
assert process_macos_plat_tag(plat, compat=compat) == expected
40+
41+
42+
@pytest.mark.parametrize(
43+
('version', 'arm', 'compat', 'expected'),
44+
[
45+
('10_9', False, False, '10_9'),
46+
('10_9', False, True, '10_9'),
47+
('10_9', True, False, '11_0'),
48+
('10_9', True, True, '10_9'),
49+
('11_3', False, False, '11_0'),
50+
('12_3', True, False, '12_0'),
51+
('12_3', False, True, '10_16'),
52+
('12_3', True, True, '10_16'),
53+
],
54+
)
55+
def check_normalization(*, version: str, arm: bool, compat: bool, expected: str) -> None:
56+
assert normalize_macos_version(version, arm=arm, compat=compat) == expected

0 commit comments

Comments
 (0)