Merge branch 'main' into darshanip/main

This commit is contained in:
Tzu-ping Chung 2022-11-10 17:42:21 +08:00
commit f927891b36
24 changed files with 389 additions and 244 deletions

View File

@ -26,8 +26,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- run: pip install nox
- run: nox -s docs
@ -60,8 +62,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Set up git credentials
run: |
git config --global user.email "pypa-dev@googlegroups.com"
@ -82,8 +86,10 @@ jobs:
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- run: pip install nox
- run: nox -s vendoring
@ -103,14 +109,15 @@ jobs:
matrix:
os: [Ubuntu, MacOS]
python:
- 3.7
- 3.8
- 3.9
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@ -120,9 +127,9 @@ jobs:
- name: Install MacOS dependencies
if: matrix.os == 'MacOS'
run: brew install bzr
run: brew install breezy
- run: pip install nox 'virtualenv<20' 'setuptools != 60.6.0'
- run: pip install nox
# Main check
- name: Run unit tests
@ -151,16 +158,17 @@ jobs:
matrix:
os: [Windows]
python:
- 3.7
- "3.7"
# Commented out, since Windows tests are expensively slow.
# - 3.8
# - 3.9
- "3.10"
# - "3.8"
# - "3.9"
# - "3.10"
- "3.11"
group: [1, 2]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@ -179,7 +187,7 @@ jobs:
$acl.AddAccessRule($rule)
Set-Acl "R:\Temp" $acl
- run: pip install nox 'virtualenv<20'
- run: pip install nox
env:
TEMP: "R:\\Temp"
@ -221,8 +229,8 @@ jobs:
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
@ -240,41 +248,6 @@ jobs:
--durations=5
--use-zipapp
# TODO: Remove this when we add Python 3.11 to CI.
tests-importlib-metadata:
name: tests for importlib.metadata backend
runs-on: ubuntu-latest
env:
_PIP_USE_IMPORTLIB_METADATA: 'true'
needs: [packaging, determine-changes]
if: >-
needs.determine-changes.outputs.tests == 'true' ||
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install Ubuntu dependencies
run: sudo apt-get install bzr
- run: pip install nox 'virtualenv<20'
- name: Run unit tests
run: >-
nox -s test-3.10 --
-m unit
--verbose --numprocesses auto --showlocals
- name: Run integration tests
run: >-
nox -s test-3.10 --
-m integration
--verbose --numprocesses auto --showlocals
--durations=5
check: # This job does nothing and is only used for the branch protection
if: always()
@ -285,7 +258,6 @@ jobs:
- tests-unix
- tests-windows
- tests-zipapp
- tests-importlib-metadata
- vendoring
runs-on: ubuntu-latest

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
# `towncrier check` runs `git diff --name-only origin/main...`, which
# needs a non-shallow clone.

View File

@ -9,6 +9,17 @@
.. towncrier release notes start
22.3.1 (2022-11-05)
===================
Bug Fixes
---------
- Fix entry point generation of ``pip.X``, ``pipX.Y``, and ``easy_install-X.Y``
to correctly account for multi-digit Python version segments (e.g. the "11"
part of 3.11). (`#11547 <https://github.com/pypa/pip/issues/11547>`_)
22.3 (2022-10-15)
=================

View File

@ -65,6 +65,13 @@ their merits.
``pip._internal.utils.deprecation.deprecated``. The function is not a part of
pip's public API.
Supported Versions
==================
The latest version of the pip is the only supported version, previous
versions should be considered unsupported. Users are encouraged to make
regular updates to their version of pip in order to remain supported.
.. _`Python 2 Support`:
Python 2 Support

3
news/11547.bugfix.rst Normal file
View File

@ -0,0 +1,3 @@
Fix entry point generation of ``pip.X``, ``pipX.Y``, and ``easy_install-X.Y``
to correctly account for multi-digit Python version segments (e.g. the "11"
part of 3.11).

View File

@ -66,7 +66,7 @@ def should_update_common_wheels() -> bool:
# -----------------------------------------------------------------------------
# Development Commands
# -----------------------------------------------------------------------------
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "pypy3"])
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"])
def test(session: nox.Session) -> None:
# Get the common wheels.
if should_update_common_wheels():

View File

@ -63,7 +63,6 @@ xfail_strict = True
markers =
network: tests that need network
incompatible_with_sysconfig
incompatible_with_test_venv
incompatible_with_venv
no_auto_tempdir_manager
unit: unit tests

View File

