Close #19552: venv and pyvenv ensurepip integration
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
index 042ed8e..efc2137 100644
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -85,7 +85,8 @@
 mechanisms for third-party virtual environment creators to customize environment
 creation according to their needs, the :class:`EnvBuilder` class.
 
-.. class:: EnvBuilder(system_site_packages=False, clear=False, symlinks=False, upgrade=False)
+.. class:: EnvBuilder(system_site_packages=False, clear=False, \
+                      symlinks=False, upgrade=False, with_pip=False)
 
     The :class:`EnvBuilder` class accepts the following keyword arguments on
     instantiation:
@@ -105,6 +106,12 @@
       environment with the running Python - for use when that Python has been
       upgraded in-place (defaults to ``False``).
 
+    * ``with_pip`` -- a Boolean value which, if True, ensures pip is
+      installed in the virtual environment
+
+    .. versionchanged:: 3.4
+       Added the ``with_pip`` parameter
+
 
     Creators of third-party virtual environment tools will be free to use the
     provided ``EnvBuilder`` class as a base class.
@@ -201,11 +208,15 @@
 
 There is also a module-level convenience function:
 
-.. function:: create(env_dir, system_site_packages=False, clear=False, symlinks=False)
+.. function:: create(env_dir, system_site_packages=False, clear=False, \
+                     symlinks=False, with_pip=False)
 
     Create an :class:`EnvBuilder` with the given keyword arguments, and call its
     :meth:`~EnvBuilder.create` method with the *env_dir* argument.
 
+    .. versionchanged:: 3.4
+       Added the ``with_pip`` parameter
+
 An example of extending ``EnvBuilder``
 --------------------------------------
 
diff --git a/Doc/using/venv-create.inc b/Doc/using/venv-create.inc
index 706ac5d..868bbc8 100644
--- a/Doc/using/venv-create.inc
+++ b/Doc/using/venv-create.inc
@@ -25,7 +25,7 @@
 The command, if run with ``-h``, will show the available options::
 
     usage: pyvenv [-h] [--system-site-packages] [--symlinks] [--clear]
-                  [--upgrade] ENV_DIR [ENV_DIR ...]
+                  [--upgrade] [--without-pip] ENV_DIR [ENV_DIR ...]
 
     Creates virtual Python environments in one or more target directories.
 
@@ -43,6 +43,11 @@
                              raised.
       --upgrade              Upgrade the environment directory to use this version
                              of Python, assuming Python has been upgraded in-place.
+      --without-pip          Skips installing or upgrading pip in the virtual
+                             environment (pip is bootstrapped by default)
+
+.. versionchanged:: 3.4
+   Installs pip by default, added the ``--without-pip`` option
 
 If the target directory already exists an error will be raised, unless
 the ``--clear`` or ``--upgrade`` option was provided.
@@ -51,6 +56,9 @@
 ``include-system-site-packages`` key, set to ``true`` if ``venv`` is
 run with the ``--system-site-packages`` option, ``false`` otherwise.
 
+Unless the ``--without-pip`` option is given, :mod:`ensurepip` will be
+invoked to bootstrap ``pip`` into the virtual environment.
+
 Multiple paths can be given to ``pyvenv``, in which case an identical
 virtualenv will be created, according to the given options, at each
 provided path.
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index dbbe157..6047f87 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -16,6 +16,10 @@
 import unittest
 import venv
 
+skipInVenv = unittest.skipIf(sys.prefix != sys.base_prefix,
+                             'Test not appropriate in a venv')
+
+
 class BaseTest(unittest.TestCase):
     """Base class for venv tests."""
 
@@ -83,8 +87,7 @@
             print('    %r' % os.listdir(bd))
         self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
 
-    @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
-                     'in a venv')
+    @skipInVenv
     def test_prefixes(self):
         """
         Test that the prefix values are as expected.
@@ -217,8 +220,7 @@
     # run the test, the pyvenv.cfg in the venv created in the test will
     # point to the venv being used to run the test, and we lose the link
     # to the source build - so Python can't initialise properly.
-    @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
-                     'in a venv')
+    @skipInVenv
     def test_executable(self):
         """
         Test that the sys.executable value is as expected.
@@ -247,8 +249,50 @@
         out, err = p.communicate()
         self.assertEqual(out.strip(), envpy.encode())
 
+
+@skipInVenv
+class EnsurePipTest(BaseTest):
+    """Test venv module installation of pip."""
+
+    def test_no_pip_by_default(self):
+        shutil.rmtree(self.env_dir)
+        self.run_with_capture(venv.create, self.env_dir)
+        envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+        try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
+        cmd = [envpy, '-c', try_import]
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        self.assertEqual(err, b"")
+        self.assertEqual(out.strip(), b"OK")
+
+    def test_explicit_no_pip(self):
+        shutil.rmtree(self.env_dir)
+        self.run_with_capture(venv.create, self.env_dir, with_pip=False)
+        envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+        try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
+        cmd = [envpy, '-c', try_import]
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        self.assertEqual(err, b"")
+        self.assertEqual(out.strip(), b"OK")
+
+    def test_with_pip(self):
+        shutil.rmtree(self.env_dir)
+        self.run_with_capture(venv.create, self.env_dir, with_pip=True)
+        envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+        cmd = [envpy, '-m', 'pip', '--version']
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        self.assertEqual(err, b"")
+        self.assertTrue(out.startswith(b"pip"))
+        self.assertIn(self.env_dir.encode(), out)
+
+
 def test_main():
