diff --git a/plugin.cpp b/plugin.cpp index b6b5899..d9eb091 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -1,282 +1,286 @@ /* This file is part of KDevelop Copyright 2002 Harald Fernengel Copyright 2007 Hamish Rodda Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include "config/globalconfigpage.h" #include "debug.h" #include "generic/job.h" #include "launchmode.h" #include "widget.h" #include "cachegrind/launcher.h" #include "callgrind/launcher.h" #include "drd/launcher.h" #include "helgrind/launcher.h" #include "massif/launcher.h" #include "memcheck/launcher.h" #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ValgrindFactory, "kdevvalgrind.json", registerPlugin();) namespace Valgrind { static const QString modelId = QStringLiteral("Valgrind"); class WidgetFactory : public KDevelop::IToolViewFactory { public: explicit WidgetFactory(Plugin* plugin) : m_plugin(plugin) { } QWidget* create(QWidget* parent = nullptr) override { return new Widget(m_plugin, parent); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ValgrindView"); } private: Plugin* m_plugin; }; Plugin::Plugin(QObject* parent, const QVariantList&) : IPlugin("kdevvalgrind", parent) , m_factory(new WidgetFactory(this)) , m_launchMode(new LaunchMode) , m_problemModel(new KDevelop::ProblemModel(this)) { qCDebug(KDEV_VALGRIND) << "setting valgrind rc file"; setXMLFile("kdevvalgrind.rc"); m_problemModel->setFeatures( KDevelop::ProblemModel::ScopeFilter | KDevelop::ProblemModel::SeverityFilter | KDevelop::ProblemModel::Grouping | KDevelop::ProblemModel::CanByPassScopeFilter); core()->languageController()->problemModelSet()->addModel(modelId, i18n("Valgrind"), m_problemModel); core()->uiController()->addToolView(i18n("Valgrind"), m_factory); core()->runController()->addLaunchMode(m_launchMode); auto pluginController = core()->pluginController(); for (auto plugin : pluginController->allPluginsForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))) { setupExecutePlugin(plugin, true); } connect(pluginController, &KDevelop::IPluginController::pluginLoaded, this, [this](KDevelop::IPlugin* plugin) { setupExecutePlugin(plugin, true); }); connect(pluginController, &KDevelop::IPluginController::unloadingPlugin, this, [this](KDevelop::IPlugin* plugin) { setupExecutePlugin(plugin, false); }); QAction* action = nullptr; action = new QAction(i18n("Memcheck: a memory error detector"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(Memcheck::launcherId); }); actionCollection()->addAction("memcheck_tool", action); action = new QAction(i18n("Cachegrind: a cache and branch-prediction profiler"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(Cachegrind::launcherId); }); actionCollection()->addAction("cachegrind_tool", action); action = new QAction(i18n("Callgrind: a call-graph generating cache and branch prediction profiler"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(Callgrind::launcherId); }); actionCollection()->addAction("callgrind_tool", action); action = new QAction(i18n("Helgrind: a thread error detector"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(Helgrind::launcherId); }); actionCollection()->addAction("helgrind_tool", action); action = new QAction(i18n("DRD: a thread error detector"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(DRD::launcherId); }); actionCollection()->addAction("drd_tool", action); action = new QAction(i18n("Massif: a heap profiler"), this); connect(action, &QAction::triggered, this, [this]() { executeDefaultLaunch(Massif::launcherId); }); actionCollection()->addAction("massif_tool", action); } Plugin::~Plugin() { } void Plugin::unload() { core()->languageController()->problemModelSet()->removeModel(modelId); core()->uiController()->removeToolView(m_factory); for (auto plugin : core()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))) { setupExecutePlugin(plugin, false); } Q_ASSERT(m_launchers.isEmpty()); core()->runController()->removeLaunchMode(m_launchMode); delete m_launchMode; } void Plugin::setupExecutePlugin(KDevelop::IPlugin* plugin, bool load) { if (plugin == this) { return; } auto iface = plugin->extension(); if (!iface) { return; } auto type = core()->runController()->launchConfigurationTypeForId(iface->nativeAppConfigTypeId()); Q_ASSERT(type); if (load) { KDevelop::ILauncher* launcher; launcher = new Memcheck::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); launcher = new Cachegrind::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); launcher = new Callgrind::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); launcher = new Helgrind::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); launcher = new DRD::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); launcher = new Massif::Launcher(this, m_launchMode); m_launchers.insert(plugin, launcher); type->addLauncher(launcher); } else { for (auto launcher : m_launchers.values(plugin)) { Q_ASSERT(launcher); m_launchers.remove(plugin, launcher); type->removeLauncher(launcher); delete launcher; } } } int Plugin::configPages() const { return 1; } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number) { return nullptr; } return new GlobalConfigPage(this, parent); } KDevelop::ProblemModel* Plugin::problemModel() const { return m_problemModel; } void Plugin::jobReadyToStart(Generic::Job* job) { for (auto action : actionCollection()->actions()) { action->setEnabled(false); } Q_ASSERT(job); if (!job->hasView()) { m_problemModel->clearProblems(); } } -void Plugin::jobFinished(Generic::Job* job, bool ok) +void Plugin::jobReadyToFinish(Generic::Job* job, bool ok) { - for (auto action : actionCollection()->actions()) { - action->setEnabled(true); - } - if (!ok) { return; } Q_ASSERT(job); if (job->hasView()) { addView(job->createView(), QStringLiteral("%1 (%2)").arg(job->target()).arg(job->tool())); core()->uiController()->findToolView("Valgrind", m_factory); } else { core()->languageController()->problemModelSet()->showModel(modelId); } } +void Plugin::jobFinished(KJob* job) +{ + Q_UNUSED(job); + for (auto action : actionCollection()->actions()) { + action->setEnabled(true); + } +} + void Plugin::executeDefaultLaunch(const QString& launcherId) { auto runController = KDevelop::Core::self()->runControllerInternal(); Q_ASSERT(runController); auto defaultLaunch = runController->defaultLaunch(); if (defaultLaunch) { defaultLaunch->setLauncherForMode(launchModeId, launcherId); runController->executeDefaultLaunch(launchModeId); } } } #include "plugin.moc" diff --git a/plugin.h b/plugin.h index 55eb8ea..48039cc 100644 --- a/plugin.h +++ b/plugin.h @@ -1,82 +1,85 @@ /* This file is part of KDevelop Copyright 2002 Harald Fernengel Copyright 2007 Hamish Rodda Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include #include +class KJob; + namespace KDevelop { class ProblemModel; class ILauncher; } namespace Valgrind { namespace Generic { class Job; } class LaunchMode; class WidgetFactory; class Plugin : public KDevelop::IPlugin { Q_OBJECT public: Plugin(QObject* parent, const QVariantList& = QVariantList()); ~Plugin() override; void unload() override; int configPages() const override; KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; KDevelop::ProblemModel* problemModel() const; void jobReadyToStart(Generic::Job* job); - void jobFinished(Generic::Job* job, bool ok); + void jobReadyToFinish(Generic::Job* job, bool ok); + void jobFinished(KJob* job); signals: void addView(QWidget* view, const QString& name); private: void setupExecutePlugin(KDevelop::IPlugin* plugin, bool load); void executeDefaultLaunch(const QString& launcherId); WidgetFactory* m_factory; LaunchMode* m_launchMode; QMultiHash m_launchers; KDevelop::ProblemModel* m_problemModel; }; } diff --git a/tools/generic/job.cpp b/tools/generic/job.cpp index a02b2cf..034bec5 100644 --- a/tools/generic/job.cpp +++ b/tools/generic/job.cpp @@ -1,272 +1,275 @@ /* This file is part of KDevelop Copyright 2011 Mathieu Lornac Copyright 2011 Damien Coppel Copyright 2011 Lionel Duc Copyright 2011 Sebastien Rannou Copyright 2011 Lucas Sarie Copyright 2006-2008 Hamish Rodda Copyright 2002 Harald Fernengel Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "debug.h" #include "plugin.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace Valgrind { namespace Generic { Job::Job( KDevelop::ILaunchConfiguration* launchConfig, QString tool, bool hasView, Plugin* plugin, QObject* parent) : KDevelop::OutputExecuteJob(parent) , m_config(launchConfig->config()) , m_tool(tool) , m_hasView(hasView) , m_plugin(plugin) { Q_ASSERT(launchConfig); Q_ASSERT(m_plugin); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr); setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput); setCapabilities(KJob::Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); auto pluginController = KDevelop::ICore::self()->pluginController(); auto iface = pluginController->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))->extension(); Q_ASSERT(iface); QString envGroup = iface->environmentGroup(launchConfig); if (envGroup.isEmpty()) { envGroup = KDevelop::EnvironmentGroupList(KSharedConfig::openConfig()).defaultGroup(); } setEnvironmentProfile(envGroup); QString errorString; m_analyzedExecutable = iface->executable(launchConfig, errorString).toLocalFile(); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } m_analyzedExecutableArguments = iface->arguments(launchConfig, errorString); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } QUrl workDir = iface->workingDirectory(launchConfig); if (workDir.isEmpty() || !workDir.isValid()) { workDir = QUrl::fromLocalFile(QFileInfo(m_analyzedExecutable).absolutePath()); } setWorkingDirectory(workDir); + + connect(this, &Job::finished, m_plugin, &Plugin::jobFinished); } Job::~Job() { } QString Job::tool() const { return m_tool; } QString Job::target() const { return QFileInfo(m_analyzedExecutable).fileName(); } bool Job::hasView() { return m_hasView; } QStringList Job::buildCommandLine() const { Settings settings; settings.load(m_config); QStringList args; args += QStringLiteral("--tool=") + m_tool; args += settings.cmdArgs(); addToolArgs(args); return args; } void Job::start() { *this << Settings::valgrindExecutablePath(); *this << buildCommandLine(); *this << m_analyzedExecutable; *this << m_analyzedExecutableArguments; qCDebug(KDEV_VALGRIND) << "executing:" << commandLine().join(' '); m_plugin->jobReadyToStart(this); KDevelop::OutputExecuteJob::start(); } void Job::postProcessStderr(const QStringList& lines) { m_errorOutput << lines; KDevelop::OutputExecuteJob::postProcessStderr(lines); } void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { bool ok = !exitCode; qCDebug(KDEV_VALGRIND) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; if (!ok) { // Here, check if Valgrind failed (because of bad parameters or whatever). // Because Valgrind always returns 1 on failure, and the profiled application's return // on success, we cannot know for sure which process returned != 0. // // The only way to guess that it is Valgrind which failed is to check stderr and look for // "valgrind: " at the beginning of the first line, even though it can still be the // profiled process that writes it on stderr. It is, however, unlikely enough to be // reliable in most cases. static const QString valgrindPrefix = QStringLiteral("valgrind: "); if (!m_errorOutput.isEmpty() && m_errorOutput.at(0).startsWith(valgrindPrefix)) { QString message = m_errorOutput.join('\n').remove(valgrindPrefix); message += QStringLiteral("\n\n"); message += i18n("Please review your Valgrind launch configuration."); KMessageBox::error(qApp->activeWindow(), message, i18n("Valgrind Error")); } } else { ok = processEnded(); } - m_plugin->jobFinished(this, ok); - emitResult(); + m_plugin->jobReadyToFinish(this, ok); + + KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); } void Job::childProcessError(QProcess::ProcessError processError) { QString errorMessage; switch (processError) { case QProcess::FailedToStart: errorMessage = i18n("Failed to start valgrind from \"%1\".", commandLine().first()); break; case QProcess::Crashed: // if the process was killed by the user, the crash was expected // don't notify the user if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { errorMessage = i18n("Valgrind crashed."); } break; case QProcess::Timedout: errorMessage = i18n("Valgrind process timed out."); break; case QProcess::WriteError: errorMessage = i18n("Write to Valgrind process failed."); break; case QProcess::ReadError: errorMessage = i18n("Read from Valgrind process failed."); break; case QProcess::UnknownError: errorMessage = i18n("Unknown Valgrind process error."); break; } if (!errorMessage.isEmpty()) { KMessageBox::error(qApp->activeWindow(), errorMessage, i18n("Valgrind Error")); } KDevelop::OutputExecuteJob::childProcessError(processError); } bool Job::processEnded() { return true; } int Job::executeProcess(const QString& executable, const QStringList& args, QByteArray& processOutput) { QString commandLine = executable + " " + args.join(' '); KDevelop::OutputExecuteJob::postProcessStdout({i18n("Executing command: "), commandLine }); QProcess process; process.start(executable, args); if (!process.waitForFinished()){ return -1; } processOutput = process.readAllStandardOutput(); QString errOutput(process.readAllStandardError()); KDevelop::OutputExecuteJob::postProcessStdout(QString(processOutput).split('\n')); KDevelop::OutputExecuteJob::postProcessStderr(errOutput.split('\n')); if (process.exitCode()) { QString message = i18n("Failed to execute the command:"); message += "\n\n"; message += commandLine; message += "\n\n"; message += i18n("Please review your Valgrind launch configuration."); KMessageBox::error(qApp->activeWindow(), message, i18n("Valgrind Error")); } return process.exitCode(); } } }