Skip to content

Commit

Permalink
normalize sdist/wheel artifact file perms (#1542)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattp- authored May 31, 2024
1 parent d73037f commit 8a6f3f4
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 1 deletion.
2 changes: 2 additions & 0 deletions backend/src/hatchling/builders/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from hatchling.builders.utils import (
get_reproducible_timestamp,
normalize_archive_path,
normalize_artifact_permissions,
normalize_file_permissions,
normalize_relative_path,
replace_file,
Expand Down Expand Up @@ -202,6 +203,7 @@ def build_standard(self, directory: str, **build_data: Any) -> str:
target = os.path.join(directory, f'{self.artifact_project_id}.tar.gz')

replace_file(archive.path, target)
normalize_artifact_permissions(target)
return target

@property
Expand Down
9 changes: 9 additions & 0 deletions backend/src/hatchling/builders/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ def normalize_file_permissions(st_mode: int) -> int:
return new_mode


def normalize_artifact_permissions(path: str) -> None:
"""
Normalize the permission bits for artifacts
"""
file_stat = os.stat(path)
new_mode = normalize_file_permissions(file_stat.st_mode)
os.chmod(path, new_mode)


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
Expand Down
4 changes: 4 additions & 0 deletions backend/src/hatchling/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
get_known_python_major_versions,
get_reproducible_timestamp,
normalize_archive_path,
normalize_artifact_permissions,
normalize_file_permissions,
normalize_inclusion_map,
replace_file,
Expand Down Expand Up @@ -483,6 +484,7 @@ def build_standard(self, directory: str, **build_data: Any) -> str:
target = os.path.join(directory, f"{self.artifact_project_id}-{build_data['tag']}.whl")

replace_file(archive.path, target)
normalize_artifact_permissions(target)
return target

def build_editable(self, directory: str, **build_data: Any) -> str:
Expand Down Expand Up @@ -571,6 +573,7 @@ def build_editable_detection(self, directory: str, **build_data: Any) -> str:
target = os.path.join(directory, f"{self.artifact_project_id}-{build_data['tag']}.whl")

replace_file(archive.path, target)
normalize_artifact_permissions(target)
return target

def build_editable_explicit(self, directory: str, **build_data: Any) -> str:
Expand Down Expand Up @@ -599,6 +602,7 @@ def build_editable_explicit(self, directory: str, **build_data: Any) -> str:
target = os.path.join(directory, f"{self.artifact_project_id}-{build_data['tag']}.whl")

replace_file(archive.path, target)
normalize_artifact_permissions(target)
return target

def write_data(
Expand Down
41 changes: 41 additions & 0 deletions tests/backend/builders/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1522,3 +1522,44 @@ def test_no_strict_naming(self, hatch, helpers, temp_dir, config_file):

stat = os.stat(str(extraction_directory / builder.artifact_project_id / 'PKG-INFO'))
assert stat.st_mtime == get_reproducible_timestamp()

def test_file_permissions_normalized(self, hatch, temp_dir, config_file):
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'
config = {
'project': {'name': project_name, 'dynamic': ['version']},
'tool': {
'hatch': {
'version': {'path': 'my_app/__about__.py'},
'build': {'targets': {'sdist': {'versions': ['standard']}}},
},
},
}
builder = SdistBuilder(str(project_path), config=config)

build_path = project_path / 'dist'

with project_path.as_cwd():
artifacts = list(builder.build())

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])
assert expected_artifact == str(build_path / f'{builder.artifact_project_id}.tar.gz')

file_stat = os.stat(expected_artifact)
# we assert that at minimum 644 is set, based on the platform (e.g.)
# windows it may be higher
assert file_stat.st_mode & 0o644
43 changes: 43 additions & 0 deletions tests/backend/builders/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3707,3 +3707,46 @@ def initialize(self, version, build_data):
tag=expected_tag,
)
helpers.assert_files(extraction_directory, expected_files)

def test_file_permissions_normalized(self, hatch, temp_dir, config_file):
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'

config = {
'project': {'name': project_name, 'dynamic': ['version']},
'tool': {
'hatch': {
'version': {'path': 'my_app/__about__.py'},
'build': {'targets': {'wheel': {'versions': ['standard'], 'strict-naming': False}}},
},
},
}
builder = WheelBuilder(str(project_path), config=config)

build_path = project_path / 'dist'

with project_path.as_cwd():
artifacts = list(builder.build())

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])
assert expected_artifact == str(
build_path / f'{builder.artifact_project_id}-{get_python_versions_tag()}-none-any.whl'
)
file_stat = os.stat(expected_artifact)
# we assert that at minimum 644 is set, based on the platform (e.g.)
# windows it may be higher
assert file_stat.st_mode & 0o644
18 changes: 17 additions & 1 deletion tests/helpers/templates/wheel/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import hashlib
import os
import tempfile

from hatchling.builders.utils import format_file_hash
from hatchling.builders.utils import format_file_hash, normalize_artifact_permissions


def update_record_file_contents(record_file, files, generated_files=()):
Expand Down Expand Up @@ -42,3 +43,18 @@ def update_record_file_contents(record_file, files, generated_files=()):
record_file.contents += f'{template_file.path.as_posix()},sha256={hash_digest},{len(raw_contents)}\n'

record_file.contents += f'{record_file.path.as_posix()},,\n'


def test_normalize_artifact_permissions():
"""
assert that this func does what we expect on a tmpfile that that starts at 600
"""
_, path = tempfile.mkstemp()

file_stat = os.stat(path)
assert file_stat.st_mode == 0o100600

normalize_artifact_permissions(path)

file_stat = os.stat(path)
assert file_stat.st_mode == 0o100644

0 comments on commit 8a6f3f4

Please sign in to comment.