From cf00cbb0826444dadcee12b55309664e9313d700 Mon Sep 17 00:00:00 2001 From: Laszlo Kiss-Kollar Date: Tue, 15 Sep 2020 20:50:53 +0100 Subject: [PATCH] Fix invalid header path when `--root` is set When we invoke setuptools with a `--root`, the header directory we take from the Scheme object already contains the root as a prefix. Passing both `--root` and `--install-headers` will result in setuptools adding the root prefix twice in the header path. As the Scheme object already has all directories relative to `--root`, we can pass these separately with the `--install-*` setuptools arguments and drop the `--root` argument. As the specific locations differ across interpreters and platforms, the included test only checks that scripts, headers and library contents are installed under the specified package root. This is not the case without the fix, as some files will be installed under the duplicated root path, outside the test `venv`. --- news/8477.bugfix | 2 + .../_internal/operations/install/legacy.py | 6 +-- src/pip/_internal/utils/setuptools_build.py | 16 +++--- tests/functional/test_install.py | 54 +++++++++++++++++++ 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 news/8477.bugfix diff --git a/news/8477.bugfix b/news/8477.bugfix new file mode 100644 index 00000000000..b4dd8dea7e7 --- /dev/null +++ b/news/8477.bugfix @@ -0,0 +1,2 @@ +Install libs, headers and data files in the correct location when +``--root`` is specified. diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py index 87227d5fed6..aae98212705 100644 --- a/src/pip/_internal/operations/install/legacy.py +++ b/src/pip/_internal/operations/install/legacy.py @@ -48,8 +48,6 @@ def install( ): # type: (...) -> bool - header_dir = scheme.headers - with TempDirectory(kind="record") as temp_dir: try: record_filename = os.path.join(temp_dir.path, 'install-record.txt') @@ -60,11 +58,11 @@ def install( record_filename=record_filename, root=root, prefix=prefix, - header_dir=header_dir, home=home, use_user_site=use_user_site, no_user_config=isolated, pycompile=pycompile, + scheme=scheme, ) runner = runner_with_spinner_message( @@ -103,7 +101,7 @@ def prepend_root(path): for line in record_lines: directory = os.path.dirname(line) if directory.endswith('.egg-info'): - egg_info_dir = prepend_root(directory) + egg_info_dir = directory break else: message = ( diff --git a/src/pip/_internal/utils/setuptools_build.py b/src/pip/_internal/utils/setuptools_build.py index 2a664b00703..e3c354f0ff7 100644 --- a/src/pip/_internal/utils/setuptools_build.py +++ b/src/pip/_internal/utils/setuptools_build.py @@ -1,5 +1,6 @@ import sys +from pip._internal.models.scheme import Scheme from pip._internal.utils.typing import MYPY_CHECK_RUNNING if MYPY_CHECK_RUNNING: @@ -140,11 +141,11 @@ def make_setuptools_install_args( record_filename, # type: str root, # type: Optional[str] prefix, # type: Optional[str] - header_dir, # type: Optional[str] home, # type: Optional[str] use_user_site, # type: bool no_user_config, # type: bool - pycompile # type: bool + pycompile, # type: bool + scheme, # type: Scheme ): # type: (...) -> List[str] assert not (use_user_site and prefix) @@ -159,8 +160,12 @@ def make_setuptools_install_args( args += ["install", "--record", record_filename] args += ["--single-version-externally-managed"] - if root is not None: - args += ["--root", root] + args += ["--install-purelib", scheme.purelib, + "--install-platlib", scheme.platlib, + "--install-headers", scheme.headers, + "--install-scripts", scheme.scripts, + "--install-data", scheme.data] + if prefix is not None: args += ["--prefix", prefix] if home is not None: @@ -173,9 +178,6 @@ def make_setuptools_install_args( else: args += ["--no-compile"] - if header_dir: - args += ["--install-headers", header_dir] - args += install_options return args diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 17a72bca82e..d6ea635d08e 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -1,4 +1,5 @@ import distutils +import fnmatch import glob import os import re @@ -14,6 +15,7 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS from pip._internal.models.index import PyPI, TestPyPI from pip._internal.utils.misc import rmtree +from pip._internal.utils.temp_dir import TempDirectory from tests.lib import ( _create_svn_repo, _create_test_package, @@ -1027,6 +1029,58 @@ def test_install_package_with_root(script, data, with_wheel): assert "Looking in links: " in result.stdout +def test_install_package_with_root_check_dirs(script): + """ + Test that specifying --root works correctly and headers, scripts + and data files are installed in the correct location. + """ + + def find_in_path(path, pattern): + for root, dirnames, filenames in os.walk(path): + for dirname in fnmatch.filter(dirnames, pattern): + return os.path.join(root, dirname) + for filename in fnmatch.filter(filenames, pattern): + return os.path.join(root, filename) + + return None + + header_path = script.scratch_path / "header.h" + header_path.write_text('/* hello world */\n') + data_path = script.scratch_path / "data_file" + data_path.write_text("bletch") + pkga_path = script.scratch_path / "pkga" + pkga_path.mkdir() + setup_py = textwrap.dedent(""" + from setuptools import setup + setup(name='pkga', + version='0.1', + py_modules=["pkga"], + entry_points={{ + 'console_scripts': ['pkga_script=pkga:main'] + }}, + headers=[{header_path!r}], + data_files=[('data_files', [{data_path!r}])] + ) + """).format(header_path=str(header_path), data_path=str(data_path)) + + pkga_path.joinpath("setup.py").write_text(setup_py) + pkga_path.joinpath("pkga.py").write_text(textwrap.dedent(""" + def main(): pass + """)) + with TempDirectory() as temp_dir: + script.pip('install', '--root', temp_dir.path, pkga_path) + # Find the installation prefix. If we are running in a virtual + # environment, we will find the entire path leading up to the + # venv replicated under the root, so we might not find the + # root installation where we expect. + venv_base = find_in_path(temp_dir.path, "venv") + assert find_in_path(venv_base, "header.h") + assert find_in_path(venv_base, "pkga-0.1*.egg-info") + assert (find_in_path(venv_base, "pkga_script") or + find_in_path(venv_base, "pkga_script.exe")) + assert find_in_path(venv_base, "data_file") + + def test_install_package_with_prefix(script, data): """ Test installing a package using pip install --prefix