Skip to content

Add Scheme model and use for wheel installation #7310

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

Merged
merged 4 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions src/pip/_internal/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from distutils.command.install import SCHEME_KEYS # type: ignore
from distutils.command.install import install as distutils_install_command

from pip._internal.models.scheme import Scheme
from pip._internal.utils import appdirs
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.typing import MYPY_CHECK_RUNNING, cast
Expand Down Expand Up @@ -153,3 +154,41 @@ def distutils_scheme(
)

return scheme


def get_scheme(
dist_name, # type: str
user=False, # type: bool
home=None, # type: Optional[str]
root=None, # type: Optional[str]
isolated=False, # type: bool
prefix=None, # type: Optional[str]
):
# type: (...) -> Scheme
"""
Get the "scheme" corresponding to the input parameters. The distutils
documentation provides the context for the available schemes:
https://docs.python.org/3/install/index.html#alternate-installation

:param dist_name: the name of the package to retrieve the scheme for, used
in the headers scheme path
:param user: indicates to use the "user" scheme
:param home: indicates to use the "home" scheme and provides the base
directory for the same
:param root: root under which other directories are re-based
:param isolated: equivalent to --no-user-cfg, i.e. do not consider
~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for
scheme paths
:param prefix: indicates to use the "prefix" scheme and provides the
base directory for the same
"""
scheme = distutils_scheme(
dist_name, user, home, root, isolated, prefix
)
return Scheme(
platlib=scheme["platlib"],
purelib=scheme["purelib"],
headers=scheme["headers"],
scripts=scheme["scripts"],
data=scheme["data"],
)
25 changes: 25 additions & 0 deletions src/pip/_internal/models/scheme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
For types associated with installation schemes.

