cmakeopenprojectwizard.cpp 30.3 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
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
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
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
****************************************************************************/
29

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
30 31
#include "cmakeopenprojectwizard.h"
#include "cmakeprojectmanager.h"
Peter Kümmel's avatar
Peter Kümmel committed
32
#include "cmakebuildconfiguration.h"
33
#include "cmakebuildinfo.h"
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
34

35
#include <coreplugin/icore.h>
36
#include <utils/hostosinfo.h>
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
37
#include <utils/pathchooser.h>
hjk's avatar
hjk committed
38
#include <utils/fancylineedit.h>
39
#include <utils/historycompleter.h>
Tobias Hunger's avatar
Tobias Hunger committed
40 41
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
42 43
#include <projectexplorer/toolchain.h>
#include <projectexplorer/abi.h>
44
#include <projectexplorer/projectexplorerconstants.h>
45
#include <texteditor/fontsettings.h>
46
#include <remotelinux/remotelinux_constants.h>
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
47

48 49 50 51 52 53
#include <QVBoxLayout>
#include <QFormLayout>
#include <QLabel>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QDateTime>
54
#include <QSettings>
55
#include <QStringList>
56
#include <QApplication>
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;

///////
//  Page Flow:
//   Start (No .user file)
//    |
//    |---> In Source Build --> Page: Tell the user about that
//                               |--> Already existing cbp file (and new enough) --> Page: Ready to load the project
//                               |--> Page: Ask for cmd options, run generator
//    |---> No in source Build --> Page: Ask the user for the build directory
//                                   |--> Already existing cbp file (and new enough) --> Page: Ready to load the project
//                                   |--> Page: Ask for cmd options, run generator

Peter Kümmel's avatar
Peter Kümmel committed
72 73 74 75 76

namespace CMakeProjectManager {
namespace Internal {
    class GeneratorInfo
    {
77
        Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::GeneratorInfo)
Peter Kümmel's avatar
Peter Kümmel committed
78
    public:
79
        enum Ninja { NoNinja, OfferNinja, ForceNinja };
80
        static QList<GeneratorInfo> generatorInfosFor(ProjectExplorer::Kit *k, Ninja n, bool preferNinja, bool hasCodeBlocks);
81 82 83

        GeneratorInfo();
        explicit GeneratorInfo(ProjectExplorer::Kit *kit, bool ninja = false);
Peter Kümmel's avatar
Peter Kümmel committed
84

85 86
        ProjectExplorer::Kit *kit() const;
        bool isNinja() const;
Peter Kümmel's avatar
Peter Kümmel committed
87

88
        QString displayName() const;
89 90
        QByteArray generatorArgument() const;
        QByteArray generator() const;
Peter Kümmel's avatar
Peter Kümmel committed
91 92 93 94 95 96 97 98 99 100

    private:
        ProjectExplorer::Kit *m_kit;
        bool m_isNinja;
    };
}
}

Q_DECLARE_METATYPE(CMakeProjectManager::Internal::GeneratorInfo);

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
GeneratorInfo::GeneratorInfo()
    : m_kit(0), m_isNinja(false)
{}

GeneratorInfo::GeneratorInfo(ProjectExplorer::Kit *kit, bool ninja)
    : m_kit(kit), m_isNinja(ninja)
{}

ProjectExplorer::Kit *GeneratorInfo::kit() const
{
    return m_kit;
}

bool GeneratorInfo::isNinja() const {
    return m_isNinja;
}

118
QByteArray GeneratorInfo::generator() const
119 120
{
    if (!m_kit)
121
        return QByteArray();
122 123 124
    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(m_kit);
    ProjectExplorer::Abi targetAbi = tc->targetAbi();
    if (m_isNinja) {
125
        return "Ninja";
126 127 128 129 130
    } else if (targetAbi.os() == ProjectExplorer::Abi::WindowsOS) {
        if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2005Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2008Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2010Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2012Flavor) {
131
            return "NMake Makefiles";
132
        } else if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMSysFlavor) {
Eike Ziller's avatar
Eike Ziller committed
133
            if (Utils::HostOsInfo::isWindowsHost())
134
                return "MinGW Makefiles";
Eike Ziller's avatar
Eike Ziller committed
135
            else
136
                return "Unix Makefiles";
137 138
        }
    }
139
    return "Unix Makefiles";
140
}
Peter Kümmel's avatar
Peter Kümmel committed
141

142
QByteArray GeneratorInfo::generatorArgument() const
143
{
144
    QByteArray tmp = generator();
145 146
    if (tmp.isEmpty())
        return tmp;
147
    return QByteArray("-GCodeBlocks - ") + tmp;
148 149 150 151 152 153 154
}

