cmakemanager.cpp 59.7 KB
Newer Older
1
2
3
/* KDevelop CMake Support
 *
 * Copyright 2006 Matt Rogers <mattr@kde.org>
4
 * Copyright 2007-2009 Aleix Pol <aleixpol@kde.org>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

22
#include "cmakemanager.h"
23
24
25

#include <QList>
#include <QVector>
26
#include <QDomDocument>
27
#include <QDir>
28
#include <QQueue>
29
#include <QThread>
30

31
32
33
34
#include <kpluginfactory.h>
#include <kpluginloader.h>
#include <KAboutData>
#include <KDialog>
35
#include <kparts/mainwindow.h>
36
#include <KUrl>
37
#include <KAction>
38
#include <KMessageBox>
39
#include <ktexteditor/document.h>
40
#include <KStandardDirs>
41

42
43
44
45
46
47
48
#include <interfaces/icore.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iproject.h>
#include <interfaces/iplugincontroller.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/contextmenuextension.h>
49
#include <interfaces/context.h>
50
#include <project/projectmodel.h>
51
#include <project/importprojectjob.h>
52
#include <project/helper.h>
53
#include <language/duchain/parsingenvironment.h>
54
#include <language/duchain/indexedstring.h>
55
56
57
58
59
#include <language/duchain/duchain.h>
#include <language/duchain/dumpchain.h>
#include <language/duchain/topducontext.h>
#include <language/duchain/declaration.h>
#include <language/duchain/duchainlock.h>
60
#include <language/duchain/duchainutils.h>
Hamish Rodda's avatar
Hamish Rodda committed
61
#include <language/codecompletion/codecompletion.h>
62

63
#include "cmakemodelitems.h"
64
#include "cmakenavigationwidget.h"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
65
#include "cmakecachereader.h"
66
67
68
#include "cmakeastvisitor.h"
#include "cmakeprojectvisitor.h"
#include "cmakeexport.h"
69
#include "cmakecodecompletionmodel.h"
70
#include "cmakeutils.h"
71
#include "cmaketypes.h"
Andreas Pakulat's avatar
Andreas Pakulat committed
72
#include "parser/cmakeparserutils.h"
73
#include "icmakedocumentation.h"
74

75
76
77
78
#ifdef CMAKEDEBUGVISITOR
#include "cmakedebugvisitor.h"
#endif

79
80
#include "ui_cmakepossibleroots.h"
#include <language/duchain/use.h>
81
#include <interfaces/idocumentation.h>
82
#include "cmakeprojectdata.h"
83
#include <cmakeconfig.h>
84

85
#include <language/highlighting/codehighlighting.h>
86
#include <interfaces/iruncontroller.h>
87
88
#include <vcs/interfaces/ibasicversioncontrol.h>
#include <vcs/vcsjob.h>
89
#include <project/interfaces/iprojectbuilder.h>
90
#include <util/environmentgrouplist.h>
91

92
93
Q_DECLARE_METATYPE(KDevelop::IProject*);

94
95
using namespace KDevelop;

96
K_PLUGIN_FACTORY(CMakeSupportFactory, registerPlugin<CMakeManager>(); )
97
K_EXPORT_PLUGIN(CMakeSupportFactory(KAboutData("kdevcmakemanager","kdevcmake", ki18n("CMake Manager"), "0.1", ki18n("Support for managing CMake projects"), KAboutData::License_GPL)))
Andreas Pakulat's avatar
Andreas Pakulat committed
98

99
100
namespace {

101
102
const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support");

103
104
105
QString fetchBuildDir(KDevelop::IProject* project)
{
    Q_ASSERT(project);
106
    return CMake::currentBuildDir(project).toLocalFile(KUrl::AddTrailingSlash);
107
108
109
110
111
}

QString fetchInstallDir(KDevelop::IProject* project)
{
    Q_ASSERT(project);
112
    return CMake::currentInstallDir(project).toLocalFile(KUrl::AddTrailingSlash);
113
114
115
116
117
118
119
}

inline QString replaceBuildDir(QString in, QString buildDir)
{
    return in.replace("#[bin_dir]", buildDir);
}

120
inline  QString replaceInstallDir(QString in, QString installDir)
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
{
    return in.replace("#[install_dir]", installDir);
}

KUrl::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs)
{
    QString buildDir = fetchBuildDir(project);
    QString installDir = fetchInstallDir(project);

    KUrl::List newList;
    foreach(const QString& _s, dirs)
    {
        QString s=_s;
        if(s.startsWith(QString::fromUtf8("#[bin_dir]")))
        {
            s= replaceBuildDir(s, buildDir);
        }
        else if(s.startsWith(QString::fromUtf8("#[install_dir]")))
        {
            s= replaceInstallDir(s, installDir);
        }
142
143
144
145
146
147
148
149
150
        KUrl d(s);
        d.cleanPath();
        d.adjustPath(KUrl::AddTrailingSlash);
//         kDebug(9042) << "resolved" << _s << "to" << d;

        if (!newList.contains(d))
        {
            newList.append(d);
        }
151
152
153
154
    }
    return newList;
}

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
void eatLeadingWhitespace(KTextEditor::Document* doc, KTextEditor::Range& eater, const KTextEditor::Range& bounds)
{
    QString text = doc->text(KTextEditor::Range(bounds.start(), eater.start()));
    int newStartLine = eater.start().line(), pos = text.length() - 2; //pos = index before eater.start
    while (pos > 0)
    {
        if (text[pos] == '\n')
            --newStartLine;
        else if (!text[pos].isSpace())
        {
            ++pos;
            break;
        }
        --pos;
    }
    int lastNewLinePos = text.lastIndexOf('\n', pos - 1);
    int newStartCol = lastNewLinePos == -1 ? eater.start().column() + pos :
                                             pos - lastNewLinePos - 1;
    eater.start().setLine(newStartLine);
    eater.start().setColumn(newStartCol);
}

177
178
179
KTextEditor::Range rangeForText(KTextEditor::Document* doc, const KTextEditor::Range& r, const QString& name)
{
    QString txt=doc->text(r);
180
181
    QRegExp match("([\\s]|^)(\\./)?"+QRegExp::escape(name));
    int namepos = match.indexIn(txt);
182
    int length = match.cap(0).size();
183
    
184
    if(namepos == -1)
185
        return KTextEditor::Range::invalid();
186
187
    //QRegExp doesn't support lookbehind asserts, and \b isn't good enough
    //so either match "^" or match "\s" and then +1 here
188
    if (txt[namepos].isSpace()) {
189
        ++namepos;
190
191
        --length;
    }
192
193
    
    KTextEditor::Cursor c(r.start());
194
195
196
197
198
199
    c.setLine(c.line() + txt.left(namepos).count('\n'));
    int lastNewLinePos = txt.lastIndexOf('\n', namepos);
    if (lastNewLinePos < 0)
        c.setColumn(r.start().column() + namepos);
    else
        c.setColumn(namepos - lastNewLinePos - 1);
200
    
201
    return KTextEditor::Range(c, KTextEditor::Cursor(c.line(), c.column()+length));
202
203
}

204
205
206
bool followUses(KTextEditor::Document* doc, RangeInRevision r, const QString& name, const KUrl& lists, bool add, const QString& replace)
{
    bool ret=false;
207
208
209
210
211
    KTextEditor::Range rx;
    if(!add)
        rx=rangeForText(doc, r.castToSimpleRange().textRange(), name);
    
    if(!add && rx.isValid())
212
    {
213
        if(replace.isEmpty())
214
215
        {
            eatLeadingWhitespace(doc, rx, r.castToSimpleRange().textRange());
216
            doc->removeText(rx);
217
        }
218
        else
219
            doc->replaceText(rx, replace);
220
        
221
222
223
224
225
226
227
228
229
230
231
232
233
234
        ret=true;
    }
    else
    {
        KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
        KDevelop::ReferencedTopDUContext topctx=DUChain::self()->chainForDocument(lists);
        QList<Declaration*> decls;
        for(int i=0; i<topctx->usesCount(); i++)
        {
            Use u = topctx->uses()[i];

            if(!r.contains(u.m_range))
                continue; //We just want the uses in the range, not the whole file

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
235
            Declaration* d=u.usedDeclaration(topctx);
236

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
237
            if(d && d->topContext()->url().toUrl()==lists)
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
                decls += d;
        }

        if(add && decls.isEmpty())
        {
            doc->insertText(r.castToSimpleRange().textRange().start(), ' '+name);
            ret=true;
        }
        else foreach(Declaration* d, decls)
        {
            r.start=d->range().end;

            for(int lineNum = r.start.line; lineNum <= r.end.line; lineNum++)
            {
                int endParenIndex = doc->line(lineNum).indexOf(')');
                if(endParenIndex >= 0) {
                    r.end = CursorInRevision(lineNum, endParenIndex);
                    break;
                }
            }

            if(!r.isEmpty())
            {
                ret = ret || followUses(doc, r, name, lists, add, replace);
            }
        }
    }
    return ret;
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
268
QString dotlessRelativeUrl(const KUrl& baseUrl, const KUrl& url)
269
270
271
272
273
274
275
{
    QString dotlessRelative = KUrl::relativeUrl(baseUrl, url);
    if (dotlessRelative.startsWith("./"))
        dotlessRelative.remove(0, 2);
    return dotlessRelative;
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
276
QString relativeToLists(const QString& listsPath, const KUrl& url)
277
278
279
280
281
282
{
    KUrl listsFolder = KUrl(KUrl(listsPath).directory(KUrl::AppendTrailingSlash));
    QString relative = dotlessRelativeUrl(listsFolder, url);
    return relative;
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
283
KUrl afterMoveUrl(const KUrl& origUrl, const KUrl& movedOrigUrl, const KUrl& movedNewUrl)
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
{
    QString difference = dotlessRelativeUrl(movedOrigUrl, origUrl);
    KUrl newUrl(movedNewUrl);
    newUrl.addPath(difference);
    return newUrl;
}

QString itemListspath(const ProjectBaseItem* item)
{
    const DescriptorAttatched *desc = 0;
    if (item->parent()->target())
        desc = dynamic_cast<const DescriptorAttatched*>(item->parent());
    else if (item->type() == ProjectBaseItem::BuildFolder)
        desc = dynamic_cast<const DescriptorAttatched*>(item);

    if (!desc)
        return QString();
    return desc->descriptor().filePath;
}

bool itemAffected(const ProjectBaseItem *item, const KUrl &changeUrl)
{
    QString listsPath = itemListspath(item);
    if (listsPath.isEmpty())
        return false;

    KUrl listsFolder(KUrl(listsPath).directory(KUrl::AppendTrailingSlash));
    //Who thought it was a good idea to have KUrl::isParentOf return true if the urls are equal?
    return listsFolder.QUrl::isParentOf(changeUrl);
}

QList<ProjectBaseItem*> cmakeListedItemsAffectedByUrlChange(const IProject *proj, const KUrl &url, KUrl rootUrl = KUrl())
{
    if (rootUrl.isEmpty())
        rootUrl = url;

    QList<ProjectBaseItem*> dirtyItems;

    QList<ProjectBaseItem*> sameUrlItems = proj->itemsForUrl(url);
    foreach(ProjectBaseItem *sameUrlItem, sameUrlItems)
    {
        if (itemAffected(sameUrlItem, rootUrl))
            dirtyItems.append(sameUrlItem);

        foreach(ProjectBaseItem* childItem, sameUrlItem->children())
            dirtyItems.append(cmakeListedItemsAffectedByUrlChange(childItem->project(), childItem->url(), rootUrl));
    }
    return dirtyItems;
}

QList<ProjectBaseItem*> cmakeListedItemsAffectedByItemsChanged(const QList<ProjectBaseItem*> &items)
{
    QList<ProjectBaseItem*> dirtyItems;
    foreach(ProjectBaseItem *item, items)
        dirtyItems.append(cmakeListedItemsAffectedByUrlChange(item->project(), item->url()));
    return dirtyItems;
}

bool changesWidgetRenameFolder(const CMakeFolderItem *folder, const KUrl &newUrl, ApplyChangesWidget *widget)
{
    QString lists = folder->descriptor().filePath;
    widget->addDocuments(IndexedString(lists));
    QString relative(relativeToLists(lists, newUrl));
    KTextEditor::Range range = folder->descriptor().argRange().castToSimpleRange().textRange();
    return widget->document()->replaceText(range, relative);
}

bool changesWidgetRemoveCMakeFolder(const CMakeFolderItem *folder, ApplyChangesWidget *widget)
{
    widget->addDocuments(IndexedString(folder->descriptor().filePath));
    KTextEditor::Range range = folder->descriptor().range().castToSimpleRange().textRange();
    return widget->document()->removeText(range);
}

bool changesWidgetAddFolder(const KUrl &folderUrl, const CMakeFolderItem *toFolder, ApplyChangesWidget *widget)
{
    QString lists(toFolder->url().path(KUrl::AddTrailingSlash).append("CMakeLists.txt"));
    QString relative(relativeToLists(lists, folderUrl));
    if (relative.endsWith('/'))
        relative.chop(1);
    QString insert = QString("add_subdirectory(%1)").arg(relative);
    widget->addDocuments(IndexedString(lists));
    return widget->document()->insertLine(widget->document()->lines(), insert);
}

bool changesWidgetMoveTargetFile(const ProjectBaseItem *file, const KUrl &newUrl, ApplyChangesWidget *widget)
{
    const DescriptorAttatched *desc = dynamic_cast<const DescriptorAttatched*>(file->parent());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
372
    RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().argRange().end);
373
374
375
376
377
378
379
380
381
382
    QString listsPath = desc->descriptor().filePath;
    QString newRelative = relativeToLists(listsPath, newUrl);
    QString oldRelative = relativeToLists(listsPath, file->url());
    widget->addDocuments(IndexedString(listsPath));
    return followUses(widget->document(), targetRange, oldRelative, listsPath, false, newRelative);
}

bool changesWidgetAddFileToTarget(const ProjectFileItem *item, const ProjectTargetItem *target, ApplyChangesWidget *widget)
{
    const DescriptorAttatched *desc = dynamic_cast<const DescriptorAttatched*>(target);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
383
    RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().range().end);
384
385
386
387
388
389
390
391
392
    QString lists = desc->descriptor().filePath;
    QString relative = relativeToLists(lists, item->url());
    widget->addDocuments(IndexedString(lists));
    return followUses(widget->document(), targetRange, relative, lists, true, QString());
}

bool changesWidgetRemoveFileFromTarget(const ProjectBaseItem *item, ApplyChangesWidget *widget)
{
    const DescriptorAttatched *desc = dynamic_cast<const DescriptorAttatched*>(item->parent());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
393
    RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().range().end);
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
    QString lists = desc->descriptor().filePath;
    QString relative = relativeToLists(lists, item->url());
    widget->addDocuments(IndexedString(lists));
    return followUses(widget->document(), targetRange, relative, lists, false, QString());
}

bool changesWidgetRemoveItems(const QList<ProjectBaseItem*> &items, ApplyChangesWidget *widget)
{
    foreach(ProjectBaseItem *item, items)
    {
        CMakeFolderItem *folder = dynamic_cast<CMakeFolderItem*>(item);
        if (folder && !changesWidgetRemoveCMakeFolder(folder, widget))
            return false;
        else if (item->parent()->target() && !changesWidgetRemoveFileFromTarget(item, widget))
            return false;
    }
    return true;
}

bool changesWidgetRemoveFilesFromTargets(const QList<ProjectFileItem*> &files, ApplyChangesWidget *widget)
{
    foreach(ProjectBaseItem *file, files)
    {
        Q_ASSERT(file->parent()->target());
        if (!changesWidgetRemoveFileFromTarget(file, widget))
            return false;
    }
    return true;
}

bool changesWidgetAddFilesToTarget(const QList<ProjectFileItem*> &files, const ProjectTargetItem* target, ApplyChangesWidget *widget)
{
    foreach(ProjectFileItem *file, files)
    {
        if (!changesWidgetAddFileToTarget(file, target, widget))
            return false;
    }
    return true;
}

CMakeFolderItem* nearestCMakeFolder(ProjectBaseItem* item)
{
436
    while(!dynamic_cast<CMakeFolderItem*>(item) && item)
437
438
439
440
441
        item = item->parent();

    return dynamic_cast<CMakeFolderItem*>(item);
}

442
443
444
445
446
447
448
449
450
451
template<class T>
QList<ProjectBaseItem*> castToBase(const QList<T*>& ptrs)
{
    QList<ProjectBaseItem*> ret;
    foreach(ProjectBaseItem* ptr, ptrs)
        ret += ptr;
    
    return ret;
}

452
453
}

454
CMakeManager::CMakeManager( QObject* parent, const QVariantList& )
455
    : KDevelop::IPlugin( CMakeSupportFactory::componentData(), parent )
456
{
457
458
    KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager )
    KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager )
459
    KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport )
460
    KDEV_USE_EXTENSION_INTERFACE( ICMakeManager)
461

462
463
    m_highlight = new KDevelop::CodeHighlighting(this);

464
    new CodeCompletion(this, new CMakeCodeCompletionModel(this), name());
465
466
    
    connect(ICore::self()->projectController(), SIGNAL(projectClosing(KDevelop::IProject*)), SLOT(projectClosing(KDevelop::IProject*)));
467
468
}

469
CMakeManager::~CMakeManager()
470
{}
471

472
KUrl CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const
473
{
474
    CMakeFolderItem *fi=dynamic_cast<CMakeFolderItem*>(item);
475
    KUrl ret;
476
    ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent();
477
478
479
    if (parent)
        ret=buildDirectory(parent);
    else
480
        ret=CMake::currentBuildDir(item->project());
481
482
    
    if(fi)
483
        ret.addPath(fi->buildDir());
484
    return ret;
485
486
}

487
KDevelop::ReferencedTopDUContext CMakeManager::initializeProject(KDevelop::IProject* project)
488
{
489
    QMutexLocker locker(&m_reparsingMutex);
490
    KUrl baseUrl=CMake::projectRoot(project);
491
    
492
    QPair<VariableMap,QStringList> initials = CMakeParserUtils::initialVariables();
493
494
495
496
497
498
    CMakeProjectData* data = &m_projectsData[project];
    
    data->clear();
    data->modulePath=initials.first["CMAKE_MODULE_PATH"];
    data->vm=initials.first;
    data->vm.insert("CMAKE_SOURCE_DIR", QStringList(baseUrl.toLocalFile(KUrl::RemoveTrailingSlash)));
499
500
501
502
    
    KUrl cachefile=buildDirectory(project->projectItem());
    cachefile.addPath("CMakeCache.txt");
    data->cache=readCache(cachefile);
503

504
505
506
507
508
    KSharedConfig::Ptr cfg = project->projectConfiguration();
    KConfigGroup group(cfg.data(), "CMake");
    if(group.hasKey("CMakeDir"))
    {
        QStringList l;
509
        foreach(const QString &path, group.readEntry("CMakeDir", QStringList()) )
510
511
512
        {
            if( QFileInfo(path).exists() )
            {
513
                data->modulePath << path;
514
515
516
517
518
519
                l << path;
            }
        }
        if( !l.isEmpty() )
            group.writeEntry("CMakeDir", l);
        else
520
            group.writeEntry("CMakeDir", data->modulePath);
521
522
    }
    else
523
        group.writeEntry("CMakeDir", data->modulePath);
524

525
    
526
    KDevelop::ReferencedTopDUContext buildstrapContext;
527
    {
528
        KUrl buildStrapUrl = baseUrl;
529
        buildStrapUrl.addPath("buildstrap");
530
        DUChainWriteLocker lock(DUChain::lock());
531
532
533
534
535
536
537
538
        
        buildstrapContext = DUChain::self()->chainForDocument(buildStrapUrl);
        
        if(buildstrapContext) {
            buildstrapContext->clearLocalDeclarations();
            buildstrapContext->clearImportedParentContexts();
            buildstrapContext->deleteChildContextsRecursively();
        }else{
539
            IndexedString idxpath(buildStrapUrl);
540
            buildstrapContext=new TopDUContext(idxpath, RangeInRevision(0,0, 0,0),
541
                                               new ParsingEnvironmentFile(idxpath));
542
543
544
545
            DUChain::self()->addDocumentChain(buildstrapContext);
        }
        
        Q_ASSERT(buildstrapContext);
546
    }
547
    ReferencedTopDUContext ref=buildstrapContext;
548
    foreach(const QString& script, initials.second)
549
    {
550
        ref = includeScript(CMakeProjectVisitor::findFile(script, m_projectsData[project].modulePath, QStringList()), project, baseUrl.toLocalFile(), ref);
551
    }
552
    
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
    //Initialize parent parts of the project that don't belong to the tree (because it's a partial import)
    if(baseUrl.isParentOf(project->folder()) && baseUrl!=project->folder())
    {
        QList<KUrl> toimport;
        toimport += baseUrl;
        while(!toimport.isEmpty()) {
            KUrl script = toimport.takeFirst(), currentDir=script;
            script.addPath("CMakeLists.txt");
            
            ref = includeScript(script.toLocalFile(), project, currentDir.toLocalFile(), ref);
            Q_ASSERT(ref);
            
            foreach(const Subdirectory& s, data->subdirectories) {
                KUrl candidate = currentDir;
                candidate.addPath(s.name);
                
                if(candidate.isParentOf(project->folder()))
                    toimport += candidate;
            }
        }
        
        dynamic_cast<CMakeFolderItem*>(project->projectItem())->setBuildDir(KUrl::relativeUrl(baseUrl, project->folder()));
    }
576
    return ref;
577
578
}

579
KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project )
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
580
{
581
    CMakeFolderItem* m_rootItem=0;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
582
583
584
    KUrl cmakeInfoFile(project->projectFileUrl());
    cmakeInfoFile = cmakeInfoFile.upUrl();
    cmakeInfoFile.addPath("CMakeLists.txt");
585

586
    KUrl folderUrl=project->folder();
587
    kDebug(9042) << "file is" << cmakeInfoFile.toLocalFile();
588

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
589
590
    if ( !cmakeInfoFile.isLocalFile() )
    {
591
        kWarning() << "error. not a local file. CMake support doesn't handle remote projects";
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
592
593
594
    }
    else
    {
595
596
        KSharedConfig::Ptr cfg = project->projectConfiguration();
        KConfigGroup group(cfg.data(), "CMake");
597

598
        if(group.hasKey("ProjectRootRelative"))
599
        {
600
            QString relative=CMake::projectRootRelative(project);
601
            folderUrl.cd(relative);
602
603
604
        }
        else
        {
605
606
607
608
609
            KDialog chooseRoot;
            QWidget *e=new QWidget(&chooseRoot);
            Ui::CMakePossibleRoots ui;
            ui.setupUi(e);
            chooseRoot.setMainWidget(e);
610
            for(KUrl aux=folderUrl; QFile::exists(aux.toLocalFile()+"/CMakeLists.txt"); aux=aux.upUrl())
611
                ui.candidates->addItem(aux.toLocalFile());
612
613
614
615
616

            if(ui.candidates->count()>1)
            {
                connect(ui.candidates, SIGNAL(itemActivated(QListWidgetItem*)), &chooseRoot,SLOT(accept()));
                ui.candidates->setMinimumSize(384,192);
617
618
619
620
621
622
                int a=chooseRoot.exec();
                if(!a || !ui.candidates->currentItem())
                {
                    return 0;
                }
                KUrl choice=KUrl(ui.candidates->currentItem()->text());
623
                CMake::setProjectRootRelative(project, KUrl::relativeUrl(folderUrl, choice));
624
625
626
627
                folderUrl=choice;
            }
            else
            {
628
                CMake::setProjectRootRelative(project, "./");
629
630
            }
        }
631

632
        m_rootItem = new CMakeFolderItem(project, project->folder(), QString(), 0 );
633

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
634
        KUrl cachefile=buildDirectory(m_rootItem);
635
        if( cachefile.isEmpty() ) {
636
            CMake::checkForNeedingConfigure(m_rootItem);
637
        }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
638
        cachefile.addPath("CMakeCache.txt");
639
        
640
641
642
643
        KDirWatch* w = new KDirWatch(project);
        w->setObjectName(project->name()+"_ProjectWatcher");
        w->addFile(cachefile.toLocalFile());
        connect(w, SIGNAL(dirty(QString)), this, SLOT(dirtyFile(QString)));
644
        connect(w, SIGNAL(created(QString)), this, SLOT(dirtyFile(QString)));
645
        connect(w, SIGNAL(deleted(QString)), this, SLOT(deletedWatched(QString)));
646
        m_watchers[project] = w;
647
        Q_ASSERT(m_rootItem->rowCount()==0);
648
        cfg->sync();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
649
650
651
652
    }
    return m_rootItem;
}

653

654
KDevelop::ReferencedTopDUContext CMakeManager::includeScript(const QString& file,
655
                                                        KDevelop::IProject * project, const QString& dir, ReferencedTopDUContext parent)
656
{
657
    m_watchers[project]->addFile(file);
658
659
660
    QString profile = CMake::currentEnvironment(project);
    const KDevelop::EnvironmentGroupList env( KGlobal::config() );
    return CMakeParserUtils::includeScript( file, parent, &m_projectsData[project], dir, env.variables(profile));
661
662
}

663

664
665

QSet<QString> filterFiles(const QStringList& orig)
666
{
667
    QSet<QString> ret;
668
669
    foreach(const QString& str, orig)
    {
670
671
672
673
674
        ///@todo This filter should be configurable, and filtering should be done on a manager-independent level
        if (str.endsWith(QLatin1Char('~')) || str.endsWith(QLatin1String(".bak")))
            continue;

        ret.insert(str);
675
676
677
678
    }
    return ret;
}

679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
QStringList resolvePaths(const KUrl& baseUrl, const QStringList& pathsToResolve) {
    QStringList resolvedPaths;
    foreach(const QString& pathToResolve, pathsToResolve)
    {
        QString dir(pathToResolve);
        if(!pathToResolve.startsWith("#["))
        {
            if(KUrl( pathToResolve ).isRelative())
            {
                KUrl path(baseUrl);
                path.addPath(pathToResolve);
                dir=path.toLocalFile();
            }

            KUrl simp(dir); //We use this to simplify dir
            simp.cleanPath();
            dir=simp.toLocalFile();
        }
        resolvedPaths.append(dir);
    }
    return resolvedPaths;
}

702
QList<KDevelop::ProjectFolderItem*> CMakeManager::parse( KDevelop::ProjectFolderItem* item )
703
{
704
    Q_ASSERT(isReloading(item->project()));
705
    QList<KDevelop::ProjectFolderItem*> folderList;
706
    CMakeFolderItem* folder = dynamic_cast<CMakeFolderItem*>( item );
707

708
709
710
711
    {
        QMutexLocker locker(&m_dirWatchersMutex);
        m_watchers[item->project()]->addDir(item->url().toLocalFile(), KDirWatch::WatchFiles);
    }
712
    
713
    KUrl cmakeListsPath(item->url());
714
715
    cmakeListsPath.addPath("CMakeLists.txt");
    
716
    if(folder && QFile::exists(cmakeListsPath.toLocalFile()))
717
    {
718
        kDebug(9042) << "parse:" << folder->url();
719
        
720
721
        KDevelop::ReferencedTopDUContext curr;
        if(item==item->project()->projectItem())
722
            curr=initializeProject(item->project());
723
        else
724
725
726
            curr=folder->formerParent()->topDUContext();
        
        kDebug(9042) << "Adding cmake: " << cmakeListsPath << " to the model";
727

728
729
730
731
        QString binDir=KUrl::relativePath(folder->project()->projectItem()->url().toLocalFile(), folder->url().toLocalFile());
        if(binDir.startsWith("./"))
            binDir=binDir.remove(0, 2);
        
732
        CMakeProjectData& data=m_projectsData[item->project()];
733

734
//         kDebug(9042) << "currentBinDir" << KUrl(data.vm.value("CMAKE_BINARY_DIR")[0]) << data.vm.value("CMAKE_CURRENT_BINARY_DIR");
735
736
737
738
739
740

    #ifdef CMAKEDEBUGVISITOR
        CMakeAstDebugVisitor dv;
        dv.walk(cmakeListsPath.toLocalFile(), f, 0);
    #endif

741
742
        ReferencedTopDUContext ctx = includeScript(cmakeListsPath.toLocalFile(), folder->project(), item->url().toLocalFile(), curr);
        folder->setTopDUContext(ctx);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
743
       /*{
744
745
746
747
748
749
        kDebug() << "dumpiiiiiing" << folder->url();
        KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
        KDevelop::dumpDUContext(v.context(), false);
        }*/

        QStringList alreadyAdded;
750
        deleteAllLater(castToBase(folder->cleanupBuildFolders(data.subdirectories)));
751
752
753
754
        foreach (const Subdirectory& subf, data.subdirectories)
        {
            if(subf.name.isEmpty() || alreadyAdded.contains(subf.name)) //empty case would not be necessary if we didn't process the wrong lines
                continue;
755
            
756
757
            KUrl path(subf.name);
            if(path.isRelative())
758
            {
759
760
                path=folder->url();
                path.addPath(subf.name);
761
            }
762
            path.adjustPath(KUrl::AddTrailingSlash);
763
            
764
            if(QDir(path.toLocalFile()).exists())
765
            {
766
767
768
769
                alreadyAdded.append(subf.name);
                CMakeFolderItem* parent=folder;
                if(path.upUrl()!=folder->url())
                    parent=0;
770

771
772
                CMakeFolderItem* a = 0;
                if(ProjectFolderItem* ff = folder->folderNamed(subf.name))
773
                {
774
                    if(ff->type()!=ProjectBaseItem::BuildFolder)
775
                        deleteItemLater(ff);
776
777
                    else
                        a = static_cast<CMakeFolderItem*>(ff);
778
                    
779
                }
780
                if(!a)
781
                    a = new CMakeFolderItem( folder->project(), path, subf.build_dir, parent );
782
783
                else
                    a->setUrl(path);
784
                
785
//                 kDebug() << "folder: " << a << a->index();
786
787
788
789
790
791
792
793
794
795
                a->setDefinitions(data.definitions);
                folderList.append( a );
                
                if(!parent) {
                    m_pending[path]=a;
                    a->setFormerParent(folder);
                }

                DescriptorAttatched* datt=static_cast<DescriptorAttatched*>(a);
                datt->setDescriptor(subf.desc);
796
            }
797
        }
798

799
800
//         if(folderList.isEmpty() && path.isParentOf(item->url()))
//             kDebug() << "poor guess";
801

802
803
        QStringList directories;
        directories += folder->url().toLocalFile(KUrl::RemoveTrailingSlash);
804
805
        directories += resolvePaths(folder->url(), data.includeDirectories);
        directories.removeDuplicates();
806
        folder->setIncludeDirectories(directories);
807
//             kDebug(9042) << "setting include directories: " << folder->url() << directories << "result: " << folder->includeDirectories();
808
        folder->setDefinitions(data.definitions);
809

810
        deleteAllLater(castToBase(folder->cleanupTargets(data.targets)));
811
812
813
814
        foreach ( const Target& t, data.targets)
        {
            QStringList files=t.files;
            QString outputName=t.name;
815
816
817
818
819
            QMap< QString, QStringList > targetProps = data.properties[TargetProperty][t.name];
            if(!targetProps.isEmpty()) {
                if(targetProps.contains("OUTPUT_NAME"))
                    outputName=targetProps["OUTPUT_NAME"].first();
                else if(targetProps.contains("FOLDER") && targetProps["FOLDER"].first()=="CTestDashboardTargets")
820
821
                    continue; //filter some annoying targets
            }
822
823
824
            
            QString path;
            switch(t.type)
825
            {
826
                case Target::Library:
827
828
829
830
                    if(targetProps.contains("LIBRARY_OUTPUT_DIRECTORY"))
                        path=targetProps["LIBRARY_OUTPUT_DIRECTORY"].first();
                    else
                        path=data.vm.value("LIBRARY_OUTPUT_PATH").join(QString());
831
832
                    break;
                case Target::Executable:
833
834
835
836
                    if(targetProps.contains("RUNTIME_OUTPUT_DIRECTORY"))
                        path=targetProps["RUNTIME_OUTPUT_DIRECTORY"].first();
                    else
                        path=data.vm.value("EXECUTABLE_OUTPUT_PATH").join(QString());
837
838
839
840
841
842
843
844
845
846
847
                    break;
                case Target::Custom:
                    break;
            }
            
            KUrl resolvedPath;
            if(!path.isEmpty())
                resolvedPath=resolveSystemDirs(folder->project(), QStringList(path)).first();
            
            KDevelop::ProjectTargetItem* targetItem = folder->targetNamed(t.type, t.name);
            if (!targetItem)
848
849
850
                switch(t.type)
                {
                    case Target::Library:
851
852
                        targetItem = new CMakeLibraryTargetItem( item->project(), t.name,
                                                                folder, t.declaration, outputName, resolvedPath);
853
854
                        break;
                    case Target::Executable:
855
856
                        targetItem = new CMakeExecutableTargetItem( item->project(), t.name,
                                                                    folder, t.declaration, outputName, resolvedPath);
857
858
                        break;
                    case Target::Custom:
859
860
                        targetItem = new CMakeCustomTargetItem( item->project(), t.name,
                                                                folder, t.declaration, outputName );
861
862
                        break;
                }
863
            
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
            DefinesAttached* defAtt = dynamic_cast<DefinesAttached*>(targetItem);
            if(defAtt)
                defAtt->defineVariables(targetProps["COMPILE_DEFINITIONS"]);

            DescriptorAttatched* descAtt=dynamic_cast<DescriptorAttatched*>(targetItem);
            if(descAtt)
                descAtt->setDescriptor(t.desc);

            if(targetProps.contains("INCLUDE_DIRECTORIES")) {
                IncludesAttached* incAtt = dynamic_cast<IncludesAttached*>(targetItem);
                if(incAtt) {
                    QStringList targetIncludeDirectories = resolvePaths(folder->url(), targetProps["INCLUDE_DIRECTORIES"]);
                    targetIncludeDirectories.removeDuplicates();
                    incAtt->setIncludeDirectories(targetIncludeDirectories);
                }
            }
880
            
881
882
883
            KUrl::List tfiles;
            foreach( const QString & sFile, t.files)
            {
884
                if(sFile.startsWith("#[") || sFile.isEmpty())
885
886
887
888
889
890
                    continue;

                KUrl sourceFile(sFile);
                if(sourceFile.isRelative()) {
                    sourceFile = folder->url();
                    sourceFile.addPath( sFile );
891
                }
Milian Wolff's avatar
Milian Wolff committed
892
                sourceFile.cleanPath();
893
                tfiles += sourceFile;
894
                kDebug(9042) << "..........Adding:" << sourceFile << sFile << folder->url();
895
            }
896
897
            
            setTargetFiles(targetItem, tfiles);
898
        }
