1import os
2import sys
3import subprocess
4import unicodedata
5from subprocess import Popen as _Popen, PIPE as _PIPE
6
7import jaraco.envs
8
9
10class VirtualEnv(jaraco.envs.VirtualEnv):
11    name = '.env'
12    # Some version of PyPy will import distutils on startup, implicitly
13    # importing setuptools, and thus leading to BackendInvalid errors
14    # when upgrading Setuptools. Bypass this behavior by avoiding the
15    # early availability and need to upgrade.
16    create_opts = ['--no-setuptools']
17
18    def run(self, cmd, *args, **kwargs):
19        cmd = [self.exe(cmd[0])] + cmd[1:]
20        kwargs = {"cwd": self.root, **kwargs}  # Allow overriding
21        # In some environments (eg. downstream distro packaging), where:
22        # - tox isn't used to run tests and
23        # - PYTHONPATH is set to point to a specific setuptools codebase and
24        # - no custom env is explicitly set by a test
25        # PYTHONPATH will leak into the spawned processes.
26        # In that case tests look for module in the wrong place (on PYTHONPATH).
27        # Unless the test sets its own special env, pass a copy of the existing
28        # environment with removed PYTHONPATH to the subprocesses.
29        if "env" not in kwargs:
30            env = dict(os.environ)
31            if "PYTHONPATH" in env:
32                del env["PYTHONPATH"]
33            kwargs["env"] = env
34        return subprocess.check_output(cmd, *args, **kwargs)
35
36
37def _which_dirs(cmd):
38    result = set()
39    for path in os.environ.get('PATH', '').split(os.pathsep):
40        filename = os.path.join(path, cmd)
41        if os.access(filename, os.X_OK):
42            result.add(path)
43    return result
44
45
46def run_setup_py(cmd, pypath=None, path=None,
47                 data_stream=0, env=None):
48    """
49    Execution command for tests, separate from those used by the
50    code directly to prevent accidental behavior issues
51    """
52    if env is None:
53        env = dict()
54        for envname in os.environ:
55            env[envname] = os.environ[envname]
56
57    # override the python path if needed
58    if pypath is not None:
59        env["PYTHONPATH"] = pypath
60
61    # override the execution path if needed
62    if path is not None:
63        env["PATH"] = path
64    if not env.get("PATH", ""):
65        env["PATH"] = _which_dirs("tar").union(_which_dirs("gzip"))
66        env["PATH"] = os.pathsep.join(env["PATH"])
67
68    cmd = [sys.executable, "setup.py"] + list(cmd)
69
70    # http://bugs.python.org/issue8557
71    shell = sys.platform == 'win32'
72
73    try:
74        proc = _Popen(
75            cmd, stdout=_PIPE, stderr=_PIPE, shell=shell, env=env,
76        )
77
78        if isinstance(data_stream, tuple):
79            data_stream = slice(*data_stream)
80        data = proc.communicate()[data_stream]
81    except OSError:
82        return 1, ''
83
84    # decode the console string if needed
85    if hasattr(data, "decode"):
86        # use the default encoding
87        data = data.decode()
88        data = unicodedata.normalize('NFC', data)
89
90    # communicate calls wait()
91    return proc.returncode, data
92