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