]> git.lyx.org Git - lyx.git/blob - src/support/os.cpp
Fix Python detection on windows
[lyx.git] / src / support / os.cpp
1 /**
2  * \file os.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Ruurd A. Reitsma
7  * \author Enrico Forestieri
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "support/convert.h"
15 #include "support/debug.h"
16 #include "support/filetools.h"
17 #include "support/qstring_helpers.h"
18 #include "support/regex.h"
19
20 #include <QDir>
21
22 #if defined(__CYGWIN__)
23 #include "support/os_cygwin.cpp"
24 #elif defined(_WIN32)
25 #include "support/os_win32.cpp"
26 #else
27 #include "support/os_unix.cpp"
28 #endif
29
30 // Static assert to break compilation on platforms where
31 // int/unsigned int is not 4 bytes. Added to make sure that
32 // e.g., the author hash is always 32-bit.
33 template<bool Condition> struct static_assert_helper;
34 template <> struct static_assert_helper<true> {};
35 enum {
36         dummy = sizeof(static_assert_helper<sizeof(int) == 4>)
37 };
38
39 namespace lyx {
40 namespace support {
41 namespace os {
42
43 int timeout_min()
44 {
45         return 3;
46 }
47
48
49 static string const python23_call(string const & binary, bool verbose = false)
50 {
51         const string version_info = " -c \"from __future__ import print_function;import sys; print(sys.version_info[:2], end=\\\"\\\")\"";
52         // Default to "python" if no binary is given.
53         if (binary.empty())
54                 return "python -tt";
55
56         if (verbose)
57                 lyxerr << "Examining " << binary << "\n";
58         // Check whether this is a python 2 or 3 binary.
59         cmd_ret const out = runCommand(binary + version_info);
60
61         smatch sm;
62         try {
63                 static regex const python_reg("\\((\\d*), (\\d*)\\)");
64                 if (out.first < 0 || !regex_match(out.second, sm, python_reg))
65                         return string();
66         } catch(regex_error const & /*e*/) {
67                 LYXERR0("Regex error! This should not happen.");
68                 return string();
69         }
70
71         int major = convert<int>(sm.str(1));
72         int minor = convert<int>(sm.str(2));
73         if((major == 2 && minor < 7) || (major == 3 && minor < 5))
74                 return string();
75
76         if (verbose)
77                 lyxerr << "Found Python " << out.second << "\n";
78         // Add the -tt switch so that mixed tab/whitespace
79         // indentation is an error
80         return binary + " -tt";
81 }
82
83
84 static string const find_python_binary()
85 {
86         // This function takes inspiration from PEP 394 and PEP 397
87         // PEP 394 -- The "python" Command on Unix-Like Systems
88         // https://www.python.org/dev/peps/pep-0394/
89         // PEP 397 -- Python launcher for Windows
90         // https://www.python.org/dev/peps/pep-0397/
91
92 #ifdef _WIN32
93         // Check through python launcher whether python 3 is
94         // installed on computer.
95         string command = python23_call("py -3");
96 #else
97         // Check whether python3 in PATH is the right one.
98         string command = python23_call("python3");
99 #endif // _WIN32
100         if (!command.empty())
101                 return command;
102
103 #ifndef _WIN32
104         // python3 does not exists, let us try to find python3.x in PATH
105         // the search is probably broader than required
106         // but we are trying hard to find a valid python binary
107         vector<string> const path = getEnvPath("PATH");
108         lyxerr << "Looking for python 3.x ...\n";
109         for (auto bin : path) {
110                 QString const dir = toqstr(bin);
111                 string const localdir = dir.toLocal8Bit().constData();
112                 QDir qdir(dir);
113                 qdir.setFilter(QDir::Files | QDir::Executable);
114                 QStringList list = qdir.entryList(QStringList("python3*"));
115                 for (auto bin2 : list) {
116                         string const binary = "\"" + addName(localdir,
117                                 bin2.toLocal8Bit().constData()) + "\"";
118                         command = python23_call(binary, true);
119                         if (!command.empty())
120                                 return command;
121                 }
122         }
123 #endif // !_WIN32
124
125         // python 3 was not found let us look for python 2
126 #ifdef _WIN32
127         command = python23_call("py -2");
128 #else
129         command = python23_call("python2");
130 #endif // _WIN32
131         if (!command.empty())
132                 return command;
133
134 #ifdef _WIN32
135         // python launcher is not installed, let cmd auto check 
136         // PATH for a python.exe
137         command = python23_call("python");
138         if (!command.empty())
139                 return command;
140
141         //failed, prepare to search PATH manually
142         vector<string> const path = getEnvPath("PATH");
143         lyxerr << "Manually looking for python in PATH ...\n";
144         QString const exeName = "python*";
145 #else
146         // python2 does not exists, let us try to find python2.x in PATH
147         // the search is probably broader than required
148         // but we are trying hard to find a valid python binary
149         lyxerr << "Looking for python 2.x ...\n";
150         QString const exeName = "python2*";
151 #endif // _WIN32
152
153         for (auto bin : path) {
154                 QString const dir = toqstr(bin);
155                 string const localdir = dir.toLocal8Bit().constData();
156                 QDir qdir(dir);
157                 qdir.setFilter(QDir::Files | QDir::Executable);
158                 QStringList list = qdir.entryList(QStringList(exeName));
159                 for (auto bin2 : list) {
160                         string const binary = "\"" + addName(localdir,
161                                 bin2.toLocal8Bit().constData()) + "\"";
162                         command = python23_call(binary, true);
163                         if (!command.empty())
164                                 return command;
165                 }
166         }
167
168         // If this happens all hope is lost that this is a sane system
169         lyxerr << "Warning: No python v2.x or 3.x binary found.\n";
170         return python23_call("");
171 }
172
173
174 string const python(bool reset)
175 {
176         static string command = find_python_binary();
177         // FIXME THREAD
178         if (reset) {
179                 command = find_python_binary();
180         }
181         return command;
182 }
183
184 } // namespace os
185 } // namespace support
186 } // namespace lyx