cmakeproject.cpp 41.7 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "cmakeproject.h"
Tobias Hunger's avatar
Tobias Hunger committed
31
32

#include "cmakebuildconfiguration.h"
con's avatar
con committed
33
34
#include "cmakeprojectconstants.h"
#include "cmakeprojectnodes.h"
hjk's avatar
hjk committed
35
#include "cmakerunconfiguration.h"
36
#include "makestep.h"
37
#include "cmakeopenprojectwizard.h"
hjk's avatar
hjk committed
38

39
#include <projectexplorer/projectexplorerconstants.h>
dt's avatar
dt committed
40
#include <projectexplorer/projectexplorer.h>
41
#include <projectexplorer/headerpath.h>
Tobias Hunger's avatar
Tobias Hunger committed
42
#include <projectexplorer/buildsteplist.h>
dt's avatar
dt committed
43
#include <projectexplorer/buildmanager.h>
44
#include <projectexplorer/buildtargetinfo.h>
Tobias Hunger's avatar
Tobias Hunger committed
45
46
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
47
#include <projectexplorer/toolchain.h>
Tobias Hunger's avatar
Tobias Hunger committed
48
#include <projectexplorer/target.h>
49
#include <projectexplorer/deployconfiguration.h>
50
#include <projectexplorer/deploymentdata.h>
51
#include <projectexplorer/projectmacroexpander.h>
Tobias Hunger's avatar
Tobias Hunger committed
52
#include <qtsupport/customexecutablerunconfiguration.h>
53
54
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
55
#include <qtsupport/uicodemodelsupport.h>
56
#include <cpptools/cppmodelmanagerinterface.h>
dt's avatar
dt committed
57
#include <extensionsystem/pluginmanager.h>
58
#include <utils/algorithm.h>
hjk's avatar
hjk committed
59
#include <utils/qtcassert.h>
60
#include <utils/stringutils.h>
61
#include <utils/hostosinfo.h>
dt's avatar
dt committed
62
#include <coreplugin/icore.h>
63
#include <coreplugin/infobar.h>
64
#include <coreplugin/documentmanager.h>
dt's avatar
dt committed
65
#include <coreplugin/editormanager/editormanager.h>
66
#include <coreplugin/variablemanager.h>
hjk's avatar
hjk committed
67

68
69
70
#include <QDebug>
#include <QDir>
#include <QFormLayout>
71
#include <QFileSystemWatcher>
con's avatar
con committed
72
73
74

using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;
75
using namespace ProjectExplorer;
76
77
78
79
80
81
82
83
84
85

// QtCreator CMake Generator wishlist:
// Which make targets we need to build to get all executables
// What is the make we need to call
// What is the actual compiler executable
// DEFINES

// Open Questions
// Who sets up the environment for cl.exe ? INCLUDEPATH and so on

86
87
88
/*!
  \class CMakeProject
*/
con's avatar
con committed
89
CMakeProject::CMakeProject(CMakeManager *manager, const QString &fileName)
90
    : m_manager(manager),
91
      m_activeTarget(0),
92
      m_fileName(fileName),
Tobias Hunger's avatar
Tobias Hunger committed
93
94
      m_rootNode(new CMakeProjectNode(fileName)),
      m_watcher(new QFileSystemWatcher(this))
con's avatar
con committed
95
{
96
    setId(Constants::CMAKEPROJECT_ID);
97
    setProjectContext(Core::Context(CMakeProjectManager::Constants::PROJECTCONTEXT));
98
    setProjectLanguages(Core::Context(ProjectExplorer::Constants::LANG_CXX));
99

100
101
    m_projectName = QFileInfo(fileName).absoluteDir().dirName();

con's avatar
con committed
102
    m_file = new CMakeFile(this, fileName);
Tobias Hunger's avatar
Tobias Hunger committed
103

104
105
    connect(this, SIGNAL(buildTargetsChanged()),
            this, SLOT(updateRunConfigurations()));
Tobias Hunger's avatar
Tobias Hunger committed
106
107

    connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString)));
108
109
110
111
}

CMakeProject::~CMakeProject()
{
112
    m_codeModelFuture.cancel();
113
114
115
    delete m_rootNode;
}

Tobias Hunger's avatar
Tobias Hunger committed
116
void CMakeProject::fileChanged(const QString &fileName)
dt's avatar
dt committed
117
{
Tobias Hunger's avatar
Tobias Hunger committed
118
    Q_UNUSED(fileName)
dt's avatar
dt committed
119

120
    parseCMakeLists();
121
122
}