899
900
    } else if( folder ) {
        // Only do cmake-stuff if its a cmake folder
901
902
        deleteAllLater(castToBase(folder->cleanupBuildFolders(QList<Subdirectory>())));
        deleteAllLater(castToBase(folder->cleanupTargets(QList<CMakeTarget>())));
903
904
905
    } else {
        // Log non-cmake folders for debugging purposes
        kDebug(9042) << "Folder Item which is not a CMake folder parsed:" << item->url() << item->type();
906
    }
907
908
    // Use item here since folder may be 0.
    reloadFiles(item);
909

910
    return folderList;
911
}
912

913
914
915
916
917
bool containsFile(const KUrl& file, const QList<ProjectFileItem*>& tfiles)
{
    foreach(ProjectFileItem* f, tfiles) {
        if(f->url()==file)
            return true;
918
    }
919
920
    return false;
}
921

922
923
924
925
926
void CMakeManager::setTargetFiles(ProjectTargetItem* target, const KUrl::List& files)
{
    QList<ProjectFileItem*> tfiles = target->fileList();
    foreach(ProjectFileItem* file, tfiles) {
        if(!files.contains(file->url()))
927
            deleteItemLater(file);
928
929
    }
    
930
    tfiles = target->fileList(); //We need to recreate the list without the removed items
931
932
933
934
    foreach(const KUrl& file, files) {
        if(!containsFile(file, tfiles))
            new KDevelop::ProjectFileItem( target->project(), file, target );   
    }
935
936
}

