diff --git a/pex/installer.py b/pex/installer.py index 9b302d8ca..9f933d0e0 100644 --- a/pex/installer.py +++ b/pex/installer.py @@ -33,7 +33,8 @@ def function_wrapper(self, *args, **kw): class InstallerBase(object): SETUP_BOOTSTRAP_HEADER = "import sys" - SETUP_BOOTSTRAP_MODULE = "sys.path.insert(0, %(path)r); import %(module)s" + SETUP_BOOTSTRAP_PYPATH = "sys.path.insert(0, %(path)r)" + SETUP_BOOTSTRAP_MODULE = "import %(module)s" SETUP_BOOTSTRAP_FOOTER = """ __file__ = 'setup.py' sys.argv[0] = 'setup.py' @@ -85,15 +86,21 @@ def capability(self): @property def bootstrap_script(self): + bootstrap_sys_paths = [] bootstrap_modules = [] for module, requirement in self.mixins().items(): path = self._interpreter.get_location(requirement) if not path: assert not self._strict # This should be caught by validation continue - bootstrap_modules.append(self.SETUP_BOOTSTRAP_MODULE % {'path': path, 'module': module}) + bootstrap_sys_paths.append(self.SETUP_BOOTSTRAP_PYPATH % {'path': path}) + bootstrap_modules.append(self.SETUP_BOOTSTRAP_MODULE % {'module': module}) return '\n'.join( - [self.SETUP_BOOTSTRAP_HEADER] + bootstrap_modules + [self.SETUP_BOOTSTRAP_FOOTER]) + [self.SETUP_BOOTSTRAP_HEADER] + + bootstrap_sys_paths + + bootstrap_modules + + [self.SETUP_BOOTSTRAP_FOOTER] + ) def run(self): if self._installed is not None: diff --git a/pex/testing.py b/pex/testing.py index 35198414b..6a44539a5 100644 --- a/pex/testing.py +++ b/pex/testing.py @@ -126,13 +126,13 @@ def write_zipfile(directory, dest, reverse=False): @contextlib.contextmanager def make_installer(name='my_project', version='0.0.0', installer_impl=EggInstaller, zip_safe=True, - install_reqs=None): + install_reqs=None, **kwargs): interp = {'project_name': name, 'version': version, 'zip_safe': zip_safe, 'install_requires': install_reqs or []} with temporary_content(PROJECT_CONTENT, interp=interp) as td: - yield installer_impl(td) + yield installer_impl(td, **kwargs) @contextlib.contextmanager diff --git a/tests/test_installer.py b/tests/test_installer.py new file mode 100644 index 000000000..81cc8b939 --- /dev/null +++ b/tests/test_installer.py @@ -0,0 +1,60 @@ +# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). +import contextlib +from collections import OrderedDict + +import pytest + +from pex.bin.pex import get_interpreter +from pex.installer import WheelInstaller +from pex.testing import ensure_python_interpreter, make_installer, temporary_dir +from pex.version import SETUPTOOLS_REQUIREMENT, WHEEL_REQUIREMENT + + +class OrderableInstaller(WheelInstaller): + def __init__(self, source_dir, strict=True, interpreter=None, install_dir=None, mixins=None): + self._mixins = mixins + super(OrderableInstaller, self).__init__(source_dir, strict, interpreter, install_dir) + + def mixins(self): + return self._mixins + + +@contextlib.contextmanager +def bare_interpreter(): + with temporary_dir() as interpreter_cache: + yield get_interpreter( + python_interpreter=ensure_python_interpreter('3.6.3'), + interpreter_cache_dir=interpreter_cache, + repos=None, + use_wheel=True + ) + + +@contextlib.contextmanager +def wheel_installer(*mixins): + with bare_interpreter() as interpreter: + with make_installer(installer_impl=OrderableInstaller, + interpreter=interpreter, + mixins=OrderedDict(mixins)) as installer: + yield installer + + +WHEEL_EXTRA = ('wheel', WHEEL_REQUIREMENT) +SETUPTOOLS_EXTRA = ('setuptools', SETUPTOOLS_REQUIREMENT) + + +def test_wheel_before_setuptools(): + with wheel_installer(WHEEL_EXTRA, SETUPTOOLS_EXTRA) as installer: + installer.bdist() + + +def test_setuptools_before_wheel(): + with wheel_installer(SETUPTOOLS_EXTRA, WHEEL_EXTRA) as installer: + installer.bdist() + + +def test_no_wheel(): + with wheel_installer(SETUPTOOLS_EXTRA) as installer: + with pytest.raises(installer.InstallFailure): + installer.bdist()