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

Improve wheel support in pex. #388

Merged
merged 25 commits into from
Jul 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f7b9dd7
Improve wheel support in pex.
MarkChuCarroll May 15, 2017
533e641
Rewrite the pex wheel fix, using the wheel installer.
MarkChuCarroll May 28, 2017
bac461a
Clean up.
MarkChuCarroll May 30, 2017
cb5da41
Fix some path confusion.
MarkChuCarroll May 30, 2017
5a9575c
Switch to use os.walk instead of os.path.walk to increase compatibili…
MarkChuCarroll May 30, 2017
027635c
Tests pass locally; trying to get them to pass in CI.
MarkChuCarroll May 30, 2017
bb4088f
Update the wheel script finder so that it works during both pex gener…
MarkChuCarroll May 31, 2017
75014cf
Move "wheelfile" import so it only gets loaded during pex generation.
MarkChuCarroll Jun 1, 2017
95c01e1
Fix isort issue.
MarkChuCarroll Jun 1, 2017
86b420c
Add an explicit dependency on "wheel" to setup.py's test deps.
MarkChuCarroll Jun 1, 2017
0e45922
Update requirements to pull wheel in for py26.
MarkChuCarroll Jun 1, 2017
e63644a
Run isort to fix import sort issues.
MarkChuCarroll Jun 1, 2017
1cd5e1b
Add a bypass of the wheel install code for Python 2.6.
MarkChuCarroll Jun 22, 2017
1637e5a
Add a bypass of the wheel install code for Python 2.6.
MarkChuCarroll Jun 22, 2017
e66ade4
Another try at fixing the build.
MarkChuCarroll Jun 22, 2017
2ed5338
Another try at fixing the build.
MarkChuCarroll Jun 22, 2017
02a9e61
Try fixing the "skipif" flag.
MarkChuCarroll Jun 22, 2017
5625240
Add "skipif" to the texs_pex_builder_wheeldep for Python 2.6
MarkChuCarroll Jun 22, 2017
570c754
Duh.
MarkChuCarroll Jun 22, 2017
bb3e066
Update isort.
MarkChuCarroll Jun 22, 2017
fcdf3fd
Another try at the import issue.
MarkChuCarroll Jun 22, 2017
031463c
Address some review comments.
MarkChuCarroll Jul 7, 2017
2701d98
isort.
MarkChuCarroll Jul 7, 2017
d84f211
Fix the 26 test.
MarkChuCarroll Jul 7, 2017
1100e3f
Keep trying to fix the stupid isort.
MarkChuCarroll Jul 7, 2017
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
27 changes: 15 additions & 12 deletions pex/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,19 +245,22 @@ def get_script_from_egg(name, dist):
return None, None


def safer_name(name):
return name.replace('-', '_')


def get_script_from_whl(name, dist):
# This is true as of at least wheel==0.24. Might need to take into account the
# metadata version bundled with the wheel.
wheel_scripts_dir = '%s-%s.data/scripts' % (safer_name(dist.key), dist.version)
if dist.resource_isdir(wheel_scripts_dir) and name in dist.resource_listdir(wheel_scripts_dir):
script_path = os.path.join(wheel_scripts_dir, name)
return (
os.path.join(dist.egg_info, script_path),
dist.get_resource_string('', script_path).replace(b'\r\n', b'\n').replace(b'\r', b'\n'))
# This can get called in different contexts; in some, it looks for files in the
# wheel archives being used to produce a pex; in others, it looks for files in the
# install wheel directory included in the pex. So we need to look at both locations.
datadir_name = "%s-%s.data" % (dist.project_name, dist.version)
wheel_scripts_dirs = ['bin', 'scripts',
os.path.join(datadir_name, "bin"),
os.path.join(datadir_name, "scripts")]
for wheel_scripts_dir in wheel_scripts_dirs:
if (dist.resource_isdir(wheel_scripts_dir) and
name in dist.resource_listdir(wheel_scripts_dir)):
# We always install wheel scripts into bin
script_path = os.path.join(wheel_scripts_dir, name)
return (
os.path.join(dist.egg_info, script_path),
dist.get_resource_string('', script_path).replace(b'\r\n', b'\n').replace(b'\r', b'\n'))
return None, None


