// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cmakeprojectimporter.h" #include "cmakebuildconfiguration.h" #include "cmakebuildsystem.h" #include "cmakekitaspect.h" #include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmaketoolmanager.h" #include "presetsmacros.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Debugger; using namespace ProjectExplorer; using namespace QtSupport; using namespace Utils; using namespace std::chrono_literals; namespace CMakeProjectManager::Internal { static Q_LOGGING_CATEGORY(cmInputLog, "qtc.cmake.import", QtWarningMsg); class ToolchainDescriptionEx { public: Utils::FilePath compilerPath; Utils::Id language; QString originalTargetTriple; operator ProjectExplorer::ToolchainDescription() const { return ProjectExplorer::ToolchainDescription{compilerPath, language}; } }; struct DirectoryData { // Project Stuff: QByteArray cmakeBuildType; FilePath buildDirectory; FilePath cmakeHomeDirectory; bool hasQmlDebugging = false; QString cmakePresetDisplayname; QString cmakePreset; // Kit Stuff FilePath cmakeBinary; QString cmakeSystemName; QString generator; QString platform; QString toolset; FilePath sysroot; QString osxSysroot; QtProjectImporter::QtVersionData qt; QList toolchains; QVariant debugger; }; static QString baseCMakeToolDisplayName(CMakeTool &tool) { if (!tool.isValid()) return QString("CMake"); CMakeTool::Version version = tool.version(); return QString("CMake %1.%2.%3").arg(version.major).arg(version.minor).arg(version.patch); } static QString uniqueCMakeToolDisplayName(CMakeTool &tool) { QString baseName = baseCMakeToolDisplayName(tool); QStringList existingNames; for (const CMakeTool *t : CMakeToolManager::cmakeTools()) existingNames << t->displayName(); return Utils::makeUniquelyNumbered(baseName, existingNames); } static std::unique_ptr ensureDir(const FilePath &path, const QString &pattern) { QTC_CHECK_RESULT(path.ensureWritableDir()); Result> tempDir = TemporaryFilePath::create(path.pathAppended(pattern), true); QTC_ASSERT_RESULT(tempDir, return nullptr); return std::move(*tempDir); } // CMakeProjectImporter static void cleanupTemporaryCMake(Kit *k, const QVariantList &vl) { if (vl.isEmpty()) return; // No temporary CMake QTC_ASSERT(vl.count() == 1, return); CMakeKitAspect::setCMakeExecutable(k, FilePath()); // Always mark Kit as not using this Qt CMakeToolManager::deregisterCMakeTool(Id::fromSetting(vl.at(0))); qCDebug(cmInputLog) << "Temporary CMake tool cleaned up."; } static void 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(Id::fromSetting(data)); FilePath actualCmake = CMakeKitAspect::cmakeExecutable(k); // User changed Kit away from temporary CMake that was set up: if (tmpCmake && actualCmake != tmpCmake->cmakeExecutable()) CMakeToolManager::deregisterCMakeTool(tmpCmake->id()); qCDebug(cmInputLog) << "Temporary CMake tool made persistent."; } CMakeProjectImporter::CMakeProjectImporter(const FilePath &path, const CMakeProject *project) : QtProjectImporter(path) , m_project(project) , m_presetsTempDir(ensureDir(path.parentDir() / ".qtcreator", "qtc-cmake-presets-XXXXXXXX")) { useTemporaryKitAspect(CMakeKitAspect::id(), &cleanupTemporaryCMake, &persistTemporaryCMake); } using CharToHexList = QList>; static const CharToHexList &charToHexList() { static const CharToHexList list = { {"<", "{3C}"}, {">", "{3E}"}, {":", "{3A}"}, {"\"", "{22}"}, {"\\", "{5C}"}, {"/", "{2F}"}, {"|", "{7C}"}, {"?", "{3F}"}, {"*", "{2A}"}, }; return list; } static QString presetNameToFileName(const QString &name) { QString fileName = name; for (const auto &p : charToHexList()) fileName.replace(p.first, p.second); return fileName; } static QString fileNameToPresetName(const QString &fileName) { QString name = fileName; for (const auto &p : charToHexList()) name.replace(p.second, p.first); return name; } static QString displayPresetName(const QString &presetName) { return QString("%1 (CMake preset)").arg(presetName); } static FilePaths findBuildFolders(const FilePath &path, const QList &patterns) { FilePaths result; if (patterns.isEmpty()) { if (path.isDir()) result.append(path); return result; } const FilePaths candidates = path.dirEntries( {{patterns.front().toString()}, QDir::Dirs | QDir::NoDotAndDotDot}); for (const auto &candidate : candidates) { if (patterns.size() == 1) result.append(candidate); else result += findBuildFolders(candidate, patterns.mid(1)); } return result; } static FilePaths findBuildFolders(const FilePath &path, const FilePath &pattern) { qCDebug(cmInputLog) << "Searching for build folders in" << path << "with pattern" << pattern; return findBuildFolders(path, pattern.pathComponents()); } QString escapeWildcards(const QString &input) { static const QMap replacements = {{'*', "[*]"}, {'?', "[?]"}, {'[', "[[]"}, {']', "[]]"}}; QString result; for (const QChar c : input) { auto it = replacements.find(c); if (it != replacements.end()) result += *it; else result += c; } return result; } // Converts a string such as "/test/my/buildfolder/pattern-*" into "/test/my/buildfolder" and "pattern-*" static std::pair pathAndPattern(const FilePath &shadowBuildDirectory) { QList path; QList withWildCard; bool seenWildCard = false; for (const QStringView &part : shadowBuildDirectory.pathComponents()) { if (part.contains('*')) seenWildCard = true; if (seenWildCard) withWildCard.append(part); else path.append(part); } return {FilePath::fromPathComponents(path), FilePath::fromPathComponents(withWildCard)}; } static FilePaths importCandidatesFromBuildFolderTemplate(const FilePath &projectFilePath) { MacroExpander expander; expander.registerExtraResolver([](QString, QString *ret) { *ret = "*"; return true; }); FilePath basePath = projectFilePath; const QString buildDirTemplate = expander.expand( escapeWildcards(buildPropertiesSettings().buildDirectoryTemplate())); FilePath patternPath = FilePath::fromUserInput(buildDirTemplate); if (patternPath.isAbsolutePath()) { const auto [base, pattern] = pathAndPattern(patternPath); basePath = base; patternPath = pattern; } return findBuildFolders(basePath, patternPath); } static QList validKitsForImport(const CMakeProject *project) { QList result; const QList kits = KitManager::kits(); for (Kit *k : kits) { if (!k->isValid()) continue; const auto kitIssues = project->projectIssues(k); if (!kitIssues.isEmpty()) { qCInfo(cmInputLog) << "Discarding kit" << k->displayName() << "due to the following issues:"; for (const auto &issue : kitIssues) qCInfo(cmInputLog) << " " << issue.description(); continue; } result.append(k); } return result; } static FilePaths importCandidatesFromKits(const FilePath &projectFilePath, const QList &kits) { FilePaths result; for (Kit *k : kits) { QString shadowBuildDirectory = CMakeBuildConfiguration::shadowBuildDirectory( projectFilePath, k, "<__bcname__>", BuildConfiguration::Unknown, true) .toUrlishString(); shadowBuildDirectory = escapeWildcards(shadowBuildDirectory).replace("<__bcname__>", "*"); const auto [path, pattern] = pathAndPattern(FilePath::fromString(shadowBuildDirectory)); result << findBuildFolders(path, pattern); } return result; } FilePaths CMakeProjectImporter::importCandidates() { if (!m_project->buildDirectoryToImport().isEmpty()) return {m_project->buildDirectoryToImport()}; FilePaths candidates = presetCandidates(); if (candidates.isEmpty()) { candidates << importCandidatesFromBuildFolderTemplate(projectFilePath()); const QList validKits = validKitsForImport(m_project); candidates << importCandidatesFromKits(projectFilePath(), validKits); } const FilePaths finalists = Utils::filteredUnique(candidates); qCInfo(cmInputLog) << "import candidates:" << finalists; return finalists; } FilePaths CMakeProjectImporter::presetCandidates() { FilePaths candidates; for (const auto &configPreset : m_project->presetsData().configurePresets) { if (configPreset.hidden) continue; if (configPreset.condition) { if (!CMakePresets::Macros::evaluatePresetCondition(configPreset, projectFilePath())) continue; } if (!m_presetsTempDir) return candidates; const FilePath configPresetDir = m_presetsTempDir->filePath() / presetNameToFileName(configPreset.name); configPresetDir.createDir(); candidates << configPresetDir; // If the binaryFilePath exists, do not try to import the existing build, so that // we don't have duplicates, one from the preset and one from the previous configuration. if (configPreset.binaryDir) { Environment env = projectDirectory().deviceEnvironment(); CMakePresets::Macros::expand(configPreset, env, projectDirectory()); QString binaryDir = *configPreset.binaryDir; CMakePresets::Macros::expand(configPreset, env, projectDirectory(), binaryDir); const FilePath binaryFilePath = FilePath::fromString(binaryDir); candidates.removeIf([&binaryFilePath](const FilePath &path) { return path == binaryFilePath; }); } } return candidates; } class DebuggerCMakeExpander { const PresetsDetails::ConfigurePreset &preset; const Environment &env; const FilePath &projectDirectory; public: DebuggerCMakeExpander( const PresetsDetails::ConfigurePreset &p, const Environment &e, const FilePath &projectDir) : preset(p) , env(e) , projectDirectory(projectDir) {} QString expand(const QString &value) const { QString result{value}; CMakePresets::Macros::expand(preset, env, projectDirectory, result); return result; } QVariantMap expand(const QVariantMap &map) const { QVariantMap result{map}; for (auto it = result.begin(); it != result.end(); ++it) if (it->canConvert()) it->setValue(expand(it->toString())); return result; } }; static QVariant findOrRegisterDebugger( Environment &env, const PresetsDetails::ConfigurePreset &preset, const DebuggerCMakeExpander& expander) { const QString debuggerKey("debugger"); if (!preset.vendor || !preset.vendor->contains(debuggerKey)) return {}; const QVariant debuggerVariant = preset.vendor->value(debuggerKey); FilePath debuggerPath = FilePath::fromUserInput(expander.expand(debuggerVariant.toString())); if (!debuggerPath.isEmpty()) { if (debuggerPath.isRelativePath()) debuggerPath = env.searchInPath(debuggerPath.fileName()); const QString mainName = Tr::tr("CMake Preset (%1) %2 Debugger"); DebuggerItem debugger; debugger.setCommand(debuggerPath); debugger.setUnexpandedDisplayName( mainName.arg(preset.name).arg(debuggerPath.completeBaseName())); debugger.setDetectionSource(DetectionSource::Manual); QString errorMessage; debugger.reinitializeFromFile(&errorMessage, &env); if (!errorMessage.isEmpty()) qCWarning(cmInputLog()) << "Error reinitializing debugger" << debuggerPath.toUserOutput() << "Error:" << errorMessage; return DebuggerItemManager::registerDebugger(debugger); } else { auto debuggerMap = debuggerVariant.toMap(); if (debuggerMap.isEmpty()) return {}; // Manually create an Id, otrhewise the Kit will not have a debugger set if (!debuggerMap.contains("Id")) debuggerMap.insert("Id", QUuid::createUuid().toString()); auto store = storeFromMap(expander.expand(debuggerMap)); DebuggerItem debugger(store); return DebuggerItemManager::registerDebugger(debugger); } } Target *CMakeProjectImporter::preferredTarget(const QList &possibleTargets) { if (!m_project->buildDirectoryToImport().isEmpty()) { return Utils::findOrDefault(possibleTargets, [this](const Target *t) { return t->activeBuildConfiguration()->buildDirectory() == m_project->buildDirectoryToImport(); }); } for (Kit *kit : m_project->oldPresetKits()) { const bool haveKit = Utils::contains(possibleTargets, [kit](const auto &target) { return target->kit() == kit; }); if (!haveKit) KitManager::deregisterKit(kit); } m_project->setOldPresetKits({}); return ProjectImporter::preferredTarget(possibleTargets); } bool CMakeProjectImporter::filter(ProjectExplorer::Kit *k) const { if (!m_project->presetsData().havePresets) return true; const auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); if (presetConfigItem.isNull()) return false; const QString presetName = presetConfigItem.expandedValue(k); return std::find_if(m_project->presetsData().configurePresets.cbegin(), m_project->presetsData().configurePresets.cend(), [&presetName](const auto &preset) { return presetName == preset.name; }) != m_project->presetsData().configurePresets.cend(); } static CMakeConfig configurationFromPresetProbe( const FilePath &importPath, const FilePath &sourceDirectory, const PresetsDetails::ConfigurePreset &configurePreset) { const FilePath cmakeListTxt = importPath / Constants::CMAKE_LISTS_TXT; cmakeListTxt.writeFileContents(QByteArray(R"( cmake_minimum_required(VERSION 3.15) project(preset-probe) set(file_path_value_list CMAKE_C_COMPILER CMAKE_CXX_COMPILER QT_HOST_PATH CMAKE_MAKE_PROGRAM) if (NOT CMAKE_SYSTEM_NAME STREQUAL "Android") list(APPEND file_path_value_list CMAKE_SYSROOT) endif() foreach (file_path_value IN LISTS file_path_value_list) if (${file_path_value}) set(${file_path_value} "${${file_path_value}}" CACHE FILEPATH "" FORCE) endif() endforeach() foreach (path_value CMAKE_PREFIX_PATH CMAKE_FIND_ROOT_PATH) if (${path_value}) set(${path_value} "${${path_value}}" CACHE PATH "" FORCE) endif() endforeach() set(string_value_list CMAKE_SYSTEM_NAME) if (NOT CMAKE_SYSTEM_NAME STREQUAL "Android") list(APPEND string_value_list CMAKE_C_COMPILER_TARGET CMAKE_CXX_COMPILER_TARGET) endif() foreach (string_value IN LISTS string_value_list) if (${string_value}) set(${string_value} "${${string_value}}" CACHE STRING "" FORCE) endif() endforeach() )")); Process cmake; cmake.setDisableUnixTerminal(); const FilePath cmakeExecutable = configurePreset.cmakeExecutable.value_or(FilePath()); Environment env = cmakeExecutable.deviceEnvironment(); CMakePresets::Macros::expand(configurePreset, env, sourceDirectory); env.setupEnglishOutput(); cmake.setEnvironment(env); QStringList args; args.emplace_back("-S"); args.emplace_back(importPath.path()); args.emplace_back("-B"); args.emplace_back(importPath.pathAppended("build/").path()); if (configurePreset.generator) { args.emplace_back("-G"); args.emplace_back(*configurePreset.generator); } if (configurePreset.architecture && configurePreset.architecture->value) { if (!configurePreset.architecture->strategy || configurePreset.architecture->strategy != PresetsDetails::ValueStrategyPair::Strategy::external) { args.emplace_back("-A"); args.emplace_back(*configurePreset.architecture->value); } } if (configurePreset.toolset && configurePreset.toolset->value) { if (!configurePreset.toolset->strategy || configurePreset.toolset->strategy != PresetsDetails::ValueStrategyPair::Strategy::external) { args.emplace_back("-T"); args.emplace_back(*configurePreset.toolset->value); } } if (configurePreset.cacheVariables) { const CMakeConfig cache = configurePreset.cacheVariables ? *configurePreset.cacheVariables : CMakeConfig(); const QString cmakeMakeProgram = cache.stringValueOf("CMAKE_MAKE_PROGRAM"); const QString toolchainFile = cache.stringValueOf("CMAKE_TOOLCHAIN_FILE"); const QString prefixPath = cache.stringValueOf("CMAKE_PREFIX_PATH"); const QString findRootPath = cache.stringValueOf("CMAKE_FIND_ROOT_PATH"); const QString qtHostPath = cache.stringValueOf("QT_HOST_PATH"); const QString sysRoot = cache.stringValueOf("CMAKE_SYSROOT"); if (!cmakeMakeProgram.isEmpty()) { args.emplace_back( QStringLiteral("-DCMAKE_MAKE_PROGRAM=%1").arg(cmakeMakeProgram)); } if (!toolchainFile.isEmpty()) { args.emplace_back( QStringLiteral("-DCMAKE_TOOLCHAIN_FILE=%1").arg(toolchainFile)); } if (!prefixPath.isEmpty()) { args.emplace_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(prefixPath)); } if (!findRootPath.isEmpty()) { args.emplace_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(findRootPath)); } if (!qtHostPath.isEmpty()) { args.emplace_back(QStringLiteral("-DQT_HOST_PATH=%1").arg(qtHostPath)); } if (!sysRoot.isEmpty()) { args.emplace_back(QStringLiteral("-DCMAKE_SYSROOT=%1").arg(sysRoot)); } } qCDebug(cmInputLog) << "CMake probing for compilers: " << cmakeExecutable.toUserOutput() << args; cmake.setCommand({cmakeExecutable, args}); cmake.runBlocking(30s); QString errorMessage; const CMakeConfig config = CMakeConfig::fromFile(importPath.pathAppended( "build/CMakeCache.txt"), &errorMessage); return config; } struct QMakeAndCMakePrefixPath { FilePath qmakePath; QString cmakePrefixPath; // can be a semicolon-separated list }; static QMakeAndCMakePrefixPath qtInfoFromCMakeCache(const CMakeConfig &config, const Environment &env) { // Qt4 way to define things (more convenient for us, so try this first;-) const FilePath qmake = config.filePathValueOf("QT_QMAKE_EXECUTABLE"); qCDebug(cmInputLog) << "QT_QMAKE_EXECUTABLE=" << qmake.toUserOutput(); // Check Qt5 settings: oh, the horror! const FilePath qtCMakeDir = [config, env] { FilePath tmp; // Check the CMake "_DIR" variable for (const auto &var : {"Qt6", "Qt6Core", "Qt5", "Qt5Core"}) { tmp = config.filePathValueOf(QByteArray(var) + "_DIR"); if (!tmp.isEmpty()) break; } return tmp; }(); qCDebug(cmInputLog) << "QtXCore_DIR=" << qtCMakeDir.toUserOutput(); const FilePath canQtCMakeDir = FilePath::fromString(qtCMakeDir.toFileInfo().canonicalFilePath()); qCInfo(cmInputLog) << "QtXCore_DIR (canonical)=" << canQtCMakeDir.toUserOutput(); const QString prefixPath = [qtCMakeDir, canQtCMakeDir, config, env] { QString result; if (!qtCMakeDir.isEmpty()) { result = canQtCMakeDir.parentDir().parentDir().parentDir().path(); // Up 3 levels... } else { // Check the CMAKE_PREFIX_PATH and "_ROOT" CMake or environment variables // This can be a single value or a semicolon-separated list for (const auto &var : {"CMAKE_PREFIX_PATH", "Qt6_ROOT", "Qt5_ROOT"}) { result = config.stringValueOf(var); if (result.isEmpty()) result = env.value(QString::fromUtf8(var)); if (!result.isEmpty()) break; } } return result; }(); qCDebug(cmInputLog) << "PrefixPath:" << prefixPath; if (!qmake.isEmpty() && !prefixPath.isEmpty()) return {qmake, prefixPath}; FilePath toolchainFile = config.filePathValueOf(QByteArray("CMAKE_TOOLCHAIN_FILE")); if (prefixPath.isEmpty() && toolchainFile.isEmpty()) return {qmake, QString()}; // Run a CMake project that would do qmake probing TemporaryDirectory qtcQMakeProbeDir("qtc-cmake-qmake-probe-XXXXXXXX"); FilePath cmakeListTxt(qtcQMakeProbeDir.filePath(Constants::CMAKE_LISTS_TXT)); cmakeListTxt.writeFileContents(QByteArray(R"( cmake_minimum_required(VERSION 3.15) project(qmake-probe LANGUAGES NONE) # Bypass Qt6's usage of find_dependency, which would require compiler # and source code probing, which slows things unnecessarily file(WRITE "${CMAKE_SOURCE_DIR}/CMakeFindDependencyMacro.cmake" [=[ macro(find_dependency dep) endmacro() ]=]) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}") find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) if (CMAKE_CROSSCOMPILING) if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") set(qmake_script_suffix ".bat") endif() find_program(qmake_binary NAMES qmake${qmake_script_suffix} PATHS "${Qt${QT_VERSION_MAJOR}_DIR}/../../../bin" NO_DEFAULT_PATH) file(WRITE "${CMAKE_SOURCE_DIR}/qmake-location.txt" "${qmake_binary}") else() file(GENERATE OUTPUT "${CMAKE_SOURCE_DIR}/qmake-location.txt" CONTENT "$") endif() # Remove a Qt CMake hack that adds lib/cmake at the end of every path in CMAKE_PREFIX_PATH list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH) list(TRANSFORM CMAKE_PREFIX_PATH REPLACE "/lib/cmake$" "") file(WRITE "${CMAKE_SOURCE_DIR}/cmake-prefix-path.txt" "${CMAKE_PREFIX_PATH}") )")); Process cmake; cmake.setDisableUnixTerminal(); Environment cmakeEnv(env); cmakeEnv.setupEnglishOutput(); cmake.setEnvironment(cmakeEnv); QString cmakeGenerator = config.stringValueOf(QByteArray("CMAKE_GENERATOR")); QString cmakeGeneratorPlatform = config.stringValueOf(QByteArray("CMAKE_GENERATOR_PLATFORM")); QString cmakeGeneratorToolset = config.stringValueOf(QByteArray("CMAKE_GENERATOR_TOOLSET")); FilePath cmakeExecutable = config.filePathValueOf(QByteArray("CMAKE_COMMAND")); FilePath cmakeMakeProgram = config.filePathValueOf(QByteArray("CMAKE_MAKE_PROGRAM")); FilePath hostPath = config.filePathValueOf(QByteArray("QT_HOST_PATH")); const QString findRootPath = config.stringValueOf("CMAKE_FIND_ROOT_PATH"); QStringList args; args.push_back("-S"); args.push_back(qtcQMakeProbeDir.path().path()); args.push_back("-B"); args.push_back(qtcQMakeProbeDir.filePath("build").path()); if (!cmakeGenerator.isEmpty()) { args.push_back("-G"); args.push_back(cmakeGenerator); } if (!cmakeGeneratorPlatform.isEmpty()) { args.push_back("-A"); args.push_back(cmakeGeneratorPlatform); } if (!cmakeGeneratorToolset.isEmpty()) { args.push_back("-T"); args.push_back(cmakeGeneratorToolset); } if (!cmakeMakeProgram.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_MAKE_PROGRAM=%1").arg(cmakeMakeProgram.path())); } if (!toolchainFile.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_TOOLCHAIN_FILE=%1").arg(toolchainFile.path())); } if (!prefixPath.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(prefixPath)); } if (!findRootPath.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(findRootPath)); } if (!hostPath.isEmpty()) { args.push_back(QStringLiteral("-DQT_HOST_PATH=%1").arg(hostPath.path())); } qCDebug(cmInputLog) << "CMake probing for qmake path: " << cmakeExecutable.toUserOutput() << args; cmake.setCommand({cmakeExecutable, args}); cmake.runBlocking(5s); const FilePath qmakeLocationTxt = qtcQMakeProbeDir.filePath("qmake-location.txt"); const FilePath qmakeLocation = FilePath::fromUtf8( qmakeLocationTxt.fileContents().value_or(QByteArray())); qCDebug(cmInputLog) << "qmake location: " << qmakeLocation.toUserOutput(); const FilePath prefixPathTxt = qtcQMakeProbeDir.filePath("cmake-prefix-path.txt"); const QString resultedPrefixPath = QString::fromUtf8( prefixPathTxt.fileContents().value_or(QByteArray())); qCDebug(cmInputLog) << "PrefixPath [after qmake probe]: " << resultedPrefixPath; return {qmakeLocation, resultedPrefixPath}; } static QList extractToolchainsFromCache(const CMakeConfig &config) { QList result; bool haveCCxxCompiler = false; for (const CMakeConfigItem &i : config) { if (!i.key.startsWith("CMAKE_") || !i.key.endsWith("_COMPILER")) continue; const QByteArray language = i.key.mid(6, i.key.size() - 6 - 9); // skip "CMAKE_" and "_COMPILER" Id languageId; if (language == "CXX") { haveCCxxCompiler = true; languageId = ProjectExplorer::Constants::CXX_LANGUAGE_ID; } else if (language == "C") { haveCCxxCompiler = true; languageId = ProjectExplorer::Constants::C_LANGUAGE_ID; } else languageId = Id::fromName(language); result.append( {FilePath::fromUtf8(i.value), languageId, config.stringValueOf("CMAKE_" + language + "_COMPILER_TARGET")}); } if (!haveCCxxCompiler) { const QByteArray generator = config.valueOf("CMAKE_GENERATOR"); QString cCompilerName; QString cxxCompilerName; if (generator.contains("Visual Studio")) { cCompilerName = "cl.exe"; cxxCompilerName = "cl.exe"; } else if (generator.contains("Xcode")) { cCompilerName = "clang"; cxxCompilerName = "clang++"; } if (!cCompilerName.isEmpty() && !cxxCompilerName.isEmpty()) { const FilePath linker = config.filePathValueOf("CMAKE_LINKER"); if (!linker.isEmpty()) { const FilePath compilerPath = linker.parentDir(); result.append( {compilerPath.pathAppended(cCompilerName), ProjectExplorer::Constants::C_LANGUAGE_ID, {}}); result.append( {compilerPath.pathAppended(cxxCompilerName), ProjectExplorer::Constants::CXX_LANGUAGE_ID, {}}); } } } return result; } static QString extractVisualStudioPlatformFromConfig(const CMakeConfig &config) { const QString cmakeGenerator = config.stringValueOf(QByteArray("CMAKE_GENERATOR")); QString platform; if (cmakeGenerator.contains("Visual Studio")) { const FilePath linker = config.filePathValueOf("CMAKE_LINKER"); const QString toolsDir = linker.parentDir().fileName(); if (toolsDir.compare("x64", Qt::CaseInsensitive) == 0) { platform = "x64"; } else if (toolsDir.compare("x86", Qt::CaseInsensitive) == 0) { platform = "Win32"; } else if (toolsDir.compare("arm64", Qt::CaseInsensitive) == 0) { platform = "ARM64"; } else if (toolsDir.compare("arm", Qt::CaseInsensitive) == 0) { platform = "ARM"; } } return platform; } void updateCompilerPaths(CMakeConfig &config, const Environment &env) { auto updateRelativePath = [&config, env](const QByteArray &key) { FilePath pathValue = config.filePathValueOf(key); if (pathValue.isAbsolutePath() || pathValue.isEmpty()) return; pathValue = env.searchInPath(pathValue.fileName()); auto it = std::find_if(config.begin(), config.end(), [&key](const CMakeConfigItem &item) { return item.key == key; }); QTC_ASSERT(it != config.end(), return); it->value = pathValue.path().toUtf8(); }; updateRelativePath("CMAKE_C_COMPILER"); updateRelativePath("CMAKE_CXX_COMPILER"); } void updateConfigWithDirectoryData(CMakeConfig &config, const std::unique_ptr &data) { auto updateCompilerValue = [&config, &data](const QByteArray &key, const Utils::Id &language) { auto tcd = Utils::findOrDefault(data->toolchains, [&language](const ToolchainDescription &t) { return t.language == language; }); if (config.contains(key)) { CMakeConfigItem &item = config[key]; if (item.value.isEmpty()) item.value = tcd.compilerPath.path().toUtf8(); } else { config.insert( CMakeConfigItem(key, CMakeConfigItem::FILEPATH, tcd.compilerPath.path().toUtf8())); } }; updateCompilerValue("CMAKE_C_COMPILER", ProjectExplorer::Constants::C_LANGUAGE_ID); updateCompilerValue("CMAKE_CXX_COMPILER", ProjectExplorer::Constants::CXX_LANGUAGE_ID); if (data->qt.qt) config.insert(CMakeConfigItem( "QT_QMAKE_EXECUTABLE", CMakeConfigItem::FILEPATH, data->qt.qt->qmakeFilePath().path().toUtf8())); } Toolchain *findExternalToolchain(const QString &presetArchitecture, const QString &presetToolset) { // A compiler path example. Note that the compiler version is not the same version from MsvcToolchain // ... \MSVC\14.29.30133\bin\Hostx64\x64\cl.exe // // And the CMakePresets.json // // "toolset": { // "value": "v142,host=x64,version=14.29.30133", // "strategy": "external" // }, // "architecture": { // "value": "x64", // "strategy": "external" // } auto msvcToolchains = ToolchainManager::toolchains([](const Toolchain *tc) { return tc->typeId() == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID; }); const QSet msvcFlavors = Utils::toSet(Utils::transform(msvcToolchains, [](const Toolchain *tc) { return tc->targetAbi().osFlavor(); })); return ToolchainManager::toolchain( [presetArchitecture, presetToolset, msvcFlavors](const Toolchain *tc) -> bool { if (tc->typeId() != ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) return false; const FilePath compilerPath = tc->compilerCommand(); const QString architecture = compilerPath.parentDir().fileName().toLower(); const QString host = compilerPath.parentDir().parentDir().fileName().toLower().replace("host", "host="); const QString version = QString("version=%1") .arg(compilerPath.parentDir().parentDir().parentDir().parentDir().fileName()); static std::pair abiTable[] = { {QStringLiteral("v144"), Abi::WindowsMsvc2026Flavor}, {QStringLiteral("v143"), Abi::WindowsMsvc2022Flavor}, {QStringLiteral("v142"), Abi::WindowsMsvc2019Flavor}, {QStringLiteral("v141"), Abi::WindowsMsvc2017Flavor}, }; Abi::OSFlavor toolsetAbi = Abi::UnknownFlavor; for (const auto &abiPair : abiTable) { if (presetToolset.contains(abiPair.first)) { toolsetAbi = abiPair.second; break; } } // User didn't specify any flavor, so pick the highest toolchain available if (toolsetAbi == Abi::UnknownFlavor) { for (const auto &abiPair : abiTable) { if (msvcFlavors.contains(abiPair.second)) { toolsetAbi = abiPair.second; break; } } } if (toolsetAbi != tc->targetAbi().osFlavor()) return false; if (presetToolset.contains("host=") && !presetToolset.contains(host)) return false; // Make sure we match also version=14.29 auto versionIndex = presetToolset.indexOf("version="); if (versionIndex != -1 && !version.startsWith(presetToolset.mid(versionIndex))) return false; if (presetArchitecture != architecture) return false; qCDebug(cmInputLog) << "For external architecture" << presetArchitecture << "and toolset" << presetToolset << "the following toolchain was selected:\n" << compilerPath.toUserOutput(); return true; }); } QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, QString *warningMessage) const { QList result; qCInfo(cmInputLog) << "Examining directory:" << importPath.toUserOutput(); if (m_presetsTempDir && importPath.isChildOf(m_presetsTempDir->filePath())) { auto data = std::make_unique(); const QString presetName = fileNameToPresetName(importPath.fileName()); PresetsDetails::ConfigurePreset configurePreset = Utils::findOrDefault(m_project->presetsData().configurePresets, [presetName](const PresetsDetails::ConfigurePreset &preset) { return preset.name == presetName; }); Environment env = projectDirectory().deviceEnvironment(); CMakePresets::Macros::expand(configurePreset, env, projectDirectory()); if (configurePreset.displayName) data->cmakePresetDisplayname = configurePreset.displayName.value(); else data->cmakePresetDisplayname = configurePreset.name; data->cmakePreset = configurePreset.name; if (!configurePreset.cmakeExecutable) { const CMakeTool *cmakeTool = CMakeToolManager::defaultCMakeTool(); if (cmakeTool) { configurePreset.cmakeExecutable = cmakeTool->cmakeExecutable(); } else { configurePreset.cmakeExecutable = FilePath(); TaskHub::addTask( Task::TaskType::DisruptingError, Tr::tr("")); } } else { FilePath cmakeExecutable = configurePreset.cmakeExecutable.value(); QString cmake = cmakeExecutable.path(); // Don't replace in scheme/host CMakePresets::Macros::expand(configurePreset, env, projectDirectory(), cmake); configurePreset.cmakeExecutable = cmakeExecutable.withNewPath(cmake); } data->cmakeBinary = configurePreset.cmakeExecutable.value(); if (configurePreset.generator) data->generator = configurePreset.generator.value(); if (configurePreset.binaryDir) { QString binaryDir = configurePreset.binaryDir.value(); CMakePresets::Macros::expand(configurePreset, env, projectDirectory(), binaryDir); data->buildDirectory = Utils::FilePath::fromString(binaryDir); } const bool architectureExternalStrategy = configurePreset.architecture && configurePreset.architecture->strategy && configurePreset.architecture->strategy == PresetsDetails::ValueStrategyPair::Strategy::external; const bool toolsetExternalStrategy = configurePreset.toolset && configurePreset.toolset->strategy && configurePreset.toolset->strategy == PresetsDetails::ValueStrategyPair::Strategy::external; if (!architectureExternalStrategy && configurePreset.architecture && configurePreset.architecture.value().value) data->platform = configurePreset.architecture.value().value.value(); if (!toolsetExternalStrategy && configurePreset.toolset && configurePreset.toolset.value().value) data->toolset = configurePreset.toolset.value().value.value(); if (architectureExternalStrategy && toolsetExternalStrategy) { const Toolchain *tc = findExternalToolchain(configurePreset.architecture->value.value_or(QString()), configurePreset.toolset->value.value_or(QString())); if (tc) tc->addToEnvironment(env); } CMakePresets::Macros::updateToolchainFile(configurePreset, env, projectDirectory(), data->buildDirectory); CMakePresets::Macros::updateCacheVariables(configurePreset, env, projectDirectory()); const CMakeConfig cache = configurePreset.cacheVariables ? configurePreset.cacheVariables.value() : CMakeConfig(); CMakeConfig config; const bool noCompilers = cache.valueOf("CMAKE_C_COMPILER").isEmpty() && cache.valueOf("CMAKE_CXX_COMPILER").isEmpty(); if (noCompilers || !configurePreset.generator) { QApplication::setOverrideCursor(Qt::WaitCursor); config = configurationFromPresetProbe(importPath, projectDirectory(), configurePreset); QApplication::restoreOverrideCursor(); if (!configurePreset.generator) { QString cmakeGenerator = config.stringValueOf(QByteArray("CMAKE_GENERATOR")); configurePreset.generator = cmakeGenerator; data->generator = cmakeGenerator; data->platform = extractVisualStudioPlatformFromConfig(config); if (!data->platform.isEmpty()) { configurePreset.architecture = PresetsDetails::ValueStrategyPair(); configurePreset.architecture->value = data->platform; } } } else { config = cache; updateCompilerPaths(config, env); config.insert(CMakeConfigItem( "CMAKE_COMMAND", CMakeConfigItem::PATH, configurePreset.cmakeExecutable.value().path().toUtf8())); if (configurePreset.generator) config.insert(CMakeConfigItem( "CMAKE_GENERATOR", CMakeConfigItem::STRING, configurePreset.generator.value().toUtf8())); } data->sysroot = config.filePathValueOf("CMAKE_SYSROOT"); data->osxSysroot = config.stringValueOf("CMAKE_OSX_SYSROOT"); data->cmakeSystemName = config.stringValueOf("CMAKE_SYSTEM_NAME"); const auto [qmake, cmakePrefixPath] = qtInfoFromCMakeCache(config, env); if (!qmake.isEmpty()) data->qt = findOrCreateQtVersion(qmake); if (!cmakePrefixPath.isEmpty() && config.valueOf("CMAKE_PREFIX_PATH").isEmpty()) config.insert( CMakeConfigItem("CMAKE_PREFIX_PATH", CMakeConfigItem::PATH, cmakePrefixPath.toUtf8())); // Toolchains: data->toolchains = extractToolchainsFromCache(config); // Update QT_QMAKE_EXECUTABLE and CMAKE_C|XX_COMPILER config values updateConfigWithDirectoryData(config, data); data->hasQmlDebugging = CMakeBuildConfiguration::hasQmlDebugging(config); data->debugger = findOrRegisterDebugger( env, configurePreset, DebuggerCMakeExpander(configurePreset, env, projectDirectory())); QByteArrayList buildConfigurationTypes = {cache.valueOf("CMAKE_BUILD_TYPE")}; if (buildConfigurationTypes.front().isEmpty()) { buildConfigurationTypes.clear(); QByteArray buildConfigurationTypesString = cache.valueOf("CMAKE_CONFIGURATION_TYPES"); if (!buildConfigurationTypesString.isEmpty()) { buildConfigurationTypes = buildConfigurationTypesString.split(';'); } else { for (int type = CMakeBuildConfigurationFactory::BuildTypeDebug; type != CMakeBuildConfigurationFactory::BuildTypeLast; ++type) { BuildInfo info = CMakeBuildConfigurationFactory::createBuildInfo( CMakeBuildConfigurationFactory::BuildType(type)); buildConfigurationTypes << info.typeName.toUtf8(); } } } for (const auto &buildType : std::as_const(buildConfigurationTypes)) { DirectoryData *newData = new DirectoryData(*data); newData->cmakeBuildType = buildType; // Handle QML Debugging auto type = CMakeBuildConfigurationFactory::buildTypeFromByteArray( newData->cmakeBuildType); if (type == CMakeBuildConfigurationFactory::BuildTypeDebug || type == CMakeBuildConfigurationFactory::BuildTypeProfile) newData->hasQmlDebugging = true; result.emplace_back(newData); } return result; } const FilePath cacheFile = importPath.pathAppended(Constants::CMAKE_CACHE_TXT); if (!cacheFile.exists()) { qCDebug(cmInputLog) << cacheFile.toUserOutput() << "does not exist, returning."; return result; } QString errorMessage; const CMakeConfig config = CMakeConfig::fromFile(cacheFile, &errorMessage); if (config.isEmpty() || !errorMessage.isEmpty()) { qCDebug(cmInputLog) << "Failed to read configuration from" << cacheFile << errorMessage; return result; } QByteArrayList buildConfigurationTypes = {config.valueOf("CMAKE_BUILD_TYPE")}; if (buildConfigurationTypes.front().isEmpty()) { QByteArray buildConfigurationTypesString = config.valueOf("CMAKE_CONFIGURATION_TYPES"); if (!buildConfigurationTypesString.isEmpty()) buildConfigurationTypes = buildConfigurationTypesString.split(';'); } const Environment env = projectDirectory().deviceEnvironment(); for (auto const &buildType: std::as_const(buildConfigurationTypes)) { auto data = std::make_unique(); data->cmakeHomeDirectory = FilePath::fromUserInput(config.stringValueOf("CMAKE_HOME_DIRECTORY")) .canonicalPath(); const FilePath canonicalProjectDirectory = projectDirectory().canonicalPath(); if (data->cmakeHomeDirectory != canonicalProjectDirectory) { *warningMessage = Tr::tr("Unexpected source directory \"%1\", expected \"%2\". " "This can be correct in some situations, for example when " "importing a standalone Qt test, but usually this is an error. " "Import the build anyway?") .arg(data->cmakeHomeDirectory.toUserOutput(), canonicalProjectDirectory.toUserOutput()); } data->hasQmlDebugging = CMakeBuildConfiguration::hasQmlDebugging(config); data->buildDirectory = importPath; data->cmakeBuildType = buildType; data->cmakeBinary = config.filePathValueOf("CMAKE_COMMAND"); data->generator = config.stringValueOf("CMAKE_GENERATOR"); data->platform = config.stringValueOf("CMAKE_GENERATOR_PLATFORM"); if (data->platform.isEmpty()) data->platform = extractVisualStudioPlatformFromConfig(config); data->toolset = config.stringValueOf("CMAKE_GENERATOR_TOOLSET"); data->sysroot = config.filePathValueOf("CMAKE_SYSROOT"); data->osxSysroot = config.stringValueOf("CMAKE_OSX_SYSROOT"); // Qt: const auto info = qtInfoFromCMakeCache(config, env); if (!info.qmakePath.isEmpty()) data->qt = findOrCreateQtVersion(info.qmakePath); // Toolchains: data->toolchains = extractToolchainsFromCache(config); qCInfo(cmInputLog) << "Offering to import" << importPath.toUserOutput(); result.push_back(static_cast(data.release())); } return result; } void CMakeProjectImporter::ensureBuildDirectory(DirectoryData &data, const Kit *k) const { if (!data.buildDirectory.isEmpty()) return; const auto cmakeBuildType = CMakeBuildConfigurationFactory::buildTypeFromByteArray( data.cmakeBuildType); BuildInfo buildInfo = CMakeBuildConfigurationFactory::createBuildInfo(cmakeBuildType); data.buildDirectory = CMakeBuildConfiguration::shadowBuildDirectory(projectFilePath(), k, buildInfo.typeName, buildInfo.buildType, true); } bool CMakeProjectImporter::matchKit(void *directoryData, const Kit *k) const { DirectoryData *data = static_cast(directoryData); const FilePath cmakeExecutable = CMakeKitAspect::cmakeExecutable(k); if (cmakeExecutable.isEmpty() || cmakeExecutable != data->cmakeBinary) return false; if (CMakeGeneratorKitAspect::generator(k) != data->generator || CMakeGeneratorKitAspect::platform(k) != data->platform || CMakeGeneratorKitAspect::toolset(k) != data->toolset) return false; const FilePath kitSysroot = SysRootKitAspect::sysRoot(k); if (kitSysroot != data->sysroot && (data->osxSysroot != "iphoneos" || !kitSysroot.contains("/iPhoneOS.platform/")) && (data->osxSysroot != "iphonesimulator" || !kitSysroot.contains("/iPhoneSimulator.platform/"))) { return false; } if (data->qt.qt && QtSupport::QtKitAspect::qtVersionId(k) != data->qt.qt->uniqueId()) return false; const bool compilersMatch = [k, data] { const QList allLanguages = ToolchainManager::allLanguages(); for (const ToolchainDescriptionEx &tcd : std::as_const(data->toolchains)) { if (!Utils::contains(allLanguages, [&tcd](const Id &language) { return language == tcd.language; })) continue; Toolchain *tc = ToolchainKitAspect::toolchain(k, tcd.language); if ((!tc || !tc->matchesCompilerCommand(tcd.compilerPath))) { return false; } } return true; }(); const bool noCompilers = [k, data] { const QList allLanguages = ToolchainManager::allLanguages(); for (const ToolchainDescriptionEx &tcd : std::as_const(data->toolchains)) { if (!Utils::contains(allLanguages, [&tcd](const Id &language) { return language == tcd.language; })) continue; Toolchain *tc = ToolchainKitAspect::toolchain(k, tcd.language); if (tc && tc->matchesCompilerCommand(tcd.compilerPath)) { return false; } } return true; }(); bool haveCMakePreset = false; if (!data->cmakePreset.isEmpty()) { const auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); const QString presetName = presetConfigItem.expandedValue(k); if (data->cmakePreset != presetName) return false; if (!k->unexpandedDisplayName().contains(displayPresetName(data->cmakePresetDisplayname))) return false; ensureBuildDirectory(*data, k); haveCMakePreset = true; } if (!compilersMatch && !(haveCMakePreset && noCompilers)) return false; qCDebug(cmInputLog) << k->displayName() << "matches directoryData for" << data->buildDirectory.toUserOutput(); return true; } static void setupBuildAndRunDevice(Kit *k, const QString &cmakeSystemName, const FilePath &sysroot) { if (cmakeSystemName == "Android") { RunDeviceTypeKitAspect::setDeviceTypeId(k, Android::Constants::ANDROID_DEVICE_TYPE); } else if (cmakeSystemName == "iOS") { if (sysroot.fileName() == "iPhoneSimulator.sdk") RunDeviceTypeKitAspect::setDeviceTypeId(k, Ios::Constants::IOS_SIMULATOR_TYPE); else RunDeviceTypeKitAspect::setDeviceTypeId(k, Ios::Constants::IOS_DEVICE_TYPE); } else if (cmakeSystemName == "Emscripten") { RunDeviceTypeKitAspect::setDeviceTypeId(k, WebAssembly::Constants::WEBASSEMBLY_DEVICE_TYPE); } else if (cmakeSystemName == "Linux" && !sysroot.isEmpty()) { RunDeviceTypeKitAspect::setDeviceTypeId(k, RemoteLinux::Constants::GenericLinuxOsType); } else if (cmakeSystemName == "QNX") { RunDeviceTypeKitAspect::setDeviceTypeId(k, Qnx::Constants::QNX_QNX_OS_TYPE); } else if (cmakeSystemName == "VxWorks") { RunDeviceTypeKitAspect::setDeviceTypeId(k, Constants::VXWORKS_DEVICE_TYPE); } } Kit *CMakeProjectImporter::createKit(void *directoryData) const { DirectoryData *data = static_cast(directoryData); return QtProjectImporter::createTemporaryKit(data->qt, [&data, this](Kit *k) { CMakeTool *cmakeTool = CMakeToolManager::findByCommand(data->cmakeBinary); if (!cmakeTool) { qCDebug(cmInputLog) << "Creating temporary CMakeTool for" << data->cmakeBinary.toUserOutput(); UpdateGuard guard(*this); auto newTool = std::make_unique(DetectionSource::Manual, CMakeTool::createId()); newTool->setFilePath(data->cmakeBinary); newTool->setDisplayName(uniqueCMakeToolDisplayName(*newTool)); cmakeTool = newTool.get(); CMakeToolManager::registerCMakeTool(std::move(newTool)); addTemporaryData(CMakeKitAspect::id(), cmakeTool->id().toSetting(), k); } CMakeKitAspect::setCMakeExecutable(k, data->cmakeBinary); CMakeGeneratorKitAspect::setGenerator(k, data->generator); CMakeGeneratorKitAspect::setPlatform(k, data->platform); CMakeGeneratorKitAspect::setToolset(k, data->toolset); SysRootKitAspect::setSysRoot(k, data->sysroot); setupBuildAndRunDevice(k, data->cmakeSystemName, data->sysroot); for (const ToolchainDescriptionEx &cmtcd : std::as_const(data->toolchains)) { const ToolchainData tcd = findOrCreateToolchains(cmtcd); QTC_ASSERT(!tcd.tcs.isEmpty(), continue); if (tcd.areTemporary) { for (Toolchain *tc : tcd.tcs) addTemporaryData(ToolchainKitAspect::id(), tc->id(), k); } Toolchain *toolchain = tcd.tcs.at(0); if (!cmtcd.originalTargetTriple.isEmpty()) toolchain->setExplicitCodeModelTargetTriple(cmtcd.originalTargetTriple); // Mark CMake presets toolchains as manual if (!data->cmakePresetDisplayname.isEmpty() && tcd.areTemporary) toolchain->setDetectionSource(DetectionSource::Manual); ToolchainKitAspect::setToolchain(k, toolchain); } if (!data->cmakePresetDisplayname.isEmpty()) { k->setUnexpandedDisplayName(displayPresetName(data->cmakePresetDisplayname)); CMakeConfigurationKitAspect::setCMakePreset(k, data->cmakePreset); } if (!data->cmakePreset.isEmpty()) ensureBuildDirectory(*data, k); if (data->debugger.isValid()) DebuggerKitAspect::setDebugger(k, data->debugger); qCInfo(cmInputLog) << "Temporary Kit created."; }); } const QList CMakeProjectImporter::buildInfoList(void *directoryData) const { auto data = static_cast(directoryData); // create info: CMakeBuildConfigurationFactory::BuildType buildType = CMakeBuildConfigurationFactory::buildTypeFromByteArray(data->cmakeBuildType); // RelWithDebInfo + QML Debugging = Profile if (buildType == CMakeBuildConfigurationFactory::BuildTypeRelWithDebInfo && data->hasQmlDebugging) buildType = CMakeBuildConfigurationFactory::BuildTypeProfile; BuildInfo info = CMakeBuildConfigurationFactory::createBuildInfo(buildType); // For CMake Presets use the provided build type if is not mapped to a known type if (!data->cmakePreset.isEmpty() && info.buildType == BuildConfiguration::Unknown) info.typeName = info.displayName = QString::fromUtf8(data->cmakeBuildType); info.buildDirectory = data->buildDirectory; info.buildSystemName = CMakeBuildSystem::name(); QVariantMap config = info.extraInfo.toMap(); // new empty, or existing one from createBuildInfo config.insert(Constants::CMAKE_HOME_DIR, data->cmakeHomeDirectory.toVariant()); // Potentially overwrite the default QML Debugging settings for the build type as set by // createBuildInfo, in case we are importing a "Debug" CMake configuration without QML Debugging config.insert(Constants::QML_DEBUG_SETTING, data->hasQmlDebugging ? TriState::Enabled.toVariant() : TriState::Default.toVariant()); if (!data->cmakePreset.isEmpty()) // hide the "(imported)" suffix for CMake presets build configurations config["hideImportedSuffix"] = true; else config[Constants::CMAKE_IMPORTED_BUILD] = true; info.extraInfo = config; qCDebug(cmInputLog) << "BuildInfo configured."; return {info}; } void CMakeProjectImporter::deleteDirectoryData(void *directoryData) const { delete static_cast(directoryData); } } // CMakeProjectManager::Internal #ifdef WITH_TESTS #include namespace CMakeProjectManager::Internal { class CMakeProjectImporterTest final : public QObject { Q_OBJECT private slots: void testCMakeProjectImporterQt_data(); void testCMakeProjectImporterQt(); void testCMakeProjectImporterToolchain_data(); void testCMakeProjectImporterToolchain(); }; void CMakeProjectImporterTest::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 CMakeProjectImporterTest::testCMakeProjectImporterQt() { QFETCH(QStringList, cache); QFETCH(QString, expectedQmake); CMakeConfig config; for (const QString &c : std::as_const(cache)) { const int pos = c.indexOf('='); Q_ASSERT(pos > 0); const QString key = c.left(pos); const QString value = c.mid(pos + 1); config.insert(CMakeConfigItem(key.toUtf8(), value.toUtf8())); } auto [realQmake, cmakePrefixPath] = qtInfoFromCMakeCache(config, Environment::systemEnvironment()); QCOMPARE(realQmake.path(), expectedQmake); } void CMakeProjectImporterTest::testCMakeProjectImporterToolchain_data() { QTest::addColumn("cache"); QTest::addColumn("expectedLanguages"); QTest::addColumn("expectedToolchains"); QTest::newRow("Empty input") << QStringList() << QByteArrayList() << FilePaths(); QTest::newRow("Unrelated input") << QStringList("CMAKE_SOMETHING_ELSE=/tmp") << QByteArrayList() << FilePaths(); QTest::newRow("CXX compiler") << QStringList({"CMAKE_CXX_COMPILER=/usr/bin/g++"}) << QByteArrayList({"Cxx"}) << FilePaths({"/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"}) << FilePaths({"/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"}) << FilePaths({"/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"}) << FilePaths({"/usr/bin/g++", "/usr/bin/clang", "/tmp/strange/compiler"}); } void CMakeProjectImporterTest::testCMakeProjectImporterToolchain() { QFETCH(QStringList, cache); QFETCH(QByteArrayList, expectedLanguages); QFETCH(FilePaths, expectedToolchains); QCOMPARE(expectedLanguages.count(), expectedToolchains.count()); CMakeConfig config; for (const QString &c : std::as_const(cache)) { const int pos = c.indexOf('='); Q_ASSERT(pos > 0); const QString key = c.left(pos); const QString value = c.mid(pos + 1); config.insert(CMakeConfigItem(key.toUtf8(), value.toUtf8())); } const QList tcs = extractToolchainsFromCache(config); QCOMPARE(tcs.count(), expectedLanguages.count()); for (int i = 0; i < tcs.count(); ++i) { QCOMPARE(tcs.at(i).language, expectedLanguages.at(i)); QCOMPARE(tcs.at(i).compilerPath, expectedToolchains.at(i)); } } QObject *createCMakeProjectImporterTest() { return new CMakeProjectImporterTest; } } // CMakeProjectManager::Internal #endif #include "cmakeprojectimporter.moc"