Files
qt-creator/src/plugins/python/pythonbuildconfiguration.cpp
Eike Ziller eb45240649 Remove PluginManager::futureSynchronizer()
Use the global synchronizer from Utils::futureSynchronizer() directly

Change-Id: Ic8843bc1ff7951c041529a258f36117f08ec4b35
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
2024-05-21 13:02:46 +00:00

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