/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Python Extensions Plugin for Qt Creator. ** ** 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 "pythonextensionsplugin.h" #include "pyutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PythonExtensions { namespace Constants { const char EXTENSIONS_DIR[] = "/python"; const char PY_PACKAGES_DIR[] = "/site-packages"; const char PY_BINDING_LIB[] = "/libPythonBinding"; const char MESSAGE_MANAGER_PREFIX[] = "Python Extensions: "; } // namespace Constants namespace Internal { PythonExtensionsPlugin::PythonExtensionsPlugin() { // Empty } PythonExtensionsPlugin::~PythonExtensionsPlugin() { // Unregister objects from the plugin manager's object pool // Delete members } bool PythonExtensionsPlugin::initialize(const QStringList &arguments, QString *errorString) { // Register objects in the plugin manager's object pool // Load settings // Add actions to menus // Connect to other plugins' signals // In the initialize function, a plugin can be sure that the plugins it // depends on have initialized their members. Q_UNUSED(arguments) Q_UNUSED(errorString) initializePythonBindings(); // Python extensions are loaded after C++ plugins for now (plan: later flag can be set) return true; } void PythonExtensionsPlugin::extensionsInitialized() { // Retrieve objects from the plugin manager's object pool // In the extensionsInitialized function, a plugin can be sure that all // plugins that depend on it are completely initialized. } bool PythonExtensionsPlugin::delayedInitialize() { // Initialize optional bindings initializeOptionalBindings(); // Pip install any requirements known for the script installRequirements(); // Python plugins are initialized here, to avoid blocking on startup initializePythonExtensions(); return true; } ExtensionSystem::IPlugin::ShutdownFlag PythonExtensionsPlugin::aboutToShutdown() { // Save settings // Disconnect from signals that are not needed during shutdown // Hide UI (if you add UI that is not in the main window directly) return SynchronousShutdown; } QDir PythonExtensionsPlugin::extensionDir() { // Search python directory in plugin paths QDir extension_dir; for (const QString &path : ExtensionSystem::PluginManager::pluginPaths()) { extension_dir = QDir(path + Constants::EXTENSIONS_DIR); if (extension_dir.exists()) break; } // Can be checked for validity with .exists() return extension_dir; } static QVector getExtensionList(const QDir &directory) { if (!directory.exists()) return {}; QStringList entries = directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); entries.removeAll("site-packages"); entries.removeAll("__pycache__"); const QVector packageExtensions = Utils::transform(entries, [](const QString &entry) { return Extension({entry, false}); }); const QStringList fileEntries = directory.entryList({"*.py"}, QDir::Files); const QVector fileExtensions = Utils::transform(fileEntries, [](const QString &entry) { return Extension({entry.left(entry.size() - 3), false}); }); return packageExtensions + fileExtensions; } QVector PythonExtensionsPlugin::extensionList() { return extensionListRef(); } QVector &PythonExtensionsPlugin::extensionListRef() { static bool initialized = false; if (!initialized) { m_extensions = getExtensionList(extensionDir()); initialized = true; } return m_extensions; } QString PythonExtensionsPlugin::pythonPackagePath() { if (extensionDir().exists()) { return extensionDir().absolutePath() + Constants::PY_PACKAGES_DIR; } else { return QString(); } } void PythonExtensionsPlugin::initializePythonBindings() { // Add our custom module directory if (extensionDir().exists()) PyUtil::addToSysPath(extensionDir().path().toStdString()); // Add directory for local python packages that are installed as requirements of extensions // Need to create it first if it doesn't exist, otherwise python will ignore the path if (!QFile::exists(pythonPackagePath())) QDir().mkpath(pythonPackagePath()); PyUtil::addToSysPath(pythonPackagePath().toStdString()); // Initialize the Python context and register global Qt Creator variable if (!PyUtil::bindCoreModules()) { qCDebug(pyLog) << "Python bindings could not be initialized"; Core::MessageManager::writeSilently(Constants::MESSAGE_MANAGER_PREFIX + tr("Python bindings could not be initialized")); return; } // Bind the plugin instance PyUtil::bindObject("QtCreator", "PythonExtensions", PyUtil::PythonExtensionsPluginType, this); } void PythonExtensionsPlugin::initializeOptionalBindings() { // Try to load optional bindings for all loaded plugins // If a plugin has optional bindings, they occur in the form of // a shared object which has the name libPythonBinding{PluginName} // and exposes a symbol called `bind' which is a void function taking // no arguments. This function is responsible for binding the // object using the exposed PyUtil api and for reporting any errors // etc. to the stderr / stdout. // Examples of projects for such libraries exist within this repository. for (int i = 0; i < ExtensionSystem::PluginManager::loadQueue().size(); i++) { // Check each plugin directory for the library (first found is used) QString name = ExtensionSystem::PluginManager::loadQueue()[i]->name(); for (const QString &path : ExtensionSystem::PluginManager::pluginPaths()) { QLibrary bindingLib(path + Constants::PY_BINDING_LIB + name); QFunctionPointer bind = bindingLib.resolve("bind"); if (bind) { qCDebug(pyLog) << "Initializing bindings for plugin" << name; bind(); break; } } } } void PythonExtensionsPlugin::installRequirements() { // Pip install any requirements.txt file found QDir extension_dir = extensionDir(); if (!extension_dir.exists()) return; QVector extension_list = extensionListRef(); for (const Extension &extension : extension_list) { QString extension_requirements(extension_dir.absolutePath() + "/" + extension.name + "/requirements.txt"); if (QFileInfo::exists(extension_requirements) && !QFileInfo::exists(extension_requirements + ".installed")) { if (!PyUtil::pipInstallRequirements(extension_requirements.toStdString(), pythonPackagePath().toStdString())) { qCDebug(pyLog) << "Failed to install requirements for extension" << extension.name; Core::MessageManager::writeSilently(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to install requirements for extension ") + extension.name); } } } } void PythonExtensionsPlugin::initializePythonExtensions() { // Search python directory in plugin paths QDir extension_dir = extensionDir(); if (!extension_dir.exists()) { qCDebug(pyLog) << "Python extension directory not found"; Core::MessageManager::writeSilently(Constants::MESSAGE_MANAGER_PREFIX + tr("Python extension directory not found")); return; } qCDebug(pyLog) << "Found Python extension directory at location" << extension_dir.absolutePath(); QVector &extension_list = extensionListRef(); qCDebug(pyLog) << "Number of Python extensions found:" << extension_list.size(); int loadedCount = 0; // Run the extension initialization code for (Extension &extension : extension_list) { qCDebug(pyLog) << "Trying to initialize extension" << extension.name; if (PyUtil::runScript("import " + extension.name.toStdString())) { extension.loaded = true; ++loadedCount; } else { qCDebug(pyLog) << "Failed to initialize extension" << extension.name; Core::MessageManager::writeSilently(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to initialize extension ") + extension.name); } } qCDebug(pyLog) << "Number of Python extensions loaded:" << loadedCount; } } // namespace Internal } // namespace PythonExtensions