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

#include "cmakeutils.h"
22
#include "cmakeprojectdata.h"
23

24 25
#include <QFileInfo>
#include <QDir>
26 27
#include <QProcess>
#include <QTemporaryDir>
28
#include <QRegularExpression>
29

30
#include <KShell>
31
#include <KLocalizedString>
32
#include <KConfigGroup>
33 34 35 36

#include <project/projectmodel.h>
#include <interfaces/iproject.h>
#include <interfaces/icore.h>
37 38
#include <interfaces/iruntimecontroller.h>
#include <interfaces/iruntime.h>
Milian Wolff's avatar
Milian Wolff committed
39
#include <interfaces/iplugincontroller.h>
40
#include <QStandardPaths>
41

42
#include "icmakedocumentation.h"
43 44
#include "cmakebuilddirchooser.h"
#include "settings/cmakecachemodel.h"
Dāvis Mosāns's avatar
Dāvis Mosāns committed
45
#include "debug.h"
46
#include "cmakebuilderconfig.h"
47
#include <cmakecachereader.h>
48
#include "parser/cmakelistsparser.h"
49

50 51
using namespace KDevelop;

52 53 54 55
namespace Config
{
namespace Old
{
56
static const QString currentBuildDirKey = QStringLiteral("CurrentBuildDir");
57
static const QString oldcmakeExecutableKey = QStringLiteral("CMake Binary"); // Todo: Remove at some point
58 59 60 61
static const QString currentBuildTypeKey = QStringLiteral("CurrentBuildType");
static const QString currentInstallDirKey = QStringLiteral("CurrentInstallDir");
static const QString currentEnvironmentKey = QStringLiteral("CurrentEnvironment");
static const QString currentExtraArgumentsKey = QStringLiteral("Extra Arguments");
62
static const QString currentCMakeExecutableKey = QStringLiteral("Current CMake Binary");
63 64
static const QString projectRootRelativeKey = QStringLiteral("ProjectRootRelative");
static const QString projectBuildDirs = QStringLiteral("BuildDirs");
65 66
}

67
static const QString buildDirIndexKey_ = QStringLiteral("Current Build Directory Index");
68 69
static const QString buildDirOverrideIndexKey = QStringLiteral("Temporary Build Directory Index");
static const QString buildDirCountKey = QStringLiteral("Build Directory Count");
70

71 72 73
//the used builddir will change for every runtime
static QString buildDirIndexKey() {
    const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name();
74
    return buildDirIndexKey_ + QLatin1Char('-') + currentRuntime;
75 76
}

77 78
namespace Specific
{
79
static const QString buildDirPathKey = QStringLiteral("Build Directory Path");
80
// TODO: migrate to more generic & consistent key term "CMake Executable"
81 82 83
// Support the old "CMake Binary" key too for backwards compatibility during
// a reasonable transition period. Both keys are saved at least until 5.2.0
// is released. Import support for the old key will need to remain for a
Yuri Chornoivan's avatar
Yuri Chornoivan committed
84
// considerably longer period, ideally.
85 86
static const QString cmakeBinaryKey = QStringLiteral("CMake Binary");
static const QString cmakeExecutableKey = QStringLiteral("CMake Executable");
87 88 89 90
static const QString cmakeBuildTypeKey = QStringLiteral("Build Type");
static const QString cmakeInstallDirKey = QStringLiteral("Install Directory");
static const QString cmakeEnvironmentKey = QStringLiteral("Environment Profile");
static const QString cmakeArgumentsKey = QStringLiteral("Extra Arguments");
91
static const QString buildDirRuntime = QStringLiteral("Runtime");
92 93
}

94 95
static const QString groupNameBuildDir = QStringLiteral("CMake Build Directory %1");
static const QString groupName = QStringLiteral("CMake");
96 97 98 99 100 101 102 103

} // namespace Config

namespace
{

KConfigGroup baseGroup( KDevelop::IProject* project )
{
104 105 106
    if (!project)
        return KConfigGroup();

107 108 109 110 111 112 113 114 115 116 117 118 119
    return project->projectConfiguration()->group( Config::groupName );
}

KConfigGroup buildDirGroup( KDevelop::IProject* project, int buildDirIndex )
{
    return baseGroup(project).group( Config::groupNameBuildDir.arg(buildDirIndex) );
}

bool buildDirGroupExists( KDevelop::IProject* project, int buildDirIndex )
{
    return baseGroup(project).hasGroup( Config::groupNameBuildDir.arg(buildDirIndex) );
}

120
QString readBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& aDefault, int buildDirectory )
121
{
122
    const int buildDirIndex = buildDirectory<0 ? CMake::currentBuildDirIndex(project) : buildDirectory;
123 124 125 126 127 128 129
    if (buildDirIndex >= 0)
        return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault );

    else
        return aDefault;
}

