Skip to content

Commit

Permalink
always set PIP_USER and PIP_NO_DEPS to 0 #838
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat committed Sep 16, 2018
1 parent e8f877e commit ad81f9a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 31 deletions.
1 change: 1 addition & 0 deletions changelog/838.feature.rst
@@ -0,0 +1 @@
always set ``PIP_USER=0`` (do not install into the user site package, but inside the virtual environment created) and ``PIP_NO_DEPS=0`` (installing without dependencies can cause broken package installations) inside tox - by :user:`gaborbernat`
87 changes: 56 additions & 31 deletions src/tox/venv.py
Expand Up @@ -5,6 +5,7 @@
import re
import sys
import warnings
from itertools import chain

import py

Expand Down Expand Up @@ -231,7 +232,7 @@ def _needs_reinstall(self, setupdir, action):
setup_py = setupdir.join("setup.py")
setup_cfg = setupdir.join("setup.cfg")
args = [self.envconfig.envpython, str(setup_py), "--name"]
env = self._getenv()
env = self._get_os_environ()
output = action.popen(args, cwd=setupdir, redirect=False, returnout=True, env=env)
name = output.strip()
args = [self.envconfig.envpython, "-c", "import sys; print(sys.path)"]
Expand Down Expand Up @@ -297,37 +298,51 @@ def _installopts(self, indexserver):
return options

def run_install_command(self, packages, action, options=()):
argv = self.envconfig.install_command[:]
i = argv.index("{packages}")
argv[i : i + 1] = packages
if "{opts}" in argv:
i = argv.index("{opts}")
argv[i : i + 1] = list(options)
def expand(val):
# expand an install command
if val == "{packages}":
for package in packages:
yield package
elif val == "{opts}":
for opt in options:
yield opt
else:
yield val

for x in ("PIP_RESPECT_VIRTUALENV", "PIP_REQUIRE_VIRTUALENV", "__PYVENV_LAUNCHER__"):
os.environ.pop(x, None)
cmd = list(chain.from_iterable(expand(val) for val in self.envconfig.install_command))

if "PYTHONPATH" not in self.envconfig.passenv:
# If PYTHONPATH not explicitly asked for, remove it.
if "PYTHONPATH" in os.environ:
self.session.report.warning(
"Discarding $PYTHONPATH from environment, to override "
"specify PYTHONPATH in 'passenv' in your configuration."
)
os.environ.pop("PYTHONPATH")
self.ensure_pip_os_environ_ok()

old_stdout = sys.stdout
sys.stdout = codecs.getwriter("utf8")(sys.stdout)
try:
self._pcall(
argv,
cmd,
cwd=self.envconfig.config.toxinidir,
action=action,
redirect=self.session.report.verbosity < 2,
)
finally:
sys.stdout = old_stdout

def ensure_pip_os_environ_ok(self):
for key in ("PIP_RESPECT_VIRTUALENV", "PIP_REQUIRE_VIRTUALENV", "__PYVENV_LAUNCHER__"):
os.environ.pop(key, None)
if "PYTHONPATH" not in self.envconfig.passenv:
# If PYTHONPATH not explicitly asked for, remove it.
if "PYTHONPATH" in os.environ:
self.session.report.warning(
"Discarding $PYTHONPATH from environment, to override "
"specify PYTHONPATH in 'passenv' in your configuration."
)
os.environ.pop("PYTHONPATH")

# installing packages at user level may mean we're not installing inside the venv
os.environ["PIP_USER"] = "0"

# installing without dependencies may lead to broken packages
os.environ["PIP_NO_DEPS"] = "0"

def _install(self, deps, extraopts=None, action=None):
if not deps:
return
Expand All @@ -353,13 +368,13 @@ def _install(self, deps, extraopts=None, action=None):
options.extend(extraopts)
self.run_install_command(packages=packages, options=options, action=action)

def _getenv(self, testcommand=False):
if testcommand:
def _get_os_environ(self, is_test_command=False):
if is_test_command:
# for executing tests we construct a clean environment
env = {}
for envname in self.envconfig.passenv:
if envname in os.environ:
env[envname] = os.environ[envname]
for env_key in self.envconfig.passenv:
if env_key in os.environ:
env[env_key] = os.environ[env_key]
else:
# for executing non-test commands we use the full
# invocation environment
Expand All @@ -377,7 +392,7 @@ def test(self, redirect=False):
self.session.make_emptydir(self.envconfig.envtmpdir)
self.envconfig.envtmpdir.ensure(dir=1)
cwd = self.envconfig.changedir
env = self._getenv(testcommand=True)
env = self._get_os_environ(is_test_command=True)
# Display PYTHONHASHSEED to assist with reproducibility.
action.setactivity("runtests", "PYTHONHASHSEED={!r}".format(env.get("PYTHONHASHSEED")))
for i, argv in enumerate(self.envconfig.commands):
Expand Down Expand Up @@ -405,7 +420,7 @@ def test(self, redirect=False):
action=action,
redirect=redirect,
ignore_ret=ignore_ret,
testcommand=True,
is_test_command=True,
)
except tox.exception.InvocationError as err:
if self.envconfig.ignore_outcome:
Expand All @@ -424,18 +439,28 @@ def test(self, redirect=False):
raise

def _pcall(
self, args, cwd, venv=True, testcommand=False, action=None, redirect=True, ignore_ret=False
self,
args,
cwd,
venv=True,
is_test_command=False,
action=None,
redirect=True,
ignore_ret=False,
):
# construct environment variables
os.environ.pop("VIRTUALENV_PYTHON", None)
env = self._get_os_environ(is_test_command=is_test_command)
bin_dir = str(self.envconfig.envbindir)
env["PATH"] = os.pathsep.join([bin_dir, os.environ["PATH"]])
self.session.report.verbosity2("setting PATH={}".format(env["PATH"]))

cwd.ensure(dir=1)
# get command
args[0] = self.getcommandpath(args[0], venv, cwd)
if sys.platform != "win32" and "TOX_LIMITED_SHEBANG" in os.environ:
args = prepend_shebang_interpreter(args)
env = self._getenv(testcommand=testcommand)
bindir = str(self.envconfig.envbindir)
env["PATH"] = p = os.pathsep.join([bindir, os.environ["PATH"]])
self.session.report.verbosity2("setting PATH={}".format(p))

cwd.ensure(dir=1) # ensure the cwd exists
return action.popen(args, cwd=cwd, env=env, redirect=redirect, ignore_ret=ignore_ret)


Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_venv.py
Expand Up @@ -625,6 +625,8 @@ def test_envbindir_path(self, newmocksession, monkeypatch):
assert "PIP_RESPECT_VIRTUALENV" not in os.environ
assert "PIP_REQUIRE_VIRTUALENV" not in os.environ
assert "__PYVENV_LAUNCHER__" not in os.environ
assert os.environ["PIP_USER"] == "0"
assert os.environ["PIP_NO_DEPS"] == "0"

def test_pythonpath_usage(self, newmocksession, monkeypatch):
monkeypatch.setenv("PYTHONPATH", "/my/awesome/library")
Expand Down

0 comments on commit ad81f9a

Please sign in to comment.