Skip to content

Commit

Permalink
Acknowledge the ARCHFLAGS environment variable on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek committed Dec 17, 2022
1 parent edb2370 commit 5b0b7af
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 6 deletions.
28 changes: 23 additions & 5 deletions backend/src/hatchling/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,7 @@ def clean(self, directory: str, versions: list[str]) -> None:
def build_standard(self, directory: str, **build_data: Any) -> str:
if 'tag' not in build_data:
if build_data['infer_tag']:
from packaging.tags import sys_tags

best_matching_tag = next(sys_tags())
tag_parts = (best_matching_tag.interpreter, best_matching_tag.abi, best_matching_tag.platform)
build_data['tag'] = '-'.join(tag_parts)
build_data['tag'] = self.get_best_matching_tag()
else:
build_data['tag'] = self.get_default_tag()

Expand Down Expand Up @@ -588,6 +584,28 @@ def get_default_tag(self) -> str:

return f'{".".join(supported_python_versions)}-none-any'

def get_best_matching_tag(self) -> str:
import sys

from packaging.tags import sys_tags

tag = next(sys_tags())
tag_parts = [tag.interpreter, tag.abi, tag.platform]

archflags = os.environ.get('ARCHFLAGS', '')
if sys.platform == 'darwin' and 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}'

return '-'.join(tag_parts)

def get_default_build_data(self) -> dict[str, Any]:
return {
'infer_tag': False,
Expand Down
1 change: 1 addition & 0 deletions docs/history/hatchling.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

***Fixed:***

- Acknowledge the `ARCHFLAGS` environment variable on macOS for the `wheel` target when build hooks set the `infer_tag` build data to `true`
- Fix dependency checking when encountering broken distributions
- Remove unnecessary encoding declaration in the default template for the `version` build hook

Expand Down
94 changes: 93 additions & 1 deletion tests/backend/builders/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# https://github.com/python/cpython/pull/26184
fixed_pathlib_resolution = pytest.mark.skipif(
platform.system() == 'Windows' and (sys.version_info < (3, 8) or sys.implementation.name == 'pypy'),
sys.platform == 'win32' and (sys.version_info < (3, 8) or sys.implementation.name == 'pypy'),
reason='pathlib.Path.resolve has bug on Windows',
)

Expand Down Expand Up @@ -2796,3 +2796,95 @@ def test_editable_sources_rewrite_error(self, hatch, helpers, temp_dir):
),
):
list(builder.build(str(build_path)))

@pytest.mark.skipif(
sys.platform != 'darwin' or sys.version_info < (3, 8),
reason='requires support for ARM on macOS',
)
@pytest.mark.parametrize(
'archflags, expected_arch',
[('-arch x86_64', 'x86_64'), ('-arch arm64', 'arm64'), ('-arch arm64 -arch x86_64', 'universal2')],
)
def test_macos_archflags(self, hatch, helpers, temp_dir, config_file, archflags, expected_arch):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()

project_name = 'My.App'

with temp_dir.as_cwd():
result = hatch('new', project_name)

assert result.exit_code == 0, result.output

project_path = temp_dir / 'my-app'

vcs_ignore_file = project_path / '.gitignore'
vcs_ignore_file.write_text('*.pyc\n*.so\n*.h')

build_script = project_path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
build_data['pure_python'] = False
build_data['infer_tag'] = True
pathlib.Path('my_app', 'lib.so').touch()
pathlib.Path('my_app', 'lib.h').touch()
"""
)
)

config = {
'project': {'name': project_name, 'requires-python': '>3', 'dynamic': ['version']},
'tool': {
'hatch': {
'version': {'path': 'my_app/__about__.py'},
'build': {
'targets': {'wheel': {'versions': ['standard']}},
'artifacts': ['my_app/lib.so'],
'hooks': {'custom': {'path': DEFAULT_BUILD_SCRIPT}},
},
},
},
}
builder = WheelBuilder(str(project_path), config=config)

build_path = project_path / 'dist'
build_path.mkdir()

with project_path.as_cwd({'ARCHFLAGS': archflags}):
artifacts = list(builder.build(str(build_path)))

assert len(artifacts) == 1
expected_artifact = artifacts[0]

build_artifacts = list(build_path.iterdir())
assert len(build_artifacts) == 1
assert expected_artifact == str(build_artifacts[0])

tag = next(sys_tags())
tag_parts = [tag.interpreter, tag.abi, tag.platform]
tag_parts[2] = tag_parts[2].replace(platform.mac_ver()[2], expected_arch)
expected_tag = '-'.join(tag_parts)
assert expected_artifact == str(build_path / f'{builder.project_id}-{expected_tag}.whl')

extraction_directory = temp_dir / '_archive'
extraction_directory.mkdir()

with zipfile.ZipFile(str(expected_artifact), 'r') as zip_archive:
zip_archive.extractall(str(extraction_directory))

metadata_directory = f'{builder.project_id}.dist-info'
expected_files = helpers.get_template_files(
'wheel.standard_default_build_script_artifacts',
project_name,
metadata_directory=metadata_directory,
tag=expected_tag,
)
helpers.assert_files(extraction_directory, expected_files)

0 comments on commit 5b0b7af

Please sign in to comment.