For a general overview of available schemes and their context, see
https://docs.python.org/3/install/index.html#alternate-installation.
"""


class Scheme(object):
"""A Scheme holds paths which are used as the base directories for
artifacts associated with a Python package.
"""
def __init__(
self,
platlib, # type: str
purelib, # type: str
headers, # type: str
scripts, # type: str
data, # type: str
):
self.platlib = platlib
self.purelib = purelib
self.headers = headers
self.scripts = scripts
self.data = data
29 changes: 15 additions & 14 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import os
import shutil
import sys
import sysconfig
import zipfile
from distutils.util import change_root

Expand All @@ -22,7 +21,7 @@
from pip._internal import pep425tags, wheel
from pip._internal.build_env import NoOpBuildEnvironment
from pip._internal.exceptions import InstallationError
from pip._internal.locations import distutils_scheme
from pip._internal.locations import get_scheme
from pip._internal.models.link import Link
from pip._internal.operations.build.metadata import generate_metadata
from pip._internal.operations.build.metadata_legacy import \
Expand Down Expand Up @@ -65,11 +64,12 @@

if MYPY_CHECK_RUNNING:
from typing import (
Any, Dict, Iterable, List, Mapping, Optional, Sequence, Union,
Any, Dict, Iterable, List, Optional, Sequence, Union,
)
from pip._internal.build_env import BuildEnvironment
from pip._internal.cache import WheelCache
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.scheme import Scheme
from pip._vendor.pkg_resources import Distribution
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.markers import Marker
Expand Down Expand Up @@ -540,7 +540,7 @@ def is_wheel(self):
def move_wheel_files(
self,
wheeldir, # type: str
scheme, # type: Mapping[str, str]
scheme, # type: Scheme
warn_script_location=True, # type: bool
pycompile=True # type: bool
):
Expand Down Expand Up @@ -852,6 +852,15 @@ def install(
pycompile=True # type: bool
):
# type: (...) -> None
scheme = get_scheme(
self.name,
user=use_user_site,
home=home,
root=root,
isolated=self.isolated,
prefix=prefix,
)

global_options = global_options if global_options is not None else []
if self.editable:
self.install_editable(
Expand All @@ -862,14 +871,11 @@ def install(
use_user_site=use_user_site,
)
return

if self.is_wheel:
version = wheel.wheel_version(self.source_dir)
wheel.check_compatibility(version, self.name)

scheme = distutils_scheme(
self.name, user=use_user_site, home=home, root=root,
isolated=self.isolated, prefix=prefix,
)
self.move_wheel_files(
self.source_dir,
scheme=scheme,
Expand All @@ -889,12 +895,7 @@ def install(
install_options = list(install_options) + \
self.options.get('install_options', [])

header_dir = None # type: Optional[str]
if running_under_virtualenv():
py_ver_str = 'python' + sysconfig.get_python_version()
header_dir = os.path.join(
sys.prefix, 'include', 'site', py_ver_str, self.name
)
header_dir = scheme.headers

with TempDirectory(kind="record") as temp_dir:
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
Expand Down
14 changes: 8 additions & 6 deletions src/pip/_internal/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@

if MYPY_CHECK_RUNNING:
from typing import (
Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any,
Dict, List, Optional, Sequence, Tuple, IO, Text, Any,
Iterable, Callable, Set,
)

from pip._internal.models.scheme import Scheme
from pip._internal.pep425tags import Pep425Tag

InstalledCSVRow = Tuple[str, ...]
Expand Down Expand Up @@ -289,7 +291,7 @@ def make(self, specification, options=None):
def install_unpacked_wheel(
name, # type: str
wheeldir, # type: str
scheme, # type: Mapping[str, str]
scheme, # type: Scheme
req_description, # type: str
pycompile=True, # type: bool
warn_script_location=True # type: bool
Expand All @@ -311,9 +313,9 @@ def install_unpacked_wheel(
# installation.

if root_is_purelib(name, wheeldir):
lib_dir = scheme['purelib']
lib_dir = scheme.purelib
else:
lib_dir = scheme['platlib']
lib_dir = scheme.platlib

info_dir = [] # type: List[str]
data_dirs = []
Expand Down Expand Up @@ -458,10 +460,10 @@ def is_entrypoint_wrapper(name):
fixer = fix_script
filter = is_entrypoint_wrapper
source = os.path.join(wheeldir, datadir, subdir)
dest = scheme[subdir]
dest = getattr(scheme, subdir)
clobber(source, dest, False, fixer=fixer, filter=filter)

maker = PipScriptMaker(None, scheme['scripts'])
maker = PipScriptMaker(None, scheme.scripts)

# Ensure old scripts are overwritten.
# See https://github.com/pypa/pip/issues/1800
Expand Down
25 changes: 14 additions & 11 deletions tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from pip._internal import pep425tags, wheel
from pip._internal.commands.wheel import WheelCommand
from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel
from pip._internal.locations import distutils_scheme
from pip._internal.locations import get_scheme
from pip._internal.models.link import Link
from pip._internal.models.scheme import Scheme
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.misc import hash_file
Expand Down Expand Up @@ -482,29 +483,31 @@ def prep(self, data, tmpdir):
self.src = os.path.join(tmpdir, 'src')
self.dest = os.path.join(tmpdir, 'dest')
unpack_file(self.wheelpath, self.src)
self.scheme = {
'scripts': os.path.join(self.dest, 'bin'),
'purelib': os.path.join(self.dest, 'lib'),
'data': os.path.join(self.dest, 'data'),
}
self.scheme = Scheme(
purelib=os.path.join(self.dest, 'lib'),
platlib=os.path.join(self.dest, 'lib'),
headers=os.path.join(self.dest, 'headers'),
scripts=os.path.join(self.dest, 'bin'),
data=os.path.join(self.dest, 'data'),
)
self.src_dist_info = os.path.join(
self.src, 'sample-1.2.0.dist-info')
self.dest_dist_info = os.path.join(
self.scheme['purelib'], 'sample-1.2.0.dist-info')
self.scheme.purelib, 'sample-1.2.0.dist-info')

def assert_installed(self):
# lib
assert os.path.isdir(
os.path.join(self.scheme['purelib'], 'sample'))
os.path.join(self.scheme.purelib, 'sample'))
# dist-info
metadata = os.path.join(self.dest_dist_info, 'METADATA')
assert os.path.isfile(metadata)
# data files
data_file = os.path.join(self.scheme['data'], 'my_data', 'data_file')
data_file = os.path.join(self.scheme.data, 'my_data', 'data_file')
assert os.path.isfile(data_file)
# package data
pkg_data = os.path.join(
self.scheme['purelib'], 'sample', 'package_data.dat')
self.scheme.purelib, 'sample', 'package_data.dat')
assert os.path.isfile(pkg_data)

def test_std_install(self, data, tmpdir):
Expand All @@ -520,7 +523,7 @@ def test_std_install(self, data, tmpdir):
def test_install_prefix(self, data, tmpdir):
prefix = os.path.join(os.path.sep, 'some', 'path')
self.prep(data, tmpdir)
scheme = distutils_scheme(
scheme = get_scheme(
self.name,
user=False,
home=None,
Expand Down