@ -325,7 +325,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
# Delete any other versioned pip entry points
pip_ep = [k for k in console if re.match(r"pip(\d(\.\d)?)?$", k)]
pip_ep = [k for k in console if re.match(r"pip(\d+(\.\d+)?)?$", k)]
for k in pip_ep:
del console[k]
easy_install_script = console.pop("easy_install", None)
@ -340,7 +340,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
)
# Delete any other versioned easy_install entry points
easy_install_ep = [
k for k in console if re.match(r"easy_install(-\d\.\d)?$", k)
k for k in console if re.match(r"easy_install(-\d+\.\d+)?$", k)
]
for k in easy_install_ep:
del console[k]

View File

@ -19,7 +19,7 @@ def _running_under_venv() -> bool:
return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
def _running_under_regular_virtualenv() -> bool:
def _running_under_legacy_virtualenv() -> bool:
"""Checks if sys.real_prefix is set.
This handles virtual environments created with pypa's virtualenv.
@ -29,8 +29,8 @@ def _running_under_regular_virtualenv() -> bool:
def running_under_virtualenv() -> bool:
"""Return True if we're running inside a virtualenv, False otherwise."""
return _running_under_venv() or _running_under_regular_virtualenv()
"""True if we're running inside a virtual environment, False otherwise."""
return _running_under_venv() or _running_under_legacy_virtualenv()
def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
@ -77,7 +77,7 @@ def _no_global_under_venv() -> bool:
return False
def _no_global_under_regular_virtualenv() -> bool:
def _no_global_under_legacy_virtualenv() -> bool:
"""Check if "no-global-site-packages.txt" exists beside site.py
This mirrors logic in pypa/virtualenv for determining whether system
@ -98,7 +98,7 @@ def virtualenv_no_global() -> bool:
if _running_under_venv():
return _no_global_under_venv()
if _running_under_regular_virtualenv():
return _no_global_under_regular_virtualenv()
if _running_under_legacy_virtualenv():
return _no_global_under_legacy_virtualenv()
return False

View File

@ -108,10 +108,6 @@ def pytest_collection_modifyitems(config: Config, items: List[pytest.Function])
if item.get_closest_marker("network") is not None:
item.add_marker(pytest.mark.flaky(reruns=3, reruns_delay=2))
if item.get_closest_marker("incompatible_with_test_venv") and config.getoption(
"--use-venv"
):
item.add_marker(pytest.mark.skip("Incompatible with test venv"))
if (
item.get_closest_marker("incompatible_with_venv")
and sys.prefix != sys.base_prefix
@ -474,9 +470,6 @@ def virtualenv_template(
):
(venv.bin / exe).unlink()
# Enable user site packages.
venv.user_site_packages = True
# Rename original virtualenv directory to make sure
# it's not reused by mistake from one of the copies.
venv_template = tmpdir / "venv_template"
@ -742,3 +735,8 @@ def mock_server() -> Iterator[MockServer]:
@pytest.fixture
def proxy(request: pytest.FixtureRequest) -> str:
return request.config.getoption("proxy")
@pytest.fixture
def enable_user_site(virtualenv: VirtualEnvironment) -> None:
virtualenv.user_site_packages = True

View File

@ -204,7 +204,7 @@ def test_build_env_overlay_prefix_has_priority(script: PipTestEnvironment) -> No
assert result.stdout.strip() == "2.0", str(result)
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_build_env_isolation(script: PipTestEnvironment) -> None:
# Create dummy `pkg` wheel.

View File

@ -862,7 +862,7 @@ def test_freeze_with_requirement_option_package_repeated_multi_file(
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_freeze_user(
script: PipTestEnvironment, virtualenv: VirtualEnvironment, data: TestData
) -> None:
@ -900,7 +900,7 @@ def test_freeze_path(tmpdir: Path, script: PipTestEnvironment, data: TestData) -
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_freeze_path_exclude_user(
tmpdir: Path, script: PipTestEnvironment, data: TestData
) -> None:

View File

@ -1,8 +1,8 @@
import distutils
import os
import re
import ssl
import sys
import sysconfig
import textwrap
from os.path import curdir, join, pardir
from pathlib import Path
@ -171,7 +171,7 @@ def test_pep518_allows_missing_requires(
assert result.files_created
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_pep518_with_user_pip(
script: PipTestEnvironment, pip_src: Path, data: TestData, common_wheels: Path
) -> None:
@ -1145,6 +1145,39 @@ def test_install_with_target_or_prefix_and_scripts_no_warning(
assert "--no-warn-script-location" not in result.stderr, str(result)
def _change_root(new_root: str, pathname: str) -> str:
"""
Adapted from distutils.
Return 'pathname' with 'new_root' prepended. If 'pathname' is
relative, this is equivalent to "os.path.join(new_root,pathname)".
Otherwise, it requires making 'pathname' relative and then joining the
two, which is tricky on DOS/Windows and Mac OS.
"""
try:
from distutils.util import change_root
except ImportError:
pass
else:
return change_root(new_root, pathname)
if os.name == "posix":
if not os.path.isabs(pathname):
return os.path.join(new_root, pathname)
else:
return os.path.join(new_root, pathname[1:])
elif os.name == "nt":
drive, path = os.path.splitdrive(pathname)
if path[0] == "\\":
path = path[1:]
return os.path.join(new_root, path)
else:
# distutils raise DistutilsPlatformError here
raise RuntimeError(f"nothing known about platform '{os.name}'")
@pytest.mark.usefixtures("with_wheel")
def test_install_package_with_root(script: PipTestEnvironment, data: TestData) -> None:
"""
@ -1163,10 +1196,8 @@ def test_install_package_with_root(script: PipTestEnvironment, data: TestData) -
normal_install_path = os.fspath(
script.base_path / script.site_packages / "simple-1.0.dist-info"
)
# use distutils to change the root exactly how the --root option does it
from distutils.util import change_root
root_path = change_root(os.path.join(script.scratch, "root"), normal_install_path)
root_path = _change_root(os.path.join(script.scratch, "root"), normal_install_path)
result.did_create(root_path)
# Should show find-links location in output
@ -1195,7 +1226,7 @@ def test_install_package_with_prefix(
rel_prefix_path = script.scratch / "prefix"
install_path = join(
distutils.sysconfig.get_python_lib(prefix=rel_prefix_path),
sysconfig.get_path("purelib", vars={"base": rel_prefix_path}),
# we still test for egg-info because no-binary implies setup.py install
f"simple-1.0-py{pyversion}.egg-info",
)
@ -1217,7 +1248,7 @@ def _test_install_editable_with_prefix(
"prefix", "lib", f"python{pyversion}", "site-packages"
)
else:
site_packages = distutils.sysconfig.get_python_lib(prefix="prefix")
site_packages = sysconfig.get_path("purelib", vars={"base": "prefix"})
# make sure target path is in PYTHONPATH
pythonpath = script.scratch_path / site_packages
@ -2075,7 +2106,7 @@ def test_target_install_ignores_distutils_config_install_prefix(
result.did_not_create(relative_script_base)
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_user_config_accepted(script: PipTestEnvironment) -> None:
# user set in the config file is parsed as 0/1 instead of True/False.
# Check that this doesn't cause a problem.

View File

@ -305,8 +305,7 @@ def test_install_local_with_subdirectory(script: PipTestEnvironment) -> None:
result.assert_installed("version_subpkg.py", editable=False)
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("with_wheel")
@pytest.mark.usefixtures("enable_user_site", "with_wheel")
def test_wheel_user_with_prefix_in_pydistutils_cfg(
script: PipTestEnvironment, data: TestData
) -> None:

View File

@ -1,6 +1,7 @@
"""
tests specific to "pip install --user"
"""
import os
import textwrap
from os.path import curdir, isdir, isfile
from pathlib import Path
@ -8,7 +9,12 @@ from pathlib import Path
import pytest
from tests.lib import pyversion # noqa: F401
from tests.lib import PipTestEnvironment, TestData, need_svn
from tests.lib import (
PipTestEnvironment,
TestData,
create_basic_wheel_for_package,
need_svn,
)
from tests.lib.local_repos import local_checkout
from tests.lib.venv import VirtualEnvironment
@ -29,9 +35,9 @@ def _patch_dist_in_site_packages(virtualenv: VirtualEnvironment) -> None:
)
@pytest.mark.usefixtures("enable_user_site")
class Tests_UserSite:
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_reset_env_system_site_packages_usersite(
self, script: PipTestEnvironment
) -> None:
@ -51,7 +57,6 @@ class Tests_UserSite:
@pytest.mark.xfail
@pytest.mark.network
@need_svn
@pytest.mark.incompatible_with_test_venv
def test_install_subversion_usersite_editable_with_distribute(
self, script: PipTestEnvironment, tmpdir: Path
) -> None:
@ -71,7 +76,6 @@ class Tests_UserSite:
)
result.assert_installed("INITools", use_user_site=True)
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("with_wheel")
def test_install_from_current_directory_into_usersite(
self, script: PipTestEnvironment, data: TestData
@ -117,7 +121,6 @@ class Tests_UserSite:
)
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_install_user_conflict_in_usersite(
self, script: PipTestEnvironment
) -> None:
@ -142,8 +145,6 @@ class Tests_UserSite:
result2.did_create(egg_info_folder)
assert not isfile(initools_v3_file), initools_v3_file
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_install_user_conflict_in_globalsite(
self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
) -> None:
@ -151,31 +152,41 @@ class Tests_UserSite:
Test user install with conflict in global site ignores site and
installs to usersite
"""
create_basic_wheel_for_package(script, "initools", "0.1")
create_basic_wheel_for_package(script, "initools", "0.2")
_patch_dist_in_site_packages(virtualenv)
script.pip("install", "INITools==0.2", "--no-binary=:all:")
result2 = script.pip("install", "--user", "INITools==0.1", "--no-binary=:all:")
script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"initools==0.2",
)
result2 = script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"initools==0.1",
)
# usersite has 0.1
# we still test for egg-info because no-binary implies setup.py install
egg_info_folder = script.user_site / f"INITools-0.1-py{pyversion}.egg-info"
dist_info_folder = script.user_site / "initools-0.1.dist-info"
initools_folder = script.user_site / "initools"
result2.did_create(egg_info_folder)
result2.did_create(dist_info_folder)
result2.did_create(initools_folder)
# site still has 0.2 (can't look in result1; have to check)
egg_info_folder = (
script.base_path
/ script.site_packages
/ f"INITools-0.2-py{pyversion}.egg-info"
dist_info_folder = (
script.base_path / script.site_packages / "initools-0.2.dist-info"
)
initools_folder = script.base_path / script.site_packages / "initools"
assert isdir(egg_info_folder)
assert isdir(dist_info_folder)
assert isdir(initools_folder)
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_upgrade_user_conflict_in_globalsite(
self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
) -> None:
@ -183,32 +194,42 @@ class Tests_UserSite:
Test user install/upgrade with conflict in global site ignores site and
installs to usersite
"""
create_basic_wheel_for_package(script, "initools", "0.2")
create_basic_wheel_for_package(script, "initools", "0.3.1")
_patch_dist_in_site_packages(virtualenv)
script.pip("install", "INITools==0.2", "--no-binary=:all:")
script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"initools==0.2",
)
result2 = script.pip(
"install", "--user", "--upgrade", "INITools", "--no-binary=:all:"
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"--upgrade",
"initools",
)
# usersite has 0.3.1
# we still test for egg-info because no-binary implies setup.py install
egg_info_folder = script.user_site / f"INITools-0.3.1-py{pyversion}.egg-info"
dist_info_folder = script.user_site / "initools-0.3.1.dist-info"
initools_folder = script.user_site / "initools"
result2.did_create(egg_info_folder)
result2.did_create(dist_info_folder)
result2.did_create(initools_folder)
# site still has 0.2 (can't look in result1; have to check)
egg_info_folder = (
script.base_path
/ script.site_packages
/ f"INITools-0.2-py{pyversion}.egg-info"
dist_info_folder = (
script.base_path / script.site_packages / "initools-0.2.dist-info"
)
initools_folder = script.base_path / script.site_packages / "initools"
assert isdir(egg_info_folder), result2.stdout
assert isdir(dist_info_folder), result2.stdout
assert isdir(initools_folder)
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_install_user_conflict_in_globalsite_and_usersite(
self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
) -> None:
@ -216,38 +237,56 @@ class Tests_UserSite:
Test user install with conflict in globalsite and usersite ignores
global site and updates usersite.
"""
initools_v3_file_name = os.path.join("initools", "configparser.py")
create_basic_wheel_for_package(script, "initools", "0.1")
create_basic_wheel_for_package(script, "initools", "0.2")
create_basic_wheel_for_package(
script,
"initools",
"0.3",
extra_files={initools_v3_file_name: "# Hi!"},
)
_patch_dist_in_site_packages(virtualenv)
script.pip("install", "INITools==0.2", "--no-binary=:all:")
script.pip("install", "--user", "INITools==0.3", "--no-binary=:all:")
result3 = script.pip("install", "--user", "INITools==0.1", "--no-binary=:all:")
script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"initools==0.2",
)
script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"initools==0.3",
)
result3 = script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"initools==0.1",
)
# usersite has 0.1
# we still test for egg-info because no-binary implies setup.py install
egg_info_folder = script.user_site / f"INITools-0.1-py{pyversion}.egg-info"
initools_v3_file = (
# file only in 0.3
script.base_path
/ script.user_site
/ "initools"
/ "configparser.py"
)
result3.did_create(egg_info_folder)
dist_info_folder = script.user_site / "initools-0.1.dist-info"
result3.did_create(dist_info_folder)
initools_v3_file = script.base_path / script.user_site / initools_v3_file_name
assert not isfile(initools_v3_file), initools_v3_file
# site still has 0.2 (can't just look in result1; have to check)
egg_info_folder = (
script.base_path
/ script.site_packages
/ f"INITools-0.2-py{pyversion}.egg-info"
dist_info_folder = (
script.base_path / script.site_packages / "initools-0.2.dist-info"
)
initools_folder = script.base_path / script.site_packages / "initools"
assert isdir(egg_info_folder)
assert isdir(dist_info_folder)
assert isdir(initools_folder)
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
def test_install_user_in_global_virtualenv_with_conflict_fails(
self, script: PipTestEnvironment
) -> None:
@ -255,27 +294,37 @@ class Tests_UserSite:
Test user install in --system-site-packages virtualenv with conflict in
site fails.
"""
create_basic_wheel_for_package(script, "pkg", "0.1")
create_basic_wheel_for_package(script, "pkg", "0.2")
script.pip("install", "INITools==0.2")
script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"pkg==0.2",
)
result2 = script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"INITools==0.1",
"pkg==0.1",
expect_error=True,
)
resultp = script.run(
"python",
"-c",
"import pkg_resources; print(pkg_resources.get_distribution"
"('initools').location)",
"from pip._internal.metadata import get_default_environment; "
"print(get_default_environment().get_distribution('pkg').location)",
)
dist_location = resultp.stdout.strip()
assert (
"Will not install to the user site because it will lack sys.path "
"precedence to {name} in {location}".format(
name="INITools",
location=dist_location,
)
in result2.stderr
)
f"Will not install to the user site because it will lack sys.path "
f"precedence to pkg in {dist_location}"
) in result2.stderr

