1 /*
2 SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "nativeappjob.h"
8
9 #include <QAbstractButton>
10 #include <QFileInfo>
11 #include <QMessageBox>
12 #include <QPointer>
13 #include <QCheckBox>
14
15 #include <KConfigGroup>
16 #include <KLocalizedString>
17 #include <KShell>
18 #include <KSharedConfig>
19
20 #include <interfaces/ilaunchconfiguration.h>
21 #include <interfaces/iruncontroller.h>
22 #include <outputview/outputmodel.h>
23 #include <util/environmentprofilelist.h>
24
25 #include <interfaces/icore.h>
26 #include <interfaces/iplugincontroller.h>
27 #include <project/projectmodel.h>
28
29 #include "executeplugin.h"
30 #include "debug.h"
31
32 using namespace KDevelop;
33
NativeAppJob(QObject * parent,KDevelop::ILaunchConfiguration * cfg)34 NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg)
35 : KDevelop::OutputExecuteJob( parent )
36 , m_name(cfg->name())
37 {
38 {
39 auto cfgGroup = cfg->config();
40 if (cfgGroup.readEntry(ExecutePlugin::isExecutableEntry, false)) {
41 m_name = cfgGroup.readEntry(ExecutePlugin::executableEntry, cfg->name()).section(QLatin1Char('/'), -1);
42 }
43 if (!cfgGroup.readEntry<bool>(ExecutePlugin::configuredByCTest, false)) {
44 m_killBeforeExecutingAgain = cfgGroup.readEntry<int>(ExecutePlugin::killBeforeExecutingAgain, askIfRunning);
45 }
46 }
47 setCapabilities(Killable);
48
49 auto* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension<IExecutePlugin>();
50 Q_ASSERT(iface);
51
52 const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig());
53 QString envProfileName = iface->environmentProfileName(cfg);
54
55 QString err;
56 QUrl executable = iface->executable( cfg, err );
57
58 if( !err.isEmpty() )
59 {
60 setError( -1 );
61 setErrorText( err );
62 return;
63 }
64
65 if (envProfileName.isEmpty()) {
66 qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken "
67 "configuration, please check run configuration '%1'. "
68 "Using default environment profile.", cfg->name() );
69 envProfileName = environmentProfiles.defaultProfileName();
70 }
71 setEnvironmentProfile(envProfileName);
72
73 QStringList arguments = iface->arguments( cfg, err );
74 if( !err.isEmpty() )
75 {
76 setError( -2 );
77 setErrorText( err );
78 }
79
80 if( error() != 0 )
81 {
82 qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText();
83 return;
84 }
85
86 setStandardToolView(KDevelop::IOutputView::RunView);
87 setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll);
88 setFilteringStrategy(OutputModel::NativeAppErrorFilter);
89 setProperties(DisplayStdout | DisplayStderr);
90
91 // Now setup the process parameters
92
93 QUrl wc = iface->workingDirectory( cfg );
94 if( !wc.isValid() || wc.isEmpty() ) {
95 wc = QUrl::fromLocalFile( QFileInfo( executable.toLocalFile() ).absolutePath() );
96 }
97 setWorkingDirectory( wc );
98
99 qCDebug(PLUGIN_EXECUTE) << "setting app:" << executable << arguments;
100
101 if (iface->useTerminal(cfg)) {
102 QString terminalCommand = iface->terminal(cfg);
103 terminalCommand.replace(QLatin1String("%exe"), KShell::quoteArg( executable.toLocalFile()) );
104 terminalCommand.replace(QLatin1String("%workdir"), KShell::quoteArg( wc.toLocalFile()) );
105 QStringList args = KShell::splitArgs(terminalCommand);
106 args.append( arguments );
107 *this << args;
108 } else {
109 *this << executable.toLocalFile();
110 *this << arguments;
111 }
112
113 setJobName(m_name);
114 }
115
findNativeJob(KJob * j)116 NativeAppJob* findNativeJob(KJob* j)
117 {
118 auto* job = qobject_cast<NativeAppJob*>(j);
119 if (!job) {
120 const QList<NativeAppJob*> jobs = j->findChildren<NativeAppJob*>();
121 if (!jobs.isEmpty())
122 job = jobs.first();
123 }
124 return job;
125 }
126
start()127 void NativeAppJob::start()
128 {
129 QVector<QPointer<NativeAppJob> > currentJobs;
130 // collect running instances of the same type
131 const auto& allCurrentJobs = ICore::self()->runController()->currentJobs();
132 for (auto j : allCurrentJobs) {
133 NativeAppJob* njob = findNativeJob(j);
134 if (njob && njob != this && njob->m_name == m_name)
135 currentJobs << njob;
136 }
137
138 if (!currentJobs.isEmpty()) {
139 int oldJobAction = m_killBeforeExecutingAgain;
140 if (oldJobAction == askIfRunning) {
141 QMessageBox msgBox(QMessageBox::Question,
142 i18nc("@title:window", "Job Already Running"),
143 i18n("'%1' is already being executed.", m_name),
144 startAnother | killAllInstances | QMessageBox::Cancel /* aka askIfRunning */);
145 msgBox.button(killAllInstances)->setText(i18nc("@action:button", "Kill All Instances"));
146 msgBox.button(startAnother)->setText(i18nc("@action:button", "Start Another"));
147 msgBox.setDefaultButton(QMessageBox::Cancel);
148
149 QCheckBox* remember = new QCheckBox(i18nc("@option:check", "Remember choice"));
150 msgBox.setCheckBox(remember);
151
152 oldJobAction = msgBox.exec();
153 if (remember->isChecked() && oldJobAction != QMessageBox::Cancel) {
154 Q_EMIT killBeforeExecutingAgainChanged(oldJobAction);
155 }
156 }
157
158 switch (oldJobAction) {
159 case startAnother:
160 break;
161 case killAllInstances:
162 for (auto & job : currentJobs) {
163 if (job)
164 job->kill(EmitResult);
165 }
166 break;
167 default: // cancel starting a new job
168 kill(EmitResult);
169 return;
170 }
171 }
172
173 OutputExecuteJob::start();
174 }
175