diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 13aee2f2de9..ec5e3a4b89f 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -1977,6 +1977,9 @@ static FilePaths librarySearchPaths(const CMakeBuildSystem *bs, const QString &b const QList CMakeBuildSystem::appTargets() const { + const CMakeConfig &cm = configurationFromCMake(); + QString emulator = cm.stringValueOf("CMAKE_CROSSCOMPILING_EMULATOR"); + QList appTargetList; const bool forAndroid = DeviceTypeKitAspect::deviceTypeId(kit()) == Android::Constants::ANDROID_DEVICE_TYPE; @@ -1989,6 +1992,15 @@ const QList CMakeBuildSystem::appTargets() const BuildTargetInfo bti; bti.displayName = ct.title; + if (ct.launchers.size() > 0) + bti.launchers = ct.launchers; + else if (!emulator.isEmpty()) { + // fallback for cmake < 3.29 + QStringList args = emulator.split(";"); + FilePath command = FilePath::fromString(args.takeFirst()); + LauncherInfo launcherInfo = { "emulator", command, args }; + bti.launchers.append(Launcher(launcherInfo, ct.sourceDirectory)); + } bti.targetFilePath = ct.executable; bti.projectFilePath = ct.sourceDirectory.cleanPath(); bti.workingDirectory = ct.workingDirectory; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildtarget.h b/src/plugins/cmakeprojectmanager/cmakebuildtarget.h index d40729a3970..934070bdfd1 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildtarget.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildtarget.h @@ -7,6 +7,7 @@ #include #include +#include #include @@ -30,6 +31,7 @@ class CMAKE_EXPORT CMakeBuildTarget public: QString title; Utils::FilePath executable; // TODO: rename to output? + QList launchers; TargetType targetType = UtilityType; bool linksToQtGui = false; bool qtcRunnable = true; diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index 6f4050a3aa4..7a6de1749de 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -342,6 +342,22 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t, } ct.libraryDirectories = filteredUnique(librarySeachPaths); qCInfo(cmakeLogger) << "libraryDirectories for target" << ct.title << ":" << ct.libraryDirectories; + + // If there are start programs, there should also be an option to select none + if (!t.launcherInfos.isEmpty()) { + LauncherInfo info { "unused", Utils::FilePath(), QStringList() }; + ct.launchers.append(Launcher(info, sourceDirectory)); + } + // if there is a test and an emulator launcher, add the emulator and + // also a combination as the last entry, but not the "test" launcher + // as it will not work for cross-compiled executables + if (t.launcherInfos.size() == 2 && t.launcherInfos[0].type == "test" && t.launcherInfos[1].type == "emulator") { + ct.launchers.append(Launcher(t.launcherInfos[1], sourceDirectory)); + ct.launchers.append(Launcher(t.launcherInfos[0], t.launcherInfos[1], sourceDirectory)); + } else if (t.launcherInfos.size() == 1) { + Launcher launcher(t.launcherInfos[0], sourceDirectory); + ct.launchers.append(launcher); + } } return ct; } diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp index d404d124db8..d9ea457600f 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -650,6 +651,19 @@ static TargetDetails extractTargetDetails(const QJsonObject &root, QString &erro }; }); } + { + const QJsonArray launchers = root.value("launchers").toArray(); + if (launchers.size() > 0) { + t.launcherInfos = transform(launchers, [](const QJsonValue &v) { + const QJsonObject o = v.toObject(); + QList arguments; + for (const QJsonValue &arg : o.value("arguments").toArray()) + arguments.append(arg.toString()); + FilePath command = FilePath::fromString(o.value("command").toString()); + return ProjectExplorer::LauncherInfo { o.value("type").toString(), command, arguments }; + }); + } + } return t; } diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.h b/src/plugins/cmakeprojectmanager/fileapiparser.h index 9043d38c260..f6a59edae2a 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.h +++ b/src/plugins/cmakeprojectmanager/fileapiparser.h @@ -197,6 +197,7 @@ public: QList artifacts; QString installPrefix; std::vector installDestination; + QList launcherInfos; std::optional link; std::optional archive; std::vector dependencies; diff --git a/src/plugins/projectexplorer/buildtargetinfo.h b/src/plugins/projectexplorer/buildtargetinfo.h index 4ba1c4de5b4..c99dec64308 100644 --- a/src/plugins/projectexplorer/buildtargetinfo.h +++ b/src/plugins/projectexplorer/buildtargetinfo.h @@ -5,12 +5,15 @@ #include "projectexplorer_export.h" +#include "runconfiguration.h" + #include #include #include namespace ProjectExplorer { +class Launcher; class PROJECTEXPLORER_EXPORT BuildTargetInfo { @@ -19,6 +22,7 @@ public: QString displayName; QString displayNameUniquifier; + QList launchers; Utils::FilePath targetFilePath; Utils::FilePath projectFilePath; Utils::FilePath workingDirectory; diff --git a/src/plugins/projectexplorer/desktoprunconfiguration.cpp b/src/plugins/projectexplorer/desktoprunconfiguration.cpp index a8d3284e9be..02f31c13452 100644 --- a/src/plugins/projectexplorer/desktoprunconfiguration.cpp +++ b/src/plugins/projectexplorer/desktoprunconfiguration.cpp @@ -70,6 +70,7 @@ private: FilePath executableToRun(const BuildTargetInfo &targetInfo) const; const Kind m_kind; + LauncherAspect launcher{this}; EnvironmentAspect environment{this}; ExecutableAspect executable{this}; ArgumentsAspect arguments{this}; @@ -90,6 +91,8 @@ void DesktopRunConfiguration::updateTargetInformation() auto terminalAspect = aspect(); terminalAspect->setUseTerminalHint(bti.targetFilePath.needsDevice() ? false : bti.usesTerminal); terminalAspect->setEnabled(!bti.targetFilePath.needsDevice()); + auto launcherAspect = aspect(); + launcherAspect->setVisible(false); if (m_kind == Qmake) { @@ -121,6 +124,12 @@ void DesktopRunConfiguration::updateTargetInformation() } else if (m_kind == CMake) { + if (bti.launchers.size() > 0) { + launcherAspect->setVisible(true); + // Use start program by default, if defined (see toBuildTarget() for details) + launcherAspect->setDefaultLauncher(bti.launchers.last()); + launcherAspect->updateLaunchers(bti.launchers); + } aspect()->setExecutable(bti.targetFilePath); aspect()->setDefaultWorkingDirectory(bti.workingDirectory); emit aspect()->environmentChanged(); diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index 2f5dfc2513d..3c78b37cc44 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -173,6 +173,9 @@ RunConfiguration::RunConfiguration(Target *target, Utils::Id id) m_commandLineGetter = [this] { + Launcher launcher; + if (const auto launcherAspect = aspect()) + launcher = launcherAspect->currentLauncher(); FilePath executable; if (const auto executableAspect = aspect()) executable = executableAspect->executable(); @@ -180,7 +183,14 @@ RunConfiguration::RunConfiguration(Target *target, Utils::Id id) if (const auto argumentsAspect = aspect()) arguments = argumentsAspect->arguments(); - return CommandLine{executable, arguments, CommandLine::Raw}; + if (launcher.command.isEmpty()) + return CommandLine{executable, arguments, CommandLine::Raw}; + + CommandLine launcherCommand(launcher.command, launcher.arguments); + launcherCommand.addArg(executable.toString()); + launcherCommand.addArgs(arguments, CommandLine::Raw); + + return launcherCommand; }; } diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index 2054a840c14..b49ad3e7d77 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -28,6 +28,51 @@ class RunConfigurationFactory; class RunConfiguration; class RunConfigurationCreationInfo; class Target; +class BuildTargetInfo; + +/** + * Contains start program entries that are retrieved + * from the cmake file api + */ +class LauncherInfo +{ +public: + QString type; + Utils::FilePath command; + QStringList arguments; +}; + +/** + * Contains a start program entry that is displayed in the run configuration interface. + * + * This follows the design for the use of "Test Launcher", the + * Wrappers for running executables on the host system and "Emulator", + * wrappers for cross-compiled applications, which are supported for + * example by the cmake build system. + */ +class PROJECTEXPLORER_EXPORT Launcher +{ +public: + Launcher() = default; + + /// Create a single launcher from the \p launcherInfo parameter, which can be of type "Test launcher" or "Emulator" + Launcher(const LauncherInfo &launcherInfo, const Utils::FilePath &sourceDirectory); + + /// Create a combined launcher from the passed info parameters, with \p testLauncherInfo + /// as first and \p emulatorLauncherInfo appended + Launcher(const LauncherInfo &testLauncherInfo, const LauncherInfo &emulatorlauncherInfo, const Utils::FilePath &sourceDirectory); + + bool operator==(const Launcher &other) const + { + return id == other.id && displayName == other.displayName && command == other.command + && arguments == other.arguments; + } + + QString id; + QString displayName; + Utils::FilePath command; + QStringList arguments; +}; /** * An interface to facilitate switching between hunks of diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp index 01003a97668..6f49b61c0e8 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.cpp +++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp @@ -797,6 +797,156 @@ Interpreter::Interpreter(const QString &_id, , autoDetected(_autoDetected) {} +static QString launcherType2UiString(const QString &type) +{ + if (type == "test") + return Tr::tr("Test"); + else if (type == "emulator") + return Tr::tr("Emulator"); + return QString(); +} + +Launcher::Launcher(const LauncherInfo &launcherInfo, const FilePath &sourceDirectory) + : id(launcherInfo.type) + , arguments(launcherInfo.arguments) +{ + if (launcherInfo.type != "unused") { + command = launcherInfo.command; + if (command.isRelativePath()) + command = sourceDirectory.resolvePath(command); + displayName = QString("%1 (%2)").arg(launcherType2UiString(launcherInfo.type), + CommandLine(command, arguments).displayName()); + } +} + +Launcher::Launcher(const LauncherInfo &testLauncherInfo, const LauncherInfo &emulatorLauncherInfo, const Utils::FilePath &sourceDirectory) + : id(testLauncherInfo.type + " + " + emulatorLauncherInfo.type) + , command(testLauncherInfo.command) + , arguments(testLauncherInfo.arguments) +{ + if (command.isRelativePath()) + command = sourceDirectory.resolvePath(command); + FilePath command1 = emulatorLauncherInfo.command; + if (command1.isRelativePath()) + command1 = sourceDirectory.resolvePath(command1); + arguments.append(command1.toString()); + arguments.append(emulatorLauncherInfo.arguments); + displayName = QString("%1 + %2 (%3)").arg(launcherType2UiString(testLauncherInfo.type), + launcherType2UiString(emulatorLauncherInfo.type), + CommandLine(command, arguments).displayName()); +} + +/*! +\class ProjectExplorer::LauncherAspect +\inmodule QtCreator + +\brief With the LauncherAspect class, a user can specify a launcher program for +use with executable files for which a launcher program is optionally available. +*/ + +LauncherAspect::LauncherAspect(AspectContainer *container) + : BaseAspect(container) +{ + addDataExtractor(this, &LauncherAspect::currentLauncher, &Data::launcher); +} + +Launcher LauncherAspect::currentLauncher() const +{ + return Utils::findOrDefault(m_launchers, Utils::equal(&Launcher::id, m_currentId)); +} + +void LauncherAspect::updateLaunchers(const QList &launchers) +{ + if (m_launchers == launchers) + return; + m_launchers = launchers; + if (m_comboBox) + updateComboBox(); +} + +void LauncherAspect::setDefaultLauncher(const Launcher &launcher) +{ + if (m_defaultId == launcher.id) + return; + m_defaultId = launcher.id; + if (m_currentId.isEmpty()) + setCurrentLauncher(launcher); +} + +void LauncherAspect::setCurrentLauncher(const Launcher &launcher) +{ + if (m_comboBox) { + const int index = m_launchers.indexOf(launcher); + if (index < 0 || index >= m_comboBox->count()) + return; + m_comboBox->setCurrentIndex(index); + } else { + setCurrentLauncherId(launcher.id); + } +} + +void LauncherAspect::fromMap(const Store &map) +{ + setCurrentLauncherId(map.value(settingsKey(), m_defaultId).toString()); +} + +void LauncherAspect::toMap(Store &map) const +{ + if (m_currentId != m_defaultId) + saveToMap(map, m_currentId, QString(), settingsKey()); +} + +void LauncherAspect::addToLayout(Layout &builder) +{ + if (QTC_GUARD(m_comboBox.isNull())) + m_comboBox = new QComboBox; + + updateComboBox(); + connect(m_comboBox, &QComboBox::currentIndexChanged, + this, &LauncherAspect::updateCurrentLauncher); + + builder.addItems({Tr::tr("Launcher:"), m_comboBox.data()}); +} + +void LauncherAspect::setCurrentLauncherId(const QString &id) +{ + if (id == m_currentId) + return; + m_currentId = id; + emit changed(); +} + +void LauncherAspect::updateCurrentLauncher() +{ + const int index = m_comboBox->currentIndex(); + if (index < 0) + return; + QTC_ASSERT(index < m_launchers.size(), return); + m_comboBox->setToolTip(m_launchers[index].command.toUserOutput()); + setCurrentLauncherId(m_launchers[index].id); +} + +void LauncherAspect::updateComboBox() +{ + int currentIndex = -1; + int defaultIndex = -1; + m_comboBox->clear(); + for (const Launcher &launcher : std::as_const(m_launchers)) { + int index = m_comboBox->count(); + m_comboBox->addItem(launcher.displayName); + m_comboBox->setItemData(index, launcher.command.toUserOutput(), Qt::ToolTipRole); + if (launcher.id == m_currentId) + currentIndex = index; + if (launcher.id == m_defaultId) + defaultIndex = index; + } + if (currentIndex >= 0) + m_comboBox->setCurrentIndex(currentIndex); + else if (defaultIndex >= 0) + m_comboBox->setCurrentIndex(defaultIndex); + updateCurrentLauncher(); +} + /*! \class ProjectExplorer::X11ForwardingAspect \inmodule QtCreator diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h index d891132ea7d..69b55c9974b 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.h +++ b/src/plugins/projectexplorer/runconfigurationaspects.h @@ -227,6 +227,36 @@ public: QString detectionSource; }; +class PROJECTEXPLORER_EXPORT LauncherAspect : public Utils::BaseAspect +{ + Q_OBJECT + +public: + LauncherAspect(Utils::AspectContainer *container = nullptr); + + Launcher currentLauncher() const; + void updateLaunchers(const QList &launchers); + void setDefaultLauncher(const Launcher &launcher); + void setCurrentLauncher(const Launcher &launcher); + void setSettingsDialogId(Utils::Id id) { m_settingsDialogId = id; } + + void fromMap(const Utils::Store &) override; + void toMap(Utils::Store &) const override; + void addToLayout(Layouting::Layout &parent) override; + + struct Data : Utils::BaseAspect::Data { Launcher launcher; }; + +private: + void setCurrentLauncherId(const QString &id); + void updateCurrentLauncher(); + void updateComboBox(); + QList m_launchers; + QPointer m_comboBox; + QString m_defaultId; + QString m_currentId; + Utils::Id m_settingsDialogId; +}; + class PROJECTEXPLORER_EXPORT MainScriptAspect : public Utils::FilePathAspect { Q_OBJECT