diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index b748886cfbe..d3da2d341c3 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -38,6 +38,7 @@ add_qtc_plugin(QmlDesigner dynamiclicensecheck.h generateresource.cpp generateresource.h openuiqmlfiledialog.cpp openuiqmlfiledialog.h openuiqmlfiledialog.ui + puppetenvironmentbuilder.cpp puppetenvironmentbuilder.h qmldesignerconstants.h qmldesignericons.h qmldesignerplugin.cpp qmldesignerplugin.h diff --git a/src/plugins/qmldesigner/designercore/instances/puppetstartdata.h b/src/plugins/qmldesigner/designercore/instances/puppetstartdata.h new file mode 100644 index 00000000000..1cd476759ed --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/puppetstartdata.h @@ -0,0 +1,21 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace QmlDesigner { + +class PuppetStartData +{ +public: + QString puppetPath; + QString workingDirectoryPath; + QString forwardOutput; + QString freeTypeOption; + QString debugPuppet; + QProcessEnvironment environment; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp new file mode 100644 index 00000000000..63b2ad53599 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "puppetstarter.h" + +#include +#include +#include + +namespace QmlDesigner { + +PuppetStarter::PuppetStarter(ProjectExplorer::Target *target, const Model *model) + + : m_target(target) + , m_availablePuppetType(FallbackPuppet) + , m_model(model) +{ +} + +QProcessUniquePointer PuppetStarter::createPuppetProcess( + const PuppetStartData &data, + const QString &puppetMode, + const QString &socketToken, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const +{ + return puppetProcess(data.puppetPath, + data.workingDirectoryPath, + data.forwardOutput, + data.freeTypeOption, + data.debugPuppet, + data.environment, + puppetMode, + socketToken, + processOutputCallback, + processFinishCallback, + customOptions); +} + +QProcessUniquePointer PuppetStarter::puppetProcess( + const QString &puppetPath, + const QString &workingDirectory, + const QString &forwardOutput, + const QString &freeTypeOption, + const QString &debugPuppet, + const QProcessEnvironment &processEnvironment, + const QString &puppetMode, + const QString &socketToken, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const +{ + QProcessUniquePointer puppetProcess{new QProcess}; + puppetProcess->setObjectName(puppetMode); + puppetProcess->setProcessEnvironment(processEnvironment); + + QObject::connect(QCoreApplication::instance(), + &QCoreApplication::aboutToQuit, + puppetProcess.get(), + &QProcess::kill); + QObject::connect(puppetProcess.get(), + static_cast(&QProcess::finished), + processFinishCallback); + + if (forwardOutput == puppetMode || forwardOutput == "all") { + puppetProcess->setProcessChannelMode(QProcess::MergedChannels); + QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); + } + puppetProcess->setWorkingDirectory(workingDirectory); + + QStringList processArguments; + if (puppetMode == "custom") + processArguments = customOptions; + else + processArguments = {socketToken, puppetMode}; + + processArguments.push_back("-graphicssystem raster"); + processArguments.push_back(freeTypeOption); + + puppetProcess->start(puppetPath, processArguments); + + if (debugPuppet == puppetMode || debugPuppet == "all") { + QMessageBox::information( + nullptr, + QCoreApplication::translate("PuppetStarter", "Puppet is starting..."), + QCoreApplication::translate( + "PuppetStarter", + "You can now attach your debugger to the %1 puppet with process id: %2.") + .arg(puppetMode, QString::number(puppetProcess->processId()))); + } + + return puppetProcess; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/puppetstarter.h b/src/plugins/qmldesigner/designercore/instances/puppetstarter.h new file mode 100644 index 00000000000..9bae37cdb6e --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/puppetstarter.h @@ -0,0 +1,93 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qprocessuniqueptr.h" + +#include + +#include + +#include +#include + +#include + +namespace ProjectExplorer { +class Target; +} // namespace ProjectExplorer + +namespace QmlDesigner { + +class PuppetBuildProgressDialog; +class Model; + +class PuppetStarter +{ +public: + enum PuppetType { FallbackPuppet, UserSpacePuppet, BinPathPuppet }; + + PuppetStarter(ProjectExplorer::Target *target, const Model *model); + + void createQml2PuppetExecutableIfMissing(); + + QProcessUniquePointer createPuppetProcess( + const PuppetStartData &data, + const QString &puppetMode, + const QString &socketToken, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions = {}) const; + + void setQrcMappingString(const QString qrcMapping); + + static QString defaultPuppetToplevelBuildDirectory(); + static QString defaultPuppetFallbackDirectory(); + static QString qmlPuppetFallbackDirectory(const DesignerSettings &settings); + +protected: + bool startBuildProcess(const QString &buildDirectoryPath, + const QString &command, + const QStringList &processArguments = QStringList(), + PuppetBuildProgressDialog *progressDialog = nullptr) const; + static QString puppetSourceDirectoryPath(); + static QString qml2PuppetProjectFile(); + + bool checkPuppetIsReady(const QString &puppetPath) const; + bool qtIsSupported() const; + QProcessUniquePointer puppetProcess(const QString &puppetPath, + const QString &workingDirectory, + const QString &forwardOutput, + const QString &freeTypeOption, + const QString &debugPuppet, + const QProcessEnvironment &processEnvironment, + const QString &puppetMode, + const QString &socketToken, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const; + + QProcessEnvironment processEnvironment() const; + + QString buildCommand() const; + QString qmakeCommand() const; + + QByteArray qtHash() const; + QDateTime qtLastModified() const; + QDateTime puppetSourceLastModified() const; + + bool useOnlyFallbackPuppet() const; + QString getStyleConfigFileName() const; + bool usesVirtualKeyboard() const; + +private: + mutable QString m_compileLog; + ProjectExplorer::Target *m_target = nullptr; + PuppetType m_availablePuppetType; + static QHash m_qml2PuppetForKitPuppetHash; + const Model *m_model = nullptr; + QString m_qrcMapping; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp new file mode 100644 index 00000000000..24e0469f048 --- /dev/null +++ b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp @@ -0,0 +1,241 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "puppetenvironmentbuilder.h" +#include "designersettings.h" +#include "qmldesignerplugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +namespace { +Q_LOGGING_CATEGORY(puppetEnvirmentBuild, "qtc.puppet.environmentBuild", QtWarningMsg) + +void filterOutQtBaseImportPath(QStringList *stringList) +{ + Utils::erase(*stringList, [](const QString &string) { + QDir dir(string); + return dir.dirName() == "qml" + && !dir.entryInfoList(QStringList("QtTest"), QDir::Dirs).isEmpty(); + }); +} + +Utils::FilePath pathForBinPuppet(ProjectExplorer::Target *target) +{ + if (!target || !target->kit()) + return {}; + + QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); + + if (currentQtVersion) + return currentQtVersion->binPath().pathAppended("qml2puppet").withExecutableSuffix(); + + return {}; +} + +} // namespace + +QProcessEnvironment PuppetEnvironmentBuilder::processEnvironment() const +{ + qCInfo(puppetEnvirmentBuild) << Q_FUNC_INFO; + m_availablePuppetType = determinePuppetType(); + m_environment = Utils::Environment::systemEnvironment(); + + addKit(); + addRendering(); + addControls(); + addPixelRatio(); + addVirtualKeyboard(); + addForceQApplication(); + addImportPaths(); + addCustomFileSelectors(); + + qCInfo(puppetEnvirmentBuild) << "Puppet environment:" << m_environment.toStringList(); + + return m_environment.toProcessEnvironment(); +} + +bool PuppetEnvironmentBuilder::usesVirtualKeyboard() const +{ + if (m_target) { + auto *qmlbuild = qobject_cast(m_target->buildSystem()); + + const Utils::EnvironmentItem virtualKeyboard("QT_IM_MODULE", "qtvirtualkeyboard"); + return qmlbuild && qmlbuild->environment().indexOf(virtualKeyboard); + } + + return false; +} + +QString PuppetEnvironmentBuilder::getStyleConfigFileName() const +{ + if (m_target) { + for (const Utils::FilePath &fileName : + m_target->project()->files(ProjectExplorer::Project::SourceFiles)) { + if (fileName.fileName() == "qtquickcontrols2.conf") + return fileName.toString(); + } + } + + return {}; +} + +void PuppetEnvironmentBuilder::addKit() const +{ + if (m_target) { + if (m_availablePuppetType == PuppetType::Kit) { + m_target->kit()->addToBuildEnvironment(m_environment); + const QtSupport::QtVersion *qt = QtSupport::QtKitAspect::qtVersion(m_target->kit()); + if (qt) { // Kits without a Qt version should not have a puppet! + // Update PATH to include QT_HOST_BINS + m_environment.prependOrSetPath(qt->hostBinPath()); + } + } + } +} + +void PuppetEnvironmentBuilder::addRendering() const +{ + m_environment.set("QML_BAD_GUI_RENDER_LOOP", "true"); + m_environment.set("QML_PUPPET_MODE", "true"); + m_environment.set("QML_DISABLE_DISK_CACHE", "true"); + m_environment.set("QMLPUPPET_RENDER_EFFECTS", "true"); + if (!m_environment.hasKey("QT_SCREEN_SCALE_FACTORS") && !m_environment.hasKey("QT_SCALE_FACTOR")) + m_environment.set("QT_AUTO_SCREEN_SCALE_FACTOR", "1"); + + const bool smoothRendering = m_designerSettings.value(DesignerSettingsKey::SMOOTH_RENDERING).toBool(); + + if (smoothRendering) + m_environment.set("QMLPUPPET_SMOOTH_RENDERING", "true"); +} + +void PuppetEnvironmentBuilder::addControls() const +{ + const QString controlsStyle = m_designerSettings.value(DesignerSettingsKey::CONTROLS_STYLE).toString(); + + if (!controlsStyle.isEmpty()) { + m_environment.set("QT_QUICK_CONTROLS_STYLE", controlsStyle); + m_environment.set("QT_LABS_CONTROLS_STYLE", controlsStyle); + } + + const QString styleConfigFileName = getStyleConfigFileName(); + + if (!styleConfigFileName.isEmpty()) + m_environment.appendOrSet("QT_QUICK_CONTROLS_CONF", styleConfigFileName); +} + +void PuppetEnvironmentBuilder::addPixelRatio() const +{ + m_environment.set("FORMEDITOR_DEVICE_PIXEL_RATIO", + QString::number(QmlDesignerPlugin::formEditorDevicePixelRatio())); +} + +void PuppetEnvironmentBuilder::addVirtualKeyboard() const +{ + if (usesVirtualKeyboard()) { + m_environment.set("QT_IM_MODULE", "qtvirtualkeyboard"); + m_environment.set("QT_VIRTUALKEYBOARD_DESKTOP_DISABLE", "1"); + } +} + +void PuppetEnvironmentBuilder::addQuick3D() const +{ + // set env var if QtQuick3D import exists + QmlDesigner::Import import = QmlDesigner::Import::createLibraryImport("QtQuick3D", "1.0"); + if (m_model.hasImport(import, true, true)) + m_environment.set("QMLDESIGNER_QUICK3D_MODE", "true"); + + import = QmlDesigner::Import::createLibraryImport("QtQuick3D.Particles3D", "1.0"); + if (m_model.hasImport(import, true, true)) + m_environment.set("QMLDESIGNER_QUICK3D_PARTICLES3D_MODE", "true"); + + bool particlemode = m_designerSettings.value("particleMode").toBool(); + if (!particlemode) + m_environment.set("QT_QUICK3D_DISABLE_PARTICLE_SYSTEMS", "1"); + else + m_environment.set("QT_QUICK3D_EDITOR_PARTICLE_SYSTEMS", "1"); +} + +void PuppetEnvironmentBuilder::addForceQApplication() const +{ + auto import = QmlDesigner::Import::createLibraryImport("QtCharts", "2.0"); + if (m_model.hasImport(import, true, true)) { + m_environment.set("QMLDESIGNER_FORCE_QAPPLICATION", "true"); + } else if (m_target) { + auto bs = qobject_cast(m_target->buildSystem()); + if (bs && bs->widgetApp()) + m_environment.set("QMLDESIGNER_FORCE_QAPPLICATION", "true"); + } +} + +void PuppetEnvironmentBuilder::addMultiLanguageDatatbase() const +{ + if (m_target) { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_target)) { + if (!multiLanguageAspect->databaseFilePath().isEmpty()) + m_environment.set("QT_MULTILANGUAGE_DATABASE", + multiLanguageAspect->databaseFilePath().toString()); + } + } +} + +void PuppetEnvironmentBuilder::addImportPaths() const +{ + QStringList importPaths = m_model.importPaths(); + + if (m_availablePuppetType == PuppetType::Fallback) + filterOutQtBaseImportPath(&importPaths); + + if (m_target) { + QStringList designerImports = m_target->additionalData("QmlDesignerImportPath").toStringList(); + importPaths.append(designerImports); + } + + if (m_availablePuppetType == PuppetType::Fallback) + importPaths.prepend(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); + + constexpr auto pathSep = Utils::HostOsInfo::pathListSeparator(); + m_environment.appendOrSet("QML2_IMPORT_PATH", importPaths.join(pathSep), pathSep); + + qCInfo(puppetEnvirmentBuild) << "Puppet import paths:" << importPaths; +} + +void PuppetEnvironmentBuilder::addCustomFileSelectors() const +{ + QStringList customFileSelectors; + + if (m_target) + customFileSelectors = m_target->additionalData("CustomFileSelectorsData").toStringList(); + + customFileSelectors.append("DesignMode"); + + constexpr auto pathSep = Utils::HostOsInfo::pathListSeparator(); + if (!customFileSelectors.isEmpty()) + m_environment.appendOrSet("QML_FILE_SELECTORS", customFileSelectors.join(","), pathSep); + + qCInfo(puppetEnvirmentBuild) << "Puppet selectors:" << customFileSelectors; +} + +PuppetType PuppetEnvironmentBuilder::determinePuppetType() const +{ + if (m_target && m_target->kit() && m_target->kit()->isValid()) { + if (pathForBinPuppet(m_target).isExecutableFile()) + return PuppetType::Kit; + } + + return PuppetType::Fallback; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/puppetenvironmentbuilder.h b/src/plugins/qmldesigner/puppetenvironmentbuilder.h new file mode 100644 index 00000000000..09aab672a20 --- /dev/null +++ b/src/plugins/qmldesigner/puppetenvironmentbuilder.h @@ -0,0 +1,54 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +enum class PuppetType { Fallback, UserSpace, Kit }; + +class PuppetEnvironmentBuilder +{ +public: + PuppetEnvironmentBuilder(ProjectExplorer::Target *target, + const class DesignerSettings &designerSettings, + const class Model &model) + : m_target(target) + , m_designerSettings(designerSettings) + , m_model(model) + {} + + QProcessEnvironment processEnvironment() const; + +private: + PuppetType determinePuppetType() const; + bool usesVirtualKeyboard() const; + QString getStyleConfigFileName() const; + void addKit() const; + void addRendering() const; + void addControls() const; + void addPixelRatio() const; + void addVirtualKeyboard() const; + void addQuick3D() const; + void addForceQApplication() const; + void addMultiLanguageDatatbase() const; + void addImportPaths() const; + void addCustomFileSelectors() const; + +private: + ProjectExplorer::Target *m_target = nullptr; + const DesignerSettings &m_designerSettings; + const Model &m_model; + mutable PuppetType m_availablePuppetType = {}; + mutable Utils::Environment m_environment; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignercore.cmake b/src/plugins/qmldesigner/qmldesignercore.cmake index c2dd2d6d5a3..7863ba30726 100644 --- a/src/plugins/qmldesigner/qmldesignercore.cmake +++ b/src/plugins/qmldesigner/qmldesignercore.cmake @@ -225,6 +225,9 @@ function(extend_with_qmldesigner_core target_name) instances/puppetbuildprogressdialog.h instances/puppetcreator.cpp instances/puppetcreator.h + instances/puppetstarter.cpp + instances/puppetstarter.h + instances/puppetstartdata.h instances/puppetdialog.cpp instances/puppetdialog.h instances/qprocessuniqueptr.h