QString GeneratorInfo::displayName() const
{
    if (!m_kit)
        return QString();
    if (m_isNinja)
155
        return tr("Ninja (%1)").arg(m_kit->displayName());
156 157 158 159 160 161 162
    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(m_kit);
    ProjectExplorer::Abi targetAbi = tc->targetAbi();
    if (targetAbi.os() == ProjectExplorer::Abi::WindowsOS) {
        if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2005Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2008Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2010Flavor
                || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2012Flavor) {
163
            return tr("NMake Generator (%1)").arg(m_kit->displayName());
164
        } else if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMSysFlavor) {
Eike Ziller's avatar
Eike Ziller committed
165
            if (Utils::HostOsInfo::isWindowsHost())
166
                return tr("MinGW Generator (%1)").arg(m_kit->displayName());
Eike Ziller's avatar
Eike Ziller committed
167
            else
168
                return tr("Unix Generator (%1)").arg(m_kit->displayName());
169 170 171
        }
    } else {
        // Non windows
172
        return tr("Unix Generator (%1)").arg(m_kit->displayName());
173 174 175 176
    }
    return QString();
}

177
QList<GeneratorInfo> GeneratorInfo::generatorInfosFor(ProjectExplorer::Kit *k, Ninja n, bool preferNinja, bool hasCodeBlocks)
178 179 180
{
    QList<GeneratorInfo> results;
    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(k);
181 182
    if (!tc)
        return results;
183
    Core::Id deviceType = ProjectExplorer::DeviceTypeKitInformation::deviceTypeId(k);
184 185
    if (deviceType !=  ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE
            && deviceType != RemoteLinux::Constants::GenericLinuxOsType)
186
        return results;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    ProjectExplorer::Abi targetAbi = tc->targetAbi();
    if (n != ForceNinja) {
        if (targetAbi.os() == ProjectExplorer::Abi::WindowsOS) {
            if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2005Flavor
                    || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2008Flavor
                    || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2010Flavor
                    || targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMsvc2012Flavor) {
                if (hasCodeBlocks)
                    results << GeneratorInfo(k);
            } else if (targetAbi.osFlavor() == ProjectExplorer::Abi::WindowsMSysFlavor) {
                results << GeneratorInfo(k);
            }
        } else {
            // Non windows
            results << GeneratorInfo(k);
        }
    }
204 205 206 207 208 209
    if (n != NoNinja) {
        if (preferNinja)
            results.prepend(GeneratorInfo(k, true));
        else
            results.append(GeneratorInfo(k, true));
    }
210 211 212 213 214 215
    return results;
}

//////////////
/// CMakeOpenProjectWizard
//////////////
Peter Kümmel's avatar
Peter Kümmel committed
216

217
CMakeOpenProjectWizard::CMakeOpenProjectWizard(CMakeManager *cmakeManager, const QString &sourceDirectory, Utils::Environment env)
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
218
    : m_cmakeManager(cmakeManager),
dt's avatar
dt committed
219
      m_sourceDirectory(sourceDirectory),
220 221 222
      m_environment(env),
      m_useNinja(false),
      m_kit(0)
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
223
{
224 225 226
    if (!compatibleKitExist())
        addPage(new NoKitPage(this));

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
227 228
    if (hasInSourceBuild()) {
        m_buildDirectory = m_sourceDirectory;
229
        addPage(new InSourceBuildPage(this));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
230
    } else {
231
        m_buildDirectory = m_sourceDirectory + QLatin1String("-build");
232
        addPage(new ShadowBuildPage(this));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
233 234
    }

235 236
    if (!m_cmakeManager->isCMakeExecutableValid())
        addPage(new ChooseCMakePage(this));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
237

238
    addPage(new CMakeRunPage(this));
239

dt's avatar
dt committed
240
    init();
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
241 242
}

243
CMakeOpenProjectWizard::CMakeOpenProjectWizard(CMakeManager *cmakeManager, CMakeOpenProjectWizard::Mode mode,
244
                                               const CMakeBuildInfo *info)
dt's avatar
dt committed
245
    : m_cmakeManager(cmakeManager),
246 247 248 249
      m_sourceDirectory(info->sourceDirectory),
      m_environment(info->environment),
      m_useNinja(info->useNinja),
      m_kit(0)
