From a4c5d96e16dac892a6d84b02bdb3c0b8e14e9e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=B6ring?= Date: Fri, 5 Apr 2019 19:47:53 +0200 Subject: [PATCH] Fix pex file looses the executable permissions of binary files (#703) Fixes #223 --- pex/util.py | 11 ++++------ tests/test_pex.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/pex/util.py b/pex/util.py index 160ad514b..932b343c4 100644 --- a/pex/util.py +++ b/pex/util.py @@ -5,14 +5,13 @@ import contextlib import os -import shutil import tempfile import uuid from hashlib import sha1 from site import makepath from threading import Lock -from pex.common import rename_if_empty, safe_mkdir, safe_mkdtemp, safe_open +from pex.common import rename_if_empty, safe_mkdir, safe_mkdtemp from pex.compatibility import exec_function from pex.finders import register_finders from pex.third_party.pkg_resources import ( @@ -176,11 +175,9 @@ def cache_distribution(cls, zf, source, target_dir): target_dir_tmp = target_dir + '.' + uuid.uuid4().hex for name in zf.namelist(): if name.startswith(source) and not name.endswith('/'): - # strip off prefix + '/' - target_name = os.path.join(dependency_basename, name[len(source) + 1:]) - with contextlib.closing(zf.open(name)) as zi: - with safe_open(os.path.join(target_dir_tmp, target_name), 'wb') as fp: - shutil.copyfileobj(zi, fp) + zf.extract(name, target_dir_tmp) + os.rename(os.path.join(target_dir_tmp, source), + os.path.join(target_dir_tmp, dependency_basename)) rename_if_empty(target_dir_tmp, target_dir) diff --git a/tests/test_pex.py b/tests/test_pex.py index 0915d991c..f3fc030c8 100644 --- a/tests/test_pex.py +++ b/tests/test_pex.py @@ -28,7 +28,10 @@ named_temporary_file, run_simple_pex_test, temporary_dir, - write_simple_pex + write_simple_pex, + safe_mkdir, + temporary_content, + run_simple_pex ) from pex.util import DistributionHelper @@ -236,6 +239,56 @@ def test_pex_run(): assert fake_stdout.read() == b'hellohello' +def test_pex_executable(): + # Tests that pex keeps executable permissions + with temporary_dir() as temp_dir: + pex_dir = os.path.join(temp_dir, 'pex_dir') + safe_mkdir(pex_dir) + + with open(os.path.join(pex_dir, 'exe.py'), 'w') as fp: + fp.write(textwrap.dedent(''' + import subprocess + import os + import sys + import my_package + path = os.path.join(os.path.dirname(my_package.__file__), 'bin/start.sh') + sys.stdout.write(subprocess.check_output([path]).decode('utf-8')) + ''')) + + project_content = { + 'setup.py': textwrap.dedent(''' + from setuptools import setup + + setup( + name='my_project', + version='0.0.0.0', + zip_safe=True, + packages=['my_package'], + package_data={'my_package': ['bin/*']}, + install_requires=[], + ) + '''), + 'my_package/__init__.py': 0, + 'my_package/bin/start.sh': ("#!/usr/bin/env bash\n" + "echo 'hello world from start.sh!'"), + 'my_package/my_module.py': 'def do_something():\n print("hello world!")\n', + } + pex_builder = PEXBuilder(path=pex_dir) + with temporary_content(project_content, perms=0o755) as project_dir: + installer = WheelInstaller(project_dir) + bdist = DistributionHelper.distribution_from_path(installer.bdist()) + pex_builder.add_dist_location(bdist.location) + pex_builder.set_executable(os.path.join(pex_dir, 'exe.py')) + pex_builder.freeze() + + app_pex = os.path.join(os.path.join(temp_dir, 'out_pex_dir'), 'app.pex') + pex_builder.build(app_pex) + std_out, rc = run_simple_pex(app_pex, + env={'PEX_ROOT': os.path.join(temp_dir, '.pex')}) + assert rc == 0 + assert std_out.decode('utf-8') == 'hello world from start.sh!\n' + + def test_pex_paths(): # Tests that PEX_PATH allows importing sources from the referenced pex. with named_temporary_file() as fake_stdout: