Repository: pypa/pip

Issue:
pip install --user misbehaves inside venv
**Environment**

* pip version: 18.0
* Python version: 3.7
* OS: Arch Linux, updated on 12. August 2018

**Description**
When using `pip install --user` inside a venv (created using the venv module coming with python), packages get installed in my ~/.local folder instead of the venv.
My pip.conf usually sets this flag per default, since I don't install packages globally. This bug makes that config option unusable.

**Expected behavior**
I would expect `venv/bin/pip list` to include the `ipdb` package. Instead, the package is installed outside of the venv.

**How to Reproduce**
```
python -m venv venv
venv/bin/pip install --user ipdb
venv/bin/pip list
```

**Output**

```
venv/bin/pip install --user ipdb
Collecting ipdb
  Using cached https://files.pythonhosted.org/packages/80/fe/4564de08f174f3846364b3add8426d14cebee228f741c27e702b2877e85b/ipdb-0.11.tar.gz
Requirement already satisfied: setuptools in ./venv/lib/python3.7/site-packages (from ipdb) (39.0.1)
Collecting ipython>=5.0.0 (from ipdb)
  Using cached https://files.pythonhosted.org/packages/f7/62/2fef7db3a7b75e8099c3d9db2630ae5ba0b9eefefd91f7497862393d90e8/ipython-6.5.0-py3-none-any.whl
Collecting pickleshare (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/9f/17/daa142fc9be6b76f26f24eeeb9a138940671490b91cb5587393f297c8317/pickleshare-0.7.4-py2.py3-none-any.whl
Collecting traitlets>=4.2 (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/93/d6/abcb22de61d78e2fc3959c964628a5771e47e7cc60d53e9342e21ed6cc9a/traitlets-4.3.2-py2.py3-none-any.whl
Collecting backcall (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/84/71/c8ca4f5bb1e08401b916c68003acf0a0655df935d74d93bf3f3364b310e0/backcall-0.1.0.tar.gz
Collecting pexpect; sys_platform != "win32" (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/89/e6/b5a1de8b0cc4e07ca1b305a4fcc3f9806025c1b651ea302646341222f88b/pexpect-4.6.0-py2.py3-none-any.whl
Collecting simplegeneric>0.8 (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip
Collecting jedi>=0.10 (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/3d/68/8bbf0ef969095a13ba0d4c77c1945bd86e9811960d052510551d29a2f23b/jedi-0.12.1-py2.py3-none-any.whl
Collecting decorator (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/bc/bb/a24838832ba35baf52f32ab1a49b906b5f82fb7c76b2f6a7e35e140bac30/decorator-4.3.0-py2.py3-none-any.whl
Collecting pygments (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/02/ee/b6e02dc6529e82b75bb06823ff7d005b141037cb1416b10c6f00fc419dca/Pygments-2.2.0-py2.py3-none-any.whl
Collecting prompt-toolkit<2.0.0,>=1.0.15 (from ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/04/d1/c6616dd03701e7e2073f06d5c3b41b012256e42b72561f16a7bd86dd7b43/prompt_toolkit-1.0.15-py3-none-any.whl
Collecting ipython-genutils (from traitlets>=4.2->ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl
Collecting six (from traitlets>=4.2->ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Collecting ptyprocess>=0.5 (from pexpect; sys_platform != "win32"->ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl
Collecting parso>=0.3.0 (from jedi>=0.10->ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/09/51/9c48a46334be50c13d25a3afe55fa05c445699304c5ad32619de953a2305/parso-0.3.1-py2.py3-none-any.whl
Collecting wcwidth (from prompt-toolkit<2.0.0,>=1.0.15->ipython>=5.0.0->ipdb)
  Using cached https://files.pythonhosted.org/packages/7e/9f/526a6947247599b084ee5232e4f9190a38f398d7300d866af3ab571a5bfe/wcwidth-0.1.7-py2.py3-none-any.whl
Installing collected packages: pickleshare, ipython-genutils, six, decorator, traitlets, backcall, ptyprocess, pexpect, simplegeneric, parso, jedi, pygments, wcwidth, prompt-toolkit, ipython, ipdb
  Running setup.py install for backcall ... done
  Running setup.py install for simplegeneric ... done
  Running setup.py install for ipdb ... done
Successfully installed backcall-0.1.0 decorator-4.3.0 ipdb-0.11 ipython-6.5.0 ipython-genutils-0.2.0 jedi-0.12.1 parso-0.3.1 pexpect-4.6.0 pickleshare-0.7.4 prompt-toolkit-1.0.15 ptyprocess-0.6.0 pygments-2.2.0 simplegeneric-0.8.1 six-1.11.0 traitlets-4.3.2 wcwidth-0.1.7
```
```
venv/bin/pip list
Package    Version
---------- -------
pip        18.0   
setuptools 39.0.1 
```

