File indexing completed on 2024-05-05 04:39:26

0001 /*
0002     SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cmakeutils.h"
0008 #include "cmakeprojectdata.h"
0009 
0010 #include <QFileInfo>
0011 #include <QProcess>
0012 #include <QTemporaryDir>
0013 #include <QRegularExpression>
0014 
0015 #include <KConfigGroup>
0016 
0017 #include <project/projectmodel.h>
0018 #include <interfaces/iproject.h>
0019 #include <interfaces/icore.h>
0020 #include <interfaces/iruntimecontroller.h>
0021 #include <interfaces/iruntime.h>
0022 #include <interfaces/iplugincontroller.h>
0023 #include <QStandardPaths>
0024 
0025 #include "icmakedocumentation.h"
0026 #include "cmakebuilddirchooser.h"
0027 #include "cmakeconfiggroupkeys.h"
0028 #include "settings/cmakecachemodel.h"
0029 #include "debug.h"
0030 #include "cmakebuilderconfig.h"
0031 #include <cmakecachereader.h>
0032 #include "parser/cmakelistsparser.h"
0033 
0034 using namespace KDevelop;
0035 
0036 namespace
0037 {
0038 
0039 KConfigGroup baseGroup( KDevelop::IProject* project )
0040 {
0041     if (!project)
0042         return KConfigGroup();
0043 
0044     return project->projectConfiguration()->group( Config::groupName );
0045 }
0046 
0047 KConfigGroup buildDirGroup( KDevelop::IProject* project, int buildDirIndex )
0048 {
0049     return baseGroup(project).group(Config::groupNameBuildDir(buildDirIndex));
0050 }
0051 
0052 bool buildDirGroupExists( KDevelop::IProject* project, int buildDirIndex )
0053 {
0054     return baseGroup(project).hasGroup(Config::groupNameBuildDir(buildDirIndex));
0055 }
0056 
0057 QString readBuildDirParameter(KDevelop::IProject* project, const char* key, const QString& aDefault, int buildDirectory)
0058 {
0059     const int buildDirIndex = buildDirectory<0 ? CMake::currentBuildDirIndex(project) : buildDirectory;
0060     if (buildDirIndex >= 0) // NOTE: we return trimmed since we may have written bogus trailing newlines in the past...
0061         return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault ).trimmed();
0062     else
0063         return aDefault;
0064 }
0065 
0066 void writeBuildDirParameter(KDevelop::IProject* project, const char* key, const QString& value)
0067 {
0068     int buildDirIndex = CMake::currentBuildDirIndex(project);
0069     if (buildDirIndex >= 0)
0070     {
0071         KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex );
0072         buildDirGrp.writeEntry( key, value );
0073     }
0074 
0075     else
0076     {
0077         qCWarning(CMAKE) << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!";
0078     }
0079 }
0080 
0081 template <typename Key>
0082 void writeProjectBaseParameter(KDevelop::IProject* project, const Key& key, const QString& value)
0083 {
0084     baseGroup(project).writeEntry(key, value);
0085 }
0086 
0087 void setBuildDirRuntime( KDevelop::IProject* project, const QString& name)
0088 {
0089     writeBuildDirParameter(project, Config::Specific::buildDirRuntime, name);
0090 }
0091 
0092 QString buildDirRuntime( KDevelop::IProject* project, int builddir)
0093 {
0094     return readBuildDirParameter(project, Config::Specific::buildDirRuntime, QString(), builddir);
0095 }
0096 
0097 } // namespace
0098 
0099 namespace CMake
0100 {
0101 
0102 KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs)
0103 {
0104     const KDevelop::Path buildDir(CMake::currentBuildDir(project));
0105     const KDevelop::Path installDir(CMake::currentInstallDir(project));
0106 
0107     KDevelop::Path::List newList;
0108     newList.reserve(dirs.size());
0109     for (const QString& s : dirs) {
0110         KDevelop::Path dir;
0111         if(s.startsWith(QLatin1String("#[bin_dir]")))
0112         {
0113             dir = KDevelop::Path(buildDir, s);
0114         }
0115         else if(s.startsWith(QLatin1String("#[install_dir]")))
0116         {
0117             dir = KDevelop::Path(installDir, s);
0118         }
0119         else
0120         {
0121             dir = KDevelop::Path(s);
0122         }
0123 
0124 //         qCDebug(CMAKE) << "resolved" << s << "to" << d;
0125 
0126         if (!newList.contains(dir))
0127         {
0128             newList.append(dir);
0129         }
0130     }
0131     return newList;
0132 }
0133 
0134 ///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp
0135 bool checkForNeedingConfigure( KDevelop::IProject* project )
0136 {
0137     auto currentRuntime = ICore::self()->runtimeController()->currentRuntime();
0138     const QString currentRuntimeName = currentRuntime->name();
0139     const KDevelop::Path builddir = currentBuildDir(project);
0140     const bool isValid = (buildDirRuntime(project, -1) == currentRuntimeName || buildDirRuntime(project, -1).isEmpty()) && builddir.isValid();
0141 
0142     if( !isValid )
0143     {
0144         auto addBuildDir = [project](const KDevelop::Path& buildFolder, const KDevelop::Path& installPrefix, const QString &extraArguments, const QString &buildType, const KDevelop::Path &cmakeExecutable){
0145             int addedBuildDirIndex = buildDirCount( project ); // old count is the new index
0146 
0147             // Initialize the kconfig items with the values from the dialog, this ensures the settings
0148             // end up in the config file once the changes are saved
0149             qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex;
0150             qCDebug(CMAKE) << "adding to cmake config: builddir path " << buildFolder;
0151             qCDebug(CMAKE) << "adding to cmake config: installdir " << installPrefix;
0152             qCDebug(CMAKE) << "adding to cmake config: extra args" << extraArguments;
0153             qCDebug(CMAKE) << "adding to cmake config: build type " << buildType;
0154             qCDebug(CMAKE) << "adding to cmake config: cmake executable " << cmakeExecutable;
0155             qCDebug(CMAKE) << "adding to cmake config: environment <null>";
0156             CMake::setBuildDirCount( project, addedBuildDirIndex + 1 );
0157             CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex );
0158             CMake::setCurrentBuildDir( project, buildFolder );
0159             CMake::setCurrentInstallDir( project, installPrefix );
0160             CMake::setCurrentExtraArguments( project, extraArguments );
0161             CMake::setCurrentBuildType( project, buildType );
0162             CMake::setCurrentCMakeExecutable(project, cmakeExecutable );
0163             CMake::setCurrentEnvironment( project, QString() );
0164         };
0165 
0166         if (!currentRuntime->buildPath().isEmpty()) {
0167             const Path newBuilddir(currentRuntime->buildPath(), QLatin1String("build-") + currentRuntimeName + project->name());
0168             const Path installPath(QString::fromUtf8(currentRuntime->getenv("KDEV_DEFAULT_INSTALL_PREFIX")));
0169 
0170             addBuildDir(newBuilddir, installPath, {}, QStringLiteral("Debug"), {});
0171             setBuildDirRuntime( project, currentRuntimeName );
0172             return true;
0173         }
0174 
0175         CMakeBuildDirChooser bd;
0176         bd.setProject( project );
0177         const auto builddirs = CMake::allBuildDirs(project);
0178         bd.setAlreadyUsed( builddirs );
0179         bd.setShowAvailableBuildDirs(!builddirs.isEmpty());
0180         bd.setCMakeExecutable(currentCMakeExecutable(project));
0181 
0182         if( !bd.exec() )
0183         {
0184             return false;
0185         }
0186 
0187         if (bd.reuseBuilddir())
0188         {
0189             CMake::setCurrentBuildDirIndex( project, bd.alreadyUsedIndex() );
0190         }
0191         else
0192         {
0193             addBuildDir(bd.buildFolder(), bd.installPrefix(), bd.extraArguments(), bd.buildType(), bd.cmakeExecutable());
0194         }
0195         setBuildDirRuntime( project, currentRuntimeName );
0196 
0197         return true;
0198     } else if( !QFile::exists( KDevelop::Path(builddir, QStringLiteral("CMakeCache.txt")).toLocalFile() ) ||
0199                 //TODO: maybe we could use the builder for that?
0200                !(QFile::exists( KDevelop::Path(builddir, QStringLiteral("Makefile")).toLocalFile() ) ||
0201                     QFile::exists( KDevelop::Path(builddir, QStringLiteral("build.ninja")).toLocalFile() ) ) )
0202     {
0203         // User entered information already, but cmake hasn't actually been run yet.
0204         setBuildDirRuntime( project, currentRuntimeName );
0205         return true;
0206     }
0207     setBuildDirRuntime( project, currentRuntimeName );
0208     return false;
0209 }
0210 
0211 QHash<KDevelop::Path, QStringList> enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir)
0212 {
0213     const QString buildPath = buildDir.toLocalFile();
0214     QHash<KDevelop::Path, QStringList> targets;
0215     QFile targetsFile(targetsFilePath.toLocalFile());
0216     if (!targetsFile.open(QIODevice::ReadOnly)) {
0217         qCDebug(CMAKE) << "Couldn't find the Targets file in" << targetsFile.fileName();
0218     }
0219 
0220     QTextStream targetsFileStream(&targetsFile);
0221     const QRegularExpression rx(QStringLiteral("^(.*)/CMakeFiles/(.*).dir$"));
0222     while (!targetsFileStream.atEnd()) {
0223         const QString line = targetsFileStream.readLine();
0224         auto match = rx.match(line);
0225         if (!match.isValid())
0226             qCDebug(CMAKE) << "invalid match for" << line;
0227         const QString sourcePath = match.captured(1).replace(buildPath, sourceDir);
0228         targets[KDevelop::Path(sourcePath)].append(match.captured(2));
0229     }
0230     return targets;
0231 }
0232 
0233 KDevelop::Path projectRoot(KDevelop::IProject* project)
0234 {
0235     if (!project) {
0236         return {};
0237     }
0238 
0239     return project->path().cd(CMake::projectRootRelative(project));
0240 }
0241 
0242 KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir )
0243 {
0244     return KDevelop::Path(readBuildDirParameter( project, Config::Specific::buildDirPathKey, QString(), builddir ));
0245 }
0246 
0247 KDevelop::Path commandsFile(KDevelop::IProject* project)
0248 {
0249     auto currentBuildDir = CMake::currentBuildDir(project);
0250     if (currentBuildDir.isEmpty()) {
0251         return {};
0252     }
0253 
0254     return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json"));
0255 }
0256 
0257 KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project)
0258 {
0259     auto currentBuildDir = CMake::currentBuildDir(project);
0260     if (currentBuildDir.isEmpty()) {
0261         return {};
0262     }
0263 
0264     return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt"));
0265 }
0266 
0267 QString currentBuildType( KDevelop::IProject* project, int builddir )
0268 {
0269     return readBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, QStringLiteral("Release"), builddir );
0270 }
0271 
0272 QString findExecutable()
0273 {
0274     auto cmake = QStandardPaths::findExecutable(QStringLiteral("cmake"));
0275 #ifdef Q_OS_WIN
0276     if (cmake.isEmpty())
0277         cmake = QStandardPaths::findExecutable(QStringLiteral("cmake"), {
0278             QStringLiteral("C:\\Program Files (x86)\\CMake\\bin"),
0279             QStringLiteral("C:\\Program Files\\CMake\\bin"),
0280             QStringLiteral("C:\\Program Files (x86)\\CMake 2.8\\bin"),
0281             QStringLiteral("C:\\Program Files\\CMake 2.8\\bin")});
0282 #endif
0283     return cmake;
0284 }
0285 
0286 QString cmakeExecutableVersion(const QString& cmakeExecutable)
0287 {
0288     QProcess p;
0289     p.setProcessChannelMode(QProcess::ForwardedErrorChannel);
0290     p.start(cmakeExecutable, {QStringLiteral("--version")});
0291     if (!p.waitForFinished()) {
0292         qCWarning(CMAKE) << "failed to read cmake version for executable" << cmakeExecutable << p.errorString();
0293         return {};
0294     }
0295 
0296     static const QRegularExpression pattern(QStringLiteral("cmake version (\\d\\.\\d+(?:\\.\\d+)?).*"));
0297     const auto output = QString::fromLocal8Bit(p.readAll());
0298     const auto match = pattern.match(output);
0299     if (!match.hasMatch()) {
0300         qCWarning(CMAKE) << "failed to read cmake version for executable" << cmakeExecutable << output;
0301         return {};
0302     }
0303 
0304     const auto version = match.captured(1);
0305     qCDebug(CMAKE) << "cmake version for executable" << cmakeExecutable << version;
0306     return version;
0307 }
0308 
0309 KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir)
0310 {
0311     auto defaultCMakeExecutable = CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile();
0312 
0313     if (!QFileInfo::exists(ICore::self()->runtimeController()->currentRuntime()->pathInHost(KDevelop::Path(defaultCMakeExecutable)).toLocalFile()))
0314         defaultCMakeExecutable = CMake::findExecutable();
0315 
0316     if (project) {
0317         // check for "CMake Executable" but for now also "CMake Binary", falling back to the default.
0318         auto projectCMakeExecutable = readBuildDirParameter( project, Config::Specific::cmakeExecutableKey,
0319             readBuildDirParameter( project, Config::Specific::cmakeBinaryKey, defaultCMakeExecutable, builddir),
0320             builddir );
0321         if (projectCMakeExecutable != defaultCMakeExecutable) {
0322             QFileInfo info(projectCMakeExecutable);
0323             if (!info.isExecutable()) {
0324                 projectCMakeExecutable = defaultCMakeExecutable;
0325             }
0326         }
0327         return KDevelop::Path(projectCMakeExecutable);
0328     }
0329     return KDevelop::Path(defaultCMakeExecutable);
0330 }
0331 
0332 KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir )
0333 {
0334     return KDevelop::Path(readBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, QString(), builddir ));
0335 }
0336 
0337 QString projectRootRelative( KDevelop::IProject* project )
0338 {
0339     return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." );
0340 }
0341 
0342 bool hasProjectRootRelative(KDevelop::IProject* project)
0343 {
0344     return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey );
0345 }
0346 
0347 QString currentExtraArguments( KDevelop::IProject* project, int builddir )
0348 {
0349     return readBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, QString(), builddir );
0350 }
0351 
0352 void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path )
0353 {
0354     writeBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() );
0355 }
0356 
0357 void setCurrentBuildType( KDevelop::IProject* project, const QString& type )
0358 {
0359     writeBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, type );
0360 }
0361 
0362 void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path)
0363 {
0364     // maintain compatibility with older versions for now
0365     writeBuildDirParameter(project, Config::Specific::cmakeBinaryKey, path.toLocalFile());
0366     writeBuildDirParameter(project, Config::Specific::cmakeExecutableKey, path.toLocalFile());
0367 }
0368 
0369 void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path )
0370 {
0371     writeBuildDirParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() );
0372 }
0373 
0374 void setProjectRootRelative( KDevelop::IProject* project, const QString& relative)
0375 {
0376     writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative );
0377 }
0378 
0379 void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string)
0380 {
0381     writeBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, string );
0382 }
0383 
0384 QString currentEnvironment(KDevelop::IProject* project, int builddir)
0385 {
0386     return readBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, QString(), builddir );
0387 }
0388 
0389 int currentBuildDirIndex( KDevelop::IProject* project )
0390 {
0391     KConfigGroup baseGrp = baseGroup(project);
0392 
0393     if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) )
0394         return baseGrp.readEntry<int>( Config::buildDirOverrideIndexKey, -1 );
0395 
0396     else if (baseGrp.hasKey(Config::buildDirIndexKey()))
0397         return baseGrp.readEntry<int>( Config::buildDirIndexKey(), -1 );
0398     else
0399         return baseGrp.readEntry<int>(Config::globalBuildDirIndexKey(), -1); // backwards compatibility
0400 }
0401 
0402 void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex )
0403 {
0404     writeProjectBaseParameter( project, Config::buildDirIndexKey(), QString::number (buildDirIndex) );
0405 }
0406 
0407 void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment )
0408 {
0409     writeBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, environment );
0410 }
0411 
0412 void initBuildDirConfig( KDevelop::IProject* project )
0413 {
0414     int buildDirIndex = currentBuildDirIndex( project );
0415     if (buildDirCount(project) <= buildDirIndex )
0416         setBuildDirCount( project, buildDirIndex + 1 );
0417 }
0418 
0419 int buildDirCount( KDevelop::IProject* project )
0420 {
0421     return baseGroup(project).readEntry<int>( Config::buildDirCountKey, 0 );
0422 }
0423 
0424 void setBuildDirCount( KDevelop::IProject* project, int count )
0425 {
0426     writeProjectBaseParameter( project, Config::buildDirCountKey, QString::number(count) );
0427 }
0428 
0429 void removeBuildDirConfig( KDevelop::IProject* project )
0430 {
0431     int buildDirIndex = currentBuildDirIndex( project );
0432     if ( !buildDirGroupExists( project, buildDirIndex ) )
0433     {
0434         qCWarning(CMAKE) << "build directory config" << buildDirIndex << "to be removed but does not exist";
0435         return;
0436     }
0437 
0438     int bdCount = buildDirCount(project);
0439     setBuildDirCount( project, bdCount - 1 );
0440     removeOverrideBuildDirIndex( project );
0441     setCurrentBuildDirIndex( project, -1 );
0442 
0443     // move (rename) the upper config groups to keep the numbering
0444     // if there's nothing to move, just delete the group physically
0445     if (buildDirIndex + 1 == bdCount)
0446         buildDirGroup( project, buildDirIndex ).deleteGroup();
0447 
0448     else for (int i = buildDirIndex + 1; i < bdCount; ++i)
0449     {
0450         KConfigGroup src = buildDirGroup( project, i );
0451         KConfigGroup dest = buildDirGroup( project, i - 1 );
0452         dest.deleteGroup();
0453         src.copyTo(&dest);
0454         src.deleteGroup();
0455     }
0456 }
0457 
0458 QHash<QString, QString> readCacheValues(const KDevelop::Path& cmakeCachePath, QSet<QString> variables)
0459 {
0460     QHash<QString, QString> ret;
0461     QFile file(cmakeCachePath.toLocalFile());
0462     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0463         qCWarning(CMAKE) << "couldn't open CMakeCache.txt" << cmakeCachePath;
0464         return ret;
0465     }
0466 
0467     QTextStream in(&file);
0468     while (!in.atEnd() && !variables.isEmpty())
0469     {
0470         QString line = in.readLine().trimmed();
0471         if(!line.isEmpty() && line[0].isLetter())
0472         {
0473             CacheLine c;
0474             c.readLine(line);
0475 
0476             if(!c.isCorrect())
0477                 continue;
0478 
0479             if (variables.remove(c.name())) {
0480                 ret[c.name()] = c.value();
0481             }
0482         }
0483     }
0484     return ret;
0485 }
0486 
0487 void updateConfig( KDevelop::IProject* project, int buildDirIndex)
0488 {
0489     if (buildDirIndex < 0)
0490         return;
0491 
0492     KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex );
0493     const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() ));
0494     const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt"));
0495 
0496     const QMap<QString, const char*> keys = {
0497         { QStringLiteral("CMAKE_COMMAND"), Config::Specific::cmakeExecutableKey },
0498         { QStringLiteral("CMAKE_INSTALL_PREFIX"), Config::Specific::cmakeInstallDirKey },
0499         { QStringLiteral("CMAKE_BUILD_TYPE"), Config::Specific::cmakeBuildTypeKey }
0500     };
0501 
0502     const QSet<QString> variables(keys.keyBegin(), keys.keyEnd());
0503     const QHash<QString, QString> cacheValues = readCacheValues(cacheFilePath, variables);
0504     for(auto it = cacheValues.constBegin(), itEnd = cacheValues.constEnd(); it!=itEnd; ++it) {
0505         const char* const key = keys.value(it.key());
0506         Q_ASSERT(key);
0507 
0508         // Use cache only when the config value is not set. Without this check we will always
0509         // overwrite values provided by the user in config dialog.
0510         if (buildDirGrp.readEntry(key).isEmpty() && !it.value().isEmpty())
0511         {
0512             buildDirGrp.writeEntry( key, it.value() );
0513         }
0514     }
0515 }
0516 
0517 void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex )
0518 {
0519     writeProjectBaseParameter( project, Config::buildDirOverrideIndexKey, QString::number(overrideBuildDirIndex) );
0520 }
0521 
0522 void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex )
0523 {
0524     KConfigGroup baseGrp = baseGroup(project);
0525 
0526     if( !baseGrp.hasKey(Config::buildDirOverrideIndexKey) )
0527         return;
0528     if( writeToMainIndex )
0529         baseGrp.writeEntry( Config::buildDirIndexKey(), baseGrp.readEntry(Config::buildDirOverrideIndexKey) );
0530 
0531     baseGrp.deleteEntry(Config::buildDirOverrideIndexKey);
0532 }
0533 
0534 ICMakeDocumentation* cmakeDocumentation()
0535 {
0536     return KDevelop::ICore::self()->pluginController()->extensionForPlugin<ICMakeDocumentation>(QStringLiteral("org.kdevelop.ICMakeDocumentation"));
0537 }
0538 
0539 QStringList allBuildDirs(KDevelop::IProject* project)
0540 {
0541     QStringList result;
0542     int bdCount = buildDirCount(project);
0543     result.reserve(bdCount);
0544     for (int i = 0; i < bdCount; ++i)
0545         result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey );
0546     return result;
0547 }
0548 
0549 QString executeProcess(const QString& execName, const QStringList& args)
0550 {
0551     Q_ASSERT(!execName.isEmpty());
0552     qCDebug(CMAKE) << "Executing:" << execName << "::" << args;
0553 
0554     QProcess p;
0555     QTemporaryDir tmp(QStringLiteral("kdevcmakemanager"));
0556     p.setWorkingDirectory( tmp.path() );
0557     p.start(execName, args, QIODevice::ReadOnly);
0558 
0559     if(!p.waitForFinished())
0560     {
0561         qCDebug(CMAKE) << "failed to execute:" << execName << args << p.exitStatus() << p.readAllStandardError();
0562     }
0563 
0564     QByteArray b = p.readAllStandardOutput();
0565     QString t = QString::fromUtf8(b.trimmed());
0566     return t;
0567 }
0568 
0569 QStringList supportedGenerators()
0570 {
0571     QStringList generatorNames;
0572 
0573     bool hasNinja = ICore::self() && ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder"));
0574     if (hasNinja)
0575         generatorNames << QStringLiteral("Ninja");
0576 
0577 #ifdef Q_OS_WIN
0578     // Visual Studio solution is the standard generator under windows, but we don't want to use
0579     // the VS IDE, so we need nmake makefiles
0580     generatorNames << QStringLiteral("NMake Makefiles") << QStringLiteral("MinGW Makefiles");
0581 #endif
0582     generatorNames << QStringLiteral("Unix Makefiles");
0583 
0584     return generatorNames;
0585 }
0586 
0587 QString defaultGenerator()
0588 {
0589     const QStringList generatorNames = supportedGenerators();
0590 
0591     QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator());
0592     if (defGen.isEmpty())
0593     {
0594         qCWarning(CMAKE) << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator()
0595                    << ", defaulting to 0";
0596         CMakeBuilderSettings::self()->setGenerator(0);
0597         defGen = generatorNames.at(0);
0598     }
0599     return defGen;
0600 }
0601 
0602 QVector<CMakeTest> importTestSuites(const Path &buildDir, const QString &cmakeTestFileName)
0603 {
0604     const auto cmakeTestFile = Path(buildDir, cmakeTestFileName).toLocalFile()  ;
0605     const auto contents = CMakeListsParser::readCMakeFile(cmakeTestFile);
0606     
0607     QVector<CMakeTest> tests;
0608     for (const auto& entry: contents) {
0609         if (entry.name == QLatin1String("add_test")) {
0610             auto args = entry.arguments;
0611             CMakeTest test;
0612             test.name = args.takeFirst().value;
0613             test.executable = args.takeFirst().value;
0614             test.arguments = kTransform<QStringList>(args, [](const CMakeFunctionArgument& arg) { return arg.value; });
0615             tests += test;
0616         } else if (entry.name == QLatin1String("subdirs")) {
0617             tests += importTestSuites(Path(buildDir, entry.arguments.first().value));
0618         } else if (entry.name == QLatin1String("include")) {
0619             // Include directive points directly to a .cmake file hosting the tests
0620             tests += importTestSuites(Path(buildDir, entry.arguments.first().value), QString());
0621         } else if (entry.name == QLatin1String("set_tests_properties")) {
0622             if(entry.arguments.count() < 4 || entry.arguments.count() % 2) {
0623                 qCWarning(CMAKE) << "found set_tests_properties() with unexpected number of arguments:"
0624                                  << entry.arguments.count();
0625                 continue;
0626             }
0627             if (tests.isEmpty() || entry.arguments.first().value != tests.last().name) {
0628                 qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value
0629                                  << " ...), but expected test " << tests.last().name;
0630                 continue;
0631             }
0632             if (entry.arguments[1].value != QLatin1String("PROPERTIES")) {
0633                 qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value
0634                                  << entry.arguments.at(1).value << "...), but expected PROPERTIES as second argument";
0635                 continue;
0636             }
0637             CMakeTest &test = tests.last();
0638             for (int i = 2; i < entry.arguments.count(); i += 2)
0639                 test.properties[entry.arguments[i].value] = entry.arguments[i + 1].value;
0640         }
0641     }
0642 
0643     return tests;
0644 }
0645 
0646 QVector<CMakeTest> importTestSuites(const Path &buildDir) {
0647     return importTestSuites(buildDir, QStringLiteral("CTestTestfile.cmake"));
0648 }
0649 
0650 }