diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt index a8feafd681..c7d70947e5 100644 --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -1,107 +1,111 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") set(KDevPlatformInterfaces_LIB_SRCS iassistant.cpp context.cpp configpage.cpp iplugin.cpp idocument.cpp icore.cpp iuicontroller.cpp iplugincontroller.cpp iprojectcontroller.cpp iproject.cpp ilanguagecontroller.cpp idocumentcontroller.cpp istatus.cpp iruncontroller.cpp isession.cpp isessionlock.cpp isourceformatter.cpp isourceformattercontroller.cpp contextmenuextension.cpp icompletionsettings.cpp iselectioncontroller.cpp idocumentationprovider.cpp idocumentationproviderprovider.cpp idocumentation.cpp idocumentationcontroller.cpp idebugcontroller.cpp ipartcontroller.cpp launchconfigurationpage.cpp launchconfigurationtype.cpp ilauncher.cpp ilaunchconfiguration.cpp ilaunchmode.cpp iprojectprovider.cpp ibuddydocumentfinder.cpp itemplateprovider.cpp itestsuite.cpp itestcontroller.cpp itoolviewactionlistener.cpp ilanguagecheck.cpp ilanguagecheckprovider.cpp + iruntime.cpp + iruntimecontroller.cpp ) configure_file(ipluginversion.h.in ${CMAKE_CURRENT_BINARY_DIR}/ipluginversion.h) kdevplatform_add_library(KDevPlatformInterfaces SOURCES ${KDevPlatformInterfaces_LIB_SRCS}) target_link_libraries(KDevPlatformInterfaces LINK_PUBLIC KF5::Parts KF5::TextEditor LINK_PRIVATE KF5::ConfigCore KF5::I18n KF5::Service ) install(FILES iassistant.h context.h configpage.h contextmenuextension.h iplugin.h icore.h iuicontroller.h iplugincontroller.h iprojectcontroller.h iproject.h ilanguagecontroller.h idocument.h idocumentcontroller.h isourceformatter.h isourceformattercontroller.h istatus.h isession.h isessionlock.h iruncontroller.h ilaunchconfiguration.h ilauncher.h launchconfigurationpage.h launchconfigurationtype.h icompletionsettings.h iselectioncontroller.h idocumentation.h idocumentationprovider.h idocumentationproviderprovider.h idocumentationcontroller.h idebugcontroller.h ipartcontroller.h ilaunchmode.h iprojectprovider.h ilanguagecheck.h ilanguagecheckprovider.h ibuddydocumentfinder.h itemplateprovider.h itestsuite.h itestcontroller.h itoolviewactionlistener.h iproblem.h + iruntime.h + iruntimecontroller.h ${CMAKE_CURRENT_BINARY_DIR}/ipluginversion.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/interfaces COMPONENT Devel ) install(FILES kdevelopplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) diff --git a/interfaces/icore.h b/interfaces/icore.h index d9bde32e22..992f66fe66 100644 --- a/interfaces/icore.h +++ b/interfaces/icore.h @@ -1,150 +1,154 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2007 Kris Wong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ICORE_H #define KDEVPLATFORM_ICORE_H #include #include "interfacesexport.h" #include "isessionlock.h" class KAboutData; /** * The KDevelop namespace contains all classes provided by the KDevelop * platform libraries. */ namespace KDevelop { class IUiController; class IPluginController; class IProjectController; class ILanguageController; class IDocumentController; class ISessionController; class IRunController; class ISourceFormatterController; class ISession; class ISelectionController; class IDocumentationController; class IDebugController; class IPartController; class IDashboardController; class ITestController; +class IRuntimeController; /** * ICore is the container class for all the various objects in use by * KDevelop. If access is needed to a particular controller, then this class * should be used. * * ICore can provide the user with instances of the following things: * - the main window(s) * - the document controller(s) * - the plugin controller * - the project controller * - the language controller * - the KPart manager * * When an object is provided to ICore so it can be used later, ICore * will take ownership of the object and upon application shutdown will take * responsibility for deleting the objects stored by ICore. */ class KDEVPLATFORMINTERFACES_EXPORT ICore: public QObject { Q_OBJECT Q_PROPERTY(KDevelop::IProjectController* projectController READ projectController) public: ~ICore() override; /** @return the static ICore instance */ static ICore *self(); /** @return ui controller */ virtual KDevelop::IUiController *uiController() = 0; /** @return plugin controller */ virtual KDevelop::IPluginController *pluginController() = 0; /** @return project controller */ virtual KDevelop::IProjectController *projectController() = 0; /** @return language controller */ virtual KDevelop::ILanguageController *languageController() = 0; /** @return part manager */ virtual KDevelop::IPartController *partController() = 0; /** @return document controller */ virtual KDevelop::IDocumentController *documentController() = 0; /** @return run controller */ virtual KDevelop::IRunController *runController() = 0; /** @return the active session */ virtual KDevelop::ISession *activeSession() = 0; /** @return the session lock for the active session */ virtual KDevelop::ISessionLock::Ptr activeSessionLock() = 0; /** @return the sourceformatter controller */ virtual KDevelop::ISourceFormatterController *sourceFormatterController() = 0; /** @return the selection controller */ virtual KDevelop::ISelectionController* selectionController() = 0; /** @return the documentation controller */ virtual KDevelop::IDocumentationController* documentationController() = 0; /** @return the debug controller */ virtual KDevelop::IDebugController* debugController() = 0; /** @return the test controller */ virtual KDevelop::ITestController* testController() = 0; + /** @return the runtime controller */ + Q_SCRIPTABLE virtual KDevelop::IRuntimeController* runtimeController() = 0; + /** @return the about data of the framework, different from the main about data which is created by the application */ virtual KAboutData aboutData() const = 0; /** @return true if the application is currently being shut down */ virtual bool shuttingDown() const = 0; Q_SIGNALS: /** Emitted when the initialization of the core components has been completed */ void initializationCompleted(); /** * Emitted immediately before tearing down the session and UI. Useful when performing any last minute * preparations such as saving settings. */ void aboutToShutdown(); /** * Emitted when the teardown of the core components has been completed. */ void shutdownCompleted(); protected: explicit ICore(QObject *parent = nullptr); static ICore *m_self; }; } #endif diff --git a/interfaces/iruntime.cpp b/interfaces/iruntime.cpp new file mode 100644 index 0000000000..dcba663ea7 --- /dev/null +++ b/interfaces/iruntime.cpp @@ -0,0 +1,24 @@ +/* This file is part of KDevelop + Copyright 2017 Aleix Pol + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "iruntime.h" + +using namespace KDevelop; + +KDevelop::IRuntime::~IRuntime() = default; diff --git a/interfaces/iruntime.h b/interfaces/iruntime.h new file mode 100644 index 0000000000..ae910a5bd9 --- /dev/null +++ b/interfaces/iruntime.h @@ -0,0 +1,64 @@ +/* This file is part of KDevelop + Copyright 2017 Aleix Pol + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVPLATFORM_IRUNTIME_H +#define KDEVPLATFORM_IRUNTIME_H + +#include "interfacesexport.h" +#include +#include + +class QProcess; +class KProcess; + +namespace KDevelop +{ +class Path; + +/** + * A runtime represents an environment that will be targetted + * + * It allows the IDE to interact with virtual systems that live in different + * namespaces. + * + * It allows to execute processes in them and translate the paths these runtimes + * offer into ones that will be visible to our process to interact with. + */ +class KDEVPLATFORMINTERFACES_EXPORT IRuntime : public QObject +{ + Q_OBJECT +public: + ~IRuntime() override; + + /** @returns a display string that identifies the runtime */ + virtual QString name() const = 0; + + virtual void startProcess(QProcess* process) = 0; + virtual void startProcess(KProcess* process) = 0; + + virtual Path pathInRuntime(const Path& localPath) = 0; + virtual Path pathInHost(const Path& runtimePath) = 0; + + virtual void setEnabled(bool enabled) = 0; +}; + +} + +#endif + diff --git a/interfaces/iruntimecontroller.cpp b/interfaces/iruntimecontroller.cpp new file mode 100644 index 0000000000..ab3a98f857 --- /dev/null +++ b/interfaces/iruntimecontroller.cpp @@ -0,0 +1,23 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "iruntimecontroller.h" + +KDevelop::IRuntimeController::IRuntimeController() = default; + +KDevelop::IRuntimeController::~IRuntimeController() = default; diff --git a/interfaces/iruntimecontroller.h b/interfaces/iruntimecontroller.h new file mode 100644 index 0000000000..1e36bcef79 --- /dev/null +++ b/interfaces/iruntimecontroller.h @@ -0,0 +1,55 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVPLATFORM_IRUNTIMECONTROLLER_H +#define KDEVPLATFORM_IRUNTIMECONTROLLER_H + +#include +#include +#include + +namespace KDevelop { +class IRuntime; + +/** + * Exposes runtimes. + * + * @author Aleix Pol + */ +class KDEVPLATFORMINTERFACES_EXPORT IRuntimeController: public QObject +{ + Q_OBJECT +public: + IRuntimeController(); + ~IRuntimeController() override; + + virtual void addRuntimes(const QVector &runtimes) = 0; + + virtual QVector availableRuntimes() const = 0; + + virtual void setCurrentRuntime(IRuntime* doc) = 0; + + virtual IRuntime* currentRuntime() const = 0; + +Q_SIGNALS: + void currentRuntimeChanged(IRuntime* currentRuntime); +}; + +} + +#endif diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index c79f2aa5e5..0b0048cb8e 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,917 +1,917 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library 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; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include "qtcompat_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parsejob.h" using namespace KDevelop; namespace { const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we do not need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || (original.fileName().isEmpty() && original.isLocalFile())) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; inline uint qHash(const DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) { if(target.notifyWhenReady) ret << target.notifyWhenReady; } return ret; } }; Q_DECLARE_TYPEINFO(DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(DocumentParsePlan, Q_MOVABLE_TYPE); class KDevelop::BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_progressTimer.setSingleShot(true); m_progressTimer.setInterval(500); ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); QObject::connect(&m_progressTimer, &QTimer::timeout, m_parser, &BackgroundParser::updateProgressBar); } void startTimerThreadSafe(int delay) { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection, Q_ARG(int, delay)); } ~BackgroundParserPrivate() { m_weaver.resume(); m_weaver.finish(); } // Non-mutex guarded functions, only call with m_mutex acquired. int currentBestRunningPriority() const { int bestRunningPriority = BackgroundParser::WorstPriority; for (const auto* decorator : m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } return bestRunningPriority; } IndexedString nextDocumentToParse() const { // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. const int bestRunningPriority = currentBestRunningPriority(); for (auto it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { const auto priority = it1.key(); if(priority > m_neededPriority) break; //The priority is not good enough to be processed right now if (m_parseJobs.count() >= m_threads && priority > BackgroundParser::NormalPriority && !specialParseJob) { break; //The additional parsing thread is reserved for higher priority parsing } for (const auto& url : it1.value()) { // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(url)) { continue; } Q_ASSERT(m_documents.contains(url)); const auto& parsePlan = m_documents[url]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { continue; } return url; } } return {}; } /** * Create a single delayed parse job * * E.g. jobs for documents which have been changed by the user, but also to * handle initial startup where we parse all project files. */ void parseDocumentsInternal() { if(m_shuttingDown) return; //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if (m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority)) { return; } const auto& url = nextDocumentToParse(); if (!url.isEmpty()) { qCDebug(LANGUAGE) << "creating parse-job" << url << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(url.str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = nullptr; { // copy shared data before unlocking the mutex const auto parsePlanConstIt = m_documents.constFind(url); const DocumentParsePlan parsePlan = *parsePlanConstIt; // we must not lock the mutex while creating a parse job // this could in turn lock e.g. the DUChain and then // we have a classic lock order inversion (since, usually, // we lock first the duchain and then our background parser // mutex) // see also: https://bugs.kde.org/show_bug.cgi?id=355100 m_mutex.unlock(); decorator = createParseJob(url, parsePlan); m_mutex.lock(); } // iterator might get invalid during the time we didn't have the lock // search again const auto parsePlanIt = m_documents.find(url); if (parsePlanIt != m_documents.end()) { // Remove all mentions of this document. for (const auto& target : qAsConst(parsePlanIt->targets)) { m_documentsForPriority[target.priority].remove(url); } m_documents.erase(parsePlanIt); } else { qWarning(LANGUAGE) << "Document got removed during parse job creation:" << url; } if (decorator) { if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread m_parseJobs.insert(url, decorator); m_weaver.enqueue(ThreadWeaver::JobPointer(decorator)); } else { --m_maxParseJobs; } if (!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); } else { // make sure we cleaned up properly // TODO: also empty m_documentsForPriority when m_documents is empty? or do we want to keep capacity? Q_ASSERT(std::none_of(m_documentsForPriority.constBegin(), m_documentsForPriority.constEnd(), [] (const QSet& docs) { return !docs.isEmpty(); })); } } m_parser->updateProgressData(); } // NOTE: you must not access any of the data structures that are protected by any of the // background parser internal mutexes in this method // see also: https://bugs.kde.org/show_bug.cgi?id=355100 ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, const DocumentParsePlan& parsePlan) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); const auto languages = m_languageController->languagesForUrl(qUrl); const auto& notifyWhenReady = parsePlan.notifyWhenReady(); for (const auto language : languages) { if (!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(parsePlan.priority()); job->setMinimumFeatures(parsePlan.features()); job->setNotifyWhenReady(notifyWhenReady); job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if (languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed for (const auto& n : notifyWhenReady) { if (!n) { continue; } QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); } return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { m_parser->setThreadCount(qgetenv("KDEV_BACKGROUNDPARSER_MAXTHREADS").toInt()); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay = 500; int m_threads = 1; bool m_shuttingDown; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; // generic high-level mutex QMutex m_mutex; // local mutex only protecting m_managed QMutex m_managedMutex; // A change tracker for each managed document QHash m_managed; int m_maxParseJobs = 0; int m_doneParseJobs = 0; QHash m_jobProgress; /// The minimum priority needed for processed jobs int m_neededPriority = BackgroundParser::WorstPriority; int m_progressMax = 0; int m_progressDone = 0; QTimer m_progressTimer; }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressData(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for (auto it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags, int delay) { -// qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); - Q_ASSERT(isValidURL(url)); + qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); +// Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); auto it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } if ( delay == ILanguageSupport::DefaultDelay ) { delay = d->m_delay; } d->startTimerThreadSafe(delay); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(d->m_delay); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressData(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(d->m_delay); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressData(); } void BackgroundParser::updateProgressData() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for (auto it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) { additionalProgress += *it; } d->m_progressMax = d->m_maxParseJobs*1000; d->m_progressDone = (additionalProgress + d->m_doneParseJobs)*1000; if (!d->m_progressTimer.isActive()) { d->m_progressTimer.start(); } } // Cancel progress updating and hide progress-bar when parsing is done. if(d->m_doneParseJobs == d->m_maxParseJobs || (d->m_neededPriority == BackgroundParser::BestPriority && d->m_weaver.queueLength() == 0)) { if (d->m_progressTimer.isActive()) { d->m_progressTimer.stop(); } emit d->m_parser->hideProgress(d->m_parser); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int milliseconds) { if (d->m_delay != milliseconds) { d->m_delay = milliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_managedMutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return nullptr; } if ( !isValidURL(url) ) { qCWarning(LANGUAGE) << "Tracker requested for invalild URL:" << url.toUrl(); } - Q_ASSERT(isValidURL(url)); +// Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_managedMutex); return d->m_managed.value(url, nullptr); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); QMutexLocker l2(&d->m_managedMutex); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this QMutexLocker l2(&d->m_managedMutex); if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !trackerForUrl(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer(int delay) { d->m_timer.start(delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::updateProgressBar() { emit showProgress(this, 0, d->m_progressMax, d->m_progressDone); } diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index c0c8372fc6..54fd9fdbbd 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,541 +1,561 @@ /* This file is part of KDevelop Copyright 2012 Ivan Shapovalov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputexecutejob.h" #include "outputmodel.h" #include "outputdelegate.h" #include "debug.h" +#include +#include +#include #include #include #include #include #include #include #include namespace KDevelop { class OutputExecuteJobPrivate { public: explicit OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); void childProcessStdout(); void childProcessStderr(); void emitProgress(const IFilterStrategy::Progress& progress); QString joinCommandLine() const; QString getJobName(); template< typename T > static void mergeEnvironment( QProcessEnvironment& dest, const T& src ); QProcessEnvironment effectiveEnvironment(const QUrl& workingDirectory) const; QStringList effectiveCommandLine() const; OutputExecuteJob* m_owner; KProcess* m_process; ProcessLineMaker* m_lineMaker; OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; QString m_environmentProfile; QHash m_environmentOverrides; QString m_jobName; bool m_outputStarted; + bool m_executeOnHost = false; }; OutputExecuteJobPrivate::OutputExecuteJobPrivate( OutputExecuteJob* owner ) : m_owner( owner ), m_process( new KProcess( m_owner ) ), m_lineMaker( new ProcessLineMaker( m_owner ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( OutputExecuteJob::JobNotStarted ), m_properties( OutputExecuteJob::DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_outputStarted( false ) { } OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), d( new OutputExecuteJobPrivate( this ) ) { d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); connect( d->m_process, static_cast(&KProcess::finished), this, &OutputExecuteJob::childProcessExited ); connect( d->m_process, static_cast(&KProcess::error), this, &OutputExecuteJob::childProcessError ); connect( d->m_process, &KProcess::readyReadStandardOutput, this, [=] { d->childProcessStdout(); } ); connect( d->m_process, &KProcess::readyReadStandardError, this, [=] { d->childProcessStderr(); } ); } OutputExecuteJob::~OutputExecuteJob() { // indicates if process is running and survives kill, then we cannot do anything bool killSuccessful = d->m_process->state() == QProcess::NotRunning; if( !killSuccessful ) { killSuccessful = doKill(); } - Q_ASSERT( d->m_process->state() == QProcess::NotRunning || !killSuccessful ); + Q_ASSERT( d->m_process->state() != QProcess::Running || !killSuccessful ); delete d; } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return d->m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return d->m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { d->m_arguments << argument; return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { d->m_arguments << arguments; return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return d->m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { d->m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { d->m_jobName = name; QString jobName = d->getJobName(); setObjectName( jobName ); setTitle( jobName ); } QUrl OutputExecuteJob::workingDirectory() const { return d->m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const QUrl& url ) { d->m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( d->m_status == JobNotStarted ); d->m_status = JobRunning; const bool isBuilder = d->m_properties.testFlag( IsBuilderHint ); const QUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( d->m_properties.testFlag( NeedWorkingDirectory ) ) { // A directory is not given, but we need it. setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "No build directory specified for a builder job." ) ); } else { setErrorText( i18n( "No working directory specified for a process." ) ); } return emitResult(); } setModel( new OutputModel ); } else { // Basic sanity checks. if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } QFileInfo workingDirInfo( effectiveWorkingDirectory.toLocalFile() ); if( !workingDirInfo.isDir() ) { // If a working directory does not actually exist, either bail out or create it empty, // depending on what we need by properties. // We use a dedicated bool variable since !isDir() may also mean that it exists, // but is not a directory, or a symlink to an inexistent object. bool successfullyCreated = false; if( !d->m_properties.testFlag( CheckWorkingDirectory ) ) { successfullyCreated = QDir().mkdir( effectiveWorkingDirectory.toLocalFile() ); } if( !successfullyCreated ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); if (d->m_filteringStrategyPtr) { model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); } else { model()->setFilteringStrategy(d->m_filteringStrategy); } setDelegate( new OutputDelegate ); connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { d->emitProgress(progress); }); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &OutputExecuteJob::postProcessStdout ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &OutputExecuteJob::postProcessStderr ); } else { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, model(), &OutputModel::appendLines ); } if( !d->m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { d->m_outputStarted = true; startOutput(); } const QString joinedCommandLine = d->joinCommandLine(); QString headerLine; if( !effectiveWorkingDirectory.isEmpty() ) { headerLine = effectiveWorkingDirectory.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ) + "> " + joinedCommandLine; } else { headerLine = joinedCommandLine; } model()->appendLine( headerLine ); if( !effectiveWorkingDirectory.isEmpty() ) { d->m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } d->m_process->setProcessEnvironment( d->effectiveEnvironment(effectiveWorkingDirectory) ); if (!d->effectiveCommandLine().isEmpty()) { d->m_process->setProgram( d->effectiveCommandLine() ); // there is no way to input data in the output view so redirect stdin to the null device d->m_process->setStandardInputFile(QProcess::nullDevice()); - qCDebug(OUTPUTVIEW) << "Starting:" << d->m_process->program().join(QStringLiteral(" ")) << "in" << d->m_process->workingDirectory(); - d->m_process->start(); + qCDebug(OUTPUTVIEW) << "Starting:" << d->effectiveCommandLine() << d->m_process->QProcess::program() << "in" << d->m_process->workingDirectory(); + if (d->m_executeOnHost) { + d->m_process->start(); + } else { + KDevelop::ICore::self()->runtimeController()->currentRuntime()->startProcess(d->m_process); + } } else { QString errorMessage = i18n("Failed to specify program to start"); model()->appendLine( i18n( "*** %1 ***", errorMessage) ); setErrorText(errorMessage); setError( FailedShownError ); emitResult(); return; } } bool OutputExecuteJob::doKill() { const int terminateKillTimeout = 1000; // msecs if( d->m_status != JobRunning ) { return true; } + d->m_status = JobCanceled; d->m_process->terminate(); bool terminated = d->m_process->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->m_process->kill(); terminated = d->m_process->waitForFinished( terminateKillTimeout ); } d->m_lineMaker->flushBuffers(); if( terminated ) { model()->appendLine( i18n( "*** Killed process ***" ) ); } else { // It survived SIGKILL, leave it alone... qCWarning(OUTPUTVIEW) << "Could not kill the running process:" << d->m_process->error(); model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); return false; } return true; } void OutputExecuteJob::childProcessError( QProcess::ProcessError processError ) { // This can be called twice: one time via an error() signal, and second - from childProcessExited(). // Avoid doing things in second time. if( d->m_status != OutputExecuteJob::JobRunning ) return; d->m_status = OutputExecuteJob::JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("%1 has failed to start", commandLine().at(0)); break; case QProcess::Crashed: errorValue = i18n("%1 has crashed", commandLine().at(0)); break; case QProcess::ReadError: errorValue = i18n("Read error"); break; case QProcess::WriteError: errorValue = i18n("Write error"); break; case QProcess::Timedout: errorValue = i18n("Waiting for the process has timed out"); break; default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", d->m_process->exitCode()); break; } // Show the toolview if it's hidden for the user to be able to diagnose errors. if( !d->m_outputStarted ) { d->m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { d->m_status = JobSucceeded; d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJobPrivate::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( OutputExecuteJob::DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJobPrivate::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( OutputExecuteJob::DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) { if (progress.percent != -1) { m_owner->emitPercent(progress.percent, 100); } if (!progress.status.isEmpty()) { emit m_owner->infoMessage(m_owner, progress.status); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; // clear the other d->m_filteringStrategyPtr.reset(nullptr); } void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) { d->m_filteringStrategyPtr.reset(filterStrategy); // clear the other d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return d->m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { d->m_properties = properties; } else { d->m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { d->m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return d->m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { d->m_environmentProfile = profile; } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { d->m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { d->m_environmentOverrides.remove( name ); } + +void OutputExecuteJob::setExecuteOnHost(bool executeHost) +{ + d->m_executeOnHost = executeHost; +} + +bool OutputExecuteJob::executeOnHost() const +{ + return d->m_executeOnHost; +} + template< typename T > void OutputExecuteJobPrivate::mergeEnvironment( QProcessEnvironment& dest, const T& src ) { for( typename T::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJobPrivate::effectiveEnvironment(const QUrl& workingDirectory) const { const EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString environmentProfile = m_owner->environmentProfile(); if( environmentProfile.isEmpty() ) { environmentProfile = environmentProfiles.defaultProfileName(); } QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); auto userEnv = environmentProfiles.variables(environmentProfile); expandVariables(userEnv, environment); OutputExecuteJobPrivate::mergeEnvironment( environment, userEnv ); OutputExecuteJobPrivate::mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( OutputExecuteJob::PortableMessages ) ) { environment.remove( QStringLiteral( "LC_ALL" ) ); environment.insert( QStringLiteral( "LC_MESSAGES" ), QStringLiteral( "C" ) ); } if (!workingDirectory.isEmpty() && environment.contains(QStringLiteral("PWD"))) { // also update the environment variable for the cwd, otherwise scripts can break easily environment.insert(QStringLiteral("PWD"), workingDirectory.toLocalFile()); } return environment; } QString OutputExecuteJobPrivate::joinCommandLine() const { return KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJobPrivate::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = m_owner->privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << m_owner->privilegedExecutionCommand() << QStringLiteral("--") << m_owner->commandLine(); } else { return m_owner->commandLine(); } } QString OutputExecuteJobPrivate::getJobName() { const QString joinedCommandLine = joinCommandLine(); if( m_properties.testFlag( OutputExecuteJob::AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { return m_jobName + ": " + joinedCommandLine; } else { return joinedCommandLine; } } else { return m_jobName; } } } // namespace KDevelop #include "moc_outputexecutejob.cpp" diff --git a/outputview/outputexecutejob.h b/outputview/outputexecutejob.h index dc11c76ec0..d28d561ef6 100644 --- a/outputview/outputexecutejob.h +++ b/outputview/outputexecutejob.h @@ -1,250 +1,253 @@ /* This file is part of KDevelop C opyright 2012 Ivan Shapoval*ov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_OUTPUTEXECUTEJOB_H #define KDEVPLATFORM_OUTPUTEXECUTEJOB_H #include "outputjob.h" #include "outputmodel.h" #include #include namespace KDevelop { class OutputExecuteJobPrivate; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputExecuteJob : public OutputJob { Q_OBJECT public: enum JobStatus { JobRunning = 0, /**< The job is running */ JobSucceeded = 1, /**< The job has succeeded */ JobCanceled = 2, /**< The job has been cancelled */ JobFailed = 3, /**< The job has failed */ JobNotStarted = 4 /**< The job hasn't been started so far */ }; enum { InvalidWorkingDirectoryError = OutputJob::UserDefinedError, UserDefinedError }; enum JobProperty { AppendProcessString = 0x001, /**< Whether to append a process string to the user-specified job name */ NeedWorkingDirectory = 0x002, /**< Whether to require a non-empty working directory to be provided */ CheckWorkingDirectory = 0x004, /**< Whether to check that the working directory actually exists (and not to create it if needed) */ PortableMessages = 0x008, /**< Whether to set LC_MESSAGES=C in the process' environment */ DisplayStdout = 0x010, /**< Whether to pass process' stdout to the output model */ DisplayStderr = 0x020, /**< Whether to pass process' stderr to the output model */ NoSilentOutput = 0x040, /**< Whether to call \ref startOutput() only if verbosity is \ref OutputJob::Verbose */ PostProcessOutput = 0x080, /**< Whether to connect line maker's signals to \ref postProcessStdout() and \ref postProcessStderr() */ IsBuilderHint = 0x100, /**< Whether to use builder-specific messages to talk to user (e. g. "build directory" instead of "working directory" */ }; Q_FLAGS(JobProperty JobProperties) Q_DECLARE_FLAGS(JobProperties, JobProperty) explicit OutputExecuteJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose ); ~OutputExecuteJob() override; /** * Get the job's status (associated with the process). * * @returns The job's status. * @see JobStatus */ JobStatus status() const; /** * Get the job's output model. * * @returns The job's output model, downcasted to \ref OutputModel */ OutputModel* model() const; /** * Returns a working directory for the job's process. * * @returns URL which has been set through \ref setWorkingDirectory(); empty URL if unset. */ virtual QUrl workingDirectory() const; /** * Set a working directory for the job's process. * Effective if \ref workingDirectory() hasn't been overridden. * * @param directory a valid local directory URL, or an empty URL to unset. */ void setWorkingDirectory( const QUrl& directory ); /** * Get process' command line. * * @returns The command line for the process, with first element in list being the program path. */ virtual QStringList commandLine() const; /** * Append an element to the command line argument list for this process. * If no executable is set yet, it will be set instead. * Effective if \ref commandLine() hasn't been overridden. * * @param argument the argument to add */ OutputExecuteJob& operator<<( const QString& argument ); /** * Append a list of elements to the command line argument list for this process. * If no executable is set yet, it will be set from the first argument in given list. * Effective if \ref commandLine() hasn't been overridden. * * @param arguments the arguments to add */ OutputExecuteJob& operator<<( const QStringList& arguments ); /** * Get the privilege escalation command ("su", "sudo", etc.) used for the job's process. * * @returns The privilege escalation command name and arguments; empty list if not set. */ virtual QStringList privilegedExecutionCommand() const; /** * Set the privilege escalation command ("su", "sudo", etc.) which will be used for the job's process. * Effective if \ref privilegedExecutionCommand() hasn't been overridden. * * @param command The privilege escalation command's name and arguments; empty list to unset. * @see privilegedCommand */ void setPrivilegedExecutionCommand( const QStringList& command ); /** * A convenience function to set the job name. * * Calls \ref setTitle() and \ref setObjectName(). * * @note If you need the command-line to be appended to the job name, * make sure that it is already configured upon calling this function. * * @param name The name to set; empty string to use default (process string). */ void setJobName( const QString& name ); /** * Set one of the standard filtering strategies for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** * Set the filtering strategy for the output model. */ void setFilteringStrategy(IFilterStrategy* filterStrategy); /** * Get the current properties of the job. * * @note Default-set properties are: \ref DisplayStdout. */ virtual JobProperties properties() const; /** * Set properties of the job. * Effective if \ref properties() hasn't been overridden. * * @param properties Which flags to add to the job. * @param override Whether to assign instead of doing bitwise OR. * @see JobProperties, properties(), unsetProperties() */ void setProperties( JobProperties properties, bool override = false ); /** * Unset properties of the job. * * @param properties Which flags to remove from the job * @see JobProperties, properties(), setProperties() */ void unsetProperties( JobProperties properties ); /** * Add a variable to the job's process environment. * * The variables added with this method override ones from the system environment and * the global environment profile, but are overridden by "PortableMessages" property. * * @param name The name of a variable to add * @param value The value of a variable to add; empty string to unset. */ void addEnvironmentOverride( const QString& name, const QString& value ); /** * Remove a variable from the override set. * * @param name The name of a variable to remove. * @note This does not force a variable to empty value; this is to undo the overriding itself. */ void removeEnvironmentOverride( const QString& name ); /** * Get the global environment profile name for the job's process. * * @returns The environment profile name to use in the job's process; empty if unset. */ virtual QString environmentProfile() const; /** * Set the environment profile name for the job's process. * Effective if \ref environmentProfile() hasn't been overridden. * * @param profile The name of profile to set. */ void setEnvironmentProfile( const QString& profile ); void start() override; + void setExecuteOnHost(bool executeHost); + bool executeOnHost() const; + protected: bool doKill() override; protected Q_SLOTS: // Redefine these functions if you want to post-process the output somehow // before it hits the output model. // Default implementations for either function call "model()->appendLines( lines );". // Do the same if you need the output to be visible. virtual void postProcessStdout( const QStringList& lines ); virtual void postProcessStderr( const QStringList& lines ); // Redefine these functions if you want to handle process' exit codes in a special manner. // One possible usage is in "cvs diff" job which returns 1 on success. virtual void childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); virtual void childProcessError( QProcess::ProcessError processError ); private: friend class OutputExecuteJobPrivate; OutputExecuteJobPrivate* d; }; } // namespace KDevelop Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::OutputExecuteJob::JobProperties); #endif // KDEVPLATFORM_OUTPUTEXECUTEJOB_H diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4cf0b7b9aa..082af46cb6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,47 +1,49 @@ ecm_optional_add_subdirectory(konsole) add_subdirectory(filemanager) if (Grantlee5_FOUND) add_subdirectory(appwizard) endif() add_subdirectory(projectmanagerview) add_subdirectory(genericprojectmanager) add_subdirectory(standardoutputview) add_subdirectory(documentview) add_subdirectory(quickopen) add_subdirectory(executescript) add_subdirectory(contextbrowser) ecm_optional_add_subdirectory(cvs) add_subdirectory(problemreporter) add_subdirectory(execute) add_subdirectory(externalscript) add_subdirectory(documentswitcher) add_subdirectory(patchreview) add_subdirectory(openwith) add_subdirectory(grepview) ecm_optional_add_subdirectory(git) ecm_optional_add_subdirectory(bazaar) ecm_optional_add_subdirectory(perforce) add_subdirectory(vcschangesview) +add_subdirectory(flatpak) +add_subdirectory(docker) if (Grantlee5_FOUND) add_subdirectory(filetemplates) add_subdirectory(codeutils) endif() add_subdirectory(testview) add_subdirectory(switchtobuddy) add_subdirectory(projectfilter) if (Qt5QuickWidgets_FOUND) add_subdirectory(welcomepage) endif() add_subdirectory(outlineview) ecm_optional_add_subdirectory(classbrowser) find_package(SubversionLibrary) set_package_properties(SubversionLibrary PROPERTIES PURPOSE "Support for Subversion integration" URL "http://subversion.tigris.org" TYPE OPTIONAL) if(SubversionLibrary_FOUND) ecm_optional_add_subdirectory(subversion) endif() diff --git a/plugins/docker/CMakeLists.txt b/plugins/docker/CMakeLists.txt new file mode 100644 index 0000000000..ca3ac0596c --- /dev/null +++ b/plugins/docker/CMakeLists.txt @@ -0,0 +1,14 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevdocker\") + +qt5_add_resources(dockerplugin_SRCS kdevdockerplugin.qrc) +ki18n_wrap_ui(dockerplugin_SRCS dockerpreferences.ui) +kconfig_add_kcfg_files(dockerplugin_SRCS dockerpreferencessettings.kcfgc) + +kdevplatform_add_plugin(kdevdocker JSON kdevdocker.json SOURCES dockerplugin.cpp dockerruntime.cpp dockerpreferences.cpp ${dockerplugin_SRCS}) +target_link_libraries(kdevdocker + KF5::CoreAddons + KDev::Interfaces + KDev::Util + KDev::OutputView + KDev::Project +) diff --git a/plugins/docker/Messages.sh b/plugins/docker/Messages.sh new file mode 100644 index 0000000000..2c97d4e8fc --- /dev/null +++ b/plugins/docker/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevdocker.pot +rm -f rc.cpp diff --git a/plugins/docker/dockerplugin.cpp b/plugins/docker/dockerplugin.cpp new file mode 100644 index 0000000000..10305e297c --- /dev/null +++ b/plugins/docker/dockerplugin.cpp @@ -0,0 +1,171 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "dockerplugin.h" +#include "dockerruntime.h" +#include "dockerpreferences.h" +#include "dockerpreferencessettings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(KDevDockerFactory, "kdevdocker.json", registerPlugin();) + +using namespace KDevelop; + +DockerPlugin::DockerPlugin(QObject *parent, const QVariantList & /*args*/) + : KDevelop::IPlugin( QStringLiteral("kdevdocker"), parent ) + , m_settings(new DockerPreferencesSettings) +{ + runtimeChanged(ICore::self()->runtimeController()->currentRuntime()); + + setXMLFile( QStringLiteral("kdevdockerplugin.rc") ); + connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &DockerPlugin::runtimeChanged); + + QProcess* process = new QProcess(this); + connect(process, QOverload::of(&QProcess::finished), this, &DockerPlugin::imagesListFinished); + process->start(QStringLiteral("docker"), {QStringLiteral("images"), QStringLiteral("--filter"), QStringLiteral("dangling=false"), QStringLiteral("--format"), QStringLiteral("{{.Repository}}:{{.Tag}}\t{{.ID}}")}, QIODevice::ReadOnly); + + DockerRuntime::s_settings = m_settings.data(); +} + +DockerPlugin::~DockerPlugin() +{ + DockerRuntime::s_settings = nullptr; +} + +void DockerPlugin::imagesListFinished(int code) +{ + if (code != 0) + return; + + QProcess* process = qobject_cast(sender()); + Q_ASSERT(process); + QTextStream stream(process); + QVector runtimes; + while(!stream.atEnd()) { + const QString line = stream.readLine(); + const QStringList parts = line.split(QLatin1Char('\t')); + + const QString tag = parts[0] == QLatin1String("") ? parts[1] : parts[0]; + runtimes << new DockerRuntime(tag); + } + + if (!runtimes.isEmpty()) + ICore::self()->runtimeController()->addRuntimes(runtimes); + process->deleteLater(); +} + +void DockerPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime) +{ + const bool isDocker = qobject_cast(newRuntime); + + for(auto action: actionCollection()->actions()) + action->setEnabled(isDocker); +} + +KDevelop::ContextMenuExtension DockerPlugin::contextMenuExtension(KDevelop::Context* context) +{ + QList urls; + + if ( context->type() == KDevelop::Context::FileContext ) { + KDevelop::FileContext* filectx = dynamic_cast( context ); + urls = filectx->urls(); + } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { + KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); + foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { + if ( item->file() ) { + urls << item->file()->path().toUrl(); + } + } + } + + for(auto it = urls.begin(); it != urls.end(); ) { + if (it->isLocalFile() && it->fileName() == QLatin1String("Dockerfile")) { + ++it; + } else { + it = urls.erase(it); + } + } + + if ( !urls.isEmpty() ) { + KDevelop::ContextMenuExtension ext; + foreach(const QUrl &url, urls) { + const KDevelop::Path file(url); + + auto action = new QAction(i18n("docker build '%1'", file.path()), this); + connect(action, &QAction::triggered, this, [this, file]() { + const auto dir = file.parent(); + const QString name = QInputDialog::getText( + ICore::self()->uiController()->activeMainWindow(), i18n("Choose tag name..."), + i18n("Tag name for '%1'", file.path()), + QLineEdit::Normal, dir.lastPathSegment() + ); + + auto process = new OutputExecuteJob; + process->setExecuteOnHost(true); + *process << QStringList{"docker", "build", "--tag", name, dir.toLocalFile()}; + connect(process, &KJob::finished, this, [this, name] (KJob* job) { + if (job->error() != 0) + return; + + ICore::self()->runtimeController()->addRuntimes({ new DockerRuntime(name) }); + }); + process->start(); + }); + ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action); + } + + return ext; + } + + return KDevelop::IPlugin::contextMenuExtension( context ); +} + +int DockerPlugin::configPages() const +{ + return 1; +} + +KDevelop::ConfigPage* DockerPlugin::configPage(int number, QWidget* parent) +{ + if (number == 0) { + return new DockerPreferences(this, m_settings.data(), parent); + } + return nullptr; +} + +#include "dockerplugin.moc" diff --git a/plugins/docker/dockerplugin.h b/plugins/docker/dockerplugin.h new file mode 100644 index 0000000000..5951e086e9 --- /dev/null +++ b/plugins/docker/dockerplugin.h @@ -0,0 +1,50 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef DOCKERRUNTIMEPROVIDER_H +#define DOCKERRUNTIMEPROVIDER_H + +#include +#include +#include +#include + +class DockerPreferencesSettings; + +class DockerPlugin : public KDevelop::IPlugin +{ +Q_OBJECT +public: + DockerPlugin(QObject *parent, const QVariantList & args); + ~DockerPlugin() override; + + KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context * context) override; + + void imagesListFinished(int code); + + int configPages() const override; + KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; + +private: + void runtimeChanged(KDevelop::IRuntime* newRuntime); + + QHash m_runtimes; + QScopedPointer m_settings; +}; + +#endif diff --git a/plugins/docker/dockerpreferences.cpp b/plugins/docker/dockerpreferences.cpp new file mode 100644 index 0000000000..e372c3cb67 --- /dev/null +++ b/plugins/docker/dockerpreferences.cpp @@ -0,0 +1,34 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "dockerpreferences.h" +#include "ui_dockerpreferences.h" + +DockerPreferences::DockerPreferences(KDevelop::IPlugin* plugin, KCoreConfigSkeleton* config, QWidget* parent) + : KDevelop::ConfigPage(plugin, config, parent) +{ + auto m_prefsUi = new Ui::DockerPreferences; + m_prefsUi->setupUi(this); +} + +DockerPreferences::~DockerPreferences() = default; + +QString DockerPreferences::name() const +{ + return QStringLiteral("Docker"); +} diff --git a/plugins/docker/dockerpreferences.h b/plugins/docker/dockerpreferences.h new file mode 100644 index 0000000000..1554dfb52a --- /dev/null +++ b/plugins/docker/dockerpreferences.h @@ -0,0 +1,39 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef DOCKERPREFERENCES_H +#define DOCKERPREFERENCES_H + +#include +#include + +namespace Ui { class DockerPreferences; } + +class DockerPreferences : public KDevelop::ConfigPage +{ + Q_OBJECT + public: + explicit DockerPreferences(KDevelop::IPlugin* plugin, KCoreConfigSkeleton* config, QWidget* parent = nullptr); + ~DockerPreferences() override; + + QString name() const override; + private: + QScopedPointer m_prefsUi; +}; + +#endif diff --git a/plugins/docker/dockerpreferences.ui b/plugins/docker/dockerpreferences.ui new file mode 100644 index 0000000000..ca45674fd4 --- /dev/null +++ b/plugins/docker/dockerpreferences.ui @@ -0,0 +1,41 @@ + + + DockerPreferences + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + 'docker run' arguments: + + + + + + + + + + Projects volume + + + + + + + + + + + diff --git a/plugins/docker/dockerpreferencessettings.kcfg b/plugins/docker/dockerpreferencessettings.kcfg new file mode 100644 index 0000000000..249ef1c390 --- /dev/null +++ b/plugins/docker/dockerpreferencessettings.kcfg @@ -0,0 +1,13 @@ + + + + + + + /src + + + diff --git a/plugins/docker/dockerpreferencessettings.kcfgc b/plugins/docker/dockerpreferencessettings.kcfgc new file mode 100644 index 0000000000..ffcbe7c6aa --- /dev/null +++ b/plugins/docker/dockerpreferencessettings.kcfgc @@ -0,0 +1,3 @@ +ClassName=DockerPreferencesSettings +File=dockerpreferencessettings.kcfg + diff --git a/plugins/docker/dockerruntime.cpp b/plugins/docker/dockerruntime.cpp new file mode 100644 index 0000000000..f33c01819a --- /dev/null +++ b/plugins/docker/dockerruntime.cpp @@ -0,0 +1,119 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "dockerruntime.h" +#include "dockerpreferencessettings.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +DockerPreferencesSettings* DockerRuntime::s_settings = nullptr; + +DockerRuntime::DockerRuntime(const QString &tag) + : KDevelop::IRuntime() + , m_tag(tag) +{ +} + +DockerRuntime::~DockerRuntime() +{ +} + +void DockerRuntime::setEnabled(bool /*enable*/) +{ +} + +static QStringList projectVolumes() +{ + QStringList ret; + QString dir = DockerRuntime::s_settings->projectsVolume(); + if (!dir.endsWith(QLatin1Char('/'))) { + dir.append(QLatin1Char('/')); + } + + for(IProject* project: ICore::self()->projectController()->projects()) { + const Path path = project->path(); + if (path.isLocalFile()) { + ret << "--volume" << QStringLiteral("%1:%2").arg(path.toLocalFile(), dir + path.lastPathSegment()); + } + } + return ret; +} + +void DockerRuntime::startProcess(QProcess* process) +{ + const QStringList args = QStringList{QStringLiteral("run"), "--rm"} << KShell::splitArgs(s_settings->extraArguments()) << projectVolumes() << m_tag << process->program() << process->arguments(); + process->setProgram("docker"); + process->setArguments(args); + qDebug() << "run..." << process->program() << args; + process->start(); +} + +void DockerRuntime::startProcess(KProcess* process) +{ + process->setProgram(QStringList{ "docker", "run", "--rm" } << KShell::splitArgs(s_settings->extraArguments()) << projectVolumes() << m_tag << process->program()); + + qDebug() << "yokai!" << process << process->program().join(' '); + process->start(); +} + +KDevelop::Path DockerRuntime::pathInHost(const KDevelop::Path& runtimePath) +{ + Path ret = runtimePath; + const Path projectsDir(DockerRuntime::s_settings->projectsVolume()); + if (projectsDir.isParentOf(runtimePath)) { + const auto relPath = runtimePath.relativePath(projectsDir); + const int index = relPath.indexOf(QLatin1Char('/')); + auto project = ICore::self()->projectController()->findProjectByName(relPath.left(index)); + if (!project) { + qWarning() << "No project for" << relPath; + } else { + const auto repPathProject = relPath.mid(index+1); + ret = Path(project->path(), repPathProject);; + } + } + return ret; +} + +KDevelop::Path DockerRuntime::pathInRuntime(const KDevelop::Path& localPath) +{ + auto project = ICore::self()->projectController()->findProjectForUrl(localPath.toUrl()); + if (project) { + const Path projectsDir(DockerRuntime::s_settings->projectsVolume()); + const QString relpath = project->path().relativePath(localPath); + const KDevelop::Path ret(projectsDir, project->name() + QLatin1Char('/') + relpath); + qDebug() << "docker pathInRuntime..." << ret << project->path() << relpath; + return ret; + } else { + qWarning() << "only project files are available on the docker runtime" << localPath; + } + return localPath; +} + diff --git a/plugins/docker/dockerruntime.h b/plugins/docker/dockerruntime.h new file mode 100644 index 0000000000..8134c301db --- /dev/null +++ b/plugins/docker/dockerruntime.h @@ -0,0 +1,51 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef DOCKERRUNTIME_H +#define DOCKERRUNTIME_H + +#include +#include +#include + +class KJob; +class DockerPreferencesSettings; + +class DockerRuntime : public KDevelop::IRuntime +{ + Q_OBJECT +public: + DockerRuntime(const QString& tag); + ~DockerRuntime() override; + + QString name() const override { return m_tag; } + + void setEnabled(bool enabled) override; + + void startProcess(KProcess *process) override; + void startProcess(QProcess *process) override; + KDevelop::Path pathInHost(const KDevelop::Path & runtimePath) override; + KDevelop::Path pathInRuntime(const KDevelop::Path & localPath) override; + + static DockerPreferencesSettings* s_settings; + +private: + const QString m_tag; +}; + +#endif diff --git a/plugins/docker/kdevdocker.json b/plugins/docker/kdevdocker.json new file mode 100644 index 0000000000..57ce5b8191 --- /dev/null +++ b/plugins/docker/kdevdocker.json @@ -0,0 +1,17 @@ +{ + "KPlugin": { + "Authors": [ + { "Name": "Aleix Pol", "Email": "aleixpol@kde.org" } + ], + "Category": "Global", + "Description": "Exposes Docker runtimes", + "Icon": "docker", + "Id": "kdevdocker", + "License": "GPL", + "Name": "Docker Support", + "ServiceTypes": [ "KDevelop/Plugin" ], + "Version": "0.1" + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Mode": "NoGUI" +} diff --git a/plugins/docker/kdevdockerplugin.qrc b/plugins/docker/kdevdockerplugin.qrc new file mode 100644 index 0000000000..7f40231df9 --- /dev/null +++ b/plugins/docker/kdevdockerplugin.qrc @@ -0,0 +1,6 @@ + + + + kdevdockerplugin.rc + + diff --git a/plugins/docker/kdevdockerplugin.rc b/plugins/docker/kdevdockerplugin.rc new file mode 100644 index 0000000000..24e310aa02 --- /dev/null +++ b/plugins/docker/kdevdockerplugin.rc @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp index 5fd905da44..946c486ee8 100644 --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -1,249 +1,252 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappjob.h" #include "executescriptplugin.h" #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include +#include #include "iexecutescriptplugin.h" #include "debug.h" using namespace KDevelop; ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(new KProcess( this )), lineMaker(new KDevelop::ProcessLineMaker( proc, this )) { qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job"; setCapabilities(Killable); IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecuteScriptPlugin"))->extension(); Q_ASSERT(iface); const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString envProfileName = iface->environmentProfileName(cfg); QString err; QString interpreterString = iface->interpreter( cfg, err ); // check for errors happens in the executescript plugin already KShell::Errors err_; QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ); if ( interpreter.isEmpty() ) { // This should not happen, because of the checks done in the executescript plugin qWarning() << "no interpreter specified"; return; } if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } QUrl script; if( !iface->runCurrentFile( cfg ) ) { script = iface->script( cfg, err ); } else { KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument(); if( !document ) { setError( -1 ); setErrorText( i18n( "There is no active document to launch." ) ); return; } - script = document->url(); + script = ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(document->url())).toUrl(); } if( !err.isEmpty() ) { setError( -3 ); setErrorText( err ); return; } QString remoteHost = iface->remoteHost( cfg, err ); if( !err.isEmpty() ) { setError( -4 ); setErrorText( err ); return; } if (envProfileName.isEmpty()) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment profile.", cfg->name() ); envProfileName = environmentProfiles.defaultProfileName(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } KDevelop::OutputModel::OutputFilterStrategy currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel; m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &ScriptAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &ScriptAppJob::processFinished ); // Now setup the process parameters proc->setEnvironment(environmentProfiles.createEnvironment(envProfileName, proc->systemEnvironment())); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); } - proc->setWorkingDirectory( wc.toLocalFile() ); + proc->setWorkingDirectory( ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(wc)).toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program; if (!remoteHost.isEmpty()) { program << QStringLiteral("ssh"); QStringList parts = remoteHost.split(QLatin1Char(':')); program << parts.first(); if (parts.length() > 1) { program << "-p "+parts.at(1); } } program << interpreter; program << script.toLocalFile(); program << arguments; qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program; proc->setOutputChannelMode(KProcess::MergedChannels); proc->setProgram( program ); setTitle(cfg->name()); } void ScriptAppJob::start() { qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(QLatin1Char( ' ' ) ) ) ); - proc->start(); + ICore::self()->runtimeController()->currentRuntime()->startProcess(proc); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ScriptAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done"; emitResult(); } void ScriptAppJob::processError( QProcess::ProcessError error ) { qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString(); if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(QLatin1Char( ' ' ) ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error"; } void ScriptAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* ScriptAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/flatpak/CMakeLists.txt b/plugins/flatpak/CMakeLists.txt new file mode 100644 index 0000000000..66c0e6b28b --- /dev/null +++ b/plugins/flatpak/CMakeLists.txt @@ -0,0 +1,11 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevflatpak\") + +qt5_add_resources(flatpakplugin_SRCS kdevflatpakplugin.qrc) +kdevplatform_add_plugin(kdevflatpak JSON kdevflatpak.json SOURCES flatpakplugin.cpp flatpakruntime.cpp ${flatpakplugin_SRCS}) +target_link_libraries(kdevflatpak + KF5::CoreAddons + KDev::Interfaces + KDev::Util + KDev::OutputView + KDev::Project +) diff --git a/plugins/flatpak/Messages.sh b/plugins/flatpak/Messages.sh new file mode 100644 index 0000000000..7604e5b870 --- /dev/null +++ b/plugins/flatpak/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevflatpak.pot +rm -f rc.cpp diff --git a/plugins/flatpak/flatpakplugin.cpp b/plugins/flatpak/flatpakplugin.cpp new file mode 100644 index 0000000000..492c44e011 --- /dev/null +++ b/plugins/flatpak/flatpakplugin.cpp @@ -0,0 +1,153 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "flatpakplugin.h" +#include "flatpakruntime.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(KDevFlatpakFactory, "kdevflatpak.json", registerPlugin();) + +using namespace KDevelop; + +FlatpakPlugin::FlatpakPlugin(QObject *parent, const QVariantList & /*args*/) + : KDevelop::IPlugin( QStringLiteral("kdevflatpak"), parent ) +{ + auto ac = actionCollection(); + + auto action = new QAction(QIcon::fromTheme(QStringLiteral("run-build-clean")), i18n("Rebuild environment"), this); + action->setWhatsThis(i18n("Recompiles all dependencies for a fresh environment.")); + action->setShortcut(Qt::CTRL | Qt::META | Qt::Key_X); + connect(action, &QAction::triggered, this, &FlatpakPlugin::rebuildCurrent); + ac->addAction(QStringLiteral("runtime_flatpak_rebuild"), action); + + auto exportAction = new QAction(QIcon::fromTheme(QStringLiteral("document-export")), i18n("Export flatpak environment..."), this); + exportAction->setWhatsThis(i18n("Exports the current build into a 'bundle.flatpak' file.")); + exportAction->setShortcut(Qt::CTRL | Qt::META | Qt::Key_E); + connect(exportAction, &QAction::triggered, this, &FlatpakPlugin::exportCurrent); + ac->addAction(QStringLiteral("runtime_flatpak_export"), exportAction); + + runtimeChanged(ICore::self()->runtimeController()->currentRuntime()); + + setXMLFile( QStringLiteral("kdevflatpakplugin.rc") ); + connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &FlatpakPlugin::runtimeChanged); +} + +FlatpakPlugin::~FlatpakPlugin() = default; + +void FlatpakPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime) +{ + const bool isFlatpak = qobject_cast(newRuntime); + + for(auto action: actionCollection()->actions()) + action->setEnabled(isFlatpak); +} + +void FlatpakPlugin::rebuildCurrent() +{ + const auto runtime = qobject_cast(ICore::self()->runtimeController()->currentRuntime()); + Q_ASSERT(runtime); + runtime->rebuild(); +} + +void FlatpakPlugin::exportCurrent() +{ + const auto runtime = qobject_cast(ICore::self()->runtimeController()->currentRuntime()); + Q_ASSERT(runtime); + + const QString name = runtime->name(); + const QString path = QFileDialog::getSaveFileName(ICore::self()->uiController()->activeMainWindow(), i18n("Export %1 to..."), {}, i18n("Flatpak Bundle (*.flatpak)")); + if (!path.isEmpty()) { + runtime->exportBundle(path); + } +} + +KDevelop::ContextMenuExtension FlatpakPlugin::contextMenuExtension(KDevelop::Context* context) +{ + QList urls; + + if ( context->type() == KDevelop::Context::FileContext ) { + KDevelop::FileContext* filectx = dynamic_cast( context ); + urls = filectx->urls(); + } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { + KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); + foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { + if ( item->file() ) { + urls << item->file()->path().toUrl(); + } + } + } + + const QRegularExpression nameRx(".*\\..*\\.*.json$"); + for(auto it = urls.begin(); it != urls.end(); ) { + if (it->isLocalFile() && it->path().contains(nameRx)) { + ++it; + } else { + it = urls.erase(it); + } + } + + if ( !urls.isEmpty() ) { + KDevelop::ContextMenuExtension ext; + foreach(const QUrl &url, urls) { + const KDevelop::Path file(url); + + auto action = new QAction(i18n("Create flatpak environment for %1", file.lastPathSegment()), this); + connect(action, &QAction::triggered, this, [this, file]() { + QTemporaryDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/kdevelop-flatpak-")); + dir.setAutoRemove(false); + + const KDevelop::Path path(dir.path()); + + auto process = FlatpakRuntime::createBuildDirectory(path, file); + connect(process, &KJob::finished, this, [this, path, file] (KJob* job) { + if (job->error() != 0) + return; + + auto runtime = new FlatpakRuntime(path, file); + ICore::self()->runtimeController()->addRuntimes({runtime}); + }); + process->start(); + }); + ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action); + } + + return ext; + } + + return KDevelop::IPlugin::contextMenuExtension( context ); +} + + +#include "flatpakplugin.moc" diff --git a/plugins/flatpak/flatpakplugin.h b/plugins/flatpak/flatpakplugin.h new file mode 100644 index 0000000000..ddecefa712 --- /dev/null +++ b/plugins/flatpak/flatpakplugin.h @@ -0,0 +1,44 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FLATPAKRUNTIMEPROVIDER_H +#define FLATPAKRUNTIMEPROVIDER_H + +#include +#include +#include +#include + +class FlatpakPlugin : public KDevelop::IPlugin +{ +Q_OBJECT +public: + FlatpakPlugin(QObject *parent, const QVariantList & args); + ~FlatpakPlugin() override; + + KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context * context) override; + +private: + void runtimeChanged(KDevelop::IRuntime* newRuntime); + void rebuildCurrent(); + void exportCurrent(); + + QHash m_runtimes; +}; + +#endif diff --git a/plugins/flatpak/flatpakruntime.cpp b/plugins/flatpak/flatpakruntime.cpp new file mode 100644 index 0000000000..0503add605 --- /dev/null +++ b/plugins/flatpak/flatpakruntime.cpp @@ -0,0 +1,86 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "flatpakruntime.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +KJob* FlatpakRuntime::createBuildDirectory(const KDevelop::Path &buildDirectory, const KDevelop::Path &file) +{ + OutputExecuteJob* process = new OutputExecuteJob; + process->setExecuteOnHost(true); + process->setJobName(i18n("Creating Flatpak %1", file.lastPathSegment())); + *process << QStringList{ "flatpak-builder", "--build-only", buildDirectory.toLocalFile(), file.toLocalFile() }; + return process; +} + +FlatpakRuntime::FlatpakRuntime(const KDevelop::Path &buildDirectory, const KDevelop::Path &file) + : KDevelop::IRuntime() + , m_file(file) + , m_buildDirectory(buildDirectory) +{ +} + +FlatpakRuntime::~FlatpakRuntime() +{ + QDir(m_buildDirectory.toLocalFile()).removeRecursively(); +} + +void FlatpakRuntime::setEnabled(bool enable) +{ +} + +void FlatpakRuntime::startProcess(QProcess* process) +{ + const QStringList args = QStringList{"build", "--socket=x11", m_buildDirectory.toLocalFile(), process->program()} << process->arguments(); + process->setProgram("flatpak"); + process->setArguments(args); + process->start(); +} + +void FlatpakRuntime::startProcess(KProcess* process) +{ + process->setProgram(QStringList{ "flatpak", "--socket=x11", "build", m_buildDirectory.toLocalFile() } << process->program()); + + qDebug() << "yokai!" << process << process->program().join(' '); + process->start(); +} + +void FlatpakRuntime::rebuild() +{ + QDir(m_buildDirectory.toLocalFile()).removeRecursively(); + createBuildDirectory(m_buildDirectory, m_file)->start(); +} + +void FlatpakRuntime::exportBundle(const QString &path) +{ + const QString develName = name().remove(QLatin1String(".json")); + + OutputExecuteJob* process = new OutputExecuteJob; + process->setExecuteOnHost(true); + process->setJobName(i18n("Exporting %1", path)); + *process << QStringList{ "flatpak", "build-bundle", m_buildDirectory.toLocalFile(), path, develName }; +} diff --git a/plugins/flatpak/flatpakruntime.h b/plugins/flatpak/flatpakruntime.h new file mode 100644 index 0000000000..437e4036e5 --- /dev/null +++ b/plugins/flatpak/flatpakruntime.h @@ -0,0 +1,54 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FLATPAKRUNTIME_H +#define FLATPAKRUNTIME_H + +#include +#include +#include + +class KJob; + +class FlatpakRuntime : public KDevelop::IRuntime +{ + Q_OBJECT +public: + FlatpakRuntime(const KDevelop::Path &buildDirectory, const KDevelop::Path &file); + ~FlatpakRuntime() override; + + QString name() const override { return m_file.lastPathSegment(); } + + void setEnabled(bool enabled) override; + + void startProcess(KProcess *process) override; + void startProcess(QProcess *process) override; + KDevelop::Path pathInHost(const KDevelop::Path & runtimePath) override { return runtimePath; } + KDevelop::Path pathInRuntime(const KDevelop::Path & localPath) override { return localPath; } + + static KJob* createBuildDirectory(const KDevelop::Path &path, const KDevelop::Path &file); + + void rebuild(); + void exportBundle(const QString &path); + +private: + const KDevelop::Path m_file; + const KDevelop::Path m_buildDirectory; +}; + +#endif diff --git a/plugins/flatpak/kdevflatpak.json b/plugins/flatpak/kdevflatpak.json new file mode 100644 index 0000000000..92b777ef24 --- /dev/null +++ b/plugins/flatpak/kdevflatpak.json @@ -0,0 +1,17 @@ +{ + "KPlugin": { + "Authors": [ + { "Name": "Aleix Pol", "Email": "aleixpol@kde.org" } + ], + "Category": "Global", + "Description": "Exposes Flatpak runtimes", + "Icon": "flatpak", + "Id": "kdevflatpak", + "License": "GPL", + "Name": "Flatpak Support", + "ServiceTypes": [ "KDevelop/Plugin" ], + "Version": "0.1" + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Mode": "NoGUI" +} diff --git a/plugins/flatpak/kdevflatpakplugin.qrc b/plugins/flatpak/kdevflatpakplugin.qrc new file mode 100644 index 0000000000..af71865ecd --- /dev/null +++ b/plugins/flatpak/kdevflatpakplugin.qrc @@ -0,0 +1,6 @@ + + + + kdevflatpakplugin.rc + + diff --git a/plugins/flatpak/kdevflatpakplugin.rc b/plugins/flatpak/kdevflatpakplugin.rc new file mode 100644 index 0000000000..ccd7b69dbf --- /dev/null +++ b/plugins/flatpak/kdevflatpakplugin.rc @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index c279f7cb11..4f7a2a16ee 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,188 +1,190 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(tests) set(KDevPlatformShell_LIB_SRCS workingsetcontroller.cpp workingsets/workingset.cpp workingsets/workingsetfilelabel.cpp workingsets/workingsettoolbutton.cpp workingsets/workingsettooltipwidget.cpp workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp ktexteditorpluginintegration.cpp shellextension.cpp core.cpp uicontroller.cpp colorschemechooser.cpp projectcontroller.cpp project.cpp partcontroller.cpp #document.cpp partdocument.cpp textdocument.cpp documentcontroller.cpp languagecontroller.cpp statusbar.cpp runcontroller.cpp unitylauncher.cpp sessioncontroller.cpp session.cpp sessionlock.cpp sessionchooserdialog.cpp savedialog.cpp sourceformattercontroller.cpp completionsettings.cpp openprojectpage.cpp openprojectdialog.cpp projectinfopage.cpp selectioncontroller.cpp documentationcontroller.cpp debugcontroller.cpp launchconfiguration.cpp launchconfigurationdialog.cpp loadedpluginsdialog.cpp testcontroller.cpp projectsourcepage.cpp configdialog.cpp editorconfigpage.cpp environmentconfigurebutton.cpp + runtimecontroller.cpp + runtimesmodel.cpp checkerstatus.cpp problem.cpp problemmodelset.cpp problemmodel.cpp problemstore.cpp watcheddocumentset.cpp filteredproblemstore.cpp progresswidget/progressmanager.cpp progresswidget/statusbarprogresswidget.cpp progresswidget/overlaywidget.cpp progresswidget/progressdialog.cpp areadisplay.cpp settings/uipreferences.cpp settings/pluginpreferences.cpp settings/sourceformattersettings.cpp settings/editstyledialog.cpp settings/projectpreferences.cpp settings/environmentwidget.cpp settings/environmentprofilemodel.cpp settings/environmentprofilelistmodel.cpp settings/environmentpreferences.cpp settings/languagepreferences.cpp settings/bgpreferences.cpp settings/templateconfig.cpp settings/templatepage.cpp settings/analyzerspreferences.cpp settings/documentationpreferences.cpp ) if(APPLE) set(KDevPlatformShell_LIB_SRCS ${KDevPlatformShell_LIB_SRCS} macdockprogressview.mm ) endif() ecm_qt_declare_logging_category(KDevPlatformShell_LIB_SRCS HEADER debug.h IDENTIFIER SHELL CATEGORY_NAME "kdevplatform.shell" ) kconfig_add_kcfg_files(KDevPlatformShell_LIB_SRCS settings/uiconfig.kcfgc settings/projectconfig.kcfgc settings/languageconfig.kcfgc settings/bgconfig.kcfgc ) ki18n_wrap_ui(KDevPlatformShell_LIB_SRCS projectinfopage.ui launchconfigurationdialog.ui projectsourcepage.ui settings/uiconfig.ui settings/editstyledialog.ui settings/sourceformattersettings.ui settings/projectpreferences.ui settings/environmentwidget.ui settings/languagepreferences.ui settings/bgpreferences.ui settings/templateconfig.ui settings/templatepage.ui ) qt5_add_resources(KDevPlatformShell_LIB_SRCS kdevplatformshell.qrc) kdevplatform_add_library(KDevPlatformShell SOURCES ${KDevPlatformShell_LIB_SRCS}) target_link_libraries(KDevPlatformShell LINK_PUBLIC KF5::XmlGui KDev::Sublime KDev::OutputView KDev::Interfaces LINK_PRIVATE KF5::GuiAddons KF5::ConfigWidgets KF5::IconThemes KF5::KIOFileWidgets KF5::KIOWidgets KF5::Parts KF5::Notifications KF5::NotifyConfig KF5::TextEditor KF5::ThreadWeaver KF5::JobWidgets KF5::ItemViews KF5::WindowSystem KF5::KCMUtils #for KPluginSelector, not sure why it is in kcmutils KF5::NewStuff # template config page KF5::Archive # template config page KDev::Debugger KDev::Project KDev::Vcs KDev::Language KDev::Util KDev::Documentation ) if(APPLE) target_link_libraries(KDevPlatformShell PRIVATE "-framework AppKit") endif() install(FILES mainwindow.h plugincontroller.h shellextension.h core.h uicontroller.h colorschemechooser.h projectcontroller.h project.h partcontroller.h partdocument.h textdocument.h documentcontroller.h languagecontroller.h session.h sessioncontroller.h sessionlock.h sourceformattercontroller.h selectioncontroller.h runcontroller.h launchconfiguration.h environmentconfigurebutton.h checkerstatus.h problem.h problemmodel.h problemmodelset.h problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel ) diff --git a/shell/core.cpp b/shell/core.cpp index 4e3c671f45..5b2d40c55a 100644 --- a/shell/core.cpp +++ b/shell/core.cpp @@ -1,586 +1,605 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "core.h" #include "core_p.h" #include #include #include #include #include #include "mainwindow.h" #include "sessioncontroller.h" #include "uicontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "partcontroller.h" #include "languagecontroller.h" #include "documentcontroller.h" #include "runcontroller.h" #include "session.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "progresswidget/progressmanager.h" #include "selectioncontroller.h" #include "debugcontroller.h" #include "kdevplatform_version.h" #include "workingsetcontroller.h" #include "testcontroller.h" +#include "runtimecontroller.h" #include "debug.h" #include #include #include namespace { void shutdownGracefully(int sig) { static volatile std::sig_atomic_t handlingSignal = 0; if ( !handlingSignal ) { handlingSignal = 1; qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully"; QCoreApplication* app = QCoreApplication::instance(); if (QApplication* guiApp = qobject_cast(app)) { guiApp->closeAllWindows(); } app->quit(); return; } // re-raise signal with default handler and trigger program termination std::signal(sig, SIG_DFL); std::raise(sig); } void installSignalHandler() { #ifdef SIGHUP std::signal(SIGHUP, shutdownGracefully); #endif #ifdef SIGINT std::signal(SIGINT, shutdownGracefully); #endif #ifdef SIGTERM std::signal(SIGTERM, shutdownGracefully); #endif } } namespace KDevelop { Core *Core::m_self = nullptr; KAboutData createAboutData() { KAboutData aboutData( QStringLiteral("kdevplatform"), i18n("KDevelop Platform"), QStringLiteral(KDEVPLATFORM_VERSION_STRING), i18n("Development Platform for IDE-like Applications"), KAboutLicense::LGPL_V2, i18n("Copyright 2004-2017, The KDevelop developers"), QString(), QStringLiteral("https://www.kdevelop.org/")); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support" ), QStringLiteral("david.nolden.kdevelop@art-master.de") ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") ); aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org")); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") ); //Veritas is outside in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integraton"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") ); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmx.de") ); return aboutData; } CorePrivate::CorePrivate(Core *core): m_aboutData( createAboutData() ), m_core(core), m_cleanedUp(false), m_shuttingDown(false) { } bool CorePrivate::initialize(Core::Setup mode, QString session ) { m_mode=mode; qCDebug(SHELL) << "Creating controllers"; if( !sessionController ) { sessionController = new SessionController(m_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(); } qCDebug(SHELL) << "Creating ui controller"; if( !uiController ) { uiController = new UiController(m_core); } qCDebug(SHELL) << "Creating plugin controller"; if( !pluginController ) { pluginController = new PluginController(m_core); const auto pluginInfos = pluginController->allPluginInfos(); if (pluginInfos.isEmpty()) { QMessageBox::critical(nullptr, i18n("Could not find any plugins"), i18n("