Do not use user setting when inside of a venv
- Fixes #7148

<!---
Thank you for your soon to be pull request. Before you submit this, please
double check to make sure that you've added a news file fragment. In pip we
generate our NEWS.rst from multiple news fragment files, and all pull requests
require either a news file fragment or a marker to indicate they don't require
one.

To read more about adding a news file fragment for your PR, please check out
our documentation at: https://pip.pypa.io/en/latest/development/contributing/#news-entries
-->



Discussion:
Geepers, I didn't know what the heck was going on until I just found this bug! Seems a serious bug. Also on Arch with Python 3.7.
Given the confirmation from another user and the obvious severity of the bug, I've marked this as a blocker for 18.1; even though it's ~2 months away. :P
BTW, the bug happens with both pip versions 10.0.1 and 18.0 for me.
Great; thanks for checking that @bulletmark! :)
@raffomania @bulletmark am I understanding correctly that you _expect_ pip to _ignore_ the `--user` flag if it's being ran inside a virtualenv? Just want to clarify. 
pip should refuse to install:

<img width="757" alt="screen shot 2018-08-20 at 7 09 07 pm" src="https://user-images.githubusercontent.com/3275593/44343800-8c4d3380-a4ac-11e8-90c7-0db249d0d815.png">

@pradyunsg yeah that's what I would expect
Erroring out is the plan described in issue #4575 "Adopting "working" scheme for every run":

> Only packages in the same scheme as the working scheme can be modified. By modifying, I mean installing or uninstalling a package. Trying to modify a package in a different scheme is not allowed and pip would print a message and error out.
The issue of this bug is not to do with explicit specification of `--user` on the command line. The issue is that a user having `user` mode set in his `~/.config/pip/pip.conf` currently can NEVER use pip inside a virtualenv, because it just ignores the virtualenv and installs to `~/.local`. That is why I described the bug above as "serious". It seems to me that pip should ignore the user mode setting in `~/.config/pip/pip.conf` when it is run inside a virtualenv.

Whether pip errors out on an explicit `--user` when run within a virtualenv, or just installs to `~/.local` as the user has explicitly asked, is another matter and another debate. Personally I think pip in that case should respect the added `--user`.