dt's avatar
dt committed
250
{
251 252
    m_kit = ProjectExplorer::KitManager::find(info->kitId);

253
    CMakeRunPage::Mode rmode;
254
    if (mode == CMakeOpenProjectWizard::NeedToCreate)
255 256 257
        rmode = CMakeRunPage::Recreate;
    else if (mode == CMakeOpenProjectWizard::WantToUpdate)
        rmode = CMakeRunPage::WantToUpdate;
258
    else if (mode == CMakeOpenProjectWizard::NeedToUpdate)
259
        rmode = CMakeRunPage::NeedToUpdate;
260 261
    else
        rmode = CMakeRunPage::ChangeDirectory;
dt's avatar
dt committed
262

263
    if (mode == CMakeOpenProjectWizard::ChangeDirectory) {
264
        m_buildDirectory = info->buildDirectory.toString();
265 266
        addPage(new ShadowBuildPage(this, true));
    }
267 268
    if (!m_cmakeManager->isCMakeExecutableValid())
        addPage(new ChooseCMakePage(this));
269

270
    addPage(new CMakeRunPage(this, rmode, info->buildDirectory.toString()));
dt's avatar
dt committed
271 272 273 274 275 276
    init();
}

void CMakeOpenProjectWizard::init()
{
    setWindowTitle(tr("CMake Wizard"));
277 278
}

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
279 280 281 282 283 284 285
CMakeManager *CMakeOpenProjectWizard::cmakeManager() const
{
    return m_cmakeManager;
}

bool CMakeOpenProjectWizard::hasInSourceBuild() const
{
286
    QFileInfo fi(m_sourceDirectory + QLatin1String("/CMakeCache.txt"));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
287 288 289 290 291
    if (fi.exists())
        return true;
    return false;
}

292 293 294 295
bool CMakeOpenProjectWizard::compatibleKitExist() const
{
    bool hasCodeBlocksGenerator = m_cmakeManager->hasCodeBlocksMsvcGenerator();
    bool hasNinjaGenerator = m_cmakeManager->hasCodeBlocksNinjaGenerator();
296
    bool preferNinja = m_cmakeManager->preferNinja();
297

298
    QList<ProjectExplorer::Kit *> kitList = ProjectExplorer::KitManager::kits();
299 300 301 302 303 304 305

    foreach (ProjectExplorer::Kit *k, kitList) {
        // OfferNinja and ForceNinja differ in what they return
        // but not whether the list is empty or not, which is what we
        // are interested in here
        QList<GeneratorInfo> infos = GeneratorInfo::generatorInfosFor(k,
                                                                      hasNinjaGenerator ? GeneratorInfo::OfferNinja : GeneratorInfo::NoNinja,
306
                                                                      preferNinja,
307 308 309 310 311 312 313
                                                                      hasCodeBlocksGenerator);
        if (!infos.isEmpty())
            return true;
    }
    return false;
}

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
314 315 316 317 318 319
bool CMakeOpenProjectWizard::existsUpToDateXmlFile() const
{
    QString cbpFile = CMakeManager::findCbpFile(QDir(buildDirectory()));
    if (!cbpFile.isEmpty()) {
        // We already have a cbp file
        QFileInfo cbpFileInfo(cbpFile);
320
        QFileInfo cmakeListsFileInfo(sourceDirectory() + QLatin1String("/CMakeLists.txt"));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

        if (cbpFileInfo.lastModified() > cmakeListsFileInfo.lastModified())
            return true;
    }
    return false;
}

QString CMakeOpenProjectWizard::buildDirectory() const
{
    return m_buildDirectory;
}

QString CMakeOpenProjectWizard::sourceDirectory() const
{
    return m_sourceDirectory;
}

void CMakeOpenProjectWizard::setBuildDirectory(const QString &directory)
{
    m_buildDirectory = directory;
}

343 344 345 346 347 348 349 350 351 352
bool CMakeOpenProjectWizard::useNinja() const
{
    return m_useNinja;
}

void CMakeOpenProjectWizard::setUseNinja(bool b)
{
    m_useNinja = b;
}

353
QString CMakeOpenProjectWizard::arguments() const
dt's avatar
dt committed
354
{
355
    return m_arguments;
dt's avatar
dt committed
356 357
}

358
void CMakeOpenProjectWizard::setArguments(const QString &args)
dt's avatar
dt committed
359
{
360
    m_arguments = args;
dt's avatar
dt committed
361 362
}

363
Utils::Environment CMakeOpenProjectWizard::environment() const
364
{
365 366 367 368 369 370
    return m_environment;
}

ProjectExplorer::Kit *CMakeOpenProjectWizard::kit() const
{
    return m_kit;
371 372
}

373
void CMakeOpenProjectWizard::setKit(ProjectExplorer::Kit *kit)
Peter Kümmel's avatar
Peter Kümmel committed
374
{
375
    m_kit = kit;
Peter Kümmel's avatar
Peter Kümmel committed
376
}
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
377

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
//////
// NoKitPage
/////

NoKitPage::NoKitPage(CMakeOpenProjectWizard *cmakeWizard)
    : QWizardPage(cmakeWizard), m_cmakeWizard(cmakeWizard)
{
    QVBoxLayout *layout = new QVBoxLayout;
    setLayout(layout);

    m_descriptionLabel = new QLabel(this);
    m_descriptionLabel->setWordWrap(true);
    layout->addWidget(m_descriptionLabel);

    m_optionsButton = new QPushButton;
393
    m_optionsButton->setText(Core::ICore::msgShowOptionsDialog());
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

    connect(m_optionsButton, SIGNAL(clicked()),
            this, SLOT(showOptions()));

    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->addWidget(m_optionsButton);
    hbox->addStretch();

    layout->addLayout(hbox);

    setTitle(tr("Check Kits"));

    connect(ProjectExplorer::KitManager::instance(), SIGNAL(kitsChanged()),
            this, SLOT(kitsChanged()));

    kitsChanged();
}

void NoKitPage::kitsChanged()
{
    if (isComplete()) {
        m_descriptionLabel->setText(tr("There are compatible kits."));
        m_optionsButton->setVisible(false);
    } else {
        m_descriptionLabel->setText(tr("Qt Creator has no kits that are suitable for CMake projects. Please configure a kit."));
        m_optionsButton->setVisible(true);
    }
    emit completeChanged();
}

bool NoKitPage::isComplete() const
{
    return m_cmakeWizard->compatibleKitExist();
}

void NoKitPage::showOptions()
{
    Core::ICore::showOptionsDialog(Core::Id(ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_CATEGORY),
                                   Core::Id(ProjectExplorer::Constants::KITS_SETTINGS_PAGE_ID), this);
}

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
435 436 437 438 439 440
InSourceBuildPage::InSourceBuildPage(CMakeOpenProjectWizard *cmakeWizard)
    : QWizardPage(cmakeWizard), m_cmakeWizard(cmakeWizard)
{
    setLayout(new QVBoxLayout);
    QLabel *label = new QLabel(this);
    label->setWordWrap(true);
441
    label->setText(tr("Qt Creator has detected an <b>in-source-build in %1</b> "
Friedemann Kleint's avatar
Friedemann Kleint committed
442
                   "which prevents shadow builds. Qt Creator will not allow you to change the build directory. "
443 444
                   "If you want a shadow build, clean your source directory and re-open the project.")
                   .arg(m_cmakeWizard->buildDirectory()));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
445
    layout()->addWidget(label);
446
    setTitle(tr("Build Location"));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
447 448
}

449
ShadowBuildPage::ShadowBuildPage(CMakeOpenProjectWizard *cmakeWizard, bool change)
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
450 451 452 453 454 455 456
    : QWizardPage(cmakeWizard), m_cmakeWizard(cmakeWizard)
{
    QFormLayout *fl = new QFormLayout;
    this->setLayout(fl);

    QLabel *label = new QLabel(this);
    label->setWordWrap(true);
457
    if (change)
458
        label->setText(tr("Please enter the directory in which you want to build your project.") + QLatin1Char(' '));
459 460 461 462 463
    else
        label->setText(tr("Please enter the directory in which you want to build your project. "
                          "Qt Creator recommends to not use the source directory for building. "
                          "This ensures that the source directory remains clean and enables multiple builds "
                          "with different settings."));
con's avatar
con committed
464
    fl->addRow(label);
465
    m_pc = new Utils::PathChooser(this);
466
    m_pc->setBaseDirectory(m_cmakeWizard->sourceDirectory());
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
467
    m_pc->setPath(m_cmakeWizard->buildDirectory());
468
    m_pc->setExpectedKind(Utils::PathChooser::Directory);
469
    m_pc->setHistoryCompleter(QLatin1String("Cmake.BuildDir.History"));
con's avatar
con committed
470
    connect(m_pc, SIGNAL(changed(QString)), this, SLOT(buildDirectoryChanged()));
471
    fl->addRow(tr("Build directory:"), m_pc);
472
    setTitle(tr("Build Location"));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
473 474 475 476 477 478 479
}

void ShadowBuildPage::buildDirectoryChanged()
{
    m_cmakeWizard->setBuildDirectory(m_pc->path());
}

480 481 482 483 484 485 486 487 488 489 490 491 492
ChooseCMakePage::ChooseCMakePage(CMakeOpenProjectWizard *cmakeWizard)
    : QWizardPage(cmakeWizard), m_cmakeWizard(cmakeWizard)
{
    QFormLayout *fl = new QFormLayout;
    fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
    setLayout(fl);

    m_cmakeLabel = new QLabel;
    m_cmakeLabel->setWordWrap(true);
    fl->addRow(m_cmakeLabel);
    // Show a field for the user to enter
    m_cmakeExecutable = new Utils::PathChooser(this);
    m_cmakeExecutable->setExpectedKind(Utils::PathChooser::ExistingCommand);
493
    m_cmakeExecutable->setHistoryCompleter(QLatin1String("Cmake.Command.History"));
494
    fl->addRow(tr("CMake Executable:"), m_cmakeExecutable);
495 496 497 498 499 500

    connect(m_cmakeExecutable, SIGNAL(editingFinished()),
            this, SLOT(cmakeExecutableChanged()));
    connect(m_cmakeExecutable, SIGNAL(browsingFinished()),
            this, SLOT(cmakeExecutableChanged()));

501
    setTitle(tr("Choose CMake Executable"));
502 503 504 505 506 507
}

void ChooseCMakePage::updateErrorText()
{
    QString cmakeExecutable = m_cmakeWizard->cmakeManager()->cmakeExecutable();
    if (m_cmakeWizard->cmakeManager()->isCMakeExecutableValid()) {
508
        m_cmakeLabel->setText(tr("The CMake executable is valid."));
509
    } else {
510
        QString text = tr("Specify the path to the CMake executable. No CMake executable was found in the path.");
511
        if (!cmakeExecutable.isEmpty()) {
512
            text += QLatin1Char(' ');
513 514
            QFileInfo fi(cmakeExecutable);
            if (!fi.exists())
515
                text += tr("The CMake executable (%1) does not exist.").arg(cmakeExecutable);
516
            else if (!fi.isExecutable())
517
                text += tr("The path %1 is not an executable.").arg(cmakeExecutable);
518
            else
519
                text += tr("The path %1 is not a valid CMake executable.").arg(cmakeExecutable);
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
        }
        m_cmakeLabel->setText(text);
    }
}

void ChooseCMakePage::cmakeExecutableChanged()
{
    m_cmakeWizard->cmakeManager()->setCMakeExecutable(m_cmakeExecutable->path());
    updateErrorText();
    emit completeChanged();
}

bool ChooseCMakePage::isComplete() const
{
    return m_cmakeWizard->cmakeManager()->isCMakeExecutableValid();
}

537
CMakeRunPage::CMakeRunPage(CMakeOpenProjectWizard *cmakeWizard, Mode mode, const QString &buildDirectory)
dt's avatar
dt committed
538 539
    : QWizardPage(cmakeWizard),
      m_cmakeWizard(cmakeWizard),
540
      m_haveCbpFile(false),
541 542
      m_mode(mode),
      m_buildDirectory(buildDirectory)
dt's avatar
dt committed
543 544 545 546 547
{
    initWidgets();
}

void CMakeRunPage::initWidgets()
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
548 549
{
    QFormLayout *fl = new QFormLayout;
con's avatar
con committed
550
    fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
551
    setLayout(fl);
552
    // Description Label
dt's avatar
dt committed
553 554 555 556
    m_descriptionLabel = new QLabel(this);
    m_descriptionLabel->setWordWrap(true);

    fl->addRow(m_descriptionLabel);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
557

558
    // Run CMake Line (with arguments)
hjk's avatar
hjk committed
559
    m_argumentsLineEdit = new Utils::FancyLineEdit(this);
560
    m_argumentsLineEdit->setHistoryCompleter(QLatin1String("CMakeArgumentsLineEdit"));
561
    m_argumentsLineEdit->selectAll();
562

563
    connect(m_argumentsLineEdit,SIGNAL(returnPressed()), this, SLOT(runCMake()));
con's avatar
con committed
564
    fl->addRow(tr("Arguments:"), m_argumentsLineEdit);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
565

566
    m_generatorComboBox = new QComboBox(this);
con's avatar
con committed
567
    fl->addRow(tr("Generator:"), m_generatorComboBox);
dt's avatar
dt committed
568

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
569
    m_runCMake = new QPushButton(this);
570
    m_runCMake->setText(tr("Run CMake"));
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
571
    connect(m_runCMake, SIGNAL(clicked()), this, SLOT(runCMake()));
572

con's avatar
con committed
573 574 575 576
    QHBoxLayout *hbox2 = new QHBoxLayout;
    hbox2->addStretch(10);
    hbox2->addWidget(m_runCMake);
    fl->addRow(hbox2);
577

578
    // Bottom output window
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
579
    m_output = new QPlainTextEdit(this);
580
    m_output->setReadOnly(true);
con's avatar
con committed
581 582 583
    // set smaller minimum size to avoid vanishing descriptions if all of the
    // above is shown and the dialog not vertically resizing to fit stuff in (Mac)
    m_output->setMinimumHeight(15);
584
    QFont f(TextEditor::FontSettings::defaultFixedFontFamily());
585 586
    f.setStyleHint(QFont::TypeWriter);
    m_output->setFont(f);
587 588 589
    QSizePolicy pl = m_output->sizePolicy();
    pl.setVerticalStretch(1);
    m_output->setSizePolicy(pl);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
590
    fl->addRow(m_output);
591 592 593 594 595

    m_exitCodeLabel = new QLabel(this);
    m_exitCodeLabel->setVisible(false);
    fl->addRow(m_exitCodeLabel);

596
    setTitle(tr("Run CMake"));
597
    setMinimumSize(600, 400);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
598 599
}

600
QByteArray CMakeRunPage::cachedGeneratorFromFile(const QString &cache)
601 602 603 604 605 606
{
    QFile fi(cache);
    if (fi.exists()) {
        // Cache exists, then read it...
        if (fi.open(QIODevice::ReadOnly | QIODevice::Text)) {
            while (!fi.atEnd()) {
607
                QByteArray line = fi.readLine();
608 609 610
                if (line.startsWith("CMAKE_GENERATOR:INTERNAL=")) {
                    int splitpos = line.indexOf('=');
                    if (splitpos != -1) {
611
                        QByteArray cachedGenerator = line.mid(splitpos + 1).trimmed();
612 613 614 615 616 617 618
                        if (!cachedGenerator.isEmpty())
                            return cachedGenerator;
                    }
                }
            }
        }
    }
619
    return QByteArray();
620 621
}

622 623
void CMakeRunPage::initializePage()
{
624
    if (m_mode == Initial) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
625
        bool upToDateXmlFile = m_cmakeWizard->existsUpToDateXmlFile();
626
        m_buildDirectory = m_cmakeWizard->buildDirectory();
627

628
        if (upToDateXmlFile) {
629
            m_descriptionLabel->setText(
630
                    tr("The directory %1 already contains a cbp file, which is recent enough. "
631
                       "You can pass special arguments and rerun CMake. "
632
                       "Or simply finish the wizard directly.").arg(m_buildDirectory));
633
            m_haveCbpFile = true;
634 635
        } else {
            m_descriptionLabel->setText(
636 637
                    tr("The directory %1 does not contain a cbp file. Qt Creator needs to create this file by running CMake. "
                       "Some projects require command line arguments to the initial CMake call.").arg(m_buildDirectory));
638
        }
639
    } else if (m_mode == CMakeRunPage::NeedToUpdate) {
640
        m_descriptionLabel->setText(tr("The directory %1 contains an outdated .cbp file. Qt "
641
                                       "Creator needs to update this file by running CMake. "
642
                                       "If you want to add additional command line arguments, "
643
                                       "add them below. Note that CMake remembers command "
Friedemann Kleint's avatar
Friedemann Kleint committed
644
                                       "line arguments from the previous runs.").arg(m_buildDirectory));
tomdeblauwe's avatar
tomdeblauwe committed
645
    } else if (m_mode == CMakeRunPage::Recreate) {
Friedemann Kleint's avatar
Friedemann Kleint committed
646
        m_descriptionLabel->setText(tr("The directory %1 specified in a build-configuration, "
647
                                       "does not contain a cbp file. Qt Creator needs to "
648
                                       "recreate this file, by running CMake. "
649
                                       "Some projects require command line arguments to "
650
                                       "the initial CMake call. Note that CMake remembers command "
Friedemann Kleint's avatar
Friedemann Kleint committed
651
                                       "line arguments from the previous runs.").arg(m_buildDirectory));
tomdeblauwe's avatar
tomdeblauwe committed
652
    } else if (m_mode == CMakeRunPage::ChangeDirectory) {
653
        m_buildDirectory = m_cmakeWizard->buildDirectory();
654
        m_descriptionLabel->setText(tr("Qt Creator needs to run CMake in the new build directory. "
655
                                       "Some projects require command line arguments to the "
656
                                       "initial CMake call."));
657 658
    } else if (m_mode == CMakeRunPage::WantToUpdate) {
        m_descriptionLabel->setText(tr("Refreshing cbp file in %1.").arg(m_buildDirectory));
659
    }
660 661 662 663

    // Build the list of generators/toolchains we want to offer
    m_generatorComboBox->clear();

664 665
    bool hasCodeBlocksGenerator = m_cmakeWizard->cmakeManager()->hasCodeBlocksMsvcGenerator();
    bool hasNinjaGenerator = m_cmakeWizard->cmakeManager()->hasCodeBlocksNinjaGenerator();
666
    bool preferNinja = m_cmakeWizard->cmakeManager()->preferNinja();
Tobias Hunger's avatar
Tobias Hunger committed
667

668 669
    if (m_mode == Initial) {
        // Try figuring out generator and toolchain from CMakeCache.txt
670
        QByteArray cachedGenerator = cachedGeneratorFromFile(m_buildDirectory + QLatin1String("/CMakeCache.txt"));
671 672

        m_generatorComboBox->show();
673
        QList<ProjectExplorer::Kit *> kitList = ProjectExplorer::KitManager::kits();
674
        int defaultIndex = 0;
675 676 677 678

        foreach (ProjectExplorer::Kit *k, kitList) {
            QList<GeneratorInfo> infos = GeneratorInfo::generatorInfosFor(k,
                                                                          hasNinjaGenerator ? GeneratorInfo::OfferNinja : GeneratorInfo::NoNinja,
679
                                                                          preferNinja,
680 681
                                                                          hasCodeBlocksGenerator);

682
            if (k == ProjectExplorer::KitManager::defaultKit())
683 684
                defaultIndex = m_generatorComboBox->count();

685 686 687
            foreach (const GeneratorInfo &info, infos)
                if (cachedGenerator.isEmpty() || info.generator() == cachedGenerator)
                    m_generatorComboBox->addItem(info.displayName(), qVariantFromValue(info));
688
        }
689 690

        m_generatorComboBox->setCurrentIndex(defaultIndex);
691 692 693 694 695 696 697 698 699 700 701
    } else {
        // Note: We don't compare the actually cached generator to what is set in the buildconfiguration
        // We assume that the buildconfiguration is correct
        GeneratorInfo::Ninja ninja;
        if (m_mode == CMakeRunPage::NeedToUpdate || m_mode == CMakeRunPage::WantToUpdate) {
            ninja = m_cmakeWizard->useNinja() ? GeneratorInfo::ForceNinja : GeneratorInfo::NoNinja;
        } else { // Recreate, ChangeDirectory
            // Note: ReCreate is technically just a removed .cbp file, we assume the cache
            // got removed too. If the cache still exists the error message from cmake should
            // be a good hint to change the generator
            ninja = hasNinjaGenerator ? GeneratorInfo::OfferNinja : GeneratorInfo::NoNinja;
702
        }
703 704 705

        QList<GeneratorInfo> infos = GeneratorInfo::generatorInfosFor(m_cmakeWizard->kit(),
                                                                      ninja,
706
                                                                      preferNinja,
707 708
                                                                      true);
        foreach (const GeneratorInfo &info, infos)
709
            m_generatorComboBox->addItem(info.displayName(), qVariantFromValue(info));
710 711 712 713 714
    }
}