130
void writeBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& value )
131
{
132
    int buildDirIndex = CMake::currentBuildDirIndex(project);
133 134 135 136 137 138 139 140
    if (buildDirIndex >= 0)
    {
        KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex );
        buildDirGrp.writeEntry( key, value );
    }

    else
    {
141
        qCWarning(CMAKE) << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!";
142 143 144 145 146 147 148 149 150
    }
}

void writeProjectBaseParameter( KDevelop::IProject* project, const QString& key, const QString& value )
{
    KConfigGroup baseGrp = baseGroup(project);
    baseGrp.writeEntry( key, value );
}

151 152 153 154 155 156 157 158 159 160
void setBuildDirRuntime( KDevelop::IProject* project, const QString& name)
{
    writeBuildDirParameter(project, Config::Specific::buildDirRuntime, name);
}

QString buildDirRuntime( KDevelop::IProject* project, int builddir)
{
    return readBuildDirParameter(project, Config::Specific::buildDirRuntime, QString(), builddir);
}

161
} // namespace
162

163 164 165
namespace CMake
{

166
KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs)
167
{
168 169
    const KDevelop::Path buildDir(CMake::currentBuildDir(project));
    const KDevelop::Path installDir(CMake::currentInstallDir(project));
170

171 172
    KDevelop::Path::List newList;
    newList.reserve(dirs.size());
173
    for (const QString& s : dirs) {
174
        KDevelop::Path dir;
175
        if(s.startsWith(QLatin1String("#[bin_dir]")))
176
        {
177
            dir = KDevelop::Path(buildDir, s);
178
        }
179
        else if(s.startsWith(QLatin1String("#[install_dir]")))
180
        {
181
            dir = KDevelop::Path(installDir, s);
182
        }
183 184 185 186 187
        else
        {
            dir = KDevelop::Path(s);
        }

Dāvis Mosāns's avatar
Dāvis Mosāns committed
188
//         qCDebug(CMAKE) << "resolved" << s << "to" << d;
189

190
        if (!newList.contains(dir))
191
        {
192
            newList.append(dir);
193 194 195 196 197
        }
    }
    return newList;
}

