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