diff --git a/news/5311.bugfix b/news/5311.bugfix new file mode 100644 index 00000000000..b9f3d74b138 --- /dev/null +++ b/news/5311.bugfix @@ -0,0 +1 @@ +Fix "pip wheel pip" being blocked by the "don't use pip to modify itself" check diff --git a/news/5312.bugfix b/news/5312.bugfix new file mode 100644 index 00000000000..b9f3d74b138 --- /dev/null +++ b/news/5312.bugfix @@ -0,0 +1 @@ +Fix "pip wheel pip" being blocked by the "don't use pip to modify itself" check diff --git a/src/pip/_internal/basecommand.py b/src/pip/_internal/basecommand.py index 5f8f6422b0a..feeee1f3355 100644 --- a/src/pip/_internal/basecommand.py +++ b/src/pip/_internal/basecommand.py @@ -11,7 +11,6 @@ from pip._internal.baseparser import ( ConfigOptionParser, UpdatingDefaultsHelpFormatter, ) -from pip._internal.compat import WINDOWS from pip._internal.download import PipSession from pip._internal.exceptions import ( BadCommand, CommandError, InstallationError, PreviousBuildDirError, @@ -321,23 +320,6 @@ def populate_requirement_set(requirement_set, args, options, finder, 'You must give at least one requirement to %(name)s ' '(see "pip help %(name)s")' % opts) - # On Windows, any operation modifying pip should be run as: - # python -m pip ... - # See https://github.com/pypa/pip/issues/1299 for more discussion - should_show_use_python_msg = ( - WINDOWS and - requirement_set.has_requirement("pip") and - os.path.basename(sys.argv[0]).startswith("pip") - ) - if should_show_use_python_msg: - new_command = [ - sys.executable, "-m", "pip" - ] + sys.argv[1:] - raise CommandError( - 'To modify pip, please run the following command:\n{}' - .format(" ".join(new_command)) - ) - def _build_package_finder(self, options, session, platform=None, python_versions=None, abi=None, implementation=None): diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index aa0988c43ad..7492d09ee5f 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -20,7 +20,10 @@ from pip._internal.resolve import Resolver from pip._internal.status_codes import ERROR from pip._internal.utils.filesystem import check_path_owner -from pip._internal.utils.misc import ensure_dir, get_installed_version +from pip._internal.utils.misc import ( + ensure_dir, get_installed_version, + protect_pip_from_modification_on_windows, +) from pip._internal.utils.temp_dir import TempDirectory from pip._internal.wheel import WheelBuilder @@ -291,6 +294,10 @@ def run(self, options, args): ) resolver.resolve(requirement_set) + protect_pip_from_modification_on_windows( + modifying_pip=requirement_set.has_requirement("pip") + ) + # If caching is disabled or wheel is not installed don't # try to build wheels. if wheel and options.cache_dir: diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index 7476fa6102e..ba5a2f55267 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -5,6 +5,7 @@ from pip._internal.basecommand import Command from pip._internal.exceptions import InstallationError from pip._internal.req import InstallRequirement, parse_requirements +from pip._internal.utils.misc import protect_pip_from_modification_on_windows class UninstallCommand(Command): @@ -63,6 +64,11 @@ def run(self, options, args): 'You must give at least one requirement to %(name)s (see ' '"pip help %(name)s")' % dict(name=self.name) ) + + protect_pip_from_modification_on_windows( + modifying_pip="pip" in reqs_to_uninstall + ) + for req in reqs_to_uninstall.values(): uninstall_pathset = req.uninstall( auto_confirm=options.yes, verbose=self.verbosity > 0, diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 571b6bf4fda..f69381b8c68 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -26,8 +26,10 @@ from pip._vendor.six.moves import input from pip._vendor.six.moves.urllib import parse as urllib_parse -from pip._internal.compat import console_to_str, expanduser, stdlib_pkgs -from pip._internal.exceptions import InstallationError +from pip._internal.compat import ( + WINDOWS, console_to_str, expanduser, stdlib_pkgs, +) +from pip._internal.exceptions import CommandError, InstallationError from pip._internal.locations import ( running_under_virtualenv, site_packages, user_site, virtualenv_no_global, write_delete_marker_file, @@ -868,3 +870,32 @@ def remove_auth_from_url(url): ) surl = urllib_parse.urlunsplit(url_pieces) return surl + + +def protect_pip_from_modification_on_windows(modifying_pip): + """Protection of pip.exe from modification on Windows + + On Windows, any operation modifying pip should be run as: + python -m pip ... + """ + pip_names = [ + "pip.exe", + "pip{}.exe".format(sys.version_info[0]), + "pip{}.{}.exe".format(*sys.version_info[:2]) + ] + + # See https://github.com/pypa/pip/issues/1299 for more discussion + should_show_use_python_msg = ( + modifying_pip and + WINDOWS and + os.path.basename(sys.argv[0]) in pip_names + ) + + if should_show_use_python_msg: + new_command = [ + sys.executable, "-m", "pip" + ] + sys.argv[1:] + raise CommandError( + 'To modify pip, please run the following command:\n{}' + .format(" ".join(new_command)) + ) diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 7926b939fbc..385db77ae34 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -40,9 +40,8 @@ def test_pep518_uses_build_env(script, data, common_wheels, command, variant): def test_pep518_with_user_pip(script, virtualenv, pip_src, data, common_wheels): virtualenv.system_site_packages = True - script.pip_install_local("--ignore-installed", - "-f", common_wheels, - "--user", pip_src) + script.pip("install", "--ignore-installed", "--user", pip_src, + use_module=True) system_pip_dir = script.site_packages_path / 'pip' system_pip_dir.rmtree() system_pip_dir.mkdir()