forked from qt-creator/qt-creator
Use the global synchronizer from Utils::futureSynchronizer() directly Change-Id: Ic8843bc1ff7951c041529a258f36117f08ec4b35 Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
437 lines
16 KiB
C++
437 lines
16 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "pythonbuildconfiguration.h"
|
|
|
|
#include "pipsupport.h"
|
|
#include "pyside.h"
|
|
#include "pysideuicextracompiler.h"
|
|
#include "pythonconstants.h"
|
|
#include "pythoneditor.h"
|
|
#include "pythonkitaspect.h"
|
|
#include "pythonlanguageclient.h"
|
|
#include "pythonproject.h"
|
|
#include "pythonsettings.h"
|
|
#include "pythontr.h"
|
|
#include "pythonutils.h"
|
|
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
|
|
|
#include <languageclient/languageclientmanager.h>
|
|
|
|
#include <projectexplorer/buildinfo.h>
|
|
#include <projectexplorer/buildsteplist.h>
|
|
#include <projectexplorer/buildsystem.h>
|
|
#include <projectexplorer/environmentaspect.h>
|
|
#include <projectexplorer/namedwidget.h>
|
|
#include <projectexplorer/processparameters.h>
|
|
#include <projectexplorer/projectexplorer.h>
|
|
#include <projectexplorer/projectnodes.h>
|
|
#include <projectexplorer/runconfiguration.h>
|
|
#include <projectexplorer/target.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/commandline.h>
|
|
#include <utils/detailswidget.h>
|
|
#include <utils/futuresynchronizer.h>
|
|
#include <utils/layoutbuilder.h>
|
|
#include <utils/mimeconstants.h>
|
|
#include <utils/qtcprocess.h>
|
|
|
|
using namespace ProjectExplorer;
|
|
using namespace Utils;
|
|
|
|
namespace Python::Internal {
|
|
|
|
PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id)
|
|
: AbstractProcessStep(bsl, id)
|
|
{
|
|
m_pysideProject.setSettingsKey("Python.PySideProjectTool");
|
|
m_pysideProject.setLabelText(Tr::tr("PySide project tool:"));
|
|
m_pysideProject.setToolTip(Tr::tr("Enter location of PySide project tool."));
|
|
m_pysideProject.setExpectedKind(PathChooser::Command);
|
|
m_pysideProject.setHistoryCompleter("Python.PySideProjectTool.History");
|
|
m_pysideProject.setReadOnly(true);
|
|
|
|
m_pysideUic.setSettingsKey("Python.PySideUic");
|
|
m_pysideUic.setLabelText(Tr::tr("PySide uic tool:"));
|
|
m_pysideUic.setToolTip(Tr::tr("Enter location of PySide uic tool."));
|
|
m_pysideUic.setExpectedKind(PathChooser::Command);
|
|
m_pysideUic.setHistoryCompleter("Python.PySideUic.History");
|
|
m_pysideUic.setReadOnly(true);
|
|
|
|
setCommandLineProvider([this] { return CommandLine(m_pysideProject(), {"build"}); });
|
|
setWorkingDirectoryProvider([this] {
|
|
return m_pysideProject().withNewMappedPath(project()->projectDirectory()); // FIXME: new path needed?
|
|
});
|
|
setEnvironmentModifier([this](Environment &env) {
|
|
env.prependOrSetPath(m_pysideProject().parentDir());
|
|
});
|
|
|
|
connect(target(), &Target::buildSystemUpdated, this, &PySideBuildStep::updateExtraCompilers);
|
|
connect(&m_pysideUic, &BaseAspect::changed, this, &PySideBuildStep::updateExtraCompilers);
|
|
}
|
|
|
|
PySideBuildStep::~PySideBuildStep()
|
|
{
|
|
qDeleteAll(m_extraCompilers);
|
|
}
|
|
|
|
void PySideBuildStep::checkForPySide(const FilePath &python)
|
|
{
|
|
PySideTools tools;
|
|
if (python.isEmpty() || !python.isExecutableFile()) {
|
|
m_pysideProject.setValue(FilePath());
|
|
m_pysideUic.setValue(FilePath());
|
|
return;
|
|
}
|
|
const FilePath dir = python.parentDir();
|
|
tools.pySideProjectPath = dir.pathAppended("pyside6-project").withExecutableSuffix();
|
|
tools.pySideUicPath = dir.pathAppended("pyside6-uic").withExecutableSuffix();
|
|
|
|
if (tools.pySideProjectPath.isExecutableFile() && tools.pySideUicPath.isExecutableFile()) {
|
|
m_pysideProject.setValue(tools.pySideProjectPath.toUserOutput());
|
|
m_pysideUic.setValue(tools.pySideUicPath.toUserOutput());
|
|
} else {
|
|
checkForPySide(python, "PySide6-Essentials");
|
|
}
|
|
}
|
|
|
|
void PySideBuildStep::checkForPySide(const FilePath &python, const QString &pySidePackageName)
|
|
{
|
|
const PipPackage package(pySidePackageName);
|
|
QObject::disconnect(m_watcherConnection);
|
|
m_watcher.reset(new QFutureWatcher<PipPackageInfo>());
|
|
m_watcherConnection = QObject::connect(m_watcher.get(), &QFutureWatcherBase::finished, this,
|
|
[this, python, pySidePackageName] {
|
|
handlePySidePackageInfo(m_watcher->result(), python, pySidePackageName);
|
|
});
|
|
const auto future = Pip::instance(python)->info(package);
|
|
m_watcher->setFuture(future);
|
|
Utils::futureSynchronizer()->addFuture(future);
|
|
}
|
|
|
|
void PySideBuildStep::handlePySidePackageInfo(const PipPackageInfo &pySideInfo,
|
|
const FilePath &python,
|
|
const QString &requestedPackageName)
|
|
{
|
|
const auto findPythonTools = [](const FilePaths &files,
|
|
const FilePath &location,
|
|
const FilePath &python) -> PySideTools {
|
|
PySideTools result;
|
|
const QString pySide6ProjectName
|
|
= OsSpecificAspects::withExecutableSuffix(python.osType(), "pyside6-project");
|
|
const QString pySide6UicName
|
|
= OsSpecificAspects::withExecutableSuffix(python.osType(), "pyside6-uic");
|
|
for (const FilePath &file : files) {
|
|
if (file.fileName() == pySide6ProjectName) {
|
|
result.pySideProjectPath = python.withNewMappedPath(location.resolvePath(file));
|
|
result.pySideProjectPath = result.pySideProjectPath.cleanPath();
|
|
if (!result.pySideUicPath.isEmpty())
|
|
return result;
|
|
} else if (file.fileName() == pySide6UicName) {
|
|
result.pySideUicPath = python.withNewMappedPath(location.resolvePath(file));
|
|
result.pySideUicPath = result.pySideUicPath.cleanPath();
|
|
if (!result.pySideProjectPath.isEmpty())
|
|
return result;
|
|
}
|
|
}
|
|
return {};
|
|
};
|
|
|
|
PySideTools tools = findPythonTools(pySideInfo.files, pySideInfo.location, python);
|
|
if (!tools.pySideProjectPath.isExecutableFile() && requestedPackageName != "PySide6") {
|
|
checkForPySide(python, "PySide6");
|
|
return;
|
|
}
|
|
|
|
m_pysideProject.setValue(tools.pySideProjectPath.toUserOutput());
|
|
m_pysideUic.setValue(tools.pySideUicPath.toUserOutput());
|
|
}
|
|
|
|
Tasking::GroupItem PySideBuildStep::runRecipe()
|
|
{
|
|
using namespace Tasking;
|
|
|
|
const auto onSetup = [this] {
|
|
if (!processParameters()->effectiveCommand().isExecutableFile())
|
|
return SetupResult::StopWithSuccess;
|
|
return SetupResult::Continue;
|
|
};
|
|
|
|
return Group { onGroupSetup(onSetup), defaultProcessTask() };
|
|
}
|
|
|
|
void PySideBuildStep::updateExtraCompilers()
|
|
{
|
|
QList<PySideUicExtraCompiler *> oldCompilers = m_extraCompilers;
|
|
m_extraCompilers.clear();
|
|
|
|
if (m_pysideUic().isExecutableFile()) {
|
|
auto uiMatcher = [](const Node *node) {
|
|
if (const FileNode *fileNode = node->asFileNode())
|
|
return fileNode->fileType() == FileType::Form;
|
|
return false;
|
|
};
|
|
const FilePaths uiFiles = project()->files(uiMatcher);
|
|
for (const FilePath &uiFile : uiFiles) {
|
|
FilePath generated = uiFile.parentDir();
|
|
generated = generated.pathAppended("/ui_" + uiFile.baseName() + ".py");
|
|
int index = Utils::indexOf(oldCompilers, [&](PySideUicExtraCompiler *oldCompiler) {
|
|
return oldCompiler->pySideUicPath() == m_pysideUic()
|
|
&& oldCompiler->project() == project() && oldCompiler->source() == uiFile
|
|
&& oldCompiler->targets() == FilePaths{generated};
|
|
});
|
|
if (index < 0) {
|
|
m_extraCompilers << new PySideUicExtraCompiler(m_pysideUic(),
|
|
project(),
|
|
uiFile,
|
|
{generated},
|
|
this);
|
|
} else {
|
|
m_extraCompilers << oldCompilers.takeAt(index);
|
|
}
|
|
}
|
|
}
|
|
for (LanguageClient::Client *client : LanguageClient::LanguageClientManager::clients()) {
|
|
if (auto pylsClient = qobject_cast<PyLSClient *>(client))
|
|
pylsClient->updateExtraCompilers(project(), m_extraCompilers);
|
|
}
|
|
qDeleteAll(oldCompilers);
|
|
}
|
|
|
|
QList<PySideUicExtraCompiler *> PySideBuildStep::extraCompilers() const
|
|
{
|
|
return m_extraCompilers;
|
|
}
|
|
|
|
Id PySideBuildStep::id()
|
|
{
|
|
return Id("Python.PysideBuildStep");
|
|
}
|
|
|
|
class PythonBuildSettingsWidget : public NamedWidget
|
|
{
|
|
public:
|
|
PythonBuildSettingsWidget(PythonBuildConfiguration *bc)
|
|
: NamedWidget(Tr::tr("Python"))
|
|
{
|
|
using namespace Layouting;
|
|
m_configureDetailsWidget = new DetailsWidget;
|
|
m_configureDetailsWidget->setSummaryText(bc->python().toUserOutput());
|
|
|
|
if (const std::optional<FilePath> venv = bc->venv()) {
|
|
auto details = new QWidget();
|
|
Form{Tr::tr("Effective venv:"), venv->toUserOutput(), br}.attachTo(details);
|
|
m_configureDetailsWidget->setWidget(details);
|
|
} else {
|
|
m_configureDetailsWidget->setState(DetailsWidget::OnlySummary);
|
|
}
|
|
|
|
Column{
|
|
m_configureDetailsWidget,
|
|
noMargin
|
|
}.attachTo(this);
|
|
}
|
|
private:
|
|
DetailsWidget *m_configureDetailsWidget;
|
|
};
|
|
|
|
class PySideBuildStepFactory final : public BuildStepFactory
|
|
{
|
|
public:
|
|
PySideBuildStepFactory()
|
|
{
|
|
registerStep<PySideBuildStep>(PySideBuildStep::id());
|
|
setSupportedProjectType(PythonProjectId);
|
|
setDisplayName(Tr::tr("Run PySide6 project tool"));
|
|
setFlags(BuildStep::UniqueStep);
|
|
}
|
|
};
|
|
|
|
void setupPySideBuildStep()
|
|
{
|
|
static PySideBuildStepFactory thePySideBuildStepFactory;
|
|
}
|
|
|
|
// PythonBuildConfiguration
|
|
|
|
PythonBuildConfiguration::PythonBuildConfiguration(Target *target, const Id &id)
|
|
: BuildConfiguration(target, id)
|
|
, m_buildSystem(std::make_unique<PythonBuildSystem>(this))
|
|
{
|
|
setInitializer([this](const BuildInfo &info) { initialize(info); });
|
|
|
|
updateCacheAndEmitEnvironmentChanged();
|
|
|
|
connect(&PySideInstaller::instance(),
|
|
&PySideInstaller::pySideInstalled,
|
|
this,
|
|
&PythonBuildConfiguration::handlePythonUpdated);
|
|
|
|
auto update = [this] {
|
|
if (isActive()) {
|
|
m_buildSystem->emitBuildSystemUpdated();
|
|
updateDocuments();
|
|
}
|
|
};
|
|
connect(target, &Target::activeBuildConfigurationChanged, this, update);
|
|
connect(project(), &Project::activeTargetChanged, this, update);
|
|
connect(ProjectExplorerPlugin::instance(),
|
|
&ProjectExplorerPlugin::fileListChanged,
|
|
this,
|
|
update);
|
|
connect(PythonSettings::instance(),
|
|
&PythonSettings::virtualEnvironmentCreated,
|
|
this,
|
|
&PythonBuildConfiguration::handlePythonUpdated);
|
|
}
|
|
|
|
NamedWidget *PythonBuildConfiguration::createConfigWidget()
|
|
{
|
|
return new PythonBuildSettingsWidget(this);
|
|
}
|
|
|
|
static QString venvTypeName()
|
|
{
|
|
static QString name = Tr::tr("New Virtual Environment");
|
|
return name;
|
|
}
|
|
|
|
void PythonBuildConfiguration::initialize(const BuildInfo &info)
|
|
{
|
|
buildSteps()->appendStep(PySideBuildStep::id());
|
|
if (info.typeName == venvTypeName()) {
|
|
m_venv = info.buildDirectory;
|
|
const FilePath venvInterpreterPath = info.buildDirectory.resolvePath(
|
|
HostOsInfo::isWindowsHost() ? FilePath::fromUserInput("Scripts/python.exe")
|
|
: FilePath::fromUserInput("bin/python"));
|
|
|
|
updatePython(venvInterpreterPath);
|
|
|
|
if (info.extraInfo.toMap().value("createVenv", false).toBool()
|
|
&& !info.buildDirectory.exists()) {
|
|
if (std::optional<Interpreter> python = PythonKitAspect::python(target()->kit()))
|
|
PythonSettings::createVirtualEnvironment(python->command, info.buildDirectory);
|
|
}
|
|
} else {
|
|
updateInterpreter(PythonKitAspect::python(target()->kit()));
|
|
}
|
|
|
|
updateCacheAndEmitEnvironmentChanged();
|
|
}
|
|
|
|
void PythonBuildConfiguration::updateInterpreter(const std::optional<Interpreter> &python)
|
|
{
|
|
updatePython(python ? python->command : FilePath());
|
|
}
|
|
|
|
void PythonBuildConfiguration::updatePython(const FilePath &python)
|
|
{
|
|
m_python = python;
|
|
if (auto buildStep = buildSteps()->firstOfType<PySideBuildStep>())
|
|
buildStep->checkForPySide(python);
|
|
updateDocuments();
|
|
m_buildSystem->requestParse();
|
|
}
|
|
|
|
void PythonBuildConfiguration::updateDocuments()
|
|
{
|
|
if (isActive()) {
|
|
const FilePaths files = project()->files(Project::AllFiles);
|
|
for (const FilePath &file : files) {
|
|
if (auto doc = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
|
if (auto pyDoc = qobject_cast<PythonDocument *>(doc))
|
|
pyDoc->updatePython(m_python);
|
|
else if (doc->mimeType() == Utils::Constants::QML_MIMETYPE)
|
|
PySideInstaller::instance().checkPySideInstallation(m_python, doc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PythonBuildConfiguration::handlePythonUpdated(const FilePath &python)
|
|
{
|
|
if (!m_python.isEmpty() && python == m_python)
|
|
updatePython(python); // retrigger pyside check
|
|
}
|
|
|
|
static const char pythonKey[] = "python";
|
|
static const char venvKey[] = "venv";
|
|
|
|
void PythonBuildConfiguration::fromMap(const Store &map)
|
|
{
|
|
BuildConfiguration::fromMap(map);
|
|
if (map.contains(venvKey))
|
|
m_venv = FilePath::fromSettings(map[venvKey]);
|
|
updatePython(FilePath::fromSettings(map[pythonKey]));
|
|
}
|
|
|
|
void PythonBuildConfiguration::toMap(Store &map) const
|
|
{
|
|
BuildConfiguration::toMap(map);
|
|
map[pythonKey] = m_python.toSettings();
|
|
if (m_venv)
|
|
map[venvKey] = m_venv->toSettings();
|
|
}
|
|
|
|
BuildSystem *PythonBuildConfiguration::buildSystem() const
|
|
{
|
|
return m_buildSystem.get();
|
|
}
|
|
|
|
FilePath PythonBuildConfiguration::python() const
|
|
{
|
|
return m_python;
|
|
}
|
|
|
|
std::optional<FilePath> PythonBuildConfiguration::venv() const
|
|
{
|
|
return m_venv;
|
|
}
|
|
|
|
class PythonBuildConfigurationFactory final : public BuildConfigurationFactory
|
|
{
|
|
public:
|
|
PythonBuildConfigurationFactory()
|
|
{
|
|
registerBuildConfiguration<PythonBuildConfiguration>("Python.PySideBuildConfiguration");
|
|
setSupportedProjectType(PythonProjectId);
|
|
setSupportedProjectMimeTypeName(Constants::C_PY_PROJECT_MIME_TYPE);
|
|
setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) {
|
|
if (std::optional<Interpreter> python = PythonKitAspect::python(k)) {
|
|
BuildInfo base;
|
|
base.buildDirectory = projectPath.parentDir();
|
|
base.displayName = python->name;
|
|
base.typeName = Tr::tr("Global Python");
|
|
base.showBuildDirConfigWidget = false;
|
|
|
|
if (isVenvPython(python->command) || !venvIsUsable(python->command))
|
|
return QList<BuildInfo>{base};
|
|
|
|
base.enabledByDefault = false;
|
|
|
|
BuildInfo venv;
|
|
const FilePath venvBase = projectPath.parentDir() / ".qtcreator"
|
|
/ FileUtils::fileSystemFriendlyName(python->name + "venv");
|
|
venv.buildDirectory = venvBase;
|
|
int i = 2;
|
|
while (venv.buildDirectory.exists())
|
|
venv.buildDirectory = venvBase.stringAppended('_' + QString::number(i++));
|
|
venv.displayName = python->name + Tr::tr(" Virtual Environment");
|
|
venv.typeName = venvTypeName();
|
|
venv.extraInfo = QVariantMap{{"createVenv", forSetup}};
|
|
return QList<BuildInfo>{base, venv};
|
|
}
|
|
return QList<BuildInfo>{};
|
|
});
|
|
}
|
|
};
|
|
|
|
void setupPythonBuildConfiguration()
|
|
{
|
|
static PythonBuildConfigurationFactory thePythonBuildConfigurationFactory;
|
|
}
|
|
|
|
} // Python::Internal
|