Tobias Hunger's avatar
Tobias Hunger committed
123
void CMakeProject::changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration *bc)
124
{
125
    if (!bc)
Tobias Hunger's avatar
Tobias Hunger committed
126
127
        return;

128
    CMakeBuildConfiguration *cmakebc = static_cast<CMakeBuildConfiguration *>(bc);
129

130
    // Pop up a dialog asking the user to rerun cmake
131
    QString cbpFile = CMakeManager::findCbpFile(QDir(bc->buildDirectory().toString()));
132
    QFileInfo cbpFileFi(cbpFile);
133
134
    CMakeOpenProjectWizard::Mode mode = CMakeOpenProjectWizard::Nothing;
    if (!cbpFileFi.exists()) {
135
        mode = CMakeOpenProjectWizard::NeedToCreate;
136
    } else {
tomdeblauwe's avatar
tomdeblauwe committed
137
        foreach (const QString &file, m_watchedFiles) {
138
            if (QFileInfo(file).lastModified() > cbpFileFi.lastModified()) {
139
140
141
142
143
                mode = CMakeOpenProjectWizard::NeedToUpdate;
                break;
            }
        }
    }
144

145
    if (mode != CMakeOpenProjectWizard::Nothing) {
146
        CMakeBuildInfo info(cmakebc);
147
        CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, mode, &info);
148
149
        if (copw.exec() == QDialog::Accepted)
            cmakebc->setUseNinja(copw.useNinja()); // NeedToCreate can change the Ninja setting
150
    }
151

152
    // reparse
153
    parseCMakeLists();
154
155
}

156
void CMakeProject::activeTargetWasChanged(Target *target)
157
{
158
159
160
161
162
163
164
165
    if (m_activeTarget) {
        disconnect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
                   this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*)));
    }

    m_activeTarget = target;

    if (!m_activeTarget)
166
        return;
Tobias Hunger's avatar
Tobias Hunger committed
167

168
169
170
171
    connect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
            this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*)));

    changeActiveBuildConfiguration(m_activeTarget->activeBuildConfiguration());
172
173
}

174
void CMakeProject::changeBuildDirectory(CMakeBuildConfiguration *bc, const QString &newBuildDirectory)
175
{
176
    bc->setBuildDirectory(Utils::FileName::fromString(newBuildDirectory));
177
    parseCMakeLists();
178
179
}

180
QString CMakeProject::shadowBuildDirectory(const QString &projectFilePath, const Kit *k, const QString &bcName)
181
{
182
183
184
185
186
    if (projectFilePath.isEmpty())
        return QString();
    QFileInfo info(projectFilePath);

    const QString projectName = QFileInfo(info.absolutePath()).fileName();
187
    ProjectExplorer::ProjectMacroExpander expander(projectFilePath, projectName, k, bcName);
188
    QDir projectDir = QDir(projectDirectory(Utils::FileName::fromString(projectFilePath)).toString());
189
190
    QString buildPath = Utils::expandMacros(Core::DocumentManager::buildDirectory(), &expander);
    return QDir::cleanPath(projectDir.absoluteFilePath(buildPath));
191
192
}

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
QStringList CMakeProject::getCXXFlagsFor(const CMakeBuildTarget &buildTarget)
{
    QString makeCommand = QDir::fromNativeSeparators(buildTarget.makeCommand);
    int startIndex = makeCommand.indexOf(QLatin1Char('\"'));
    int endIndex = makeCommand.indexOf(QLatin1Char('\"'), startIndex + 1);
    if (startIndex != -1 && endIndex != -1) {
        startIndex += 1;
        QString makefile = makeCommand.mid(startIndex, endIndex - startIndex);
        int slashIndex = makefile.lastIndexOf(QLatin1Char('/'));
        makefile.truncate(slashIndex);
        makefile.append(QLatin1String("/CMakeFiles/") + buildTarget.title + QLatin1String(".dir/flags.make"));
        QFile file(makefile);
        if (file.exists()) {
            file.open(QIODevice::ReadOnly | QIODevice::Text);
            QTextStream stream(&file);
            while (!stream.atEnd()) {
                QString line = stream.readLine().trimmed();
                if (line.startsWith(QLatin1String("CXX_FLAGS ="))) {
                    // Skip past =
                    return line.mid(11).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts);
                }
            }
        }
    }

    // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were
    // found
    // Get "all" target's working directory
    QString buildNinjaFile = QDir::fromNativeSeparators(buildTarget.workingDirectory);
    buildNinjaFile += QLatin1String("/build.ninja");
    QFile buildNinja(buildNinjaFile);
    if (buildNinja.exists()) {
        buildNinja.open(QIODevice::ReadOnly | QIODevice::Text);
        QTextStream stream(&buildNinja);
        bool cxxFound = false;
        while (!stream.atEnd()) {
            QString line = stream.readLine().trimmed();
            // Look for a build rule which invokes CXX_COMPILER
            if (line.startsWith(QLatin1String("build"))) {
                cxxFound = line.indexOf(QLatin1String("CXX_COMPILER")) != -1;
            } else if (cxxFound && line.startsWith(QLatin1String("FLAGS ="))) {
                // Skip past =
                return line.mid(7).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts);
            }
        }
    }
    return QStringList();
}

242
bool CMakeProject::parseCMakeLists()
243
{
Tobias Hunger's avatar
Tobias Hunger committed
244
    if (!activeTarget() ||
Tobias Hunger's avatar
Tobias Hunger committed
245
        !activeTarget()->activeBuildConfiguration()) {
246
        return false;
Tobias Hunger's avatar
Tobias Hunger committed
247
    }
Tobias Hunger's avatar
Tobias Hunger committed
248

249
    CMakeBuildConfiguration *activeBC = static_cast<CMakeBuildConfiguration *>(activeTarget()->activeBuildConfiguration());
hjk's avatar
hjk committed
250
    foreach (Core::IDocument *document, Core::DocumentModel::openedDocuments())
251
        if (isProjectFile(document->filePath()))
252
            document->infoBar()->removeInfo("CMakeEditor.RunCMake");
253

254
    // Find cbp file
255
    QString cbpFile = CMakeManager::findCbpFile(activeBC->buildDirectory().toString());
256

257
258
    if (cbpFile.isEmpty()) {
        emit buildTargetsChanged();
259
        return false;
260
261
    }

262
    // setFolderName
263
    m_rootNode->setDisplayName(QFileInfo(cbpFile).completeBaseName());
264
    CMakeCbpParser cbpparser;
265
266
    // Parsing
    //qDebug()<<"Parsing file "<<cbpFile;
267
    if (!cbpparser.parseCbpFile(cbpFile, projectDirectory().toString())) {
Tobias Hunger's avatar
Tobias Hunger committed
268
269
        // TODO report error
        emit buildTargetsChanged();
270
        return false;
Tobias Hunger's avatar
Tobias Hunger committed
271
    }
272

273
274
275
276
    foreach (const QString &file, m_watcher->files())
        if (file != cbpFile)
            m_watcher->removePath(file);

277
    // how can we ensure that it is completely written?
278
279
    m_watcher->addPath(cbpFile);

Tobias Hunger's avatar
Tobias Hunger committed
280
    m_projectName = cbpparser.projectName();
281
    m_rootNode->setDisplayName(cbpparser.projectName());
282

Tobias Hunger's avatar
Tobias Hunger committed
283
284
285
286
287
    //qDebug()<<"Building Tree";
    QList<ProjectExplorer::FileNode *> fileList = cbpparser.fileList();
    QSet<QString> projectFiles;
    if (cbpparser.hasCMakeFiles()) {
        fileList.append(cbpparser.cmakeFileList());
tomdeblauwe's avatar
tomdeblauwe committed
288
        foreach (const ProjectExplorer::FileNode *node, cbpparser.cmakeFileList())
Tobias Hunger's avatar
Tobias Hunger committed
289
290
291
            projectFiles.insert(node->path());
    } else {
        // Manually add the CMakeLists.txt file
292
        QString cmakeListTxt = projectDirectory().toString() + QLatin1String("/CMakeLists.txt");
293
294
        bool generated = false;
        fileList.append(new ProjectExplorer::FileNode(cmakeListTxt, ProjectExplorer::ProjectFileType, generated));
Tobias Hunger's avatar
Tobias Hunger committed
295
296
        projectFiles.insert(cmakeListTxt);
    }
297

Tobias Hunger's avatar
Tobias Hunger committed
298
    m_watchedFiles = projectFiles;
299

Tobias Hunger's avatar
Tobias Hunger committed
300
301
302
303
    m_files.clear();
    foreach (ProjectExplorer::FileNode *fn, fileList)
        m_files.append(fn->path());
    m_files.sort();
con's avatar
con committed
304

Tobias Hunger's avatar
Tobias Hunger committed
305
    buildTree(m_rootNode, fileList);
306

Tobias Hunger's avatar
Tobias Hunger committed
307
308
    //qDebug()<<"Adding Targets";
    m_buildTargets = cbpparser.buildTargets();
dt's avatar
dt committed
309
//        qDebug()<<"Printing targets";
tomdeblauwe's avatar
tomdeblauwe committed
310
//        foreach (CMakeBuildTarget ct, m_buildTargets) {
dt's avatar
dt committed
311
312
313
314
315
//            qDebug()<<ct.title<<" with executable:"<<ct.executable;
//            qDebug()<<"WD:"<<ct.workingDirectory;
//            qDebug()<<ct.makeCommand<<ct.makeCleanCommand;
//            qDebug()<<"";
//        }
dt's avatar
dt committed
316

317
    updateApplicationAndDeploymentTargets();
dt's avatar
dt committed
318
319

    createUiCodeModelSupport();
320

321
322
    Kit *k = activeTarget()->kit();
    ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(k);
Tobias Hunger's avatar
Tobias Hunger committed
323
    if (!tc) {
324
325
326
        emit buildTargetsChanged();
        emit fileListChanged();
        return true;
Tobias Hunger's avatar
Tobias Hunger committed
327
    }
328

329
330
    CppTools::CppModelManagerInterface *modelmanager =
            CppTools::CppModelManagerInterface::instance();
Tobias Hunger's avatar
Tobias Hunger committed
331
    if (modelmanager) {
332
        CppTools::ProjectInfo pinfo = modelmanager->projectInfo(this);
333
334
        pinfo.clearProjectParts();

335
        CppTools::ProjectPartBuilder ppBuilder(pinfo);
336

337
        foreach (const CMakeBuildTarget &cbt, m_buildTargets) {
338
            // This explicitly adds -I. to the include paths
339
340
341
342
343
344
345
346
347
348
349
            QStringList includePaths = cbt.includeFiles;
            includePaths += projectDirectory().toString();
            ppBuilder.setIncludePaths(includePaths);
            ppBuilder.setCFlags(getCXXFlagsFor(cbt));
            ppBuilder.setCxxFlags(getCXXFlagsFor(cbt));
            ppBuilder.setDefines(cbt.defines);
            ppBuilder.setDisplayName(cbt.title);

            const QList<Core::Id> languages = ppBuilder.createProjectPartsForFiles(cbt.files);
            foreach (Core::Id language, languages)
                setProjectLanguage(language, true);
350
        }
351

352
        m_codeModelFuture.cancel();
353
        m_codeModelFuture = modelmanager->updateProjectInfo(pinfo);
Tobias Hunger's avatar
Tobias Hunger committed
354
    }
355

356
    emit displayNameChanged();
357
    emit buildTargetsChanged();
358
    emit fileListChanged();
Tobias Hunger's avatar
Tobias Hunger committed
359

360
361
    emit activeBC->emitBuildTypeChanged();

362
    return true;
con's avatar
con committed
363
364
}

365
366
367
368
369
bool CMakeProject::isProjectFile(const QString &fileName)
{
    return m_watchedFiles.contains(fileName);
}

Tobias Hunger's avatar
Tobias Hunger committed
370
371
372
373
374
QList<CMakeBuildTarget> CMakeProject::buildTargets() const
{
    return m_buildTargets;
}

375
QStringList CMakeProject::buildTargetTitles(bool runnable) const
con's avatar
con committed
376
{
377
    QStringList results;
378
    foreach (const CMakeBuildTarget &ct, m_buildTargets) {
379
        if (runnable && (ct.executable.isEmpty() || ct.library))
380
            continue;
381
        results << ct.title;
382
    }
383
    return results;
con's avatar
con committed
384
385
}

386
bool CMakeProject::hasBuildTarget(const QString &title) const
387
{
388
    foreach (const CMakeBuildTarget &ct, m_buildTargets) {
389
390
391
392
393
394
        if (ct.title == title)
            return true;
    }
    return false;
}

395
void CMakeProject::gatherFileNodes(ProjectExplorer::FolderNode *parent, QList<ProjectExplorer::FileNode *> &list)
con's avatar
con committed
396
{
tomdeblauwe's avatar
tomdeblauwe committed
397
    foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes())
398
        gatherFileNodes(folder, list);
tomdeblauwe's avatar
tomdeblauwe committed
399
    foreach (ProjectExplorer::FileNode *file, parent->fileNodes())
400
401
402
        list.append(file);
}

403
404
405
406
407
bool sortNodesByPath(Node *a, Node *b)
{
    return a->path() < b->path();
}

408
409
410
411
412
void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> newList)
{
    // Gather old list
    QList<ProjectExplorer::FileNode *> oldList;
    gatherFileNodes(rootNode, oldList);
413
414
    Utils::sort(oldList, sortNodesByPath);
    Utils::sort(newList, sortNodesByPath);
415
416
417
418

    QList<ProjectExplorer::FileNode *> added;
    QList<ProjectExplorer::FileNode *> deleted;

419
    ProjectExplorer::compareSortedLists(oldList, newList, deleted, added, sortNodesByPath);
420

421
    qDeleteAll(ProjectExplorer::subtractSortedList(newList, added, sortNodesByPath));
422
423
424
425

    // add added nodes
    foreach (ProjectExplorer::FileNode *fn, added) {
//        qDebug()<<"added"<<fn->path();
con's avatar
con committed
426
427
428
        // Get relative path to rootNode
        QString parentDir = QFileInfo(fn->path()).absolutePath();
        ProjectExplorer::FolderNode *folder = findOrCreateFolder(rootNode, parentDir);
429
        folder->addFileNodes(QList<ProjectExplorer::FileNode *>()<< fn);
con's avatar
con committed
430
    }
431

Tobias Hunger's avatar
Tobias Hunger committed
432
    // remove old file nodes and check whether folder nodes can be removed
433
434
435
    foreach (ProjectExplorer::FileNode *fn, deleted) {
        ProjectExplorer::FolderNode *parent = fn->parentFolderNode();
//        qDebug()<<"removed"<<fn->path();
436
        parent->removeFileNodes(QList<ProjectExplorer::FileNode *>() << fn);
437
438
439
        // Check for empty parent
        while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) {
            ProjectExplorer::FolderNode *grandparent = parent->parentFolderNode();
440
            grandparent->removeFolderNodes(QList<ProjectExplorer::FolderNode *>() << parent);
441
442
443
444
445
            parent = grandparent;
            if (parent == rootNode)
                break;
        }
    }
con's avatar
con committed
446
447
448
449
450
}

ProjectExplorer::FolderNode *CMakeProject::findOrCreateFolder(CMakeProjectNode *rootNode, QString directory)
{
    QString relativePath = QDir(QFileInfo(rootNode->path()).path()).relativeFilePath(directory);
451
    QStringList parts = relativePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
con's avatar
con committed
452
    ProjectExplorer::FolderNode *parent = rootNode;
453
    QString path = QFileInfo(rootNode->path()).path();
hjk's avatar
hjk committed
454
    foreach (const QString &part, parts) {
455
456
        path += QLatin1Char('/');
        path += part;
con's avatar
con committed
457
458
        // Find folder in subFolders
        bool found = false;
hjk's avatar
hjk committed
459
        foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes()) {
460
            if (folder->path() == path) {
con's avatar
con committed
461
462
463
464
465
466
467
468
                // yeah found something :)
                parent = folder;
                found = true;
                break;
            }
        }
        if (!found) {
            // No FolderNode yet, so create it
469
            ProjectExplorer::FolderNode *tmp = new ProjectExplorer::FolderNode(path);
470
            tmp->setDisplayName(part);
471
            parent->addFolderNodes(QList<ProjectExplorer::FolderNode *>() << tmp);
con's avatar
con committed
472
473
474
475
476
477
            parent = tmp;
        }
    }
    return parent;
}

478
QString CMakeProject::displayName() const
con's avatar
con committed
479
{
480
    return m_projectName;
con's avatar
con committed
481
482
}

483
Core::IDocument *CMakeProject::document() const
con's avatar
con committed
484
485
486
487
{
    return m_file;
}

488
CMakeManager *CMakeProject::projectManager() const
con's avatar
con committed
489
490
491
492
493
494
495
496
497
498
499
500
{
    return m_manager;
}

ProjectExplorer::ProjectNode *CMakeProject::rootProjectNode() const
{
    return m_rootNode;
}


QStringList CMakeProject::files(FilesMode fileMode) const
{
501
    Q_UNUSED(fileMode)
con's avatar
con committed
502
503
504
    return m_files;
}

505
bool CMakeProject::fromMap(const QVariantMap &map)
con's avatar
con committed
506
{
507
508
    if (!Project::fromMap(map))
        return false;
con's avatar
con committed
509

510
511
    bool hasUserFile = activeTarget();
    if (!hasUserFile) {
512
        CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, projectDirectory().toString(), Utils::Environment::systemEnvironment());
513
514
        if (copw.exec() != QDialog::Accepted)
            return false;
515
516
517
        Kit *k = copw.kit();
        Target *t = new Target(this, k);
        CMakeBuildConfiguration *bc(new CMakeBuildConfiguration(t));
518
        bc->setDefaultDisplayName(QLatin1String("all"));
519
        bc->setUseNinja(copw.useNinja());
520
        bc->setBuildDirectory(Utils::FileName::fromString(copw.buildDirectory()));
521
522
523
524
525
526
527
528
        ProjectExplorer::BuildStepList *buildSteps = bc->stepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
        ProjectExplorer::BuildStepList *cleanSteps = bc->stepList(ProjectExplorer::Constants::BUILDSTEPS_CLEAN);

        // Now create a standard build configuration
        buildSteps->insertStep(0, new MakeStep(buildSteps));

        MakeStep *cleanMakeStep = new MakeStep(cleanSteps);
        cleanSteps->insertStep(0, cleanMakeStep);
529
        cleanMakeStep->setAdditionalArguments(QLatin1String("clean"));
530
531
532
533
        cleanMakeStep->setClean(true);

        t->addBuildConfiguration(bc);

534
        t->updateDefaultDeployConfigurations();
535
536
537
538
539
540
541
542
543

        addTarget(t);
    } else {
        // We have a user file, but we could still be missing the cbp file
        // or simply run createXml with the saved settings
        QFileInfo sourceFileInfo(m_fileName);
        CMakeBuildConfiguration *activeBC = qobject_cast<CMakeBuildConfiguration *>(activeTarget()->activeBuildConfiguration());
        if (!activeBC)
            return false;
544
        QString cbpFile = CMakeManager::findCbpFile(QDir(activeBC->buildDirectory().toString()));
545
546
547
548
549
550
551
552
553
        QFileInfo cbpFileFi(cbpFile);

        CMakeOpenProjectWizard::Mode mode = CMakeOpenProjectWizard::Nothing;
        if (!cbpFileFi.exists())
            mode = CMakeOpenProjectWizard::NeedToCreate;
        else if (cbpFileFi.lastModified() < sourceFileInfo.lastModified())
            mode = CMakeOpenProjectWizard::NeedToUpdate;

        if (mode != CMakeOpenProjectWizard::Nothing) {
554
            CMakeBuildInfo info(activeBC);
555
            CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, mode, &info);
556
557
558
559
560
            if (copw.exec() != QDialog::Accepted)
                return false;
            else
                activeBC->setUseNinja(copw.useNinja());
        }
dt's avatar
dt committed
561
    }
562

563
    parseCMakeLists();
564

565
566
567
568
569
570
571
572
    m_activeTarget = activeTarget();
    if (m_activeTarget)
        connect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
                this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*)));

    connect(this, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)),
            this, SLOT(activeTargetWasChanged(ProjectExplorer::Target*)));

dt's avatar
dt committed
573
    return true;
con's avatar
con committed
574
575
}

576
577
bool CMakeProject::setupTarget(Target *t)
{
578
    t->updateDefaultBuildConfigurations();
579
580
    if (t->buildConfigurations().isEmpty())
        return false;
581
    t->updateDefaultDeployConfigurations();
582
583
584
585

    return true;
}

586
CMakeBuildTarget CMakeProject::buildTargetForTitle(const QString &title)
587
{
tomdeblauwe's avatar
tomdeblauwe committed
588
    foreach (const CMakeBuildTarget &ct, m_buildTargets)
589
590
        if (ct.title == title)
            return ct;
591
    return CMakeBuildTarget();
592
593
}

dt's avatar
dt committed
594
595
QString CMakeProject::uiHeaderFile(const QString &uiFile)
{
596
    QFileInfo fi(uiFile);
597
    Utils::FileName project = projectDirectory();
598
599
600
601
602
603
604
605
606
607
608
609
610
611
    Utils::FileName baseDirectory = Utils::FileName::fromString(fi.absolutePath());

    while (baseDirectory.isChildOf(project)) {
        Utils::FileName cmakeListsTxt = baseDirectory;
        cmakeListsTxt.appendPath(QLatin1String("CMakeLists.txt"));
        if (cmakeListsTxt.toFileInfo().exists())
            break;
        QDir dir(baseDirectory.toString());
        dir.cdUp();
        baseDirectory = Utils::FileName::fromString(dir.absolutePath());
    }

    QDir srcDirRoot = QDir(project.toString());
    QString relativePath = srcDirRoot.relativeFilePath(baseDirectory.toString());
612
    QDir buildDir = QDir(activeTarget()->activeBuildConfiguration()->buildDirectory().toString());
dt's avatar
dt committed
613
614
615
616
    QString uiHeaderFilePath = buildDir.absoluteFilePath(relativePath);
    uiHeaderFilePath += QLatin1String("/ui_");
    uiHeaderFilePath += fi.completeBaseName();
    uiHeaderFilePath += QLatin1String(".h");
617

dt's avatar
dt committed
618
619
620
    return QDir::cleanPath(uiHeaderFilePath);
}

621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
void CMakeProject::updateRunConfigurations()
{
    foreach (Target *t, targets())
        updateRunConfigurations(t);
}

// TODO Compare with updateDefaultRunConfigurations();
void CMakeProject::updateRunConfigurations(Target *t)
{
    // *Update* runconfigurations:
    QMultiMap<QString, CMakeRunConfiguration*> existingRunConfigurations;
    QList<ProjectExplorer::RunConfiguration *> toRemove;
    foreach (ProjectExplorer::RunConfiguration *rc, t->runConfigurations()) {
        if (CMakeRunConfiguration* cmakeRC = qobject_cast<CMakeRunConfiguration *>(rc))
            existingRunConfigurations.insert(cmakeRC->title(), cmakeRC);
        QtSupport::CustomExecutableRunConfiguration *ceRC =
                qobject_cast<QtSupport::CustomExecutableRunConfiguration *>(rc);
        if (ceRC && !ceRC->isConfigured())
            toRemove << rc;
    }

    foreach (const CMakeBuildTarget &ct, buildTargets()) {
        if (ct.library)
            continue;
        if (ct.executable.isEmpty())
            continue;
        QList<CMakeRunConfiguration *> list = existingRunConfigurations.values(ct.title);
        if (!list.isEmpty()) {
            // Already exists, so override the settings...
            foreach (CMakeRunConfiguration *rc, list) {
                rc->setExecutable(ct.executable);
                rc->setBaseWorkingDirectory(ct.workingDirectory);
                rc->setEnabled(true);
            }
            existingRunConfigurations.remove(ct.title);
        } else {
            // Does not exist yet
            Core::Id id = CMakeRunConfigurationFactory::idFromBuildTarget(ct.title);
            CMakeRunConfiguration *rc = new CMakeRunConfiguration(t, id, ct.executable,
                                                                  ct.workingDirectory, ct.title);
            t->addRunConfiguration(rc);
        }
    }
    QMultiMap<QString, CMakeRunConfiguration *>::const_iterator it =
            existingRunConfigurations.constBegin();
    for ( ; it != existingRunConfigurations.constEnd(); ++it) {
        CMakeRunConfiguration *rc = it.value();
        // The executables for those runconfigurations aren't build by the current buildconfiguration
        // We just set a disable flag and show that in the display name
        rc->setEnabled(false);
        // removeRunConfiguration(rc);
    }

    foreach (ProjectExplorer::RunConfiguration *rc, toRemove)
        t->removeRunConfiguration(rc);

    if (t->runConfigurations().isEmpty()) {
        // Oh no, no run configuration,
        // create a custom executable run configuration
        t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t));
    }
}

684
685
686
687
688
689
690
691
692
void CMakeProject::updateApplicationAndDeploymentTargets()
{
    Target *t = activeTarget();

    QFile deploymentFile;
    QTextStream deploymentStream;
    QString deploymentPrefix;
    QDir sourceDir;

693
    sourceDir.setPath(t->project()->projectDirectory().toString());
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
    deploymentFile.setFileName(sourceDir.filePath(QLatin1String("QtCreatorDeployment.txt")));
    if (deploymentFile.open(QFile::ReadOnly | QFile::Text)) {
        deploymentStream.setDevice(&deploymentFile);
        deploymentPrefix = deploymentStream.readLine();
        if (!deploymentPrefix.endsWith(QLatin1Char('/')))
            deploymentPrefix.append(QLatin1Char('/'));
    }

    BuildTargetInfoList appTargetList;
    DeploymentData deploymentData;
    QDir buildDir(t->activeBuildConfiguration()->buildDirectory().toString());
    foreach (const CMakeBuildTarget &ct, m_buildTargets) {
        if (ct.executable.isEmpty())
            continue;

        deploymentData.addFile(ct.executable, deploymentPrefix + buildDir.relativeFilePath(QFileInfo(ct.executable).dir().path()), DeployableFile::TypeExecutable);
        if (!ct.library) {
            // TODO: Put a path to corresponding .cbp file into projectFilePath?
            appTargetList.list << BuildTargetInfo(ct.executable, ct.executable);
        }
    }

    QString absoluteSourcePath = sourceDir.absolutePath();
    if (!absoluteSourcePath.endsWith(QLatin1Char('/')))
        absoluteSourcePath.append(QLatin1Char('/'));
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
719
720
    if (deploymentStream.device()) {
        while (!deploymentStream.atEnd()) {
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
721
722
723
724
            QString line = deploymentStream.readLine();
            if (!line.contains(QLatin1Char(':')))
                continue;
            QStringList file = line.split(QLatin1Char(':'));
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
725
726
            deploymentData.addFile(absoluteSourcePath + file.at(0), deploymentPrefix + file.at(1));
        }
727
728
729
730
731
732
    }

    t->setApplicationTargets(appTargetList);
    t->setDeploymentData(deploymentData);
}

dt's avatar
dt committed
733
734
void CMakeProject::createUiCodeModelSupport()
{
Tobias Hunger's avatar
Tobias Hunger committed
735
    QHash<QString, QString> uiFileHash;
dt's avatar
dt committed
736
737
738

    // Find all ui files
    foreach (const QString &uiFile, m_files) {
Tobias Hunger's avatar
Tobias Hunger committed
739
740
        if (uiFile.endsWith(QLatin1String(".ui")))
            uiFileHash.insert(uiFile, uiHeaderFile(uiFile));
dt's avatar
dt committed
741
742
    }

743
    QtSupport::UiCodeModelManager::update(this, uiFileHash);
dt's avatar
dt committed
744
745
}

746
747
// CMakeFile

con's avatar
con committed
748
CMakeFile::CMakeFile(CMakeProject *parent, QString fileName)
749
    : Core::IDocument(parent), m_project(parent)
con's avatar
con committed
750
{
751
    setId("Cmake.ProjectFile");
752
    setMimeType(QLatin1String(Constants::CMAKEPROJECTMIMETYPE));
753
    setFilePath(fileName);
con's avatar
con committed
754
755
}

756
bool CMakeFile::save(QString *errorString, const QString &fileName, bool autoSave)
con's avatar
con committed
757
758
759
{
    // Once we have an texteditor open for this file, we probably do
    // need to implement this, don't we.
760
    Q_UNUSED(errorString)
761
    Q_UNUSED(fileName)
762
    Q_UNUSED(autoSave)
con's avatar
con committed
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
    return false;
}

QString CMakeFile::defaultPath() const
{
    return QString();
}

QString CMakeFile::suggestedFileName() const
{
    return QString();
}

bool CMakeFile::isModified() const
{
    return false;
}

bool CMakeFile::isSaveAsAllowed() const
{
    return false;
}

786
Core::IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
con's avatar
con committed
787
{
788
789
790
791
792
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
}

793
bool CMakeFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
794
{
795
    Q_UNUSED(errorString)
796
797
    Q_UNUSED(flag)
    Q_UNUSED(type)
798
    return true;
con's avatar
con committed
799
800
}

801
CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) : m_buildConfiguration(0)
con's avatar
con committed
802
{
803
    QFormLayout *fl = new QFormLayout(this);
804
    fl->setContentsMargins(20, -1, 0, -1);
805
    fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
806
    setLayout(fl);
807

808
    QPushButton *runCmakeButton = new QPushButton(tr("Run CMake..."));
809
810
811
812
    connect(runCmakeButton, SIGNAL(clicked()),
            this, SLOT(runCMake()));
    fl->addRow(tr("Reconfigure project:"), runCmakeButton);

813
814
815
816
817
818
819
820
821
822
823
    m_pathLineEdit = new QLineEdit(this);
    m_pathLineEdit->setReadOnly(true);

    QHBoxLayout *hbox = new QHBoxLayout();
    hbox->addWidget(m_pathLineEdit);

    m_changeButton = new QPushButton(this);
    m_changeButton->setText(tr("&Change"));
    connect(m_changeButton, SIGNAL(clicked()), this, SLOT(openChangeBuildDirectoryDialog()));
    hbox->addWidget(m_changeButton);

824
    fl->addRow(tr("Build directory:"), hbox);
con's avatar
con committed
825

826
    m_buildConfiguration = bc;
827
    m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString());
828
    if (m_buildConfiguration->buildDirectory() == bc->target()->project()->projectDirectory())
829
830
        m_changeButton->setEnabled(false);
    else
831
        m_changeButton->setEnabled(true);
832

833
    setDisplayName(tr("CMake"));
834
835
}

