Skip to content

Commit

Permalink
Fixed wheel unpack not setting the executable bit for executable fi…
Browse files Browse the repository at this point in the history
…les in the archive

Fixes #505.
  • Loading branch information
agronholm committed Mar 13, 2023
1 parent 26d2e0d commit 42e342c
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/news.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release Notes
**UNRELEASED**

- Updated vendored ``packaging`` to 23.0
- ``wheel unpack`` now preserves the executable attribute of extracted files
- Fixed spaces in platform names not being converted to underscores (PR by David Tucker)
- Fixed ``RECORD`` files in generated wheels missing the regular file attribute
- Fixed ``DeprecationWarning`` about the use of the deprecated ``pkg_resources`` API
Expand Down
12 changes: 11 additions & 1 deletion src/wheel/cli/unpack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import os
import stat
from pathlib import Path

from ..wheelfile import WheelFile
Expand All @@ -14,10 +16,18 @@ def unpack(path: str, dest: str = ".") -> None:
:param path: The path to the wheel.
:param dest: Destination directory (default to current directory).
"""
umask = os.umask(0)
os.umask(umask)
with WheelFile(path) as wf:
namever = wf.parsed_filename.group("namever")
destination = Path(dest) / namever
print(f"Unpacking to: {destination}...", end="", flush=True)
wf.extractall(destination)
for zinfo in wf.filelist:
extracted_path = destination / zinfo.filename
wf.extract(zinfo, extracted_path)

# Set the executable bit if it was set in the archive
if stat.S_IMODE(zinfo.external_attr >> 16 & 0o111):
extracted_path.chmod(0o777 & ~umask | 0o111)

print("OK")
23 changes: 23 additions & 0 deletions tests/cli/test_unpack.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from __future__ import annotations

import platform
import stat

import pytest

from wheel.cli.unpack import unpack
from wheel.wheelfile import WheelFile


def test_unpack(wheel_paths, tmp_path):
Expand All @@ -10,3 +16,20 @@ def test_unpack(wheel_paths, tmp_path):
"""
for wheel_path in wheel_paths:
unpack(wheel_path, str(tmp_path))


@pytest.mark.skipif(
platform.system() == "Windows", reason="Windows does not support the executable bit"
)
def test_unpack_executable_bit(tmp_path):
wheel_path = tmp_path / "test-1.0-py3-none-any.whl"
script_path = tmp_path / "script"
script_path.write_text("test script")
script_path.chmod(0o777)
with WheelFile(wheel_path, "w") as wf:
wf.write(str(script_path), "script")

script_path.unlink()
script_path = script_path.parent / "test-1.0" / "script"
unpack(str(wheel_path), str(tmp_path))
assert stat.S_IMODE(script_path.stat().st_mode) & 0o111

0 comments on commit 42e342c

Please sign in to comment.