198
///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp
199
bool checkForNeedingConfigure( KDevelop::IProject* project )
200
{
201 202
    auto currentRuntime = ICore::self()->runtimeController()->currentRuntime();
    const QString currentRuntimeName = currentRuntime->name();
Milian Wolff's avatar
Milian Wolff committed
203
    const KDevelop::Path builddir = currentBuildDir(project);
204
    const bool isValid = (buildDirRuntime(project, -1) == currentRuntimeName || buildDirRuntime(project, -1).isEmpty()) && builddir.isValid();
205 206

    if( !isValid )
207
    {
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
        auto addBuildDir = [project](const KDevelop::Path& buildFolder, const KDevelop::Path& installPrefix, const QString &extraArguments, const QString &buildType, const KDevelop::Path &cmakeExecutable){
            int addedBuildDirIndex = buildDirCount( project ); // old count is the new index

            // Initialize the kconfig items with the values from the dialog, this ensures the settings
            // end up in the config file once the changes are saved
            qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex;
            qCDebug(CMAKE) << "adding to cmake config: builddir path " << buildFolder;
            qCDebug(CMAKE) << "adding to cmake config: installdir " << installPrefix;
            qCDebug(CMAKE) << "adding to cmake config: extra args" << extraArguments;
            qCDebug(CMAKE) << "adding to cmake config: build type " << buildType;
            qCDebug(CMAKE) << "adding to cmake config: cmake executable " << cmakeExecutable;
            qCDebug(CMAKE) << "adding to cmake config: environment <null>";
            CMake::setBuildDirCount( project, addedBuildDirIndex + 1 );
            CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex );
            CMake::setCurrentBuildDir( project, buildFolder );
            CMake::setCurrentInstallDir( project, installPrefix );
            CMake::setCurrentExtraArguments( project, extraArguments );
            CMake::setCurrentBuildType( project, buildType );
            CMake::setCurrentCMakeExecutable(project, cmakeExecutable );
            CMake::setCurrentEnvironment( project, QString() );
        };

        if (!currentRuntime->buildPath().isEmpty()) {
231
            const Path newBuilddir(currentRuntime->buildPath(), QLatin1String("build-") + currentRuntimeName + project->name());
232
            const Path installPath(QString::fromUtf8(currentRuntime->getenv("KDEV_DEFAULT_INSTALL_PREFIX")));
Dāvis Mosāns's avatar
Dāvis Mosāns committed
233

234 235 236 237 238 239
            addBuildDir(newBuilddir, installPath, {}, QStringLiteral("Debug"), {});
            setBuildDirRuntime( project, currentRuntimeName );
            return true;
        }

        CMakeBuildDirChooser bd;
240 241 242 243
        bd.setProject( project );
        const auto builddirs = CMake::allBuildDirs(project);
        bd.setAlreadyUsed( builddirs );
        bd.setShowAvailableBuildDirs(!builddirs.isEmpty());
244
        bd.setCMakeExecutable(currentCMakeExecutable(project));
245

246
        if( !bd.exec() )
247 248 249 250
        {
            return false;
        }

251 252
        if (bd.reuseBuilddir())
        {
253
            CMake::setCurrentBuildDirIndex( project, bd.alreadyUsedIndex() );
254 255 256
        }
        else
        {
257
            addBuildDir(bd.buildFolder(), bd.installPrefix(), bd.extraArguments(), bd.buildType(), bd.cmakeExecutable());
258
        }
259
        setBuildDirRuntime( project, currentRuntimeName );
260

261
        return true;
262
    } else if( !QFile::exists( KDevelop::Path(builddir, QStringLiteral("CMakeCache.txt")).toLocalFile() ) ||
Milian Wolff's avatar
Milian Wolff committed
263
                //TODO: maybe we could use the builder for that?
264 265
               !(QFile::exists( KDevelop::Path(builddir, QStringLiteral("Makefile")).toLocalFile() ) ||
                    QFile::exists( KDevelop::Path(builddir, QStringLiteral("build.ninja")).toLocalFile() ) ) )
266 267
    {
        // User entered information already, but cmake hasn't actually been run yet.
268
        setBuildDirRuntime( project, currentRuntimeName );
269
        return true;
Milian Wolff's avatar
Milian Wolff committed
270
    }
271
    setBuildDirRuntime( project, currentRuntimeName );
272 273 274
    return false;
}

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
QHash<KDevelop::Path, QStringList> enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir)
{
    const QString buildPath = buildDir.toLocalFile();
    QHash<KDevelop::Path, QStringList> targets;
    QFile targetsFile(targetsFilePath.toLocalFile());
    if (!targetsFile.open(QIODevice::ReadOnly)) {
        qCDebug(CMAKE) << "Couldn't find the Targets file in" << targetsFile.fileName();
    }

    QTextStream targetsFileStream(&targetsFile);
    const QRegularExpression rx(QStringLiteral("^(.*)/CMakeFiles/(.*).dir$"));
    while (!targetsFileStream.atEnd()) {
        const QString line = targetsFileStream.readLine();
        auto match = rx.match(line);
        if (!match.isValid())
            qCDebug(CMAKE) << "invalid match for" << line;
        const QString sourcePath = match.captured(1).replace(buildPath, sourceDir);
        targets[KDevelop::Path(sourcePath)].append(match.captured(2));
    }
    return targets;
}

Milian Wolff's avatar
Milian Wolff committed
297
KDevelop::Path projectRoot(KDevelop::IProject* project)
298
{
299
    if (!project) {
Milian Wolff's avatar
Milian Wolff committed
300
        return {};
301 302
    }

Milian Wolff's avatar
Milian Wolff committed
303
    return project->path().cd(CMake::projectRootRelative(project));
304 305
}

306
KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir )
307
{
308
    return KDevelop::Path(readBuildDirParameter( project, Config::Specific::buildDirPathKey, QString(), builddir ));
309 310
}

