mirror of https://github.com/pypa/pip
Merge branch 'master' into refactor/reduce-action-at-distance
This commit is contained in:
commit
255a518157
|
@ -171,6 +171,28 @@ You can also refer to :ref:`constraints files <Constraints Files>`, like this::
|
|||
|
||||
-c some_constraints.txt
|
||||
|
||||
.. _`Using Environment Variables`:
|
||||
|
||||
Using Environment Variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 10, pip supports the use of environment variables inside the
|
||||
requirements file. You can now store sensitive data (tokens, keys, etc.) in
|
||||
environment variables and only specify the variable name for your requirements,
|
||||
letting pip lookup the value at runtime. This approach aligns with the commonly
|
||||
used `12-factor configuration pattern <https://12factor.net/config>`_.
|
||||
|
||||
You have to use the POSIX format for variable names including brackets around
|
||||
the uppercase name as shown in this example: ``${API_TOKEN}``. pip will attempt
|
||||
to find the corresponding environment variable defined on the host system at
|
||||
runtime.
|
||||
|
||||
.. note::
|
||||
|
||||
There is no support for other variable expansion syntaxes such as
|
||||
``$VARIABLE`` and ``%VARIABLE%``.
|
||||
|
||||
|
||||
.. _`Example Requirements File`:
|
||||
|
||||
Example Requirements File
|
||||
|
@ -432,6 +454,21 @@ Tags or revisions can be installed like so::
|
|||
[-e] bzr+https://bzr.example.com/MyProject/trunk@2019#egg=MyProject
|
||||
[-e] bzr+http://bzr.example.com/MyProject/trunk@v1.0#egg=MyProject
|
||||
|
||||
Using Environment Variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 10, pip also makes it possible to use environment variables which
|
||||
makes it possible to reference private repositories without having to store
|
||||
access tokens in the requirements file. For example, a private git repository
|
||||
allowing Basic Auth for authentication can be refenced like this::
|
||||
|
||||
[-e] git+http://${AUTH_USER}:${AUTH_PASSWORD}@git.example.com/MyProject#egg=MyProject
|
||||
[-e] git+https://${AUTH_USER}:${AUTH_PASSWORD}@git.example.com/MyProject#egg=MyProject
|
||||
|
||||
.. note::
|
||||
|
||||
Only ``${VARIABLE}`` is supported, other formats like ``$VARIABLE`` or
|
||||
``%VARIABLE%`` won't work.
|
||||
|
||||
Finding Packages
|
||||
++++++++++++++++
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Interactive setup.py files will no longer hang indefinitely.
|
|
@ -0,0 +1,2 @@
|
|||
pip now supports environment variable expansion in requirement files using
|
||||
only ``${VARIABLE}`` syntax on all platforms.
|
|
@ -0,0 +1,3 @@
|
|||
Installing from a local directory or a VCS URL now builds a wheel to install,
|
||||
rather than running ``setup.py install``. Wheels from these sources are not
|
||||
cached.
|
|
@ -0,0 +1 @@
|
|||
Use log level `info` instead of `warning` when ignoring packages due to environment markers.
|
|
@ -0,0 +1 @@
|
|||
Abort if reading configuration causes encoding errors.
|
|
@ -0,0 +1 @@
|
|||
Interactive setup.py files will no longer hang indefinitely.
|
|
@ -0,0 +1 @@
|
|||
Run 'setup.py develop' inside pep518 build environment.
|
|
@ -1 +1 @@
|
|||
Upgraded distro to 1.0.4.
|
||||
Upgraded distro to 1.2.0.
|
||||
|
|
|
@ -114,14 +114,16 @@ class Command(object):
|
|||
def main(self, args):
|
||||
options, args = self.parse_args(args)
|
||||
|
||||
verbosity = options.verbose - options.quiet
|
||||
if verbosity >= 1:
|
||||
# Set verbosity so that it can be used elsewhere.
|
||||
self.verbosity = options.verbose - options.quiet
|
||||
|
||||
if self.verbosity >= 1:
|
||||
level = "DEBUG"
|
||||
elif verbosity == -1:
|
||||
elif self.verbosity == -1:
|
||||
level = "WARNING"
|
||||
elif verbosity == -2:
|
||||
elif self.verbosity == -2:
|
||||
level = "ERROR"
|
||||
elif verbosity <= -3:
|
||||
elif self.verbosity <= -3:
|
||||
level = "CRITICAL"
|
||||
else:
|
||||
level = "INFO"
|
||||
|
|
|
@ -9,7 +9,7 @@ from distutils.util import strtobool
|
|||
|
||||
from pip._vendor.six import string_types
|
||||
|
||||
from pip._internal.configuration import Configuration
|
||||
from pip._internal.configuration import Configuration, ConfigurationError
|
||||
from pip._internal.utils.misc import get_terminal_size
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -177,9 +177,6 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
the environ. Does a little special handling for certain types of
|
||||
options (lists)."""
|
||||
|
||||
# Load the configuration
|
||||
self.config.load()
|
||||
|
||||
# Accumulate complex default state.
|
||||
self.values = optparse.Values(self.defaults)
|
||||
late_eval = set()
|
||||
|
@ -224,6 +221,12 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
# Old, pre-Optik 1.5 behaviour.
|
||||
return optparse.Values(self.defaults)
|
||||
|
||||
# Load the configuration, or error out in case of an error
|
||||
try:
|
||||
self.config.load()
|
||||
except ConfigurationError as err:
|
||||
self.exit(2, err.args[0])
|
||||
|
||||
defaults = self._update_defaults(self.defaults.copy()) # ours
|
||||
for option in self._get_all_options():
|
||||
default = defaults.get(option.dest)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
"""Build Environment used for isolation during sdist building
|
||||
"""
|
||||
|
||||
import os
|
||||
from sysconfig import get_paths
|
||||
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
||||
class BuildEnvironment(object):
|
||||
"""Creates and manages an isolated environment to install build deps
|
||||
"""
|
||||
|
||||
def __init__(self, no_clean):
|
||||
self._temp_dir = TempDirectory(kind="build-env")
|
||||
self._no_clean = no_clean
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._temp_dir.path
|
||||
|
||||
def __enter__(self):
|
||||
self._temp_dir.create()
|
||||
|
||||
self.save_path = os.environ.get('PATH', None)
|
||||
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
|
||||
|
||||
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||
install_dirs = get_paths(install_scheme, vars={
|
||||
'base': self.path,
|
||||
'platbase': self.path,
|
||||
})
|
||||
|
||||
scripts = install_dirs['scripts']
|
||||
if self.save_path:
|
||||
os.environ['PATH'] = scripts + os.pathsep + self.save_path
|
||||
else:
|
||||
os.environ['PATH'] = scripts + os.pathsep + os.defpath
|
||||
|
||||
if install_dirs['purelib'] == install_dirs['platlib']:
|
||||
lib_dirs = install_dirs['purelib']
|
||||
else:
|
||||
lib_dirs = install_dirs['purelib'] + os.pathsep + \
|
||||
install_dirs['platlib']
|
||||
if self.save_pythonpath:
|
||||
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
|
||||
self.save_pythonpath
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = lib_dirs
|
||||
|
||||
return self.path
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if not self._no_clean:
|
||||
self._temp_dir.cleanup()
|
||||
if self.save_path is None:
|
||||
os.environ.pop('PATH', None)
|
||||
else:
|
||||
os.environ['PATH'] = self.save_path
|
||||
|
||||
if self.save_pythonpath is None:
|
||||
os.environ.pop('PYTHONPATH', None)
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = self.save_pythonpath
|
||||
|
||||
def cleanup(self):
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
|
||||
class NoOpBuildEnvironment(BuildEnvironment):
|
||||
"""A no-op drop-in replacement for BuildEnvironment
|
||||
"""
|
||||
|
||||
def __init__(self, no_clean):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
|
@ -11,6 +11,7 @@ from pip._vendor.packaging.utils import canonicalize_name
|
|||
from pip._internal import index
|
||||
from pip._internal.compat import expanduser
|
||||
from pip._internal.download import path_to_url
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -102,13 +103,18 @@ class Cache(object):
|
|||
|
||||
return index.Link(path_to_url(path))
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
class WheelCache(Cache):
|
||||
|
||||
class SimpleWheelCache(Cache):
|
||||
"""A cache of wheels for future installs.
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
super(WheelCache, self).__init__(cache_dir, format_control, {"binary"})
|
||||
super(SimpleWheelCache, self).__init__(
|
||||
cache_dir, format_control, {"binary"}
|
||||
)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
"""Return a directory to store cached wheels for link
|
||||
|
@ -127,8 +133,7 @@ class WheelCache(Cache):
|
|||
"""
|
||||
parts = self._get_cache_path_parts(link)
|
||||
|
||||
# Inside of the base location for cached wheels, expand our parts and
|
||||
# join them all together.
|
||||
# Store wheels within the root cache_dir
|
||||
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||
|
||||
def get(self, link, package_name):
|
||||
|
@ -148,3 +153,50 @@ class WheelCache(Cache):
|
|||
return link
|
||||
|
||||
return self._link_for_candidate(link, min(candidates)[1])
|
||||
|
||||
|
||||
class EphemWheelCache(SimpleWheelCache):
|
||||
"""A SimpleWheelCache that creates it's own temporary cache directory
|
||||
"""
|
||||
|
||||
def __init__(self, format_control):
|
||||
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
|
||||
self._temp_dir.create()
|
||||
|
||||
super(EphemWheelCache, self).__init__(
|
||||
self._temp_dir.path, format_control
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
|
||||
class WheelCache(Cache):
|
||||
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
|
||||
|
||||
This Cache allows for gracefully degradation, using the ephem wheel cache
|
||||
when a certain link is not found in the simple wheel cache first.
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
super(WheelCache, self).__init__(
|
||||
cache_dir, format_control, {'binary'}
|
||||
)
|
||||
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
|
||||
self._ephem_cache = EphemWheelCache(format_control)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
return self._wheel_cache.get_path_for_link(link)
|
||||
|
||||
def get_ephem_path_for_link(self, link):
|
||||
return self._ephem_cache.get_path_for_link(link)
|
||||
|
||||
def get(self, link, package_name):
|
||||
retval = self._wheel_cache.get(link, package_name)
|
||||
if retval is link:
|
||||
retval = self._ephem_cache.get(link, package_name)
|
||||
return retval
|
||||
|
||||
def cleanup(self):
|
||||
self._wheel_cache.cleanup()
|
||||
self._ephem_cache.cleanup()
|
||||
|
|
|
@ -89,5 +89,8 @@ class FreezeCommand(Command):
|
|||
exclude_editable=options.exclude_editable,
|
||||
)
|
||||
|
||||
for line in freeze(**freeze_kwargs):
|
||||
sys.stdout.write(line + '\n')
|
||||
try:
|
||||
for line in freeze(**freeze_kwargs):
|
||||
sys.stdout.write(line + '\n')
|
||||
finally:
|
||||
wheel_cache.cleanup()
|
||||
|
|
|
@ -224,10 +224,10 @@ class InstallCommand(RequirementCommand):
|
|||
global_options = options.global_options or []
|
||||
|
||||
with self._build_session(options) as session:
|
||||
|
||||
finder = self._build_package_finder(options, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
|
@ -246,18 +246,19 @@ class InstallCommand(RequirementCommand):
|
|||
require_hashes=options.require_hashes,
|
||||
)
|
||||
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session, self.name,
|
||||
wheel_cache
|
||||
)
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
)
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
|
@ -339,7 +340,7 @@ class InstallCommand(RequirementCommand):
|
|||
message_parts.append("\n")
|
||||
|
||||
logger.error(
|
||||
"".join(message_parts), exc_info=(options.verbose > 1)
|
||||
"".join(message_parts), exc_info=(self.verbosity > 1)
|
||||
)
|
||||
return ERROR
|
||||
except PreviousBuildDirError:
|
||||
|
@ -349,6 +350,7 @@ class InstallCommand(RequirementCommand):
|
|||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
|
||||
if options.target_dir:
|
||||
self._handle_target_dir(
|
||||
|
|
|
@ -65,7 +65,7 @@ class UninstallCommand(Command):
|
|||
)
|
||||
for req in reqs_to_uninstall.values():
|
||||
uninstall_pathset = req.uninstall(
|
||||
auto_confirm=options.yes, verbose=options.verbose != 0
|
||||
auto_confirm=options.yes, verbose=self.verbosity > 0,
|
||||
)
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
|
|
|
@ -146,35 +146,35 @@ class WheelCommand(RequirementCommand):
|
|||
require_hashes=options.require_hashes,
|
||||
)
|
||||
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session, self.name,
|
||||
wheel_cache
|
||||
)
|
||||
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
progress_bar=options.progress_bar,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
progress_bar=options.progress_bar,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
# build wheels
|
||||
wb = WheelBuilder(
|
||||
finder, preparer, wheel_cache,
|
||||
|
@ -195,3 +195,4 @@ class WheelCommand(RequirementCommand):
|
|||
finally:
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
|
|
|
@ -11,6 +11,7 @@ Some terminology:
|
|||
A single word describing where the configuration key-value pair came from
|
||||
"""
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
@ -283,8 +284,14 @@ class Configuration(object):
|
|||
# Doing this is useful when modifying and saving files, where we don't
|
||||
# need to construct a parser.
|
||||
if os.path.exists(fname):
|
||||
parser.read(fname)
|
||||
|
||||
try:
|
||||
parser.read(fname)
|
||||
except UnicodeDecodeError:
|
||||
raise ConfigurationError((
|
||||
"ERROR: "
|
||||
"Configuration file contains invalid %s characters.\n"
|
||||
"Please fix your configuration, located at %s\n"
|
||||
) % (locale.getpreferredencoding(False), fname))
|
||||
return parser
|
||||
|
||||
def _load_environment_vars(self):
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
"""Prepares a distribution for installation
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from copy import copy
|
||||
|
||||
from pip._vendor import pkg_resources, requests
|
||||
|
||||
from pip._internal.build_env import NoOpBuildEnvironment
|
||||
from pip._internal.compat import expanduser
|
||||
from pip._internal.download import (
|
||||
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
|
||||
|
@ -14,9 +18,14 @@ from pip._internal.exceptions import (
|
|||
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
|
||||
PreviousBuildDirError, VcsHashUnsupported,
|
||||
)
|
||||
from pip._internal.index import FormatControl
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.hashes import MissingHashes
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import display_path, normalize_path
|
||||
from pip._internal.utils.misc import (
|
||||
call_subprocess, display_path, normalize_path,
|
||||
)
|
||||
from pip._internal.utils.ui import open_spinner
|
||||
from pip._internal.vcs import vcs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -38,6 +47,26 @@ def make_abstract_dist(req):
|
|||
return IsSDist(req)
|
||||
|
||||
|
||||
def _install_build_reqs(finder, prefix, build_requirements):
|
||||
# NOTE: What follows is not a very good thing.
|
||||
# Eventually, this should move into the BuildEnvironment class and
|
||||
# that should handle all the isolation and sub-process invocation.
|
||||
finder = copy(finder)
|
||||
finder.format_control = FormatControl(set(), set([":all:"]))
|
||||
urls = [
|
||||
finder.find_requirement(
|
||||
InstallRequirement.from_line(r), upgrade=False).url
|
||||
for r in build_requirements
|
||||
]
|
||||
args = [
|
||||
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
|
||||
'--prefix', prefix,
|
||||
] + list(urls)
|
||||
|
||||
with open_spinner("Installing build dependencies") as spinner:
|
||||
call_subprocess(args, show_stdout=False, spinner=spinner)
|
||||
|
||||
|
||||
class DistAbstraction(object):
|
||||
"""Abstracts out the wheel vs non-wheel Resolver.resolve() logic.
|
||||
|
||||
|
@ -64,7 +93,7 @@ class DistAbstraction(object):
|
|||
"""Return a setuptools Dist object."""
|
||||
raise NotImplementedError(self.dist)
|
||||
|
||||
def prep_for_dist(self):
|
||||
def prep_for_dist(self, finder):
|
||||
"""Ensure that we can get a Dist for this requirement."""
|
||||
raise NotImplementedError(self.dist)
|
||||
|
||||
|
@ -75,7 +104,7 @@ class IsWheel(DistAbstraction):
|
|||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prep_for_dist(self):
|
||||
def prep_for_dist(self, finder):
|
||||
# FIXME:https://github.com/pypa/pip/issues/1112
|
||||
pass
|
||||
|
||||
|
@ -91,9 +120,27 @@ class IsSDist(DistAbstraction):
|
|||
)
|
||||
return dist
|
||||
|
||||
def prep_for_dist(self):
|
||||
self.req.run_egg_info()
|
||||
self.req.assert_source_matches_version()
|
||||
def prep_for_dist(self, finder):
|
||||
# Before calling "setup.py egg_info", we need to set-up the build
|
||||
# environment.
|
||||
|
||||
build_requirements, isolate = self.req.get_pep_518_info()
|
||||
|
||||
if 'setuptools' not in build_requirements:
|
||||
logger.warning(
|
||||
"This version of pip does not implement PEP 516, so "
|
||||
"it cannot build a wheel without setuptools. You may need to "
|
||||
"upgrade to a newer version of pip.")
|
||||
|
||||
if not isolate:
|
||||
self.req.build_env = NoOpBuildEnvironment(no_clean=False)
|
||||
|
||||
with self.req.build_env as prefix:
|
||||
if isolate:
|
||||
_install_build_reqs(finder, prefix, build_requirements)
|
||||
|
||||
self.req.run_egg_info()
|
||||
self.req.assert_source_matches_version()
|
||||
|
||||
|
||||
class Installed(DistAbstraction):
|
||||
|
@ -101,7 +148,7 @@ class Installed(DistAbstraction):
|
|||
def dist(self, finder):
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prep_for_dist(self):
|
||||
def prep_for_dist(self, finder):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -259,14 +306,15 @@ class RequirementPreparer(object):
|
|||
(req, exc, req.link)
|
||||
)
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
abstract_dist.prep_for_dist()
|
||||
abstract_dist.prep_for_dist(finder)
|
||||
if self._download_should_save:
|
||||
# Make a .zip of the source_dir we already created.
|
||||
if req.link.scheme in vcs.all_schemes:
|
||||
req.archive(self.download_dir)
|
||||
return abstract_dist
|
||||
|
||||
def prepare_editable_requirement(self, req, require_hashes, use_user_site):
|
||||
def prepare_editable_requirement(self, req, require_hashes, use_user_site,
|
||||
finder):
|
||||
"""Prepare an editable requirement
|
||||
"""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
|
@ -284,7 +332,7 @@ class RequirementPreparer(object):
|
|||
req.update_editable(not self._download_should_save)
|
||||
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
abstract_dist.prep_for_dist()
|
||||
abstract_dist.prep_for_dist(finder)
|
||||
|
||||
if self._download_should_save:
|
||||
req.archive(self.download_dir)
|
||||
|
|
|
@ -23,6 +23,12 @@ __all__ = ['parse_requirements']
|
|||
SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
|
||||
COMMENT_RE = re.compile(r'(^|\s)+#.*$')
|
||||
|
||||
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
|
||||
# variable name consisting of only uppercase letters, digits or the '_'
|
||||
# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
|
||||
# 2013 Edition.
|
||||
ENV_VAR_RE = re.compile(r'(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})')
|
||||
|
||||
SUPPORTED_OPTIONS = [
|
||||
cmdoptions.constraints,
|
||||
cmdoptions.editable,
|
||||
|
@ -94,6 +100,7 @@ def preprocess(content, options):
|
|||
lines_enum = join_lines(lines_enum)
|
||||
lines_enum = ignore_comments(lines_enum)
|
||||
lines_enum = skip_regex(lines_enum, options)
|
||||
lines_enum = expand_env_variables(lines_enum)
|
||||
return lines_enum
|
||||
|
||||
|
||||
|
@ -302,3 +309,30 @@ def skip_regex(lines_enum, options):
|
|||
pattern = re.compile(skip_regex)
|
||||
lines_enum = filterfalse(lambda e: pattern.search(e[1]), lines_enum)
|
||||
return lines_enum
|
||||
|
||||
|
||||
def expand_env_variables(lines_enum):
|
||||
"""Replace all environment variables that can be retrieved via `os.getenv`.
|
||||
|
||||
The only allowed format for environment variables defined in the
|
||||
requirement file is `${MY_VARIABLE_1}` to ensure two things:
|
||||
|
||||
1. Strings that contain a `$` aren't accidentally (partially) expanded.
|
||||
2. Ensure consistency across platforms for requirement files.
|
||||
|
||||
These points are the result of a discusssion on the `github pull
|
||||
request #3514 <https://github.com/pypa/pip/pull/3514>`_.
|
||||
|
||||
Valid characters in variable names follow the `POSIX standard
|
||||
<http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
|
||||
to uppercase letter, digits and the `_` (underscore).
|
||||
"""
|
||||
for line_number, line in lines_enum:
|
||||
for env_var, var_name in ENV_VAR_RE.findall(line):
|
||||
value = os.getenv(var_name)
|
||||
if not value:
|
||||
continue
|
||||
|
||||
line = line.replace(env_var, value)
|
||||
|
||||
yield line_number, line
|
||||
|
|
|
@ -12,7 +12,7 @@ import zipfile
|
|||
from distutils.util import change_root
|
||||
from email.parser import FeedParser
|
||||
|
||||
from pip._vendor import pkg_resources, six
|
||||
from pip._vendor import pkg_resources, pytoml, six
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
|
@ -22,6 +22,7 @@ from pip._vendor.packaging.version import Version
|
|||
from pip._vendor.pkg_resources import RequirementParseError, parse_requirements
|
||||
|
||||
from pip._internal import wheel
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.compat import native_str
|
||||
from pip._internal.download import (
|
||||
is_archive_file, is_url, path_to_url, url_to_path,
|
||||
|
@ -126,6 +127,7 @@ class InstallRequirement(object):
|
|||
self.is_direct = False
|
||||
|
||||
self.isolated = isolated
|
||||
self.build_env = BuildEnvironment(no_clean=True)
|
||||
|
||||
@classmethod
|
||||
def from_editable(cls, editable_req, comes_from=None, isolated=False,
|
||||
|
@ -450,6 +452,22 @@ class InstallRequirement(object):
|
|||
|
||||
return pp_toml
|
||||
|
||||
def get_pep_518_info(self):
|
||||
"""Get a list of the packages required to build the project, if any,
|
||||
and a flag indicating whether pyproject.toml is present, indicating
|
||||
that the build should be isolated.
|
||||
|
||||
Build requirements can be specified in a pyproject.toml, as described
|
||||
in PEP 518. If this file exists but doesn't specify build
|
||||
requirements, pip will default to installing setuptools and wheel.
|
||||
"""
|
||||
if os.path.isfile(self.pyproject_toml):
|
||||
with open(self.pyproject_toml) as f:
|
||||
pp_toml = pytoml.load(f)
|
||||
build_sys = pp_toml.get('build-system', {})
|
||||
return (build_sys.get('requires', ['setuptools', 'wheel']), True)
|
||||
return (['setuptools', 'wheel'], False)
|
||||
|
||||
def run_egg_info(self):
|
||||
assert self.source_dir
|
||||
if self.name:
|
||||
|
@ -866,6 +884,7 @@ class InstallRequirement(object):
|
|||
rmtree(self.source_dir)
|
||||
self.source_dir = None
|
||||
self._temp_build_dir.cleanup()
|
||||
self.build_env.cleanup()
|
||||
|
||||
def install_editable(self, install_options,
|
||||
global_options=(), prefix=None):
|
||||
|
@ -880,19 +899,20 @@ class InstallRequirement(object):
|
|||
|
||||
with indent_log():
|
||||
# FIXME: should we do --install-headers here too?
|
||||
call_subprocess(
|
||||
[
|
||||
sys.executable,
|
||||
'-c',
|
||||
SETUPTOOLS_SHIM % self.setup_py
|
||||
] +
|
||||
list(global_options) +
|
||||
['develop', '--no-deps'] +
|
||||
list(install_options),
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
[
|
||||
sys.executable,
|
||||
'-c',
|
||||
SETUPTOOLS_SHIM % self.setup_py
|
||||
] +
|
||||
list(global_options) +
|
||||
['develop', '--no-deps'] +
|
||||
list(install_options),
|
||||
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
)
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
)
|
||||
|
||||
self.install_succeeded = True
|
||||
|
||||
|
|
|
@ -54,16 +54,16 @@ class RequirementSet(object):
|
|||
already be added. Note that None implies that this is a user
|
||||
supplied requirement, vs an inferred one.
|
||||
:param extras_requested: an iterable of extras used to evaluate the
|
||||
environement markers.
|
||||
environment markers.
|
||||
:return: Additional requirements to scan. That is either [] if
|
||||
the requirement is not applicable, or [install_req] if the
|
||||
requirement is applicable and has just been added.
|
||||
"""
|
||||
name = install_req.name
|
||||
if not install_req.match_markers(extras_requested):
|
||||
logger.warning("Ignoring %s: markers '%s' don't match your "
|
||||
"environment", install_req.name,
|
||||
install_req.markers)
|
||||
logger.info("Ignoring %s: markers '%s' don't match your "
|
||||
"environment", install_req.name,
|
||||
install_req.markers)
|
||||
return []
|
||||
|
||||
# This check has to come after we filter requirements with the
|
||||
|
|
|
@ -187,7 +187,8 @@ class Resolver(object):
|
|||
|
||||
if req.editable:
|
||||
return self.preparer.prepare_editable_requirement(
|
||||
req, self.require_hashes, self.use_user_site,
|
||||
req, self.require_hashes, self.use_user_site, self.finder,
|
||||
|
||||
)
|
||||
|
||||
# satisfied_by is only evaluated by calling _check_skip_installed,
|
||||
|
@ -220,7 +221,9 @@ class Resolver(object):
|
|||
if req.satisfied_by:
|
||||
should_modify = (
|
||||
self.upgrade_strategy != "to-satisfy-only" or
|
||||
self.force_reinstall or self.ignore_installed
|
||||
self.force_reinstall or
|
||||
self.ignore_installed or
|
||||
req.link.scheme == 'file'
|
||||
)
|
||||
if should_modify:
|
||||
self._set_req_to_reinstall(req)
|
||||
|
@ -244,11 +247,12 @@ class Resolver(object):
|
|||
return []
|
||||
|
||||
req_to_install.prepared = True
|
||||
abstract_dist = self._get_abstract_dist_for(req_to_install)
|
||||
|
||||
# register tmp src for cleanup in case something goes wrong
|
||||
requirement_set.reqs_to_cleanup.append(req_to_install)
|
||||
|
||||
abstract_dist = self._get_abstract_dist_for(req_to_install)
|
||||
|
||||
# Parse and return dependencies
|
||||
dist = abstract_dist.dist(self.finder)
|
||||
try:
|
||||
|
|
|
@ -678,9 +678,10 @@ def call_subprocess(cmd, show_stdout=True, cwd=None,
|
|||
env.pop(name, None)
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
|
||||
cwd=cwd, env=env,
|
||||
cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
|
||||
stdout=stdout, cwd=cwd, env=env,
|
||||
)
|
||||
proc.stdin.close()
|
||||
except Exception as exc:
|
||||
logger.critical(
|
||||
"Error %s while executing command %s", exc, command_desc,
|
||||
|
|
|
@ -17,14 +17,14 @@ import sys
|
|||
import warnings
|
||||
from base64 import urlsafe_b64encode
|
||||
from email.parser import Parser
|
||||
from sysconfig import get_paths
|
||||
|
||||
from pip._vendor import pkg_resources, pytoml
|
||||
from pip._vendor import pkg_resources
|
||||
from pip._vendor.distlib.scripts import ScriptMaker
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.six import StringIO
|
||||
|
||||
from pip._internal import pep425tags
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.download import path_to_url, unpack_url
|
||||
from pip._internal.exceptions import (
|
||||
InstallationError, InvalidWheelFilename, UnsupportedWheel,
|
||||
|
@ -602,58 +602,6 @@ class Wheel(object):
|
|||
return bool(set(tags).intersection(self.file_tags))
|
||||
|
||||
|
||||
class BuildEnvironment(object):
|
||||
"""Context manager to install build deps in a simple temporary environment
|
||||
"""
|
||||
def __init__(self, no_clean):
|
||||
self._temp_dir = TempDirectory(kind="build-env")
|
||||
self._no_clean = no_clean
|
||||
|
||||
def __enter__(self):
|
||||
self._temp_dir.create()
|
||||
|
||||
self.save_path = os.environ.get('PATH', None)
|
||||
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
|
||||
|
||||
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||
install_dirs = get_paths(install_scheme, vars={
|
||||
'base': self._temp_dir.path,
|
||||
'platbase': self._temp_dir.path,
|
||||
})
|
||||
|
||||
scripts = install_dirs['scripts']
|
||||
if self.save_path:
|
||||
os.environ['PATH'] = scripts + os.pathsep + self.save_path
|
||||
else:
|
||||
os.environ['PATH'] = scripts + os.pathsep + os.defpath
|
||||
|
||||
if install_dirs['purelib'] == install_dirs['platlib']:
|
||||
lib_dirs = install_dirs['purelib']
|
||||
else:
|
||||
lib_dirs = install_dirs['purelib'] + os.pathsep + \
|
||||
install_dirs['platlib']
|
||||
if self.save_pythonpath:
|
||||
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
|
||||
self.save_pythonpath
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = lib_dirs
|
||||
|
||||
return self._temp_dir.path
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if not self._no_clean:
|
||||
self._temp_dir.cleanup()
|
||||
if self.save_path is None:
|
||||
os.environ.pop('PATH', None)
|
||||
else:
|
||||
os.environ['PATH'] = self.save_path
|
||||
|
||||
if self.save_pythonpath is None:
|
||||
os.environ.pop('PYTHONPATH', None)
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = self.save_pythonpath
|
||||
|
||||
|
||||
class WheelBuilder(object):
|
||||
"""Build wheels from a RequirementSet."""
|
||||
|
||||
|
@ -669,54 +617,13 @@ class WheelBuilder(object):
|
|||
self.global_options = global_options or []
|
||||
self.no_clean = no_clean
|
||||
|
||||
def _find_build_reqs(self, req):
|
||||
"""Get a list of the packages required to build the project, if any,
|
||||
and a flag indicating whether pyproject.toml is present, indicating
|
||||
that the build should be isolated.
|
||||
|
||||
Build requirements can be specified in a pyproject.toml, as described
|
||||
in PEP 518. If this file exists but doesn't specify build
|
||||
requirements, pip will default to installing setuptools and wheel.
|
||||
"""
|
||||
if os.path.isfile(req.pyproject_toml):
|
||||
with open(req.pyproject_toml) as f:
|
||||
pp_toml = pytoml.load(f)
|
||||
return pp_toml.get('build-system', {})\
|
||||
.get('requires', ['setuptools', 'wheel']), True
|
||||
|
||||
return ['setuptools', 'wheel'], False
|
||||
|
||||
def _install_build_reqs(self, reqs, prefix):
|
||||
# Local import to avoid circular import (wheel <-> req_install)
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.index import FormatControl
|
||||
# Ignore the --no-binary option when installing the build system, so
|
||||
# we don't recurse trying to build a self-hosting build system.
|
||||
finder = copy.copy(self.finder)
|
||||
finder.format_control = FormatControl(set(), set())
|
||||
urls = [finder.find_requirement(InstallRequirement.from_line(r),
|
||||
upgrade=False).url
|
||||
for r in reqs]
|
||||
|
||||
args = [sys.executable, '-m', 'pip', 'install', '--ignore-installed',
|
||||
'--prefix', prefix] + list(urls)
|
||||
with open_spinner("Installing build dependencies") as spinner:
|
||||
call_subprocess(args, show_stdout=False, spinner=spinner)
|
||||
|
||||
def _build_one(self, req, output_dir, python_tag=None):
|
||||
"""Build one wheel.
|
||||
|
||||
:return: The filename of the built wheel, or None if the build failed.
|
||||
"""
|
||||
build_reqs, isolate = self._find_build_reqs(req)
|
||||
if 'setuptools' not in build_reqs:
|
||||
logger.warning(
|
||||
"This version of pip does not implement PEP 516, so "
|
||||
"it cannot build a wheel without setuptools. You may need to "
|
||||
"upgrade to a newer version of pip.")
|
||||
# Install build deps into temporary directory (PEP 518)
|
||||
with BuildEnvironment(self.no_clean) as prefix:
|
||||
self._install_build_reqs(build_reqs, prefix)
|
||||
with req.build_env:
|
||||
return self._build_one_inside_env(req, output_dir,
|
||||
python_tag=python_tag,
|
||||
isolate=True)
|
||||
|
@ -806,6 +713,7 @@ class WheelBuilder(object):
|
|||
|
||||
buildset = []
|
||||
for req in requirements:
|
||||
ephem_cache = False
|
||||
if req.constraint:
|
||||
continue
|
||||
if req.is_wheel:
|
||||
|
@ -816,7 +724,8 @@ class WheelBuilder(object):
|
|||
elif autobuilding and req.editable:
|
||||
pass
|
||||
elif autobuilding and req.link and not req.link.is_artifact:
|
||||
pass
|
||||
# VCS checkout. Build wheel just for this run.
|
||||
ephem_cache = True
|
||||
elif autobuilding and not req.source_dir:
|
||||
pass
|
||||
else:
|
||||
|
@ -824,9 +733,8 @@ class WheelBuilder(object):
|
|||
link = req.link
|
||||
base, ext = link.splitext()
|
||||
if index.egg_info_matches(base, None, link) is None:
|
||||
# Doesn't look like a package - don't autobuild a wheel
|
||||
# because we'll have no way to lookup the result sanely
|
||||
continue
|
||||
# E.g. local directory. Build wheel just for this run.
|
||||
ephem_cache = True
|
||||
if "binary" not in index.fmt_ctl_formats(
|
||||
self.finder.format_control,
|
||||
canonicalize_name(req.name)):
|
||||
|
@ -835,7 +743,7 @@ class WheelBuilder(object):
|
|||
"being disabled for it.", req.name,
|
||||
)
|
||||
continue
|
||||
buildset.append(req)
|
||||
buildset.append((req, ephem_cache))
|
||||
|
||||
if not buildset:
|
||||
return True
|
||||
|
@ -843,15 +751,19 @@ class WheelBuilder(object):
|
|||
# Build the wheels.
|
||||
logger.info(
|
||||
'Building wheels for collected packages: %s',
|
||||
', '.join([req.name for req in buildset]),
|
||||
', '.join([req.name for (req, _) in buildset]),
|
||||
)
|
||||
_cache = self.wheel_cache # shorter name
|
||||
with indent_log():
|
||||
build_success, build_failure = [], []
|
||||
for req in buildset:
|
||||
for req, ephem in buildset:
|
||||
python_tag = None
|
||||
if autobuilding:
|
||||
python_tag = pep425tags.implementation_tag
|
||||
output_dir = self.wheel_cache.get_path_for_link(req.link)
|
||||
if ephem:
|
||||
output_dir = _cache.get_ephem_path_for_link(req.link)
|
||||
else:
|
||||
output_dir = _cache.get_path_for_link(req.link)
|
||||
try:
|
||||
ensure_dir(output_dir)
|
||||
except OSError as e:
|
||||
|
|
|
@ -557,18 +557,14 @@ def _get_win_folder_with_jna(csidl_name):
|
|||
|
||||
if system == "win32":
|
||||
try:
|
||||
import win32com.shell
|
||||
_get_win_folder = _get_win_folder_with_pywin32
|
||||
from ctypes import windll
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
except ImportError:
|
||||
try:
|
||||
from ctypes import windll
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
import com.sun.jna
|
||||
_get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
try:
|
||||
import com.sun.jna
|
||||
_get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
|
||||
|
||||
#---- self test code
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from appdirs import *
|
|
@ -38,9 +38,6 @@ import argparse
|
|||
import subprocess
|
||||
|
||||
|
||||
if not sys.platform.startswith('linux'):
|
||||
raise ImportError('Unsupported platform: {0}'.format(sys.platform))
|
||||
|
||||
_UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc')
|
||||
_OS_RELEASE_BASENAME = 'os-release'
|
||||
|
||||
|
@ -511,6 +508,21 @@ def distro_release_attr(attribute):
|
|||
return _distro.distro_release_attr(attribute)
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
"""A version of @property which caches the value. On access, it calls the
|
||||
underlying function and sets the value in `__dict__` so future accesses
|
||||
will not re-call the property.
|
||||
"""
|
||||
def __init__(self, f):
|
||||
self._fname = f.__name__
|
||||
self._f = f
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
assert obj is not None, 'call {} on an instance'.format(self._fname)
|
||||
ret = obj.__dict__[self._fname] = self._f(obj)
|
||||
return ret
|
||||
|
||||
|
||||
class LinuxDistribution(object):
|
||||
"""
|
||||
Provides information about a Linux distribution.
|
||||
|
@ -576,6 +588,9 @@ class LinuxDistribution(object):
|
|||
`distro release file`_ that is actually used as a data source. The
|
||||
empty string if no distro release file is used as a data source.
|
||||
|
||||
* ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
|
||||
This controls whether the lsb information will be loaded.
|
||||
|
||||
Raises:
|
||||
|
||||
* :py:exc:`IOError`: Some I/O issue with an os-release file or distro
|
||||
|
@ -591,26 +606,20 @@ class LinuxDistribution(object):
|
|||
self.os_release_file = os_release_file or \
|
||||
os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME)
|
||||
self.distro_release_file = distro_release_file or '' # updated later
|
||||
self._os_release_info = self._get_os_release_info()
|
||||
self._lsb_release_info = self._get_lsb_release_info() \
|
||||
if include_lsb else {}
|
||||
self._distro_release_info = self._get_distro_release_info()
|
||||
self.include_lsb = include_lsb
|
||||
|
||||
def __repr__(self):
|
||||
"""Return repr of all info
|
||||
"""
|
||||
return \
|
||||
"LinuxDistribution(" \
|
||||
"os_release_file={0!r}, " \
|
||||
"distro_release_file={1!r}, " \
|
||||
"_os_release_info={2!r}, " \
|
||||
"_lsb_release_info={3!r}, " \
|
||||
"_distro_release_info={4!r})".format(
|
||||
self.os_release_file,
|
||||
self.distro_release_file,
|
||||
self._os_release_info,
|
||||
self._lsb_release_info,
|
||||
self._distro_release_info)
|
||||
"os_release_file={self.os_release_file!r}, " \
|
||||
"distro_release_file={self.distro_release_file!r}, " \
|
||||
"include_lsb={self.include_lsb!r}, " \
|
||||
"_os_release_info={self._os_release_info!r}, " \
|
||||
"_lsb_release_info={self._lsb_release_info!r}, " \
|
||||
"_distro_release_info={self._distro_release_info!r})".format(
|
||||
self=self)
|
||||
|
||||
def linux_distribution(self, full_distribution_name=True):
|
||||
"""
|
||||
|
@ -835,7 +844,8 @@ class LinuxDistribution(object):
|
|||
"""
|
||||
return self._distro_release_info.get(attribute, '')
|
||||
|
||||
def _get_os_release_info(self):
|
||||
@cached_property
|
||||
def _os_release_info(self):
|
||||
"""
|
||||
Get the information items from the specified os-release file.
|
||||
|
||||
|
@ -907,34 +917,24 @@ class LinuxDistribution(object):
|
|||
pass
|
||||
return props
|
||||
|
||||
def _get_lsb_release_info(self):
|
||||
@cached_property
|
||||
def _lsb_release_info(self):
|
||||
"""
|
||||
Get the information items from the lsb_release command output.
|
||||
|
||||
Returns:
|
||||
A dictionary containing all information items.
|
||||
"""
|
||||
cmd = 'lsb_release -a'
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = process.communicate()
|
||||
stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8')
|
||||
code = process.returncode
|
||||
if code == 0:
|
||||
content = stdout.splitlines()
|
||||
return self._parse_lsb_release_content(content)
|
||||
elif code == 127: # Command not found
|
||||
if not self.include_lsb:
|
||||
return {}
|
||||
else:
|
||||
if sys.version_info[:2] >= (3, 5):
|
||||
raise subprocess.CalledProcessError(code, cmd, stdout, stderr)
|
||||
elif sys.version_info[:2] >= (2, 7):
|
||||
raise subprocess.CalledProcessError(code, cmd, stdout)
|
||||
elif sys.version_info[:2] == (2, 6):
|
||||
raise subprocess.CalledProcessError(code, cmd)
|
||||
with open(os.devnull, 'w') as devnull:
|
||||
try:
|
||||
cmd = ('lsb_release', '-a')
|
||||
stdout = subprocess.check_output(cmd, stderr=devnull)
|
||||
except OSError: # Command not found
|
||||
return {}
|
||||
content = stdout.decode(sys.getfilesystemencoding()).splitlines()
|
||||
return self._parse_lsb_release_content(content)
|
||||
|
||||
@staticmethod
|
||||
def _parse_lsb_release_content(lines):
|
||||
|
@ -952,7 +952,6 @@ class LinuxDistribution(object):
|
|||
"""
|
||||
props = {}
|
||||
for line in lines:
|
||||
line = line.decode('utf-8') if isinstance(line, bytes) else line
|
||||
kv = line.strip('\n').split(':', 1)
|
||||
if len(kv) != 2:
|
||||
# Ignore lines without colon.
|
||||
|
@ -961,7 +960,8 @@ class LinuxDistribution(object):
|
|||
props.update({k.replace(' ', '_').lower(): v.strip()})
|
||||
return props
|
||||
|
||||
def _get_distro_release_info(self):
|
||||
@cached_property
|
||||
def _distro_release_info(self):
|
||||
"""
|
||||
Get the information items from the specified distro release file.
|
||||
|
||||
|
@ -1001,6 +1001,9 @@ class LinuxDistribution(object):
|
|||
'fedora-release',
|
||||
'gentoo-release',
|
||||
'mageia-release',
|
||||
'mandrake-release',
|
||||
'mandriva-release',
|
||||
'mandrivalinux-release',
|
||||
'manjaro-release',
|
||||
'oracle-release',
|
||||
'redhat-release',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
appdirs==1.4.3
|
||||
distlib==0.2.6
|
||||
distro==1.0.4
|
||||
distro==1.2.0
|
||||
html5lib==1.0b10
|
||||
six==1.11.0
|
||||
colorama==0.3.9
|
||||
|
|
|
@ -144,9 +144,11 @@ def update_stubs(ctx):
|
|||
|
||||
print("[vendoring.update_stubs] Add mypy stubs")
|
||||
|
||||
# Some projects need stubs other than a simple <name>.pyi
|
||||
extra_stubs_needed = {
|
||||
"six": ["six.__init__", "six.moves"]
|
||||
# Some projects need stubs other than a simple <name>.pyi
|
||||
"six": ["six.__init__", "six.moves"],
|
||||
# Some projects should not have stubs coz they're single file modules
|
||||
"appdirs": [],
|
||||
}
|
||||
|
||||
for lib in vendored_libs:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/appdirs.py
|
||||
index ae67001a..2bd39110 100644
|
||||
--- a/src/pip/_vendor/appdirs.py
|
||||
+++ b/src/pip/_vendor/appdirs.py
|
||||
@@ -557,18 +557,14 @@ def _get_win_folder_with_jna(csidl_name):
|
||||
|
||||
if system == "win32":
|
||||
try:
|
||||
- import win32com.shell
|
||||
- _get_win_folder = _get_win_folder_with_pywin32
|
||||
+ from ctypes import windll
|
||||
+ _get_win_folder = _get_win_folder_with_ctypes
|
||||
except ImportError:
|
||||
try:
|
||||
- from ctypes import windll
|
||||
- _get_win_folder = _get_win_folder_with_ctypes
|
||||
+ import com.sun.jna
|
||||
+ _get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
- try:
|
||||
- import com.sun.jna
|
||||
- _get_win_folder = _get_win_folder_with_jna
|
||||
- except ImportError:
|
||||
- _get_win_folder = _get_win_folder_from_registry
|
||||
+ _get_win_folder = _get_win_folder_from_registry
|
||||
|
||||
|
||||
#---- self test code
|
Binary file not shown.
|
@ -1,2 +1,2 @@
|
|||
[build-system]
|
||||
requires=["simple==3.0", "setuptools", "wheel"]
|
||||
requires=["simplewheel==2.0", "setuptools", "wheel"]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
import simple # ensure dependency is installed
|
||||
|
||||
setup(name='pep518',
|
||||
version='3.0',
|
||||
packages=find_packages()
|
||||
|
|
|
@ -994,16 +994,15 @@ def test_install_builds_wheels(script, data, common_wheels):
|
|||
# and built wheels for upper and wheelbroken
|
||||
assert "Running setup.py bdist_wheel for upper" in str(res), str(res)
|
||||
assert "Running setup.py bdist_wheel for wheelb" in str(res), str(res)
|
||||
# But not requires_wheel... which is a local dir and thus uncachable.
|
||||
assert "Running setup.py bdist_wheel for requir" not in str(res), str(res)
|
||||
# Wheels are built for local directories, but not cached.
|
||||
assert "Running setup.py bdist_wheel for requir" in str(res), str(res)
|
||||
# wheelbroken has to run install
|
||||
# into the cache
|
||||
assert wheels != [], str(res)
|
||||
# and installed from the wheel
|
||||
assert "Running setup.py install for upper" not in str(res), str(res)
|
||||
# the local tree can't build a wheel (because we can't assume that every
|
||||
# build will have a suitable unique key to cache on).
|
||||
assert "Running setup.py install for requires-wheel" in str(res), str(res)
|
||||
# Wheels are built for local directories, but not cached.
|
||||
assert "Running setup.py install for requir" not in str(res), str(res)
|
||||
# wheelbroken has to run install
|
||||
assert "Running setup.py install for wheelb" in str(res), str(res)
|
||||
# We want to make sure we used the correct implementation tag
|
||||
|
@ -1027,13 +1026,12 @@ def test_install_no_binary_disables_building_wheels(
|
|||
assert expected in str(res), str(res)
|
||||
# and built wheels for wheelbroken only
|
||||
assert "Running setup.py bdist_wheel for wheelb" in str(res), str(res)
|
||||
# But not requires_wheel... which is a local dir and thus uncachable.
|
||||
assert "Running setup.py bdist_wheel for requir" not in str(res), str(res)
|
||||
# Nor upper, which was blacklisted
|
||||
# Wheels are built for local directories, but not cached across runs
|
||||
assert "Running setup.py bdist_wheel for requir" in str(res), str(res)
|
||||
# Don't build wheel for upper which was blacklisted
|
||||
assert "Running setup.py bdist_wheel for upper" not in str(res), str(res)
|
||||
# the local tree can't build a wheel (because we can't assume that every
|
||||
# build will have a suitable unique key to cache on).
|
||||
assert "Running setup.py install for requires-wheel" in str(res), str(res)
|
||||
# Wheels are built for local directories, but not cached across runs
|
||||
assert "Running setup.py install for requir" not in str(res), str(res)
|
||||
# And these two fell back to sdist based installed.
|
||||
assert "Running setup.py install for wheelb" in str(res), str(res)
|
||||
assert "Running setup.py install for upper" in str(res), str(res)
|
||||
|
@ -1204,10 +1202,10 @@ def test_basic_install_environment_markers(script):
|
|||
)
|
||||
"""))
|
||||
|
||||
res = script.pip('install', '--no-index', pkga_path, expect_stderr=True)
|
||||
res = script.pip('install', '--no-index', pkga_path)
|
||||
# missing_pkg should be ignored
|
||||
assert ("Ignoring missing-pkg: markers 'python_version == \"1.0\"' don't "
|
||||
"match your environment") in res.stderr, str(res)
|
||||
"match your environment") in res.stdout, str(res)
|
||||
assert "Successfully installed pkga-0.1" in res.stdout, str(res)
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ def test_no_clean_option_blocks_cleaning_after_install(script, data):
|
|||
build = script.base_path / 'pip-build'
|
||||
script.pip(
|
||||
'install', '--no-clean', '--no-index', '--build', build,
|
||||
'--find-links=%s' % data.find_links, 'simple',
|
||||
'--find-links=%s' % data.find_links, 'simple', expect_temp=True,
|
||||
)
|
||||
assert exists(build)
|
||||
|
||||
|
@ -134,7 +134,7 @@ def test_cleanup_prevented_upon_build_dir_exception(script, data):
|
|||
result = script.pip(
|
||||
'install', '-f', data.find_links, '--no-index', 'simple',
|
||||
'--build', build,
|
||||
expect_error=True,
|
||||
expect_error=True, expect_temp=True,
|
||||
)
|
||||
|
||||
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR
|
||||
|
|
|
@ -300,6 +300,14 @@ def test_constraints_local_editable_install_causes_error(script, data):
|
|||
assert 'Could not satisfy constraints for' in result.stderr
|
||||
|
||||
|
||||
def test_constraints_local_editable_install_pep518(script, data):
|
||||
to_install = data.src.join("pep518-3.0")
|
||||
|
||||
script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
|
||||
script.pip(
|
||||
'install', '--no-index', '-f', data.find_links, '-e', to_install)
|
||||
|
||||
|
||||
def test_constraints_local_install_causes_error(script, data):
|
||||
script.scratch_path.join("constraints.txt").write(
|
||||
"singlemodule==0.0.0"
|
||||
|
@ -484,11 +492,10 @@ def test_install_unsupported_wheel_link_with_marker(script):
|
|||
result = script.pip(
|
||||
'install', '-r', script.scratch_path / 'with-marker.txt',
|
||||
expect_error=False,
|
||||
expect_stderr=True,
|
||||
)
|
||||
|
||||
assert ("Ignoring asdf: markers 'sys_platform == \"xyz\"' don't match "
|
||||
"your environment") in result.stderr
|
||||
"your environment") in result.stdout
|
||||
assert len(result.files_created) == 0
|
||||
|
||||
|
||||
|
|
|
@ -68,24 +68,29 @@ class Tests_UserSite:
|
|||
)
|
||||
result.assert_installed('INITools', use_user_site=True)
|
||||
|
||||
def test_install_curdir_usersite(self, script, virtualenv, data):
|
||||
@pytest.mark.network
|
||||
def test_install_from_current_directory_into_usersite(
|
||||
self, script, virtualenv, data, common_wheels):
|
||||
"""
|
||||
Test installing current directory ('.') into usersite
|
||||
"""
|
||||
virtualenv.system_site_packages = True
|
||||
script.pip("install", "wheel", '--no-index', '-f', common_wheels)
|
||||
|
||||
run_from = data.packages.join("FSPkg")
|
||||
result = script.pip(
|
||||
'install', '-vvv', '--user', curdir,
|
||||
cwd=run_from,
|
||||
expect_error=False,
|
||||
)
|
||||
|
||||
fspkg_folder = script.user_site / 'fspkg'
|
||||
egg_info_folder = (
|
||||
script.user_site / 'FSPkg-0.1.dev0-py%s.egg-info' % pyversion
|
||||
)
|
||||
assert fspkg_folder in result.files_created, result.stdout
|
||||
|
||||
assert egg_info_folder in result.files_created
|
||||
dist_info_folder = (
|
||||
script.user_site / 'FSPkg-0.1.dev0.dist-info'
|
||||
)
|
||||
assert dist_info_folder in result.files_created
|
||||
|
||||
def test_install_user_venv_nositepkgs_fails(self, script, data):
|
||||
"""
|
||||
|
|
|
@ -193,7 +193,7 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(
|
|||
result = script.pip(
|
||||
'wheel', '--no-index', '--find-links=%s' % data.find_links,
|
||||
'--build', script.venv_path / 'build',
|
||||
'simple==3.0', expect_error=True,
|
||||
'simple==3.0', expect_error=True, expect_temp=True,
|
||||
)
|
||||
|
||||
# Then I see that the error code is the right one
|
||||
|
|
|
@ -495,6 +495,58 @@ class TestParseRequirements(object):
|
|||
|
||||
assert finder.index_urls == ['Good']
|
||||
|
||||
def test_expand_existing_env_variables(self, tmpdir, finder):
|
||||
template = (
|
||||
'https://%s:x-oauth-basic@github.com/user/%s/archive/master.zip'
|
||||
)
|
||||
|
||||
env_vars = (
|
||||
('GITHUB_TOKEN', 'notarealtoken'),
|
||||
('DO_12_FACTOR', 'awwyeah'),
|
||||
)
|
||||
|
||||
with open(tmpdir.join('req1.txt'), 'w') as fp:
|
||||
fp.write(template % tuple(['${%s}' % k for k, _ in env_vars]))
|
||||
|
||||
with patch('pip._internal.req.req_file.os.getenv') as getenv:
|
||||
getenv.side_effect = lambda n: dict(env_vars)[n]
|
||||
|
||||
reqs = list(parse_requirements(
|
||||
tmpdir.join('req1.txt'),
|
||||
finder=finder,
|
||||
session=PipSession()
|
||||
))
|
||||
|
||||
assert len(reqs) == 1, \
|
||||
'parsing requirement file with env variable failed'
|
||||
|
||||
expected_url = template % tuple([v for _, v in env_vars])
|
||||
assert reqs[0].link.url == expected_url, \
|
||||
'variable expansion in req file failed'
|
||||
|
||||
def test_expand_missing_env_variables(self, tmpdir, finder):
|
||||
req_url = (
|
||||
'https://${NON_EXISTENT_VARIABLE}:$WRONG_FORMAT@'
|
||||
'%WINDOWS_FORMAT%github.com/user/repo/archive/master.zip'
|
||||
)
|
||||
|
||||
with open(tmpdir.join('req1.txt'), 'w') as fp:
|
||||
fp.write(req_url)
|
||||
|
||||
with patch('pip._internal.req.req_file.os.getenv') as getenv:
|
||||
getenv.return_value = ''
|
||||
|
||||
reqs = list(parse_requirements(
|
||||
tmpdir.join('req1.txt'),
|
||||
finder=finder,
|
||||
session=PipSession()
|
||||
))
|
||||
|
||||
assert len(reqs) == 1, \
|
||||
'parsing requirement file with env variable failed'
|
||||
assert reqs[0].link.url == req_url, \
|
||||
'ignoring invalid env variable in req file failed'
|
||||
|
||||
def test_join_lines(self, tmpdir, finder):
|
||||
with open(tmpdir.join("req1.txt"), "w") as fp:
|
||||
fp.write("--extra-index-url url1 \\\n--extra-index-url url2")
|
||||
|
|
|
@ -23,8 +23,8 @@ from pip._internal.utils.encoding import auto_decode
|
|||
from pip._internal.utils.glibc import check_glibc_version
|
||||
from pip._internal.utils.hashes import Hashes, MissingHashes
|
||||
from pip._internal.utils.misc import (
|
||||
egg_link_path, ensure_dir, get_installed_distributions, get_prog,
|
||||
normalize_path, rmtree, untar_file, unzip_file,
|
||||
call_subprocess, egg_link_path, ensure_dir, get_installed_distributions,
|
||||
get_prog, normalize_path, rmtree, untar_file, unzip_file,
|
||||
)
|
||||
from pip._internal.utils.packaging import check_dist_requires_python
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
@ -612,3 +612,15 @@ class TestGetProg(object):
|
|||
executable
|
||||
)
|
||||
assert get_prog() == expected
|
||||
|
||||
|
||||
def test_call_subprocess_works_okay_when_just_given_nothing():
|
||||
try:
|
||||
call_subprocess([sys.executable, '-c', 'print("Hello")'])
|
||||
except Exception:
|
||||
assert False, "Expected subprocess call to succeed"
|
||||
|
||||
|
||||
def test_call_subprocess_closes_stdin():
|
||||
with pytest.raises(InstallationError):
|
||||
call_subprocess([sys.executable, '-c', 'input()'])
|
||||
|
|
Loading…
Reference in New Issue