forked from qt-creator/qt-creator
Python: use kits page in python wizards
Change-Id: I1f7aaf145443481546abb868c8c167186600b848 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
415
src/plugins/python/pythonbuildconfiguration.cpp
Normal file
415
src/plugins/python/pythonbuildconfiguration.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
// 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 <extensionsystem/pluginmanager.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/futuresynchronizer.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/process.h>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace Python::Internal {
|
||||
|
||||
PySideBuildStepFactory::PySideBuildStepFactory()
|
||||
{
|
||||
registerStep<PySideBuildStep>(PySideBuildStep::id());
|
||||
setSupportedProjectType(PythonProjectId);
|
||||
setDisplayName(Tr::tr("Run PySide6 project tool"));
|
||||
setFlags(BuildStep::UniqueStep);
|
||||
}
|
||||
|
||||
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, [=] {
|
||||
handlePySidePackageInfo(m_watcher->result(), python, pySidePackageName);
|
||||
});
|
||||
const auto future = Pip::instance(python)->info(package);
|
||||
m_watcher->setFuture(future);
|
||||
ExtensionSystem::PluginManager::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;
|
||||
};
|
||||
|
||||
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();
|
||||
const FilePaths files = project()->files(Project::AllFiles);
|
||||
for (const FilePath &file : files) {
|
||||
if (auto doc = qobject_cast<PythonDocument *>(
|
||||
Core::DocumentModel::documentForFilePath(file))) {
|
||||
doc->updatePython(m_python);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
if (isActive()) {
|
||||
const FilePaths files = project()->files(Project::AllFiles);
|
||||
for (const FilePath &file : files) {
|
||||
if (auto doc = qobject_cast<PythonDocument *>(
|
||||
Core::DocumentModel::documentForFilePath(file))) {
|
||||
doc->updatePython(m_python);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_buildSystem->requestParse();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
PythonBuildConfigurationFactory::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))
|
||||
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>{};
|
||||
});
|
||||
}
|
||||
|
||||
} // Python::Internal
|
||||
Reference in New Issue
Block a user