// Copyright (C) 2019 Luxoft Sweden AB // Copyright (C) 2018 Pelagicore AG // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "appmanagerruncontrol.h" #include "appmanagerconstants.h" #include "appmanagerstringaspect.h" #include "appmanagertargetinformation.h" #include "appmanagertr.h" #include "appmanagerutilities.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace AppManager::Internal { // AppManagerRunner class AppManagerRunner final : public SimpleTargetRunner { public: AppManagerRunner(RunControl *runControl) : SimpleTargetRunner(runControl) { setId("ApplicationManagerPlugin.Run.TargetRunner"); connect(this, &RunWorker::stopped, this, [this, runControl] { appendMessage(Tr::tr("%1 exited.").arg(runControl->runnable().command.toUserOutput()), OutputFormat::NormalMessageFormat); }); setStartModifier([this, runControl] { FilePath controller = runControl->aspectData()->filePath; QString appId = runControl->aspectData()->value; QString instanceId = runControl->aspectData()->value; QString documentUrl = runControl->aspectData()->value; QStringList envVars; if (auto envAspect = runControl->aspectData()) envVars = envAspect->environment.toStringList(); // Always use the default environment to start the appman-controller in // The env variables from the EnvironmentAspect are set through the controller setEnvironment({}); // Prevent the write channel to be closed, otherwise the appman-controller will exit setProcessMode(ProcessMode::Writer); CommandLine cmd{controller}; if (!instanceId.isEmpty()) cmd.addArgs({"--instance-id", instanceId}); if (envVars.isEmpty()) { cmd.addArgs({"start-application", "-eio", appId}); } else { cmd.addArgs({"debug-application", "-eio"}); cmd.addArg(envVars.join(' ')); cmd.addArg(appId); } if (!documentUrl.isEmpty()) cmd.addArg(documentUrl); setCommandLine(cmd); }); } }; // AppManInferiorRunner class AppManInferiorRunner : public SimpleTargetRunner { public: AppManInferiorRunner(RunControl *runControl, bool usePerf, bool useGdbServer, bool useQmlServer, QmlDebug::QmlDebugServicesPreset qmlServices) : SimpleTargetRunner(runControl), m_usePerf(usePerf), m_useGdbServer(useGdbServer), m_useQmlServer(useQmlServer), m_qmlServices(qmlServices) { setId(AppManager::Constants::DEBUG_LAUNCHER_ID); setEssential(true); if (usePerf) { suppressDefaultStdOutHandling(); runControl->setProperty("PerfProcess", QVariant::fromValue(process())); } m_portsGatherer = new Debugger::DebugServerPortsGatherer(runControl); m_portsGatherer->setUseGdbServer(useGdbServer || usePerf); m_portsGatherer->setUseQmlServer(useQmlServer); addStartDependency(m_portsGatherer); setStartModifier([this, runControl] { FilePath controller = runControl->aspectData()->filePath; QString appId = runControl->aspectData()->value; QString instanceId = runControl->aspectData()->value; QString documentUrl = runControl->aspectData()->value; QStringList envVars; if (auto envAspect = runControl->aspectData()) envVars = envAspect->environment.toStringList(); const int gdbServerPort = m_portsGatherer->gdbServer().port(); const int qmlServerPort = m_portsGatherer->qmlServer().port(); CommandLine cmd{controller}; if (!instanceId.isEmpty()) cmd.addArgs({"--instance-id", instanceId}); cmd.addArg("debug-application"); if (m_useGdbServer || m_useQmlServer) { QStringList debugArgs; debugArgs.append(envVars.join(' ')); if (m_useGdbServer) { debugArgs.append(QString("gdbserver :%1").arg(gdbServerPort)); } if (m_useQmlServer) { debugArgs.append(QString("%program% %1 %arguments%") .arg(qmlDebugCommandLineArguments(m_qmlServices, QString("port:%1").arg(qmlServerPort), true))); } cmd.addArg(debugArgs.join(' ')); } if (m_usePerf) { const Store perfArgs = runControl->settingsData(PerfProfiler::Constants::PerfSettingsId); const QString recordArgs = perfArgs[PerfProfiler::Constants::PerfRecordArgsId].toString(); cmd.addArg(QString("perf record %1 -o - --").arg(recordArgs)); } cmd.addArg("-eio"); cmd.addArg(appId); if (!documentUrl.isEmpty()) cmd.addArg(documentUrl); // Always use the default environment to start the appman-controller in // The env variables from the EnvironmentAspect are set through the controller setEnvironment({}); // Prevent the write channel to be closed, otherwise the appman-controller will exit setProcessMode(ProcessMode::Writer); setCommandLine(cmd); appendMessage(Tr::tr("Starting Application Manager debugging..."), NormalMessageFormat); appendMessage(Tr::tr("Using: %1.").arg(cmd.toUserOutput()), NormalMessageFormat); }); } QUrl perfServer() const { return m_portsGatherer->gdbServer(); } QUrl gdbServer() const { return m_portsGatherer->gdbServer(); } QUrl qmlServer() const { return m_portsGatherer->qmlServer(); } private: Debugger::DebugServerPortsGatherer *m_portsGatherer = nullptr; bool m_usePerf; bool m_useGdbServer; bool m_useQmlServer; QmlDebug::QmlDebugServicesPreset m_qmlServices; }; // AppManagerDebugSupport class AppManagerDebugSupport final : public Debugger::DebuggerRunTool { private: FilePath m_symbolFile; AppManInferiorRunner *m_debuggee = nullptr; public: AppManagerDebugSupport(RunControl *runControl) : DebuggerRunTool(runControl) { setId("ApplicationManagerPlugin.Debug.Support"); m_debuggee = new AppManInferiorRunner(runControl, false, isCppDebugging(), isQmlDebugging(), QmlDebug::QmlDebuggerServices); addStartDependency(m_debuggee); addStopDependency(m_debuggee); Target *target = runControl->target(); const Internal::TargetInformation targetInformation(target); if (!targetInformation.isValid()) return; if (targetInformation.manifest.isQmlRuntime()) { m_symbolFile = getToolFilePath(Constants::APPMAN_LAUNCHER_QML, target->kit(), DeviceKitAspect::device(target->kit())); } else if (targetInformation.manifest.isNativeRuntime()) { m_symbolFile = Utils::findOrDefault(target->buildSystem()->applicationTargets(), [&](const BuildTargetInfo &ti) { return ti.buildKey == targetInformation.manifest.code || ti.projectFilePath.toString() == targetInformation.manifest.code; }).targetFilePath; } else { reportFailure(Tr::tr("Cannot debug: Only QML and native applications are supported.")); } } private: void start() override { if (m_symbolFile.isEmpty()) { reportFailure(Tr::tr("Cannot debug: Local executable is not set.")); return; } setStartMode(Debugger::AttachToRemoteServer); setCloseMode(Debugger::KillAndExitMonitorAtClose); if (isQmlDebugging()) setQmlServer(m_debuggee->qmlServer()); if (isCppDebugging()) { setUseExtendedRemote(false); setUseContinueInsteadOfRun(true); setContinueAfterAttach(true); setRemoteChannel(m_debuggee->gdbServer()); setSymbolFile(m_symbolFile); QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(runControl()->kit()); if (version) { setSolibSearchPath(version->qtSoPaths()); addSearchDirectory(version->qmlPath()); } auto sysroot = SysRootKitAspect().sysRoot(runControl()->kit()); if (sysroot.isEmpty()) setSysRoot("/"); else setSysRoot(sysroot); } DebuggerRunTool::start(); } }; // AppManagerQmlToolingSupport class AppManagerQmlToolingSupport final : public RunWorker { public: explicit AppManagerQmlToolingSupport(RunControl *runControl) : RunWorker(runControl) { setId("AppManagerQmlToolingSupport"); QmlDebug::QmlDebugServicesPreset services = QmlDebug::servicesForRunMode(runControl->runMode()); m_runner = new AppManInferiorRunner(runControl, false, false, true, services); addStartDependency(m_runner); addStopDependency(m_runner); m_worker = runControl->createWorker(QmlDebug::runnerIdForRunMode(runControl->runMode())); m_worker->addStartDependency(this); addStopDependency(m_worker); // Make sure the QML Profiler is stopped before the appman-controller m_runner->addStopDependency(m_worker); } private: void start() final { m_worker->recordData("QmlServerUrl", m_runner->qmlServer()); reportStarted(); } AppManInferiorRunner *m_runner = nullptr; RunWorker *m_worker = nullptr; }; // AppManagerDevicePerfProfilerSupport class AppManagerPerfProfilerSupport final : public RunWorker { public: explicit AppManagerPerfProfilerSupport(RunControl *runControl) : RunWorker(runControl) { setId("AppManagerPerfProfilerSupport"); m_profilee = new AppManInferiorRunner(runControl, true, false, false, QmlDebug::NoQmlDebugServices); addStartDependency(m_profilee); addStopDependency(m_profilee); } private: AppManInferiorRunner *m_profilee = nullptr; }; // Factories class AppManagerRunWorkerFactory final : public RunWorkerFactory { public: AppManagerRunWorkerFactory() { setProduct(); addSupportedRunMode(ProjectExplorer::Constants::NORMAL_RUN_MODE); addSupportedRunConfig(Constants::RUNCONFIGURATION_ID); addSupportedRunConfig(Constants::RUNANDDEBUGCONFIGURATION_ID); } }; class AppManagerDebugWorkerFactory final : public RunWorkerFactory { public: AppManagerDebugWorkerFactory() { setProduct(); addSupportedRunMode(ProjectExplorer::Constants::DEBUG_RUN_MODE); addSupportedRunConfig(Constants::RUNANDDEBUGCONFIGURATION_ID); } }; class AppManagerQmlToolingWorkerFactory final : public RunWorkerFactory { public: AppManagerQmlToolingWorkerFactory() { setProduct(); addSupportedRunMode(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); addSupportedRunMode(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); addSupportedRunConfig(Constants::RUNANDDEBUGCONFIGURATION_ID); } }; class AppManagerPerfProfilerWorkerFactory final : public RunWorkerFactory { public: AppManagerPerfProfilerWorkerFactory() { setProduct(); addSupportedRunMode("PerfRecorder"); addSupportedRunConfig(Constants::RUNANDDEBUGCONFIGURATION_ID); } }; void setupAppManagerRunWorker() { static AppManagerRunWorkerFactory theAppManagerRunWorkerFactory; } void setupAppManagerDebugWorker() { static AppManagerDebugWorkerFactory theAppManagerDebugWorkerFactory; } void setupAppManagerQmlToolingWorker() { static AppManagerQmlToolingWorkerFactory theAppManagerQmlToolingWorkerFactory; } void setupAppManagerPerfProfilerWorker() { static AppManagerPerfProfilerWorkerFactory theAppManagerPerfProfilerWorkerFactory; } } // AppManager::Internal