Skip to content

Commit 229b03c

Browse files
Modify wheel#unzip to preserve file execute permissions. (#46)
1 parent 536653b commit 229b03c

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

extract_wheels/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ def main() -> None:
6767

6868
pip_args = [sys.executable, "-m", "pip", "wheel", "-r", args.requirements]
6969
# Assumes any errors are logged by pip so do nothing. This command will fail if pip fails
70-
subprocess.check_output(
71-
pip_args
72-
)
70+
subprocess.check_output(pip_args)
7371

7472
extras = requirements.parse_extras(args.requirements)
7573

extract_wheels/lib/wheel.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
"""Utility class to inspect an extracted wheel directory"""
22
import glob
33
import os
4+
import stat
45
import zipfile
56
from typing import Dict, Optional, Set
67

78
import pkg_resources
89
import pkginfo
910

1011

12+
def current_umask() -> int:
13+
"""Get the current umask which involves having to set it temporarily."""
14+
mask = os.umask(0)
15+
os.umask(mask)
16+
return mask
17+
18+
19+
def set_extracted_file_to_default_mode_plus_executable(path: str) -> None:
20+
"""
21+
Make file present at path have execute for user/group/world
22+
(chmod +x) is no-op on windows per python docs
23+
"""
24+
os.chmod(path, (0o777 & ~current_umask() | 0o111))
25+
26+
1127
class Wheel:
1228
"""Representation of the compressed .whl file"""
1329

@@ -43,6 +59,19 @@ def dependencies(self, extras_requested: Optional[Set[str]] = None) -> Set[str]:
4359
def unzip(self, directory: str) -> None:
4460
with zipfile.ZipFile(self.path, "r") as whl:
4561
whl.extractall(directory)
62+
# The following logic is borrowed from Pip:
63+
# https://github.com/pypa/pip/blob/cc48c07b64f338ac5e347d90f6cb4efc22ed0d0b/src/pip/_internal/utils/unpacking.py#L240
64+
for info in whl.infolist():
65+
name = info.filename
66+
# Do not attempt to modify directories.
67+
if name.endswith("/") or name.endswith("\\"):
68+
continue
69+
mode = info.external_attr >> 16
70+
# if mode and regular file and any execute permissions for
71+
# user/group/world?
72+
if mode and stat.S_ISREG(mode) and mode & 0o111:
73+
name = os.path.join(directory, name)
74+
set_extracted_file_to_default_mode_plus_executable(name)
4675

4776

4877
def get_dist_info(wheel_dir: str) -> str:

0 commit comments

Comments
 (0)