Skip to content

Commit ad81f9a

Browse files
committed
always set PIP_USER and PIP_NO_DEPS to 0 #838
1 parent e8f877e commit ad81f9a

File tree

3 files changed

+59
-31
lines changed

3 files changed

+59
-31
lines changed

changelog/838.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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`

src/tox/venv.py

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import sys
77
import warnings
8+
from itertools import chain
89

910
import py
1011

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

299300
def run_install_command(self, packages, action, options=()):
300-
argv = self.envconfig.install_command[:]
301-
i = argv.index("{packages}")
302-
argv[i : i + 1] = packages
303-
if "{opts}" in argv:
304-
i = argv.index("{opts}")
305-
argv[i : i + 1] = list(options)
301+
def expand(val):
302+
# expand an install command
303+
if val == "{packages}":
304+
for package in packages:
305+
yield package
306+
elif val == "{opts}":
307+
for opt in options:
308+
yield opt
309+
else:
310+
yield val
306311

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

310-
if "PYTHONPATH" not in self.envconfig.passenv:
311-
# If PYTHONPATH not explicitly asked for, remove it.
312-
if "PYTHONPATH" in os.environ:
313-
self.session.report.warning(
314-
"Discarding $PYTHONPATH from environment, to override "
315-
"specify PYTHONPATH in 'passenv' in your configuration."
316-
)
317-
os.environ.pop("PYTHONPATH")
314+
self.ensure_pip_os_environ_ok()
318315

319316
old_stdout = sys.stdout
320317
sys.stdout = codecs.getwriter("utf8")(sys.stdout)
321318
try:
322319
self._pcall(
323-
argv,
320+
cmd,
324321
cwd=self.envconfig.config.toxinidir,
325322
action=action,
326323
redirect=self.session.report.verbosity < 2,
327324
)
328325
finally:
329326
sys.stdout = old_stdout
330327

328+
def ensure_pip_os_environ_ok(self):
329+
for key in ("PIP_RESPECT_VIRTUALENV", "PIP_REQUIRE_VIRTUALENV", "__PYVENV_LAUNCHER__"):
330+
os.environ.pop(key, None)
331+
if "PYTHONPATH" not in self.envconfig.passenv:
332+
# If PYTHONPATH not explicitly asked for, remove it.
333+
if "PYTHONPATH" in os.environ:
334+
self.session.report.warning(
335+
"Discarding $PYTHONPATH from environment, to override "
336+
"specify PYTHONPATH in 'passenv' in your configuration."
337+
)
338+
os.environ.pop("PYTHONPATH")
339+
340+
# installing packages at user level may mean we're not installing inside the venv
341+
os.environ["PIP_USER"] = "0"
342+
343+
# installing without dependencies may lead to broken packages
344+
os.environ["PIP_NO_DEPS"] = "0"
345+
331346
def _install(self, deps, extraopts=None, action=None):
332347
if not deps:
333348
return
@@ -353,13 +368,13 @@ def _install(self, deps, extraopts=None, action=None):
353368
options.extend(extraopts)
354369
self.run_install_command(packages=packages, options=options, action=action)
355370

356-
def _getenv(self, testcommand=False):
357-
if testcommand:
371+
def _get_os_environ(self, is_test_command=False):
372+
if is_test_command:
358373
# for executing tests we construct a clean environment
359374
env = {}
360-
for envname in self.envconfig.passenv:
361-
if envname in os.environ:
362-
env[envname] = os.environ[envname]
375+
for env_key in self.envconfig.passenv:
376+
if env_key in os.environ:
377+
env[env_key] = os.environ[env_key]
363378
else:
364379
# for executing non-test commands we use the full
365380
# invocation environment
@@ -377,7 +392,7 @@ def test(self, redirect=False):
377392
self.session.make_emptydir(self.envconfig.envtmpdir)
378393
self.envconfig.envtmpdir.ensure(dir=1)
379394
cwd = self.envconfig.changedir
380-
env = self._getenv(testcommand=True)
395+
env = self._get_os_environ(is_test_command=True)
381396
# Display PYTHONHASHSEED to assist with reproducibility.
382397
action.setactivity("runtests", "PYTHONHASHSEED={!r}".format(env.get("PYTHONHASHSEED")))
383398
for i, argv in enumerate(self.envconfig.commands):
@@ -405,7 +420,7 @@ def test(self, redirect=False):
405420
action=action,
406421
redirect=redirect,
407422
ignore_ret=ignore_ret,
408-
testcommand=True,
423+
is_test_command=True,
409424
)
410425
except tox.exception.InvocationError as err:
411426
if self.envconfig.ignore_outcome:
@@ -424,18 +439,28 @@ def test(self, redirect=False):
424439
raise
425440

426441
def _pcall(
427-
self, args, cwd, venv=True, testcommand=False, action=None, redirect=True, ignore_ret=False
442+
self,
443+
args,
444+
cwd,
445+
venv=True,
446+
is_test_command=False,
447+
action=None,
448+
redirect=True,
449+
ignore_ret=False,
428450
):
451+
# construct environment variables
429452
os.environ.pop("VIRTUALENV_PYTHON", None)
453+
env = self._get_os_environ(is_test_command=is_test_command)
454+
bin_dir = str(self.envconfig.envbindir)
455+
env["PATH"] = os.pathsep.join([bin_dir, os.environ["PATH"]])
456+
self.session.report.verbosity2("setting PATH={}".format(env["PATH"]))
430457

431-
cwd.ensure(dir=1)
458+
# get command
432459
args[0] = self.getcommandpath(args[0], venv, cwd)
433460
if sys.platform != "win32" and "TOX_LIMITED_SHEBANG" in os.environ:
434461
args = prepend_shebang_interpreter(args)
435-
env = self._getenv(testcommand=testcommand)
436-
bindir = str(self.envconfig.envbindir)
437-
env["PATH"] = p = os.pathsep.join([bindir, os.environ["PATH"]])
438-
self.session.report.verbosity2("setting PATH={}".format(p))
462+
463+
cwd.ensure(dir=1) # ensure the cwd exists
439464
return action.popen(args, cwd=cwd, env=env, redirect=redirect, ignore_ret=ignore_ret)
440465

441466

tests/unit/test_venv.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ def test_envbindir_path(self, newmocksession, monkeypatch):
625625
assert "PIP_RESPECT_VIRTUALENV" not in os.environ
626626
assert "PIP_REQUIRE_VIRTUALENV" not in os.environ
627627
assert "__PYVENV_LAUNCHER__" not in os.environ
628+
assert os.environ["PIP_USER"] == "0"
629+
assert os.environ["PIP_NO_DEPS"] == "0"
628630

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

0 commit comments

Comments
 (0)