Skip to content

Commit

Permalink
Fix invalid header path when --root is set
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
lkollar committed Oct 7, 2020
1 parent 75befb5 commit cf00cbb
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 11 deletions.
2 changes: 2 additions & 0 deletions news/8477.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Install libs, headers and data files in the correct location when
``--root`` is specified.
6 changes: 2 additions & 4 deletions src/pip/_internal/operations/install/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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(
Expand Down Expand Up @@ -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 = (
Expand Down
16 changes: 9 additions & 7 deletions src/pip/_internal/utils/setuptools_build.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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
54 changes: 54 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import distutils
import fnmatch
import glob
import os
import re
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit cf00cbb

Please sign in to comment.