Expand Down
4 changes: 4 additions & 0 deletions pex/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ def get_location(self, req):
if req.key == dist_name and dist_version in req:
return location

def supports_wheel_install(self):
"""Wheel installs are broken in python 2.6"""
return self.version >= (2, 7)

def __hash__(self):
return hash((self._binary, self._identity))

Expand Down
40 changes: 39 additions & 1 deletion pex/pex_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import
from __future__ import absolute_import, print_function

import logging
import os
Expand Down Expand Up @@ -253,7 +253,45 @@ def _add_dist_dir(self, path, dist_name):
self._copy_or_link(filename, target)
return CacheHelper.dir_hash(path)

def _get_installer_paths(self, base):
"""Set up an overrides dict for WheelFile.install that installs the contents
of a wheel into its own base in the pex dependencies cache.
"""
return {
'purelib': base,
'headers': os.path.join(base, 'headers'),
'scripts': os.path.join(base, 'bin'),
'platlib': base,
'data': base
}

def _add_dist_zip(self, path, dist_name):
# We need to distinguish between wheels and other zips. Most of the time,
# when we have a zip, it contains its contents in an importable form.
# But wheels don't have to be importable, so we need to force them
# into an importable shape. We can do that by installing it into its own
# wheel dir.
if not self.interpreter.supports_wheel_install():
self._logger.warn("Wheel dependency on %s may not work correctly with Python 2.6." %
dist_name)

if self.interpreter.supports_wheel_install() and dist_name.endswith("whl"):
from wheel.install import WheelFile
tmp = safe_mkdtemp()
whltmp = os.path.join(tmp, 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):
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)
return CacheHelper.dir_hash(whltmp)

with open_zip(path) as zf:
for name in zf.namelist():
if name.endswith('/'):
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
],
install_requires = [
SETUPTOOLS_REQUIREMENT,
WHEEL_REQUIREMENT,
],
tests_require = [
'mock',
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import contextlib
import marshal
import os

import marshal
import pytest
from twitter.common.contextutil import temporary_dir

Expand Down
2 changes: 2 additions & 0 deletions tests/test_pex.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ def test_site_libs_excludes_prefix():
assert sys.prefix not in site_libs


@pytest.mark.skipif(sys.version_info < (2, 7),
reason="wheel script installation is broken on python 2.6")
@pytest.mark.parametrize('zip_safe', (False, True))
@pytest.mark.parametrize('project_name', ('my_project', 'my-project'))
@pytest.mark.parametrize('installer_impl', (EggInstaller, WheelInstaller))
Expand Down
28 changes: 28 additions & 0 deletions tests/test_pex_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import os
import stat
import sys
import zipfile
from contextlib import closing

Expand All @@ -27,6 +28,16 @@
fp.write('success')
"""

wheeldeps_exe_main = """
import sys
from pyparsing import *
from my_package.my_module import do_something
do_something()

with open(sys.argv[1], 'w') as fp:
fp.write('success')
"""


def test_pex_builder():
# test w/ and w/o zipfile dists
Expand Down Expand Up @@ -57,6 +68,23 @@ def test_pex_builder():
assert fp.read() == 'success'


@pytest.mark.skipif(sys.version_info < (2, 7),
reason="wheel script installation is broken on python 2.6")
def test_pex_builder_wheeldep():
"""Repeat the pex_builder test, but this time include an import of
something from a wheel that doesn't come in importable form.
"""
with nested(temporary_dir(), make_bdist('p1', zipped=True)) as (td, p1):
pyparsing_path = "./tests/example_packages/pyparsing-2.1.10-py2.py3-none-any.whl"
dist = DistributionHelper.distribution_from_path(pyparsing_path)
write_pex(td, wheeldeps_exe_main, dists=[p1, dist])
success_txt = os.path.join(td, 'success.txt')
PEX(td).run(args=[success_txt])
assert os.path.exists(success_txt)
with open(success_txt) as fp:
assert fp.read() == 'success'


def test_pex_builder_shebang():
def builder(shebang):
pb = PEXBuilder()
Expand Down