836
void CMakeBuildSettingsWidget::openChangeBuildDirectoryDialog()
837
{
838
    CMakeProject *project = static_cast<CMakeProject *>(m_buildConfiguration->target()->project());
839
    CMakeBuildInfo info(m_buildConfiguration);
840
841
    CMakeOpenProjectWizard copw(Core::ICore::mainWindow(),
                                project->projectManager(), CMakeOpenProjectWizard::ChangeDirectory,
842
                                &info);
843
    if (copw.exec() == QDialog::Accepted) {
844
        project->changeBuildDirectory(m_buildConfiguration, copw.buildDirectory());
845
        m_buildConfiguration->setUseNinja(copw.useNinja());
846
        m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString());
847
    }
848
849
}

850
851
void CMakeBuildSettingsWidget::runCMake()
{
852
853
    if (!ProjectExplorer::ProjectExplorerPlugin::instance()->saveModifiedFiles())
        return;
854
    CMakeProject *project = static_cast<CMakeProject *>(m_buildConfiguration->target()->project());
855
    CMakeBuildInfo info(m_buildConfiguration);
856
857
    CMakeOpenProjectWizard copw(Core::ICore::mainWindow(),
                                project->projectManager(),
858
                                CMakeOpenProjectWizard::WantToUpdate, &info);
Tobias Hunger's avatar
Tobias Hunger committed
859
    if (copw.exec() == QDialog::Accepted)
860
        project->parseCMakeLists();
861
862
}

863
864
865
866
/////
// CMakeCbpParser
////

867
868
869
870
871
// called after everything is parsed
// this function tries to figure out to which CMakeBuildTarget
// each file belongs, so that it gets the appropriate defines and
// compiler flags
void CMakeCbpParser::sortFiles()
872
{
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
    QList<Utils::FileName> fileNames = Utils::transform(m_fileList, [] (FileNode *node) {
        return Utils::FileName::fromString(node->path());
    });

    Utils::sort(fileNames);


    CMakeBuildTarget *last = 0;
    Utils::FileName parentDirectory;

    foreach (const Utils::FileName &fileName, fileNames) {
        if (fileName.parentDir() == parentDirectory && last) {
            // easy case, same parent directory as last file
            last->files.append(fileName.toString());
        } else {
            int bestLength = -1;
            int bestIndex = -1;

            for (int i = 0; i < m_buildTargets.size(); ++i) {
                const CMakeBuildTarget &target = m_buildTargets.at(i);
                if (fileName.isChildOf(Utils::FileName::fromString(target.sourceDirectory))
                                       && target.sourceDirectory.size() > bestLength) {
                    bestLength = target.sourceDirectory.size();
                    bestIndex = i;
                }
            }

            if (bestIndex == -1 && !m_buildTargets.isEmpty())
                bestIndex = 0;

            if (bestIndex != -1) {
                m_buildTargets[bestIndex].files.append(fileName.toString());
                last = &m_buildTargets[bestIndex];
                parentDirectory = fileName.parentDir();
            }
        }
    }
}

bool CMakeCbpParser::parseCbpFile(const QString &fileName, const QString &sourceDirectory)
{
    m_buildDirectory = QFileInfo(fileName).absolutePath();
    m_sourceDirectory = sourceDirectory;

917
918
919
920
    QFile fi(fileName);
    if (fi.exists() && fi.open(QFile::ReadOnly)) {
        setDevice(&fi);

hjk's avatar
hjk committed
921
        while (!atEnd()) {
922
            readNext();
923
            if (name() == QLatin1String("CodeBlocks_project_file"))
924
                parseCodeBlocks_project_file();
925
            else if (isStartElement())
926
927
                parseUnknownElement();
        }
928
929
930

        sortFiles();

931
932
933
934
935
936
937
938
        fi.close();
        return true;
    }
    return false;
}

void CMakeCbpParser::parseCodeBlocks_project_file()
{
hjk's avatar
hjk committed
939
    while (!atEnd()) {
940
        readNext();
941
        if (isEndElement())
942
            return;
943
        else if (name() == QLatin1String("Project"))
944
            parseProject();
945
        else if (isStartElement())
946
947
948
949
950
951
            parseUnknownElement();
    }
}

void CMakeCbpParser::parseProject()
{
hjk's avatar
hjk committed
952
    while (!atEnd()) {
953
        readNext();
954
        if (isEndElement())
955
            return;
956
        else if (name() == QLatin1String("Option"))
957
            parseOption();
958
        else if (name() == QLatin1String("Unit"))
959
            parseUnit();
960
        else if (name() == QLatin1String("Build"))
961
            parseBuild();
962
        else if (isStartElement())
963
964
965
966
967
968
            parseUnknownElement();
    }
}

void CMakeCbpParser::parseBuild()
{
hjk's avatar
hjk committed
969
    while (!atEnd()) {
970
        readNext();
971
        if (isEndElement())
972
            return;
973
        else if (name() == QLatin1String("Target"))
974
            parseBuildTarget();
975
        else if (isStartElement())
976
977
978
979
            parseUnknownElement();
    }
}

980
void CMakeCbpParser::parseBuildTarget()
981
{
982
    m_buildTarget.clear();
dt's avatar
dt committed
983

984
985
    if <