Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pypa/hatch
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: hatchling-v1.24.0
Choose a base ref
...
head repository: pypa/hatch
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: hatchling-v1.24.1
Choose a head ref
  • 2 commits
  • 5 files changed
  • 1 contributor

Commits on Apr 18, 2024

  1. Maintain file permissions (#1397)

    ofek authored Apr 18, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    6324646 View commit details
  2. release Hatchling v1.24.1

    ofek committed Apr 18, 2024
    Copy the full SHA
    544be24 View commit details
2 changes: 1 addition & 1 deletion backend/src/hatchling/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.24.0'
__version__ = '1.24.1'
3 changes: 2 additions & 1 deletion backend/src/hatchling/builders/utils.py
Original file line number Diff line number Diff line change
@@ -112,6 +112,7 @@ def normalize_file_permissions(st_mode: int) -> int:

def set_zip_info_mode(zip_info: ZipInfo, mode: int = 0o644) -> None:
"""
https://github.com/python/cpython/blob/v3.12.3/Lib/zipfile/__init__.py#L574
https://github.com/takluyver/flit/commit/3889583719888aef9f28baaa010e698cb7884904
"""
zip_info.external_attr = mode << 16
zip_info.external_attr = (mode & 0xFFFF) << 16
33 changes: 26 additions & 7 deletions backend/src/hatchling/builders/wheel.py
Original file line number Diff line number Diff line change
@@ -98,7 +98,7 @@ def add_file(self, included_file: IncludedFile) -> tuple[str, str, str]:

# https://github.com/takluyver/flit/pull/66
new_mode = normalize_file_permissions(file_stat.st_mode)
set_zip_info_mode(zip_info, new_mode & 0xFFFF)
set_zip_info_mode(zip_info, new_mode)
if stat.S_ISDIR(file_stat.st_mode): # no cov
zip_info.external_attr |= 0x10
else:
@@ -123,9 +123,19 @@ def write_metadata(self, relative_path: str, contents: str | bytes) -> tuple[str
relative_path = f'{self.metadata_directory}/{normalize_archive_path(relative_path)}'
return self.write_file(relative_path, contents)

def write_shared_script(self, relative_path: str, contents: str | bytes) -> tuple[str, str, str]:
relative_path = f'{self.shared_data_directory}/scripts/{normalize_archive_path(relative_path)}'
return self.write_file(relative_path, contents)
def write_shared_script(self, included_file: IncludedFile, contents: str | bytes) -> tuple[str, str, str]:
relative_path = (
f'{self.shared_data_directory}/scripts/{normalize_archive_path(included_file.distribution_path)}'
)
if sys.platform == 'win32':
return self.write_file(relative_path, contents)

file_stat = os.stat(included_file.path)
return self.write_file(
relative_path,
contents,
mode=normalize_file_permissions(file_stat.st_mode) if self.reproducible else file_stat.st_mode,
)

def add_shared_file(self, shared_file: IncludedFile) -> tuple[str, str, str]:
shared_file.distribution_path = f'{self.shared_data_directory}/data/{shared_file.distribution_path}'
@@ -137,13 +147,22 @@ def add_extra_metadata_file(self, extra_metadata_file: IncludedFile) -> tuple[st
)
return self.add_file(extra_metadata_file)

def write_file(self, relative_path: str, contents: str | bytes) -> tuple[str, str, str]:
def write_file(
self,
relative_path: str,
contents: str | bytes,
*,
mode: int | None = None,
) -> tuple[str, str, str]:
if not isinstance(contents, bytes):
contents = contents.encode('utf-8')

time_tuple = self.time_tuple or (2020, 2, 2, 0, 0, 0)
zip_info = zipfile.ZipInfo(relative_path, time_tuple)
set_zip_info_mode(zip_info)
if mode is None:
set_zip_info_mode(zip_info)
else:
set_zip_info_mode(zip_info, mode)

hash_obj = hashlib.sha256(contents)
hash_digest = format_file_hash(hash_obj.digest())
@@ -628,7 +647,7 @@ def add_shared_scripts(self, archive: WheelArchive, records: RecordFile, build_d
content.write(f.read())
break

record = archive.write_shared_script(shared_script.distribution_path, content.getvalue())
record = archive.write_shared_script(shared_script, content.getvalue())
records.write(record)

def write_metadata(
6 changes: 6 additions & 0 deletions docs/history/hatchling.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

## [1.24.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.24.1) - 2024-04-18 ## {: #hatchling-v1.24.1 }

***Fixed:***

- Maintain file permissions for `shared-scripts` option/`shared_scripts` build data of the `wheel` target

## [1.24.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.24.0) - 2024-04-16 ## {: #hatchling-v1.24.0 }

***Added:***
53 changes: 41 additions & 12 deletions tests/backend/builders/test_wheel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

import os
import platform
import sys
import zipfile
from typing import TYPE_CHECKING

import pytest
from packaging.tags import sys_tags
@@ -12,6 +15,9 @@
from hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors
from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT

if TYPE_CHECKING:
from hatch.utils.fs import Path

# https://github.com/python/cpython/pull/26184
fixed_pathlib_resolution = pytest.mark.skipif(
sys.platform == 'win32' and (sys.version_info < (3, 8) or sys.implementation.name == 'pypy'),
@@ -23,6 +29,17 @@ def get_python_versions_tag():
return '.'.join(f'py{major_version}' for major_version in get_known_python_major_versions())


def extract_zip(zip_path: Path, target: Path) -> None:
with zipfile.ZipFile(zip_path, 'r') as z:
for name in z.namelist():
member = z.getinfo(name)
path = z.extract(member, target)
if member.is_dir():
os.chmod(path, 0o755)
else:
os.chmod(path, member.external_attr >> 16)


def test_class():
assert issubclass(WheelBuilder, BuilderInterface)

@@ -1996,7 +2013,7 @@ def initialize(self, version, build_data):
)
helpers.assert_files(extraction_directory, expected_files)

def test_default_shared_scripts(self, hatch, helpers, temp_dir, config_file):
def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()

@@ -2013,7 +2030,12 @@ def test_default_shared_scripts(self, hatch, helpers, temp_dir, config_file):
shared_data_path.ensure_dir_exists()

binary_contents = os.urandom(1024)
(shared_data_path / 'binary').write_bytes(binary_contents)
binary_file = shared_data_path / 'binary'
binary_file.write_bytes(binary_contents)
if not platform.windows:
expected_mode = 0o755
binary_file.chmod(expected_mode)

(shared_data_path / 'other_script.sh').write_text(
helpers.dedent(
"""
@@ -2085,10 +2107,7 @@ def test_default_shared_scripts(self, hatch, helpers, temp_dir, config_file):
assert expected_artifact == str(build_artifacts[0])

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

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

metadata_directory = f'{builder.project_id}.dist-info'
shared_data_directory = f'{builder.project_id}.data'
@@ -2101,7 +2120,11 @@ def test_default_shared_scripts(self, hatch, helpers, temp_dir, config_file):
)
helpers.assert_files(extraction_directory, expected_files)

def test_default_shared_scripts_from_build_data(self, hatch, helpers, temp_dir, config_file):
if not platform.windows:
extracted_binary = extraction_directory / shared_data_directory / 'scripts' / 'binary'
assert extracted_binary.stat().st_mode & 0o777 == expected_mode

def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, temp_dir, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()

@@ -2118,7 +2141,12 @@ def test_default_shared_scripts_from_build_data(self, hatch, helpers, temp_dir,
shared_data_path.ensure_dir_exists()

binary_contents = os.urandom(1024)
(shared_data_path / 'binary').write_bytes(binary_contents)
binary_file = shared_data_path / 'binary'
binary_file.write_bytes(binary_contents)
if not platform.windows:
expected_mode = 0o755
binary_file.chmod(expected_mode)

(shared_data_path / 'other_script.sh').write_text(
helpers.dedent(
"""
@@ -2205,10 +2233,7 @@ def initialize(self, version, build_data):
assert expected_artifact == str(build_artifacts[0])

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

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

metadata_directory = f'{builder.project_id}.dist-info'
shared_data_directory = f'{builder.project_id}.data'
@@ -2221,6 +2246,10 @@ def initialize(self, version, build_data):
)
helpers.assert_files(extraction_directory, expected_files)

if not platform.windows:
extracted_binary = extraction_directory / shared_data_directory / 'scripts' / 'binary'
assert extracted_binary.stat().st_mode & 0o777 == expected_mode

def test_default_extra_metadata(self, hatch, helpers, temp_dir, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()