On Wed, Jan 25, 2017 at 2:57 PM Dave Page <dp...@pgadmin.org> wrote: > One where the virtualenv is in a path that's not related to the > location of the runtime. If I'm reading the code correctly, you're > taking the appDir (the location of the runtime executable) and > appending a platform-specific path to the virtual env (e.g. on Linux, > "/../venv/bin/"), and expecting to find the Python executable in the > resulting directory. > > However, in a dev environment, we may have the venv in > ~/.virtualenvs/pgadmin4 and the runtime in > ~/git/pgadmin4/runtime/<builddir> for example. In an RPM based > installation, there is no virtualenv - the Python code is installed in > the system site-packages directory, and the runtime in /usr/bin (which > may or may not be where python is found - assuming it's called that, > and not python3). > > > Granted, "/python" is nonsense and should probably just be "python". > Also, I > > think PATH doesn't need to be touched at all anymore, so the lines > involving > > pathEnv can be removed. >
I've now implemented this (see attached updated patch) and Python will configure itself to match whatever usable "python" it finds first in the PATH, including any activated virtualenv. I think the RPM setup will still work properly, as the non-virtual environment with all system packages is the fallback case. > > > > How about a settings key to force the program name? That would allow > > choosing any other virtualenv to run with. > > That might be a better option, and is basically how (for example) > Pycharms does it. In that case I'd argue that we should search > relative known locations first (such as the installers on Windows/Mac > would lay down), and then fall back to the configured Python > executable. That would allow installed copies to remain independent of > development copies. The only possible wrinkle would be whether a > relocatable installation such as the aforementioned installers would > be able to find their environments based purely on the executable > name. > Wouldn't the installed Python on Windows or OSX be caught be the PATH search Python does, too? Unfortunately I can't test either.
From 14cd1000f23e5d450380efb51926132eba64f26e Mon Sep 17 00:00:00 2001 From: "Jan Alexander Steffens (heftig)" <jan.steff...@gmail.com> Date: Fri, 6 Jan 2017 12:11:35 +0100 Subject: [PATCH] Simplify Server's python setup If the program name is set to match the venv's interpreter executable, Py_Initialize will set up sys.path to match the virtualenv. Use PySys_SetArgvEx to avoid the path getting modified further. Tested on Arch Linux using Python 2.7.13 and Python 3.6. --- runtime/Server.cpp | 221 ++++++++++++----------------------------------------- runtime/Server.h | 6 +- 2 files changed, 52 insertions(+), 175 deletions(-) diff --git a/runtime/Server.cpp b/runtime/Server.cpp index e5be20569faaf6c4..709819fa5fd9865a 100644 --- a/runtime/Server.cpp +++ b/runtime/Server.cpp @@ -22,183 +22,78 @@ // App headers #include "Server.h" -static void add_to_path(QString &python_path, QString path, bool prepend=false) -{ - if (!python_path.contains(path)) - { - if (!prepend) - { - if (!python_path.isEmpty() && !python_path.endsWith(";")) - python_path.append(";"); - - python_path.append(path); - } - else - { - if (!python_path.isEmpty() && !python_path.startsWith(";")) - python_path.prepend(";"); - - python_path.prepend(path); - } - } -} - Server::Server(quint16 port) { // Appserver port m_port = port; - m_wcAppName = NULL; // Initialise Python - Py_NoSiteFlag=1; - Py_DontWriteBytecodeFlag=1; + Py_DontWriteBytecodeFlag = 1; + + // Get the application directory + QString appDir = qApp->applicationDirPath(); + + // Build (and canonicalise) the virtual environment path +#ifdef Q_OS_MAC + QFileInfo venvBinPath(appDir + "/../Resources/venv/bin"); +#elif defined(Q_OS_WIN) + QFileInfo venvBinPath(appDir + "/../venv"); +#else + QFileInfo venvBinPath(appDir + "/../venv/bin"); +#endif + QString binPath = venvBinPath.canonicalFilePath(); + + if (binPath.isEmpty()) + // Use default Python environment from PATH + binPath.append("python"); + else + // Ensure the python symlink is not resolved + binPath.append("/python"); // Python3 requires conversion of char * to wchar_t *, so... #ifdef PYTHON2 - Py_SetProgramName(PGA_APP_NAME.toUtf8().data()); + QByteArray appName = binPath.toLocal8Bit(); + m_pyAppName = new char[strlen(appName.data()) + 1]; + strcpy(m_pyAppName, appName.data()); + qDebug() << "Python interpreter: " << QString::fromLocal8Bit(m_pyAppName); #else - char *appName = PGA_APP_NAME.toUtf8().data(); - const size_t cSize = strlen(appName)+1; - m_wcAppName = new wchar_t[cSize]; - mbstowcs (m_wcAppName, appName, cSize); - Py_SetProgramName(m_wcAppName); + std::wstring appName = binPath.toStdWString(); + m_pyAppName = new wchar_t[wcslen(appName.c_str()) + 1]; + wcscpy(m_pyAppName, appName.c_str()); + qDebug() << "Python interpreter: " << QString::fromWCharArray(m_pyAppName); #endif - - // Setup the search path - QSettings settings; - QString python_path = settings.value("PythonPath").toString(); - - // Get the application directory - QString app_dir = qApp->applicationDirPath(), - path_env = qgetenv("PATH"), - pythonHome; - QStringList path_list; - int i; - -#ifdef Q_OS_MAC - // In the case we're running in a release appbundle, we need to ensure the - // bundled virtual env is included in the Python path. We include it at the - // end, so expert users can override the path, but we do not save it, because - // if users move the app bundle, we'll end up with dead entries - - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../Resources/venv/bin"); - QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python"); - QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload"); - QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages"); - QFileInfo venvPath(app_dir + "/../Resources/venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append the path, if it's not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDynLibPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#elif defined(Q_OS_WIN) - - // In the case we're running in a release application, we need to ensure the - // bundled virtual env is included in the Python path. We include it at the - // end, so expert users can override the path, but we do not save it. - - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../venv"); - QFileInfo venvLibPath(app_dir + "/../venv/Lib"); - QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs"); - QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages"); - QFileInfo venvPath(app_dir + "/../venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append paths, if they're not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDLLsPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#else - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../venv/bin"); - QFileInfo venvLibPath(app_dir + "/../venv/lib/python"); - QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload"); - QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages"); - QFileInfo venvPath(app_dir + "/../venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append the path, if it's not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDynLibPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#endif - - qputenv("PATH", path_env.toUtf8().data()); - - if (python_path.length() > 0) - { - // Split the path setting into individual entries - path_list = python_path.split(";", QString::SkipEmptyParts); - python_path = QString(); - - // Add new additional path elements - for (i = path_list.size() - 1; i >= 0 ; --i) - { - python_path.append(path_list.at(i)); - if (i > 0) - { -#if defined(Q_OS_WIN) - python_path.append(";"); -#else - python_path.append(":"); -#endif - } - } - qputenv("PYTHONPATH", python_path.toUtf8().data()); - } - - qDebug() << "Python path: " << python_path - << "\nPython Home: " << pythonHome; - if (!pythonHome.isEmpty()) - { -#ifdef PYTHON2 - Py_SetPythonHome(pythonHome.toUtf8().data()); -#else - char *python_home = pythonHome.toUtf8().data(); - const size_t cSize = strlen(python_home) + 1; - wchar_t* wcPythonHome = new wchar_t[cSize]; - mbstowcs (wcPythonHome, python_home, cSize); - - Py_SetPythonHome(wcPythonHome); -#endif - } + Py_SetProgramName(m_pyAppName); Py_Initialize(); + /* + * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later + * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, + * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. + * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. + */ + PySys_SetArgvEx(1, &m_pyAppName, 0); + // Get the current path PyObject* sysPath = PySys_GetObject((char*)"path"); - - // Add new additional path elements - for (i = path_list.size() - 1; i >= 0 ; --i) - { + PyObject* sysPathRepr = PyObject_Repr(sysPath); #ifdef PYTHON2 - PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data())); + qDebug() << "Python path: " << QString::fromLocal8Bit(PyString_AsString(sysPathRepr)); #else -#if PY_MINOR_VERSION > 2 - PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data())); -#else - PyList_Append(sysPath, PyBytes_FromString(path_list.at(i).toUtf8().data())); + PyObject* sysPathBytes = PyUnicode_AsEncodedString(sysPathRepr, "utf8", "replace"); + qDebug() << "Python path: " << QString::fromUtf8(PyBytes_AsString(sysPathBytes)); + Py_DECREF(sysPathBytes); #endif -#endif - } + Py_DECREF(sysPathRepr); } Server::~Server() { - if (m_wcAppName) - delete m_wcAppName; - // Shutdown Python Py_Finalize(); + + if (m_pyAppName) + delete [] m_pyAppName; } bool Server::Init() @@ -258,33 +153,11 @@ void Server::run() // Run the app! #ifdef PYTHON2 - /* - * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later - * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, - * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. - * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. - */ - char* n_argv[] = { m_appfile.toUtf8().data() }; - PySys_SetArgv(1, n_argv); - PyObject* PyFileObject = PyFile_FromString(m_appfile.toUtf8().data(), (char *)"r"); int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile.toUtf8().data()); if (ret != 0) setError(tr("Failed to launch the application server, server thread exiting.")); #else - /* - * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later - * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, - * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. - * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. - */ - char *appName = m_appfile.toUtf8().data(); - const size_t cSize = strlen(appName)+1; - wchar_t* wcAppName = new wchar_t[cSize]; - mbstowcs (wcAppName, appName, cSize); - wchar_t* n_argv[] = { wcAppName }; - PySys_SetArgv(1, n_argv); - int fd = fileno(cp); PyObject* PyFileObject = PyFile_FromFd(fd, m_appfile.toUtf8().data(), (char *)"r", -1, NULL, NULL,NULL,1); if (PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile.toUtf8().data()) != 0) diff --git a/runtime/Server.h b/runtime/Server.h index 8df8503bb553eca4..5cc51f3d8b3e1f9b 100644 --- a/runtime/Server.h +++ b/runtime/Server.h @@ -39,7 +39,11 @@ private: QString m_error; quint16 m_port; - wchar_t *m_wcAppName; +#ifdef PYTHON2 + char *m_pyAppName; +#else + wchar_t *m_pyAppName; +#endif }; #endif // SERVER_H -- 2.11.0
-- Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers