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 }