File indexing completed on 2024-04-21 04:36:03
0001 /* 0002 * XDebug Debugger Support 0003 * 0004 * Copyright 2006 Vladimir Prus <ghost@cs.msu.su> 0005 * Copyright 2007 Hamish Rodda <rodda@kde.org> 0006 * Copyright 2009 Andreas Pakulat <apaku@gmx.de> 0007 * Copyright 2009 Niko Sams <niko.sams@gmail.com> 0008 * 0009 * This program is free software; you can redistribute it and/or modify 0010 * it under the terms of the GNU General Public License as 0011 * published by the Free Software Foundation; either version 2 of the 0012 * License, or (at your option) any later version. 0013 * 0014 * This program is distributed in the hope that it will be useful, 0015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 * GNU General Public License for more details. 0018 * 0019 * You should have received a copy of the GNU General Public 0020 * License along with this program; if not, write to the 0021 * Free Software Foundation, Inc., 0022 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0023 */ 0024 0025 #include "debugjob.h" 0026 0027 #include <QFileInfo> 0028 #include <QDesktopServices> 0029 0030 #include <QDebug> 0031 #include <KProcess> 0032 #include <KMessageBox> 0033 #include <KParts/MainWindow> 0034 #include <KLocalizedString> 0035 0036 #include <outputview/outputmodel.h> 0037 #include <interfaces/ilaunchconfiguration.h> 0038 #include <execute/iexecuteplugin.h> 0039 #include <interfaces/iproject.h> 0040 #include <project/interfaces/iprojectbuilder.h> 0041 #include <project/builderjob.h> 0042 #include <interfaces/iuicontroller.h> 0043 #include <project/interfaces/ibuildsystemmanager.h> 0044 #include <util/executecompositejob.h> 0045 #include <interfaces/iplugincontroller.h> 0046 #include <interfaces/icore.h> 0047 #include <util/processlinemaker.h> 0048 #include <executescript/iexecutescriptplugin.h> 0049 #include <iexecutebrowserplugin.h> 0050 0051 #include <kdevplatform_version.h> 0052 #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) 0053 #include <util/environmentgrouplist.h> 0054 #else 0055 #include <util/environmentprofilelist.h> 0056 #endif 0057 0058 #include "debugsession.h" 0059 #include "xdebugplugin.h" 0060 #include "connection.h" 0061 #include "debuggerdebug.h" 0062 0063 namespace XDebug { 0064 XDebugJob::XDebugJob(DebugSession* session, KDevelop::ILaunchConfiguration* cfg, QObject* parent) 0065 : KDevelop::OutputJob(parent) 0066 , m_proc(nullptr) 0067 , m_session(session) 0068 { 0069 setCapabilities(Killable); 0070 0071 session->setLaunchConfiguration(cfg); 0072 0073 setObjectName(cfg->name()); 0074 0075 IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecuteScriptPlugin")->extension<IExecuteScriptPlugin>(); 0076 Q_ASSERT(iface); 0077 0078 #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) 0079 KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); 0080 QString envgrp = iface->environmentGroup(cfg); 0081 #else 0082 KDevelop::EnvironmentProfileList l(KSharedConfig::openConfig()); 0083 QString envgrp = iface->environmentProfileName(cfg); 0084 #endif 0085 0086 QString err; 0087 QString interpreter = iface->interpreter(cfg, err); 0088 0089 if (!err.isEmpty()) { 0090 setError(-1); 0091 setErrorText(err); 0092 return; 0093 } 0094 0095 QUrl script = iface->script(cfg, err); 0096 0097 if (!err.isEmpty()) { 0098 setError(-3); 0099 setErrorText(err); 0100 return; 0101 } 0102 0103 QString remoteHost = iface->remoteHost(cfg, err); 0104 if (!err.isEmpty()) { 0105 setError(-4); 0106 setErrorText(err); 0107 return; 0108 } 0109 0110 if (envgrp.isEmpty()) { 0111 qCWarning(KDEV_PHP_DEBUGGER) << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " 0112 "configuration, please check run configuration '%1'. " 0113 "Using default environment group.", cfg->name()); 0114 #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) 0115 envgrp = l.defaultGroup(); 0116 #else 0117 envgrp = l.defaultProfileName(); 0118 #endif 0119 } 0120 0121 QStringList arguments = iface->arguments(cfg, err); 0122 if (!err.isEmpty()) { 0123 setError(-2); 0124 setErrorText(err); 0125 } 0126 0127 if (error() != 0) { 0128 qCWarning(KDEV_PHP_DEBUGGER) << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); 0129 return; 0130 } 0131 0132 QString ideKey = "kdev" + QString::number(qrand()); 0133 //TODO NIKO set ideKey to session? 0134 0135 m_proc = new KProcess(this); 0136 0137 m_lineMaker = new KDevelop::ProcessLineMaker(m_proc, this); 0138 0139 setStandardToolView(KDevelop::IOutputView::RunView); 0140 setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); 0141 KDevelop::OutputModel* m = new KDevelop::OutputModel(); 0142 m->setFilteringStrategy(KDevelop::OutputModel::ScriptErrorFilter); 0143 setModel(m); 0144 0145 connect(m_lineMaker, &KDevelop::ProcessLineMaker::receivedStdoutLines, model(),&KDevelop::OutputModel::appendLines); 0146 0147 #if QT_VERSION < 0x050600 0148 connect(m_proc, static_cast<void(QProcess::*)(QProcess::ProcessError)>(&QProcess::error), 0149 #else 0150 connect(m_proc, &QProcess::errorOccurred, 0151 #endif 0152 this, &XDebugJob::processError); 0153 0154 connect(m_proc, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)> (&QProcess::finished), this, &XDebugJob::processFinished); 0155 0156 QStringList env = l.createEnvironment(envgrp, m_proc->systemEnvironment()); 0157 env << "XDEBUG_CONFIG=\"remote_enable=1 \""; 0158 m_proc->setEnvironment(env); 0159 0160 QUrl wc = iface->workingDirectory(cfg); 0161 if (!wc.isValid() || wc.isEmpty()) { 0162 wc = QUrl(QFileInfo(script.toLocalFile()).absolutePath()); 0163 } 0164 m_proc->setWorkingDirectory(wc.toLocalFile()); 0165 m_proc->setProperty("executable", interpreter); 0166 0167 QStringList program; 0168 if (!remoteHost.isEmpty()) { 0169 program << "ssh"; 0170 QStringList parts = remoteHost.split(":"); 0171 program << parts.first(); 0172 if (parts.length() > 1) { 0173 program << "-p " + parts.at(1); 0174 } 0175 program << "XDEBUG_CONFIG=\"remote_enable=1 \""; 0176 } 0177 qCDebug(KDEV_PHP_DEBUGGER) << program; 0178 program << interpreter; 0179 program << "-d xdebug.remote_enable=1"; 0180 QString remoteHostSetting = cfg->config().readEntry("RemoteHost", QString()); 0181 int remotePortSetting = cfg->config().readEntry("RemotePort", 9000); 0182 if (!remoteHostSetting.isEmpty()) { 0183 program << "-d xdebug.remote_host=" + remoteHostSetting; 0184 } 0185 program << "-d xdebug.remote_port=" + QString::number(remotePortSetting); 0186 program << "-d xdebug.idekey=" + ideKey; 0187 program << script.toLocalFile(); 0188 program << arguments; 0189 0190 qCDebug(KDEV_PHP_DEBUGGER) << "setting app:" << program; 0191 0192 m_proc->setOutputChannelMode(KProcess::MergedChannels); 0193 0194 m_proc->setProgram(program); 0195 0196 setTitle(cfg->name()); 0197 setStandardToolView(KDevelop::IOutputView::DebugView); 0198 } 0199 0200 void XDebugJob::start() 0201 { 0202 qCDebug(KDEV_PHP_DEBUGGER) << "launching?" << m_proc; 0203 if (m_proc) { 0204 QString err; 0205 if (!m_session->listenForConnection(err)) { 0206 qCWarning(KDEV_PHP_DEBUGGER) << "listening for connection failed"; 0207 setError(-1); 0208 setErrorText(err); 0209 emitResult(); 0210 m_session->stopDebugger(); 0211 return; 0212 } 0213 0214 startOutput(); 0215 qCDebug(KDEV_PHP_DEBUGGER) << "starting" << m_proc->program().join(" "); 0216 appendLine(i18n("Starting: %1", m_proc->program().join(" "))); 0217 m_proc->start(); 0218 } else 0219 { 0220 qCWarning(KDEV_PHP_DEBUGGER) << "No process, something went wrong when creating the job"; 0221 // No process means we've returned early on from the constructor, some bad error happened 0222 emitResult(); 0223 m_session->stopDebugger(); 0224 } 0225 } 0226 0227 KProcess* XDebugJob::process() const 0228 { 0229 return m_proc; 0230 } 0231 0232 bool XDebugJob::doKill() 0233 { 0234 qCDebug(KDEV_PHP_DEBUGGER); 0235 if (m_session) { 0236 m_session->stopDebugger(); 0237 } 0238 return true; 0239 } 0240 0241 void XDebugJob::processFinished(int exitCode, QProcess::ExitStatus status) 0242 { 0243 m_lineMaker->flushBuffers(); 0244 if (exitCode == 0 && status == QProcess::NormalExit) { 0245 appendLine(i18n("*** Exited normally ***")); 0246 } else 0247 if (status == QProcess::NormalExit) { 0248 appendLine(i18n("*** Exited with return code: %1 ***", QString::number(exitCode))); 0249 } else 0250 if (error() == KJob::KilledJobError) { 0251 appendLine(i18n("*** Process aborted ***")); 0252 } else { 0253 appendLine(i18n("*** Crashed with return code: %1 ***", QString::number(exitCode))); 0254 } 0255 qCDebug(KDEV_PHP_DEBUGGER) << "Process done"; 0256 emitResult(); 0257 } 0258 0259 void XDebugJob::processError(QProcess::ProcessError error) 0260 { 0261 if (error == QProcess::FailedToStart) { 0262 setError(-1); 0263 QString errmsg = i18n("Could not start program '%1'. Make sure that the " 0264 "path is specified correctly.", m_proc->property("executable").toString()); 0265 setErrorText(errmsg); 0266 m_session->stopDebugger(); 0267 emitResult(); 0268 } 0269 qCDebug(KDEV_PHP_DEBUGGER) << "Process error"; 0270 } 0271 0272 void XDebugJob::appendLine(const QString& l) 0273 { 0274 if (KDevelop::OutputModel* m = model()) { 0275 m->appendLine(l); 0276 } 0277 } 0278 0279 KDevelop::OutputModel* XDebugJob::model() 0280 { 0281 return dynamic_cast<KDevelop::OutputModel*>(KDevelop::OutputJob::model()); 0282 } 0283 0284 XDebugBrowserJob::XDebugBrowserJob(DebugSession* session, KDevelop::ILaunchConfiguration* cfg, QObject* parent) 0285 : KJob(parent) 0286 , m_session(session) 0287 { 0288 setCapabilities(Killable); 0289 0290 session->setLaunchConfiguration(cfg); 0291 0292 setObjectName(cfg->name()); 0293 0294 IExecuteBrowserPlugin* iface = KDevelop::ICore::self()->pluginController() 0295 ->pluginForExtension("org.kdevelop.IExecuteBrowserPlugin")->extension<IExecuteBrowserPlugin>(); 0296 Q_ASSERT(iface); 0297 0298 QString err; 0299 m_url = iface->url(cfg, err); 0300 if (!err.isEmpty()) { 0301 m_url.clear(); 0302 setError(-1); 0303 setErrorText(err); 0304 return; 0305 } 0306 m_browser = iface->browser(cfg); 0307 0308 setObjectName(cfg->name()); 0309 0310 connect(m_session, &KDevelop::IDebugSession::finished,this, &XDebugBrowserJob::sessionFinished); 0311 0312 m_session->setAcceptMultipleConnections(true); 0313 } 0314 0315 void XDebugBrowserJob::start() 0316 { 0317 qCDebug(KDEV_PHP_DEBUGGER) << "launching?" << m_url; 0318 if (!m_url.isValid()) { 0319 emitResult(); 0320 return; 0321 } 0322 0323 QString err; 0324 if (!m_session->listenForConnection(err)) { 0325 qCWarning(KDEV_PHP_DEBUGGER) << "listening for connection failed"; 0326 setError(-1); 0327 setErrorText(err); 0328 emitResult(); 0329 m_session->stopDebugger(); 0330 return; 0331 } 0332 0333 QUrl url = m_url; 0334 url.setQuery("XDEBUG_SESSION_START=kdev"); 0335 0336 launchBrowser(url); 0337 } 0338 0339 0340 void XDebugBrowserJob::processFailedToStart() 0341 { 0342 qCWarning(KDEV_PHP_DEBUGGER) << "Cannot start application" << m_browser; 0343 setError(-1); 0344 QString errmsg = i18n("Could not start program '%1'. Make sure that the " 0345 "path is specified correctly.", m_browser); 0346 setErrorText(errmsg); 0347 emitResult(); 0348 qCDebug(KDEV_PHP_DEBUGGER) << "Process error"; 0349 m_session->stopDebugger(); 0350 } 0351 0352 0353 void XDebugBrowserJob::launchBrowser(QUrl &url) 0354 { 0355 if (m_browser.isEmpty()) { 0356 if (!QDesktopServices::openUrl(url)) { 0357 qCWarning(KDEV_PHP_DEBUGGER) << "openUrl failed, something went wrong when creating the job"; 0358 emitResult(); 0359 m_session->stopDebugger(); 0360 } 0361 } else { 0362 KProcess proc(this); 0363 0364 proc.setProgram(QStringList() << m_browser << url.url()); 0365 if( !proc.startDetached() ) 0366 { 0367 processFailedToStart(); 0368 } 0369 } 0370 } 0371 0372 bool XDebugBrowserJob::doKill() 0373 { 0374 qCDebug(KDEV_PHP_DEBUGGER); 0375 m_session->stopDebugger(); 0376 QUrl url = m_url; 0377 url.setQuery("XDEBUG_SESSION_STOP_NO_EXEC=kdev"); 0378 launchBrowser(url); 0379 return true; 0380 } 0381 0382 void XDebugBrowserJob::sessionFinished() 0383 { 0384 emitResult(); 0385 } 0386 0387 }