gh-95299: Stop installing setuptools as a part of ensurepip and venv (#101039)

Remove the bundled setuptools wheel from ensurepip, and stop installing setuptools in environments created by venv.

Co-Authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM>
Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net>
This commit is contained in:
Pradyun Gedam 2023-04-17 23:43:34 -05:00 committed by GitHub
parent f39e00f952
commit ece20dba12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 57 additions and 48 deletions

View File

@ -1,4 +1,4 @@
name: Verify bundled pip and setuptools name: Verify bundled wheels
on: on:
workflow_dispatch: workflow_dispatch:
@ -29,5 +29,5 @@ jobs:
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: '3' python-version: '3'
- name: Compare checksums of bundled pip and setuptools to ones published on PyPI - name: Compare checksum of bundled wheels to the ones published on PyPI
run: ./Tools/build/verify_ensurepip_wheels.py run: ./Tools/build/verify_ensurepip_wheels.py

View File

@ -284,11 +284,14 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. method:: upgrade_dependencies(context) .. method:: upgrade_dependencies(context)
Upgrades the core venv dependency packages (currently ``pip`` and Upgrades the core venv dependency packages (currently ``pip``)
``setuptools``) in the environment. This is done by shelling out to the in the environment. This is done by shelling out to the
``pip`` executable in the environment. ``pip`` executable in the environment.
.. versionadded:: 3.9 .. versionadded:: 3.9
.. versionchanged:: 3.12
``setuptools`` is no longer a core venv dependency.
.. method:: post_setup(context) .. method:: post_setup(context)

View File

@ -61,12 +61,16 @@ The command, if run with ``-h``, will show the available options::
environment (pip is bootstrapped by default) environment (pip is bootstrapped by default)
--prompt PROMPT Provides an alternative prompt prefix for this --prompt PROMPT Provides an alternative prompt prefix for this
environment. environment.
--upgrade-deps Upgrade core dependencies: pip setuptools to the --upgrade-deps Upgrade core dependencies (pip) to the
latest version in PyPI latest version in PyPI
Once an environment has been created, you may wish to activate it, e.g. by Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory. sourcing an activate script in its bin directory.
.. versionchanged:: 3.12
``setuptools`` is no longer a core venv dependency.
.. versionchanged:: 3.9 .. versionchanged:: 3.9
Add ``--upgrade-deps`` option to upgrade pip + setuptools to the latest on PyPI Add ``--upgrade-deps`` option to upgrade pip + setuptools to the latest on PyPI
@ -104,4 +108,3 @@ invoked to bootstrap ``pip`` into the virtual environment.
Multiple paths can be given to ``venv``, in which case an identical virtual Multiple paths can be given to ``venv``, in which case an identical virtual
environment will be created, according to the given options, at each provided environment will be created, according to the given options, at each provided
path. path.

View File

@ -731,6 +731,24 @@ Removed
project can be installed: it still provides ``distutils``. project can be installed: it still provides ``distutils``.
(Contributed by Victor Stinner in :gh:`92584`.) (Contributed by Victor Stinner in :gh:`92584`.)
* Remove the bundled setuptools wheel from :mod:`ensurepip`,
and stop installing setuptools in environments created by :mod:`venv`.
``pip (>= 22.1)`` does not require setuptools to be installed in the
environment. ``setuptools``-based (and ``distutils``-based) packages
can still be used with ``pip install``, since pip will provide
``setuptools`` in the build environment it uses for building a
package.
``easy_install``, ``pkg_resources``, ``setuptools`` and ``distutils``
are no longer provided by default in environments created with
``venv`` or bootstrapped with ``ensurepip``, since they are part of
the ``setuptools`` package. For projects relying on these at runtime,
the ``setuptools`` project should be declared as a dependency and
installed separately (typically, using pip).
(Contributed by Pradyun Gedam in :gh:`95299`.)
* Removed many old deprecated :mod:`unittest` features: * Removed many old deprecated :mod:`unittest` features:
- A number of :class:`~unittest.TestCase` method aliases: - A number of :class:`~unittest.TestCase` method aliases:

View File

@ -9,11 +9,9 @@ from importlib import resources
__all__ = ["version", "bootstrap"] __all__ = ["version", "bootstrap"]
_PACKAGE_NAMES = ('setuptools', 'pip') _PACKAGE_NAMES = ('pip',)
_SETUPTOOLS_VERSION = "65.5.0"
_PIP_VERSION = "23.0.1" _PIP_VERSION = "23.0.1"
_PROJECTS = [ _PROJECTS = [
("setuptools", _SETUPTOOLS_VERSION, "py3"),
("pip", _PIP_VERSION, "py3"), ("pip", _PIP_VERSION, "py3"),
] ]
@ -153,17 +151,17 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
_disable_pip_configuration_settings() _disable_pip_configuration_settings()
# By default, installing pip and setuptools installs all of the # By default, installing pip installs all of the
# following scripts (X.Y == running Python version): # following scripts (X.Y == running Python version):
# #
# pip, pipX, pipX.Y, easy_install, easy_install-X.Y # pip, pipX, pipX.Y
# #
# pip 1.5+ allows ensurepip to request that some of those be left out # pip 1.5+ allows ensurepip to request that some of those be left out
if altinstall: if altinstall:
# omit pip, pipX and easy_install # omit pip, pipX
os.environ["ENSUREPIP_OPTIONS"] = "altinstall" os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
elif not default_pip: elif not default_pip:
# omit pip and easy_install # omit pip
os.environ["ENSUREPIP_OPTIONS"] = "install" os.environ["ENSUREPIP_OPTIONS"] = "install"
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
@ -271,14 +269,14 @@ def _main(argv=None):
action="store_true", action="store_true",
default=False, default=False,
help=("Make an alternate install, installing only the X.Y versioned " help=("Make an alternate install, installing only the X.Y versioned "
"scripts (Default: pipX, pipX.Y, easy_install-X.Y)."), "scripts (Default: pipX, pipX.Y)."),
) )
parser.add_argument( parser.add_argument(
"--default-pip", "--default-pip",
action="store_true", action="store_true",
default=False, default=False,
help=("Make a default pip install, installing the unqualified pip " help=("Make a default pip install, installing the unqualified pip "
"and easy_install in addition to the versioned scripts."), "in addition to the versioned scripts."),
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)

View File

@ -20,7 +20,6 @@ class TestPackages(unittest.TestCase):
# Test version() # Test version()
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl") self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl")
self.touch(tmpdir, "setuptools-49.1.3-py3-none-any.whl")
with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
self.assertEqual(ensurepip.version(), '1.2.3b1') self.assertEqual(ensurepip.version(), '1.2.3b1')
@ -36,15 +35,12 @@ class TestPackages(unittest.TestCase):
# use bundled wheel packages # use bundled wheel packages
self.assertIsNotNone(packages['pip'].wheel_name) self.assertIsNotNone(packages['pip'].wheel_name)
self.assertIsNotNone(packages['setuptools'].wheel_name)
def test_get_packages_with_dir(self): def test_get_packages_with_dir(self):
# Test _get_packages() with a wheel package directory # Test _get_packages() with a wheel package directory
setuptools_filename = "setuptools-49.1.3-py3-none-any.whl"
pip_filename = "pip-20.2.2-py2.py3-none-any.whl" pip_filename = "pip-20.2.2-py2.py3-none-any.whl"
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
self.touch(tmpdir, setuptools_filename)
self.touch(tmpdir, pip_filename) self.touch(tmpdir, pip_filename)
# not used, make sure that it's ignored # not used, make sure that it's ignored
self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl") self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl")
@ -53,15 +49,12 @@ class TestPackages(unittest.TestCase):
unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
packages = ensurepip._get_packages() packages = ensurepip._get_packages()
self.assertEqual(packages['setuptools'].version, '49.1.3')
self.assertEqual(packages['setuptools'].wheel_path,
os.path.join(tmpdir, setuptools_filename))
self.assertEqual(packages['pip'].version, '20.2.2') self.assertEqual(packages['pip'].version, '20.2.2')
self.assertEqual(packages['pip'].wheel_path, self.assertEqual(packages['pip'].wheel_path,
os.path.join(tmpdir, pip_filename)) os.path.join(tmpdir, pip_filename))
# wheel package is ignored # wheel package is ignored
self.assertEqual(sorted(packages), ['pip', 'setuptools']) self.assertEqual(sorted(packages), ['pip'])
class EnsurepipMixin: class EnsurepipMixin:
@ -92,13 +85,13 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "setuptools", "pip", unittest.mock.ANY, "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
additional_paths = self.run_pip.call_args[0][1] additional_paths = self.run_pip.call_args[0][1]
self.assertEqual(len(additional_paths), 2) self.assertEqual(len(additional_paths), 1)
def test_bootstrapping_with_root(self): def test_bootstrapping_with_root(self):
ensurepip.bootstrap(root="/foo/bar/") ensurepip.bootstrap(root="/foo/bar/")
@ -107,7 +100,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "--root", "/foo/bar/", unittest.mock.ANY, "--root", "/foo/bar/",
"setuptools", "pip", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -118,7 +111,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "--user", "setuptools", "pip", unittest.mock.ANY, "--user", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -129,7 +122,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "--upgrade", "setuptools", "pip", unittest.mock.ANY, "--upgrade", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -140,7 +133,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "-v", "setuptools", "pip", unittest.mock.ANY, "-v", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -151,7 +144,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "-vv", "setuptools", "pip", unittest.mock.ANY, "-vv", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -162,7 +155,7 @@ class TestBootstrap(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "-vvv", "setuptools", "pip", unittest.mock.ANY, "-vvv", "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
@ -239,7 +232,6 @@ class TestUninstall(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"uninstall", "-y", "--disable-pip-version-check", "pip", "uninstall", "-y", "--disable-pip-version-check", "pip",
"setuptools",
] ]
) )
@ -250,7 +242,6 @@ class TestUninstall(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"uninstall", "-y", "--disable-pip-version-check", "-v", "pip", "uninstall", "-y", "--disable-pip-version-check", "-v", "pip",
"setuptools",
] ]
) )
@ -261,7 +252,6 @@ class TestUninstall(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"uninstall", "-y", "--disable-pip-version-check", "-vv", "pip", "uninstall", "-y", "--disable-pip-version-check", "-vv", "pip",
"setuptools",
] ]
) )
@ -272,7 +262,7 @@ class TestUninstall(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"uninstall", "-y", "--disable-pip-version-check", "-vvv", "uninstall", "-y", "--disable-pip-version-check", "-vvv",
"pip", "setuptools", "pip"
] ]
) )
@ -312,13 +302,13 @@ class TestBootstrappingMainFunction(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"install", "--no-cache-dir", "--no-index", "--find-links", "install", "--no-cache-dir", "--no-index", "--find-links",
unittest.mock.ANY, "setuptools", "pip", unittest.mock.ANY, "pip",
], ],
unittest.mock.ANY, unittest.mock.ANY,
) )
additional_paths = self.run_pip.call_args[0][1] additional_paths = self.run_pip.call_args[0][1]
self.assertEqual(len(additional_paths), 2) self.assertEqual(len(additional_paths), 1)
self.assertEqual(exit_code, 0) self.assertEqual(exit_code, 0)
def test_bootstrapping_error_code(self): def test_bootstrapping_error_code(self):
@ -344,7 +334,6 @@ class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
"uninstall", "-y", "--disable-pip-version-check", "pip", "uninstall", "-y", "--disable-pip-version-check", "pip",
"setuptools",
] ]
) )

