File indexing completed on 2024-04-28 04:37:15

0001 /*
0002     SPDX-FileCopyrightText: 2007 Alexander Dymo <adymo@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Kris Wong <kris.p.wong@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "core.h"
0009 #include "core_p.h"
0010 
0011 #include <QApplication>
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <language/backgroundparser/backgroundparser.h>
0016 #include <language/duchain/duchain.h>
0017 
0018 #include "mainwindow.h"
0019 #include "sessioncontroller.h"
0020 #include "uicontroller.h"
0021 #include "plugincontroller.h"
0022 #include "projectcontroller.h"
0023 #include "partcontroller.h"
0024 #include "languagecontroller.h"
0025 #include "documentcontroller.h"
0026 #include "runcontroller.h"
0027 #include "session.h"
0028 #include "documentationcontroller.h"
0029 #include "sourceformattercontroller.h"
0030 #include "progresswidget/progressmanager.h"
0031 #include "selectioncontroller.h"
0032 #include "debugcontroller.h"
0033 #include "kdevplatform_version.h"
0034 #include "workingsetcontroller.h"
0035 #include "testcontroller.h"
0036 #include "runtimecontroller.h"
0037 #include "debug.h"
0038 
0039 #include <KUser>
0040 
0041 #include <QDir>
0042 #include <QFile>
0043 #include <QStandardPaths>
0044 #include <QUuid>
0045 
0046 #include <csignal>
0047 
0048 namespace {
0049 void shutdownGracefully(int sig)
0050 {
0051     static volatile std::sig_atomic_t handlingSignal = 0;
0052 
0053     if ( !handlingSignal ) {
0054         handlingSignal = 1;
0055         qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully";
0056         QCoreApplication* app = QCoreApplication::instance();
0057         if (auto* guiApp = qobject_cast<QApplication*>(app)) {
0058             guiApp->closeAllWindows();
0059         }
0060         app->quit();
0061         return;
0062     }
0063 
0064     // re-raise signal with default handler and trigger program termination
0065     std::signal(sig, SIG_DFL);
0066     std::raise(sig);
0067 }
0068 
0069 void installSignalHandler()
0070 {
0071 #ifdef SIGHUP
0072     std::signal(SIGHUP, shutdownGracefully);
0073 #endif
0074 #ifdef SIGINT
0075     std::signal(SIGINT, shutdownGracefully);
0076 #endif
0077 #ifdef SIGTERM
0078     std::signal(SIGTERM, shutdownGracefully);
0079 #endif
0080 }
0081 
0082 /**
0083  * Initialize per-session temporary directory and ensure its cleanup.
0084  *
0085  * When KDevelop crashes, it leaves many files, some of which are huge, in the temporary directory.
0086  * These files are not automatically removed until system restart. No space may be left in the
0087  * temporary directory after multiple crashes. This function removes the temporary directory for the
0088  * active session on KDevelop start. Removing the session temporary directory on KDevelop exit is
0089  * risky because some temporary files might still be in use. A user is likely to restart a crashed
0090  * KDevelop session to continue working, so clearing on start is almost perfect.
0091  *
0092  * @param session the currently active session
0093  *
0094  * @return the active session's temporary directory path
0095  *         or the system temporary directory path in case of error.
0096  */
0097 QString setupSessionTemporaryDirectory(const KDevelop::ISession& session)
0098 {
0099     QString systemTempDirPath = QDir::tempPath();
0100 
0101     // Session IDs are generated via QUuid::createUuid(), so they should be unique across all users in a system.
0102     // However, users could copy/share session directories and thus get equal session IDs. If two users are logged
0103     // in a system and have equal-ID KDevelop sessions open, the per-session temporary directory paths would coincide.
0104     // Prevent such conflicts by including the real user ID in the temporary directory path.
0105     const QString userId = KUserId::currentUserId().toString();
0106 
0107     // The temporary directory path does not contain the session ID in test mode, because most KDevelop
0108     // tests create a temporary session with a unique id on each run. Such temporary sessions are never
0109     // loaded again, and so their temporary files cannot possibly be removed on next start. Including
0110     // session IDs in temporary directory names for test sessions are only harmful then: empty directories
0111     // accumulate (and even nonempty directories as some tests don't remove their files during cleanup).
0112     // The session name is used in place of the session ID in test mode. TestCore::initializeNonStatic() sets most
0113     // test session names to "test-" + <test executable name>. The few custom test session names are also sufficiently
0114     // verbose and unique. Separate temporary directories for different tests allow running tests concurrently.
0115     // QStandardPaths::isTestModeEnabled() is undocumented internal API. But KSharedConfig::openConfig() relies on it.
0116     // So we venture to use this API here despite the risk of removal without notice in a future Qt version.
0117     const QString subdirName = QStandardPaths::isTestModeEnabled() ? session.name() : session.id().toString();
0118 
0119     QString tempDirPath = QLatin1String("%1/kdevelop-%2/%3").arg(systemTempDirPath, userId, subdirName);
0120     QDir tempDir(tempDirPath);
0121 
0122     // Remove files left after a possible previous crash of this session.
0123     if (!tempDir.removeRecursively()) {
0124         qCWarning(SHELL) << "couldn't delete the session temporary directory" << tempDirPath;
0125         return systemTempDirPath;
0126     }
0127 
0128     // Create the session temporary directory.
0129     if (!tempDir.mkpath(tempDirPath)) {
0130         qCWarning(SHELL) << "couldn't create the session temporary directory" << tempDirPath;
0131         return systemTempDirPath;
0132     }
0133 
0134     // Allow only the owner to access the temporary directory, just like QTemporaryDir() does.
0135     if (!QFile::setPermissions(tempDirPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
0136         qCWarning(SHELL) << "couldn't set permissions for the session temporary directory" << tempDirPath;
0137         return systemTempDirPath;
0138     }
0139 
0140     qCDebug(SHELL) << "set up the session temporary directory" << tempDirPath;
0141     return tempDirPath;
0142 }
0143 } // unnamed namespace
0144 
0145 namespace KDevelop {
0146 
0147 Core *Core::m_self = nullptr;
0148 
0149 CorePrivate::CorePrivate(Core *core)
0150     : m_core(core)
0151     , m_cleanedUp(false)
0152     , m_shuttingDown(false)
0153 {
0154 }
0155 
0156 bool CorePrivate::initialize(Core::Setup mode, const QString& session )
0157 {
0158     m_mode=mode;
0159 
0160     qCDebug(SHELL) << "Creating controllers";
0161 
0162     if( !sessionController )
0163     {
0164         sessionController = new SessionController(m_core);
0165     }
0166     if( !workingSetController && !(mode & Core::NoUi) )
0167     {
0168         workingSetController = new WorkingSetController();
0169     }
0170     qCDebug(SHELL) << "Creating ui controller";
0171     if( !uiController )
0172     {
0173         uiController = new UiController(m_core);
0174     }
0175     qCDebug(SHELL) << "Creating plugin controller";
0176 
0177     if( !pluginController )
0178     {
0179         pluginController = new PluginController(m_core);
0180         const auto pluginInfos = pluginController->allPluginInfos();
0181         if (pluginInfos.isEmpty()) {
0182             QMessageBox::critical(nullptr,
0183                                   i18nc("@title:window", "No Plugins Found"),
0184                                   i18n("<p>Could not find any plugins during startup.<br/>"
0185                                   "Please make sure QT_PLUGIN_PATH is set correctly.</p>"
0186                                   "Refer to <a href=\"https://community.kde.org/Guidelines_and_HOWTOs/Build_from_source#Set_up_the_runtime_environment\">this article</a> for more information."),
0187                                   QMessageBox::Abort, QMessageBox::Abort);
0188             qCWarning(SHELL) << "Could not find any plugins, aborting";
0189             return false;
0190         }
0191     }
0192     if( !partController && !(mode & Core::NoUi))
0193     {
0194         partController = new PartController(m_core, uiController->defaultMainWindow());
0195     }
0196 
0197     if( !projectController )
0198     {
0199         projectController = new ProjectController(m_core);
0200     }
0201 
0202     if( !documentController )
0203     {
0204         documentController = new DocumentController(m_core);
0205     }
0206 
0207     if( !languageController )
0208     {
0209         // Must be initialized after documentController, because the background parser depends
0210         // on the document controller.
0211         languageController = new LanguageController(m_core);
0212     }
0213 
0214     if( !runController )
0215     {
0216         runController = new RunController(m_core);
0217     }
0218 
0219     if( !sourceFormatterController )
0220     {
0221         sourceFormatterController = new SourceFormatterController(m_core);
0222     }
0223 
0224     if ( !progressController)
0225     {
0226         progressController = ProgressManager::instance();
0227     }
0228 
0229     if( !selectionController )
0230     {
0231         selectionController = new SelectionController(m_core);
0232     }
0233 
0234     if( !documentationController && !(mode & Core::NoUi) )
0235     {
0236         documentationController = new DocumentationController(m_core);
0237     }
0238 
0239     if( !runtimeController )
0240     {
0241         runtimeController = new RuntimeController(m_core);
0242     }
0243 
0244     if( !debugController )
0245     {
0246         debugController = new DebugController(m_core);
0247     }
0248 
0249     if( !testController )
0250     {
0251         testController = new TestController(m_core);
0252     }
0253 
0254     qCDebug(SHELL) << "Done creating controllers";
0255 
0256     qCDebug(SHELL) << "Initializing controllers";
0257 
0258     sessionController->initialize( session );
0259     if( !sessionController->activeSessionLock() ) {
0260         return false;
0261     }
0262     m_sessionTemporaryDirectoryPath = setupSessionTemporaryDirectory(*sessionController->activeSession());
0263 
0264     // TODO: Is this early enough, or should we put the loading of the session into
0265     // the controller construct
0266     DUChain::initialize();
0267 
0268     if (!(mode & Core::NoUi)) {
0269         uiController->initialize();
0270     }
0271     languageController->initialize();
0272     languageController->backgroundParser()->suspend();
0273     // eventually resume the background parser once the project controller
0274     // has been initialized. At that point we know whether there are projects loading
0275     // which the background parser is handling internally to defer parse jobs
0276     QObject::connect(projectController.data(), &ProjectController::initialized,
0277                      m_core, [this]() {
0278                          languageController->backgroundParser()->resume();
0279                      });
0280 
0281     if (partController) {
0282         partController->initialize();
0283     }
0284     projectController->initialize();
0285     documentController->initialize();
0286 
0287     /* This is somewhat messy.  We want to load the areas before
0288         loading the plugins, so that when each plugin is loaded we
0289         know if an area wants some of the tool view from that plugin.
0290         OTOH, loading of areas creates documents, and some documents
0291         might require that a plugin is already loaded.
0292         Probably, the best approach would be to plugins to just add
0293         tool views to a list of available tool view, and then grab
0294         those tool views when loading an area.  */
0295 
0296     qCDebug(SHELL) << "Initializing plugin controller (loading session plugins)";
0297     pluginController->initialize();
0298 
0299     /* To make breakpoints show up in the UI, we need to make sure
0300        DebugController is initialized and has loaded BreakpointModel
0301        before UI is made visible. */
0302     debugController->initialize();
0303 
0304     qCDebug(SHELL) << "Initializing working set controller";
0305     if(!(mode & Core::NoUi))
0306     {
0307         workingSetController->initialize();
0308         /* Need to do this after everything else is loaded.  It's too
0309             hard to restore position of views, and toolbars, and whatever
0310             that are not created yet.  */
0311         uiController->loadAllAreas(KSharedConfig::openConfig());
0312         uiController->defaultMainWindow()->show();
0313     }
0314 
0315     qCDebug(SHELL) << "Initializing remaining controllers";
0316     runController->initialize();
0317     sourceFormatterController->initialize();
0318     selectionController->initialize();
0319     if (documentationController) {
0320         documentationController->initialize();
0321     }
0322     testController->initialize();
0323     runtimeController->initialize();
0324 
0325     installSignalHandler();
0326 
0327     qCDebug(SHELL) << "Done initializing controllers";
0328 
0329     return true;
0330 }
0331 CorePrivate::~CorePrivate()
0332 {
0333     delete selectionController.data();
0334     delete projectController.data();
0335     delete languageController.data();
0336     delete pluginController.data();
0337     delete uiController.data();
0338     delete partController.data();
0339     delete documentController.data();
0340     delete runController.data();
0341     delete sessionController.data();
0342     delete sourceFormatterController.data();
0343     delete documentationController.data();
0344     delete debugController.data();
0345     delete workingSetController.data();
0346     delete testController.data();
0347     delete runtimeController.data();
0348 }
0349 
0350 bool Core::initialize(Setup mode, const QString& session)
0351 {
0352     if (m_self)
0353         return true;
0354 
0355     m_self = new Core();
0356     bool ret = m_self->d->initialize(mode, session);
0357 
0358     if(ret)
0359         emit m_self->initializationCompleted();
0360 
0361     return ret;
0362 }
0363 
0364 Core *KDevelop::Core::self()
0365 {
0366     return m_self;
0367 }
0368 
0369 Core::Core(QObject *parent)
0370     : ICore(parent)
0371 {
0372     d = new CorePrivate(this);
0373 
0374     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown);
0375 }
0376 
0377 Core::Core(CorePrivate* dd, QObject* parent)
0378 : ICore(parent), d(dd)
0379 {
0380     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown);
0381 }
0382 
0383 Core::~Core()
0384 {
0385     qCDebug(SHELL) << "Destroying Core";
0386 
0387     //Cleanup already called before mass destruction of GUI
0388     delete d;
0389     m_self = nullptr;
0390 }
0391 
0392 Core::Setup Core::setupFlags() const
0393 {
0394     return d->m_mode;
0395 }
0396 
0397 void Core::shutdown()
0398 {
0399     qCDebug(SHELL) << "Shutting down Core";
0400 
0401     if (!d->m_shuttingDown) {
0402         cleanup();
0403         deleteLater();
0404     }
0405 
0406     qCDebug(SHELL) << "Shutdown done";
0407 }
0408 
0409 bool Core::shuttingDown() const
0410 {
0411     return d->m_shuttingDown;
0412 }
0413 
0414 void Core::cleanup()
0415 {
0416     qCDebug(SHELL) << "Starting Core cleanup";
0417 
0418     d->m_shuttingDown = true;
0419     emit aboutToShutdown();
0420 
0421     if (!d->m_cleanedUp) {
0422         // first of all: request stop of all background parser jobs
0423         d->languageController->backgroundParser()->abortAllJobs();
0424         d->languageController->backgroundParser()->suspend();
0425 
0426         d->debugController->cleanup();
0427         d->selectionController->cleanup();
0428 
0429         if (!(d->m_mode & Core::NoUi)) {
0430             // Save the layout of the ui here, so run it first
0431             d->uiController->cleanup();
0432         }
0433 
0434         if (d->workingSetController)
0435             d->workingSetController->cleanup();
0436 
0437         /* Must be called before projectController->cleanup(). */
0438         // Closes all documents (discards, as already saved if the user wished earlier)
0439         d->documentController->cleanup();
0440         d->runController->cleanup();
0441         if (d->partController) {
0442             d->partController->cleanup();
0443         }
0444         d->projectController->cleanup();
0445         d->sourceFormatterController->cleanup();
0446 
0447         // before unloading language plugins, we need to make sure all parse jobs are done
0448         d->languageController->backgroundParser()->waitForIdle();
0449 
0450         DUChain::self()->shutdown();
0451 
0452         // Only unload plugins after the DUChain shutdown to prevent issues with non-loaded factories for types
0453         // See: https://bugs.kde.org/show_bug.cgi?id=379669
0454         d->pluginController->cleanup();
0455 
0456         d->sessionController->cleanup();
0457 
0458         d->testController->cleanup();
0459 
0460         //Disable the functionality of the language controller
0461         d->languageController->cleanup();
0462     }
0463 
0464     d->m_cleanedUp = true;
0465     emit shutdownCompleted();
0466 }
0467 
0468 IUiController *Core::uiController()
0469 {
0470     return d->uiController.data();
0471 }
0472 
0473 ISession* Core::activeSession()
0474 {
0475     return sessionController()->activeSession();
0476 }
0477 
0478 ISessionLock::Ptr Core::activeSessionLock()
0479 {
0480     return sessionController()->activeSessionLock();
0481 }
0482 
0483 QString Core::sessionTemporaryDirectoryPath() const
0484 {
0485     return d->m_sessionTemporaryDirectoryPath;
0486 }
0487 
0488 SessionController *Core::sessionController()
0489 {
0490     return d->sessionController.data();
0491 }
0492 
0493 UiController *Core::uiControllerInternal()
0494 {
0495     return d->uiController.data();
0496 }
0497 
0498 IPluginController *Core::pluginController()
0499 {
0500     return d->pluginController.data();
0501 }
0502 
0503 PluginController *Core::pluginControllerInternal()
0504 {
0505     return d->pluginController.data();
0506 }
0507 
0508 IProjectController *Core::projectController()
0509 {
0510     return d->projectController.data();
0511 }
0512 
0513 ProjectController *Core::projectControllerInternal()
0514 {
0515     return d->projectController.data();
0516 }
0517 
0518 IPartController *Core::partController()
0519 {
0520     return d->partController.data();
0521 }
0522 
0523 PartController *Core::partControllerInternal()
0524 {
0525     return d->partController.data();
0526 }
0527 
0528 ILanguageController *Core::languageController()
0529 {
0530     return d->languageController.data();
0531 }
0532 
0533 LanguageController *Core::languageControllerInternal()
0534 {
0535     return d->languageController.data();
0536 }
0537 
0538 IDocumentController *Core::documentController()
0539 {
0540     return d->documentController.data();
0541 }
0542 
0543 DocumentController *Core::documentControllerInternal()
0544 {
0545     return d->documentController.data();
0546 }
0547 
0548 IRunController *Core::runController()
0549 {
0550     return d->runController.data();
0551 }
0552 
0553 RunController *Core::runControllerInternal()
0554 {
0555     return d->runController.data();
0556 }
0557 
0558 ISourceFormatterController* Core::sourceFormatterController()
0559 {
0560     return d->sourceFormatterController.data();
0561 }
0562 
0563 SourceFormatterController* Core::sourceFormatterControllerInternal()
0564 {
0565     return d->sourceFormatterController.data();
0566 }
0567 
0568 
0569 ProgressManager *Core::progressController()
0570 {
0571     return d->progressController.data();
0572 }
0573 
0574 ISelectionController* Core::selectionController()
0575 {
0576     return d->selectionController.data();
0577 }
0578 
0579 IDocumentationController* Core::documentationController()
0580 {
0581     return d->documentationController.data();
0582 }
0583 
0584 DocumentationController* Core::documentationControllerInternal()
0585 {
0586     return d->documentationController.data();
0587 }
0588 
0589 IRuntimeController* Core::runtimeController()
0590 {
0591     return d->runtimeController.data();
0592 }
0593 
0594 RuntimeController* Core::runtimeControllerInternal()
0595 {
0596     return d->runtimeController.data();
0597 }
0598 
0599 IDebugController* Core::debugController()
0600 {
0601     return d->debugController.data();
0602 }
0603 
0604 DebugController* Core::debugControllerInternal()
0605 {
0606     return d->debugController.data();
0607 }
0608 
0609 ITestController* Core::testController()
0610 {
0611     return d->testController.data();
0612 }
0613 
0614 TestController* Core::testControllerInternal()
0615 {
0616     return d->testController.data();
0617 }
0618 
0619 WorkingSetController* Core::workingSetControllerInternal()
0620 {
0621     return d->workingSetController.data();
0622 }
0623 
0624 QString Core::version()
0625 {
0626     return QStringLiteral(KDEVPLATFORM_VERSION_STRING);
0627 }
0628 
0629 }
0630 
0631 #include "moc_core.cpp"