1 /*
2     SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org>
3     SPDX-FileCopyrightText: 2007-2013 Aleix Pol <aleixpol@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "cmakemanager.h"
9 #include "cmakeedit.h"
10 #include "cmakeutils.h"
11 #include "cmakeprojectdata.h"
12 #include "duchain/cmakeparsejob.h"
13 #include "cmakeimportjsonjob.h"
14 #include "debug.h"
15 #include "cmakecodecompletionmodel.h"
16 #include "cmakenavigationwidget.h"
17 #include "icmakedocumentation.h"
18 #include "cmakemodelitems.h"
19 #include "testing/ctestutils.h"
20 #include "testing/ctestsuite.h"
21 #include "testing/ctestfindjob.h"
22 #include "cmakeserverimportjob.h"
23 #include "cmakeserver.h"
24 #include "cmakefileapi.h"
25 #include "cmakefileapiimportjob.h"
26 
27 #ifndef CMAKEMANAGER_NO_SETTINGS
28 #include "settings/cmakepreferences.h"
29 #endif
30 
31 #include <QApplication>
32 #include <QDir>
33 #include <QReadWriteLock>
34 #include <QThread>
35 #include <QFileSystemWatcher>
36 #include <QTimer>
37 
38 #include <KPluginFactory>
39 #include <QUrl>
40 #include <QAction>
41 #include <KMessageBox>
42 #include <KTextEditor/Document>
43 #include <KDirWatch>
44 
45 #include <interfaces/icore.h>
46 #include <interfaces/idocumentcontroller.h>
47 #include <interfaces/iprojectcontroller.h>
48 #include <interfaces/iproject.h>
49 #include <interfaces/iplugincontroller.h>
50 #include <interfaces/iruntimecontroller.h>
51 #include <interfaces/iruntime.h>
52 #include <interfaces/iruncontroller.h>
53 #include <interfaces/itestcontroller.h>
54 #include <interfaces/iuicontroller.h>
55 #include <interfaces/contextmenuextension.h>
56 #include <interfaces/context.h>
57 #include <interfaces/idocumentation.h>
58 #include <util/executecompositejob.h>
59 #include <language/highlighting/codehighlighting.h>
60 #include <project/projectmodel.h>
61 #include <project/helper.h>
62 #include <project/interfaces/iprojectbuilder.h>
63 #include <project/projectfiltermanager.h>
64 #include <language/codecompletion/codecompletion.h>
65 #include <language/duchain/duchainlock.h>
66 #include <language/duchain/use.h>
67 #include <language/duchain/duchain.h>
68 #include <makefileresolver/makefileresolver.h>
69 #include <sublime/message.h>
70 
71 using namespace KDevelop;
72 
73 K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin<CMakeManager>(); )
74 
CMakeManager(QObject * parent,const QVariantList &)75 CMakeManager::CMakeManager( QObject* parent, const QVariantList& )
76     : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcmakemanager"), parent )
77     , m_filter( new ProjectFilterManager( this ) )
78 {
79     if (CMake::findExecutable().isEmpty()) {
80         setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?"));
81         m_highlight = nullptr;
82         return;
83     }
84 
85     m_highlight = new KDevelop::CodeHighlighting(this);
86 
87     new CodeCompletion(this, new CMakeCodeCompletionModel(this), name());
88 
89     connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing);
90     connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects);
91     connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded);
92 }
93 
~CMakeManager()94 CMakeManager::~CMakeManager()
95 {
96     parseLock()->lockForWrite();
97     // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state
98     parseLock()->unlock();
99 }
100 
hasBuildInfo(ProjectBaseItem * item) const101 bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const
102 {
103     return m_projects[item->project()].data.compilationData.files.contains(item->path());
104 }
105 
buildDirectory(KDevelop::ProjectBaseItem * item) const106 Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const
107 {
108     return Path(CMake::currentBuildDir(item->project()));
109 }
110 
import(KDevelop::IProject * project)111 KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project )
112 {
113     CMake::checkForNeedingConfigure(project);
114 
115     return AbstractFileManagerPlugin::import(project);
116 }
117 
118 class ChooseCMakeInterfaceJob : public ExecuteCompositeJob
119 {
120     Q_OBJECT
121 public:
ChooseCMakeInterfaceJob(IProject * project,CMakeManager * manager)122     ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager)
123         : ExecuteCompositeJob(manager, {})
124         , project(project)
125         , manager(manager)
126     {
127     }
128 
start()129     void start() override {
130         auto tryCMakeServer = [this]() {
131             qCDebug(CMAKE) << "try cmake server for import";
132             server.reset(new CMakeServer(project));
133             connect(server.data(), &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection);
134             connect(server.data(), &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection);
135         };
136         if (CMake::FileApi::supported(CMake::currentCMakeExecutable(project).toLocalFile())) {
137             qCDebug(CMAKE) << "Using cmake-file-api for import of" << project->path();
138             addSubjob(manager->builder()->configure(project));
139             auto* importJob = new CMake::FileApi::ImportJob(project, this);
140             connect(importJob, &CMake::FileApi::ImportJob::dataAvailable, this, [this, tryCMakeServer](const CMakeProjectData& data) {
141                 if (!data.compilationData.isValid) {
142                     tryCMakeServer();
143                 } else {
144                     manager->integrateData(data, project);
145                 }
146             });
147             addSubjob(importJob);
148             ExecuteCompositeJob::start();
149         } else {
150             tryCMakeServer();
151         }
152     }
153 
154 private:
successfulConnection()155     void successfulConnection() {
156         auto job = new CMakeServerImportJob(project, server, this);
157         connect(job, &CMakeServerImportJob::result, this, [this, job](){
158             if (job->error() == 0) {
159                 manager->integrateData(job->projectData(), job->project(), server);
160             }
161         });
162         addSubjob(job);
163         ExecuteCompositeJob::start();
164     }
165 
failedConnection(int code)166     void failedConnection(int code) {
167         Q_ASSERT(code > 0);
168         Q_ASSERT(!server->isServerAvailable());
169 
170         qCDebug(CMAKE) << "CMake does not provide server mode, using compile_commands.json to import" << project->name();
171 
172         // parse the JSON file
173         auto* job = new CMakeImportJsonJob(project, this);
174 
175         // create the JSON file if it doesn't exist
176         auto commandsFile = CMake::commandsFile(project);
177         if (!QFileInfo::exists(commandsFile.toLocalFile())) {
178             qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure";
179             addSubjob(manager->builder()->configure(project));
180         }
181 
182         connect(job, &CMakeImportJsonJob::result, this, [this, job]() {
183             if (job->error() == 0) {
184                 manager->integrateData(job->projectData(), job->project());
185             }
186         });
187         addSubjob(job);
188         ExecuteCompositeJob::start();
189     }
190 
191     QSharedPointer<CMakeServer> server;
192     IProject* const project;
193     CMakeManager* const manager;
194 };
195 
createImportJob(ProjectFolderItem * item)196 KJob* CMakeManager::createImportJob(ProjectFolderItem* item)
197 {
198     auto project = item->project();
199 
200     auto job = new ChooseCMakeInterfaceJob(project, this);
201     connect(job, &KJob::result, this, [this, job, project](){
202         if (job->error() != 0) {
203             qCWarning(CMAKE) << "couldn't load project successfully" << project->name() << job->error() << job->errorText();
204             showConfigureErrorMessage(project->name(), job->errorText());
205         }
206     });
207 
208     const QList<KJob*> jobs = {
209         job,
210         KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing
211     };
212 
213     Q_ASSERT(!jobs.contains(nullptr));
214     auto* composite = new ExecuteCompositeJob(this, jobs);
215 //     even if the cmake call failed, we want to load the project so that the project can be worked on
216     composite->setAbortOnError(false);
217     return composite;
218 }
219 
targets() const220 QList<KDevelop::ProjectTargetItem*> CMakeManager::targets() const
221 {
222     QList<KDevelop::ProjectTargetItem*> ret;
223     for (auto it = m_projects.begin(), end = m_projects.end(); it != end; ++it) {
224         IProject* p = it.key();
225         ret+=p->projectItem()->targetList();
226     }
227     return ret;
228 }
229 
fileInformation(KDevelop::ProjectBaseItem * item) const230 CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const
231 {
232     const auto& data = m_projects[item->project()].data.compilationData;
233 
234     auto toCanonicalPath = [](const Path &path) -> Path {
235         // if the path contains a symlink, then we will not find it in the lookup table
236         // as that only only stores canonicalized paths. Thus, we fallback to
237         // to the canonicalized path and see if that brings up any matches
238         const auto localPath = path.toLocalFile();
239         const auto canonicalPath = QFileInfo(localPath).canonicalFilePath();
240         return (localPath == canonicalPath) ? path : Path(canonicalPath);
241     };
242 
243     auto path = item->path();
244     if (!item->folder()) {
245         // try to look for file meta data directly
246         auto it = data.files.find(path);
247         if (it == data.files.end()) {
248             // fallback to canonical path lookup
249             auto canonical = toCanonicalPath(path);
250             if (canonical != path) {
251                 it = data.files.find(canonical);
252             }
253         }
254         if (it != data.files.end()) {
255             return *it;
256         }
257         // else look for a file in the parent folder
258         path = path.parent();
259     }
260 
261     while (true) {
262         // try to look for a file in the current folder path
263         auto it = data.fileForFolder.find(path);
264         if (it == data.fileForFolder.end()) {
265             // fallback to canonical path lookup
266             auto canonical = toCanonicalPath(path);
267             if (canonical != path) {
268                 it = data.fileForFolder.find(canonical);
269             }
270         }
271         if (it != data.fileForFolder.end()) {
272             return data.files[it.value()];
273         }
274         if (!path.hasParent()) {
275             break;
276         }
277         path = path.parent();
278     }
279 
280     qCDebug(CMAKE) << "no information found for" << item->path();
281     return {};
282 }
283 
includeDirectories(KDevelop::ProjectBaseItem * item) const284 Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const
285 {
286     return fileInformation(item).includes;
287 }
288 
frameworkDirectories(KDevelop::ProjectBaseItem * item) const289 Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const
290 {
291     return fileInformation(item).frameworkDirectories;
292 }
293 
defines(KDevelop::ProjectBaseItem * item) const294 QHash<QString, QString> CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const
295 {
296     return fileInformation(item).defines;
297 }
298 
extraArguments(KDevelop::ProjectBaseItem * item) const299 QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const
300 {
301     return fileInformation(item).compileFlags;
302 }
303 
builder() const304 KDevelop::IProjectBuilder * CMakeManager::builder() const
305 {
306     IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevCMakeBuilder"));
307     Q_ASSERT(i);
308     auto* _builder = i->extension<KDevelop::IProjectBuilder>();
309     Q_ASSERT(_builder );
310     return _builder ;
311 }
312 
reload(KDevelop::ProjectFolderItem * folder)313 bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder)
314 {
315     qCDebug(CMAKE) << "reloading" << folder->path();
316 
317     IProject* project = folder->project();
318     if (!project->isReady())
319         return false;
320 
321     KJob *job = createImportJob(folder);
322     project->setReloadJob(job);
323     ICore::self()->runController()->registerJob( job );
324     if (folder == project->projectItem()) {
325         connect(job, &KJob::finished, this, [project](KJob* job) {
326             if (job->error())
327                 return;
328 
329             emit KDevelop::ICore::self()->projectController()->projectConfigurationChanged(project);
330             KDevelop::ICore::self()->projectController()->reparseProject(project);
331         });
332     }
333 
334     return true;
335 }
336 
populateTargets(ProjectFolderItem * folder,const QHash<KDevelop::Path,QVector<CMakeTarget>> & targets)337 static void populateTargets(ProjectFolderItem* folder, const QHash<KDevelop::Path, QVector<CMakeTarget>>& targets)
338 {
339     auto isValidTarget = [](const CMakeTarget& target) -> bool {
340         if (target.type != CMakeTarget::Custom)
341             return true;
342 
343         // utility targets with empty sources are strange (e.g. _QCH) -> skip them
344         if (target.sources.isEmpty())
345             return false;
346 
347         auto match
348             = [](const auto& needles, auto&& op) { return std::any_of(std::begin(needles), std::end(needles), op); };
349         auto startsWith = [&](const auto& needle) { return target.name.startsWith(needle); };
350         auto endsWith = [&](const auto& needle) { return target.name.endsWith(needle); };
351         auto equals = [&](const auto& needle) { return target.name == needle; };
352 
353         const auto invalidPrefixes = { QLatin1String("install/") };
354         const auto invalidSuffixes
355             = { QLatin1String("_automoc"), QLatin1String("_autogen"), QLatin1String("_autogen_timestamp_deps") };
356         const auto standardTargets
357             = { QLatin1String("edit_cache"), QLatin1String("rebuild_cache"), QLatin1String("list_install_components"),
358                 QLatin1String("test"), // not really standard, but applicable for make and ninja
359                 QLatin1String("install") };
360         return !match(invalidPrefixes, startsWith) && !match(invalidSuffixes, endsWith)
361             && !match(standardTargets, equals);
362     };
363 
364     auto isValidTargetSource = [](const Path& source) {
365         const auto& segments = source.segments();
366         const auto lastSegment = source.lastPathSegment();
367         // skip non-existent cmake internal rule files
368         if (lastSegment.endsWith(QLatin1String(".rule"))) {
369             return false;
370         }
371 
372         const auto secondToLastSegment = segments.value(segments.size() - 2);
373         // ignore generated cmake-internal files
374         if (secondToLastSegment == QLatin1String("CMakeFiles")) {
375             return false;
376         }
377 
378         // also skip *_autogen/timestamp files
379         if (lastSegment == QLatin1String("timestamp") && secondToLastSegment.endsWith(QLatin1String("_autogen"))) {
380             return false;
381         }
382 
383         return true;
384     };
385 
386     // start by deleting all targets, the type may have changed anyways
387     const auto tl = folder->targetList();
388     for (ProjectTargetItem* item : tl) {
389         delete item;
390     }
391 
392     QHash<QString, ProjectBaseItem*> folderItems;
393     folderItems[{}] = folder;
394     auto findOrCreateFolderItem = [&folderItems, folder](const QString& targetFolder)
395     {
396         auto& item = folderItems[targetFolder];
397         if (!item) {
398             item = new ProjectTargetItem(folder->project(), targetFolder, folder);
399             // these are "virtual" folders, they keep the original path
400             item->setPath(folder->path());
401         }
402         return item;
403     };
404 
405     // target folder name (or empty) to list of targets
406     for (const auto &target : targets[folder->path()]) {
407         if (!isValidTarget(target)) {
408             continue;
409         }
410 
411         auto* targetFolder = findOrCreateFolderItem(target.folder);
412         auto* targetItem = [&]() -> ProjectBaseItem* {
413             switch(target.type) {
414                 case CMakeTarget::Executable:
415                     return new CMakeTargetItem(targetFolder, target.name, target.artifacts.value(0));
416                 case CMakeTarget::Library:
417                     return new ProjectLibraryTargetItem(folder->project(), target.name, targetFolder);
418                 case CMakeTarget::Custom:
419                     return new ProjectTargetItem(folder->project(), target.name, targetFolder);
420             }
421             Q_UNREACHABLE();
422         }();
423 
424         for (const auto& source : target.sources) {
425             if (!isValidTargetSource(source)) {
426                 continue;
427             }
428             new ProjectFileItem(folder->project(), source, targetItem);
429         }
430     }
431 }
432 
cleanupTestSuites(const QVector<CTestSuite * > & testSuites,const QVector<CTestFindJob * > & testSuiteJobs)433 static void cleanupTestSuites(const QVector<CTestSuite*>& testSuites, const QVector<CTestFindJob*>& testSuiteJobs)
434 {
435     for (auto* testSuiteJob : testSuiteJobs) {
436         testSuiteJob->kill(KJob::Quietly);
437     }
438     for (auto* testSuite : testSuites) {
439         ICore::self()->testController()->removeTestSuite(testSuite);
440         delete testSuite;
441     }
442 }
443 
integrateData(const CMakeProjectData & data,KDevelop::IProject * project,const QSharedPointer<CMakeServer> & server)444 void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project, const QSharedPointer<CMakeServer>& server)
445 {
446     if (server) {
447         connect(server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) {
448             if (response[QStringLiteral("type")] == QLatin1String("signal")) {
449                 if (response[QStringLiteral("name")] == QLatin1String("dirty")) {
450                     m_projects[project].server->configure({});
451                 } else
452                     qCDebug(CMAKE) << "unhandled signal response..." << project << response;
453             } else if (response[QStringLiteral("type")] == QLatin1String("error")) {
454                 showConfigureErrorMessage(project->name(), response[QStringLiteral("errorMessage")].toString());
455             } else if (response[QStringLiteral("type")] == QLatin1String("reply")) {
456                 const auto inReplyTo = response[QStringLiteral("inReplyTo")];
457                 if (inReplyTo == QLatin1String("configure")) {
458                     m_projects[project].server->compute();
459                 } else if (inReplyTo == QLatin1String("compute")) {
460                     m_projects[project].server->codemodel();
461                 } else if(inReplyTo == QLatin1String("codemodel")) {
462                     auto &data = m_projects[project].data;
463                     CMakeServerImportJob::processCodeModel(response, data);
464                     populateTargets(project->projectItem(), data.targets);
465                 } else {
466                     qCDebug(CMAKE) << "unhandled reply response..." << project << response;
467                 }
468             } else {
469                 qCDebug(CMAKE) << "unhandled response..." << project << response;
470             }
471         });
472     } else if (!m_projects.contains(project)) {
473         auto* reloadTimer = new QTimer(project);
474         reloadTimer->setSingleShot(true);
475         reloadTimer->setInterval(1000);
476         connect(reloadTimer, &QTimer::timeout, this, [project, this]() {
477             reload(project->projectItem());
478         });
479         connect(projectWatcher(project), &KDirWatch::dirty, reloadTimer, [this, project, reloadTimer](const QString &strPath) {
480             const auto& cmakeFiles = m_projects[project].data.cmakeFiles;
481             KDevelop::Path path(strPath);
482             auto it = cmakeFiles.find(path);
483             if (it == cmakeFiles.end() || it->isGenerated || it->isExternal) {
484                 return;
485             }
486             qCDebug(CMAKE) << "eventually starting reload due to change of" << strPath;
487             reloadTimer->start();
488         });
489     }
490 
491     auto& projectData = m_projects[project];
492     cleanupTestSuites(projectData.testSuites, projectData.testSuiteJobs);
493 
494     QVector<CTestSuite*> testSuites;
495     QVector<CTestFindJob*> testSuiteJobs;
496     for (auto& suite : CTestUtils::createTestSuites(data.testSuites, data.targets, project)) {
497         auto* testSuite = suite.release();
498         testSuites.append(testSuite);
499         auto* job = new CTestFindJob(testSuite);
500         connect(job, &KJob::result, this, [this, job, project, testSuite]() {
501             if (!job->error()) {
502                 ICore::self()->testController()->addTestSuite(testSuite);
503             }
504             m_projects[project].testSuiteJobs.removeOne(job);
505         });
506         ICore::self()->runController()->registerJob(job);
507         testSuiteJobs.append(job);
508     }
509 
510     projectData = { data, server, std::move(testSuites), std::move(testSuiteJobs) };
511     populateTargets(project->projectItem(), data.targets);
512 }
513 
targets(KDevelop::ProjectFolderItem * folder) const514 QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const
515 {
516     return folder->targetList();
517 }
518 
name() const519 QString CMakeManager::name() const
520 {
521     return languageName().str();
522 }
523 
languageName()524 IndexedString CMakeManager::languageName()
525 {
526     static IndexedString name("CMake");
527     return name;
528 }
529 
createParseJob(const IndexedString & url)530 KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url)
531 {
532     return new CMakeParseJob(url, this);
533 }
534 
codeHighlighting() const535 KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const
536 {
537     return m_highlight;
538 }
539 
removeFilesFromTargets(const QList<ProjectFileItem * > &)540 bool CMakeManager::removeFilesFromTargets(const QList<ProjectFileItem*> &/*files*/)
541 {
542     return false;
543 }
544 
addFilesToTarget(const QList<ProjectFileItem * > &,ProjectTargetItem *)545 bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/)
546 {
547     return false;
548 }
549 
termRangeAtPosition(const KTextEditor::Document * textDocument,const KTextEditor::Cursor & position) const550 KTextEditor::Range CMakeManager::termRangeAtPosition(const KTextEditor::Document* textDocument,
551                                                      const KTextEditor::Cursor& position) const
552 {
553     const KTextEditor::Cursor step(0, 1);
554 
555     enum ParseState {
556         NoChar,
557         NonLeadingChar,
558         AnyChar,
559     };
560 
561     ParseState parseState = NoChar;
562     KTextEditor::Cursor start = position;
563     while (true) {
564         const QChar c = textDocument->characterAt(start);
565         if (c.isDigit()) {
566             parseState = NonLeadingChar;
567         } else if (c.isLetter() || c == QLatin1Char('_')) {
568             parseState = AnyChar;
569         } else {
570             // also catches going out of document range, where c is invalid
571             break;
572         }
573         start -= step;
574     }
575 
576     if (parseState != AnyChar) {
577         return KTextEditor::Range::invalid();
578     }
579     // undo step before last valid char
580     start += step;
581 
582     KTextEditor::Cursor end = position + step;
583     while (true) {
584         const QChar c = textDocument->characterAt(end);
585         if (!(c.isDigit() || c.isLetter() || c == QLatin1Char('_'))) {
586             // also catches going out of document range, where c is invalid
587             break;
588         }
589         end += step;
590     }
591 
592     return KTextEditor::Range(start, end);
593 }
594 
showConfigureErrorMessage(const QString & projectName,const QString & errorMessage) const595 void CMakeManager::showConfigureErrorMessage(const QString& projectName, const QString& errorMessage) const
596 {
597     if (!QApplication::activeWindow()) {
598         // Do not show a message box if there is no active window in order not to block unit tests.
599         return;
600     }
601     const QString messageText = i18n(
602         "Failed to configure project '%1' (error message: %2)."
603         " As a result, KDevelop's code understanding will likely be broken.\n"
604         "\n"
605         "To fix this issue, please ensure that the project's CMakeLists.txt files"
606         " are correct, and KDevelop is configured to use the correct CMake version and settings."
607         " Then right-click the project item in the projects tool view and click 'Reload'.",
608         projectName, errorMessage);
609     auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
610     ICore::self()->uiController()->postMessage(message);
611 }
612 
specialLanguageObjectNavigationWidget(const QUrl & url,const KTextEditor::Cursor & position)613 QPair<QWidget*, KTextEditor::Range> CMakeManager::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position)
614 {
615     KTextEditor::Range itemRange;
616     CMakeNavigationWidget* doc = nullptr;
617 
618     KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url));
619     if(top)
620     {
621         int useAt=top->findUseAt(top->transformToLocalRevision(position));
622         if(useAt>=0)
623         {
624             Use u=top->uses()[useAt];
625             doc = new CMakeNavigationWidget(top, u.usedDeclaration(top->topContext()));
626             itemRange = u.m_range.castToSimpleRange();
627         }
628     }
629 
630     if (!doc) {
631         ICMakeDocumentation* docu=CMake::cmakeDocumentation();
632         if( docu )
633         {
634             const auto* document = ICore::self()->documentController()->documentForUrl(url);
635             const auto* textDocument = document->textDocument();
636             itemRange = termRangeAtPosition(textDocument, position);
637             if (itemRange.isValid()) {
638                 const auto id = textDocument->text(itemRange);
639 
640                 if (!id.isEmpty()) {
641                     IDocumentation::Ptr desc=docu->description(id, url);
642                     if (desc) {
643                         doc=new CMakeNavigationWidget(top, desc);
644                     }
645                 }
646             }
647         }
648     }
649 
650     return {doc, itemRange};
651 }
652 
cacheValue(KDevelop::IProject *,const QString &) const653 QPair<QString, QString> CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const
654 { return QPair<QString, QString>(); }
655 
projectClosing(IProject * p)656 void CMakeManager::projectClosing(IProject* p)
657 {
658     auto it = m_projects.find(p);
659     if (it != m_projects.end()) {
660         cleanupTestSuites(it->testSuites, it->testSuiteJobs);
661         m_projects.erase(it);
662     }
663 }
664 
filterManager() const665 ProjectFilterManager* CMakeManager::filterManager() const
666 {
667     return m_filter;
668 }
669 
folderAdded(KDevelop::ProjectFolderItem * folder)670 void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder)
671 {
672     populateTargets(folder, m_projects.value(folder->project()).data.targets);
673 }
674 
createFolderItem(IProject * project,const Path & path,ProjectBaseItem * parent)675 ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent)
676 {
677 //     TODO: when we have data about targets, use folders with targets or similar
678     if (QFile::exists(path.toLocalFile()+QLatin1String("/CMakeLists.txt")))
679         return new KDevelop::ProjectBuildFolderItem( project, path, parent );
680     else
681         return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent);
682 }
683 
perProjectConfigPages() const684 int CMakeManager::perProjectConfigPages() const
685 {
686     return 1;
687 }
688 
perProjectConfigPage(int number,const ProjectConfigOptions & options,QWidget * parent)689 ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent)
690 {
691 #ifdef CMAKEMANAGER_NO_SETTINGS
692     Q_UNUSED(number);
693     Q_UNUSED(options);
694     Q_UNUSED(parent);
695     return nullptr;
696 #else
697     if (number == 0) {
698         return new CMakePreferences(this, options, parent);
699     }
700     return nullptr;
701 #endif
702 }
703 
reloadProjects()704 void CMakeManager::reloadProjects()
705 {
706     const auto& projects = m_projects.keys();
707     for (IProject* project : projects) {
708         CMake::checkForNeedingConfigure(project);
709         reload(project->projectItem());
710     }
711 }
712 
targetInformation(KDevelop::ProjectTargetItem * item) const713 CMakeTarget CMakeManager::targetInformation(KDevelop::ProjectTargetItem* item) const
714 {
715     const auto targets = m_projects[item->project()].data.targets[item->parent()->path()];
716     for (auto target: targets) {
717         if (item->text() == target.name) {
718             return target;
719         }
720     }
721     return {};
722 }
723 
compiler(KDevelop::ProjectTargetItem * item) const724 KDevelop::Path CMakeManager::compiler(KDevelop::ProjectTargetItem* item) const
725 {
726     const auto targetInfo = targetInformation(item);
727     if (targetInfo.sources.isEmpty()) {
728         qCDebug(CMAKE) << "could not find target" << item->text();
729         return {};
730     }
731 
732     const auto info = m_projects[item->project()].data.compilationData.files[targetInfo.sources.constFirst()];
733     const auto lang = info.language;
734     if (lang.isEmpty()) {
735         qCDebug(CMAKE) << "no language for" << item << item->text() << info.defines << targetInfo.sources.constFirst();
736         return {};
737     }
738     const QString var = QLatin1String("CMAKE_") + lang + QLatin1String("_COMPILER");
739     const auto ret = CMake::readCacheValues(KDevelop::Path(buildDirectory(item), QStringLiteral("CMakeCache.txt")), {var});
740     qCDebug(CMAKE) << "compiler for" << lang << var << ret;
741     return KDevelop::Path(ret.value(var));
742 }
743 
744 #include "cmakemanager.moc"
745