Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PEXBuilder.clone. #575

Merged
merged 3 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions pex/pex_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ def __init__(self, path=None, interpreter=None, chroot=None, pex_info=None, prea
The temporary directory created when ``path`` is not specified is now garbage collected on
interpreter exit.
"""
self._chroot = chroot or Chroot(path or safe_mkdtemp())
self._frozen = False
self._interpreter = interpreter or PythonInterpreter.get()
self._shebang = self._interpreter.identity.hashbang()
self._logger = logging.getLogger(__name__)
self._chroot = chroot or Chroot(path or safe_mkdtemp())
self._pex_info = pex_info or PexInfo.default(self._interpreter)
self._preamble = to_bytes(preamble or '')
self._copy = copy

self._shebang = self._interpreter.identity.hashbang()
self._logger = logging.getLogger(__name__)
self._frozen = False
self._distributions = set()
self._pex_info = pex_info or PexInfo.default(interpreter)

def _ensure_unfrozen(self, name='Operation'):
if self._frozen:
Expand Down Expand Up @@ -110,14 +111,13 @@ def clone(self, into=None):
"""
chroot_clone = self._chroot.clone(into=into)
clone = self.__class__(
chroot=chroot_clone,
interpreter=self._interpreter,
pex_info=self._pex_info.copy(),
preamble=self._preamble,
copy=self._copy)
chroot=chroot_clone,
interpreter=self._interpreter,
pex_info=self._pex_info.copy(),
preamble=self._preamble,
copy=self._copy)
clone.set_shebang(self._shebang)
for dist in self._distributions:
clone.add_distribution(dist)
clone._distributions = self._distributions.copy()
return clone

def path(self):
Expand Down Expand Up @@ -286,14 +286,12 @@ def _add_dist_zip(self, path, dist_name):
os.mkdir(whltmp)
wf = WheelFile(path)
wf.install(overrides=self._get_installer_paths(whltmp), force=True)
for (root, _, files) in os.walk(whltmp):
for root, _, files in os.walk(whltmp):
pruned_dir = os.path.relpath(root, tmp)
for f in files:
fullpath = os.path.join(root, f)
if os.path.isdir(fullpath):
continue
target = os.path.join(self._pex_info.internal_cache, pruned_dir, f)
self._chroot.copy(fullpath, target)
self._copy_or_link(fullpath, target)
return CacheHelper.dir_hash(whltmp)

with open_zip(path) as zf:
Expand Down
8 changes: 5 additions & 3 deletions pex/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,22 @@ def get_dep_dist_names_from_pex(pex_path, match_prefix=''):


@contextlib.contextmanager
def temporary_content(content_map, interp=None, seed=31337):
def temporary_content(content_map, interp=None, seed=31337, perms=0o644):
"""Write content to disk where content is map from string => (int, string).

If target is int, write int random bytes. Otherwise write contents of string."""
random.seed(seed)
interp = interp or {}
with temporary_dir() as td:
for filename, size_or_content in content_map.items():
safe_mkdir(os.path.dirname(os.path.join(td, filename)))
with open(os.path.join(td, filename), 'wb') as fp:
dest = os.path.join(td, filename)
safe_mkdir(os.path.dirname(dest))
with open(dest, 'wb') as fp:
if isinstance(size_or_content, int):
fp.write(random_bytes(size_or_content))
else:
fp.write((size_or_content % interp).encode('utf-8'))
os.chmod(dest, perms)
yield td


Expand Down
51 changes: 51 additions & 0 deletions tests/test_bdist_pex.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import os
import stat
import subprocess
import sys
from textwrap import dedent

from twitter.common.contextutil import pushd

from pex.common import open_zip
from pex.testing import temporary_content


Expand Down Expand Up @@ -81,3 +83,52 @@ def test_pex_args_shebang_with_spaces():

def test_pex_args_shebang_without_spaces():
assert_pex_args_shebang('#!/usr/bin/python')


def test_unwriteable_contents():
my_app_setup_py = dedent("""
Eric-Arellano marked this conversation as resolved.
Show resolved Hide resolved
from setuptools import setup

setup(
name='my_app',
version='0.0.0',
zip_safe=True,
packages=['my_app'],
include_package_data=True,
package_data={'my_app': ['unwriteable.so']},
)
""")

UNWRITEABLE_PERMS = 0o400
with temporary_content({'setup.py': my_app_setup_py,
'my_app/__init__.py': '',
'my_app/unwriteable.so': 42},
Eric-Arellano marked this conversation as resolved.
Show resolved Hide resolved
perms=UNWRITEABLE_PERMS) as my_app_project_dir:
with pushd(my_app_project_dir):
subprocess.check_call([sys.executable, 'setup.py', 'bdist_wheel'])

uses_my_app_setup_py = dedent("""
from setuptools import setup

setup(
name='uses_my_app',
version='0.0.0',
zip_safe=True,
install_requires=['my_app'],
)
""")
with temporary_content({'setup.py': uses_my_app_setup_py}) as uses_my_app_project_dir:
with pushd(uses_my_app_project_dir):
subprocess.check_call([sys.executable,
'setup.py',
'bdist_pex',
'--pex-args=--disable-cache --no-pypi -f {}'
.format(os.path.join(my_app_project_dir, 'dist'))])

with open_zip('dist/uses_my_app-0.0.0.pex') as zf:
unwriteable_sos = [path for path in zf.namelist()
if path.endswith('my_app/unwriteable.so')]
assert 1 == len(unwriteable_sos)
unwriteable_so = unwriteable_sos.pop()
zf.extract(unwriteable_so)
assert UNWRITEABLE_PERMS == stat.S_IMODE(os.stat(unwriteable_so).st_mode)