diff --git a/news/5188.bugfix b/news/5188.bugfix new file mode 100644 index 00000000000..27325639e88 --- /dev/null +++ b/news/5188.bugfix @@ -0,0 +1 @@ +Fix PEP 518 support. diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index 04e1861ebb0..791d7346588 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -2,6 +2,7 @@ """ import os +from distutils.sysconfig import get_python_lib from sysconfig import get_paths from pip._internal.utils.temp_dir import TempDirectory @@ -38,11 +39,14 @@ def __enter__(self): else: os.environ['PATH'] = scripts + os.pathsep + os.defpath - if install_dirs['purelib'] == install_dirs['platlib']: - lib_dirs = install_dirs['purelib'] + # Note: prefer distutils' sysconfig to get the + # library paths so PyPy is correctly supported. + purelib = get_python_lib(plat_specific=0, prefix=self.path) + platlib = get_python_lib(plat_specific=1, prefix=self.path) + if purelib == platlib: + lib_dirs = purelib else: - lib_dirs = install_dirs['purelib'] + os.pathsep + \ - install_dirs['platlib'] + lib_dirs = purelib + os.pathsep + platlib if self.save_pythonpath: os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \ self.save_pythonpath diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index 8a85d873001..ac55f91e62e 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -11,7 +11,6 @@ from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req import RequirementSet from pip._internal.resolve import Resolver -from pip._internal.utils.misc import import_or_raise from pip._internal.utils.temp_dir import TempDirectory from pip._internal.wheel import WheelBuilder @@ -102,28 +101,7 @@ def __init__(self, *args, **kw): self.parser.insert_option_group(0, index_opts) self.parser.insert_option_group(0, cmd_opts) - def check_required_packages(self): - import_or_raise( - 'wheel.bdist_wheel', - CommandError, - "'pip wheel' requires the 'wheel' package. To fix this, run: " - "pip install wheel" - ) - - need_setuptools_message = ( - "'pip wheel' requires setuptools >= 0.8 for dist-info support. " - "To fix this, run: pip install --upgrade setuptools>=0.8" - ) - pkg_resources = import_or_raise( - 'pkg_resources', - CommandError, - need_setuptools_message - ) - if not hasattr(pkg_resources, 'DistInfoDistribution'): - raise CommandError(need_setuptools_message) - def run(self, options, args): - self.check_required_packages() cmdoptions.check_install_build_global(options) index_urls = [options.index_url] + options.extra_index_urls diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 2c4ff94cb1c..7d1297ea00b 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -126,25 +126,31 @@ def prep_for_dist(self, finder, build_isolation): build_requirements, isolate = self.req.get_pep_518_info() should_isolate = build_isolation and isolate - if 'setuptools' not in build_requirements: + minimum_requirements = ('setuptools', 'wheel') + missing_requirements = set(minimum_requirements) - set( + pkg_resources.Requirement(r).key + for r in build_requirements + ) + if missing_requirements: + def format_reqs(rs): + return ' and '.join(map(repr, sorted(rs))) logger.warning( - "%s does not include 'setuptools' as a buildtime requirement " - "in its pyproject.toml.", self.req.name, + "Missing build time requirements in pyproject.toml for %s: " + "%s.", self.req, format_reqs(missing_requirements) ) logger.warning( "This version of pip does not implement PEP 517 so it cannot " - "build a wheel without setuptools." + "build a wheel without %s.", format_reqs(minimum_requirements) ) - if not should_isolate: - self.req.build_env = NoOpBuildEnvironment(no_clean=False) - - with self.req.build_env as prefix: - if should_isolate: + if should_isolate: + with self.req.build_env as prefix: _install_build_reqs(finder, prefix, build_requirements) + else: + self.req.build_env = NoOpBuildEnvironment(no_clean=False) - self.req.run_egg_info() - self.req.assert_source_matches_version() + self.req.run_egg_info() + self.req.assert_source_matches_version() class Installed(DistAbstraction): diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 04bb3fd65c0..ddd167c66c0 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -413,24 +413,6 @@ def setup_py_dir(self): @property def setup_py(self): assert self.source_dir, "No source dir for %s" % self - cmd = [sys.executable, '-c', 'import setuptools'] - output = call_subprocess( - cmd, - show_stdout=False, - command_desc='python -c "import setuptools"', - on_returncode='ignore', - ) - - if output: - if get_installed_version('setuptools') is None: - add_msg = "Please install setuptools." - else: - add_msg = output - # Setuptools is not available - raise InstallationError( - "Could not import setuptools which is required to " - "install from a source distribution.\n%s" % add_msg - ) setup_py = os.path.join(self.setup_py_dir, 'setup.py') @@ -496,11 +478,12 @@ def run_egg_info(self): egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info') ensure_dir(egg_info_dir) egg_base_option = ['--egg-base', 'pip-egg-info'] - call_subprocess( - egg_info_cmd + egg_base_option, - cwd=self.setup_py_dir, - show_stdout=False, - command_desc='python setup.py egg_info') + with self.build_env: + call_subprocess( + egg_info_cmd + egg_base_option, + cwd=self.setup_py_dir, + show_stdout=False, + command_desc='python setup.py egg_info') if not self.req: if isinstance(parse_version(self.pkg_info()["Version"]), Version): @@ -788,12 +771,13 @@ def install(self, install_options, global_options=None, root=None, msg = 'Running setup.py install for %s' % (self.name,) with open_spinner(msg) as spinner: with indent_log(): - call_subprocess( - install_args + install_options, - cwd=self.setup_py_dir, - show_stdout=False, - spinner=spinner, - ) + with self.build_env: + call_subprocess( + install_args + install_options, + cwd=self.setup_py_dir, + show_stdout=False, + spinner=spinner, + ) if not os.path.exists(record_filename): logger.debug('Record file %s not found', record_filename) diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 87db792d066..f1fcf14748c 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -19,48 +19,30 @@ from tests.lib.path import Path -def test_without_setuptools(script, data): - script.pip("uninstall", "setuptools", "-y") - result = script.run( - "python", "-c", - "import pip._internal; pip._internal.main([" - "'install', " - "'INITools==0.2', " - "'-f', '%s', " - "'--no-binary=:all:'])" % data.packages, - expect_error=True, - ) - assert ( - "Could not import setuptools which is required to install from a " - "source distribution." - in result.stderr - ) - assert "Please install setuptools" in result.stderr - - -def test_with_setuptools_and_import_error(script, data): - # Make sure we get an ImportError while importing setuptools - setuptools_init_path = script.site_packages_path.join( - "setuptools", "__init__.py") - with open(setuptools_init_path, 'a') as f: - f.write('\nraise ImportError("toto")') - - result = script.run( - "python", "-c", - "import pip._internal; pip._internal.main([" - "'install', " - "'INITools==0.2', " - "'-f', '%s', " - "'--no-binary=:all:'])" % data.packages, - expect_error=True, - ) - assert ( - "Could not import setuptools which is required to install from a " - "source distribution." - in result.stderr - ) - assert "Traceback " in result.stderr - assert "ImportError: toto" in result.stderr +@pytest.mark.parametrize('original_setuptools', ('missing', 'bad')) +def test_pep518_uses_build_env(script, data, original_setuptools): + if original_setuptools == 'missing': + script.pip("uninstall", "-y", "setuptools") + elif original_setuptools == 'bad': + setuptools_init_path = script.site_packages_path.join( + "setuptools", "__init__.py") + with open(setuptools_init_path, 'a') as f: + f.write('\nraise ImportError("toto")') + else: + raise ValueError(original_setuptools) + to_install = data.src.join("pep518-3.0") + for command in ('install', 'wheel'): + kwargs = {} + if sys.version_info[:2] == (3, 3): + # Ignore Python 3.3 deprecation warning... + kwargs['expect_stderr'] = True + script.run( + "python", "-c", + "import pip._internal; pip._internal.main([" + "%r, " "'-f', %r, " "%r, " + "])" % (command, str(data.packages), str(to_install)), + **kwargs + ) @pytest.mark.network diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index b83702599e1..68772f378fe 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -9,17 +9,6 @@ from tests.lib import pyversion -def test_basic_pip_wheel_fails_without_wheel(script, data): - """ - Test 'pip wheel' fails without wheel - """ - result = script.pip( - 'wheel', '--no-index', '-f', data.find_links, 'simple==3.0', - expect_error=True, - ) - assert "'pip wheel' requires the 'wheel' package" in result.stderr - - def test_wheel_exit_status_code_when_no_requirements(script, common_wheels): """ Test wheel exit status code when no requirements specified