diff --git a/plugins/cmake/cmakeutils.cpp b/plugins/cmake/cmakeutils.cpp index b8c07fada9..9d6b6318d3 100644 --- a/plugins/cmake/cmakeutils.cpp +++ b/plugins/cmake/cmakeutils.cpp @@ -1,737 +1,727 @@ /* KDevelop CMake Support * * Copyright 2009 Andreas Pakulat * * 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" #include "cmakeprojectdata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "icmakedocumentation.h" #include "cmakebuilddirchooser.h" #include "settings/cmakecachemodel.h" #include "debug.h" #include "cmakebuilderconfig.h" #include #include "parser/cmakelistsparser.h" using namespace KDevelop; namespace Config { namespace Old { static const QString currentBuildDirKey = QStringLiteral("CurrentBuildDir"); static const QString oldcmakeExecutableKey = QStringLiteral("CMake Binary"); // Todo: Remove at some point 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"); static const QString currentCMakeExecutableKey = QStringLiteral("Current CMake Binary"); static const QString projectRootRelativeKey = QStringLiteral("ProjectRootRelative"); static const QString projectBuildDirs = QStringLiteral("BuildDirs"); } static const QString buildDirIndexKey_ = QStringLiteral("Current Build Directory Index"); static const QString buildDirOverrideIndexKey = QStringLiteral("Temporary Build Directory Index"); static const QString buildDirCountKey = QStringLiteral("Build Directory Count"); //the used builddir will change for every runtime static QString buildDirIndexKey() { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); return buildDirIndexKey_ + '-' + currentRuntime; } namespace Specific { static const QString buildDirPathKey = QStringLiteral("Build Directory Path"); // TODO: migrate to more generic & consistent key term "CMake Executable" // 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 // considably longer period, ideally. static const QString cmakeBinaryKey = QStringLiteral("CMake Binary"); static const QString cmakeExecutableKey = QStringLiteral("CMake Executable"); 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"); static const QString buildDirRuntime = QStringLiteral("Runtime"); } static const QString groupNameBuildDir = QStringLiteral("CMake Build Directory %1"); static const QString groupName = QStringLiteral("CMake"); } // namespace Config namespace { KConfigGroup baseGroup( KDevelop::IProject* project ) { if (!project) return KConfigGroup(); 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) ); } QString readBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& aDefault, int buildDirectory ) { const int buildDirIndex = buildDirectory<0 ? CMake::currentBuildDirIndex(project) : buildDirectory; if (buildDirIndex >= 0) return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault ); else return aDefault; } void writeBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { int buildDirIndex = CMake::currentBuildDirIndex(project); if (buildDirIndex >= 0) { KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); buildDirGrp.writeEntry( key, value ); } else { qCWarning(CMAKE) << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!"; } } void writeProjectBaseParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { KConfigGroup baseGrp = baseGroup(project); baseGrp.writeEntry( key, value ); } 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); } } // namespace namespace CMake { KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs) { const KDevelop::Path buildDir(CMake::currentBuildDir(project)); const KDevelop::Path installDir(CMake::currentInstallDir(project)); KDevelop::Path::List newList; newList.reserve(dirs.size()); foreach(const QString& s, dirs) { KDevelop::Path dir; if(s.startsWith(QLatin1String("#[bin_dir]"))) { dir = KDevelop::Path(buildDir, s); } else if(s.startsWith(QLatin1String("#[install_dir]"))) { dir = KDevelop::Path(installDir, s); } else { dir = KDevelop::Path(s); } // qCDebug(CMAKE) << "resolved" << s << "to" << d; if (!newList.contains(dir)) { newList.append(dir); } } return newList; } ///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp bool checkForNeedingConfigure( KDevelop::IProject* project ) { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); const KDevelop::Path builddir = currentBuildDir(project); const bool isValid = (buildDirRuntime(project, -1) == currentRuntime || buildDirRuntime(project, -1).isEmpty()) && builddir.isValid(); if( !isValid ) { CMakeBuildDirChooser bd; bd.setProject( project ); const auto builddirs = CMake::allBuildDirs(project); bd.setAlreadyUsed( builddirs ); bd.setShowAvailableBuildDirs(!builddirs.isEmpty()); bd.setCMakeExecutable(currentCMakeExecutable(project)); if( !bd.exec() ) { return false; } if (bd.reuseBuilddir()) { CMake::setCurrentBuildDirIndex( project, bd.alreadyUsedIndex() ); } else { QString newbuilddir = bd.buildFolder().toLocalFile(); 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 " << bd.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bd.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bd.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bd.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bd.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment "; CMake::setBuildDirCount( project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex ); CMake::setCurrentBuildDir( project, bd.buildFolder() ); CMake::setCurrentInstallDir( project, bd.installPrefix() ); CMake::setCurrentExtraArguments( project, bd.extraArguments() ); CMake::setCurrentBuildType( project, bd.buildType() ); CMake::setCurrentCMakeExecutable(project, bd.cmakeExecutable()); CMake::setCurrentEnvironment( project, QString() ); } setBuildDirRuntime( project, currentRuntime ); return true; } else if( !QFile::exists( KDevelop::Path(builddir, QStringLiteral("CMakeCache.txt")).toLocalFile() ) || //TODO: maybe we could use the builder for that? !(QFile::exists( KDevelop::Path(builddir, QStringLiteral("Makefile")).toLocalFile() ) || QFile::exists( KDevelop::Path(builddir, QStringLiteral("build.ninja")).toLocalFile() ) ) ) { // User entered information already, but cmake hasn't actually been run yet. setBuildDirRuntime( project, currentRuntime ); return true; } setBuildDirRuntime( project, currentRuntime ); return false; } QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir) { const QString buildPath = buildDir.toLocalFile(); QHash 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; } KDevelop::Path projectRoot(KDevelop::IProject* project) { if (!project) { return {}; } return project->path().cd(CMake::projectRootRelative(project)); } KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir ) { return KDevelop::Path(readBuildDirParameter( project, Config::Specific::buildDirPathKey, QString(), builddir )); } KDevelop::Path commandsFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json")); } KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt")); } QString currentBuildType( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, QStringLiteral("Release"), builddir ); } QString findExecutable() { auto cmake = QStandardPaths::findExecutable(QStringLiteral("cmake")); #ifdef Q_OS_WIN if (cmake.isEmpty()) cmake = QStandardPaths::findExecutable("cmake",{ "C:\\Program Files (x86)\\CMake\\bin", "C:\\Program Files\\CMake\\bin", "C:\\Program Files (x86)\\CMake 2.8\\bin", "C:\\Program Files\\CMake 2.8\\bin"}); #endif return cmake; } KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir) { const auto defaultCMakeExecutable = CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile(); 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); } KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir ) { const QString defaultInstallDir = #ifdef Q_OS_WIN QStringLiteral("C:\\Program Files"); #else QStringLiteral("/usr/local"); #endif return KDevelop::Path(readBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, defaultInstallDir, builddir )); } QString projectRootRelative( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." ); } bool hasProjectRootRelative(KDevelop::IProject* project) { return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey ); } QString currentExtraArguments( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, QString(), builddir ); } -QString currentCmakeExecutable( KDevelop::IProject* project, int builddir ) -{ - return readBuildDirParameter( project, Config::Specific::cmakeExecutableKey, QString(), builddir ); -} - void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() ); } void setCurrentBuildType( KDevelop::IProject* project, const QString& type ) { writeBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, type ); } 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()); } void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() ); } void setProjectRootRelative( KDevelop::IProject* project, const QString& relative) { writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative ); } void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string) { writeBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, string ); } -void setCurrentCmakeExecutable( KDevelop::IProject* project, const QString& string ) -{ - writeBuildDirParameter( project, Config::Specific::cmakeExecutableKey, string ); -} - QString currentEnvironment(KDevelop::IProject* project, int builddir) { return readBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, QString(), builddir ); } int currentBuildDirIndex( KDevelop::IProject* project ) { KConfigGroup baseGrp = baseGroup(project); if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) ) return baseGrp.readEntry( Config::buildDirOverrideIndexKey, 0 ); else if (baseGrp.hasKey(Config::buildDirIndexKey())) return baseGrp.readEntry( Config::buildDirIndexKey(), 0 ); else return baseGrp.readEntry( Config::buildDirIndexKey_, 0 ); // backwards compatibility } void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirIndexKey(), QString::number (buildDirIndex) ); } void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ) { writeBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, environment ); } 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( 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 ) ) { qCWarning(CMAKE) << "build directory config" << buildDirIndex << "to be removed but does not exist"; 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(); } } QHash readCacheValues(const KDevelop::Path& cmakeCachePath, QSet variables) { QHash 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(); if(!line.isEmpty() && line[0].isLetter()) { CacheLine c; c.readLine(line); if(!c.isCorrect()) continue; if (variables.remove(c.name())) { ret[c.name()] = c.value(); } } } return ret; } void updateConfig( KDevelop::IProject* project, int buildDirIndex) { if (buildDirIndex < 0) return; KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() )); const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt")); const QMap keys = { { QStringLiteral("CMAKE_COMMAND"), Config::Specific::cmakeExecutableKey }, { QStringLiteral("CMAKE_INSTALL_PREFIX"), Config::Specific::cmakeInstallDirKey }, { QStringLiteral("CMAKE_BUILD_TYPE"), Config::Specific::cmakeBuildTypeKey } }; const QHash 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()); // 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() ); } } } void attemptMigrate( KDevelop::IProject* project ) { if ( !baseGroup(project).hasKey( Config::Old::projectBuildDirs ) ) { qCDebug(CMAKE) << "CMake settings migration: already done, exiting"; return; } KConfigGroup baseGrp = baseGroup(project); KDevelop::Path buildDir( baseGrp.readEntry( Config::Old::currentBuildDirKey, QString() ) ); int buildDirIndex = -1; const QStringList existingBuildDirs = baseGrp.readEntry( Config::Old::projectBuildDirs, QStringList() ); { // also, find current build directory in this list (we need an index, not path) QString currentBuildDirCanonicalPath = QDir( buildDir.toLocalFile() ).canonicalPath(); for( int i = 0; i < existingBuildDirs.count(); ++i ) { const QString& nextBuildDir = existingBuildDirs.at(i); if( QDir(nextBuildDir).canonicalPath() == currentBuildDirCanonicalPath ) { buildDirIndex = i; } } } int buildDirsCount = existingBuildDirs.count(); 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 << ")"; baseGrp.writeEntry( Config::buildDirCountKey, buildDirsCount ); baseGrp.writeEntry( Config::buildDirIndexKey(), buildDirIndex ); for (int i = 0; i < buildDirsCount; ++i) { qCDebug(CMAKE) << "CMake settings migration: writing group" << i << ": path" << existingBuildDirs.at(i); KConfigGroup buildDirGrp = buildDirGroup( project, i ); buildDirGrp.writeEntry( Config::Specific::buildDirPathKey, existingBuildDirs.at(i) ); } baseGrp.deleteEntry( Config::Old::currentBuildDirKey ); baseGrp.deleteEntry( Config::Old::currentCMakeExecutableKey ); 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 ) baseGrp.writeEntry( Config::buildDirIndexKey(), baseGrp.readEntry(Config::buildDirOverrideIndexKey) ); baseGrp.deleteEntry(Config::buildDirOverrideIndexKey); } ICMakeDocumentation* cmakeDocumentation() { return KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.ICMakeDocumentation")); } QStringList allBuildDirs(KDevelop::IProject* project) { QStringList result; int bdCount = buildDirCount(project); for (int i = 0; i < bdCount; ++i) result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey ); return result; } QString executeProcess(const QString& execName, const QStringList& args) { Q_ASSERT(!execName.isEmpty()); qCDebug(CMAKE) << "Executing:" << execName << "::" << args; QProcess p; QTemporaryDir tmp(QStringLiteral("kdevcmakemanager")); p.setWorkingDirectory( tmp.path() ); p.start(execName, args, QIODevice::ReadOnly); if(!p.waitForFinished()) { qCDebug(CMAKE) << "failed to execute:" << execName << args << p.exitStatus() << p.readAllStandardError(); } QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); return t; } QStringList supportedGenerators() { QStringList generatorNames; bool hasNinja = ICore::self() && ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder")); if (hasNinja) generatorNames << QStringLiteral("Ninja"); #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 generatorNames << QStringLiteral("NMake Makefiles") << QStringLiteral("MinGW Makefiles"); #endif generatorNames << QStringLiteral("Unix Makefiles"); return generatorNames; } QString defaultGenerator() { const QStringList generatorNames = supportedGenerators(); QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator()); if (defGen.isEmpty()) { qCWarning(CMAKE) << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator() << ", defaulting to 0"; CMakeBuilderSettings::self()->setGenerator(0); defGen = generatorNames.at(0); } return defGen; } QVector importTestSuites(const Path &buildDir) { const auto contents = CMakeListsParser::readCMakeFile(buildDir.toLocalFile() + "/CTestTestfile.cmake"); QVector tests; for (const auto& entry: contents) { if (entry.name == QLatin1String("add_test")) { auto args = entry.arguments; Test test; test.name = args.takeFirst().value; test.executable = args.takeFirst().value; test.arguments = kTransform(args, [](const CMakeFunctionArgument& arg) { return arg.value; }); tests += test; } else if (entry.name == QLatin1String("subdirs")) { tests += importTestSuites(Path(buildDir, entry.arguments.constFirst().value)); } 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; } if (tests.isEmpty() || entry.arguments.constFirst().value != tests.constLast().name) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.constFirst().value << " ...), but expected test " << tests.constLast().name; continue; } if (entry.arguments[1].value != QLatin1String("PROPERTIES")) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.constFirst().value << 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; } } return tests; } } diff --git a/plugins/cmake/cmakeutils.h b/plugins/cmake/cmakeutils.h index 357478f3cc..3642b94c21 100644 --- a/plugins/cmake/cmakeutils.h +++ b/plugins/cmake/cmakeutils.h @@ -1,277 +1,267 @@ /* KDevelop CMake Support * * Copyright 2009 Andreas Pakulat * * 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. */ #ifndef CMAKEUTILS_H #define CMAKEUTILS_H #include "cmakecommonexport.h" #include #include #include namespace KDevelop { class ProjectBaseItem; class IProject; class Path; } struct Test; class ICMakeDocumentation; class CMakeCacheModel; template static T kTransform(const Q& list, W func) { T ret; ret.reserve(list.size()); for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) ret += func(*it); return ret; } template static int kIndexOf(const Q& list, W func) { int i = 0; for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) { if (func(*it)) return i; ++i; } return -1; } template static T kFilter(const Q &input, _UnaryOperation op) { T ret; for(const auto& v : input) { if (op(v)) ret += v; } return ret; } namespace CMake { /** * Checks whether there's a need to run cmake for the given project item * This is the case if no builddir has been specified, in which case * it asks for one. * * @returns true if configure should be run, false otherwise */ KDEVCMAKECOMMON_EXPORT bool checkForNeedingConfigure( KDevelop::IProject* project ); /** * @returns the current builddir for the given project or an empty url if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the path to the 'compile_commands.json' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path commandsFile( KDevelop::IProject* project ); /** * @returns the path to the 'CMakeFiles/TargetDirectories.txt' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path targetDirectoriesFile( KDevelop::IProject* project ); /** * @returns the current build type for the given project or "Release" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentBuildType( KDevelop::IProject* project, int builddir = -1 ); - /** - * @returns the current build type for the given project or "Release" as default value. - */ - KDEVCMAKECOMMON_EXPORT QString currentCmakeExecutable( KDevelop::IProject* project, int builddir = -1 ); - /** * @returns the CMake executable, taking into account standard * installation dirs on Windows, or empty string in case of failure. */ KDEVCMAKECOMMON_EXPORT QString findExecutable(); /** * @returns the current CMake executable for the given project, falling back to * CMakeBuilderSettings::self()->cmakeExecutable() as the user-specified KDevelop-wide default value. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir = -1); /** * @returns the current install dir for the given project or "/usr/local" as default value. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the current extra arguments for the given project or "" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentExtraArguments( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT QString projectRootRelative( KDevelop::IProject* project ); /** * @returns whether there's projectRootRelative defined */ KDEVCMAKECOMMON_EXPORT bool hasProjectRootRelative( KDevelop::IProject* project ); /** * Extracts target names from builddir/CMakeFiles/TargetDirectories.txt and maps corresponding source locations to them. */ KDEVCMAKECOMMON_EXPORT QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir); /** * Convenience function to get the project root. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path projectRoot( KDevelop::IProject* project ); /** * @returns the environment configuration for a @p project */ KDEVCMAKECOMMON_EXPORT QString currentEnvironment( KDevelop::IProject* project, int builddir = -1 ); /** * Sets the current install dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path &path ); /** * Sets the current build type for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildType( KDevelop::IProject* project, const QString& type ); /** * Sets the current CMake executable for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path); /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ); /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setProjectRootRelative( KDevelop::IProject* project, const QString& path); /** * Sets the current extra arguments for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentExtraArguments( KDevelop::IProject* project, const QString& args ); - /** - * Sets the current CMake binary the given project. - */ - KDEVCMAKECOMMON_EXPORT void setCurrentCmakeExecutable(KDevelop::IProject* project, const QString& string); - /** * Obtains a cmake documentation instance if it exists */ KDEVCMAKECOMMON_EXPORT ICMakeDocumentation* cmakeDocumentation(); /** * Retrieves the configured build directories for @p project. */ KDEVCMAKECOMMON_EXPORT QStringList allBuildDirs(KDevelop::IProject* project); /** * Attempts to migrate the CMake configuration to per-builddir format. * Silently returns if the migration has already been performed. */ KDEVCMAKECOMMON_EXPORT void attemptMigrate( KDevelop::IProject* project ); /** * Attempts to update CMake configuration keys from the cache data (CMakeCache.txt) * * The model is created based on build directory path for the given index @p buildDirectory */ KDEVCMAKECOMMON_EXPORT void updateConfig( KDevelop::IProject* project, int buildDirectory); /** * Returns the current build directory count. */ KDEVCMAKECOMMON_EXPORT int buildDirCount( KDevelop::IProject* project ); /** * Sets the build directory count (equivalent to adding a new build directory). */ KDEVCMAKECOMMON_EXPORT void setBuildDirCount( KDevelop::IProject* project, int count ); /** * @returns the current builddir index for the given project or -1 if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT int currentBuildDirIndex( KDevelop::IProject *project ); /** * Sets the current build dir index for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ); /** * A hack to avoid adding an optional "build directory index" parameter to all functions here. * This function sets an alternate build directory index key that overrides regular build directory index. */ KDEVCMAKECOMMON_EXPORT void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ); /** * This removes build directory override key (\ref setOverrideBuildDirIndex). * Silently returns if there is no override. * * @param writeToMainIndex Whether the overridden index should be saved to regular */ KDEVCMAKECOMMON_EXPORT void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex = false ); /** * Sets the environment configuration for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ); /** * Removes current build directory (overridden or not) from the project configuration. * Override is then cleared and index set to -1. */ KDEVCMAKECOMMON_EXPORT void removeBuildDirConfig( KDevelop::IProject* project ); KDEVCMAKECOMMON_EXPORT KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs); /** Runs the process specified by @p execName with @p args */ KDEVCMAKECOMMON_EXPORT QString executeProcess(const QString& execName, const QStringList& args=QStringList()); /** Opens @p cmakeCachePath and parses it, returns a hash with the specified keys in @p variables */ KDEVCMAKECOMMON_EXPORT QHash readCacheValues(const KDevelop::Path& cmakeCachePath, QSet variables); KDEVCMAKECOMMON_EXPORT QStringList supportedGenerators(); KDEVCMAKECOMMON_EXPORT QString defaultGenerator(); KDEVCMAKECOMMON_EXPORT QVector importTestSuites(const KDevelop::Path &buildDir); } #endif diff --git a/plugins/cmake/settings/cmakepreferences.cpp b/plugins/cmake/settings/cmakepreferences.cpp index a5ae6534df..f2bcb81f54 100644 --- a/plugins/cmake/settings/cmakepreferences.cpp +++ b/plugins/cmake/settings/cmakepreferences.cpp @@ -1,411 +1,409 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2008 Aleix Pol * * 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 "cmakepreferences.h" #include #include #include #include #include #include #include #include #include #include "ui_cmakebuildsettings.h" #include "cmakecachedelegate.h" #include "cmakebuilddirchooser.h" #include "cmakebuilderconfig.h" #include #include #include #include #include #include using namespace KDevelop; CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent) : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr) { m_prefsUi = new Ui::CMakeBuildSettings; m_prefsUi->setupUi(this); m_prefsUi->addBuildDir->setIcon(QIcon::fromTheme( QStringLiteral("list-add") )); m_prefsUi->removeBuildDir->setIcon(QIcon::fromTheme( QStringLiteral("list-remove") )); m_prefsUi->addBuildDir->setText(QString()); m_prefsUi->removeBuildDir->setText(QString()); m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList)); m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection); m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true); m_prefsUi->cacheList->verticalHeader()->hide(); // configure the extraArguments widget to span the advanced box width but not // expand the dialog to the width of the longest element in the argument history. // static_cast needed because KComboBox::minimumSizeHint() override mistakingly made it protected m_prefsUi->extraArguments->setMinimumWidth(static_cast(m_prefsUi->extraArguments)->minimumSizeHint().width()); m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_prefsUi->extraArguments); connect(m_prefsUi->buildDirs, static_cast(&KComboBox::currentIndexChanged), this, &CMakePreferences::buildDirChanged); connect(m_prefsUi->showInternal, &QCheckBox::stateChanged, this, &CMakePreferences::showInternal); connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir); connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir); connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced); connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &CMakePreferences::changed); connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured, this, &CMakePreferences::changed); connect(m_prefsUi->installationPrefix, &KUrlRequester::textChanged, this, &CMakePreferences::changed); connect(m_prefsUi->buildType, static_cast(&QComboBox::currentIndexChanged), this, &CMakePreferences::changed); connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::editTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->cMakeExecutable, &KUrlRequester::textChanged, this, &CMakePreferences::changed); showInternal(m_prefsUi->showInternal->checkState()); m_subprojFolder = Path(options.projectTempFile).parent(); qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile; // foreach(const QVariant &v, args) // { // qCDebug(CMAKE) << "arg: " << v.toString(); // } m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment); m_prefsUi->showAdvanced->setChecked(false); showAdvanced(false); reset(); // load the initial values } CMakePreferences::~CMakePreferences() { CMake::removeOverrideBuildDirIndex(m_project); delete m_extraArgumentsHistory; delete m_prefsUi; } void CMakePreferences::initAdvanced() { m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); m_prefsUi->installationPrefix->setText(CMake::currentInstallDir(m_project).toLocalFile()); m_prefsUi->installationPrefix->setMode(KFile::Directory); const QString buildType = CMake::currentBuildType(m_project); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } m_prefsUi->buildType->setCurrentIndex(m_prefsUi->buildType->findText(buildType)); m_prefsUi->extraArguments->setEditText(CMake::currentExtraArguments(m_project)); - m_prefsUi->cMakeExecutable->setText(CMake::currentCmakeExecutable(m_project)); + m_prefsUi->cMakeExecutable->setText(CMake::currentCMakeExecutable(m_project).toLocalFile()); } void CMakePreferences::reset() { qCDebug(CMAKE) << "********loading"; m_prefsUi->buildDirs->clear(); m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) ); CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) ); initAdvanced(); m_srcFolder = m_project->path(); m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0); // QString cmDir=group.readEntry("CMakeDirectory"); // m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir)); // qCDebug(CMAKE) << "cmakedir" << cmDir; } void CMakePreferences::apply() { qCDebug(CMAKE) << "*******saving"; // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir(). // We won't rewrite it here based on the data from m_prefsUi->buildDirs. CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection int savedBuildDir = CMake::currentBuildDirIndex(m_project); if (savedBuildDir < 0) { // no build directory exists: skip any writing to config file as well as configuring return; } CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() ); CMake::setCurrentInstallDir( m_project, Path(m_prefsUi->installationPrefix->text()) ); const QString buildType = m_prefsUi->buildType->currentText(); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } CMake::setCurrentBuildType( m_project, buildType ); CMake::setCurrentExtraArguments( m_project, m_prefsUi->extraArguments->currentText() ); - CMake::setCurrentCmakeExecutable( m_project, m_prefsUi->cMakeExecutable->text() ); + CMake::setCurrentCMakeExecutable( m_project, Path(m_prefsUi->cMakeExecutable->text()) ); qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project); qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project); qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project); qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project); qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project); - qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCmakeExecutable(m_project); - //We run cmake on the builddir to generate it configure(); } void CMakePreferences::defaults() { // do nothing } void CMakePreferences::configureCacheView() { // Sets up the cache view after model re-creation/reset. // Emits changed(false) because model re-creation probably means // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want. m_prefsUi->cacheList->setModel(m_currentModel); m_prefsUi->cacheList->hideColumn(1); m_prefsUi->cacheList->hideColumn(3); m_prefsUi->cacheList->hideColumn(4); m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200); if( m_currentModel ) { m_prefsUi->cacheList->setEnabled( true ); foreach(const QModelIndex & idx, m_currentModel->persistentIndices()) { m_prefsUi->cacheList->openPersistentEditor(idx); } } else { m_prefsUi->cacheList->setEnabled( false ); } showInternal(m_prefsUi->showInternal->checkState()); } void CMakePreferences::updateCache(const Path &newBuildDir) { const Path file = newBuildDir.isValid() ? Path(newBuildDir, QStringLiteral("CMakeCache.txt")) : Path(); if(QFile::exists(file.toLocalFile())) { if (m_currentModel) { m_currentModel->deleteLater(); } m_currentModel = new CMakeCacheModel(this, file); configureCacheView(); connect(m_currentModel, &CMakeCacheModel::itemChanged, this, &CMakePreferences::cacheEdited); connect(m_currentModel, &CMakeCacheModel::modelReset, this, &CMakePreferences::configureCacheView); connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, &CMakePreferences::listSelectionChanged); } else { disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr); if (m_currentModel) { m_currentModel->deleteLater(); m_currentModel = nullptr; } configureCacheView(); } if( !m_currentModel ) emit changed(); } void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& ) { qCDebug(CMAKE) << "item " << index << " selected"; QModelIndex idx = index.sibling(index.row(), 3); QModelIndex idxType = index.sibling(index.row(), 1); QString comment=QStringLiteral("%1. %2") .arg(m_currentModel->itemFromIndex(idxType)->text()) .arg(m_currentModel->itemFromIndex(idx)->text()); m_prefsUi->commentText->setText(comment); } void CMakePreferences::showInternal(int state) { if(!m_currentModel) return; bool showAdv=(state == Qt::Checked); for(int i=0; irowCount(); i++) { bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i)); m_prefsUi->cacheList->setRowHidden(i, hidden); } } void CMakePreferences::buildDirChanged(int index) { CMake::setOverrideBuildDirIndex( m_project, index ); const Path buildDir = CMake::currentBuildDir(m_project); initAdvanced(); updateCache(buildDir); qCDebug(CMAKE) << "builddir Changed" << buildDir; emit changed(); } void CMakePreferences::cacheUpdated() { const Path buildDir = CMake::currentBuildDir(m_project); updateCache(buildDir); qCDebug(CMAKE) << "cache updated for" << buildDir; } void CMakePreferences::createBuildDir() { CMakeBuildDirChooser bdCreator; bdCreator.setProject( m_project ); // NOTE: (on removing the trailing slashes) // Generally, we have no clue about how shall a trailing slash look in the current system. // Moreover, the slash may be a part of the filename. // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed paths? QStringList used = CMake::allBuildDirs( m_project ); bdCreator.setAlreadyUsed(used); bdCreator.setCMakeExecutable(Path(CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile())); if(bdCreator.exec()) { int addedBuildDirIndex = m_prefsUi->buildDirs->count(); // 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 " << bdCreator.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bdCreator.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment empty"; CMake::setOverrideBuildDirIndex( m_project, addedBuildDirIndex ); CMake::setBuildDirCount( m_project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() ); CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() ); CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() ); CMake::setCurrentBuildType( m_project, bdCreator.buildType() ); CMake::setCurrentCMakeExecutable(m_project, bdCreator.cmakeExecutable()); CMake::setCurrentEnvironment( m_project, QString() ); QString newbuilddir = bdCreator.buildFolder().toLocalFile(); m_prefsUi->buildDirs->addItem( newbuilddir ); m_prefsUi->buildDirs->setCurrentIndex( addedBuildDirIndex ); m_prefsUi->removeBuildDir->setEnabled( true ); qCDebug(CMAKE) << "Emitting changed signal for cmake kcm"; emit changed(); } //TODO: Save it for next runs } void CMakePreferences::removeBuildDir() { int curr=m_prefsUi->buildDirs->currentIndex(); if(curr < 0) return; Path removedPath = CMake::currentBuildDir( m_project ); QString removed = removedPath.toLocalFile(); if(QDir(removed).exists()) { KMessageBox::ButtonCode ret = KMessageBox::warningYesNo(this, i18n("The %1 directory is about to be removed in KDevelop's list.\n" "Do you want KDevelop to remove it in the file system as well?", removed)); if(ret == KMessageBox::Yes) { auto deleteJob = KIO::del(removedPath.toUrl()); KJobWidgets::setWindow(deleteJob, this); if (!deleteJob->exec()) KMessageBox::error(this, i18n("Could not remove: %1", removed)); } } qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr; qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath; qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project ); qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project ); qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project ); qCDebug(CMAKE) << "removing from cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project ); CMake::removeBuildDirConfig(m_project); m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged() if(m_prefsUi->buildDirs->count()==0) m_prefsUi->removeBuildDir->setEnabled(false); emit changed(); } void CMakePreferences::configure() { IProjectBuilder *b=m_project->buildSystemManager()->builder(); KJob* job=b->configure(m_project); if( m_currentModel ) { QVariantMap map = m_currentModel->changedValues(); job->setProperty("extraCMakeCacheValues", map); connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset); } else { connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated); } connect(job, &KJob::finished, m_project, &IProject::reloadModel); ICore::self()->runController()->registerJob(job); } void CMakePreferences::showAdvanced(bool v) { qCDebug(CMAKE) << "toggle pressed: " << v; m_prefsUi->advancedBox->setHidden(!v); } QString CMakePreferences::name() const { return i18n("CMake"); } QString CMakePreferences::fullName() const { return i18n("Configure CMake settings"); } QIcon CMakePreferences::icon() const { return QIcon::fromTheme("cmake"); } diff --git a/plugins/cmakebuilder/cmakejob.cpp b/plugins/cmakebuilder/cmakejob.cpp index baac02afe0..0df8c2f8c1 100644 --- a/plugins/cmakebuilder/cmakejob.cpp +++ b/plugins/cmakebuilder/cmakejob.cpp @@ -1,146 +1,143 @@ /* KDevelop CMake Support * * Copyright 2006-2007 Andreas Pakulat * Copyright 2008 Hamish Rodda * * 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 "cmakejob.h" #include #include #include #include #include #include #include #include #include #include #include "cmakeutils.h" #include "debug.h" using namespace KDevelop; CMakeJob::CMakeJob(QObject* parent) : OutputExecuteJob(parent) , m_project(nullptr) { setCapabilities( Killable ); setFilteringStrategy( OutputModel::CompilerFilter ); setProperties( NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint ); setToolTitle( i18n("CMake") ); setStandardToolView( KDevelop::IOutputView::BuildView ); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); } void CMakeJob::start() { qCDebug(KDEV_CMAKEBUILDER) << "Configuring cmake" << workingDirectory(); if( !m_project ) { setError(NoProjectError); setErrorText(QStringLiteral("Internal error: no project specified to configure.")); emitResult(); return; } QDir::temp().mkpath(workingDirectory().toLocalFile()); CMake::updateConfig( m_project, CMake::currentBuildDirIndex(m_project) ); OutputExecuteJob::start(); } QUrl CMakeJob::workingDirectory() const { KDevelop::Path path = CMake::currentBuildDir( m_project ); qCDebug(KDEV_CMAKEBUILDER) << "builddir: " << path; Q_ASSERT(path.isValid()); //We cannot get the project folder as a build directory! return path.toUrl(); } QStringList CMakeJob::commandLine() const { QStringList args; - QString cmakeProjectExecutable = CMake::currentCmakeExecutable( m_project ); - args << - (cmakeProjectExecutable.isEmpty() ? - CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile() : cmakeProjectExecutable); + args << CMake::currentCMakeExecutable( m_project ).toLocalFile(); args << QStringLiteral("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"); QString installDir = CMake::currentInstallDir( m_project ).toLocalFile(); if( !installDir.isEmpty() ) { args << QStringLiteral("-DCMAKE_INSTALL_PREFIX=%1").arg(installDir); } QString buildType = CMake::currentBuildType( m_project ); if( !buildType.isEmpty() ) { args << QStringLiteral("-DCMAKE_BUILD_TYPE=%1").arg(buildType); } QVariantMap cacheArgs = property("extraCMakeCacheValues").toMap(); for( auto it = cacheArgs.constBegin(), itEnd = cacheArgs.constEnd(); it!=itEnd; ++it) { args << QStringLiteral("-D%1=%2").arg(it.key()).arg(it.value().toString()); } auto rt = ICore::self()->runtimeController()->currentRuntime(); //if we are creating a new build directory, we'll want to specify the generator QDir builddir(rt->pathInRuntime(CMake::currentBuildDir( m_project )).toLocalFile()); if(!builddir.exists() || !builddir.exists(QStringLiteral("CMakeCache.txt"))) { CMakeBuilderSettings::self()->load(); args << QStringLiteral("-G") << CMake::defaultGenerator(); } QString cmakeargs = CMake::currentExtraArguments( m_project ); if( !cmakeargs.isEmpty() ) { KShell::Errors err; QStringList tmp = KShell::splitArgs( cmakeargs, KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err == KShell::NoError ) { args += tmp; } else { qCWarning(KDEV_CMAKEBUILDER) << "Ignoring cmake Extra arguments"; if( err == KShell::BadQuoting ) { qCWarning(KDEV_CMAKEBUILDER) << "CMake arguments badly quoted:" << cmakeargs; } else { qCWarning(KDEV_CMAKEBUILDER) << "CMake arguments had meta character:" << cmakeargs; } } } args << rt->pathInRuntime(CMake::projectRoot( m_project )).toLocalFile(); return args; } QString CMakeJob::environmentProfile() const { return CMake::currentEnvironment( m_project ); } void CMakeJob::setProject(KDevelop::IProject* project) { m_project = project; if (m_project) setJobName( i18n("CMake: %1", m_project->name()) ); }