mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-16 17:39:58 +00:00
Python Startup 1 of 2
.. look for python in path and check version and module path, then use this when initialising to ensure the installed modules are used, not the local copy. .. needs testing on Windows and need to enable the user to specify the location of Python so users don't need to modify PATH.
This commit is contained in:
@@ -20,10 +20,12 @@
|
||||
*/
|
||||
|
||||
#include "PythonEmbed.h"
|
||||
#include "Utils.h"
|
||||
#include "Settings.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
|
||||
// Python header - using a C preprocessor hack
|
||||
// GC_PYTHON_INCDIR is from gcconfig.pri, typically python3.6
|
||||
@@ -45,85 +47,184 @@ PythonEmbed::~PythonEmbed()
|
||||
{
|
||||
}
|
||||
|
||||
bool PythonEmbed::pythonInstalled(QString &pyhome, QString &pypath)
|
||||
{
|
||||
// where to check
|
||||
QString path = QProcessEnvironment::systemEnvironment().value("PATH", "");
|
||||
|
||||
// what is is typically installed as ?
|
||||
QStringList binarynames;
|
||||
binarynames << "python3" << "python";
|
||||
|
||||
// what we found
|
||||
QStringList installnames;
|
||||
|
||||
// lets search
|
||||
foreach(QString name, binarynames) {
|
||||
installnames = Utils::searchPath(path, name, true);
|
||||
if (installnames.count() >0) break;
|
||||
}
|
||||
|
||||
// if we failed, its not installed
|
||||
if (installnames.count()==0) return false;
|
||||
|
||||
// lets just use the first one we found
|
||||
QString pythonbinary = installnames[0];
|
||||
pyhome=pythonbinary;
|
||||
|
||||
// get the version and path via an interaction
|
||||
QProcess py;
|
||||
py.setProgram(pythonbinary);
|
||||
|
||||
// set the arguments
|
||||
QStringList args;
|
||||
args << "-c";
|
||||
args << QString("import sys\n"
|
||||
"print('ZZ',sys.version_info.major,'ZZ')\n"
|
||||
"print('ZZ', '%1'.join(sys.path), 'ZZ')\n"
|
||||
"quit()\n").arg(PATHSEP);
|
||||
py.setArguments(args);
|
||||
py.start();
|
||||
|
||||
// failed to start python
|
||||
if (py.waitForStarted(500) == false) {
|
||||
fprintf(stderr, "Failed to start: %s\n", pythonbinary.toStdString().c_str());
|
||||
py.terminate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for output, should be rapid
|
||||
if (py.waitForReadyRead(2000)==false) {
|
||||
fprintf(stderr, "Didn't get output: %s\n", pythonbinary.toStdString().c_str());
|
||||
py.terminate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// get output
|
||||
QString output = py.readAll();
|
||||
|
||||
// close if it didn't already
|
||||
if (py.waitForFinished(500)==false) {
|
||||
fprintf(stderr, "forced terminate of %s\n", pythonbinary.toStdString().c_str());
|
||||
py.terminate();
|
||||
}
|
||||
|
||||
// scan output
|
||||
QRegExp contents("^ZZ(.*)ZZ\nZZ(.*)ZZ\n$");
|
||||
if (contents.exactMatch(output)) {
|
||||
QString vmajor=contents.cap(1);
|
||||
QString path=contents.cap(2);
|
||||
|
||||
// check its version 3
|
||||
if (vmajor.toInt() != 3) {
|
||||
fprintf(stderr, "%s is not version 3, it's version %d\n", pythonbinary.toStdString().c_str(), vmajor.toInt());
|
||||
return false;
|
||||
}
|
||||
|
||||
// now get python path
|
||||
pypath = path;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
// didn't understand !
|
||||
fprintf(stderr, "Python output doesn't parse: %s\n", output.toStdString().c_str());
|
||||
}
|
||||
|
||||
// by default we return false (pessimistic)
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonEmbed::PythonEmbed(const bool verbose, const bool interactive) : verbose(verbose), interactive(interactive)
|
||||
{
|
||||
loaded = false;
|
||||
threadid=-1;
|
||||
name = QString("GoldenCheetah");
|
||||
|
||||
// tell python our program name
|
||||
Py_SetProgramName((wchar_t*) name.toStdString().c_str());
|
||||
// is python3 installed?
|
||||
if (pythonInstalled(pyhome, pypath)) {
|
||||
|
||||
// our own module
|
||||
PyImport_AppendInittab("goldencheetah", PyInit_goldencheetah);
|
||||
// tell python our program name
|
||||
Py_SetProgramName((wchar_t*) name.toStdString().c_str());
|
||||
|
||||
// need to load the interpreter etc
|
||||
Py_InitializeEx(0);
|
||||
// our own module
|
||||
PyImport_AppendInittab("goldencheetah", PyInit_goldencheetah);
|
||||
|
||||
// set the module path in the same way the interpreter would
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
// need to load the interpreter etc
|
||||
Py_InitializeEx(0);
|
||||
|
||||
// did module import fail (python not installed)
|
||||
if (sys == NULL) {
|
||||
// set path - allocate storage for it...
|
||||
wchar_t *here = new wchar_t(pypath.length()+1);
|
||||
pypath.toWCharArray(here);
|
||||
here[pypath.length()]=0;
|
||||
PySys_SetPath(here);
|
||||
|
||||
// abort embedding
|
||||
QMessageBox msg(QMessageBox::Information, QObject::tr("Python not installed"),
|
||||
QObject::tr("Python v3.6 or higher is required for Python.\nPython disabled in preferences."));
|
||||
appsettings->setValue(GC_EMBED_PYTHON, false);
|
||||
loaded=false;
|
||||
msg.exec();
|
||||
return;
|
||||
}
|
||||
// set the module path in the same way the interpreter would
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
|
||||
PyObject *path = PyObject_GetAttrString(sys, "path");
|
||||
PyList_Append(path, PyUnicode_FromString("."));
|
||||
// did module import fail (python not installed properly?)
|
||||
if (sys != NULL) {
|
||||
|
||||
// get version
|
||||
version = QString(Py_GetVersion());
|
||||
version.replace("\n", " ");
|
||||
//PyObject *path = PyObject_GetAttrString(sys, "path");
|
||||
//PyList_Append(path, PyUnicode_FromString("."));
|
||||
|
||||
fprintf(stderr, "Python loaded [%s]\n", version.toStdString().c_str());
|
||||
// get version
|
||||
version = QString(Py_GetVersion());
|
||||
version.replace("\n", " ");
|
||||
|
||||
// our base code - traps stdout and loads goldencheetan module
|
||||
// mapping all the bindings to a GC object.
|
||||
std::string stdOutErr = ("import sys\n"
|
||||
fprintf(stderr, "Python loaded [%s]\n", version.toStdString().c_str());
|
||||
|
||||
// our base code - traps stdout and loads goldencheetan module
|
||||
// mapping all the bindings to a GC object.
|
||||
std::string stdOutErr = ("import sys\n"
|
||||
#ifdef Q_OS_LINUX
|
||||
"import os\n"
|
||||
"sys.setdlopenflags(os.RTLD_NOW | os.RTLD_DEEPBIND)\n"
|
||||
"import os\n"
|
||||
"sys.setdlopenflags(os.RTLD_NOW | os.RTLD_DEEPBIND)\n"
|
||||
#endif
|
||||
"class CatchOutErr:\n"
|
||||
" def __init__(self):\n"
|
||||
" self.value = ''\n"
|
||||
" def write(self, txt):\n"
|
||||
" self.value += txt\n"
|
||||
"catchOutErr = CatchOutErr()\n"
|
||||
"sys.stdout = catchOutErr\n"
|
||||
"sys.stderr = catchOutErr\n"
|
||||
"import goldencheetah\n"
|
||||
"GC=goldencheetah.Bindings()\n");
|
||||
"class CatchOutErr:\n"
|
||||
" def __init__(self):\n"
|
||||
" self.value = ''\n"
|
||||
" def write(self, txt):\n"
|
||||
" self.value += txt\n"
|
||||
"catchOutErr = CatchOutErr()\n"
|
||||
"sys.stdout = catchOutErr\n"
|
||||
"sys.stderr = catchOutErr\n"
|
||||
"import goldencheetah\n"
|
||||
"GC=goldencheetah.Bindings()\n");
|
||||
|
||||
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
|
||||
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
|
||||
|
||||
// now load the library
|
||||
QFile lib(":python/library.py");
|
||||
if (lib.open(QFile::ReadOnly)) {
|
||||
QString libstring=lib.readAll();
|
||||
lib.close();
|
||||
PyRun_SimpleString(libstring.toLatin1().constData());
|
||||
}
|
||||
// now load the library
|
||||
QFile lib(":python/library.py");
|
||||
if (lib.open(QFile::ReadOnly)) {
|
||||
QString libstring=lib.readAll();
|
||||
lib.close();
|
||||
PyRun_SimpleString(libstring.toLatin1().constData());
|
||||
}
|
||||
|
||||
|
||||
// setup trapping of output
|
||||
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
|
||||
catcher = static_cast<void*>(PyObject_GetAttrString(pModule,"catchOutErr"));
|
||||
clear = static_cast<void*>(PyObject_GetAttrString(static_cast<PyObject*>(catcher), "__init__"));
|
||||
PyErr_Print(); //make python print any errors
|
||||
PyErr_Clear(); //and clear them !
|
||||
// setup trapping of output
|
||||
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
|
||||
catcher = static_cast<void*>(PyObject_GetAttrString(pModule,"catchOutErr"));
|
||||
clear = static_cast<void*>(PyObject_GetAttrString(static_cast<PyObject*>(catcher), "__init__"));
|
||||
PyErr_Print(); //make python print any errors
|
||||
PyErr_Clear(); //and clear them !
|
||||
|
||||
// prepare for threaded processing
|
||||
PyEval_InitThreads();
|
||||
mainThreadState = PyEval_SaveThread();
|
||||
loaded = true;
|
||||
// prepare for threaded processing
|
||||
PyEval_InitThreads();
|
||||
mainThreadState = PyEval_SaveThread();
|
||||
loaded = true;
|
||||
return;
|
||||
} // sys != NULL
|
||||
} // pythonInstalled == true
|
||||
|
||||
// if we get here loading failed
|
||||
QMessageBox msg(QMessageBox::Information, QObject::tr("Python not installed or in path"),
|
||||
QObject::tr("Python v3.6 or higher is required for Python.\nPython disabled in preferences."));
|
||||
appsettings->setValue(GC_EMBED_PYTHON, false);
|
||||
loaded=false;
|
||||
msg.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
// run on called thread
|
||||
|
||||
Reference in New Issue
Block a user