1from __future__ import unicode_literals
2
3import json
4import sys
5
6import tox
7from tox import reporter
8from tox.constants import SITE_PACKAGE_QUERY_SCRIPT, VERSION_QUERY_SCRIPT
9
10
11class Interpreters:
12    def __init__(self, hook):
13        self.name2executable = {}
14        self.executable2info = {}
15        self.hook = hook
16
17    def get_executable(self, envconfig):
18        """ return path object to the executable for the given
19        name (e.g. python2.7, python3.6, python etc.)
20        if name is already an existing path, return name.
21        If an interpreter cannot be found, return None.
22        """
23        try:
24            return self.name2executable[envconfig.envname]
25        except KeyError:
26            exe = self.hook.tox_get_python_executable(envconfig=envconfig)
27            reporter.verbosity2("{} uses {}".format(envconfig.envname, exe))
28            self.name2executable[envconfig.envname] = exe
29            return exe
30
31    def get_info(self, envconfig):
32        executable = self.get_executable(envconfig)
33        name = envconfig.basepython
34        if not executable:
35            return NoInterpreterInfo(name=name)
36        try:
37            return self.executable2info[executable]
38        except KeyError:
39            info = run_and_get_interpreter_info(name, executable)
40            self.executable2info[executable] = info
41            return info
42
43    def get_sitepackagesdir(self, info, envdir):
44        if not info.executable:
45            return ""
46        envdir = str(envdir)
47        try:
48            res = exec_on_interpreter(str(info.executable), SITE_PACKAGE_QUERY_SCRIPT, str(envdir))
49        except ExecFailed as e:
50            reporter.verbosity1("execution failed: {} -- {}".format(e.out, e.err))
51            return ""
52        else:
53            return res["dir"]
54
55
56def run_and_get_interpreter_info(name, executable):
57    assert executable
58    try:
59        result = exec_on_interpreter(str(executable), VERSION_QUERY_SCRIPT)
60        result["version_info"] = tuple(result["version_info"])  # fix json dump transformation
61        del result["name"]
62        del result["version"]
63        result["executable"] = str(executable)
64    except ExecFailed as e:
65        return NoInterpreterInfo(name, executable=e.executable, out=e.out, err=e.err)
66    else:
67        return InterpreterInfo(name, **result)
68
69
70def exec_on_interpreter(*args):
71    from subprocess import Popen, PIPE
72
73    popen = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True)
74    out, err = popen.communicate()
75    if popen.returncode:
76        raise ExecFailed(args[0], args[1:], out, err)
77    if err:
78        sys.stderr.write(err)
79    try:
80        result = json.loads(out)
81    except Exception:
82        raise ExecFailed(args[0], args[1:], out, "could not decode {!r}".format(out))
83    return result
84
85
86class ExecFailed(Exception):
87    def __init__(self, executable, source, out, err):
88        self.executable = executable
89        self.source = source
90        self.out = out
91        self.err = err
92
93
94class InterpreterInfo:
95    def __init__(self, name, executable, version_info, sysplatform, is_64):
96        self.name = name
97        self.executable = executable
98        self.version_info = version_info
99        self.sysplatform = sysplatform
100        self.is_64 = is_64
101
102    def __str__(self):
103        return "<executable at {}, version_info {}>".format(self.executable, self.version_info)
104
105
106class NoInterpreterInfo:
107    def __init__(self, name, executable=None, out=None, err="not found"):
108        self.name = name
109        self.executable = executable
110        self.version_info = None
111        self.out = out
112        self.err = err
113
114    def __str__(self):
115        if self.executable:
116            return "<executable at {}, not runnable>".format(self.executable)
117        else:
118            return "<executable not found for: {}>".format(self.name)
119
120
121if tox.INFO.IS_WIN:
122    from .windows import tox_get_python_executable
123else:
124    from .unix import tox_get_python_executable
125assert tox_get_python_executable
126