Could not find any plugins during startup.
" "Please make sure QT_PLUGIN_PATH is set correctly.

" "Refer to this article for more information."), QMessageBox::Abort, QMessageBox::Abort); qCWarning(SHELL) << "Could not find any plugins, aborting"; return false; } } if( !partController && !(mode & Core::NoUi)) { partController = new PartController(m_core, uiController.data()->defaultMainWindow()); } if( !projectController ) { projectController = new ProjectController(m_core); } if( !documentController ) { documentController = new DocumentController(m_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. languageController = new LanguageController(m_core); } if( !runController ) { runController = new RunController(m_core); } if( !sourceFormatterController ) { sourceFormatterController = new SourceFormatterController(m_core); } if ( !progressController) { progressController = ProgressManager::instance(); } if( !selectionController ) { selectionController = new SelectionController(m_core); } if( !documentationController && !(mode & Core::NoUi) ) { documentationController = new DocumentationController(m_core); } + if( !runtimeController ) + { + runtimeController = new RuntimeController(m_core); + } + if( !debugController ) { debugController = new DebugController(m_core); } if( !testController ) { testController = new TestController(m_core); } qCDebug(SHELL) << "Done creating controllers"; qCDebug(SHELL) << "Initializing controllers"; sessionController.data()->initialize( session ); if( !sessionController.data()->activeSessionLock() ) { return false; } // TODO: Is this early enough, or should we put the loading of the session into // the controller construct DUChain::initialize(); if (!(mode & Core::NoUi)) { uiController.data()->initialize(); } languageController.data()->initialize(); if (partController) { partController.data()->initialize(); } projectController.data()->initialize(); documentController.data()->initialize(); /* This is somewhat messy. We want to load the areas before loading the plugins, so that when each plugin is loaded we know if an area wants some of the tool view from that plugin. OTOH, loading of areas creates documents, and some documents might require that a plugin is already loaded. Probably, the best approach would be to plugins to just add tool views to a list of available tool view, and then grab those tool views when loading an area. */ qCDebug(SHELL) << "Initializing plugin controller (loading session plugins)"; pluginController.data()->initialize(); qCDebug(SHELL) << "Initializing working set controller"; if(!(mode & Core::NoUi)) { workingSetController.data()->initialize(); /* Need to do this after everything else is loaded. It's too hard to restore position of views, and toolbars, and whatever that are not created yet. */ uiController.data()->loadAllAreas(KSharedConfig::openConfig()); uiController.data()->defaultMainWindow()->show(); } qCDebug(SHELL) << "Initializing remaining controllers"; runController.data()->initialize(); sourceFormatterController.data()->initialize(); selectionController.data()->initialize(); if (documentationController) { documentationController.data()->initialize(); } debugController.data()->initialize(); testController.data()->initialize(); + runtimeController.data()->initialize(); installSignalHandler(); qCDebug(SHELL) << "Done initializing controllers"; return true; } CorePrivate::~CorePrivate() { delete selectionController.data(); delete projectController.data(); delete languageController.data(); delete pluginController.data(); delete uiController.data(); delete partController.data(); delete documentController.data(); delete runController.data(); delete sessionController.data(); delete sourceFormatterController.data(); delete documentationController.data(); delete debugController.data(); delete workingSetController.data(); delete testController.data(); + delete runtimeController.data(); selectionController.clear(); projectController.clear(); languageController.clear(); pluginController.clear(); uiController.clear(); partController.clear(); documentController.clear(); runController.clear(); sessionController.clear(); sourceFormatterController.clear(); documentationController.clear(); debugController.clear(); workingSetController.clear(); testController.clear(); + runtimeController.clear(); } bool Core::initialize(QObject* splash, Setup mode, const QString& session ) { if (splash) { QTimer::singleShot( 200, splash, &QObject::deleteLater ); } return initialize(mode, session); } bool Core::initialize(Setup mode, const QString& session) { if (m_self) return true; m_self = new Core(); bool ret = m_self->d->initialize(mode, session); if(ret) emit m_self->initializationCompleted(); return ret; } Core *KDevelop::Core::self() { return m_self; } Core::Core(QObject *parent) : ICore(parent) { d = new CorePrivate(this); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::Core(CorePrivate* dd, QObject* parent) : ICore(parent), d(dd) { connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::~Core() { qCDebug(SHELL); //Cleanup already called before mass destruction of GUI delete d; m_self = nullptr; } Core::Setup Core::setupFlags() const { return d->m_mode; } void Core::shutdown() { qCDebug(SHELL); if (!d->m_shuttingDown) { cleanup(); deleteLater(); } qCDebug(SHELL) << "Shutdown done"; } bool Core::shuttingDown() const { return d->m_shuttingDown; } void Core::cleanup() { qCDebug(SHELL); d->m_shuttingDown = true; emit aboutToShutdown(); if (!d->m_cleanedUp) { // first of all: stop background jobs d->languageController->backgroundParser()->abortAllJobs(); d->languageController->backgroundParser()->suspend(); d->debugController.data()->cleanup(); d->selectionController.data()->cleanup(); // Save the layout of the ui here, so run it first d->uiController.data()->cleanup(); if (d->workingSetController) d->workingSetController.data()->cleanup(); /* Must be called before projectController.data()->cleanup(). */ // Closes all documents (discards, as already saved if the user wished earlier) d->documentController.data()->cleanup(); d->runController.data()->cleanup(); if (d->partController) { d->partController->cleanup(); } d->projectController.data()->cleanup(); d->sourceFormatterController.data()->cleanup(); d->pluginController.data()->cleanup(); d->sessionController.data()->cleanup(); d->testController.data()->cleanup(); //Disable the functionality of the language controller d->languageController.data()->cleanup(); DUChain::self()->shutdown(); } d->m_cleanedUp = true; emit shutdownCompleted(); } KAboutData Core::aboutData() const { return d->m_aboutData; } IUiController *Core::uiController() { return d->uiController.data(); } ISession* Core::activeSession() { return sessionController()->activeSession(); } ISessionLock::Ptr Core::activeSessionLock() { return sessionController()->activeSessionLock(); } SessionController *Core::sessionController() { return d->sessionController.data(); } UiController *Core::uiControllerInternal() { return d->uiController.data(); } IPluginController *Core::pluginController() { return d->pluginController.data(); } PluginController *Core::pluginControllerInternal() { return d->pluginController.data(); } IProjectController *Core::projectController() { return d->projectController.data(); } ProjectController *Core::projectControllerInternal() { return d->projectController.data(); } IPartController *Core::partController() { return d->partController.data(); } PartController *Core::partControllerInternal() { return d->partController.data(); } ILanguageController *Core::languageController() { return d->languageController.data(); } LanguageController *Core::languageControllerInternal() { return d->languageController.data(); } IDocumentController *Core::documentController() { return d->documentController.data(); } DocumentController *Core::documentControllerInternal() { return d->documentController.data(); } IRunController *Core::runController() { return d->runController.data(); } RunController *Core::runControllerInternal() { return d->runController.data(); } ISourceFormatterController* Core::sourceFormatterController() { return d->sourceFormatterController.data(); } SourceFormatterController* Core::sourceFormatterControllerInternal() { return d->sourceFormatterController.data(); } ProgressManager *Core::progressController() { return d->progressController.data(); } ISelectionController* Core::selectionController() { return d->selectionController.data(); } IDocumentationController* Core::documentationController() { return d->documentationController.data(); } DocumentationController* Core::documentationControllerInternal() { return d->documentationController.data(); } +IRuntimeController* Core::runtimeController() +{ + return d->runtimeController.data(); +} + +RuntimeController* Core::runtimeControllerInternal() +{ + return d->runtimeController.data(); +} + IDebugController* Core::debugController() { return d->debugController.data(); } DebugController* Core::debugControllerInternal() { return d->debugController.data(); } ITestController* Core::testController() { return d->testController.data(); } TestController* Core::testControllerInternal() { return d->testController.data(); } WorkingSetController* Core::workingSetControllerInternal() { return d->workingSetController.data(); } QString Core::version() { return QStringLiteral(KDEVPLATFORM_VERSION_STRING); } } diff --git a/shell/core.h b/shell/core.h index b577f584c2..f127448dcb 100644 --- a/shell/core.h +++ b/shell/core.h @@ -1,134 +1,137 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_CORE_H #define KDEVPLATFORM_CORE_H #include "shellexport.h" #include class KAboutData; namespace KDevelop { class UiController; class PluginController; class ProjectController; class LanguageController; class PartController; class DocumentController; class RunController; class SessionController; class CorePrivate; class SourceFormatterController; class ProgressManager; class SelectionController; class DocumentationController; class DebugController; class WorkingSetController; +class RuntimeController; class TestController; class KDEVPLATFORMSHELL_EXPORT Core: public ICore { Q_OBJECT public: enum Setup { Default=0, NoUi=1 }; static QString version(); /** Initialize the core of the kdevplatform application * returns false if the initialization fails, which may happen * if the same session is already active in another instance * * @param mode the mode in which to run * @param session the name or uuid of the session to be loaded * */ static bool initialize(Setup mode=Default, const QString& session = {}); // TODO: remove before 5.2 release static QT_DEPRECATED bool initialize(QObject* splash = nullptr, Setup mode=Default, const QString& session = {}); /** * \brief Provide access an instance of Core */ static Core *self(); virtual ~Core(); IUiController *uiController() override; IPluginController *pluginController() override; IProjectController *projectController() override; ILanguageController *languageController() override; IPartController *partController() override; IDocumentController *documentController() override; IRunController *runController() override; ISourceFormatterController* sourceFormatterController() override; ISelectionController* selectionController() override; IDocumentationController* documentationController() override; IDebugController* debugController() override; ITestController* testController() override; + IRuntimeController* runtimeController() override; ISession *activeSession() override; ISessionLock::Ptr activeSessionLock() override; KAboutData aboutData() const override; /// The following methods may only be used within the shell. UiController *uiControllerInternal(); PluginController *pluginControllerInternal(); ProjectController *projectControllerInternal(); LanguageController *languageControllerInternal(); PartController *partControllerInternal(); DocumentController *documentControllerInternal(); RunController *runControllerInternal(); DocumentationController *documentationControllerInternal(); DebugController *debugControllerInternal(); WorkingSetController* workingSetControllerInternal(); SourceFormatterController* sourceFormatterControllerInternal(); TestController* testControllerInternal(); + RuntimeController* runtimeControllerInternal(); /// @internal SessionController *sessionController(); /// @internal ProgressManager *progressController(); void cleanup(); bool shuttingDown() const override; Core::Setup setupFlags() const; public Q_SLOTS: void shutdown(); protected: friend class CorePrivate; explicit Core( KDevelop::CorePrivate* dd, QObject* parent = nullptr ); KDevelop::CorePrivate *d; static Core *m_self; private: explicit Core(QObject *parent = nullptr); }; } #endif diff --git a/shell/core_p.h b/shell/core_p.h index 76babbb831..1c3e9594fa 100644 --- a/shell/core_p.h +++ b/shell/core_p.h @@ -1,79 +1,81 @@ /*************************************************************************** * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLATFORM_CORE_P_H #define KDEVPLATFORM_PLATFORM_CORE_P_H #include #include #include namespace KDevelop { class RunController; class PartController; class LanguageController; class DocumentController; class ProjectController; class PluginController; class UiController; class SessionController; class SourceFormatterController; class ProgressManager; class SelectionController; class DocumentationController; class DebugController; class WorkingSetController; class TestController; +class RuntimeController; class KDEVPLATFORMSHELL_EXPORT CorePrivate { public: explicit CorePrivate(Core *core); ~CorePrivate(); bool initialize( Core::Setup mode, QString session ); QPointer pluginController; QPointer uiController; QPointer projectController; QPointer languageController; QPointer partController; QPointer documentController; QPointer runController; QPointer sessionController; QPointer sourceFormatterController; QPointer progressController; QPointer selectionController; QPointer documentationController; QPointer debugController; QPointer workingSetController; QPointer testController; + QPointer runtimeController; KAboutData m_aboutData; Core *m_core; bool m_cleanedUp; bool m_shuttingDown; Core::Setup m_mode; }; } #endif diff --git a/shell/runtimecontroller.cpp b/shell/runtimecontroller.cpp new file mode 100644 index 0000000000..e0c00c935f --- /dev/null +++ b/shell/runtimecontroller.cpp @@ -0,0 +1,146 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "runtimecontroller.h" +#include +#include +#include +#include +#include +#include +#include +#include "core.h" +#include "uicontroller.h" +#include "mainwindow.h" + +#include "runtimesmodel.h" + +using namespace KDevelop; + +class IdentityRuntime : public IRuntime +{ + QString name() const override { return i18n("Host System"); } + + void startProcess(KProcess *process) override { + connect(process, &QProcess::errorOccurred, this, [process](QProcess::ProcessError error) { + qWarning() << "error!!!" << error << process->program(); + }); + process->start(); + } + void startProcess(QProcess *process) override { + connect(process, &QProcess::errorOccurred, this, [process](QProcess::ProcessError error) { + qWarning() << "error!!!" << error << process->program(); + }); + process->start(); + } + KDevelop::Path pathInHost(const KDevelop::Path & runtimePath) override { return runtimePath; } + KDevelop::Path pathInRuntime(const KDevelop::Path & localPath) override { return localPath; } + void setEnabled(bool /*enabled*/) override {} +}; + +KDevelop::RuntimeController::RuntimeController(KDevelop::Core* core) + : m_runtimesMenu(new QMenu()) +{ + m_model = new RuntimesModel(this); + addRuntimes({new IdentityRuntime}); + setCurrentRuntime(m_runtimes.constFirst()); + + // TODO not multi-window friendly, FIXME + KActionCollection* ac = core->uiControllerInternal()->defaultMainWindow()->actionCollection(); + + auto action = new QAction(this); + action->setStatusTip(i18n("Allows to select a runtime")); + action->setMenu(m_runtimesMenu.data()); + action->setIcon(QIcon::fromTheme(QStringLiteral("file-library-symbolic"))); + auto updateActionText = [action](IRuntime* currentRuntime){ + action->setText(i18n("Runtime: %1", currentRuntime->name())); + }; + connect(this, &RuntimeController::currentRuntimeChanged, action, updateActionText); + updateActionText(m_currentRuntime); + + ac->addAction(QStringLiteral("switch_runtimes"), action); +} + +KDevelop::RuntimeController::~RuntimeController() +{ + m_currentRuntime = nullptr; + qDeleteAll(m_runtimes); +} + +void KDevelop::RuntimeController::initialize() +{ +} + +KDevelop::IRuntime * KDevelop::RuntimeController::currentRuntime() const +{ + Q_ASSERT(m_currentRuntime); + return m_currentRuntime; +} + +QVector KDevelop::RuntimeController::availableRuntimes() const +{ + return m_runtimes; +} + +void KDevelop::RuntimeController::setCurrentRuntime(KDevelop::IRuntime* runtime) +{ + if (m_currentRuntime == runtime) + return; + + Q_ASSERT(m_runtimes.contains(runtime)); + + if (m_currentRuntime) { + m_currentRuntime->setEnabled(false); + } + qDebug() << "setting runtime..." << runtime->name() << "was" << m_currentRuntime; + m_currentRuntime = runtime; + Q_EMIT currentRuntimeChanged(runtime); + + m_currentRuntime->setEnabled(false); +} + +void KDevelop::RuntimeController::addRuntimes(const QVector& runtimes) +{ + m_model->beginInsertRows({}, m_runtimes.size(), m_runtimes.size()+runtimes.size()); + m_runtimes << runtimes; + m_model->endInsertRows(); + + for(auto runtime : runtimes) { + QAction* runtimeAction = new QAction(runtime->name(), m_runtimesMenu.data()); + runtimeAction->setCheckable(true); + connect(runtimeAction, &QAction::triggered, runtime, [this, runtime]() { + setCurrentRuntime(runtime); + }); + connect(this, &RuntimeController::currentRuntimeChanged, runtimeAction, [runtimeAction, runtime](IRuntime* currentRuntime) { + runtimeAction->setChecked(runtime == currentRuntime); + }); + + m_runtimesMenu->addAction(runtimeAction); + } +} + +KDevelop::RuntimesModel * KDevelop::RuntimeController::model() const +{ + return m_model; +} + +void KDevelop::RuntimeController::setRuntimeAt(int pos) +{ + setCurrentRuntime(m_runtimes.at(pos)); +} + diff --git a/shell/runtimecontroller.h b/shell/runtimecontroller.h new file mode 100644 index 0000000000..bf31e60ceb --- /dev/null +++ b/shell/runtimecontroller.h @@ -0,0 +1,67 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVPLATFORM_RUNTIMECONTROLLER_H +#define KDEVPLATFORM_RUNTIMECONTROLLER_H + +#include +#include +#include + +class RuntimeViewFactory; +class QAction; +class QMenu; + +namespace KDevelop +{ + +class Core; +class Context; +class ContextMenuExtension; +class RuntimesModel; + +class RuntimeController : public IRuntimeController +{ + Q_OBJECT +public: + explicit RuntimeController(Core* core); + ~RuntimeController() override; + + void initialize(); + + void addRuntimes(const QVector &runtimes) override; +// void removeRuntimes(const QVector &runtimes) override; + QVector availableRuntimes() const override; + + KDevelop::IRuntime * currentRuntime() const override; + void setCurrentRuntime(KDevelop::IRuntime * runtime) override; + + RuntimesModel* model() const; + +private: + void setRuntimeAt(int pos); + + QScopedPointer const m_runtimesMenu; + RuntimesModel* m_model = nullptr; + QVector m_runtimes; + IRuntime* m_currentRuntime = nullptr; +}; + +} + +#endif diff --git a/shell/runtimesmodel.cpp b/shell/runtimesmodel.cpp new file mode 100644 index 0000000000..7474955ff0 --- /dev/null +++ b/shell/runtimesmodel.cpp @@ -0,0 +1,50 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "runtimesmodel.h" + +KDevelop::RuntimesModel::RuntimesModel(KDevelop::RuntimeController* controller) + : QAbstractListModel(controller) + , m_controller(controller) +{ +} + +KDevelop::IRuntime * KDevelop::RuntimesModel::runtimeAt(int row) const +{ + return m_controller->availableRuntimes().at(row); +} + +int KDevelop::RuntimesModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_controller->availableRuntimes().count(); +} + +QVariant KDevelop::RuntimesModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.parent().isValid() || index.row() >= m_controller->availableRuntimes().count()) + return {}; + + const auto runtime = runtimeAt(index.row()); + switch(role) { + case Qt::DisplayRole: + return runtime->name(); + case Qt::UserRole: + return QVariant::fromValue(runtime); + } + return {}; +} diff --git a/shell/runtimesmodel.h b/shell/runtimesmodel.h new file mode 100644 index 0000000000..31536ff795 --- /dev/null +++ b/shell/runtimesmodel.h @@ -0,0 +1,42 @@ +/* + Copyright 2017 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "runtimecontroller.h" + +namespace KDevelop +{ + +class RuntimeController; + +class RuntimesModel : public QAbstractListModel +{ + Q_OBJECT +public: + friend class KDevelop::RuntimeController; + + RuntimesModel(RuntimeController* controller); + + QVariant data(const QModelIndex & index, int role) const override; + int rowCount(const QModelIndex & parent) const override; + KDevelop::IRuntime* runtimeAt(int row) const; + +private: + KDevelop::RuntimeController* const m_controller; +}; + +}