It is arguably a design error that by default pip installs globally (see the long running and still open debate in issue #1668) so many users just set the user default in their config and forget about it, like I did a month or so ago then scratched my head for ages why my next virtualenv was not getting set up correctly.
This is duplicate of issue #4141 (*Behavior of --user in virtualenv*), which mentions error message shown also by @pradyunsg in his comment. It's strange OP did not get this error.

@bulletmark 

Different behavior based on where an option was specified (configuration file vs command line) would be very odd.


@piotr-dobrogost Agreed. I'd be against having behaviour differ based on *where* the user specifies `--user`. What about an environment variable? Should a persistent environment variable be treated differently than `PIP_USER=yes pip install foo`? I don't think we want to start down this route...

I understand that people see setting `--user` in a config file as being somehow different in intention, as more of a choice to default to user than a request to always force user, but that's just not what it is.

> many users just set the user default in their config

And that's the problem - setting `user=true` in the pip config file is *not* "setting the user default", rather it's "asking pip to use `--user` for all installs", which is subtly (but significantly) different. I guess there may be some value in adding a warning note to the docs explaining that point.

Having user installs be the default is what #1668 is about, and discussions about that should take place over there.
@pfmoore, so you are happy with the current situation that if you set `user` in your config you can NEVER pip install in a virtualenv?! That is the important problem here. What happens if you specify `--user` on the command line when running in a virtualenv is another less important issue.
@piotr-dobrogost, as I said (my paragraph 2) that is a less important issue. I'd also be happy if that case just reported an error to the user. The important issue is my paragraphs 1 and 3.
@pfmoore 

I would argue issue #1668 is orthogonal to this one. The problem here (and in many issues related to *user scheme*) is pip not establishing current *working scheme*, which is the problem raised in issue #4575.
@piotr-dobrogost Good point. See my comment on #4575 - there's a need for a "`local,user`" option for the working scheme, which says "`local` if possible, otherwise `user`". But apart from that detail, yes this is essentially #4575.

@bulletmark No, I'm not happy with the current situation[1], but I don't agree that the correct fix is to break the current behaviour that specifying options in the config file works exactly the same as specifying them on the command line.

[1] Actually, on a personal basis, I *am* perfectly happy with the current situation, as I don't specify `user` in my pip config, because it doesn't do the right thing :smile: (and anyway, I avoid global installs anyway, both system and user...) But I agree, my working practices aren't the point here.
For the non-usability in a venv, I think it's reasonable to modify the message to say that "if you've modified configuration files to include user = true, put user = false in your virtualenv config file". That's a workaround at best but I think it's the least effort change.

IMO the bug here is that isn't pip printing the error message, i.e. treating the value set in a configuration file differently from a CLI option. That's a severe bug, as it's (silently) breaking the isolation of the virtualenv.

Other than that, the rest of the discussion here is either related to user-by-default (#1668) or related to mixing of schemes (#4575). Let's keep have those discussions there.

Could someone provide clearer instructions to reproduce this issue?
Pip should not be used globally but since user mode is not the default (as per https://github.com/pypa/pip/issues/1668) people want to set the user option in their `~/.config/pip/pip.conf`. However if you do this you can not use pip with virtual environments because the packages get installed into your `~/.local` instead of the venv. Thus as the OP says here, the "user" config file option is useless, and very confusing to users. Pretty simple and shocking bug.
Dropping this from 18.1, since this is not something introduced in 18.0 and there's no clear plan on how to fix this within pip anyway.


I have run into this issue with pip 19.0.3 and 19.2.2. Spent the whole day trying to figure out why packages do not get installed while in the virtual environment. It is a pity that such important issues are not fixed. I think we need another PEP for this situation... :(
> Could someone provide clearer instructions to reproduce this issue?

Reiterating what I said 11 months ago.

> PEP

We don’t need a PEP here. We need someone to explain how to reproduce this issue, so that someone can debug it.

Packaging PEPs are needed for issues that affect multiple tools, and a broader design discussion is needed about interoperability.
Does the following serve as a demonstration?

1. Install Ubuntu 18.04, install systems pythons (2.7 and 3.6.8), install system pip. system pip is v.9.0.1

2. install/upgrade pip in the user space. If i am not mistaken, i did it with the following command:

```
$ pip install --user --upgrade pip
$ pip --version
=> 19.2.3
``` 

3. scenario that works (you can skip it)

```
$ mkdir bigbang; cd bigbang
$ python3 -m venv pip-no-user
	
$ . pip-no-user/bin/activate
$ which pip
=> /path/to/bigbang/pip-no-user/bin/pip

$ pip --version
=> 9.0.1 # this is system pip in ubuntu 18.04

$ pip install --upgrade pip
# upgrades the version inside virtual environment

$ pip --version
=> 19.2.3

$ pip list | grep tabulate
=> nothing, as expected

$ pip install tabulate
$ pip list | grep tabulate
=> tabulate      0.8.3 

# lets do a closer test by loading the library
$ python
>>> import tabulate
=> OK

# type ctrl+d to exit repl

$ deactivate
```

conclusion: as expected, the package tabulate was installed in the virtual environment

4. configure pip to always perform user install. this is accomplished by adding the following to the configuration file `$HOME/.pip/pip.conf`

```
[install]
user = true
```

Lets do some testing

```
$ pip list | grep tabulate
=> nothing

$ pip install tabulate
$ pip install tabulate
=> Requirement already satisfied: tabulate in HOME/.local/lib/python3.6/site-packages (0.8.3)

$ pip list | grep tabulate
=> tabulate      0.8.3 

$ python3
>>> import tabulate
=>OK
```

Conclusion: the package was successfully installed into user space without explicitly giving `--user` option to pip

5. Based on the setup outlined in 4). This is the failing scenario: when in virtual environment, the libraries are *not* installed in the environment but rather in user space (HOME/.local).

```
$ python3 -m venv pip-user-true
$ . pip-user-true/bin/activate
$ pip --version
=> pip 9.0.1
```

At this point one could run

```	
$ pip install --upgrade pip
```

but i am not doing it, because it fucks up `HOME/.local/bin/pip` by changing shebang to be `
#!/path/to/bigbang/pip-user-true/bin/python3`
	
The question is why `HOME/.local/bin/pip` was updated if environment-specific pip should have been updated, namely this one: `/path/to/bigbang/pip-user-true/bin/pip`.

Next we check if tabulate (installed in step 4 in user space) is available:

```
$ pip list |grep tabulate
=> nothing
	
$ python
>>> import tabulate
=> ModuleNotFoundError: No module named 'tabulate'
```

Conclusion: as expected, virtual environment isolates current project from user space.

And finally the painful part.

```
$ pip install tabulate
=> Collecting tabulate
=> Cache entry deserialization failed, entry ignored
=> Installing collected packages: tabulate
=> Successfully installed tabulate-0.8.3
```

Weird message 'Cache entry deserialization failed, entry ignored'. Heh.

```
$ pip list | grep tabulate
=> nothing... weird

$ python
>>> import tabulate
=> ModuleNotFoundError: No module named 'tabulate'
```

It smells bad. Lets continue the journey. Installing tabulate library also drops a command line tool. It should be in the environment bin `/path/to/bigbang/pip-user-true/bin/tabulate` but in reality it is found in user bin:

```
$ ls HOME/.local/bin/tabulate
=> ok
```

Lets look at the script more closely:

```
$ head -n1 HOME/.local/bin/tabulate
=> #!/path/to/bigbang/pip-user-true/bin/python3
```

This is weird! Shebang is environment-specific but the script is in user space. Something is wrong here.

And the last chance. Try loading the library we (think we) have just installed.

```
$ python
>>> import tabulate
ModuleNotFoundError: No module named 'tabulate'
```

hmmm. It is not available in the environment. 

6. Scratching the head... Undoing modifications in `.pip/pip.conf` for the benefit of everything.

Sorry for the long read.
Thanks, that makes things much clearer.

To summarise, `--user` doesn't work properly when used within a virtual environment. Normally, the obvious workaround is "don't do that", but when you have `--user` forced on by a config file (or environment variable) it's much less straightforward to "not do that". Is that a reasonable summary?

Your example didn't explicitly demonstrate using an explicit `--user` command line option from within an activated virtual environment, but I assume it would work like case (5).

The fix for the underlying problem is under discussion in #4575. The fact that there's no `--no-user` option to override `user=true` in a config file is an omission, but it's only really needed because people put `user=true` in their config, and in reality that doesn't do what they want it to do (which is to make pip work as if we'd implemented #1668 (user as default)) so `--no-user` is a case of piling workaround on top of workaround.
> To summarise, --user doesn't work properly when used within a virtual environment. Normally, the obvious workaround is "don't do that", but when you have --user forced on by a config file (or environment variable) it's much less straightforward to "not do that". Is that a reasonable summary?

yes. this is what happens. I would be almost happy if the documentation of `pip` and/or `venv` at least makes a note in bold/red that `--user` scheme does not play well with `venv`. Even better if invocation of `python -m venv envname` throws a warning when it detects that the user scheme is activated (either --user or by a configuration option). I think informing the user would already reduce the amount of wasted time.. Before there is a final solution for this issue. 


Thank you for that! It helps a lot!

--user is supposed to abort when installing from within an activated virtualenv. That seems to not be happening.

I'll look into why that's happening and print an error message instead of doing the wrong thing.
Is this venv specific or does this also happen with virtualenv?
> Is this venv specific or does this also happen with virtualenv?

Okay, this is `venv` specific.

Basically, pip was never updated to also look at [PEP 405's system-site-packages][1] mechanism. I'll file a PR to fix this in a bit.

FWIW, it's a matter of updating the function `virtualenv_no_global()` in pip, to actually look do the checks for PEP 405-based virtual environments.

[1]: https://www.python.org/dev/peps/pep-0405/#isolation-from-system-site-packages


Resolving patch:
diff --git a/src/pip/_internal/utils/virtualenv.py b/src/pip/_internal/utils/virtualenv.py
--- a/src/pip/_internal/utils/virtualenv.py
+++ b/src/pip/_internal/utils/virtualenv.py
@@ -1,34 +1,115 @@
-import os.path
+from __future__ import absolute_import
+
+import logging
+import os
+import re
 import site
 import sys
 
+from pip._internal.utils.typing import MYPY_CHECK_RUNNING
+
+if MYPY_CHECK_RUNNING:
+    from typing import List, Optional
+
+logger = logging.getLogger(__name__)
+_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile(
+    r"include-system-site-packages\s*=\s*(?P<value>true|false)"
+)
+
+
+def _running_under_venv():
+    # type: () -> bool
+    """Checks if sys.base_prefix and sys.prefix match.
+
+    This handles PEP 405 compliant virtual environments.
+    """
+    return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
+
+
+def _running_under_regular_virtualenv():
+    # type: () -> bool
+    """Checks if sys.real_prefix is set.
+
+    This handles virtual environments created with pypa's virtualenv.
+    """
+    # pypa/virtualenv case
+    return hasattr(sys, 'real_prefix')
+
 
 def running_under_virtualenv():
     # type: () -> bool
+    """Return True if we're running inside a virtualenv, False otherwise.
     """
-    Return True if we're running inside a virtualenv, False otherwise.
+    return _running_under_venv() or _running_under_regular_virtualenv()
+
 
+def _get_pyvenv_cfg_lines():
+    # type: () -> Optional[List[str]]
+    """Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
+
+    Returns None, if it could not read/access the file.
     """
-    if hasattr(sys, 'real_prefix'):
-        # pypa/virtualenv case
-        return True
-    elif sys.prefix != getattr(sys, "base_prefix", sys.prefix):
-        # PEP 405 venv
+    pyvenv_cfg_file = os.path.join(sys.prefix, 'pyvenv.cfg')
+    try:
+        with open(pyvenv_cfg_file) as f:
+            return f.read().splitlines()  # avoids trailing newlines
+    except IOError:
+        return None
+
+
+def _no_global_under_venv():
+    # type: () -> bool
+    """Check `{sys.prefix}/pyvenv.cfg` for system site-packages inclusion
+
+    PEP 405 specifies that when system site-packages are not supposed to be
+    visible from a virtual environment, `pyvenv.cfg` must contain the following
+    line:
+
+        include-system-site-packages = false
+
+    Additionally, log a warning if accessing the file fails.
+    """
+    cfg_lines = _get_pyvenv_cfg_lines()
+    if cfg_lines is None:
+        # We're not in a "sane" venv, so assume there is no system
+        # site-packages access (since that's PEP 405's default state).
+        logger.warning(
+            "Could not access 'pyvenv.cfg' despite a virtual environment "
+            "being active. Assuming global site-packages is not accessible "
+            "in this environment."
+        )
         return True
 
+    for line in cfg_lines:
+        match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line)
+        if match is not None and match.group('value') == 'false':
+            return True
     return False
 
 
-def virtualenv_no_global():
+def _no_global_under_regular_virtualenv():
     # type: () -> bool
+    """Check if "no-global-site-packages.txt" exists beside site.py
+
+    This mirrors logic in pypa/virtualenv for determining whether system
+    site-packages are visible in the virtual environment.
     """
-    Return True if in a venv and no system site packages.
-    """
-    # this mirrors the logic in virtualenv.py for locating the
-    # no-global-site-packages.txt file
     site_mod_dir = os.path.dirname(os.path.abspath(site.__file__))
-    no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt')
-    if running_under_virtualenv() and os.path.isfile(no_global_file):
-        return True
-    else:
-        return False
+    no_global_site_packages_file = os.path.join(
+        site_mod_dir, 'no-global-site-packages.txt',
+    )
+    return os.path.exists(no_global_site_packages_file)
+
+
+def virtualenv_no_global():
+    # type: () -> bool
+    """Returns a boolean, whether running in venv with no system site-packages.
+    """
+
+    if _running_under_regular_virtualenv():
+        return _no_global_under_regular_virtualenv()
+
+    if _running_under_venv():
+        return _no_global_under_venv()
+
+    return False


Test patch:
diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py
--- a/tests/functional/test_freeze.py
+++ b/tests/functional/test_freeze.py
@@ -703,6 +703,7 @@ def test_freeze_with_requirement_option_package_repeated_multi_file(script):
 
 
 @pytest.mark.network
+@pytest.mark.incompatible_with_test_venv
 def test_freeze_user(script, virtualenv, data):
     """
     Testing freeze with --user, first we have to install some stuff.
@@ -733,6 +734,7 @@ def test_freeze_path(tmpdir, script, data):
     _check_output(result.stdout, expected)
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_freeze_path_exclude_user(tmpdir, script, data):
     """
     Test freeze with --path and make sure packages from --user are not picked
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -124,6 +124,7 @@ def test_pep518_allows_missing_requires(script, data, common_wheels):
     assert result.files_created
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_pep518_with_user_pip(script, pip_src, data, common_wheels):
     """
     Check that build dependencies are installed into the build
@@ -1593,6 +1594,7 @@ def test_target_install_ignores_distutils_config_install_prefix(script):
     assert relative_script_base not in result.files_created
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_user_config_accepted(script):
     # user set in the config file is parsed as 0/1 instead of True/False.
     # Check that this doesn't cause a problem.
diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py
--- a/tests/functional/test_install_reqs.py
+++ b/tests/functional/test_install_reqs.py
@@ -227,6 +227,7 @@ def test_install_local_with_subdirectory(script):
     result.assert_installed('version_subpkg.py', editable=False)
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_wheel_user_with_prefix_in_pydistutils_cfg(
         script, data, with_wheel):
     if os.name == 'posix':
diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py
--- a/tests/functional/test_install_user.py
+++ b/tests/functional/test_install_user.py
@@ -27,6 +27,7 @@ def dist_in_site_packages(dist):
 class Tests_UserSite:
 
     @pytest.mark.network
+    @pytest.mark.incompatible_with_test_venv
     def test_reset_env_system_site_packages_usersite(self, script):
         """
         Check user site works as expected.
@@ -42,6 +43,7 @@ def test_reset_env_system_site_packages_usersite(self, script):
 
     @pytest.mark.network
     @need_svn
+    @pytest.mark.incompatible_with_test_venv
     def test_install_subversion_usersite_editable_with_distribute(
             self, script, tmpdir):
         """
@@ -55,6 +57,7 @@ def test_install_subversion_usersite_editable_with_distribute(
         )
         result.assert_installed('INITools', use_user_site=True)
 
+    @pytest.mark.incompatible_with_test_venv
     def test_install_from_current_directory_into_usersite(
             self, script, data, with_wheel):
         """
@@ -75,7 +78,6 @@ def test_install_from_current_directory_into_usersite(
         )
         assert dist_info_folder in result.files_created
 
-    @pytest.mark.incompatible_with_test_venv
     def test_install_user_venv_nositepkgs_fails(self, virtualenv,
                                                 script, data):
         """
@@ -96,6 +98,7 @@ def test_install_user_venv_nositepkgs_fails(self, virtualenv,
         )
 
     @pytest.mark.network
+    @pytest.mark.incompatible_with_test_venv
     def test_install_user_conflict_in_usersite(self, script):
         """
         Test user install with conflict in usersite updates usersite.
@@ -119,6 +122,7 @@ def test_install_user_conflict_in_usersite(self, script):
         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, script):
         """
         Test user install with conflict in global site ignores site and
@@ -149,6 +153,7 @@ def test_install_user_conflict_in_globalsite(self, virtualenv, script):
         assert isdir(initools_folder)
 
     @pytest.mark.network
+    @pytest.mark.incompatible_with_test_venv
     def test_upgrade_user_conflict_in_globalsite(self, virtualenv, script):
         """
         Test user install/upgrade with conflict in global site ignores site and
@@ -178,6 +183,7 @@ def test_upgrade_user_conflict_in_globalsite(self, virtualenv, script):
         assert isdir(initools_folder)
 
     @pytest.mark.network
+    @pytest.mark.incompatible_with_test_venv
     def test_install_user_conflict_in_globalsite_and_usersite(
             self, virtualenv, script):
         """
@@ -214,6 +220,7 @@ def test_install_user_conflict_in_globalsite_and_usersite(
         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):
         """
diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py
--- a/tests/functional/test_install_wheel.py
+++ b/tests/functional/test_install_wheel.py
@@ -233,6 +233,7 @@ def test_wheel_record_lines_in_deterministic_order(script, data):
     assert record_lines == sorted(record_lines)
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_install_user_wheel(script, data, with_wheel):
     """
     Test user install from wheel (that has a script)
diff --git a/tests/functional/test_list.py b/tests/functional/test_list.py
--- a/tests/functional/test_list.py
+++ b/tests/functional/test_list.py
@@ -94,6 +94,7 @@ def test_local_columns_flag(simple_script):
 
 
 @pytest.mark.network
+@pytest.mark.incompatible_with_test_venv
 def test_user_flag(script, data):
     """
     Test the behavior of --user flag in the list command
@@ -110,6 +111,7 @@ def test_user_flag(script, data):
 
 
 @pytest.mark.network
+@pytest.mark.incompatible_with_test_venv
 def test_user_columns_flag(script, data):
     """
     Test the behavior of --user --format=columns flags in the list command
@@ -502,6 +504,7 @@ def test_list_path(tmpdir, script, data):
     assert {'name': 'simple', 'version': '2.0'} in json_result
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_list_path_exclude_user(tmpdir, script, data):
     """
     Test list with --path and make sure packages from --user are not picked
diff --git a/tests/functional/test_uninstall_user.py b/tests/functional/test_uninstall_user.py
--- a/tests/functional/test_uninstall_user.py
+++ b/tests/functional/test_uninstall_user.py
@@ -9,6 +9,7 @@
 from tests.lib import assert_all_changes, pyversion
 
 
+@pytest.mark.incompatible_with_test_venv
 class Tests_UninstallUserSite:
 
     @pytest.mark.network
diff --git a/tests/unit/test_build_env.py b/tests/unit/test_build_env.py
--- a/tests/unit/test_build_env.py
+++ b/tests/unit/test_build_env.py
@@ -162,6 +162,7 @@ def test_build_env_overlay_prefix_has_priority(script):
     assert result.stdout.strip() == '2.0', str(result)
 
 
+@pytest.mark.incompatible_with_test_venv
 def test_build_env_isolation(script):
 
     # Create dummy `pkg` wheel.
diff --git a/tests/unit/test_utils_virtualenv.py b/tests/unit/test_utils_virtualenv.py
--- a/tests/unit/test_utils_virtualenv.py
+++ b/tests/unit/test_utils_virtualenv.py
@@ -1,3 +1,4 @@
+import logging
 import site
 import sys
 
@@ -32,20 +33,103 @@ def test_running_under_virtualenv(
 
 
 @pytest.mark.parametrize(
-    "running_under_virtualenv, no_global_file, expected", [
+    "under_virtualenv, no_global_file, expected", [
         (False, False, False),
         (False, True, False),
         (True, False, False),
         (True, True, True),
     ],
 )
-def test_virtualenv_no_global(
-        monkeypatch, tmpdir,
-        running_under_virtualenv, no_global_file, expected):
+def test_virtualenv_no_global_with_regular_virtualenv(
+    monkeypatch,
+    tmpdir,
+    under_virtualenv,
+    no_global_file,
+    expected,
+):
+    monkeypatch.setattr(virtualenv, '_running_under_venv', lambda: False)
+
     monkeypatch.setattr(site, '__file__', tmpdir / 'site.py')
     monkeypatch.setattr(
-        virtualenv, 'running_under_virtualenv',
-        lambda: running_under_virtualenv)
+        virtualenv, '_running_under_regular_virtualenv',
+        lambda: under_virtualenv,
+    )
     if no_global_file:
         (tmpdir / 'no-global-site-packages.txt').touch()
+
     assert virtualenv.virtualenv_no_global() == expected
+
+
+@pytest.mark.parametrize(
+    "pyvenv_cfg_lines, under_venv, expected, expect_warning", [
+        (None, False, False, False),
+        (None, True, True, True),  # this has a warning.
+        (
+            [
+                "home = <we do not care>",
+                "include-system-site-packages = true",
+                "version = <we do not care>",
+            ],
+            True,
+            False,
+            False,
+        ),
+        (
+            [
+                "home = <we do not care>",
+                "include-system-site-packages = false",
+                "version = <we do not care>",
+            ],
+            True,
+            True,
+            False,
+        ),
+    ],
+)
+def test_virtualenv_no_global_with_pep_405_virtual_environment(
+    monkeypatch,
+    caplog,
+    pyvenv_cfg_lines,
+    under_venv,
+    expected,
+    expect_warning,
+):
+    monkeypatch.setattr(
+        virtualenv, '_running_under_regular_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
+
+    if expect_warning:
+        assert caplog.records
+
+        # Check for basic information
+        message = caplog.records[-1].getMessage().lower()
+        assert "could not access 'pyvenv.cfg'" in message
+        assert "assuming global site-packages is not accessible" in message
+
+
+@pytest.mark.parametrize(
+    "contents, expected", [
+        (None, None),
+        ("", []),
+        ("a = b\nc = d\n", ["a = b", "c = d"]),
+        ("a = b\nc = d", ["a = b", "c = d"]),  # no trailing newlines
+    ]
+)
+def test_get_pyvenv_cfg_lines_for_pep_405_virtual_environment(
+    monkeypatch,
+    tmpdir,
+    contents,
+    expected,
+):
+    monkeypatch.setattr(sys, 'prefix', str(tmpdir))
+    if contents is not None:
+        tmpdir.joinpath('pyvenv.cfg').write_text(contents)
+
+    assert virtualenv._get_pyvenv_cfg_lines() == expected