Milian Wolff's avatar
Milian Wolff committed
311
KDevelop::Path commandsFile(KDevelop::IProject* project)
Kevin Funk's avatar
Kevin Funk committed
312 313 314 315 316 317
{
    auto currentBuildDir = CMake::currentBuildDir(project);
    if (currentBuildDir.isEmpty()) {
        return {};
    }

Milian Wolff's avatar
Milian Wolff committed
318
    return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json"));
Kevin Funk's avatar
Kevin Funk committed
319 320
}

321 322 323 324 325 326 327 328 329 330
KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project)
{
    auto currentBuildDir = CMake::currentBuildDir(project);
    if (currentBuildDir.isEmpty()) {
        return {};
    }

    return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt"));
}

331
QString currentBuildType( KDevelop::IProject* project, int builddir )
332
{
333
    return readBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, QStringLiteral("Release"), builddir );
334 335
}

336 337
QString findExecutable()
{
338
    auto cmake = QStandardPaths::findExecutable(QStringLiteral("cmake"));
339 340
#ifdef Q_OS_WIN
    if (cmake.isEmpty())
341 342 343 344 345
        cmake = QStandardPaths::findExecutable(QStringLiteral("cmake"), {
            QStringLiteral("C:\\Program Files (x86)\\CMake\\bin"),
            QStringLiteral("C:\\Program Files\\CMake\\bin"),
            QStringLiteral("C:\\Program Files (x86)\\CMake 2.8\\bin"),
            QStringLiteral("C:\\Program Files\\CMake 2.8\\bin")});
346 347 348 349
#endif
    return cmake;
}

350 351
KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir)
{
352 353 354 355 356
    auto defaultCMakeExecutable = CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile();

    if (!QFileInfo::exists(ICore::self()->runtimeController()->currentRuntime()->pathInHost(KDevelop::Path(defaultCMakeExecutable)).toLocalFile()))
        defaultCMakeExecutable = CMake::findExecutable();

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    if (project) {
        // check for "CMake Executable" but for now also "CMake Binary", falling back to the default.
        auto projectCMakeExecutable = readBuildDirParameter( project, Config::Specific::cmakeExecutableKey,
            readBuildDirParameter( project, Config::Specific::cmakeBinaryKey, defaultCMakeExecutable, builddir),
            builddir );
        if (projectCMakeExecutable != defaultCMakeExecutable) {
            QFileInfo info(projectCMakeExecutable);
            if (!info.isExecutable()) {
                projectCMakeExecutable = defaultCMakeExecutable;
            }
        }
        return KDevelop::Path(projectCMakeExecutable);
    }
    return KDevelop::Path(defaultCMakeExecutable);
}

373
KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir )
374
{
375
    return KDevelop::Path(readBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, QString(), builddir ));
376 377
}

378 379
QString projectRootRelative( KDevelop::IProject* project )
{
380
    return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." );
381 382
}

383 384 385 386 387
bool hasProjectRootRelative(KDevelop::IProject* project)
{
    return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey );
}

388
QString currentExtraArguments( KDevelop::IProject* project, int builddir )
389
{
390
    return readBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, QString(), builddir );
391 392
}

Milian Wolff's avatar
Milian Wolff committed
393
void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path )
394
{
395
    writeBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() );
396 397
}

398
void setCurrentBuildType( KDevelop::IProject* project, const QString& type )
399
{
400
    writeBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, type );
401 402
}

403 404 405 406 407 408 409
void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path)
{
    // maintain compatibility with older versions for now
    writeBuildDirParameter(project, Config::Specific::cmakeBinaryKey, path.toLocalFile());
    writeBuildDirParameter(project, Config::Specific::cmakeExecutableKey, path.toLocalFile());
}

Milian Wolff's avatar
Milian Wolff committed
410
void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path )
411
{
412
    writeBuildDirParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() );
413 414
}

415 416
void setProjectRootRelative( KDevelop::IProject* project, const QString& relative)
{
417
    writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative );
418
}
419

420 421
void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string)
{
422
    writeBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, string );
423 424
}

425
QString currentEnvironment(KDevelop::IProject* project, int builddir)
426
{
427
    return readBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, QString(), builddir );
428 429 430 431 432 433 434
}

int currentBuildDirIndex( KDevelop::IProject* project )
{
    KConfigGroup baseGrp = baseGroup(project);

    if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) )
435
        return baseGrp.readEntry<int>( Config::buildDirOverrideIndexKey, -1 );
436