937
QList<KDevelop::ProjectTargetItem*> CMakeManager::targets() const
938
{
939
    QList<KDevelop::ProjectTargetItem*> ret;
940
    foreach(IProject* p, m_watchers.keys())
941
942
943
944
    {
        ret+=p->projectItem()->targetList();
    }
    return ret;
945
946
}

947

948
KUrl::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const
949
{
950
    IncludesAttached* includer=0;
951
//     kDebug(9042) << "Querying inc dirs for " << item;
952
    while(item)
953
    {
954
955
956
957
958
        includer = dynamic_cast<IncludesAttached*>( item );
        if(includer) {
            QStringList dirs = includer->includeDirectories(item);
            return resolveSystemDirs(item->project(), dirs);
        }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
959
        item = item->parent();
960
//         kDebug(9042) << "Looking for an includer: " << item;
961
    }
962
963
    // No includer found, so no include-directories to be returned;
    return KUrl::List();
964
965
}

966
QHash< QString, QString > CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const
967
{
968
969
970
971
    DefinesAttached* att=0;
    ProjectBaseItem* it=item;
    kDebug(9042) << "Querying defines for " << item << dynamic_cast<ProjectTargetItem*>(item);
    while(!att && item)
972
    {
973
974
        att = dynamic_cast<DefinesAttached*>( item );
        it = item;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
975
        item = item->parent();
976
//         kDebug(9042) << "Looking for a folder: " << folder << item;
977
    }
978
    if( !att ) {
979
980
981
        // Not a CMake folder, so no defines to be returned;
        return QHash<QString,QString>();
    }
982

983
984
    CMakeFolderItem* folder = dynamic_cast<CMakeFolderItem*>(it);
    CMakeDefinitions defs = att->definitions(folder ? folder->formerParent() : dynamic_cast<CMakeFolderItem*>(item));
Niko Sams's avatar
Niko Sams committed
985
    //qDebug() << "lalala" << defs << it->url();
986
    return defs;
987
988
}

989
KDevelop::IProjectBuilder * CMakeManager::builder() const
990
{
991
    IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IProjectBuilder", "KDevCMakeBuilder");
992
    Q_ASSERT(i);
993
    KDevelop::IProjectBuilder* _builder = i->extension<KDevelop::IProjectBuilder>();
994
995
    Q_ASSERT(_builder );
    return _builder ;
996
}
997

998
/*void CMakeProjectManager::parseOnly(KDevelop::IProject* project, const KUrl &url)
999
{
1000
    kDebug(9042) << "Looking for" << url << " to regenerate";