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"