diff --git a/kdevplatform/shell/runtimecontroller.cpp b/kdevplatform/shell/runtimecontroller.cpp index 90c47fb980..fb66148cec 100644 --- a/kdevplatform/shell/runtimecontroller.cpp +++ b/kdevplatform/shell/runtimecontroller.cpp @@ -1,155 +1,155 @@ /* Copyright 2017 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runtimecontroller.h" #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "mainwindow.h" #include "debug.h" using namespace KDevelop; class IdentityRuntime : public IRuntime { QString name() const override { return i18n("Host System"); } void startProcess(KProcess *process) const override { connect(process, &QProcess::errorOccurred, this, [process](QProcess::ProcessError error) { qCWarning(SHELL) << "error:" << error << process->program() << process->errorString(); }); process->start(); } void startProcess(QProcess *process) const override { connect(process, &QProcess::errorOccurred, this, [process](QProcess::ProcessError error) { qCWarning(SHELL) << "error:" << error << process->program() << process->errorString(); }); process->start(); } KDevelop::Path pathInHost(const KDevelop::Path & runtimePath) const override { return runtimePath; } KDevelop::Path pathInRuntime(const KDevelop::Path & localPath) const override { return localPath; } void setEnabled(bool /*enabled*/) override {} QByteArray getenv(const QByteArray & varname) const override { return qgetenv(varname); } }; KDevelop::RuntimeController::RuntimeController(KDevelop::Core* core) : m_core(core) { const bool haveUI = (core->setupFlags() != Core::NoUi); if (haveUI) { m_runtimesMenu.reset(new QMenu()); } addRuntimes(new IdentityRuntime); - setCurrentRuntime(m_runtimes.constFirst()); + setCurrentRuntime(m_runtimes.first()); if (haveUI) { setupActions(); } } KDevelop::RuntimeController::~RuntimeController() { m_currentRuntime->setEnabled(false); m_currentRuntime = nullptr; } void RuntimeController::setupActions() { // TODO not multi-window friendly, FIXME KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); auto action = new QAction(this); action->setStatusTip(i18n("Allows to select a runtime")); action->setMenu(m_runtimesMenu.data()); action->setIcon(QIcon::fromTheme(QStringLiteral("file-library-symbolic"))); auto updateActionText = [action](IRuntime* currentRuntime){ action->setText(i18n("Runtime: %1", currentRuntime->name())); }; connect(this, &RuntimeController::currentRuntimeChanged, action, updateActionText); updateActionText(m_currentRuntime); ac->addAction(QStringLiteral("switch_runtimes"), action); } void KDevelop::RuntimeController::initialize() { } KDevelop::IRuntime * KDevelop::RuntimeController::currentRuntime() const { Q_ASSERT(m_currentRuntime); return m_currentRuntime; } QVector KDevelop::RuntimeController::availableRuntimes() const { return m_runtimes; } void KDevelop::RuntimeController::setCurrentRuntime(KDevelop::IRuntime* runtime) { if (m_currentRuntime == runtime) return; Q_ASSERT(m_runtimes.contains(runtime)); if (m_currentRuntime) { m_currentRuntime->setEnabled(false); } qCDebug(SHELL) << "setting runtime..." << runtime->name() << "was" << m_currentRuntime; m_currentRuntime = runtime; m_currentRuntime->setEnabled(true); Q_EMIT currentRuntimeChanged(runtime); } void KDevelop::RuntimeController::addRuntimes(KDevelop::IRuntime * runtime) { if (!runtime->parent()) runtime->setParent(this); if (m_core->setupFlags() != Core::NoUi) { QAction* runtimeAction = new QAction(runtime->name(), m_runtimesMenu.data()); runtimeAction->setCheckable(true); connect(runtimeAction, &QAction::triggered, runtime, [this, runtime]() { setCurrentRuntime(runtime); }); connect(this, &RuntimeController::currentRuntimeChanged, runtimeAction, [runtimeAction, runtime](IRuntime* currentRuntime) { runtimeAction->setChecked(runtime == currentRuntime); }); connect(runtime, &QObject::destroyed, this, [this, runtimeAction](QObject* obj) { Q_ASSERT(m_currentRuntime != obj); m_runtimes.removeAll(qobject_cast(obj)); delete runtimeAction; }); m_runtimesMenu->addAction(runtimeAction); } else { connect(runtime, &QObject::destroyed, this, [this](QObject* obj) { Q_ASSERT(m_currentRuntime != obj); m_runtimes.removeAll(qobject_cast(obj)); }); } m_runtimes << runtime; } diff --git a/plugins/android/androidruntime.cpp b/plugins/android/androidruntime.cpp index b57752b6e6..6b25d773e4 100644 --- a/plugins/android/androidruntime.cpp +++ b/plugins/android/androidruntime.cpp @@ -1,91 +1,91 @@ /* Copyright 2017 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "androidruntime.h" #include "androidpreferencessettings.h" #include "debug_android.h" #include #include using namespace KDevelop; AndroidPreferencesSettings* AndroidRuntime::s_settings = nullptr; AndroidRuntime::AndroidRuntime() : KDevelop::IRuntime() { } AndroidRuntime::~AndroidRuntime() { } void AndroidRuntime::setEnabled(bool /*enable*/) { } static void setEnvironmentVariables(QProcess* process) { auto env = process->processEnvironment(); env.insert("ANDROID_NDK", AndroidRuntime::s_settings->ndk()); env.insert("ANDROID_SDK_ROOT", AndroidRuntime::s_settings->sdk()); process->setProcessEnvironment(env); } //integrates with ECM static QStringList args() { return { "-DCMAKE_TOOLCHAIN_FILE=" + AndroidRuntime::s_settings->cmakeToolchain(), "-DANDROID_ABI=" + AndroidRuntime::s_settings->abi(), "-DANDROID_NDK=" + AndroidRuntime::s_settings->ndk(), "-DANDROID_TOOLCHAIN=" + AndroidRuntime::s_settings->toolchain(), "-DANDROID_API_LEVEL=" + AndroidRuntime::s_settings->api(), "-DANDROID_ARCHITECTURE=" + AndroidRuntime::s_settings->arch(), "-DANDROID_SDK_BUILD_TOOLS_REVISION=" + AndroidRuntime::s_settings->buildtools() }; } void AndroidRuntime::startProcess(QProcess* process) const { if (process->program().endsWith(QLatin1String("cmake"))) { process->setArguments(process->arguments() << args()); setEnvironmentVariables(process); } qCDebug(ANDROID) << "starting qprocess" << process->program() << process->arguments(); process->start(); } void AndroidRuntime::startProcess(KProcess* process) const { - if (process->program().constFirst().endsWith(QLatin1String("cmake"))) { + if (process->program().first().endsWith(QLatin1String("cmake"))) { process->setProgram(process->program() << args()); setEnvironmentVariables(process); } qCDebug(ANDROID) << "starting kprocess" << process->program().join(' '); process->start(); } QByteArray AndroidRuntime::getenv(const QByteArray &varname) const { return qgetenv(varname); } diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp index a3f0714556..07d1067bc8 100644 --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -1,510 +1,532 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsesession.h" #include #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QVector extraArgs() { const auto extraArgsString = QString::fromLatin1(qgetenv("KDEV_CLANG_EXTRA_ARGUMENTS")); const auto extraArgs = KShell::splitArgs(extraArgsString); // transform to list of QByteArrays QVector result; result.reserve(extraArgs.size()); foreach (const QString& arg, extraArgs) { result << arg.toLatin1(); } clangDebug() << "Passing extra arguments to clang:" << result; return result; } +void sanitizeArguments(QVector& arguments) +{ + // We remove the -Werror flag, and replace -Werror=foo by -Wfoo. + // Warning as error may cause problem to the clang parser. + const auto asError = QByteArrayLiteral("-Werror="); + for (auto& argument : arguments) { + if (argument == "-Werror") { + argument.clear(); + } else if (argument.startsWith(asError)) { + // replace -Werror=foo by -Wfoo + argument.remove(2, asError.length() - 2); + } + } +} + QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=26913 if (path.endsWith(QLatin1String(".cl"), Qt::CaseInsensitive)) { return {QByteArrayLiteral("-xcl")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=23700 if (path.endsWith(QLatin1String(".cu"), Qt::CaseInsensitive)) { return {QByteArrayLiteral("-xcuda")}; } if (parserSettings.parserOptions.isEmpty()) { // The parserOptions can be empty for some unit tests that use ParseSession directly auto defaultArguments = ClangSettingsManager::self()->parserSettings(path).toClangAPI(); + defaultArguments.append(QByteArrayLiteral("-nostdinc")); defaultArguments.append(QByteArrayLiteral("-nostdinc++")); defaultArguments.append(QByteArrayLiteral("-xc++")); + + sanitizeArguments(defaultArguments); return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); if (parserSettings.isCpp()) { result.append(QByteArrayLiteral("-nostdinc++")); } if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); + + sanitizeArguments(result); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); + + sanitizeArguments(result); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { foreach (const Path& url, includes) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } void addFrameworkDirectories(QVector* args, QVector* otherArgs, const Path::List& frameworkDirectories, const char* cliSwitch) { foreach (const Path& url, frameworkDirectories) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); if (!info.isDir()) { qCWarning(KDEV_CLANG) << "supposed framework directory is not a directory:" << url.pathOrUrl(); continue; } QByteArray path = url.toLocalFile().toUtf8(); otherArgs->append(cliSwitch); otherArgs->append(path); args->append(cliSwitch); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } bool needGccCompatibility(const ClangParsingEnvironment& environment) { const auto& defines = environment.defines(); // TODO: potentially do the same for the intel compiler? return defines.contains(QStringLiteral("__GNUC__")) && !environment.defines().contains(QStringLiteral("__clang__")); } bool hasQtIncludes(const Path::List& includePaths) { return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { return path.lastPathSegment() == QLatin1String("QtCore"); }) != includePaths.end(); } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_DetailedPreprocessingRecord #if CINDEX_VERSION_MINOR >= 34 | CXTranslationUnit_KeepGoing #endif ; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults #if CINDEX_VERSION_MINOR >= 32 | CXTranslationUnit_CreatePreambleOnFirstParse #endif | CXTranslationUnit_PrecompiledPreamble; if (environment.quality() == ClangParsingEnvironment::Unknown) { flags |= CXTranslationUnit_Incomplete; } } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); // uses QByteArray as smart-pointer for const char* ownership QVector smartArgs; smartArgs.reserve(includes.system.size() + includes.project.size() + pchInclude.isValid() + arguments.size() + 1); clangArguments.reserve(smartArgs.size()); std::transform(arguments.constBegin(), arguments.constEnd(), std::back_inserter(clangArguments), [] (const QByteArray &argument) { return argument.constData(); }); // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); smartArgs << pchFile; clangArguments << pchFile.constData(); } if (needGccCompatibility(environment)) { const auto compatFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/gcc_compat.h")).toUtf8(); if (!compatFile.isEmpty()) { smartArgs << compatFile; clangArguments << "-include" << compatFile.constData(); } } if (hasQtIncludes(includes.system)) { const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/wrappedQtHeaders"), QStandardPaths::LocateDirectory).toUtf8(); if (!wrappedQtHeaders.isEmpty()) { smartArgs << wrappedQtHeaders; clangArguments << "-isystem" << wrappedQtHeaders.constData(); const QByteArray qtCore = wrappedQtHeaders + "/QtCore"; smartArgs << qtCore; clangArguments << "-isystem" << qtCore.constData(); } } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); const auto& frameworkDirectories = environment.frameworkDirectories(); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.system, "-iframework"); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.project, "-F"); smartArgs << writeDefinesFile(environment.defines()); clangArguments << "-imacros" << smartArgs.last().constData(); // append extra args from environment variable static const auto extraArgs = ::extraArgs(); foreach (const QByteArray& arg, extraArgs) { clangArguments << arg.constData(); } QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } // debugging: print hypothetical clang invocation including args (for easy c&p for local testing) if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_ARGS")) { QTextStream out(stdout); out << "Invocation: clang"; foreach (const auto& arg, clangArguments) { out << " " << arg; } out << " " << tuUrl.byteArray().constData() << "\n"; } const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { qCWarning(KDEV_CLANG) << "clang_parseTranslationUnit2 return with error code" << code; if (!qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS")) { qCWarning(KDEV_CLANG) << " (start KDevelop with `KDEV_CLANG_DISPLAY_DIAGS=1 kdevelop` to see more diagnostics)"; } } if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, QByteArray(tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qCWarning(KDEV_CLANG) << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } QByteArray ParseSessionData::writeDefinesFile(const QMap& defines) { m_definesFile.open(); Q_ASSERT(m_definesFile.isWritable()); { QTextStream definesStream(&m_definesFile); // don't show warnings about redefined macros definesStream << "#pragma clang system_header\n"; for (auto it = defines.begin(); it != defines.end(); ++it) { if (it.key().startsWith(QLatin1String("__has_include(")) || it.key().startsWith(QLatin1String("__has_include_next("))) { continue; } definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; } } m_definesFile.close(); if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DEFINES")) { QFile f(m_definesFile.fileName()); f.open(QIODevice::ReadOnly); Q_ASSERT(f.isReadable()); QTextStream out(stdout); out << "Defines file: " << f.fileName() << "\n" << f.readAll() << f.size() << "\n VS defines:" << defines.size() << "\n"; } return m_definesFile.fileName().toUtf8(); } void ParseSessionData::setUnit(CXTranslationUnit unit) { m_unit = unit; if (m_unit) { const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); } else { m_file = nullptr; } } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // extra clang diagnostics const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); // missing-include problems are so severe in clang that we always propagate // them to this document, to ensure that the user will see the error. if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { continue; } ProblemPointer problem(ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit)); problems << problem; clang_disposeDiagnostic(diagnostic); } // other problem sources TodoExtractor extractor(unit(), file); problems << extractor.problems(); #if CINDEX_VERSION_MINOR > 30 // note that the below warning is triggered on every reparse when there is a precompiled preamble // see also TestDUChain::testReparseIncludeGuard const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); const IndexedString indexedPath(path); if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } #endif return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); const auto code = clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)); if (code != CXError_Success) { qCWarning(KDEV_CLANG) << "clang_reparseTranslationUnit return with error code" << code; // if error code != 0 => clang_reparseTranslationUnit invalidates the old translation unit => clean up clang_disposeTranslationUnit(d->m_unit); d->setUnit(nullptr); return false; } // update state d->setUnit(d->m_unit); return true; } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; } diff --git a/plugins/cmake/cmakeutils.cpp b/plugins/cmake/cmakeutils.cpp index 9d6b6318d3..fd666d1b89 100644 --- a/plugins/cmake/cmakeutils.cpp +++ b/plugins/cmake/cmakeutils.cpp @@ -1,727 +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 ); } 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 ); } 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)); + tests += importTestSuites(Path(buildDir, entry.arguments.first().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; + if (tests.isEmpty() || entry.arguments.first().value != tests.last().name) { + qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value + << " ...), but expected test " << tests.last().name; continue; } if (entry.arguments[1].value != QLatin1String("PROPERTIES")) { - qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.constFirst().value + qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().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/testing/ctestutils.cpp b/plugins/cmake/testing/ctestutils.cpp index 55c7c5a476..7d44202a1e 100644 --- a/plugins/cmake/testing/ctestutils.cpp +++ b/plugins/cmake/testing/ctestutils.cpp @@ -1,68 +1,68 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula CTestTestfile.cmake parsing uses code from the xUnit plugin Copyright 2008 Manuel Breugelmans Copyright 2010 Daniel Calviño Sánchez 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ctestutils.h" #include "ctestsuite.h" #include "ctestfindjob.h" #include #include #include #include #include #include #include #include using namespace KDevelop; // TODO: we are lacking introspection into targets, to see what files belong to each target. static CMakeTarget targetByName(const QHash< KDevelop::Path, QVector>& targets, const QString& name) { for (const auto &subdir: targets.values()) { for (const auto &target: subdir) { if (target.name == name) return target; } } return {}; } void CTestUtils::createTestSuites(const QVector& testSuites, const QHash< KDevelop::Path, QVector>& targets, KDevelop::IProject* project) { foreach (const Test& test, testSuites) { KDevelop::Path executablePath; if (QDir::isAbsolutePath(test.executable)) { executablePath = KDevelop::Path(test.executable); } else { const auto target = targetByName(targets, test.executable); if (target.artifacts.isEmpty()) { continue; } - executablePath = target.artifacts.constFirst(); + executablePath = target.artifacts.first(); } CTestSuite* suite = new CTestSuite(test.name, executablePath, {}, project, test.arguments, test.properties); ICore::self()->runController()->registerJob(new CTestFindJob(suite)); } } diff --git a/plugins/cmake/tests/test_cmakemanager.cpp b/plugins/cmake/tests/test_cmakemanager.cpp index 1050400f71..3ba9675e91 100644 --- a/plugins/cmake/tests/test_cmakemanager.cpp +++ b/plugins/cmake/tests/test_cmakemanager.cpp @@ -1,393 +1,393 @@ /* This file is part of KDevelop Copyright 2010 Esben Mose Hansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_cmakemanager.h" #include "testhelpers.h" #include "cmakemodelitems.h" #include "cmakeutils.h" #include "cmakeimportjsonjob.h" #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestCMakeManager) using namespace KDevelop; void TestCMakeManager::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); AutoTestShell::init({"kdevcmakemanager"}); TestCore::initialize(); cleanup(); } void TestCMakeManager::cleanupTestCase() { TestCore::shutdown(); } void TestCMakeManager::cleanup() { foreach(IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); } QVERIFY(ICore::self()->projectController()->projects().isEmpty()); } void TestCMakeManager::testWithBuildDirProject() { loadProject(QStringLiteral("with_build_dir")); } void TestCMakeManager::testIncludePaths() { IProject* project = loadProject(QStringLiteral("single_subdirectory")); Path sourceDir = project->path(); Path fooCpp(sourceDir, QStringLiteral("subdir/foo.cpp")); QVERIFY(QFile::exists(fooCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(fooCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the target, once the plain file ProjectBaseItem* fooCppItem = items.first(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem); QVERIFY(includeDirs.size() >= 3); Path buildDir(project->buildSystemManager()->buildDirectory(fooCppItem)); QVERIFY(includeDirs.contains(buildDir)); Path subBuildDir(buildDir, QStringLiteral("subdir/")); QVERIFY(includeDirs.contains(subBuildDir)); Path subDir(sourceDir, QStringLiteral("subdir/")); QVERIFY(includeDirs.contains(subDir)); } void TestCMakeManager::testRelativePaths() { IProject* project = loadProject(QStringLiteral("relative_paths"), QStringLiteral("/out")); Path codeCpp(project->path(), QStringLiteral("../src/code.cpp")); QVERIFY(QFile::exists( codeCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(codeCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Abort); QCOMPARE(items.size(), 1); // once in the target ProjectBaseItem* fooCppItem = items.first(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem); Path incDir(project->path(), QStringLiteral("../inc/")); QVERIFY(includeDirs.contains( incDir )); } void TestCMakeManager::testTargetIncludePaths() { IProject* project = loadProject(QStringLiteral("target_includes")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { ProjectBaseItem* mainContainer = mainCppItem->parent(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); if (mainContainer->target()) { foundInTarget = true; Path targetIncludesDir(project->path(), QStringLiteral("includes/")); QVERIFY(includeDirs.contains(targetIncludesDir)); } } QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QVERIFY(foundInTarget); } void TestCMakeManager::testTargetIncludeDirectories() { IProject* project = loadProject(QStringLiteral("target_include_directories")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { ProjectBaseItem* mainContainer = mainCppItem->parent(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); if (mainContainer->target()) { foundInTarget = true; QVERIFY(includeDirs.contains(Path(project->path(), "includes/"))); QVERIFY(includeDirs.contains(Path(project->path(), "libincludes/"))); } } QEXPECT_FAIL("", "files aren't being added to the target", Continue); QVERIFY(foundInTarget); } void TestCMakeManager::testQt5App() { IProject* project = loadProject(QStringLiteral("qt5_app")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == QLatin1String("QtCore"); foundGui |= filename == QLatin1String("QtGui"); foundWidgets |= filename == QLatin1String("QtWidgets"); } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); } void TestCMakeManager::testQt5AppOld() { IProject* project = loadProject(QStringLiteral("qt5_app_old")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == QLatin1String("QtCore"); foundGui |= filename == QLatin1String("QtGui"); foundWidgets |= filename == QLatin1String("QtWidgets"); } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); } void TestCMakeManager::testKF5App() { IProject* project = loadProject(QStringLiteral("kf5_app")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false, foundWidgetsAddons = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); qDebug() << "xxxxxxxxx" << includeDirs; foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == QLatin1String("QtCore"); foundGui |= filename == QLatin1String("QtGui"); foundWidgets |= filename == QLatin1String("QtWidgets"); foundWidgetsAddons |= filename == QLatin1String("KWidgetsAddons"); } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); QVERIFY(foundWidgetsAddons); } void TestCMakeManager::testDefines() { IProject* project = loadProject(QStringLiteral("defines")); Path mainCpp(project->path(), QStringLiteral("main.cpp")); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { QHash defines = project->buildSystemManager()->defines(mainCppItem); QCOMPARE(defines.value("B", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("BV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("BV2", QStringLiteral("not found")), QStringLiteral("2")); // QCOMPARE(defines.value("BAR", QStringLiteral("not found")), QStringLiteral("foo")); // QCOMPARE(defines.value("FOO", QStringLiteral("not found")), QStringLiteral("bar")); // QCOMPARE(defines.value("BLA", QStringLiteral("not found")), QStringLiteral("blub")); QCOMPARE(defines.value("ASDF", QStringLiteral("not found")), QStringLiteral("asdf")); QCOMPARE(defines.value("XYZ", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("A", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("AV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("AV2", QStringLiteral("not found")), QStringLiteral("2")); QCOMPARE(defines.value("C", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("CV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("CV2", QStringLiteral("not found")), QStringLiteral("2")); QCOMPARE(defines.size(), 13); foundInTarget = true; } QVERIFY(foundInTarget); } void TestCMakeManager::testCustomTargetSources() { IProject* project = loadProject(QStringLiteral("custom_target_sources")); QList targets = project->buildSystemManager()->targets(project->projectItem()); QVERIFY(targets.size() == 1); QEXPECT_FAIL("", "Will fix soon, hopefully", Abort); ProjectTargetItem *target = targets.first(); QCOMPARE(target->fileList().size(), 1); QCOMPARE(target->fileList().first()->baseName(), QStringLiteral("foo.cpp")); } void TestCMakeManager::testConditionsInSubdirectoryBasedOnRootVariables() { IProject* project = loadProject(QStringLiteral("conditions_in_subdirectory_based_on_root_variables")); Path rootFooCpp(project->path(), QStringLiteral("foo.cpp")); QVERIFY(QFile::exists(rootFooCpp.toLocalFile())); QList< ProjectBaseItem* > rootFooItems = project->itemsForPath(IndexedString(rootFooCpp.pathOrUrl())); QEXPECT_FAIL("", "files aren't being added to the target", Continue); QCOMPARE(rootFooItems.size(), 4); // three items for the targets, one item for the plain file Path subdirectoryFooCpp(project->path(), QStringLiteral("subdirectory/foo.cpp")); QVERIFY(QFile::exists(subdirectoryFooCpp.toLocalFile())); QList< ProjectBaseItem* > subdirectoryFooItems = project->itemsForPath(IndexedString(subdirectoryFooCpp.pathOrUrl())); QEXPECT_FAIL("", "files aren't being added to the target", Continue); QCOMPARE(subdirectoryFooItems.size(), 4); // three items for the targets, one item for the plain file } void TestCMakeManager::testEnumerateTargets() { QString tempDir = QDir::tempPath(); QTemporaryFile targetDirectoriesFile; QTemporaryDir subdir; auto opened = targetDirectoriesFile.open(); QVERIFY(opened); QVERIFY(subdir.isValid()); const QString targetDirectoriesContent = tempDir + "/CMakeFiles/first_target.dir\n" + tempDir + "/CMakeFiles/second_target.dir\r\n" + tempDir + "/" + subdir.path() + "/CMakeFiles/third_target.dir"; targetDirectoriesFile.write(targetDirectoriesContent.toLatin1()); targetDirectoriesFile.close(); QHash targets = CMake::enumerateTargets(Path(targetDirectoriesFile.fileName()), tempDir, Path(tempDir)); QCOMPARE(targets.value(Path(tempDir)).value(0), QStringLiteral("first_target")); QCOMPARE(targets.value(Path(tempDir)).value(1), QStringLiteral("second_target")); QCOMPARE(targets.value(Path(tempDir + "/" + subdir.path())).value(0), QStringLiteral("third_target")); } void TestCMakeManager::testReload() { IProject* project = loadProject(QStringLiteral("tiny_project")); const Path sourceDir = project->path(); auto fmp = dynamic_cast(project->projectFileManager()); QVERIFY(fmp); auto projectItem = project->projectItem(); auto targets = projectItem->targetList(); auto job = fmp->createImportJob(project->projectItem()); project->setReloadJob(job); QSignalSpy spy(job, &KJob::finished); job->start(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(projectItem, project->projectItem()); QCOMPARE(targets, projectItem->targetList()); } void TestCMakeManager::testFaultyTarget() { loadProject(QStringLiteral("faulty_target")); } void TestCMakeManager::testParenthesesInTestArguments() { IProject* project = loadProject(QStringLiteral("parentheses_in_test_arguments")); auto job = new CMakeImportJsonJob(project, this); QVERIFY(job->exec()); } void TestCMakeManager::testExecutableOutputPath() { const auto prevSuitesCount = ICore::self()->testController()->testSuites().count(); qRegisterMetaType("KDevelop::ITestSuite*"); QSignalSpy spy(ICore::self()->testController(), &ITestController::testSuiteAdded); IProject* project = loadProject(QStringLiteral("randomexe")); const auto targets = project->projectItem()->targetList(); QCOMPARE(targets.count(), 1); - const auto target = targets.constFirst()->executable(); + const auto target = targets.first()->executable(); QVERIFY(target); const KDevelop::Path exePath(target->executable()->builtUrl()); QCOMPARE(exePath, KDevelop::Path(project->buildSystemManager()->buildDirectory(project->projectItem()), QLatin1String("randomplace/mytest"))); QVERIFY(spy.count() || spy.wait(100000)); auto suites = ICore::self()->testController()->testSuites(); QCOMPARE(suites.count(), prevSuitesCount + 1); const CTestSuite* suite = static_cast(ICore::self()->testController()->findTestSuite(project, "mytest")); QCOMPARE(suite->executable(), exePath); }