-    run_unittest(BasicTest)
+    run_unittest(BasicTest, EnsurePipTest)
 
 if __name__ == "__main__":
     test_main()
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index a57db0e..2991c66 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -24,10 +24,13 @@
                         raised.
   --upgrade             Upgrade the environment directory to use this version
                         of Python, assuming Python has been upgraded in-place.
+  --without-pip         Skips installing or upgrading pip in the virtual
+                        environment (pip is bootstrapped by default)
 """
 import logging
 import os
 import shutil
+import subprocess
 import sys
 import sysconfig
 import types
@@ -56,14 +59,17 @@
     :param symlinks: If True, attempt to symlink rather than copy files into
                      virtual environment.
     :param upgrade: If True, upgrade an existing virtual environment.
+    :param with_pip: If True, ensure pip is installed in the virtual
+                     environment
     """
 
     def __init__(self, system_site_packages=False, clear=False,
-                 symlinks=False, upgrade=False):
+                 symlinks=False, upgrade=False, with_pip=False):
         self.system_site_packages = system_site_packages
         self.clear = clear
         self.symlinks = symlinks
         self.upgrade = upgrade
+        self.with_pip = with_pip
 
     def create(self, env_dir):
         """
@@ -76,6 +82,8 @@
         context = self.ensure_directories(env_dir)
         self.create_configuration(context)
         self.setup_python(context)
+        if self.with_pip:
+            self._setup_pip(context)
         if not self.upgrade:
             self.setup_scripts(context)
             self.post_setup(context)
@@ -224,6 +232,12 @@
                     shutil.copyfile(src, dst)
                     break
 
+    def _setup_pip(self, context):
+        """Installs or upgrades pip in a virtual environment"""
+        cmd = [context.env_exe, '-m', 'ensurepip', '--upgrade',
+                                                   '--default-pip']
+        subprocess.check_output(cmd)
+
     def setup_scripts(self, context):
         """
         Set up scripts into the created environment from a directory.
@@ -317,7 +331,8 @@
                     shutil.copymode(srcfile, dstfile)
 
 
-def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
+def create(env_dir, system_site_packages=False, clear=False,
+                    symlinks=False, with_pip=False):
     """
     Create a virtual environment in a directory.
 
@@ -333,9 +348,11 @@
                   raised.
     :param symlinks: If True, attempt to symlink rather than copy files into
                      virtual environment.
+    :param with_pip: If True, ensure pip is installed in the virtual
+                     environment
     """
     builder = EnvBuilder(system_site_packages=system_site_packages,
-                                   clear=clear, symlinks=symlinks)
+                         clear=clear, symlinks=symlinks, with_pip=with_pip)
     builder.create(env_dir)
 
 def main(args=None):
@@ -390,12 +407,19 @@
                                                'directory to use this version '
                                                'of Python, assuming Python '
                                                'has been upgraded in-place.')
+        parser.add_argument('--without-pip', dest='with_pip',
+                            default=True, action='store_false',
+                            help='Skips installing or upgrading pip in the '
+                                 'virtual environment (pip is bootstrapped '
+                                 'by default)')
         options = parser.parse_args(args)
         if options.upgrade and options.clear:
             raise ValueError('you cannot supply --upgrade and --clear together.')
         builder = EnvBuilder(system_site_packages=options.system_site,
-                             clear=options.clear, symlinks=options.symlinks,
-                             upgrade=options.upgrade)
+                             clear=options.clear,
+                             symlinks=options.symlinks,
+                             upgrade=options.upgrade,
+                             with_pip=options.with_pip)
         for d in options.dirs:
             builder.create(d)
 
diff --git a/Misc/NEWS b/Misc/NEWS
index c33c92d..24ca366 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -65,6 +65,8 @@
 Library
 -------
 
+- Issue #19552: venv now supports bootstrapping pip into virtual environments
+
 - Issue #17134: Finalize interface to Windows' certificate store. Cert and
   CRL enumeration are now two functions. enum_certificates() also returns
   purpose flags as set of OIDs.
@@ -378,6 +380,9 @@
 Tools/Demos
 -----------
 
+- Issue #19552: pyvenv now bootstraps pip into virtual environments by
+  default (pass --without-pip to request the old behaviour)
+
 - Issue #19390: Argument Clinic no longer accepts malformed Python
   and C ids.