Skip to content

Commit

Permalink
Honor file mode for wheel install_as_egg (#3167)
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri committed Apr 4, 2022
2 parents 1b4d0c2 + 44fe94c commit cde42d6
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 23 deletions.
1 change: 1 addition & 0 deletions changelog.d/3167.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Honor unix file mode in ZipFile when installing wheel via ``install_as_egg`` -- by :user:`delijati`
50 changes: 29 additions & 21 deletions setuptools/archive_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,37 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
raise UnrecognizedFormat("%s is not a zip file" % (filename,))

with zipfile.ZipFile(filename) as z:
for info in z.infolist():
name = info.filename
_unpack_zipfile_obj(z, extract_dir, progress_filter)

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

target = os.path.join(extract_dir, *name.split('/'))
target = progress_filter(name, target)
if not target:
continue
if name.endswith('/'):
# directory
ensure_directory(target)
else:
# file
ensure_directory(target)
data = z.read(info.filename)
with open(target, 'wb') as f:
f.write(data)
unix_attributes = info.external_attr >> 16
if unix_attributes:
os.chmod(target, unix_attributes)
def _unpack_zipfile_obj(zipfile_obj, extract_dir, progress_filter=default_filter):
"""Internal/private API used by other parts of setuptools.
Similar to ``unpack_zipfile``, but receives an already opened :obj:`zipfile.ZipFile`
object instead of a filename.
"""
for info in zipfile_obj.infolist():
name = info.filename

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

target = os.path.join(extract_dir, *name.split('/'))
target = progress_filter(name, target)
if not target:
continue
if name.endswith('/'):
# directory
ensure_directory(target)
else:
# file
ensure_directory(target)
data = zipfile_obj.read(info.filename)
with open(target, 'wb') as f:
f.write(data)
unix_attributes = info.external_attr >> 16
if unix_attributes:
os.chmod(target, unix_attributes)


def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):
Expand Down
87 changes: 87 additions & 0 deletions setuptools/tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from distutils.sysconfig import get_config_var
from distutils.util import get_platform
import contextlib
import pathlib
import stat
import glob
import inspect
import os
Expand Down Expand Up @@ -614,3 +616,88 @@ def sys_tags():
monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags)
assert Wheel(
'onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible()


def test_wheel_mode():
@contextlib.contextmanager
def build_wheel(extra_file_defs=None, **kwargs):
file_defs = {
'setup.py': (DALS(
'''
# -*- coding: utf-8 -*-
from setuptools import setup
import setuptools
setup(**%r)
'''
) % kwargs).encode('utf-8'),
}
if extra_file_defs:
file_defs.update(extra_file_defs)
with tempdir() as source_dir:
path.build(file_defs, source_dir)
runsh = pathlib.Path(source_dir) / "script.sh"
os.chmod(runsh, 0o777)
subprocess.check_call((sys.executable, 'setup.py',
'-q', 'bdist_wheel'), cwd=source_dir)
yield glob.glob(os.path.join(source_dir, 'dist', '*.whl'))[0]

params = dict(
id='script',
file_defs={
'script.py': DALS(
'''
#/usr/bin/python
print('hello world!')
'''
),
'script.sh': DALS(
'''
#/bin/sh
echo 'hello world!'
'''
),
},
setup_kwargs=dict(
scripts=['script.py', 'script.sh'],
),
install_tree=flatten_tree({
'foo-1.0-py{py_version}.egg': {
'EGG-INFO': [
'PKG-INFO',
'RECORD',
'WHEEL',
'top_level.txt',
{'scripts': [
'script.py',
'script.sh'
]}

]
}
})
)

project_name = params.get('name', 'foo')
version = params.get('version', '1.0')
install_tree = params.get('install_tree')
file_defs = params.get('file_defs', {})
setup_kwargs = params.get('setup_kwargs', {})

with build_wheel(
name=project_name,
version=version,
install_requires=[],
extras_require={},
extra_file_defs=file_defs,
**setup_kwargs
) as filename, tempdir() as install_dir:
_check_wheel_install(filename, install_dir,
install_tree, project_name,
version, None)
w = Wheel(filename)
base = pathlib.Path(install_dir) / w.egg_name()
script_sh = base / "EGG-INFO" / "scripts" / "script.sh"
assert script_sh.exists()
if sys.platform != 'win32':
# Editable file mode has no effect on Windows
assert oct(stat.S_IMODE(script_sh.stat().st_mode)) == "0o777"
4 changes: 2 additions & 2 deletions setuptools/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from setuptools.extern.packaging.tags import sys_tags
from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.command.egg_info import write_requirements
from setuptools.archive_util import _unpack_zipfile_obj


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

0 comments on commit cde42d6

Please sign in to comment.