/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Python Extensions Plugin for QtCreator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "pyutil.h" #ifdef Q_OS_UNIX # include // dlopen #endif #include #include #include #include #include #include // These are used in python and cause compile-time errors // if still defined. #undef signals #undef slots #include #include #include // Python has no concept of private variables and there // is no way to declare a namespace or scope that will be // inaccessible from the user script. // To avoid naming collisions with the setup and tear down // scripts that attempt to separate different extensions on // the level of user code (or at least make them appear separated), // this macro mangles the names of variables used. // Use as: // "... some Python code ..." privatName("my_var_name") "... more code ..." #define privateName(name) "qt_creator_" name "_symbol_mchawrioklpilnjajqkfl" // Setup and utility functions for QtCreator bindings // from typesystem.xml #if PY_MAJOR_VERSION >= 3 extern "C" PyObject *PyInit_QtCreator(); #else extern "C" void initQtCreator(); #endif // These variables store all Python types exported by QtCreators bindings, // as well as the types exported for QtWidgets. extern PyTypeObject **SbkPythonExtension_QtCreatorTypes; // This variable stores the Python module generated by Shiboken extern PyObject *SbkPythonExtension_QtCreatorModuleObject; namespace PyUtil { static State state = PythonUninitialized; static void cleanup() { if (state > PythonUninitialized) { Py_Finalize(); state = PythonUninitialized; } } State init() { if (state > PythonUninitialized) return state; // If there is an active python virtual environment, use that environment's packages location. QByteArray virtualEnvPath = qgetenv("VIRTUAL_ENV"); if (!virtualEnvPath.isEmpty()) qputenv("PYTHONHOME", virtualEnvPath); // Python's shared libraries don't work properly if included from other // shared libraries. See https://mail.python.org/pipermail/new-bugs-announce/2008-November/003322.html #ifdef Q_OS_UNIX #if PY_MAJOR_VERSION >= 3 std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+"m.so"; #else std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+".so"; #endif dlopen(version.c_str(), RTLD_LAZY | RTLD_GLOBAL); #endif Py_Initialize(); qAddPostRoutine(cleanup); state = PythonInitialized; #if PY_MAJOR_VERSION >= 3 const bool pythonInitialized = PyInit_QtCreator() != nullptr; #else const bool pythonInitialized = true; initQtCreator(); #endif const bool pyErrorOccurred = PyErr_Occurred() != nullptr; if (pythonInitialized && !pyErrorOccurred) { state = QtCreatorModuleLoaded; } else { if (pyErrorOccurred) PyErr_Print(); qWarning("Failed to initialize the QtCreator module."); } // The Python interpreter eats SIGINT, which is not what we want. // This stops it from happening. // See https://mail.python.org/pipermail/cplusplus-sig/2012-December/016858.html if (PyRun_SimpleString(std::string( "import signal as " privateName("signal") "\n" "" privateName("signal") ".signal(" privateName("signal") ".SIGINT, " privateName("signal") ".SIG_DFL)" ).c_str()) == -1) { if (PyErr_Occurred()) PyErr_Print(); qWarning("Failed to prevent SIGINT capture."); } return state; } bool createModule(const std::string &moduleName) { if (init() != QtCreatorModuleLoaded) return false; PyObject *module = PyImport_AddModule(moduleName.c_str()); if (!module) { if (PyErr_Occurred()) PyErr_Print(); qWarning() << __FUNCTION__ << "Failed to create module"; return false; } return true; } bool bindObject(const QString &moduleName, const QString &name, int index, void *o) { if (init() != QtCreatorModuleLoaded) return false; // Generate the type PyTypeObject *typeObject = SbkPythonExtension_QtCreatorTypes[index]; PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast(typeObject), o); if (!po) { qWarning() << __FUNCTION__ << "Failed to create wrapper for" << o; return false; } Py_INCREF(po); return bindPyObject(moduleName, name, po); } bool bindShibokenModuleObject(const QString &moduleName, const QString &name) { return bindPyObject(moduleName, name, SbkPythonExtension_QtCreatorModuleObject); } bool bindPyObject(const QString &moduleName, const QString &name, void *obj) { if (init() != QtCreatorModuleLoaded) return false; PyObject *module = PyImport_AddModule(moduleName.toLocal8Bit().constData()); if (!module) { if (PyErr_Occurred()) PyErr_Print(); qWarning() << __FUNCTION__ << "Failed to locate module" << moduleName; return false; } if (PyModule_AddObject(module, name.toLocal8Bit().constData(), (PyObject *)obj) < 0) { if (PyErr_Occurred()) PyErr_Print(); qWarning() << __FUNCTION__ << "Failed to add object" << name << "to" << moduleName; return false; } return true; } bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj) { PyObject *moduleDict = PyModule_GetDict((PyObject *)obj); if (!moduleDict) { if (PyErr_Occurred()) PyErr_Print(); qWarning("Could not obtain module dict"); return false; } PyObject *moduleItem = PyDict_GetItemString(moduleDict, name.toLocal8Bit().constData()); if (!moduleDict) { if (PyErr_Occurred()) PyErr_Print(); qWarning() << "Could not obtain module item" << name; return false; } return bindPyObject(moduleName, name, (void *)moduleItem); } bool runScript(const std::string &script) { if (init() == PythonUninitialized) return false; if (PyRun_SimpleString(script.c_str()) == -1) { if (PyErr_Occurred()) PyErr_Print(); return false; } return true; } bool runScriptWithPath(const std::string &script, const std::string &path) { // I couldn't find a direct c api, but this should cause no variable name // collisions. It also cleans the imported modules after the script finishes const std::string s = "import sys as " privateName("sys") "\n" "" privateName("path") " = list(" privateName("sys") ".path)\n" "" privateName("sys") ".path.insert(0, \"" + path + "\")\n" "" privateName("loaded_modules") " = list(" privateName("sys") ".modules.keys())\n" "" + script + "\n" "" privateName("sys") ".path = " privateName("path") "\n" "for m in list(" privateName("sys") ".modules):\n" " if m not in " privateName("loaded_modules") ":\n" " del(" privateName("sys") ".modules[m])\n"; return runScript(s); } bool addToSysPath(const std::string &path) { // Add a path to Pythons sys.path // Used for installing dependencies into custom // directory const std::string s = "import sys as " privateName("sys") "\n" "" privateName("sys") ".path.append(\"" + path + "\")"; return runScript(s); } bool pipInstallRequirements(const std::string &requirements, const std::string &target) { // Run a requirements.txt file with pip const std::string s = "import subprocess, sys\n" "subprocess.check_call(\"{} -m pip install -t " + target + " -r " + requirements + "\".format(sys.executable).split())\n" "open(\"" + requirements + "\".replace(\"requirements.txt\",\"requirements.txt.installed\"),\"a\").close()\n"; return runScriptWithPath(s, ""); } } // namespace PyUtil