diff --git a/kdevplatform/shell/runcontroller.cpp b/kdevplatform/shell/runcontroller.cpp index 5198c4ece9..ae051ce1e4 100644 --- a/kdevplatform/shell/runcontroller.cpp +++ b/kdevplatform/shell/runcontroller.cpp @@ -1,1093 +1,1093 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } using Target = QPair; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class KDevelop::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { const auto launchers = type->launchers(); auto it = std::find_if(launchers.begin(), launchers.end(), [&](ILauncher* l) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); return (l->supportedModes().contains(mode->id())); }); if (it != launchers.end()) { ILauncher* launcher = *it; QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); auto it = std::find_if(launchConfigurations.constBegin(), launchConfigurations.constEnd(), [&] (LaunchConfiguration* l) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; return true; } return false; }); ILaunchConfiguration* ilaunch = (it != launchConfigurations.constEnd()) ? *it : nullptr; if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); auto* launch = static_cast(ilaunch); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { const auto actions = currentTargetAction->actions(); for (QAction* a : actions) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(QVariant::fromValue(l)); } void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList()); for (const QString& cfg : configs) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d_ptr(new RunControllerPrivate) { Q_D(RunController); setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->contextItem = nullptr; d->executeMode = nullptr; d->debugMode = nullptr; d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() = default; void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { Q_D(RunController); delete d->executeMode; d->executeMode = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { Q_D(RunController); d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); const auto projects = Core::self()->projectController()->projects(); for (IProject* project : projects) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return nullptr; } auto* run = static_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { const QString messageText = i18n("The current launch configuration does not support the '%1' mode.", runMode); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { Q_D(RunController); QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); - action = new QAction(i18n("Configure Launches..."), this); + action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::ALT + Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { Q_D(RunController); return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { Q_D(RunController); d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { Q_D(RunController); if (!d->currentTargetAction) return; const auto actions = d->currentTargetAction->actions(); for (QAction* action : actions) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { Q_D(const RunController); QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { Q_D(RunController); if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connect syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { Q_D(RunController); IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { Q_D(RunController); bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { Q_D(RunController); // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 const auto jobs = d->jobs.keys(); for (KJob* job : jobs) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qCWarning(SHELL) << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { auto* action = qobject_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { auto* message = new Sublime::Message(job->errorString(), Sublime::Message::Error); Core::self()->uiController()->postMessage(message); } } } void RunController::jobDestroyed(QObject* job) { Q_D(RunController); KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qCWarning(SHELL) << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { Q_D(const RunController); return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; const auto configsInternal = launchConfigurationsInternal(); configs.reserve(configsInternal.size()); for (LaunchConfiguration* config : configsInternal) { configs << config; } return configs; } QList RunController::launchConfigurationsInternal() const { Q_D(const RunController); return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { Q_D(const RunController); return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); const auto oldLaunchConfigurations = d->launchConfigurations; for (LaunchConfiguration* l : oldLaunchConfigurations) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { Q_D(const RunController); return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { Q_D(const RunController); auto it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { Q_D(RunController); if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qCWarning(SHELL) << "no default launch!"; } } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { const auto configs = Core::self()->runControllerInternal()->launchConfigurations(); return std::any_of(configs.begin(), configs.end(), [&](ILaunchConfiguration* config) { return (config->name() == name); }); } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); auto* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { Q_D(const RunController); return d->delegate; } ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent) { Q_D(RunController); d->launchAsInfo.clear(); d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { auto* prjctx = static_cast(ctx); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; for (ILaunchMode* mode : qAsConst(d->launchModes)) { auto* menu = new KActionMenu(i18n("%1 As...", mode->name() ), parent); const auto types = launchConfigurationTypes(); for (LaunchConfigurationType* type : types) { bool hasLauncher = false; const auto launchers = type->launchers(); for (ILauncher* launcher : launchers) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); auto* act = new QAction(menu); act->setText( type->name() ); qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name(); connect(act, &QAction::triggered, this, [this, i] () { Q_D(RunController); d->launchAs(i); } ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } else { delete menu; } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/plugins/okteta/kdevokteta.rc b/plugins/okteta/kdevokteta.rc index 08300303f1..43b448f855 100644 --- a/plugins/okteta/kdevokteta.rc +++ b/plugins/okteta/kdevokteta.rc @@ -1,86 +1,87 @@ - + Permissions View Bookmarks + Main Toolbar diff --git a/plugins/projectmanagerview/kdevprojectmanagerview.rc b/plugins/projectmanagerview/kdevprojectmanagerview.rc index c264290d92..08b440bf12 100644 --- a/plugins/projectmanagerview/kdevprojectmanagerview.rc +++ b/plugins/projectmanagerview/kdevprojectmanagerview.rc @@ -1,28 +1,29 @@ - + + Main Toolbar diff --git a/release-scripts/create_log.py b/release-scripts/create_log.py index f320a6dd8b..2d05a2fdb7 100755 --- a/release-scripts/create_log.py +++ b/release-scripts/create_log.py @@ -1,149 +1,153 @@ #!/usr/bin/env python3 # stolen from releaseme/plasma, which was stolen from release-tools Applications/15.04 branch # ported to Python 3 and fixed the worst issues + removed Plasma-related bits --Kevin from __future__ import print_function import argparse import os import subprocess import sys import cgi -def createLog(workingDir, fromVersion, toVersion, repositoryName=None, showInterestingChangesOnly=True): +def createLog(workingDir, fromVersion, toVersion, excludeBranch=None, repositoryName=None, showInterestingChangesOnly=True): if not repositoryName: # use cwd name as repository name repositoryName = os.path.split(workingDir)[1] p = subprocess.Popen('git fetch', shell=True, cwd=workingDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if p.wait() != 0: raise NameError('git fetch failed') p = subprocess.Popen('git rev-parse ' + fromVersion + ' ' + toVersion, shell=True, cwd=workingDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if p.wait() != 0: raise NameError("git rev-parse failed -- correct to/from version?") - p = subprocess.Popen('git log ' + fromVersion + '...' + toVersion, shell=True, cwd=workingDir, + branchToExclude = ("^" + excludeBranch + " ") if excludeBranch else "" + p = subprocess.Popen('git log ' + branchToExclude + fromVersion + '...' + toVersion, shell=True, cwd=workingDir, stdout=subprocess.PIPE, universal_newlines=True) commit = [] commits = [] for line in p.stdout: if line.startswith("commit"): if len(commit) > 1 and not ignoreCommit: commits.append(commit) commitHash = line[7:].strip() ignoreCommit = False commit = [commitHash] elif line.startswith("Author"): pass elif line.startswith("Date"): pass elif line.startswith("Merge"): pass else: line = line.strip() if line.startswith("Merge remote-tracking branch"): ignoreCommit = True elif line.startswith("SVN_SILENT"): ignoreCommit = True elif line.startswith("GIT_SILENT"): ignoreCommit = True elif line.startswith("Merge branch"): ignoreCommit = True elif line.startswith("Update version number for"): ignoreCommit = True elif line: commit.append(line) # Add the last commit if len(commit) > 1 and not ignoreCommit: commits.append(commit) commitLogEntries = [] if len(commits) > 0: for commit in commits: extra = "" changelog = commit[1] for line in commit: line = cgi.escape(line) if line.startswith("BUGS:"): bugNumbers = line[line.find(":") + 1:].strip() for bugNumber in bugNumbers.split(","): if bugNumber.isdigit(): if extra: extra += ". " extra += "fixes bug #" + bugNumber + "" elif line.startswith("BUG:"): bugNumber = line[line.find(":") + 1:].strip() if bugNumber.isdigit(): if extra: extra += ". " extra += "fixes bug #" + bugNumber + "" elif line.startswith("REVIEW:"): if extra: extra += ". " reviewNumber = line[line.find(":") + 1:].strip() extra += "code review #" + reviewNumber + "" # jr addition 2017-02 phab link elif line.startswith("Differential Revision:"): if extra: extra += ". " reviewNumber = line[line.find("org/") + 4:].strip() extra += "code review " + reviewNumber + "" elif line.startswith("CCBUG:"): if extra: extra += ". " bugNumber = line[line.find(":") + 1:].strip() extra += "See bug #" + bugNumber + "" elif line.startswith("FEATURE:"): feature = line[line.find(":") + 1:].strip() if feature.isdigit(): if extra: extra += ". " extra += "Implements feature #" + feature + "" else: if feature: changelog = feature elif line.startswith("CHANGELOG:"): changelog = line[11:] # remove word "CHANGELOG: " elif line.startswith("Merge Plasma"): pass if showInterestingChangesOnly and not extra: continue commitHash = commit[0] if not changelog.endswith("."): changelog = changelog + "." + if extra: + extra = ". " + extra capitalizedChangelog = changelog[0].capitalize() + changelog[1:] - commitLogEntries.append("
  • " + capitalizedChangelog + " (commit. " + extra + ")
  • ") + commitLogEntries.append("
  • " + capitalizedChangelog + " (commit" + extra + ")
  • ") # Print result to stdout print("

    " + repositoryName + "

    ") if len(commitLogEntries) > 0: print("
      ") for commitLogEntry in commitLogEntries: print(commitLogEntry) print("
    \n\n") else: print("No changes") if p.wait() != 0: raise NameError('git log failed', repositoryName, fromVersion, toVersion) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Create HTML log based on Git history in the current working directory') parser.add_argument('--repositoryName', type=str, help='The path to the Git repositoryNamesitory (default: name of current working dir', default=None) - parser.add_argument('from_version', type=str, help='The start of the revision range (e.g. "v5.0.0")') - parser.add_argument('to_version', type=str, help='The end of the revision range (e.g. "v5.0.1"') + parser.add_argument('from_version', type=str, help='The start of the revision range (e.g. "v5.5.0")') + parser.add_argument('to_version', type=str, help='The end of the revision range (e.g. "v5.5.1")') + parser.add_argument('--excludeBranch', type=str, help='The old branch to ignore (e.g. "5.4")', default=None) args = parser.parse_args() - createLog(os.getcwd(), args.from_version, args.to_version, args.repositoryName) + createLog(os.getcwd(), args.from_version, args.to_version, args.excludeBranch) diff --git a/release-scripts/create_log_all.py b/release-scripts/create_log_all.py index 7388b919c2..4f20cf68c5 100755 --- a/release-scripts/create_log_all.py +++ b/release-scripts/create_log_all.py @@ -1,27 +1,28 @@ #!/usr/bin/env python3 import argparse import os import subprocess # local imports import create_log DIR_PATH = os.path.dirname(os.path.realpath(__file__)) -def run(fromVersion, toVersion): +def run(fromVersion, toVersion, excludeBranch): repositories = [] with open(os.path.join(DIR_PATH, "REPOSITORIES.inc"), 'r') as f: repositories = f.read().split() for repository in repositories: workingDir = os.path.join("..", repository) - create_log.createLog(workingDir, fromVersion, toVersion) + create_log.createLog(workingDir, fromVersion, toVersion, excludeBranch) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Create HTML log based on Git history for released KDevelop projects') - parser.add_argument('from_version', type=str, help='The start of the revision range (e.g. "v5.0.0")') - parser.add_argument('to_version', type=str, help='The end of the revision range (e.g. "v5.0.1"') + parser.add_argument('from_version', type=str, help='The start of the revision range (e.g. "v5.5.0")') + parser.add_argument('to_version', type=str, help='The end of the revision range (e.g. "v5.5.1")') + parser.add_argument('--excludeBranch', type=str, help='The old branch to ignore (e.g. "5.4")', default=None) args = parser.parse_args() - run(args.from_version, args.to_version) + run(args.from_version, args.to_version, args.excludeBranch)