View File

@ -544,6 +544,11 @@ def test_reinstalling_works_with_editable_non_master_branch(
# TODO(pnasrat) fix all helpers to do right things with paths on windows.
@pytest.mark.skipif("sys.platform == 'win32'")
@pytest.mark.xfail(
condition=True,
reason="Git submodule against file: is not working; waiting for a good solution",
run=True,
)
def test_check_submodule_addition(script: PipTestEnvironment) -> None:
"""
Submodules are pulled in on install and updated on upgrade.

View File

@ -1,9 +1,9 @@
import base64
import csv
import distutils
import hashlib
import os
import shutil
import sysconfig
from pathlib import Path
from typing import Any
@ -284,7 +284,9 @@ def test_install_wheel_with_prefix(
"--find-links",
tmpdir,
)
lib = distutils.sysconfig.get_python_lib(prefix=os.path.join("scratch", "prefix"))
lib = sysconfig.get_path(
"purelib", vars={"base": os.path.join("scratch", "prefix")}
)
result.did_create(lib)
@ -404,8 +406,7 @@ def test_wheel_record_lines_have_updated_hash_for_scripts(
]
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("with_wheel")
@pytest.mark.usefixtures("enable_user_site", "with_wheel")
def test_install_user_wheel(
script: PipTestEnvironment, shared_data: TestData, tmpdir: Path
) -> None:

View File

@ -129,7 +129,7 @@ def test_multiple_exclude_and_normalization(
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_user_flag(script: PipTestEnvironment, data: TestData) -> None:
"""
Test the behavior of --user flag in the list command
@ -144,7 +144,7 @@ def test_user_flag(script: PipTestEnvironment, data: TestData) -> None:
@pytest.mark.network
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_user_columns_flag(script: PipTestEnvironment, data: TestData) -> None:
"""
Test the behavior of --user --format=columns flags in the list command
@ -656,7 +656,7 @@ def test_list_path(tmpdir: Path, script: PipTestEnvironment, data: TestData) ->
assert {"name": "simple", "version": "2.0"} in json_result
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_list_path_exclude_user(
tmpdir: Path, script: PipTestEnvironment, data: TestData
) -> None:

View File

@ -7,7 +7,7 @@ from tests.lib import PipTestEnvironment, create_basic_wheel_for_package
from tests.lib.venv import VirtualEnvironment
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_new_resolver_install_user(script: PipTestEnvironment) -> None:
create_basic_wheel_for_package(script, "base", "0.1.0")
result = script.pip(
@ -22,7 +22,7 @@ def test_new_resolver_install_user(script: PipTestEnvironment) -> None:
result.did_create(script.user_site / "base")
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_new_resolver_install_user_satisfied_by_global_site(
script: PipTestEnvironment,
) -> None:
@ -53,7 +53,7 @@ def test_new_resolver_install_user_satisfied_by_global_site(
result.did_not_create(script.user_site / "base")
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
def test_new_resolver_install_user_conflict_in_user_site(
script: PipTestEnvironment,
) -> None:
@ -91,39 +91,6 @@ def test_new_resolver_install_user_conflict_in_user_site(
result.did_not_create(base_2_dist_info)
@pytest.mark.incompatible_with_test_venv
def test_new_resolver_install_user_in_virtualenv_with_conflict_fails(
script: PipTestEnvironment,
) -> None:
create_basic_wheel_for_package(script, "base", "1.0.0")
create_basic_wheel_for_package(script, "base", "2.0.0")
script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"base==2.0.0",
)
result = script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"--user",
"base==1.0.0",
expect_error=True,
)
error_message = (
"Will not install to the user site because it will lack sys.path "
"precedence to base in {}"
).format(os.path.normcase(script.site_packages_path))
assert error_message in result.stderr
@pytest.fixture()
def patch_dist_in_site_packages(virtualenv: VirtualEnvironment) -> None:
# Since the tests are run from a virtualenv, and to avoid the "Will not
@ -141,8 +108,7 @@ def patch_dist_in_site_packages(virtualenv: VirtualEnvironment) -> None:
)
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("patch_dist_in_site_packages")
@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
def test_new_resolver_install_user_reinstall_global_site(
script: PipTestEnvironment,
) -> None:
@ -177,8 +143,7 @@ def test_new_resolver_install_user_reinstall_global_site(
assert "base" in site_packages_content
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("patch_dist_in_site_packages")
@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
def test_new_resolver_install_user_conflict_in_global_site(
script: PipTestEnvironment,
) -> None:
@ -215,8 +180,7 @@ def test_new_resolver_install_user_conflict_in_global_site(
assert "base-1.0.0.dist-info" in site_packages_content
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("patch_dist_in_site_packages")
@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
def test_new_resolver_install_user_conflict_in_global_and_user_sites(
script: PipTestEnvironment,
) -> None:

View File

@ -6,12 +6,12 @@ from os.path import isdir, isfile, normcase
import pytest
from tests.functional.test_install_user import _patch_dist_in_site_packages
from tests.lib import pyversion # noqa: F401
from tests.lib import PipTestEnvironment, TestData, assert_all_changes
from tests.lib.venv import VirtualEnvironment
from tests.lib.wheel import make_wheel
@pytest.mark.incompatible_with_test_venv
@pytest.mark.usefixtures("enable_user_site")
class Tests_UninstallUserSite:
@pytest.mark.network
def test_uninstall_from_usersite(self, script: PipTestEnvironment) -> None:
@ -28,14 +28,39 @@ class Tests_UninstallUserSite:
"""
Test uninstall from usersite (with same dist in global site)
"""
entry_points_txt = "[console_scripts]\nscript = pkg:func"
make_wheel(
"pkg",
"0.1",
extra_metadata_files={"entry_points.txt": entry_points_txt},
).save_to_dir(script.scratch_path)
make_wheel(
"pkg",
"0.1.1",
extra_metadata_files={"entry_points.txt": entry_points_txt},
).save_to_dir(script.scratch_path)
_patch_dist_in_site_packages(virtualenv)
script.pip_install_local("pip-test-package==0.1", "--no-binary=:all:")
result2 = script.pip_install_local(
"--user", "pip-test-package==0.1.1", "--no-binary=:all:"
script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--no-warn-script-location",
"pkg==0.1",
)
result3 = script.pip("uninstall", "-vy", "pip-test-package")
result2 = script.pip(
"install",
"--no-index",
"--find-links",
script.scratch_path,
"--no-warn-script-location",
"--user",
"pkg==0.1.1",
)
result3 = script.pip("uninstall", "-vy", "pkg")
# uninstall console is mentioning user scripts, but not global scripts
assert normcase(script.user_bin_path) in result3.stdout, str(result3)
@ -45,13 +70,8 @@ class Tests_UninstallUserSite:
assert_all_changes(result2, result3, [script.venv / "build", "cache"])
# site still has 0.2 (can't look in result1; have to check)
# keep checking for egg-info because no-binary implies setup.py install
egg_info_folder = (
script.base_path
/ script.site_packages
/ f"pip_test_package-0.1-py{pyversion}.egg-info"
)
assert isdir(egg_info_folder)
dist_info_folder = script.base_path / script.site_packages / "pkg-0.1.dist-info"
assert isdir(dist_info_folder)
def test_uninstall_editable_from_usersite(
self, script: PipTestEnvironment, data: TestData

View File

@ -3,10 +3,11 @@ import os
import shutil
import subprocess
import sys
import sysconfig
import textwrap
import venv as _venv
from pathlib import Path
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Dict, Optional, Union
import virtualenv as _virtualenv
@ -30,7 +31,7 @@ class VirtualEnvironment:
location: Path,
template: Optional["VirtualEnvironment"] = None,
venv_type: Optional[VirtualEnvironmentType] = None,
):
) -> None:
self.location = location
assert template is None or venv_type is None
self._venv_type: VirtualEnvironmentType
@ -46,7 +47,13 @@ class VirtualEnvironment:
self._update_paths()
self._create()
def _update_paths(self) -> None:
@property
def _legacy_virtualenv(self) -> bool:
if self._venv_type != "virtualenv":
return False
return int(_virtualenv.__version__.split(".", 1)[0]) < 20
def __update_paths_legacy(self) -> None:
home, lib, inc, bin = _virtualenv.path_locations(self.location)
self.bin = Path(bin)
self.site = Path(lib) / "site-packages"
@ -57,6 +64,21 @@ class VirtualEnvironment:
else:
self.lib = Path(lib)
def _update_paths(self) -> None:
if self._legacy_virtualenv:
self.__update_paths_legacy()
return
bases = {
"installed_base": self.location,
"installed_platbase": self.location,
"base": self.location,
"platbase": self.location,
}
paths = sysconfig.get_paths(vars=bases)
self.bin = Path(paths["scripts"])
self.site = Path(paths["purelib"])
self.lib = Path(paths["stdlib"])
def __repr__(self) -> str:
return f"<VirtualEnvironment {self.location}>"
@ -66,7 +88,11 @@ class VirtualEnvironment:
if self._template:
# On Windows, calling `_virtualenv.path_locations(target)`
# will have created the `target` directory...
if sys.platform == "win32" and self.location.exists():
if (
self._legacy_virtualenv
and sys.platform == "win32"
and self.location.exists()
):
self.location.rmdir()
# Clone virtual environment from template.
shutil.copytree(self._template.location, self.location, symlinks=True)
@ -74,7 +100,7 @@ class VirtualEnvironment:
self._user_site_packages = self._template.user_site_packages
else:
# Create a new virtual environment.
if self._venv_type == "virtualenv":
if self._legacy_virtualenv:
subprocess.check_call(
[
sys.executable,
@ -83,20 +109,31 @@ class VirtualEnvironment:
"--no-pip",
"--no-wheel",
"--no-setuptools",
str(self.location),
os.fspath(self.location),
]
)
self._fix_virtualenv_site_module()
self._fix_legacy_virtualenv_site_module()
elif self._venv_type == "virtualenv":
_virtualenv.cli_run(
[
"--no-pip",
"--no-wheel",
"--no-setuptools",
os.fspath(self.location),
],
)
elif self._venv_type == "venv":
builder = _venv.EnvBuilder()
context = builder.ensure_directories(self.location)
builder.create_configuration(context)
builder.setup_python(context)
self.site.mkdir(parents=True, exist_ok=True)
else:
raise RuntimeError(f"Unsupported venv type {self._venv_type!r}")
self.sitecustomize = self._sitecustomize
self.user_site_packages = self._user_site_packages
def _fix_virtualenv_site_module(self) -> None:
def _fix_legacy_virtualenv_site_module(self) -> None:
# Patch `site.py` so user site work as expected.
site_py = self.lib / "site.py"
with open(site_py) as fp:
@ -131,17 +168,15 @@ class VirtualEnvironment:
assert compileall.compile_file(str(site_py), quiet=1, force=True)
def _customize_site(self) -> None:
contents = ""
if self._venv_type == "venv":
if self._legacy_virtualenv:
contents = ""
else:
# Enable user site (before system).
contents += textwrap.dedent(
"""
contents = textwrap.dedent(
f"""
import os, site, sys
if not os.environ.get('PYTHONNOUSERSITE', False):
site.ENABLE_USER_SITE = True
site.ENABLE_USER_SITE = {self._user_site_packages}
# First, drop system-sites related paths.
original_sys_path = sys.path[:]
known_paths = set()
@ -152,10 +187,9 @@ class VirtualEnvironment:
if path in original_sys_path:
original_sys_path.remove(path)
sys.path = original_sys_path
# Second, add user-site.
site.addsitedir(site.getusersitepackages())
if {self._user_site_packages}:
site.addsitedir(site.getusersitepackages())
# Third, add back system-sites related paths.
for path in site.getsitepackages():
site.addsitedir(path)
@ -168,6 +202,21 @@ class VirtualEnvironment:
# Make sure bytecode is up-to-date too.
assert compileall.compile_file(str(sitecustomize), quiet=1, force=True)
def _rewrite_pyvenv_cfg(self, replacements: Dict[str, str]) -> None:
pyvenv_cfg = self.location.joinpath("pyvenv.cfg")
lines = pyvenv_cfg.read_text(encoding="utf-8").splitlines()
def maybe_replace_line(line: str) -> str:
key = line.split("=", 1)[0].strip()
try:
value = replacements[key]
except KeyError: # No need to replace.
return line
return f"{key} = {value}"
lines = [maybe_replace_line(line) for line in lines]
pyvenv_cfg.write_text("\n".join(lines), encoding="utf-8")
def clear(self) -> None:
self._create(clear=True)
@ -192,11 +241,14 @@ class VirtualEnvironment:
@user_site_packages.setter
def user_site_packages(self, value: bool) -> None:
self._user_site_packages = value
if self._venv_type == "virtualenv":
if self._legacy_virtualenv:
marker = self.lib / "no-global-site-packages.txt"
if self._user_site_packages:
marker.unlink()
else:
marker.touch()
elif self._venv_type == "venv":
else:
self._rewrite_pyvenv_cfg(
{"include-system-site-packages": str(bool(value)).lower()}
)
self._customize_site()

View File

@ -7,7 +7,8 @@ pytest-rerunfailures
pytest-xdist
scripttest
setuptools
virtualenv < 20.0
virtualenv < 20.0 ; python_version < '3.10'
virtualenv >= 20.0 ; python_version >= '3.10'
werkzeug
wheel
tomli-w

View File

@ -63,7 +63,7 @@ def test_virtualenv_no_global_with_regular_virtualenv(
monkeypatch.setattr(site, "__file__", os.fspath(tmpdir / "site.py"))
monkeypatch.setattr(
virtualenv,
"_running_under_regular_virtualenv",
"_running_under_legacy_virtualenv",
lambda: under_virtualenv,
)
if no_global_file:
@ -73,7 +73,7 @@ def test_virtualenv_no_global_with_regular_virtualenv(
@pytest.mark.parametrize(
"pyvenv_cfg_lines, under_venv, expected, expect_warning",
"pyvenv_cfg_lines, under_venv, expect_no_global, expect_warning",
[
(None, False, False, False),
(None, True, True, True), # this has a warning.
@ -104,15 +104,15 @@ def test_virtualenv_no_global_with_pep_405_virtual_environment(
caplog: pytest.LogCaptureFixture,
pyvenv_cfg_lines: Optional[List[str]],
under_venv: bool,
expected: bool,
expect_no_global: bool,
expect_warning: bool,
) -> None:
monkeypatch.setattr(virtualenv, "_running_under_regular_virtualenv", lambda: False)
monkeypatch.setattr(virtualenv, "_running_under_legacy_virtualenv", lambda: False)
monkeypatch.setattr(virtualenv, "_get_pyvenv_cfg_lines", lambda: pyvenv_cfg_lines)
monkeypatch.setattr(virtualenv, "_running_under_venv", lambda: under_venv)
with caplog.at_level(logging.WARNING):
assert virtualenv.virtualenv_no_global() == expected
assert virtualenv.virtualenv_no_global() == expect_no_global
if expect_warning:
assert caplog.records

View File

@ -3,6 +3,7 @@ import csv
import logging
import os
import pathlib
import sys
import textwrap
from email import message_from_string
from pathlib import Path
@ -22,7 +23,11 @@ from pip._internal.models.direct_url import (
from pip._internal.models.scheme import Scheme
from pip._internal.operations.build.wheel_legacy import get_legacy_build_wheel_path
from pip._internal.operations.install import wheel
from pip._internal.operations.install.wheel import InstalledCSVRow, RecordPath
from pip._internal.operations.install.wheel import (
InstalledCSVRow,
RecordPath,
get_console_script_specs,
)
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.misc import hash_file
from pip._internal.utils.unpacking import unpack_file
@ -681,3 +686,31 @@ class TestWheelHashCalculators:
h, length = wheel.rehash(os.fspath(self.test_file))
assert length == str(self.test_file_len)
assert h == self.test_file_hash_encoded
def test_get_console_script_specs_replaces_python_version(
monkeypatch: pytest.MonkeyPatch,
) -> None:
# Fake Python version.
monkeypatch.setattr(sys, "version_info", (10, 11))
entry_points = {
"pip": "real_pip",
"pip99": "whatever",
"pip99.88": "whatever",
"easy_install": "real_easy_install",
"easy_install-99.88": "whatever",
# The following shouldn't be replaced.
"not_pip_or_easy_install-99": "whatever",
"not_pip_or_easy_install-99.88": "whatever",
}
specs = get_console_script_specs(entry_points)
assert specs == [
"pip = real_pip",
"pip10 = real_pip",
"pip10.11 = real_pip",
"easy_install = real_easy_install",
"easy_install-10.11 = real_easy_install",
"not_pip_or_easy_install-99 = whatever",
"not_pip_or_easy_install-99.88 = whatever",
]