2019-07-22 14:39:01 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of 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.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include "pythonrunconfiguration.h"
|
|
|
|
|
|
2022-03-16 09:35:02 +01:00
|
|
|
#include "pyside.h"
|
2022-03-30 14:42:33 +02:00
|
|
|
#include "pysidebuildconfiguration.h"
|
2022-05-25 15:24:09 +02:00
|
|
|
#include "pysideuicextracompiler.h"
|
2019-07-22 14:39:01 +02:00
|
|
|
#include "pythonconstants.h"
|
2021-12-13 14:19:30 +01:00
|
|
|
#include "pythonlanguageclient.h"
|
2019-07-22 14:39:01 +02:00
|
|
|
#include "pythonproject.h"
|
|
|
|
|
#include "pythonsettings.h"
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include <languageclient/languageclientmanager.h>
|
|
|
|
|
|
2022-03-30 14:42:33 +02:00
|
|
|
#include <projectexplorer/buildsteplist.h>
|
2019-11-15 15:44:45 +01:00
|
|
|
#include <projectexplorer/buildsystem.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <projectexplorer/localenvironmentaspect.h>
|
|
|
|
|
#include <projectexplorer/runconfigurationaspects.h>
|
|
|
|
|
#include <projectexplorer/target.h>
|
|
|
|
|
#include <projectexplorer/taskhub.h>
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
|
2020-09-18 12:11:40 +02:00
|
|
|
#include <utils/aspects.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <utils/fileutils.h>
|
2020-09-18 12:11:40 +02:00
|
|
|
#include <utils/layoutbuilder.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <utils/outputformatter.h>
|
|
|
|
|
#include <utils/theme/theme.h>
|
|
|
|
|
|
|
|
|
|
#include <QBoxLayout>
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
#include <QFormLayout>
|
|
|
|
|
#include <QPlainTextEdit>
|
|
|
|
|
#include <QPushButton>
|
|
|
|
|
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace Python {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2020-04-14 15:28:44 +02:00
|
|
|
class PythonOutputLineParser : public OutputLineParser
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2020-04-14 15:28:44 +02:00
|
|
|
PythonOutputLineParser()
|
2019-07-22 14:39:01 +02:00
|
|
|
// Note that moc dislikes raw string literals.
|
|
|
|
|
: filePattern("^(\\s*)(File \"([^\"]+)\", line (\\d+), .*$)")
|
|
|
|
|
{
|
|
|
|
|
TaskHub::clearTasks(PythonErrorTaskCategory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2020-04-14 15:28:44 +02:00
|
|
|
Result handleLine(const QString &text, OutputFormat format) final
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
2020-03-18 12:56:35 +01:00
|
|
|
if (!m_inTraceBack) {
|
|
|
|
|
m_inTraceBack = format == StdErrFormat
|
|
|
|
|
&& text.startsWith("Traceback (most recent call last):");
|
2020-04-09 17:47:01 +02:00
|
|
|
if (m_inTraceBack)
|
2020-03-19 16:00:37 +01:00
|
|
|
return Status::InProgress;
|
|
|
|
|
return Status::NotHandled;
|
2020-03-18 12:56:35 +01:00
|
|
|
}
|
|
|
|
|
|
2020-06-26 13:59:38 +02:00
|
|
|
const Utils::Id category(PythonErrorTaskCategory);
|
2020-03-18 12:56:35 +01:00
|
|
|
const QRegularExpressionMatch match = filePattern.match(text);
|
|
|
|
|
if (match.hasMatch()) {
|
2020-04-09 17:47:01 +02:00
|
|
|
const LinkSpec link(match.capturedStart(2), match.capturedLength(2), match.captured(2));
|
2020-03-18 12:56:35 +01:00
|
|
|
const auto fileName = FilePath::fromString(match.captured(3));
|
2020-06-23 13:00:43 +02:00
|
|
|
const int lineNumber = match.captured(4).toInt();
|
2020-03-18 12:56:35 +01:00
|
|
|
m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category});
|
2020-04-09 17:47:01 +02:00
|
|
|
return {Status::InProgress, {link}};
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 16:00:37 +01:00
|
|
|
Status status = Status::InProgress;
|
2020-03-18 12:56:35 +01:00
|
|
|
if (text.startsWith(' ')) {
|
|
|
|
|
// Neither traceback start, nor file, nor error message line.
|
|
|
|
|
// Not sure if that can actually happen.
|
|
|
|
|
if (m_tasks.isEmpty()) {
|
|
|
|
|
m_tasks.append({Task::Warning, text.trimmed(), {}, -1, category});
|
2019-07-22 14:39:01 +02:00
|
|
|
} else {
|
2020-03-18 12:56:35 +01:00
|
|
|
Task &task = m_tasks.back();
|
2020-05-12 16:26:34 +02:00
|
|
|
if (!task.summary.isEmpty())
|
|
|
|
|
task.summary += ' ';
|
|
|
|
|
task.summary += text.trimmed();
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
2020-03-18 12:56:35 +01:00
|
|
|
} else {
|
|
|
|
|
// The actual exception. This ends the traceback.
|
|
|
|
|
TaskHub::addTask({Task::Error, text, {}, -1, category});
|
|
|
|
|
for (auto rit = m_tasks.crbegin(), rend = m_tasks.crend(); rit != rend; ++rit)
|
2019-07-22 14:39:01 +02:00
|
|
|
TaskHub::addTask(*rit);
|
2020-03-18 12:56:35 +01:00
|
|
|
m_tasks.clear();
|
|
|
|
|
m_inTraceBack = false;
|
2020-03-19 16:00:37 +01:00
|
|
|
status = Status::Done;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
2020-03-19 16:00:37 +01:00
|
|
|
return status;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 16:00:37 +01:00
|
|
|
bool handleLink(const QString &href) final
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
const QRegularExpressionMatch match = filePattern.match(href);
|
|
|
|
|
if (!match.hasMatch())
|
2020-03-19 16:00:37 +01:00
|
|
|
return false;
|
2019-07-22 14:39:01 +02:00
|
|
|
const QString fileName = match.captured(3);
|
2020-06-23 13:00:43 +02:00
|
|
|
const int lineNumber = match.captured(4).toInt();
|
2021-11-01 17:02:02 +01:00
|
|
|
Core::EditorManager::openEditorAt({FilePath::fromString(fileName), lineNumber});
|
2020-03-19 16:00:37 +01:00
|
|
|
return true;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QRegularExpression filePattern;
|
2020-03-18 12:56:35 +01:00
|
|
|
QList<Task> m_tasks;
|
|
|
|
|
bool m_inTraceBack;
|
2019-07-22 14:39:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2022-05-30 12:38:46 +02:00
|
|
|
PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
|
|
|
|
|
: RunConfiguration(target, id)
|
|
|
|
|
{
|
|
|
|
|
auto interpreterAspect = addAspect<InterpreterAspect>();
|
|
|
|
|
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
|
|
|
|
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
|
|
|
|
|
|
|
|
|
|
connect(interpreterAspect, &InterpreterAspect::changed,
|
|
|
|
|
this, &PythonRunConfiguration::currentInterpreterChanged);
|
|
|
|
|
|
|
|
|
|
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
|
|
|
|
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
|
|
|
|
|
|
|
|
|
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(
|
|
|
|
|
project()->projectDirectory());
|
|
|
|
|
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
|
|
|
|
|
Interpreter defaultInterpreter = interpreters.isEmpty() ? PythonSettings::defaultInterpreter()
|
|
|
|
|
: interpreters.first();
|
|
|
|
|
if (!defaultInterpreter.command.isExecutableFile())
|
|
|
|
|
defaultInterpreter = PythonSettings::interpreters().value(0);
|
|
|
|
|
interpreterAspect->setDefaultInterpreter(defaultInterpreter);
|
|
|
|
|
|
|
|
|
|
auto bufferedAspect = addAspect<BoolAspect>();
|
|
|
|
|
bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered");
|
|
|
|
|
bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox);
|
|
|
|
|
bufferedAspect->setToolTip(tr("Enabling improves output performance, "
|
|
|
|
|
"but results in delayed output."));
|
|
|
|
|
|
|
|
|
|
auto scriptAspect = addAspect<MainScriptAspect>();
|
|
|
|
|
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
|
|
|
|
scriptAspect->setLabelText(tr("Script:"));
|
|
|
|
|
scriptAspect->setDisplayStyle(StringAspect::LabelDisplay);
|
|
|
|
|
|
|
|
|
|
addAspect<LocalEnvironmentAspect>(target);
|
|
|
|
|
|
|
|
|
|
auto argumentsAspect = addAspect<ArgumentsAspect>(macroExpander());
|
|
|
|
|
|
|
|
|
|
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
|
|
|
|
|
addAspect<TerminalAspect>();
|
|
|
|
|
|
|
|
|
|
setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] {
|
|
|
|
|
CommandLine cmd{interpreterAspect->currentInterpreter().command};
|
|
|
|
|
if (!bufferedAspect->value())
|
|
|
|
|
cmd.addArg("-u");
|
|
|
|
|
cmd.addArg(scriptAspect->filePath().fileName());
|
|
|
|
|
cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw);
|
|
|
|
|
return cmd;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setUpdater([this, scriptAspect] {
|
|
|
|
|
const BuildTargetInfo bti = buildTargetInfo();
|
|
|
|
|
const QString script = bti.targetFilePath.toUserOutput();
|
|
|
|
|
setDefaultDisplayName(tr("Run %1").arg(script));
|
|
|
|
|
scriptAspect->setValue(script);
|
|
|
|
|
aspect<WorkingDirectoryAspect>()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
|
2022-05-25 15:24:09 +02:00
|
|
|
connect(target, &Target::buildSystemUpdated, this, &PythonRunConfiguration::updateExtraCompilers);
|
|
|
|
|
currentInterpreterChanged();
|
2022-06-15 13:20:05 +02:00
|
|
|
|
|
|
|
|
setRunnableModifier([](Runnable &r) {
|
|
|
|
|
r.workingDirectory = r.workingDirectory.onDevice(r.command.executable());
|
|
|
|
|
});
|
2022-06-16 15:33:32 +02:00
|
|
|
|
|
|
|
|
connect(PySideInstaller::instance(), &PySideInstaller::pySideInstalled, this,
|
|
|
|
|
[this](const FilePath &python) {
|
|
|
|
|
if (python == aspect<InterpreterAspect>()->currentInterpreter().command)
|
|
|
|
|
checkForPySide(python);
|
|
|
|
|
});
|
2022-05-25 15:24:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PythonRunConfiguration::~PythonRunConfiguration()
|
|
|
|
|
{
|
|
|
|
|
qDeleteAll(m_extraCompilers);
|
2022-05-30 12:38:46 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-16 15:33:32 +02:00
|
|
|
void PythonRunConfiguration::checkForPySide(const FilePath &python)
|
2022-05-30 12:38:46 +02:00
|
|
|
{
|
|
|
|
|
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps();
|
|
|
|
|
|
|
|
|
|
Utils::FilePath pySideProjectPath;
|
2022-05-25 15:24:09 +02:00
|
|
|
m_pySideUicPath.clear();
|
2022-05-30 12:38:46 +02:00
|
|
|
const PipPackage pySide6Package("PySide6");
|
|
|
|
|
const PipPackageInfo info = pySide6Package.info(python);
|
|
|
|
|
|
|
|
|
|
for (const FilePath &file : qAsConst(info.files)) {
|
|
|
|
|
if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-project")) {
|
|
|
|
|
pySideProjectPath = info.location.resolvePath(file);
|
|
|
|
|
pySideProjectPath = pySideProjectPath.cleanPath();
|
2022-05-25 15:24:09 +02:00
|
|
|
if (!m_pySideUicPath.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
} else if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-uic")) {
|
|
|
|
|
m_pySideUicPath = info.location.resolvePath(file);
|
|
|
|
|
m_pySideUicPath = m_pySideUicPath.cleanPath();
|
|
|
|
|
if (!pySideProjectPath.isEmpty())
|
|
|
|
|
break;
|
2022-05-30 12:38:46 +02:00
|
|
|
}
|
2019-10-01 13:16:17 +02:00
|
|
|
}
|
2022-03-30 14:42:33 +02:00
|
|
|
|
2022-06-02 14:40:11 +02:00
|
|
|
// Workaround that pip might return an incomplete file list on windows
|
|
|
|
|
if (HostOsInfo::isWindowsHost() && !python.needsDevice()
|
|
|
|
|
&& !info.location.isEmpty() && m_pySideUicPath.isEmpty()) {
|
2022-06-20 13:55:25 +02:00
|
|
|
// Scripts is next to the site-packages install dir for user installations
|
|
|
|
|
FilePath scripts = info.location.parentDir().pathAppended("Scripts");
|
|
|
|
|
if (!scripts.exists()) {
|
|
|
|
|
// in global/venv installations Scripts is next to Lib/site-packages
|
|
|
|
|
scripts = info.location.parentDir().parentDir().pathAppended("Scripts");
|
|
|
|
|
}
|
2022-06-02 14:40:11 +02:00
|
|
|
auto userInstalledPySideTool = [&](const QString &toolName) {
|
|
|
|
|
const FilePath tool = scripts.pathAppended(HostOsInfo::withExecutableSuffix(toolName));
|
|
|
|
|
return tool.isExecutableFile() ? tool : FilePath();
|
|
|
|
|
};
|
|
|
|
|
m_pySideUicPath = userInstalledPySideTool("pyside6-uic");
|
|
|
|
|
if (pySideProjectPath.isEmpty())
|
|
|
|
|
pySideProjectPath = userInstalledPySideTool("pyside6-project");
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 15:24:09 +02:00
|
|
|
updateExtraCompilers();
|
|
|
|
|
|
2022-05-30 12:38:46 +02:00
|
|
|
if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>())
|
|
|
|
|
pySideBuildStep->updatePySideProjectPath(pySideProjectPath);
|
2022-06-16 15:33:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonRunConfiguration::currentInterpreterChanged()
|
|
|
|
|
{
|
|
|
|
|
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
|
|
|
|
|
checkForPySide(python);
|
2022-05-30 12:38:46 +02:00
|
|
|
|
|
|
|
|
for (FilePath &file : project()->files(Project::AllFiles)) {
|
|
|
|
|
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
2022-06-01 07:48:46 +02:00
|
|
|
if (document->mimeType() == Constants::C_PY_MIMETYPE
|
|
|
|
|
|| document->mimeType() == Constants::C_PY3_MIMETYPE) {
|
2022-05-30 12:38:46 +02:00
|
|
|
PyLSConfigureAssistant::openDocumentWithPython(python, document);
|
|
|
|
|
PySideInstaller::checkPySideInstallation(python, document);
|
2022-03-30 14:42:33 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-30 12:38:46 +02:00
|
|
|
}
|
2019-07-22 14:39:01 +02:00
|
|
|
|
2022-05-25 15:24:09 +02:00
|
|
|
QList<PySideUicExtraCompiler *> PythonRunConfiguration::extraCompilers() const
|
|
|
|
|
{
|
|
|
|
|
return m_extraCompilers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonRunConfiguration::updateExtraCompilers()
|
|
|
|
|
{
|
|
|
|
|
QList<PySideUicExtraCompiler *> oldCompilers = m_extraCompilers;
|
|
|
|
|
m_extraCompilers.clear();
|
|
|
|
|
|
|
|
|
|
if (m_pySideUicPath.isExecutableFile()) {
|
|
|
|
|
auto uiMatcher = [](const ProjectExplorer::Node *node) {
|
|
|
|
|
if (const ProjectExplorer::FileNode *fileNode = node->asFileNode())
|
|
|
|
|
return fileNode->fileType() == ProjectExplorer::FileType::Form;
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
const FilePaths uiFiles = project()->files(uiMatcher);
|
|
|
|
|
for (const FilePath &uiFile : uiFiles) {
|
|
|
|
|
Utils::FilePath generated = uiFile.parentDir();
|
|
|
|
|
generated = generated.pathAppended("/ui_" + uiFile.baseName() + ".py");
|
|
|
|
|
int index = Utils::indexOf(oldCompilers, [&](PySideUicExtraCompiler *oldCompiler) {
|
|
|
|
|
return oldCompiler->pySideUicPath() == m_pySideUicPath
|
|
|
|
|
&& oldCompiler->project() == project() && oldCompiler->source() == uiFile
|
|
|
|
|
&& oldCompiler->targets() == Utils::FilePaths{generated};
|
|
|
|
|
});
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
m_extraCompilers << new PySideUicExtraCompiler(m_pySideUicPath,
|
|
|
|
|
project(),
|
|
|
|
|
uiFile,
|
|
|
|
|
{generated},
|
|
|
|
|
this);
|
|
|
|
|
} else {
|
|
|
|
|
m_extraCompilers << oldCompilers.takeAt(index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
|
|
|
|
|
if (auto client = PyLSClient::clientForPython(python))
|
|
|
|
|
client->updateExtraCompilers(project(), m_extraCompilers);
|
|
|
|
|
qDeleteAll(oldCompilers);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 14:39:01 +02:00
|
|
|
PythonRunConfigurationFactory::PythonRunConfigurationFactory()
|
|
|
|
|
{
|
2022-04-13 12:26:54 +02:00
|
|
|
registerRunConfiguration<PythonRunConfiguration>(Constants::C_PYTHONRUNCONFIGURATION_ID);
|
2019-07-22 14:39:01 +02:00
|
|
|
addSupportedProjectType(PythonProjectId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PythonOutputFormatterFactory::PythonOutputFormatterFactory()
|
|
|
|
|
{
|
2020-04-22 10:46:09 +02:00
|
|
|
setFormatterCreator([](Target *t) -> QList<OutputLineParser *> {
|
2020-04-22 10:20:36 +02:00
|
|
|
if (t && t->project()->mimeType() == Constants::C_PY_MIMETYPE)
|
2020-04-22 10:46:09 +02:00
|
|
|
return {new PythonOutputLineParser};
|
|
|
|
|
return {};
|
2019-07-22 14:39:01 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Python
|