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 }