bool CMakeRunPage::validatePage()
{
715 716 717 718 719 720
    int index = m_generatorComboBox->currentIndex();
    if (index == -1)
        return false;
    GeneratorInfo generatorInfo = m_generatorComboBox->itemData(index).value<GeneratorInfo>();
    m_cmakeWizard->setKit(generatorInfo.kit());
    m_cmakeWizard->setUseNinja(generatorInfo.isNinja());
721
    return QWizardPage::validatePage();
722 723
}

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
724 725
void CMakeRunPage::runCMake()
{
726
    m_haveCbpFile = false;
727 728

    Utils::Environment env = m_cmakeWizard->environment();
729 730
    int index = m_generatorComboBox->currentIndex();

731
    if (index == -1) {
732 733 734
        m_output->appendPlainText(tr("No generator selected."));
        return;
    }
735 736 737
    GeneratorInfo generatorInfo = m_generatorComboBox->itemData(index).value<GeneratorInfo>();
    m_cmakeWizard->setKit(generatorInfo.kit());
    m_cmakeWizard->setUseNinja(generatorInfo.isNinja());
738

739 740 741 742
    // If mode is initial the user chooses the kit, otherwise it's already choosen
    // and the environment already contains the kit
    if (m_mode == Initial)
        generatorInfo.kit()->addToEnvironment(env);
743

qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
744 745
    m_runCMake->setEnabled(false);
    m_argumentsLineEdit->setEnabled(false);
746 747
    m_generatorComboBox->setEnabled(false);

748 749
    m_output->clear();

750
    CMakeManager *cmakeManager = m_cmakeWizard->cmakeManager();
751
    if (m_cmakeWizard->cmakeManager()->isCMakeExecutableValid()) {
752
        m_cmakeProcess = new Utils::QtcProcess();
753 754
        connect(m_cmakeProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(cmakeReadyReadStandardOutput()));
        connect(m_cmakeProcess, SIGNAL(readyReadStandardError()), this, SLOT(cmakeReadyReadStandardError()));
755
        connect(m_cmakeProcess, SIGNAL(finished(int)), this, SLOT(cmakeFinished()));
756
        cmakeManager->createXmlFile(m_cmakeProcess, m_argumentsLineEdit->text(), m_cmakeWizard->sourceDirectory(),
757
                                    m_buildDirectory, env, QString::fromLatin1(generatorInfo.generatorArgument()));
758 759 760
    } else {
        m_runCMake->setEnabled(true);
        m_argumentsLineEdit->setEnabled(true);
761
        m_generatorComboBox->setEnabled(true);
762
        m_output->appendPlainText(tr("No valid CMake executable specified."));
763
    }
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
764 765
}

766 767 768 769 770 771 772 773 774
static QColor mix_colors(QColor a, QColor b)
{
    return QColor((a.red() + 2 * b.red()) / 3, (a.green() + 2 * b.green()) / 3,
                  (a.blue() + 2* b.blue()) / 3, (a.alpha() + 2 * b.alpha()) / 3);
}

void CMakeRunPage::cmakeReadyReadStandardOutput()
{
    QTextCursor cursor(m_output->document());
dt_'s avatar
dt_ committed
775
    cursor.movePosition(QTextCursor::End);
776 777 778 779 780 781
    QTextCharFormat tf;

    QFont font = m_output->font();
    tf.setFont(font);
    tf.setForeground(m_output->palette().color(QPalette::Text));

782
    cursor.insertText(QString::fromLocal8Bit(m_cmakeProcess->readAllStandardOutput()), tf);
783 784 785
}

void CMakeRunPage::cmakeReadyReadStandardError()
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
786
{
787 788 789 790 791 792 793 794 795
    QTextCursor cursor(m_output->document());
    QTextCharFormat tf;

    QFont font = m_output->font();
    QFont boldFont = font;
    boldFont.setBold(true);
    tf.setFont(boldFont);
    tf.setForeground(mix_colors(m_output->palette().color(QPalette::Text), QColor(Qt::red)));

796
    cursor.insertText(QString::fromLocal8Bit(m_cmakeProcess->readAllStandardError()), tf);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
797 798 799 800 801 802
}

void CMakeRunPage::cmakeFinished()
{
    m_runCMake->setEnabled(true);
    m_argumentsLineEdit->setEnabled(true);
803 804
    m_generatorComboBox->setEnabled(true);

805 806
    if (m_cmakeProcess->exitCode() != 0) {
        m_exitCodeLabel->setVisible(true);
Jarek Kobus's avatar
Jarek Kobus committed
807
        m_exitCodeLabel->setText(tr("CMake exited with errors. Please check CMake output."));
808
        static_cast<Utils::HistoryCompleter *>(m_argumentsLineEdit->completer())->removeHistoryItem(0);
809
        m_haveCbpFile = false;
810 811
    } else {
        m_exitCodeLabel->setVisible(false);
812
        m_haveCbpFile = true;
813
    }
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
814 815
    m_cmakeProcess->deleteLater();
    m_cmakeProcess = 0;
816
    m_cmakeWizard->setArguments(m_argumentsLineEdit->text());
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
817 818 819 820 821 822
    emit completeChanged();
}

void CMakeRunPage::cleanupPage()
{
    m_output->clear();
823
    m_haveCbpFile = false;
824
    m_exitCodeLabel->setVisible(false);
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
825 826 827 828 829
    emit completeChanged();
}

bool CMakeRunPage::isComplete() const
{
830 831
    int index = m_generatorComboBox->currentIndex();
    return index != -1 && m_haveCbpFile;
qtc-committer@nokia.com's avatar
qtc-committer@nokia.com committed
832
}