Repository: pypa/virtualenv

Issue:
Environment created from another environment ignores --no-pip, --no-wheel, --no-setuptools options
**Issue**

Affected:  virtualenv 20.0.x

When environment created from another environment with --no-pip option, pip is still installed.
It is working with virtualenv 16.x (regression)

Case:
- Create first environt and install vitrtualenv inside it: 
virtualenv one
one/bin/python -m pip install virtualenv

- Create second environment with no pip:
one/bin/python -m virtualenv two --no-pip

Result: pip installed
Expected: no pip should be installed

(same with other flags) 

**Environment**

- OS: ubuntu 19.10
- packages: python3, python3-pip
- pip updated, virtualenv installed with pip

**Output of the virtual environment creation**

The above case execution:

```sh
xvik@xvik:/tmp$ python3 --version
Python 3.7.5
xvik@xvik:/tmp$ python3 -m pip --version
pip 20.0.2 from /home/xvik/.local/lib/python3.7/site-packages/pip (python 3.7)
xvik@xvik:/tmp$ python3 -m virtualenv --version
virtualenv 20.0.10 from /home/xvik/.local/lib/python3.7/site-packages/virtualenv/__init__.py

xvik@xvik:/tmp$ python3 -m virtualenv one
created virtual environment CPython3.7.5.final.0-64 in 140ms
  creator CPython3Posix(dest=/tmp/one, clear=False, global=False)
  seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/xvik/.local/share/virtualenv/seed-app-data/v1)
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

xvik@xvik:/tmp$ one/bin/python -m pip install virtualenv
Collecting virtualenv
  Using cached virtualenv-20.0.10-py2.py3-none-any.whl (4.6 MB)
Collecting six<2,>=1.9.0
  Using cached six-1.14.0-py2.py3-none-any.whl (10 kB)
Collecting importlib-metadata<2,>=0.12; python_version < "3.8"
  Using cached importlib_metadata-1.5.0-py2.py3-none-any.whl (30 kB)
Collecting filelock<4,>=3.0.0
  Using cached filelock-3.0.12-py3-none-any.whl (7.6 kB)
Collecting appdirs<2,>=1.4.3
  Using cached appdirs-1.4.3-py2.py3-none-any.whl (12 kB)
Processing /home/xvik/.cache/pip/wheels/a2/19/da/a15d4e2bedf3062c739b190d5cb5b7b2ecfbccb6b0d93c861b/distlib-0.3.0-py3-none-any.whl
Collecting zipp>=0.5
  Using cached zipp-3.1.0-py3-none-any.whl (4.9 kB)
Installing collected packages: six, zipp, importlib-metadata, filelock, appdirs, distlib, virtualenv
Successfully installed appdirs-1.4.3 distlib-0.3.0 filelock-3.0.12 importlib-metadata-1.5.0 six-1.14.0 virtualenv-20.0.10 zipp-3.1.0

xvik@xvik:/tmp$ one/bin/python -m virtualenv two --no-pip -v
find interpreter for spec PythonSpec(path=/tmp/one/bin/python)
proposed PythonInfo(spec=CPython3.7.5.final.0-64, system=/usr/bin/python3.7, exe=/tmp/one/bin/python, platform=linux, version='3.7.5 (default, Nov 20 2019, 09:21:52) \n[GCC 9.2.1 20191008]', encoding_fs_io=utf-8-UTF-8)
create virtual environment via CPython3Posix(dest=/tmp/two, clear=False, global=False)
add seed packages via FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/xvik/.local/share/virtualenv/seed-app-data/v1)
changing mode of /tmp/two/bin/wheel to 755
changing mode of /tmp/two/bin/wheel-3.7 to 755
changing mode of /tmp/two/bin/wheel3 to 755
changing mode of /tmp/two/bin/easy_install to 755
changing mode of /tmp/two/bin/easy_install-3.7 to 755
changing mode of /tmp/two/bin/easy_install3 to 755
changing mode of /tmp/two/bin/pip3 to 755
changing mode of /tmp/two/bin/pip-3.7 to 755
changing mode of /tmp/two/bin/pip to 755
add activators for Bash, CShell, Fish, PowerShell, Python, Xonsh
created virtual environment CPython3.7.5.final.0-64 in 133ms
  creator CPython3Posix(dest=/tmp/two, clear=False, global=False)
  seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/xvik/.local/share/virtualenv/seed-app-data/v1)
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

xvik@xvik:/tmp$ two/bin/python -m pip --version
pip 20.0.2 from /tmp/two/lib/python3.7/site-packages/pip (python 3.7)
```

Discussion:

Patch:
diff --git a/docs/changelog/1712.bugfix.rst b/docs/changelog/1712.bugfix.rst
new file mode 100644
index 00000000..cd6a628c
--- /dev/null
+++ b/docs/changelog/1712.bugfix.rst
@@ -0,0 +1,1 @@
+Fix ``--no-pip``, ``--no-setuptools``, ``--no-wheel`` not being respected - by :user:`gaborbernat`.
diff --git a/src/virtualenv/__main__.py b/src/virtualenv/__main__.py
index 267ddfc5..ae85c680 100644
--- a/src/virtualenv/__main__.py
+++ b/src/virtualenv/__main__.py
@@ -37,8 +37,9 @@ class LogSession(object):
         lines = [
             "created virtual environment {} in {:.0f}ms".format(spec, elapsed),
             "  creator {}".format(ensure_text(str(self.session.creator))),
-            "  seeder {}".format(ensure_text(str(self.session.seeder)),),
         ]
+        if self.session.seeder.enabled:
+            lines += ("  seeder {}".format(ensure_text(str(self.session.seeder)),),)
         if self.session.activators:
             lines.append("  activators {}".format(",".join(i.__class__.__name__ for i in self.session.activators)))
         return os.linesep.join(lines)
diff --git a/src/virtualenv/seed/embed/base_embed.py b/src/virtualenv/seed/embed/base_embed.py
index 98ba65fe..1067a7b7 100644
--- a/src/virtualenv/seed/embed/base_embed.py
+++ b/src/virtualenv/seed/embed/base_embed.py
@@ -16,7 +16,6 @@ class BaseEmbed(Seeder):
 
     def __init__(self, options):
         super(BaseEmbed, self).__init__(options, enabled=options.no_seed is False)
-
         self.download = options.download
         self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]
 
@@ -33,8 +32,15 @@ class BaseEmbed(Seeder):
         self.no_wheel = options.no_wheel
         self.app_data = options.app_data.folder
 
+        if not self.package_version():
+            self.enabled = False
+
     def package_version(self):
-        return {package: getattr(self, "{}_version".format(package)) for package in self.packages}
+        return {
+            package: getattr(self, "{}_version".format(package))
+            for package in self.packages
+            if getattr(self, "no_{}".format(package)) is False
+        }
 
     @classmethod
     def add_parser_arguments(cls, parser, interpreter, app_data):
@@ -86,6 +92,8 @@ class BaseEmbed(Seeder):
             result += "extra_search_dir={},".format(", ".join(ensure_text(str(i)) for i in self.extra_search_dir))
         result += "download={},".format(self.download)
         for package in self.packages:
+            if getattr(self, "no_{}".format(package)):
+                continue
             result += " {}{},".format(
                 package, "={}".format(getattr(self, "{}_version".format(package), None) or "latest")
             )
diff --git a/src/virtualenv/seed/embed/pip_invoke.py b/src/virtualenv/seed/embed/pip_invoke.py
index c007af36..74c96f1b 100644
--- a/src/virtualenv/seed/embed/pip_invoke.py
+++ b/src/virtualenv/seed/embed/pip_invoke.py
@@ -36,11 +36,12 @@ class PipInvoke(BaseEmbed):
         cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:"]
         if not self.download:
             cmd.append("--no-index")
-        for key, ver in self.package_version().items():
+        pkg_versions = self.package_version()
+        for key, ver in pkg_versions.items():
             cmd.append("{}{}".format(key, "=={}".format(ver) if ver is not None else ""))
         with ExitStack() as stack:
             folders = set()
-            for context in (ensure_file_on_disk(get_bundled_wheel(p, version), self.app_data) for p in self.packages):
+            for context in (ensure_file_on_disk(get_bundled_wheel(p, version), self.app_data) for p in pkg_versions):
                 folders.add(stack.enter_context(context).parent)
             for folder in folders:
                 cmd.extend(["--find-links", str(folder)])
diff --git a/src/virtualenv/seed/via_app_data/via_app_data.py b/src/virtualenv/seed/via_app_data/via_app_data.py
index 50d3afb9..06a75b07 100644
--- a/src/virtualenv/seed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/via_app_data/via_app_data.py
@@ -39,7 +39,7 @@ class FromAppData(BaseEmbed):
             return
         base_cache = self.base_cache / creator.interpreter.version_release_str
         with self._get_seed_wheels(creator, base_cache) as name_to_whl:
-            pip_version = name_to_whl["pip"].stem.split("-")[1]
+            pip_version = name_to_whl["pip"].stem.split("-")[1] if "pip" in name_to_whl else None
             installer_class = self.installer_class(pip_version)
 
             def _install(name, wheel):
@@ -86,7 +86,7 @@ class FromAppData(BaseEmbed):
             yield name_to_whl
 
     def installer_class(self, pip_version):
-        if self.symlinks:
+        if self.symlinks and pip_version:
             # symlink support requires pip 19.3+
             pip_version_int = tuple(int(i) for i in pip_version.split(".")[0:2])
             if pip_version_int >= (19, 3):


Test patch:
diff --git a/tests/unit/seed/test_boostrap_link_via_app_data.py b/tests/unit/seed/test_boostrap_link_via_app_data.py
index 493871dd..7b753c32 100644
--- a/tests/unit/seed/test_boostrap_link_via_app_data.py
+++ b/tests/unit/seed/test_boostrap_link_via_app_data.py
@@ -121,3 +121,14 @@ def test_base_bootstrap_link_via_app_data_not_writable(tmp_path, current_fastest
     dest = tmp_path / "venv"
     result = cli_run(["--seeder", "app-data", "--creator", current_fastest, "--clear-app-data", "-vv", str(dest)])
     assert result
+
+
+@pytest.mark.slow
+@pytest.mark.timeout(timeout=60)
+@pytest.mark.parametrize("pkg", ["pip", "setuptools", "wheel"])
+def test_base_bootstrap_link_via_app_data_no(tmp_path, coverage_env, current_fastest, session_app_data, pkg):
+    create_cmd = [str(tmp_path), "--seeder", "app-data", "--no-{}".format(pkg)]
+    result = cli_run(create_cmd)
+    assert not (result.creator.purelib / pkg).exists()
+    for key in {"pip", "setuptools", "wheel"} - {pkg}:
+        assert (result.creator.purelib / key).exists()
diff --git a/tests/unit/seed/test_pip_invoke.py b/tests/unit/seed/test_pip_invoke.py
index 745d2149..b750753d 100644
--- a/tests/unit/seed/test_pip_invoke.py
+++ b/tests/unit/seed/test_pip_invoke.py
@@ -9,7 +9,8 @@ from virtualenv.seed.embed.wheels import BUNDLE_SUPPORT
 
 @pytest.mark.slow
 @pytest.mark.timeout(timeout=60)
-def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, current_fastest):
+@pytest.mark.parametrize("no", ["pip", "setuptools", "wheel", ""])
+def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, current_fastest, no):
     bundle_ver = BUNDLE_SUPPORT[PythonInfo.current_system().version_release_str]
     create_cmd = [
         "--seeder",
@@ -23,6 +24,8 @@ def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, current_fastest):
         "--creator",
         current_fastest,
     ]
+    if no:
+        create_cmd.append("--no-{}".format(no))
     result = cli_run(create_cmd)
     coverage_env()
     assert result
@@ -31,8 +34,13 @@ def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, current_fastest):
     pip = site_package / "pip"
     setuptools = site_package / "setuptools"
     wheel = site_package / "wheel"
-
     files_post_first_create = list(site_package.iterdir())
-    assert pip in files_post_first_create
-    assert setuptools in files_post_first_create
-    assert wheel in files_post_first_create
+
+    if no:
+        no_file = locals()[no]
+        assert no not in files_post_first_create
+
+    for key in ("pip", "setuptools", "wheel"):
+        if key == no:
+            continue
+        assert locals()[key] in files_post_first_create