// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pythonutils.h" #include "pythonbuildconfiguration.h" #include "pythonconstants.h" #include "pythonkitaspect.h" #include "pythonproject.h" #include "pythonsettings.h" #include "pythontr.h" #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace Python::Internal { static QHash &userDefinedPythonsForDocument() { static QHash userDefines; return userDefines; } FilePath detectPython(const FilePath &documentPath) { Project *project = documentPath.isEmpty() ? nullptr : ProjectManager::projectForFile(documentPath); if (!project) project = ProjectManager::startupProject(); FilePaths dirs = Environment::systemEnvironment().path(); if (project && project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE) { if (const Target *target = project->activeTarget()) { if (auto bc = qobject_cast(target->activeBuildConfiguration())) return bc->python(); if (const std::optional python = PythonKitAspect::python(target->kit())) return python->command; } } const FilePath userDefined = userDefinedPythonsForDocument().value(documentPath); if (userDefined.exists()) return userDefined; // check whether this file is inside a python virtual environment const QList venvInterpreters = PythonSettings::detectPythonVenvs(documentPath); if (!venvInterpreters.isEmpty()) return venvInterpreters.first().command; auto defaultInterpreter = PythonSettings::defaultInterpreter().command; if (defaultInterpreter.exists()) return defaultInterpreter; auto pythonFromPath = [dirs](const FilePath &toCheck) { const FilePaths found = toCheck.searchAllInDirectories(dirs); for (const FilePath &python : found) { // Windows creates empty redirector files that may interfere if (python.exists() && python.osType() == OsTypeWindows && python.fileSize() != 0) return python; } return FilePath(); }; const FilePath fromPath3 = pythonFromPath("python3"); if (fromPath3.exists()) return fromPath3; const FilePath fromPath = pythonFromPath("python"); if (fromPath.exists()) return fromPath; return PythonSettings::interpreters().value(0).command; } void definePythonForDocument(const FilePath &documentPath, const FilePath &python) { userDefinedPythonsForDocument()[documentPath] = python; } static QStringList replImportArgs(const FilePath &pythonFile, ReplType type) { using MimeTypes = QList; const MimeTypes mimeTypes = pythonFile.isEmpty() || type == ReplType::Unmodified ? MimeTypes() : mimeTypesForFileName(pythonFile.toString()); const bool isPython = Utils::anyOf(mimeTypes, [](const MimeType &mt) { return mt.inherits("text/x-python") || mt.inherits("text/x-python3"); }); if (type == ReplType::Unmodified || !isPython) return {}; const auto import = type == ReplType::Import ? QString("import %1").arg(pythonFile.completeBaseName()) : QString("from %1 import *").arg(pythonFile.completeBaseName()); return {"-c", QString("%1; print('Running \"%1\"')").arg(import)}; } void openPythonRepl(QObject *parent, const FilePath &file, ReplType type) { Q_UNUSED(parent) static const auto workingDir = [](const FilePath &file) { if (file.isEmpty()) { if (Project *project = ProjectManager::startupProject()) return project->projectDirectory(); return FilePath::currentWorkingPath(); } return file.absolutePath(); }; const FilePath pythonCommand = detectPython(file); Process process; process.setCommand({pythonCommand, {"-i", replImportArgs(file, type)}}); process.setWorkingDirectory(workingDir(file)); process.setTerminalMode(TerminalMode::Detached); process.start(); if (process.error() != QProcess::UnknownError) { Core::MessageManager::writeDisrupting( Tr::tr((process.error() == QProcess::FailedToStart) ? "Failed to run Python (%1): \"%2\"." : "Error while running Python (%1): \"%2\".") .arg(process.commandLine().toUserOutput(), process.errorString())); } } QString pythonName(const FilePath &pythonPath) { static QHash nameForPython; if (!pythonPath.exists()) return {}; QString name = nameForPython.value(pythonPath); if (name.isEmpty()) { Process pythonProcess; pythonProcess.setCommand({pythonPath, {"--version"}}); using namespace std::chrono_literals; pythonProcess.runBlocking(2s); if (pythonProcess.result() != ProcessResult::FinishedWithSuccess) return {}; name = pythonProcess.allOutput().trimmed(); nameForPython[pythonPath] = name; } return name; } PythonProject *pythonProjectForFile(const FilePath &file) { for (Project *project : ProjectManager::projects()) { if (auto pythonProject = qobject_cast(project)) { if (pythonProject->isKnownFile(file)) return pythonProject; } } return nullptr; } void createVenv(const FilePath &python, const FilePath &venvPath, const std::function &callback) { QTC_ASSERT(python.isExecutableFile(), callback(false); return); QTC_ASSERT(!venvPath.exists() || venvPath.isDir(), callback(false); return); const CommandLine command(python, QStringList{"-m", "venv", venvPath.toUserOutput()}); auto process = new Process; auto progress = new Core::ProcessProgress(process); progress->setDisplayName(Tr::tr("Create Python venv")); QObject::connect(process, &Process::done, [process, callback](){ callback(process->result() == ProcessResult::FinishedWithSuccess); process->deleteLater(); }); process->setCommand(command); process->start(); } bool isVenvPython(const FilePath &python) { return python.parentDir().parentDir().pathAppended("pyvenv.cfg").exists(); } static bool isUsableHelper(QHash *cache, const QString &keyString, const QString &commandArg, const FilePath &python) { auto it = cache->find(python); if (it == cache->end()) { const Key key = keyFromString(keyString); Process process; process.setCommand({python, {"-m", commandArg, "-h"}}); process.runBlocking(); const bool usable = process.result() == ProcessResult::FinishedWithSuccess; it = cache->insert(python, usable); } return *it; } bool venvIsUsable(const FilePath &python) { static QHash cache; return isUsableHelper(&cache, "pyVenvIsUsable", "venv", python); } bool pipIsUsable(const FilePath &python) { static QHash cache; return isUsableHelper(&cache, "pyPipIsUsable", "pip", python); } QString pythonVersion(const FilePath &python) { static QReadWriteLock lock; static QMap versionCache; { QReadLocker locker(&lock); auto it = versionCache.constFind(python); if (it != versionCache.constEnd()) return *it; } Process p; p.setCommand({python, {"--version"}}); p.runBlocking(); if (p.result() == ProcessResult::FinishedWithSuccess) { const QString version = p.readAllStandardOutput().trimmed(); QWriteLocker locker(&lock); versionCache.insert(python, version); return version; } return QString(); } } // Python::Internal