diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h index 3d2fb108826..0bfac24365c 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ b/src/plugins/cmakeprojectmanager/builddirmanager.h @@ -86,13 +86,12 @@ signals: void dataAvailable() const; void errorOccured(const QString &err) const; -protected: - const Utils::FileName workDirectory() const; - private: void emitDataAvailable(); void checkConfiguration(); + const Utils::FileName workDirectory() const; + void updateReaderType(std::function todo); void updateReaderData(); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index bfbe5135a45..ce561412b1c 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -124,6 +124,8 @@ private: friend class CMakeProjectManager::CMakeProject; }; +class CMakeProjectImporter; + class CMakeBuildConfigurationFactory : public ProjectExplorer::IBuildConfigurationFactory { Q_OBJECT @@ -159,6 +161,8 @@ private: CMakeBuildInfo *createBuildInfo(const ProjectExplorer::Kit *k, const QString &sourceDir, BuildType buildType) const; + + friend class CMakeProjectImporter; }; } // namespace Internal diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index d22af004755..79c4a9a763f 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -76,6 +76,11 @@ CMakeKitInformation::CMakeKitInformation() [this]() { foreach (Kit *k, KitManager::kits()) fix(k); }); } +Core::Id CMakeKitInformation::id() +{ + return TOOL_ID; +} + CMakeTool *CMakeKitInformation::cmakeTool(const Kit *k) { if (!k) diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.h b/src/plugins/cmakeprojectmanager/cmakekitinformation.h index 05d1d036b46..50152dcdae7 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.h +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.h @@ -41,6 +41,8 @@ class CMAKE_EXPORT CMakeKitInformation : public ProjectExplorer::KitInformation public: CMakeKitInformation(); + static Core::Id id(); + static CMakeTool *cmakeTool(const ProjectExplorer::Kit *k); static void setCMakeTool(ProjectExplorer::Kit *k, const Core::Id id); diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 7ad84824eb8..e5495f16cc4 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -267,6 +267,13 @@ void CMakeProject::buildCMakeTarget(const QString &buildTarget) bc->buildTarget(buildTarget); } +ProjectImporter *CMakeProject::projectImporter() const +{ + if (!m_projectImporter) + m_projectImporter = std::make_unique(projectFilePath()); + return m_projectImporter.get(); +} + QList CMakeProject::buildTargets() const { CMakeBuildConfiguration *bc = nullptr; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 1965609e559..959aea47d30 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -26,6 +26,7 @@ #pragma once #include "cmake_global.h" +#include "cmakeprojectimporter.h" #include "treescanner.h" #include @@ -36,6 +37,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QFileSystemWatcher; QT_END_NAMESPACE @@ -102,6 +105,8 @@ public: // Context menu actions: void buildCMakeTarget(const QString &buildTarget); + ProjectExplorer::ProjectImporter *projectImporter() const final; + signals: /// emitted when cmake is running: void parsingStarted(); @@ -135,6 +140,7 @@ private: Internal::TreeScanner m_treeScanner; QHash m_mimeBinaryCache; QList m_allFiles; + mutable std::unique_ptr m_projectImporter; friend class Internal::CMakeBuildConfiguration; friend class Internal::CMakeBuildSettingsWidget; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp new file mode 100644 index 00000000000..8a1d6476b5a --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "cmakeprojectimporter.h" + +#include "builddirmanager.h" +#include "cmakebuildconfiguration.h" +#include "cmakebuildinfo.h" +#include "cmakekitinformation.h" +#include "cmaketoolmanager.h" + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace QtSupport; + +namespace { + +Q_LOGGING_CATEGORY(cmInputLog, "qtc.cmake.import"); + +struct CMakeToolChainData +{ + QByteArray languageId; + Utils::FileName compilerPath; + Core::Id mapLanguageIdToQtC() const + { + const QByteArray li = languageId.toLower(); + if (li == "cxx") + return ProjectExplorer::Constants::CXX_LANGUAGE_ID; + else if (li == "c") + return ProjectExplorer::Constants::C_LANGUAGE_ID; + else + return Core::Id::fromName(languageId); + } +}; + +struct DirectoryData +{ + // Project Stuff: + QByteArray cmakeBuildType; + Utils::FileName buildDirectory; + + // Kit Stuff + Utils::FileName cmakeBinary; + QByteArray generator; + QByteArray extraGenerator; + QByteArray platform; + QByteArray toolset; + QByteArray sysroot; + QtProjectImporter::QtVersionData qt; + QVector toolChains; +}; + +static QStringList scanDirectory(const QString &path, const QString &prefix) +{ + QStringList result; + qCDebug(cmInputLog()) << "Scanning for directories matching" << prefix << "in" << path; + + const QDir base = QDir(path); + foreach (const QString &dir, base.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + const QString subPath = path + '/' + dir; + qCDebug(cmInputLog()) << "Checking" << subPath; + if (dir.startsWith(prefix)) + result.append(subPath); + } + return result; +} + +} // namespace + +namespace CMakeProjectManager { +namespace Internal { + +CMakeProjectImporter::CMakeProjectImporter(const Utils::FileName &path) : QtProjectImporter(path) +{ + useTemporaryKitInformation(CMakeKitInformation::id(), + [this](Kit *k, const QVariantList &vl) { cleanupTemporaryCMake(k, vl); }, + [this](Kit *k, const QVariantList &vl) { persistTemporaryCMake(k, vl); }); + +} + +QStringList CMakeProjectImporter::importCandidates() +{ + QStringList candidates; + + QFileInfo pfi = projectFilePath().toFileInfo(); + candidates << scanDirectory(pfi.absolutePath(), "build"); + + foreach (Kit *k, KitManager::kits()) { + QFileInfo fi(CMakeBuildConfiguration::shadowBuildDirectory(projectFilePath(), k, + QString(), BuildConfiguration::Unknown).toString()); + candidates << scanDirectory(fi.absolutePath(), QString()); + } + const QStringList finalists = Utils::filteredUnique(candidates); + qCInfo(cmInputLog()) << "import candidates:" << finalists; + return finalists; +} + +static Utils::FileName qmakeFromCMakeCache(const CMakeConfig &config) +{ + // Qt4 way to define things (more convenient for us, so try this first;-) + Utils::FileName qmake + = Utils::FileName::fromUtf8(CMakeConfigItem::valueOf(QByteArray("QT_QMAKE_EXECUTABLE"), config)); + qCDebug(cmInputLog()) << "QT_QMAKE_EXECUTABLE=" << qmake.toUserOutput(); + if (!qmake.isEmpty()) + return qmake; + + // Check Qt5 settings: oh, the horror! + const Utils::FileName qtCMakeDir + = Utils::FileName::fromUtf8(CMakeConfigItem::valueOf(QByteArray("Qt5Core_DIR"), config)); + qCDebug(cmInputLog()) << "Qt5Core_DIR=" << qtCMakeDir.toUserOutput(); + const Utils::FileName canQtCMakeDir = Utils::FileName::fromString(qtCMakeDir.toFileInfo().canonicalFilePath()); + qCInfo(cmInputLog()) << "Qt5Core_DIR (canonical)=" << canQtCMakeDir.toUserOutput(); + if (qtCMakeDir.isEmpty()) + return Utils::FileName(); + const Utils::FileName baseQtDir = canQtCMakeDir.parentDir().parentDir().parentDir(); // Up 3 levels... + qCDebug(cmInputLog()) << "BaseQtDir:" << baseQtDir.toUserOutput(); + + // "Parse" Qt5Core/Qt5CoreConfigExtras.cmake: + { + // This assumes that roughly this kind of data is found in + // inside Qt5Core/Qt5CoreConfigExtras.cmake: + // + // if (NOT TARGET Qt5::qmake) + // add_executable(Qt5::qmake IMPORTED) + // set(imported_location "${_qt5Core_install_prefix}/bin/qmake") + // _qt5_Core_check_file_exists(${imported_location}) + // set_target_properties(Qt5::qmake PROPERTIES + // IMPORTED_LOCATION ${imported_location} + // ) + // endif() + + QFile extras(qtCMakeDir.toString() + "/Qt5CoreConfigExtras.cmake"); + if (!extras.open(QIODevice::ReadOnly)) + return Utils::FileName(); + + QByteArray data; + bool inQmakeSection = false; + // Read in 4k chunks, lines longer than that are going to be ignored + while (!extras.atEnd()) { + data = extras.read(4 * 1024 - data.count()); + int startPos = 0; + forever { + const int pos = data.indexOf('\n', startPos); + if (pos < 0) { + data = data.mid(startPos); + break; + } + + QByteArray line = data.mid(startPos, pos - startPos); + const QByteArray origLine = line; + + startPos = pos + 1; + + line.replace("(", " ( "); + line.replace(")", " ) "); + line = line.simplified(); + + if (line == "if ( NOT TARGET Qt5::qmake )") + inQmakeSection = true; + + if (!inQmakeSection) + continue; + + const QByteArray set = "set ( imported_location "; + if (line.startsWith(set)) { + const int sp = origLine.indexOf('}'); + const int ep = origLine.lastIndexOf('"'); + + QTC_ASSERT(sp > 0, return Utils::FileName()); + QTC_ASSERT(ep > sp + 2, return Utils::FileName()); + QTC_ASSERT(ep < origLine.count(), return Utils::FileName()); + + // Eat the leading "}/" and trailing " + const QByteArray locationPart = origLine.mid(sp + 2, ep - 2 - sp); + Utils::FileName result = baseQtDir; + result.appendPath(QString::fromUtf8(locationPart)); + return result; + } + } + } + } + + // Now try to make sense of .../Qt5CoreConfig.cmake: + return Utils::FileName(); +} + +QVector extractToolChainsFromCache(const CMakeConfig &config) +{ + QVector result; + for (const CMakeConfigItem &i : config) { + if (!i.key.startsWith("CMAKE_") || !i.key.endsWith("_COMPILER")) + continue; + const QByteArray language = i.key.mid(6, i.key.count() - 6 - 9); // skip "CMAKE_" and "_COMPILER" + result.append({ language, Utils::FileName::fromUtf8(i.value) }); + } + return result; +} + +QList CMakeProjectImporter::examineDirectory(const Utils::FileName &importPath) const +{ + qCInfo(cmInputLog()) << "Examining directory:" << importPath.toUserOutput(); + Utils::FileName cacheFile = importPath; + cacheFile.appendPath("CMakeCache.txt"); + + if (!cacheFile.exists()) { + qCDebug(cmInputLog()) << cacheFile.toUserOutput() << "does not exist, returning."; + return { }; + } + + QString errorMessage; + const CMakeConfig config = BuildDirManager::parseConfiguration(cacheFile, &errorMessage); + if (config.isEmpty() || !errorMessage.isEmpty()) { + qCDebug(cmInputLog()) << "Failed to read configuration from" << cacheFile << errorMessage; + return { }; + } + const auto homeDir + = Utils::FileName::fromUserInput(QString::fromUtf8(CMakeConfigItem::valueOf("CMAKE_HOME_DIRECTORY", config))); + if (homeDir != projectDirectory()) { + qCDebug(cmInputLog()) << "Wrong source directory:" << homeDir.toUserOutput() + << "expected:" << projectDirectory().toUserOutput(); + return { }; + } + + auto data = std::make_unique(); + data->buildDirectory = importPath; + data->cmakeBuildType = CMakeConfigItem::valueOf("CMAKE_BUILD_TYPE", config); + + data->cmakeBinary + = Utils::FileName::fromUtf8(CMakeConfigItem::valueOf("CMAKE_COMMAND", config)); + data->generator = CMakeConfigItem::valueOf("CMAKE_GENERATOR", config); + data->extraGenerator = CMakeConfigItem::valueOf("CMAKE_EXTRA_GENERATOR", config); + data->platform = CMakeConfigItem::valueOf("CMAKE_GENERATOR_PLATFORM", config); + data->toolset = CMakeConfigItem::valueOf("CMAKE_GENERATOR_TOOLSET", config); + + data->sysroot = CMakeConfigItem::valueOf("CMAKE_SYSROOT", config); + + // Qt: + const Utils::FileName qmake = qmakeFromCMakeCache(config); + if (!qmake.isEmpty()) + data->qt = findOrCreateQtVersion(qmake); + + // ToolChains: + data->toolChains = extractToolChainsFromCache(config); + + qCInfo(cmInputLog()) << "Offering to import" << importPath.toUserOutput(); + return { static_cast(data.release()) }; +} + +bool CMakeProjectImporter::matchKit(void *directoryData, const Kit *k) const +{ + const DirectoryData *data = static_cast(directoryData); + + CMakeTool *cm = CMakeKitInformation::cmakeTool(k); + if (!cm || cm->cmakeExecutable() != data->cmakeBinary) + return false; + + if (CMakeGeneratorKitInformation::generator(k) != QString::fromUtf8(data->generator) + || CMakeGeneratorKitInformation::extraGenerator(k) != QString::fromUtf8(data->extraGenerator) + || CMakeGeneratorKitInformation::platform(k) != QString::fromUtf8(data->platform) + || CMakeGeneratorKitInformation::toolset(k) != QString::fromUtf8(data->toolset)) + return false; + + if (SysRootKitInformation::sysRoot(k) != Utils::FileName::fromUtf8(data->sysroot)) + return false; + + if (data->qt.qt && QtSupport::QtKitInformation::qtVersionId(k) != data->qt.qt->uniqueId()) + return false; + + for (const CMakeToolChainData &tcd : data->toolChains) { + ToolChain *tc = ToolChainKitInformation::toolChain(k, tcd.mapLanguageIdToQtC()); + if (!tc || tc->compilerCommand() != tcd.compilerPath) + return false; + } + + qCDebug(cmInputLog()) << k->displayName() + << "matches directoryData for" << data->buildDirectory.toUserOutput(); + return true; +} + +Kit *CMakeProjectImporter::createKit(void *directoryData) const +{ + const DirectoryData *data = static_cast(directoryData); + + return QtProjectImporter::createTemporaryKit(data->qt, [&data, this](Kit *k) { + const CMakeToolData cmtd = findOrCreateCMakeTool(data->cmakeBinary); + QTC_ASSERT(cmtd.cmakeTool, return); + if (cmtd.isTemporary) + addTemporaryData(CMakeKitInformation::id(), cmtd.cmakeTool->id().toSetting(), k); + + CMakeGeneratorKitInformation::setGenerator(k, QString::fromUtf8(data->generator)); + CMakeGeneratorKitInformation::setExtraGenerator(k, QString::fromUtf8(data->extraGenerator)); + CMakeGeneratorKitInformation::setPlatform(k, QString::fromUtf8(data->platform)); + CMakeGeneratorKitInformation::setToolset(k, QString::fromUtf8(data->toolset)); + + SysRootKitInformation::setSysRoot(k, Utils::FileName::fromUtf8(data->sysroot)); + + for (const CMakeToolChainData &cmtcd : data->toolChains) { + const ToolChainData tcd + = findOrCreateToolChains(cmtcd.compilerPath, cmtcd.mapLanguageIdToQtC()); + QTC_ASSERT(!tcd.tcs.isEmpty(), continue); + + if (tcd.areTemporary) { + for (ToolChain *tc : tcd.tcs) + addTemporaryData(ToolChainKitInformation::id(), tc->id(), k); + } + + ToolChainKitInformation::setToolChain(k, tcd.tcs.at(0)); + } + + qCInfo(cmInputLog()) << "Temporary Kit created."; + }); +} + +QList CMakeProjectImporter::buildInfoListForKit(const Kit *k, void *directoryData) const +{ + QList result; + DirectoryData *data = static_cast(directoryData); + auto factory = qobject_cast( + IBuildConfigurationFactory::find(k, projectFilePath().toString())); + if (!factory) + return result; + + // create info: + std::unique_ptr + info(factory->createBuildInfo(k, projectDirectory().toString(), + CMakeBuildConfigurationFactory::buildTypeFromByteArray(data->cmakeBuildType))); + info->buildDirectory = data->buildDirectory; + info->displayName = info->typeName; + + bool found = false; + foreach (BuildInfo *bInfo, result) { + if (*static_cast(bInfo) == *info) { + found = true; + break; + } + } + if (!found) + result << info.release(); + + qCDebug(cmInputLog()) << "BuildInfo configured."; + + return result; +} + +CMakeProjectImporter::CMakeToolData +CMakeProjectImporter::findOrCreateCMakeTool(const Utils::FileName &cmakeToolPath) const +{ + CMakeToolData result; + result.cmakeTool = CMakeToolManager::findByCommand(cmakeToolPath); + if (!result.cmakeTool) { + qCDebug(cmInputLog()) << "Creating temporary CMakeTool for" << cmakeToolPath.toUserOutput(); + result.cmakeTool = new CMakeTool(CMakeTool::ManualDetection, CMakeTool::createId()); + result.isTemporary = true; + } + return result; +} + +void CMakeProjectImporter::deleteDirectoryData(void *directoryData) const +{ + delete static_cast(directoryData); +} + +void CMakeProjectImporter::cleanupTemporaryCMake(Kit *k, const QVariantList &vl) +{ + if (vl.isEmpty()) + return; // No temporary CMake + QTC_ASSERT(vl.count() == 1, return); + CMakeKitInformation::setCMakeTool(k, Core::Id()); // Always mark Kit as not using this Qt + CMakeToolManager::deregisterCMakeTool(Core::Id::fromSetting(vl.at(0))); + qCDebug(cmInputLog()) << "Temporary CMake tool cleaned up."; +} + +void CMakeProjectImporter::persistTemporaryCMake(Kit *k, const QVariantList &vl) +{ + if (vl.isEmpty()) + return; // No temporary CMake + QTC_ASSERT(vl.count() == 1, return); + const QVariant data = vl.at(0); + CMakeTool *tmpCmake = CMakeToolManager::findById(Core::Id::fromSetting(data)); + CMakeTool *actualCmake = CMakeKitInformation::cmakeTool(k); + + // User changed Kit away from temporary CMake that was set up: + if (tmpCmake && actualCmake != tmpCmake) + CMakeToolManager::deregisterCMakeTool(tmpCmake->id()); + + qCDebug(cmInputLog()) << "Temporary CMake tool made persistent."; +} + +} // namespace Internal +} // namespace CMakeProjectManager + +#ifdef WITH_TESTS + +#include "cmakeprojectplugin.h" + +#include + +namespace CMakeProjectManager { +namespace Internal { + +void CMakeProjectPlugin::testCMakeProjectImporterQt_data() +{ + QTest::addColumn("cache"); + QTest::addColumn("expectedQmake"); + + QTest::newRow("Empty input") + << QStringList() << QString(); + + QTest::newRow("Qt4") + << QStringList({ QString::fromLatin1("QT_QMAKE_EXECUTABLE=/usr/bin/xxx/qmake") }) + << "/usr/bin/xxx/qmake"; + + // Everything else will require Qt installations! +} + +void CMakeProjectPlugin::testCMakeProjectImporterQt() +{ + QFETCH(QStringList, cache); + QFETCH(QString, expectedQmake); + + CMakeConfig config; + foreach (const QString &c, cache) { + const int pos = c.indexOf('='); + Q_ASSERT(pos > 0); + const QString key = c.left(pos); + const QString value = c.mid(pos + 1); + config.append(CMakeConfigItem(key.toUtf8(), value.toUtf8())); + } + + Utils::FileName realQmake = qmakeFromCMakeCache(config); + QCOMPARE(realQmake.toString(), expectedQmake); +} +void CMakeProjectPlugin::testCMakeProjectImporterToolChain_data() +{ + QTest::addColumn("cache"); + QTest::addColumn("expectedLanguages"); + QTest::addColumn("expectedToolChains"); + + QTest::newRow("Empty input") + << QStringList() << QByteArrayList() << QStringList(); + + QTest::newRow("Unrelated input") + << QStringList("CMAKE_SOMETHING_ELSE=/tmp") << QByteArrayList() << QStringList(); + QTest::newRow("CXX compiler") + << QStringList({ "CMAKE_CXX_COMPILER=/usr/bin/g++" }) + << QByteArrayList({ "CXX" }) + << QStringList({ "/usr/bin/g++" }); + QTest::newRow("CXX compiler, C compiler") + << QStringList({ "CMAKE_CXX_COMPILER=/usr/bin/g++", "CMAKE_C_COMPILER=/usr/bin/clang" }) + << QByteArrayList({ "CXX", "C" }) + << QStringList({ "/usr/bin/g++", "/usr/bin/clang" }); + QTest::newRow("CXX compiler, C compiler, strange compiler") + << QStringList({ "CMAKE_CXX_COMPILER=/usr/bin/g++", + "CMAKE_C_COMPILER=/usr/bin/clang", + "CMAKE_STRANGE_LANGUAGE_COMPILER=/tmp/strange/compiler" }) + << QByteArrayList({ "CXX", "C", "STRANGE_LANGUAGE" }) + << QStringList({ "/usr/bin/g++", "/usr/bin/clang", "/tmp/strange/compiler" }); + QTest::newRow("CXX compiler, C compiler, strange compiler (with junk)") + << QStringList({ "FOO=test", + "CMAKE_CXX_COMPILER=/usr/bin/g++", + "CMAKE_BUILD_TYPE=debug", + "CMAKE_C_COMPILER=/usr/bin/clang", + "SOMETHING_COMPILER=/usr/bin/something", + "CMAKE_STRANGE_LANGUAGE_COMPILER=/tmp/strange/compiler", + "BAR=more test" }) + << QByteArrayList({ "CXX", "C", "STRANGE_LANGUAGE" }) + << QStringList({ "/usr/bin/g++", "/usr/bin/clang", "/tmp/strange/compiler" }); +} + +void CMakeProjectPlugin::testCMakeProjectImporterToolChain() +{ + QFETCH(QStringList, cache); + QFETCH(QByteArrayList, expectedLanguages); + QFETCH(QStringList, expectedToolChains); + + QCOMPARE(expectedLanguages.count(), expectedToolChains.count()); + + CMakeConfig config; + foreach (const QString &c, cache) { + const int pos = c.indexOf('='); + Q_ASSERT(pos > 0); + const QString key = c.left(pos); + const QString value = c.mid(pos + 1); + config.append(CMakeConfigItem(key.toUtf8(), value.toUtf8())); + } + + QVector tcs = extractToolChainsFromCache(config); + QCOMPARE(tcs.count(), expectedLanguages.count()); + for (int i = 0; i < tcs.count(); ++i) { + QCOMPARE(tcs.at(i).languageId, expectedLanguages.at(i)); + QCOMPARE(tcs.at(i).compilerPath.toString(), expectedToolChains.at(i)); + } +} + +} // namespace Internal +} // namespace CMakeProjectManager + +#endif diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h new file mode 100644 index 00000000000..f2cfa620e0b --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace CMakeProjectManager { + +class CMakeTool; + +namespace Internal { + +class CMakeProjectImporter : public QtSupport::QtProjectImporter +{ +public: + CMakeProjectImporter(const Utils::FileName &path); + + QStringList importCandidates() final; + +private: + QList examineDirectory(const Utils::FileName &importPath) const final; + bool matchKit(void *directoryData, const ProjectExplorer::Kit *k) const final; + ProjectExplorer::Kit *createKit(void *directoryData) const final; + QList buildInfoListForKit(const ProjectExplorer::Kit *k, + void *directoryData) const final; + + struct CMakeToolData { + bool isTemporary = false; + CMakeTool *cmakeTool = nullptr; + }; + CMakeToolData findOrCreateCMakeTool(const Utils::FileName &cmakeToolPath) const; + + void deleteDirectoryData(void *directoryData) const final; + + void cleanupTemporaryCMake(ProjectExplorer::Kit *k, const QVariantList &vl); + void persistTemporaryCMake(ProjectExplorer::Kit *k, const QVariantList &vl); +}; + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 8043387d9dd..d7c87b4d96d 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -7,6 +7,7 @@ HEADERS = builddirmanager.h \ cmakebuildstep.h \ cmakeconfigitem.h \ cmakeproject.h \ + cmakeprojectimporter.h \ cmakeprojectplugin.h \ cmakeprojectmanager.h \ cmakeprojectconstants.h \ @@ -40,6 +41,7 @@ SOURCES = builddirmanager.cpp \ cmakebuildstep.cpp \ cmakeconfigitem.cpp \ cmakeproject.cpp \ + cmakeprojectimporter.cpp \ cmakeprojectplugin.cpp \ cmakeprojectmanager.cpp \ cmakeprojectnodes.cpp \ diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index a3c3c478aed..7f6e81fde6d 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -49,6 +49,8 @@ QtcPlugin { "cmakeproject.cpp", "cmakeproject.h", "cmakeproject.qrc", + "cmakeprojectimporter.cpp", + "cmakeprojectimporter.h", "cmakeprojectconstants.h", "cmakeprojectmanager.cpp", "cmakeprojectmanager.h", diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.h b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.h index 5c5fb6bd8c8..806677d3809 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.h @@ -60,6 +60,12 @@ private slots: void testCMakeSplitValue_data(); void testCMakeSplitValue(); + + void testCMakeProjectImporterQt_data(); + void testCMakeProjectImporterQt(); + + void testCMakeProjectImporterToolChain_data(); + void testCMakeProjectImporterToolChain(); #endif private: