aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/python/pythonbuildconfiguration.cpp
diff options
context:
space:
mode:
authorDavid Schulz <[email protected]>2023-11-01 15:05:03 +0100
committerDavid Schulz <[email protected]>2023-11-28 12:17:14 +0000
commit09e94ae4ac040ad313b7b86e62489dee7cd3804a (patch)
tree1e907c076ba18b326b413ed5ac551db4a3e5e380 /src/plugins/python/pythonbuildconfiguration.cpp
parent12428bf1d67bd282c5276383c91d968451b44036 (diff)
Python: use kits page in python wizards
Change-Id: I1f7aaf145443481546abb868c8c167186600b848 Reviewed-by: Christian Stenger <[email protected]>
Diffstat (limited to 'src/plugins/python/pythonbuildconfiguration.cpp')
-rw-r--r--src/plugins/python/pythonbuildconfiguration.cpp415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/plugins/python/pythonbuildconfiguration.cpp b/src/plugins/python/pythonbuildconfiguration.cpp
new file mode 100644
index 00000000000..f320dce3cc2
--- /dev/null
+++ b/src/plugins/python/pythonbuildconfiguration.cpp
@@ -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