cmakeproject.cpp 43 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 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
        foreach (const CMakeBuildTarget &cbt, m_buildTargets) {
            typedef CppTools::ProjectPart ProjectPart;
            ProjectPart::Ptr part(new ProjectPart);
            part->project = this;
            part->displayName = cbt.title;
            part->projectFile = projectFilePath().toString();

            // This explicitly adds -I. to the include paths
            part->headerPaths += ProjectPart::HeaderPath(projectDirectory().toString(),
                                                         ProjectPart::HeaderPath::IncludePath);

            foreach (const QString &includeFile, cbt.includeFiles) {
                ProjectPart::HeaderPath hp(includeFile, ProjectPart::HeaderPath::IncludePath);

                // CodeBlocks is utterly ignorant of frameworks on Mac, and won't report framework
                // paths. The work-around is to check if the include path ends in ".framework", and
                // if so, add the parent directory as framework path.
                if (includeFile.endsWith(QLatin1String(".framework"))) {
                    const int slashIdx = includeFile.lastIndexOf(QLatin1Char('/'));
                    if (slashIdx != -1) {
                        hp = ProjectPart::HeaderPath(includeFile.left(slashIdx),
                                                     ProjectPart::HeaderPath::FrameworkPath);
                        continue;
                    }
359
                }
360 361

                part->headerPaths += hp;
362 363
            }

364
            part->projectDefines += cbt.defines;
365

366 367 368 369
            // TODO rewrite
            CppTools::ProjectFileAdder adder(part->files);
            foreach (const QString &file, cbt.files)
                adder.maybeAdd(file);
370

371
            QStringList cxxflags = getCXXFlagsFor(cbt);
372

373 374 375 376
            part->evaluateToolchain(tc,
                                    cxxflags,
                                    cxxflags,
                                    SysRootKitInformation::sysRoot(k));
377

378 379 380
            setProjectLanguage(ProjectExplorer::Constants::LANG_CXX, !part->files.isEmpty());
            pinfo.appendProjectPart(part);
        }
381
        m_codeModelFuture.cancel();
382
        m_codeModelFuture = modelmanager->updateProjectInfo(pinfo);
383

Tobias Hunger's avatar
Tobias Hunger committed
384
    }
385

386
    emit displayNameChanged();
387
    emit buildTargetsChanged();
388
    emit fileListChanged();
Tobias Hunger's avatar
Tobias Hunger committed
389

390 391
    emit activeBC->emitBuildTypeChanged();

392
    return true;
con's avatar
con committed
393 394
}

395 396 397 398 399
bool CMakeProject::isProjectFile(const QString &fileName)
{
    return m_watchedFiles.contains(fileName);
}

Tobias Hunger's avatar
Tobias Hunger committed
400 401 402 403 404
QList<CMakeBuildTarget> CMakeProject::buildTargets() const
{
    return m_buildTargets;
}

405
QStringList CMakeProject::buildTargetTitles(bool runnable) const
con's avatar
con committed
406
{
407
    QStringList results;
408
    foreach (const CMakeBuildTarget &ct, m_buildTargets) {
409
        if (runnable && (ct.executable.isEmpty() || ct.library))
410
            continue;
411
        results << ct.title;
412
    }
413
    return results;
con's avatar
con committed
414 415
}

416
bool CMakeProject::hasBuildTarget(const QString &title) const
417
{
418
    foreach (const CMakeBuildTarget &ct, m_buildTargets) {
419 420 421 422 423 424
        if (ct.title == title)
            return true;
    }
    return false;
}

425
void CMakeProject::gatherFileNodes(ProjectExplorer::FolderNode *parent, QList<ProjectExplorer::FileNode *> &list)
con's avatar
con committed
426
{
tomdeblauwe's avatar
tomdeblauwe committed
427
    foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes())
428
        gatherFileNodes(folder, list);
tomdeblauwe's avatar
tomdeblauwe committed
429
    foreach (ProjectExplorer::FileNode *file, parent->fileNodes())
430 431 432
        list.append(file);
}

433 434 435 436 437
bool sortNodesByPath(Node *a, Node *b)
{
    return a->path() < b->path();
}

438 439 440 441 442
void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> newList)
{
    // Gather old list
    QList<ProjectExplorer::FileNode *> oldList;
    gatherFileNodes(rootNode, oldList);
443 444
    Utils::sort(oldList, sortNodesByPath);
    Utils::sort(newList, sortNodesByPath);
445 446 447 448

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

449
    ProjectExplorer::compareSortedLists(oldList, newList, deleted, added, sortNodesByPath);
450

451
    qDeleteAll(ProjectExplorer::subtractSortedList(newList, added, sortNodesByPath));
452 453 454 455

    // add added nodes
    foreach (ProjectExplorer::FileNode *fn, added) {
//        qDebug()<<"added"<<fn->path();
con's avatar
con committed
456 457 458
        // Get relative path to rootNode
        QString parentDir = QFileInfo(fn->path()).absolutePath();
        ProjectExplorer::FolderNode *folder = findOrCreateFolder(rootNode, parentDir);
459
        folder->addFileNodes(QList<ProjectExplorer::FileNode *>()<< fn);
con's avatar
con committed
460
    }
461

Tobias Hunger's avatar
Tobias Hunger committed
462
    // remove old file nodes and check whether folder nodes can be removed
463 464 465
    foreach (ProjectExplorer::FileNode *fn, deleted) {
        ProjectExplorer::FolderNode *parent = fn->parentFolderNode();
//        qDebug()<<"removed"<<fn->path();
466
        parent->removeFileNodes(QList<ProjectExplorer::FileNode *>() << fn);
467 468 469
        // Check for empty parent
        while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) {
            ProjectExplorer::FolderNode *grandparent = parent->parentFolderNode();
470
            grandparent->removeFolderNodes(QList<ProjectExplorer::FolderNode *>() << parent);
471 472 473 474 475
            parent = grandparent;
            if (parent == rootNode)
                break;
        }
    }
con's avatar
con committed
476 477 478 479 480
}

ProjectExplorer::FolderNode *CMakeProject::findOrCreateFolder(CMakeProjectNode *rootNode, QString directory)
{
    QString relativePath = QDir(QFileInfo(rootNode->path()).path()).relativeFilePath(directory);
481
    QStringList parts = relativePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
con's avatar
con committed
482
    ProjectExplorer::FolderNode *parent = rootNode;
483
    QString path = QFileInfo(rootNode->path()).path();
hjk's avatar
hjk committed
484
    foreach (const QString &part, parts) {
485 486
        path += QLatin1Char('/');
        path += part;
con's avatar
con committed
487 488
        // Find folder in subFolders
        bool found = false;
hjk's avatar
hjk committed
489
        foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes()) {
490
            if (folder->path() == path) {
con's avatar
con committed
491 492 493 494 495 496 497 498
                // yeah found something :)
                parent = folder;
                found = true;
                break;
            }
        }
        if (!found) {
            // No FolderNode yet, so create it
499
            ProjectExplorer::FolderNode *tmp = new ProjectExplorer::FolderNode(path);
500
            tmp->setDisplayName(part);
501
            parent->addFolderNodes(QList<ProjectExplorer::FolderNode *>() << tmp);
con's avatar
con committed
502 503 504 505 506 507
            parent = tmp;
        }
    }
    return parent;
}

508
QString CMakeProject::displayName() const
con's avatar
con committed
509
{
510
    return m_projectName;
con's avatar
con committed
511 512
}

513
Core::IDocument *CMakeProject::document() const
con's avatar
con committed
514 515 516 517
{
    return m_file;
}

518
CMakeManager *CMakeProject::projectManager() const
con's avatar
con committed
519 520 521 522 523 524 525 526 527 528 529 530
{
    return m_manager;
}

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


QStringList CMakeProject::files(FilesMode fileMode) const
{
531
    Q_UNUSED(fileMode)
con's avatar
con committed
532 533 534
    return m_files;
}

535
bool CMakeProject::fromMap(const QVariantMap &map)
con's avatar
con committed
536
{
537 538
    if (!Project::fromMap(map))
        return false;
con's avatar
con committed
539

540 541
    bool hasUserFile = activeTarget();
    if (!hasUserFile) {
542
        CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, projectDirectory().toString(), Utils::Environment::systemEnvironment());
543 544
        if (copw.exec() != QDialog::Accepted)
            return false;
545 546 547
        Kit *k = copw.kit();
        Target *t = new Target(this, k);
        CMakeBuildConfiguration *bc(new CMakeBuildConfiguration(t));
548
        bc->setDefaultDisplayName(QLatin1String("all"));
549
        bc->setUseNinja(copw.useNinja());
550
        bc->setBuildDirectory(Utils::FileName::fromString(copw.buildDirectory()));
551 552 553 554 555 556 557 558
        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);
559
        cleanMakeStep->setAdditionalArguments(QLatin1String("clean"));
560 561 562 563
        cleanMakeStep->setClean(true);

        t->addBuildConfiguration(bc);

564
        t->updateDefaultDeployConfigurations();
565 566 567 568 569 570 571 572 573

        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;
574
        QString cbpFile = CMakeManager::findCbpFile(QDir(activeBC->buildDirectory().toString()));
575 576 577 578 579 580 581 582 583
        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) {
584
            CMakeBuildInfo info(activeBC);
585
            CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, mode, &info);
586 587 588 589 590
            if (copw.exec() != QDialog::Accepted)
                return false;
            else
                activeBC->setUseNinja(copw.useNinja());
        }
dt's avatar
dt committed
591
    }
592

593
    parseCMakeLists();
594

595 596 597 598 599 600 601 602
    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
603
    return true;
con's avatar
con committed
604 605
}

606 607
bool CMakeProject::setupTarget(Target *t)
{
608
    t->updateDefaultBuildConfigurations();
609 610
    if (t->buildConfigurations().isEmpty())
        return false;
611
    t->updateDefaultDeployConfigurations();
612 613 614 615

    return true;
}

616
CMakeBuildTarget CMakeProject::buildTargetForTitle(const QString &title)
617
{
tomdeblauwe's avatar
tomdeblauwe committed
618
    foreach (const CMakeBuildTarget &ct, m_buildTargets)
619 620
        if (ct.title == title)
            return ct;
621
    return CMakeBuildTarget();
622 623
}

dt's avatar
dt committed
624 625
QString CMakeProject::uiHeaderFile(const QString &uiFile)
{
626
    QFileInfo fi(uiFile);
627
    Utils::FileName project = projectDirectory();
628 629 630 631 632 633 634 635 636 637 638 639 640 641
    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());
642
    QDir buildDir = QDir(activeTarget()->activeBuildConfiguration()->buildDirectory().toString());
dt's avatar
dt committed
643 644 645 646
    QString uiHeaderFilePath = buildDir.absoluteFilePath(relativePath);
    uiHeaderFilePath += QLatin1String("/ui_");
    uiHeaderFilePath += fi.completeBaseName();
    uiHeaderFilePath += QLatin1String(".h");
647

dt's avatar
dt committed
648 649 650
    return QDir::cleanPath(uiHeaderFilePath);
}

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 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
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));
    }
}

714 715 716 717 718 719 720 721 722
void CMakeProject::updateApplicationAndDeploymentTargets()
{
    Target *t = activeTarget();

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

723
    sourceDir.setPath(t->project()->projectDirectory().toString());
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
    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
749 750
    if (deploymentStream.device()) {
        while (!deploymentStream.atEnd()) {
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
751 752 753 754
            QString line = deploymentStream.readLine();
            if (!line.contains(QLatin1Char(':')))
                continue;
            QStringList file = line.split(QLatin1Char(':'));
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
755 756
            deploymentData.addFile(absoluteSourcePath + file.at(0), deploymentPrefix + file.at(1));
        }
757 758 759 760 761 762
    }

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

dt's avatar
dt committed
763 764
void CMakeProject::createUiCodeModelSupport()
{
Tobias Hunger's avatar
Tobias Hunger committed
765
    QHash<QString, QString> uiFileHash;
dt's avatar
dt committed
766 767 768

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

773
    QtSupport::UiCodeModelManager::update(this, uiFileHash);
dt's avatar
dt committed
774 775
}

776 777
// CMakeFile

con's avatar
con committed
778
CMakeFile::CMakeFile(CMakeProject *parent, QString fileName)
779
    : Core::IDocument(parent), m_project(parent)
con's avatar
con committed
780
{
781
    setId("Cmake.ProjectFile");
782
    setMimeType(QLatin1String(Constants::CMAKEPROJECTMIMETYPE));
783
    setFilePath(fileName);
con's avatar
con committed
784 785
}

786
bool CMakeFile::save(QString *errorString, const QString &fileName, bool autoSave)
con's avatar
con committed
787 788 789
{
    // Once we have an texteditor open for this file, we probably do
    // need to implement this, don't we.
790
    Q_UNUSED(errorString)
791
    Q_UNUSED(fileName)
792
    Q_UNUSED(autoSave)
con's avatar
con committed
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
    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;
}

816
Core::IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
con's avatar
con committed
817
{
818 819 820 821 822
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
}

823
bool CMakeFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
824
{
825
    Q_UNUSED(errorString)
826 827
    Q_UNUSED(flag)
    Q_UNUSED(type)
828
    return true;
con's avatar
con committed
829 830
}

831
CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) : m_buildConfiguration(0)
con's avatar
con committed
832
{
833
    QFormLayout *fl = new QFormLayout(this);
834
    fl->setContentsMargins(20, -1, 0, -1);
835
    fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
836
    setLayout(fl);
837

838
    QPushButton *runCmakeButton = new QPushButton(tr("Run CMake..."));
839 840 841 842
    connect(runCmakeButton, SIGNAL(clicked()),
            this, SLOT(runCMake()));
    fl->addRow(tr("Reconfigure project:"), runCmakeButton);

843 844 845 846 847 848 849 850 851 852 853
    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);

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

856
    m_buildConfiguration = bc;
857
    m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString());
858
    if (m_buildConfiguration->buildDirectory() == bc->target()->project()->projectDirectory())
859 860
        m_changeButton->setEnabled(false);
    else
861
        m_changeButton->setEnabled(true);
862

863
    setDisplayName(tr("CMake"));
864 865
}

866
void CMakeBuildSettingsWidget::openChangeBuildDirectoryDialog()
867
{
868
    CMakeProject *project = static_cast<CMakeProject *>(m_buildConfiguration->target()->project());
869
    CMakeBuildInfo info(m_buildConfiguration);
870 871
    CMakeOpenProjectWizard copw(Core::ICore::mainWindow(),
                                project->projectManager(), CMakeOpenProjectWizard::ChangeDirectory,
872
                                &info);
873
    if (copw.exec() == QDialog::Accepted) {
874
        project->changeBuildDirectory(m_buildConfiguration, copw.buildDirectory());
875
        m_buildConfiguration->setUseNinja(copw.useNinja());
876
        m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString());
877
    }
878 879
}

880 881
void CMakeBuildSettingsWidget::runCMake()
{
882 883
    if (!ProjectExplorer::ProjectExplorerPlugin::instance()->saveModifiedFiles())
        return;
884
    CMakeProject *project = static_cast<CMakeProject *>(m_buildConfiguration->target()->project());
885
    CMakeBuildInfo info(m_buildConfiguration);
886 887
    CMakeOpenProjectWizard copw(Core::ICore::mainWindow(),
                                project->projectManager(),
888
                                CMakeOpenProjectWizard::WantToUpdate, &info);
Tobias Hunger's avatar
Tobias Hunger committed
889
    if (copw.exec() == QDialog::Accepted)
890
        project->parseCMakeLists();
891 892
}

893 894 895 896
/////
// CMakeCbpParser
////

897 898 899 900 901
// 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()
902
{
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
    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.