437
    else if (baseGrp.hasKey(Config::buildDirIndexKey()))
438
        return baseGrp.readEntry<int>( Config::buildDirIndexKey(), -1 );
439
    else
440
        return baseGrp.readEntry<int>( Config::buildDirIndexKey_, -1 ); // backwards compatibility
441 442 443 444
}

void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex )
{
445
    writeProjectBaseParameter( project, Config::buildDirIndexKey(), QString::number (buildDirIndex) );
446 447 448 449
}

void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment )
{
450
    writeBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, environment );
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
}

void initBuildDirConfig( KDevelop::IProject* project )
{
    int buildDirIndex = currentBuildDirIndex( project );
    if (buildDirCount(project) <= buildDirIndex )
        setBuildDirCount( project, buildDirIndex + 1 );
}

int buildDirCount( KDevelop::IProject* project )
{
    return baseGroup(project).readEntry<int>( Config::buildDirCountKey, 0 );
}

void setBuildDirCount( KDevelop::IProject* project, int count )
{
    writeProjectBaseParameter( project, Config::buildDirCountKey, QString::number(count) );
}

void removeBuildDirConfig( KDevelop::IProject* project )
{
    int buildDirIndex = currentBuildDirIndex( project );
    if ( !buildDirGroupExists( project, buildDirIndex ) )
    {
475
        qCWarning(CMAKE) << "build directory config" << buildDirIndex << "to be removed but does not exist";
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
        return;
    }

    int bdCount = buildDirCount(project);
    setBuildDirCount( project, bdCount - 1 );
    removeOverrideBuildDirIndex( project );
    setCurrentBuildDirIndex( project, -1 );

    // move (rename) the upper config groups to keep the numbering
    // if there's nothing to move, just delete the group physically
    if (buildDirIndex + 1 == bdCount)
        buildDirGroup( project, buildDirIndex ).deleteGroup();

    else for (int i = buildDirIndex + 1; i < bdCount; ++i)
    {
        KConfigGroup src = buildDirGroup( project, i );
        KConfigGroup dest = buildDirGroup( project, i - 1 );
        dest.deleteGroup();
        src.copyTo(&dest);
        src.deleteGroup();
    }
}

499 500 501 502 503 504 505 506 507 508 509 510 511
QHash<QString, QString> readCacheValues(const KDevelop::Path& cmakeCachePath, QSet<QString> variables)
{
    QHash<QString, QString> ret;
    QFile file(cmakeCachePath.toLocalFile());
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCWarning(CMAKE) << "couldn't open CMakeCache.txt" << cmakeCachePath;
        return ret;
    }

    QTextStream in(&file);
    while (!in.atEnd() && !variables.isEmpty())
    {
        QString line = in.readLine().trimmed();
512
        if(!line.isEmpty() && line[0].isLetter())
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
        {
            CacheLine c;
            c.readLine(line);

            if(!c.isCorrect())
                continue;

            if (variables.remove(c.name())) {
                ret[c.name()] = c.value();
            }
        }
    }
    return ret;
}

528
void updateConfig( KDevelop::IProject* project, int buildDirIndex)
529 530 531 532 533
{
    if (buildDirIndex < 0)
        return;

    KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex );
534 535
    const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() ));
    const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt"));
536

537
    const QMap<QString, QString> keys = {
538
        { QStringLiteral("CMAKE_COMMAND"), Config::Specific::cmakeExecutableKey },
539 540
        { QStringLiteral("CMAKE_INSTALL_PREFIX"), Config::Specific::cmakeInstallDirKey },
        { QStringLiteral("CMAKE_BUILD_TYPE"), Config::Specific::cmakeBuildTypeKey }
541
    };
542 543 544 545 546

    const QHash<QString, QString> cacheValues = readCacheValues(cacheFilePath, keys.keys().toSet());
    for(auto it = cacheValues.constBegin(), itEnd = cacheValues.constEnd(); it!=itEnd; ++it) {
        const QString key = keys.value(it.key());
        Q_ASSERT(!key.isEmpty());
547 548 549 550 551 552 553

        // Use cache only when the config value is not set. Without this check we will always
        // overwrite values provided by the user in config dialog.
        if (buildDirGrp.readEntry(key).isEmpty() && !it.value().isEmpty())
        {
            buildDirGrp.writeEntry( key, it.value() );
        }
554
    }
555 556 557 558 559 560
}

void attemptMigrate( KDevelop::IProject* project )
{
    if ( !baseGroup(project).hasKey( Config::Old::projectBuildDirs ) )
    {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
561
        qCDebug(CMAKE) << "CMake settings migration: already done, exiting";
562 563 564 565 566
        return;
    }

    KConfigGroup baseGrp = baseGroup(project);

Milian Wolff's avatar
Milian Wolff committed
567
    KDevelop::Path buildDir( baseGrp.readEntry( Config::Old::currentBuildDirKey, QString() ) );
568
    int buildDirIndex = -1;
Milian Wolff's avatar
Milian Wolff committed
569
    const QStringList existingBuildDirs = baseGrp.readEntry( Config::Old::projectBuildDirs, QStringList() );
570 571 572 573
    {
        // also, find current build directory in this list (we need an index, not path)
        QString currentBuildDirCanonicalPath = QDir( buildDir.toLocalFile() ).canonicalPath();

Milian Wolff's avatar
Milian Wolff committed
574
        for( int i = 0; i < existingBuildDirs.count(); ++i )
575
        {
Milian Wolff's avatar
Milian Wolff committed
576
            const QString& nextBuildDir = existingBuildDirs.at(i);
577 578 579 580 581 582 583 584
            if( QDir(nextBuildDir).canonicalPath() == currentBuildDirCanonicalPath )
            {
                buildDirIndex = i;
            }
        }
    }
    int buildDirsCount = existingBuildDirs.count();

Dāvis Mosāns's avatar
Dāvis Mosāns committed
585 586 587
    qCDebug(CMAKE) << "CMake settings migration: existing build directories" << existingBuildDirs;
    qCDebug(CMAKE) << "CMake settings migration: build directory count" << buildDirsCount;
    qCDebug(CMAKE) << "CMake settings migration: current build directory" << buildDir << "(index" << buildDirIndex << ")";
588 589

    baseGrp.writeEntry( Config::buildDirCountKey, buildDirsCount );
590
    baseGrp.writeEntry( Config::buildDirIndexKey(), buildDirIndex );
591 592 593

    for (int i = 0; i < buildDirsCount; ++i)
    {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
594
        qCDebug(CMAKE) << "CMake settings migration: writing group" << i << ": path" << existingBuildDirs.at(i);
595 596

        KConfigGroup buildDirGrp = buildDirGroup( project, i );
Milian Wolff's avatar
Milian Wolff committed
597
        buildDirGrp.writeEntry( Config::Specific::buildDirPathKey, existingBuildDirs.at(i) );
598 599 600
    }

    baseGrp.deleteEntry( Config::Old::currentBuildDirKey );
601
    baseGrp.deleteEntry( Config::Old::currentCMakeExecutableKey );
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
    baseGrp.deleteEntry( Config::Old::currentBuildTypeKey );
    baseGrp.deleteEntry( Config::Old::currentInstallDirKey );
    baseGrp.deleteEntry( Config::Old::currentEnvironmentKey );
    baseGrp.deleteEntry( Config::Old::currentExtraArgumentsKey );
    baseGrp.deleteEntry( Config::Old::projectBuildDirs );
}

void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex )
{
    writeProjectBaseParameter( project, Config::buildDirOverrideIndexKey, QString::number(overrideBuildDirIndex) );
}

void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex )
{
    KConfigGroup baseGrp = baseGroup(project);

    if( !baseGrp.hasKey(Config::buildDirOverrideIndexKey) )
        return;
    if( writeToMainIndex )
621
        baseGrp.writeEntry( Config::buildDirIndexKey(), baseGrp.readEntry(Config::buildDirOverrideIndexKey) );
622 623

    baseGrp.deleteEntry(Config::buildDirOverrideIndexKey);
624 625
}

626 627
ICMakeDocumentation* cmakeDocumentation()
{
628
    return KDevelop::ICore::self()->pluginController()->extensionForPlugin<ICMakeDocumentation>(QStringLiteral("org.kdevelop.ICMakeDocumentation"));
629 630
}

631 632
QStringList allBuildDirs(KDevelop::IProject* project)
{
633 634
    QStringList result;
    int bdCount = buildDirCount(project);
635
    result.reserve(bdCount);
636
    for (int i = 0; i < bdCount; ++i)
Milian Wolff's avatar
Milian Wolff committed
637
        result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey );
638
    return result;
639 640
}

641 642 643
QString executeProcess(const QString& execName, const QStringList& args)
{
    Q_ASSERT(!execName.isEmpty());
Kevin Funk's avatar
Kevin Funk committed
644
    qCDebug(CMAKE) << "Executing:" << execName << "::" << args;
645 646

    QProcess p;
647
    QTemporaryDir tmp(QStringLiteral("kdevcmakemanager"));
648 649 650 651 652
    p.setWorkingDirectory( tmp.path() );
    p.start(execName, args, QIODevice::ReadOnly);

    if(!p.waitForFinished())
    {
653
        qCDebug(CMAKE) << "failed to execute:" << execName << args << p.exitStatus() << p.readAllStandardError();
654 655 656
    }

    QByteArray b = p.readAllStandardOutput();
657
    QString t = QString::fromUtf8(b.trimmed());
658 659 660
    return t;
}

661 662 663 664
QStringList supportedGenerators()
{
    QStringList generatorNames;

665
    bool hasNinja = ICore::self() && ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder"));
666
    if (hasNinja)
667
        generatorNames << QStringLiteral("Ninja");
668 669 670 671

#ifdef Q_OS_WIN
    // Visual Studio solution is the standard generator under windows, but we don't want to use
    // the VS IDE, so we need nmake makefiles
672
    generatorNames << QStringLiteral("NMake Makefiles") << QStringLiteral("MinGW Makefiles");
673
#endif
674
    generatorNames << QStringLiteral("Unix Makefiles");
675 676 677 678 679 680 681 682 683 684 685

    return generatorNames;
}

QString defaultGenerator()
{
    const QStringList generatorNames = supportedGenerators();

    QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator());
    if (defGen.isEmpty())
    {
686
        qCWarning(CMAKE) << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator()
687 688 689 690 691 692 693
                   << ", defaulting to 0";
        CMakeBuilderSettings::self()->setGenerator(0);
        defGen = generatorNames.at(0);
    }
    return defGen;
}

694
QVector<Test> importTestSuites(const Path &buildDir, const QString &cmakeTestFileName)
695
{
696 697 698
    const auto cmakeTestFile = Path(buildDir, cmakeTestFileName).toLocalFile()  ;
    const auto contents = CMakeListsParser::readCMakeFile(cmakeTestFile);
    
699
    QVector<Test> tests;
700 701 702
    for (const auto& entry: contents) {
        if (entry.name == QLatin1String("add_test")) {
            auto args = entry.arguments;
703
            Test test;
704
            test.name = args.takeFirst().value;
705
            test.executable = args.takeFirst().value;
706
            test.arguments = kTransform<QStringList>(args, [](const CMakeFunctionArgument& arg) { return arg.value; });
707
            tests += test;
708
        } else if (entry.name == QLatin1String("subdirs")) {
709
            tests += importTestSuites(Path(buildDir, entry.arguments.first().value));
710 711 712
        } else if (entry.name == QLatin1String("include")) {
            // Include directive points directly to a .cmake file hosting the tests
            tests += importTestSuites(Path(buildDir, entry.arguments.first().value), QString());
713 714 715 716 717 718
        } else if (entry.name == QLatin1String("set_tests_properties")) {
            if(entry.arguments.count() < 4 || entry.arguments.count() % 2) {
                qCWarning(CMAKE) << "found set_tests_properties() with unexpected number of arguments:"
                                 << entry.arguments.count();
                continue;
            }
719 720 721
            if (tests.isEmpty() || entry.arguments.first().value != tests.last().name) {
                qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value
                                 << " ...), but expected test " << tests.last().name;
722 723 724
                continue;
            }
            if (entry.arguments[1].value != QLatin1String("PROPERTIES")) {
725
                qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value
726 727 728 729 730 731
                                 << entry.arguments.at(1).value << "...), but expected PROPERTIES as second argument";
                continue;
            }
            Test &test = tests.last();
            for (int i = 2; i < entry.arguments.count(); i += 2)
                test.properties[entry.arguments[i].value] = entry.arguments[i + 1].value;
732 733 734 735
        }
    }

    return tests;
736 737
}

738 739 740 741
QVector<Test> importTestSuites(const Path &buildDir) {
    return importTestSuites(buildDir, QStringLiteral("CTestTestfile.cmake"));
}

742
}