Skip to content

Commit cde42d6

Browse files
committed
Honor file mode for wheel install_as_egg (#3167)
2 parents 1b4d0c2 + 44fe94c commit cde42d6

File tree

4 files changed

+119
-23
lines changed

4 files changed

+119
-23
lines changed

changelog.d/3167.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Honor unix file mode in ZipFile when installing wheel via ``install_as_egg`` -- by :user:`delijati`

setuptools/archive_util.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,29 +100,37 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
100100
raise UnrecognizedFormat("%s is not a zip file" % (filename,))
101101

102102
with zipfile.ZipFile(filename) as z:
103-
for info in z.infolist():
104-
name = info.filename
103+
_unpack_zipfile_obj(z, extract_dir, progress_filter)
105104

106-
# don't extract absolute paths or ones with .. in them
107-
if name.startswith('/') or '..' in name.split('/'):
108-
continue
109105

110-
target = os.path.join(extract_dir, *name.split('/'))
111-
target = progress_filter(name, target)
112-
if not target:
113-
continue
114-
if name.endswith('/'):
115-
# directory
116-
ensure_directory(target)
117-
else:
118-
# file
119-
ensure_directory(target)
120-
data = z.read(info.filename)
121-
with open(target, 'wb') as f:
122-
f.write(data)
123-
unix_attributes = info.external_attr >> 16
124-
if unix_attributes:
125-
os.chmod(target, unix_attributes)
106+
def _unpack_zipfile_obj(zipfile_obj, extract_dir, progress_filter=default_filter):
107+
"""Internal/private API used by other parts of setuptools.
108+
Similar to ``unpack_zipfile``, but receives an already opened :obj:`zipfile.ZipFile`
109+
object instead of a filename.
110+
"""
111+
for info in zipfile_obj.infolist():
112+
name = info.filename
113+
114+
# don't extract absolute paths or ones with .. in them
115+
if name.startswith('/') or '..' in name.split('/'):
116+
continue
117+
118+
target = os.path.join(extract_dir, *name.split('/'))
119+
target = progress_filter(name, target)
120+
if not target:
121+
continue
122+
if name.endswith('/'):
123+
# directory
124+
ensure_directory(target)
125+
else:
126+
# file
127+
ensure_directory(target)
128+
data = zipfile_obj.read(info.filename)
129+
with open(target, 'wb') as f:
130+
f.write(data)
131+
unix_attributes = info.external_attr >> 16
132+
if unix_attributes:
133+
os.chmod(target, unix_attributes)
126134

127135

128136
def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):

setuptools/tests/test_wheel.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from distutils.sysconfig import get_config_var
77
from distutils.util import get_platform
88
import contextlib
9+
import pathlib
10+
import stat
911
import glob
1012
import inspect
1113
import os
@@ -614,3 +616,88 @@ def sys_tags():
614616
monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags)
615617
assert Wheel(
616618
'onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible()
619+
620+
621+
def test_wheel_mode():
622+
@contextlib.contextmanager
623+
def build_wheel(extra_file_defs=None, **kwargs):
624+
file_defs = {
625+
'setup.py': (DALS(
626+
'''
627+
# -*- coding: utf-8 -*-
628+
from setuptools import setup
629+
import setuptools
630+
setup(**%r)
631+
'''
632+
) % kwargs).encode('utf-8'),
633+
}
634+
if extra_file_defs:
635+
file_defs.update(extra_file_defs)
636+
with tempdir() as source_dir:
637+
path.build(file_defs, source_dir)
638+
runsh = pathlib.Path(source_dir) / "script.sh"
639+
os.chmod(runsh, 0o777)
640+
subprocess.check_call((sys.executable, 'setup.py',
641+
'-q', 'bdist_wheel'), cwd=source_dir)
642+
yield glob.glob(os.path.join(source_dir, 'dist', '*.whl'))[0]
643+
644+
params = dict(
645+
id='script',
646+
file_defs={
647+
'script.py': DALS(
648+
'''
649+
#/usr/bin/python
650+
print('hello world!')
651+
'''
652+
),
653+
'script.sh': DALS(
654+
'''
655+
#/bin/sh
656+
echo 'hello world!'
657+
'''
658+
),
659+
},
660+
setup_kwargs=dict(
661+
scripts=['script.py', 'script.sh'],
662+
),
663+
install_tree=flatten_tree({
664+
'foo-1.0-py{py_version}.egg': {
665+
'EGG-INFO': [
666+
'PKG-INFO',
667+
'RECORD',
668+
'WHEEL',
669+
'top_level.txt',
670+
{'scripts': [
671+
'script.py',
672+
'script.sh'
673+
]}
674+
675+
]
676+
}
677+
})
678+
)
679+
680+
project_name = params.get('name', 'foo')
681+
version = params.get('version', '1.0')
682+
install_tree = params.get('install_tree')
683+
file_defs = params.get('file_defs', {})
684+
setup_kwargs = params.get('setup_kwargs', {})
685+
686+
with build_wheel(
687+
name=project_name,
688+
version=version,
689+
install_requires=[],
690+
extras_require={},
691+
extra_file_defs=file_defs,
692+
**setup_kwargs
693+
) as filename, tempdir() as install_dir:
694+
_check_wheel_install(filename, install_dir,
695+
install_tree, project_name,
696+
version, None)
697+
w = Wheel(filename)
698+
base = pathlib.Path(install_dir) / w.egg_name()
699+
script_sh = base / "EGG-INFO" / "scripts" / "script.sh"
700+
assert script_sh.exists()
701+
if sys.platform != 'win32':
702+
# Editable file mode has no effect on Windows
703+
assert oct(stat.S_IMODE(script_sh.stat().st_mode)) == "0o777"

setuptools/wheel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from setuptools.extern.packaging.tags import sys_tags
1616
from setuptools.extern.packaging.utils import canonicalize_name
1717
from setuptools.command.egg_info import write_requirements
18+
from setuptools.archive_util import _unpack_zipfile_obj
1819

1920

2021
WHEEL_NAME = re.compile(
@@ -121,8 +122,7 @@ def get_metadata(name):
121122
raise ValueError(
122123
'unsupported wheel format version: %s' % wheel_version)
123124
# Extract to target directory.
124-
os.mkdir(destination_eggdir)
125-
zf.extractall(destination_eggdir)
125+
_unpack_zipfile_obj(zf, destination_eggdir)
126126
# Convert metadata.
127127
dist_info = os.path.join(destination_eggdir, dist_info)
128128
dist = pkg_resources.Distribution.from_location(

0 commit comments

Comments
 (0)