View File

@ -227,7 +227,6 @@ class BasicTest(BaseTest):
'install', 'install',
'--upgrade', '--upgrade',
'pip', 'pip',
'setuptools'
] ]
) )
@ -745,7 +744,6 @@ class EnsurePipTest(BaseTest):
# future pip versions, this test can likely be relaxed further. # future pip versions, this test can likely be relaxed further.
out = out.decode("latin-1") # Force to text, prevent decoding errors out = out.decode("latin-1") # Force to text, prevent decoding errors
self.assertIn("Successfully uninstalled pip", out) self.assertIn("Successfully uninstalled pip", out)
self.assertIn("Successfully uninstalled setuptools", out)
# Check pip is now gone from the virtual environment. This only # Check pip is now gone from the virtual environment. This only
# applies in the system_site_packages=False case, because in the # applies in the system_site_packages=False case, because in the
# other case, pip may still be available in the system site-packages # other case, pip may still be available in the system site-packages

View File

@ -13,7 +13,7 @@ import sysconfig
import types import types
CORE_VENV_DEPS = ('pip', 'setuptools') CORE_VENV_DEPS = ('pip',)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -523,7 +523,7 @@ def main(args=None):
'this environment.') 'this environment.')
parser.add_argument('--upgrade-deps', default=False, action='store_true', parser.add_argument('--upgrade-deps', default=False, action='store_true',
dest='upgrade_deps', dest='upgrade_deps',
help=f'Upgrade core dependencies: {", ".join(CORE_VENV_DEPS)} ' help=f'Upgrade core dependencies ({", ".join(CORE_VENV_DEPS)}) '
'to the latest version in PyPI') 'to the latest version in PyPI')
options = parser.parse_args(args) options = parser.parse_args(args)
if options.upgrade and options.clear: if options.upgrade and options.clear:

View File

@ -56,19 +56,19 @@ if [ -d /usr/local/bin ] ; then
cd /usr/local/bin cd /usr/local/bin
# Create pipx.y and easy_install-x.y links if /usr/local/bin/pythonx.y # Create pipx.y links if /usr/local/bin/pythonx.y
# is linked to this framework version # is linked to this framework version
install_links_if_our_fw "python${PYVER}" \ install_links_if_our_fw "python${PYVER}" \
"pip${PYVER}" "easy_install-${PYVER}" "pip${PYVER}"
# Create pipx link if /usr/local/bin/pythonx is linked to this version # Create pipx link if /usr/local/bin/pythonx is linked to this version
install_links_if_our_fw "python${PYMAJOR}" \ install_links_if_our_fw "python${PYMAJOR}" \
"pip${PYMAJOR}" "pip${PYMAJOR}"
# Create pip and easy_install link if /usr/local/bin/python # Create pip link if /usr/local/bin/python
# is linked to this version # is linked to this version
install_links_if_our_fw "python" \ install_links_if_our_fw "python" \
"pip" "easy_install" "pip"
) )
fi fi
exit 0 exit 0

View File

@ -166,7 +166,6 @@ altinstallunixtools:
-if test "x$(ENSUREPIP)" != "xno" ; then \ -if test "x$(ENSUREPIP)" != "xno" ; then \
cd "$(DESTDIR)$(FRAMEWORKUNIXTOOLSPREFIX)/bin" && \ cd "$(DESTDIR)$(FRAMEWORKUNIXTOOLSPREFIX)/bin" && \
for fn in \ for fn in \
easy_install-$(VERSION) \
pip$(VERSION) \ pip$(VERSION) \
; \ ; \
do \ do \

View File

@ -0,0 +1 @@
Remove the bundled setuptools wheel from ``ensurepip``, and stop installing setuptools in environments created by ``venv``.

View File

@ -14,7 +14,7 @@ import re
from pathlib import Path from pathlib import Path
from urllib.request import urlopen from urllib.request import urlopen
PACKAGE_NAMES = ("pip", "setuptools") PACKAGE_NAMES = ("pip",)
ENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip" ENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip"
WHEEL_DIR = ENSURE_PIP_ROOT / "_bundled" WHEEL_DIR = ENSURE_PIP_ROOT / "_bundled"
ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8") ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8")