File indexing completed on 2024-05-05 04:39:25
0001 /* 0002 SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org> 0003 SPDX-FileCopyrightText: 2007-2013 Aleix Pol <aleixpol@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "cmakemanager.h" 0009 #include "cmakeutils.h" 0010 #include "cmakeprojectdata.h" 0011 #include "duchain/cmakeparsejob.h" 0012 #include "cmakeimportjsonjob.h" 0013 #include "debug.h" 0014 #include "cmakecodecompletionmodel.h" 0015 #include "cmakenavigationwidget.h" 0016 #include "icmakedocumentation.h" 0017 #include "cmakemodelitems.h" 0018 #include "testing/ctestutils.h" 0019 #include "testing/ctestsuite.h" 0020 #include "testing/ctestfindjob.h" 0021 #include "cmakeserverimportjob.h" 0022 #include "cmakeserver.h" 0023 #include "cmakefileapi.h" 0024 #include "cmakefileapiimportjob.h" 0025 0026 #ifndef CMAKEMANAGER_NO_SETTINGS 0027 #include "settings/cmakepreferences.h" 0028 #endif 0029 0030 #include <QApplication> 0031 #include <QDir> 0032 #include <QReadWriteLock> 0033 #include <QThread> 0034 #include <QFileSystemWatcher> 0035 #include <QTimer> 0036 0037 #include <KPluginFactory> 0038 #include <QUrl> 0039 #include <QAction> 0040 #include <KTextEditor/Document> 0041 #include <KDirWatch> 0042 0043 #include <interfaces/icore.h> 0044 #include <interfaces/idocumentcontroller.h> 0045 #include <interfaces/iprojectcontroller.h> 0046 #include <interfaces/iproject.h> 0047 #include <interfaces/iplugincontroller.h> 0048 #include <interfaces/iruntimecontroller.h> 0049 #include <interfaces/iruntime.h> 0050 #include <interfaces/iruncontroller.h> 0051 #include <interfaces/itestcontroller.h> 0052 #include <interfaces/iuicontroller.h> 0053 #include <interfaces/contextmenuextension.h> 0054 #include <interfaces/context.h> 0055 #include <interfaces/idocumentation.h> 0056 #include <util/executecompositejob.h> 0057 #include <language/highlighting/codehighlighting.h> 0058 #include <project/projectmodel.h> 0059 #include <project/helper.h> 0060 #include <project/interfaces/iprojectbuilder.h> 0061 #include <language/codecompletion/codecompletion.h> 0062 #include <language/duchain/duchainlock.h> 0063 #include <language/duchain/use.h> 0064 #include <language/duchain/duchain.h> 0065 #include <makefileresolver/makefileresolver.h> 0066 #include <sublime/message.h> 0067 0068 using namespace KDevelop; 0069 0070 K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin<CMakeManager>(); ) 0071 0072 CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) 0073 : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcmakemanager"), parent ) 0074 { 0075 if (CMake::findExecutable().isEmpty()) { 0076 setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); 0077 m_highlight = nullptr; 0078 return; 0079 } 0080 0081 m_highlight = new KDevelop::CodeHighlighting(this); 0082 0083 new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); 0084 0085 connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); 0086 connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects); 0087 connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); 0088 } 0089 0090 CMakeManager::~CMakeManager() 0091 { 0092 parseLock()->lockForWrite(); 0093 // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state 0094 parseLock()->unlock(); 0095 } 0096 0097 bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const 0098 { 0099 return m_projects[item->project()].data.compilationData.files.contains(item->path()); 0100 } 0101 0102 Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const 0103 { 0104 return Path(CMake::currentBuildDir(item->project())); 0105 } 0106 0107 KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) 0108 { 0109 CMake::checkForNeedingConfigure(project); 0110 0111 return AbstractFileManagerPlugin::import(project); 0112 } 0113 0114 class ChooseCMakeInterfaceJob : public ExecuteCompositeJob 0115 { 0116 Q_OBJECT 0117 public: 0118 ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager, bool forceConfigure) 0119 : ExecuteCompositeJob(manager, {}) 0120 , project(project) 0121 , manager(manager) 0122 , forceConfigure(forceConfigure) 0123 { 0124 } 0125 0126 void start() override { 0127 if (CMake::FileApi::supported(CMake::currentCMakeExecutable(project).toLocalFile())) { 0128 qCDebug(CMAKE) << "Using cmake-file-api for import of" << project->path(); 0129 0130 // try to import the data directly, if possible and not outdated 0131 if (forceConfigure) { 0132 reconfigureThenImport(); 0133 } else { 0134 tryDirectImport(); 0135 } 0136 ExecuteCompositeJob::start(); 0137 } else { 0138 tryCMakeServer(); 0139 } 0140 } 0141 0142 private: 0143 void tryCMakeServer() 0144 { 0145 qCDebug(CMAKE) << "try cmake server for import"; 0146 server.reset(new CMakeServer(project)); 0147 connect(server.data(), &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); 0148 connect(server.data(), &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); 0149 } 0150 0151 void successfulConnection() { 0152 auto job = new CMakeServerImportJob(project, server, this); 0153 connect(job, &CMakeServerImportJob::result, this, [this, job](){ 0154 if (job->error() == 0) { 0155 manager->integrateData(job->projectData(), job->project(), server); 0156 } 0157 }); 0158 addSubjob(job); 0159 ExecuteCompositeJob::start(); 0160 } 0161 0162 void failedConnection(int code) { 0163 Q_ASSERT(code > 0); 0164 Q_ASSERT(!server->isServerAvailable()); 0165 0166 qCDebug(CMAKE) << "CMake does not provide server mode, using compile_commands.json to import" << project->name(); 0167 0168 // parse the JSON file 0169 auto* job = new CMakeImportJsonJob(project, this); 0170 0171 // create the JSON file if it doesn't exist 0172 auto commandsFile = CMake::commandsFile(project); 0173 if (!QFileInfo::exists(commandsFile.toLocalFile())) { 0174 qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; 0175 addSubjob(manager->builder()->configure(project)); 0176 } 0177 0178 connect(job, &CMakeImportJsonJob::result, this, [this, job]() { 0179 if (job->error() == 0) { 0180 manager->integrateData(job->projectData(), job->project()); 0181 } 0182 }); 0183 addSubjob(job); 0184 ExecuteCompositeJob::start(); 0185 } 0186 0187 void reconfigureThenImport() 0188 { 0189 addSubjob(manager->builder()->configure(project)); 0190 auto* importJob = new CMake::FileApi::ImportJob(project, this); 0191 connect(importJob, &CMake::FileApi::ImportJob::dataAvailable, this, &ChooseCMakeInterfaceJob::fileImportDone); 0192 addSubjob(importJob); 0193 } 0194 0195 void tryDirectImport() 0196 { 0197 auto* importJob = new CMake::FileApi::ImportJob(project, this); 0198 importJob->setInvalidateOutdatedData(); 0199 importJob->setEmitInvalidData(); 0200 connect(importJob, &CMake::FileApi::ImportJob::dataAvailable, this, [this](const CMakeProjectData& data) { 0201 if (!data.compilationData.isValid) { 0202 qCDebug(CMAKE) << "reconfiguring project" << project->name() << "because project data is outdated"; 0203 reconfigureThenImport(); 0204 } else { 0205 Q_ASSERT_X(!data.isOutdated, Q_FUNC_INFO, 0206 "ImportJob::setInvalidateOutdatedData() failed to mark outdated data invalid."); 0207 qCDebug(CMAKE) << "skipping configure project" << project->name() 0208 << "because project data is up to date"; 0209 fileImportDone(data); 0210 } 0211 }); 0212 addSubjob(importJob); 0213 } 0214 0215 void fileImportDone(const CMakeProjectData& data) 0216 { 0217 Q_ASSERT(data.compilationData.isValid); 0218 manager->integrateData(data, project); 0219 } 0220 0221 QSharedPointer<CMakeServer> server; 0222 IProject* const project; 0223 CMakeManager* const manager; 0224 const bool forceConfigure; 0225 }; 0226 0227 KJob* CMakeManager::createImportJob(ProjectFolderItem* item, bool forceConfigure) 0228 { 0229 auto project = item->project(); 0230 0231 delete m_configureStatusMessages.value(project); // discard now-obsolete message from the previous configuration 0232 0233 auto job = new ChooseCMakeInterfaceJob(project, this, forceConfigure); 0234 connect(job, &KJob::result, this, [this, job, project]() { 0235 if (job->error() != 0) { 0236 qCWarning(CMAKE) << "couldn't load project successfully" << project->name() << job->error() 0237 << job->errorText(); 0238 showConfigureErrorMessage(*project, job->errorString()); 0239 } 0240 }); 0241 0242 const QList<KJob*> jobs = { 0243 job, 0244 KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing 0245 }; 0246 0247 Q_ASSERT(!jobs.contains(nullptr)); 0248 auto* composite = new ExecuteCompositeJob(this, jobs); 0249 // even if the cmake call failed, we want to load the project so that the project can be worked on 0250 composite->setAbortOnSubjobError(false); 0251 return composite; 0252 } 0253 0254 KJob* CMakeManager::createImportJob(ProjectFolderItem* item) 0255 { 0256 return createImportJob(item, false); 0257 } 0258 0259 QList<KDevelop::ProjectTargetItem*> CMakeManager::targets() const 0260 { 0261 QList<KDevelop::ProjectTargetItem*> ret; 0262 for (auto it = m_projects.begin(), end = m_projects.end(); it != end; ++it) { 0263 IProject* p = it.key(); 0264 ret+=p->projectItem()->targetList(); 0265 } 0266 return ret; 0267 } 0268 0269 CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const 0270 { 0271 const auto projectData = m_projects.constFind(item->project()); 0272 if (projectData == m_projects.cend()) { 0273 return {}; 0274 } 0275 const auto& data = projectData->data.compilationData; 0276 0277 const auto itemPath = item->path(); 0278 if (!data.isValid || data.missingFiles.contains(itemPath)) { 0279 return {}; 0280 } 0281 0282 auto toCanonicalPath = [](const Path &path) -> Path { 0283 // if the path contains a symlink, then we will not find it in the lookup table 0284 // as that only only stores canonicalized paths. Thus, we fallback to 0285 // to the canonicalized path and see if that brings up any matches 0286 const auto localPath = path.toLocalFile(); 0287 const auto canonicalPath = QFileInfo(localPath).canonicalFilePath(); 0288 return (localPath == canonicalPath) ? path : Path(canonicalPath); 0289 }; 0290 0291 auto path = itemPath; 0292 if (!item->folder()) { 0293 // try to look for file meta data directly 0294 auto it = data.files.find(path); 0295 if (it == data.files.end()) { 0296 // fallback to canonical path lookup 0297 auto canonical = toCanonicalPath(path); 0298 if (canonical != path) { 0299 it = data.files.find(canonical); 0300 } 0301 } 0302 if (it != data.files.end()) { 0303 return *it; 0304 } 0305 // else look for a file in the parent folder 0306 path = path.parent(); 0307 } 0308 0309 while (true) { 0310 // try to look for a file in the current folder path 0311 auto it = data.fileForFolder.find(path); 0312 if (it == data.fileForFolder.end()) { 0313 // fallback to canonical path lookup 0314 auto canonical = toCanonicalPath(path); 0315 if (canonical != path) { 0316 it = data.fileForFolder.find(canonical); 0317 } 0318 } 0319 if (it != data.fileForFolder.end()) { 0320 return data.files[it.value()]; 0321 } 0322 if (!path.hasParent()) { 0323 break; 0324 } 0325 path = path.parent(); 0326 } 0327 0328 qCDebug(CMAKE) << "no information found for" << itemPath; 0329 data.missingFiles.insert(itemPath); 0330 return {}; 0331 } 0332 0333 Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const 0334 { 0335 return fileInformation(item).includes; 0336 } 0337 0338 Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const 0339 { 0340 return fileInformation(item).frameworkDirectories; 0341 } 0342 0343 QHash<QString, QString> CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const 0344 { 0345 return fileInformation(item).defines; 0346 } 0347 0348 QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const 0349 { 0350 return fileInformation(item).compileFlags; 0351 } 0352 0353 KDevelop::IProjectBuilder * CMakeManager::builder() const 0354 { 0355 IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevCMakeBuilder")); 0356 Q_ASSERT(i); 0357 auto* _builder = i->extension<KDevelop::IProjectBuilder>(); 0358 Q_ASSERT(_builder ); 0359 return _builder ; 0360 } 0361 0362 bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) 0363 { 0364 qCDebug(CMAKE) << "reloading" << folder->path(); 0365 0366 IProject* project = folder->project(); 0367 if (!project->isReady()) { 0368 qCDebug(CMAKE) << "the project is being reloaded, aborting reload!"; 0369 return false; 0370 } 0371 0372 KJob* job = createImportJob(folder, true); 0373 project->setReloadJob(job); 0374 ICore::self()->runController()->registerJob( job ); 0375 if (folder == project->projectItem()) { 0376 connect(job, &KJob::finished, this, [project](KJob* job) { 0377 if (job->error()) 0378 return; 0379 0380 emit KDevelop::ICore::self()->projectController()->projectConfigurationChanged(project); 0381 KDevelop::ICore::self()->projectController()->reparseProject(project); 0382 }); 0383 } 0384 0385 return true; 0386 } 0387 0388 static void populateTargets(ProjectFolderItem* folder, const QHash<KDevelop::Path, QVector<CMakeTarget>>& targets) 0389 { 0390 auto isValidTarget = [](const CMakeTarget& target) -> bool { 0391 if (target.type != CMakeTarget::Custom) 0392 return true; 0393 0394 // utility targets with empty sources are strange (e.g. _QCH) -> skip them 0395 if (target.sources.isEmpty()) 0396 return false; 0397 0398 auto match 0399 = [](const auto& needles, auto&& op) { return std::any_of(std::begin(needles), std::end(needles), op); }; 0400 auto startsWith = [&](const auto& needle) { return target.name.startsWith(needle); }; 0401 auto endsWith = [&](const auto& needle) { return target.name.endsWith(needle); }; 0402 auto equals = [&](const auto& needle) { return target.name == needle; }; 0403 0404 const auto invalidPrefixes = { QLatin1String("install/") }; 0405 const auto invalidSuffixes 0406 = { QLatin1String("_automoc"), QLatin1String("_autogen"), QLatin1String("_autogen_timestamp_deps") }; 0407 const auto standardTargets 0408 = { QLatin1String("edit_cache"), QLatin1String("rebuild_cache"), QLatin1String("list_install_components"), 0409 QLatin1String("test"), // not really standard, but applicable for make and ninja 0410 QLatin1String("install") }; 0411 return !match(invalidPrefixes, startsWith) && !match(invalidSuffixes, endsWith) 0412 && !match(standardTargets, equals); 0413 }; 0414 0415 auto isValidTargetSource = [](const Path& source) { 0416 const auto& segments = source.segments(); 0417 const auto lastSegment = source.lastPathSegment(); 0418 // skip non-existent cmake internal rule files 0419 if (lastSegment.endsWith(QLatin1String(".rule"))) { 0420 return false; 0421 } 0422 0423 const auto secondToLastSegment = segments.value(segments.size() - 2); 0424 // ignore generated cmake-internal files 0425 if (secondToLastSegment == QLatin1String("CMakeFiles")) { 0426 return false; 0427 } 0428 0429 // also skip *_autogen/timestamp files 0430 if (lastSegment == QLatin1String("timestamp") && secondToLastSegment.endsWith(QLatin1String("_autogen"))) { 0431 return false; 0432 } 0433 0434 return true; 0435 }; 0436 0437 // start by deleting all targets, the type may have changed anyways 0438 const auto tl = folder->targetList(); 0439 for (ProjectTargetItem* item : tl) { 0440 delete item; 0441 } 0442 0443 QHash<QString, ProjectBaseItem*> folderItems; 0444 folderItems[{}] = folder; 0445 auto findOrCreateFolderItem = [&folderItems, folder](const QString& targetFolder) 0446 { 0447 auto& item = folderItems[targetFolder]; 0448 if (!item) { 0449 item = new ProjectTargetItem(folder->project(), targetFolder, folder); 0450 // these are "virtual" folders, they keep the original path 0451 item->setPath(folder->path()); 0452 } 0453 return item; 0454 }; 0455 0456 // target folder name (or empty) to list of targets 0457 for (const auto &target : targets[folder->path()]) { 0458 if (!isValidTarget(target)) { 0459 continue; 0460 } 0461 0462 auto* targetFolder = findOrCreateFolderItem(target.folder); 0463 auto* targetItem = [&]() -> ProjectBaseItem* { 0464 switch(target.type) { 0465 case CMakeTarget::Executable: 0466 return new CMakeTargetItem(targetFolder, target.name, target.artifacts.value(0)); 0467 case CMakeTarget::Library: 0468 return new ProjectLibraryTargetItem(folder->project(), target.name, targetFolder); 0469 case CMakeTarget::Custom: 0470 return new ProjectTargetItem(folder->project(), target.name, targetFolder); 0471 } 0472 Q_UNREACHABLE(); 0473 }(); 0474 0475 for (const auto& source : target.sources) { 0476 if (!isValidTargetSource(source)) { 0477 continue; 0478 } 0479 new ProjectFileItem(folder->project(), source, targetItem); 0480 } 0481 } 0482 } 0483 0484 static void cleanupTestSuites(const QVector<CTestSuite*>& testSuites, const QVector<CTestFindJob*>& testSuiteJobs) 0485 { 0486 for (auto* testSuiteJob : testSuiteJobs) { 0487 testSuiteJob->kill(KJob::Quietly); 0488 } 0489 for (auto* testSuite : testSuites) { 0490 ICore::self()->testController()->removeTestSuite(testSuite); 0491 delete testSuite; 0492 } 0493 } 0494 0495 void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project, const QSharedPointer<CMakeServer>& server) 0496 { 0497 // TODO: show the warning message only after the entire import job finishes. When the message 0498 // is shown here (earlier), the user can follow the advice and manage to reload the project 0499 // before the current import finishes. Then KDevelop would print a kdevelop.plugins.cmake.debug 0500 // message "the project is being reloaded, aborting reload!" and ignore the reload request. 0501 if (data.isOutdated) { 0502 showConfigureOutdatedMessage(*project); 0503 } 0504 0505 if (server) { 0506 connect(server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { 0507 if (response[QStringLiteral("type")] == QLatin1String("signal")) { 0508 if (response[QStringLiteral("name")] == QLatin1String("dirty")) { 0509 m_projects[project].server->configure({}); 0510 } else 0511 qCDebug(CMAKE) << "unhandled signal response..." << project << response; 0512 } else if (response[QStringLiteral("type")] == QLatin1String("error")) { 0513 showConfigureErrorMessage(*project, response[QStringLiteral("errorMessage")].toString()); 0514 } else if (response[QStringLiteral("type")] == QLatin1String("reply")) { 0515 const auto inReplyTo = response[QStringLiteral("inReplyTo")]; 0516 if (inReplyTo == QLatin1String("configure")) { 0517 m_projects[project].server->compute(); 0518 } else if (inReplyTo == QLatin1String("compute")) { 0519 m_projects[project].server->codemodel(); 0520 } else if(inReplyTo == QLatin1String("codemodel")) { 0521 auto &data = m_projects[project].data; 0522 CMakeServerImportJob::processCodeModel(response, data); 0523 populateTargets(project->projectItem(), data.targets); 0524 } else { 0525 qCDebug(CMAKE) << "unhandled reply response..." << project << response; 0526 } 0527 } else { 0528 qCDebug(CMAKE) << "unhandled response..." << project << response; 0529 } 0530 }); 0531 } else if (!m_projects.contains(project)) { 0532 auto* reloadTimer = new QTimer(project); 0533 reloadTimer->setSingleShot(true); 0534 reloadTimer->setInterval(1000); 0535 connect(reloadTimer, &QTimer::timeout, this, [project, this]() { 0536 reload(project->projectItem()); 0537 }); 0538 connect(projectWatcher(project), &KDirWatch::dirty, reloadTimer, [this, project, reloadTimer](const QString &strPath) { 0539 const auto it = m_projects.constFind(project); 0540 if (it == m_projects.cend() || !it->data.cmakeFiles.contains(Path{strPath})) { 0541 return; 0542 } 0543 qCDebug(CMAKE) << "eventually starting reload due to change of" << strPath; 0544 reloadTimer->start(); 0545 }); 0546 } 0547 0548 auto& projectData = m_projects[project]; 0549 cleanupTestSuites(projectData.testSuites, projectData.testSuiteJobs); 0550 0551 QVector<CTestSuite*> testSuites; 0552 QVector<CTestFindJob*> testSuiteJobs; 0553 for (auto& suite : CTestUtils::createTestSuites(data.testSuites, data.targets, project)) { 0554 auto* testSuite = suite.release(); 0555 testSuites.append(testSuite); 0556 auto* job = new CTestFindJob(testSuite); 0557 connect(job, &KJob::result, this, [this, job, project, testSuite]() { 0558 if (!job->error()) { 0559 ICore::self()->testController()->addTestSuite(testSuite); 0560 } 0561 m_projects[project].testSuiteJobs.removeOne(job); 0562 }); 0563 ICore::self()->runController()->registerJob(job); 0564 testSuiteJobs.append(job); 0565 } 0566 0567 projectData = { data, server, std::move(testSuites), std::move(testSuiteJobs) }; 0568 populateTargets(project->projectItem(), data.targets); 0569 } 0570 0571 QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const 0572 { 0573 return folder->targetList(); 0574 } 0575 0576 QString CMakeManager::name() const 0577 { 0578 return languageName().str(); 0579 } 0580 0581 IndexedString CMakeManager::languageName() 0582 { 0583 static IndexedString name("CMake"); 0584 return name; 0585 } 0586 0587 KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) 0588 { 0589 return new CMakeParseJob(url, this); 0590 } 0591 0592 KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const 0593 { 0594 return m_highlight; 0595 } 0596 0597 bool CMakeManager::removeFilesFromTargets(const QList<ProjectFileItem*> &/*files*/) 0598 { 0599 return false; 0600 } 0601 0602 bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) 0603 { 0604 return false; 0605 } 0606 0607 KTextEditor::Range CMakeManager::termRangeAtPosition(const KTextEditor::Document* textDocument, 0608 const KTextEditor::Cursor& position) const 0609 { 0610 const KTextEditor::Cursor step(0, 1); 0611 0612 enum ParseState { 0613 NoChar, 0614 NonLeadingChar, 0615 AnyChar, 0616 }; 0617 0618 ParseState parseState = NoChar; 0619 KTextEditor::Cursor start = position; 0620 while (true) { 0621 const QChar c = textDocument->characterAt(start); 0622 if (c.isDigit()) { 0623 parseState = NonLeadingChar; 0624 } else if (c.isLetter() || c == QLatin1Char('_')) { 0625 parseState = AnyChar; 0626 } else { 0627 // also catches going out of document range, where c is invalid 0628 break; 0629 } 0630 start -= step; 0631 } 0632 0633 if (parseState != AnyChar) { 0634 return KTextEditor::Range::invalid(); 0635 } 0636 // undo step before last valid char 0637 start += step; 0638 0639 KTextEditor::Cursor end = position + step; 0640 while (true) { 0641 const QChar c = textDocument->characterAt(end); 0642 if (!(c.isDigit() || c.isLetter() || c == QLatin1Char('_'))) { 0643 // also catches going out of document range, where c is invalid 0644 break; 0645 } 0646 end += step; 0647 } 0648 0649 return KTextEditor::Range(start, end); 0650 } 0651 0652 void CMakeManager::showConfigureOutdatedMessage(const KDevelop::IProject& project) 0653 { 0654 const QString messageText = i18n( 0655 "Configured project '%1' with outdated CMake data." 0656 " As a result, KDevelop's code understanding may be wrong.\n" 0657 "\n" 0658 "To fix this issue, please right-click the project item in the projects tool view and click 'Reload'.", 0659 project.name()); 0660 showConfigureStatusMessage(project, messageText, Sublime::Message::Warning); 0661 } 0662 0663 void CMakeManager::showConfigureErrorMessage(const IProject& project, const QString& errorMessage) 0664 { 0665 const QString messageText = i18n( 0666 "Failed to configure project '%1' (error message: %2)." 0667 " As a result, KDevelop's code understanding will likely be broken.\n" 0668 "\n" 0669 "To fix this issue, please ensure that the project's CMakeLists.txt files" 0670 " are correct, and KDevelop is configured to use the correct CMake version and settings." 0671 " Then right-click the project item in the projects tool view and click 'Reload'.", 0672 project.name(), errorMessage); 0673 showConfigureStatusMessage(project, messageText, Sublime::Message::Error); 0674 } 0675 0676 void CMakeManager::showConfigureStatusMessage(const IProject& project, const QString& messageText, 0677 Sublime::Message::MessageType messageType) 0678 { 0679 auto& message = m_configureStatusMessages[&project]; 0680 Q_ASSERT_X(!message, Q_FUNC_INFO, "The previous message must have been discarded earlier."); 0681 message = new Sublime::Message(messageText, messageType); 0682 ICore::self()->uiController()->postMessage(message); 0683 } 0684 0685 QPair<QWidget*, KTextEditor::Range> CMakeManager::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) 0686 { 0687 KTextEditor::Range itemRange; 0688 CMakeNavigationWidget* doc = nullptr; 0689 0690 KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); 0691 if(top) 0692 { 0693 int useAt=top->findUseAt(top->transformToLocalRevision(position)); 0694 if(useAt>=0) 0695 { 0696 Use u=top->uses()[useAt]; 0697 doc = new CMakeNavigationWidget(top, u.usedDeclaration(top->topContext())); 0698 itemRange = u.m_range.castToSimpleRange(); 0699 } 0700 } 0701 0702 if (!doc) { 0703 ICMakeDocumentation* docu=CMake::cmakeDocumentation(); 0704 if( docu ) 0705 { 0706 const auto* document = ICore::self()->documentController()->documentForUrl(url); 0707 const auto* textDocument = document->textDocument(); 0708 itemRange = termRangeAtPosition(textDocument, position); 0709 if (itemRange.isValid()) { 0710 const auto id = textDocument->text(itemRange); 0711 0712 if (!id.isEmpty()) { 0713 IDocumentation::Ptr desc=docu->description(id, url); 0714 if (desc) { 0715 doc=new CMakeNavigationWidget(top, desc); 0716 } 0717 } 0718 } 0719 } 0720 } 0721 0722 return {doc, itemRange}; 0723 } 0724 0725 QPair<QString, QString> CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const 0726 { return QPair<QString, QString>(); } 0727 0728 void CMakeManager::projectClosing(IProject* p) 0729 { 0730 const auto it = m_projects.constFind(p); 0731 if (it != m_projects.cend()) { 0732 cleanupTestSuites(it->testSuites, it->testSuiteJobs); 0733 m_projects.erase(it); 0734 } 0735 0736 delete m_configureStatusMessages.take(p); // discard the message, because closing its project obsoletes it 0737 } 0738 0739 void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) 0740 { 0741 populateTargets(folder, m_projects.value(folder->project()).data.targets); 0742 } 0743 0744 ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) 0745 { 0746 // TODO: when we have data about targets, use folders with targets or similar 0747 if (QFile::exists(path.toLocalFile()+QLatin1String("/CMakeLists.txt"))) 0748 return new KDevelop::ProjectBuildFolderItem( project, path, parent ); 0749 else 0750 return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); 0751 } 0752 0753 int CMakeManager::perProjectConfigPages() const 0754 { 0755 return 1; 0756 } 0757 0758 ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) 0759 { 0760 #ifdef CMAKEMANAGER_NO_SETTINGS 0761 Q_UNUSED(number); 0762 Q_UNUSED(options); 0763 Q_UNUSED(parent); 0764 return nullptr; 0765 #else 0766 if (number == 0) { 0767 return new CMakePreferences(this, options, parent); 0768 } 0769 return nullptr; 0770 #endif 0771 } 0772 0773 void CMakeManager::reloadProjects() 0774 { 0775 const auto& projects = m_projects.keys(); 0776 for (IProject* project : projects) { 0777 CMake::checkForNeedingConfigure(project); 0778 reload(project->projectItem()); 0779 } 0780 } 0781 0782 CMakeTarget CMakeManager::targetInformation(KDevelop::ProjectTargetItem* item) const 0783 { 0784 const auto targets = m_projects[item->project()].data.targets[item->parent()->path()]; 0785 for (auto target: targets) { 0786 if (item->text() == target.name) { 0787 return target; 0788 } 0789 } 0790 return {}; 0791 } 0792 0793 KDevelop::Path CMakeManager::compiler(KDevelop::ProjectTargetItem* item) const 0794 { 0795 const auto targetInfo = targetInformation(item); 0796 if (targetInfo.sources.isEmpty()) { 0797 qCDebug(CMAKE) << "could not find target" << item->text(); 0798 return {}; 0799 } 0800 0801 const auto info = m_projects[item->project()].data.compilationData.files[targetInfo.sources.constFirst()]; 0802 const auto lang = info.language; 0803 if (lang.isEmpty()) { 0804 qCDebug(CMAKE) << "no language for" << item << item->text() << info.defines << targetInfo.sources.constFirst(); 0805 return {}; 0806 } 0807 const QString var = QLatin1String("CMAKE_") + lang + QLatin1String("_COMPILER"); 0808 const auto ret = CMake::readCacheValues(KDevelop::Path(buildDirectory(item), QStringLiteral("CMakeCache.txt")), {var}); 0809 qCDebug(CMAKE) << "compiler for" << lang << var << ret; 0810 return KDevelop::Path(ret.value(var)); 0811 } 0812 0813 #include "cmakemanager.moc" 0814 #include "moc_cmakemanager.cpp"