diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 848411a761f..17c199e144b 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -35,5 +35,7 @@ add_qtc_plugin(CMakeProjectManager fileapidataextractor.cpp fileapidataextractor.h fileapiparser.cpp fileapiparser.h fileapireader.cpp fileapireader.h + presetsparser.cpp presetsparser.h + presetsmacros.cpp presetsmacros.h projecttreehelper.cpp projecttreehelper.h ) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index a581833b7ad..c2ba8a19307 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -8,12 +8,15 @@ #include "cmakebuildsystem.h" #include "cmakeconfigitem.h" #include "cmakekitinformation.h" +#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmakeprojectplugin.h" #include "cmakespecificsettings.h" #include "configmodel.h" #include "configmodelitemdelegate.h" #include "fileapiparser.h" +#include "presetsmacros.h" +#include "presetsparser.h" #include #include @@ -1163,6 +1166,164 @@ static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString buildT return cmd; } +static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArguments, + const CMakeProject *project, + const Kit *k, + const Utils::Environment &env) + +{ + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); + if (presetItem.isNull()) + return; + + // Remove the -DQTC_CMAKE_PRESET argument, which is only used as a kit marker + const QString presetArgument = presetItem.toArgument(); + const QString presetName = presetItem.expandedValue(k); + initialArguments.removeIf( + [presetArgument](const QString &item) { return item == presetArgument; }); + + PresetsDetails::ConfigurePreset configurePreset + = Utils::findOrDefault(project->presetsData().configurePresets, + [presetName](const PresetsDetails::ConfigurePreset &preset) { + return preset.name == presetName; + }); + + // Add the command line arguments + if (configurePreset.warnings) { + if (configurePreset.warnings.value().dev) { + bool value = configurePreset.warnings.value().dev.value(); + initialArguments.append(value ? QString("-Wdev") : QString("-Wno-dev")); + } + if (configurePreset.warnings.value().deprecated) { + bool value = configurePreset.warnings.value().deprecated.value(); + initialArguments.append(value ? QString("-Wdeprecated") : QString("-Wno-deprecated")); + } + if (configurePreset.warnings.value().uninitialized + && configurePreset.warnings.value().uninitialized.value()) + initialArguments.append("--warn-uninitialized"); + if (configurePreset.warnings.value().unusedCli + && !configurePreset.warnings.value().unusedCli.value()) + initialArguments.append(" --no-warn-unused-cli"); + if (configurePreset.warnings.value().systemVars + && configurePreset.warnings.value().systemVars.value()) + initialArguments.append("--check-system-vars"); + } + + if (configurePreset.errors) { + if (configurePreset.errors.value().dev) { + bool value = configurePreset.errors.value().dev.value(); + initialArguments.append(value ? QString("-Werror=dev") : QString("-Wno-error=dev")); + } + if (configurePreset.errors.value().deprecated) { + bool value = configurePreset.errors.value().deprecated.value(); + initialArguments.append(value ? QString("-Werror=deprecated") + : QString("-Wno-error=deprecated")); + } + } + + if (configurePreset.debug) { + if (configurePreset.debug.value().find && configurePreset.debug.value().find.value()) + initialArguments.append("--debug-find"); + if (configurePreset.debug.value().tryCompile + && configurePreset.debug.value().tryCompile.value()) + initialArguments.append("--debug-trycompile"); + if (configurePreset.debug.value().output && configurePreset.debug.value().output.value()) + initialArguments.append("--debug-output"); + } + + // Merge the presets cache variables + CMakeConfig cache; + if (configurePreset.cacheVariables) + cache = configurePreset.cacheVariables.value(); + + for (const CMakeConfigItem &presetItemRaw : cache) { + + // Expand the CMakePresets Macros + CMakeConfigItem presetItem(presetItemRaw); + + QString presetItemValue = QString::fromUtf8(presetItem.value); + CMakePresets::Macros::expand(configurePreset, env, project->projectDirectory(), presetItemValue); + presetItem.value = presetItemValue.toUtf8(); + + const QString presetItemArg = presetItem.toArgument(); + const QString presetItemArgNoType = presetItemArg.left(presetItemArg.indexOf(":")); + + auto it = std::find_if(initialArguments.begin(), + initialArguments.end(), + [presetItemArgNoType](const QString &arg) { + return arg.startsWith(presetItemArgNoType); + }); + + if (it != initialArguments.end()) { + QString &arg = *it; + CMakeConfigItem argItem = CMakeConfigItem::fromString(arg.mid(2)); // skip -D + + // For multi value path variables append the non Qt path + if (argItem.key == "CMAKE_PREFIX_PATH" || argItem.key == "CMAKE_FIND_ROOT_PATH") { + QStringList presetValueList = presetItem.expandedValue(k).split(";"); + + // Remove the expanded Qt path from the presets values + QString argItemExpandedValue = argItem.expandedValue(k); + presetValueList.removeIf([argItemExpandedValue](const QString &presetPath) { + QStringList argItemPaths = argItemExpandedValue.split(";"); + for (const QString &argPath : argItemPaths) { + const FilePath argFilePath = FilePath::fromString(argPath); + const FilePath presetFilePath = FilePath::fromString(presetPath); + + if (argFilePath == presetFilePath) + return true; + } + return false; + }); + + // Add the presets values to the final argument + for (const QString &presetPath : presetValueList) { + argItem.value.append(";"); + argItem.value.append(presetPath.toUtf8()); + } + + arg = argItem.toArgument(); + } else if (argItem.key == "CMAKE_C_COMPILER" || argItem.key == "CMAKE_CXX_COMPILER" + || argItem.key == "QT_QMAKE_EXECUTABLE" || argItem.key == "QT_HOST_PATH" + || argItem.key == "CMAKE_PROJECT_INCLUDE_BEFORE" + || argItem.key == "CMAKE_TOOLCHAIN_FILE") { + const FilePath argFilePath = FilePath::fromString(argItem.expandedValue(k)); + const FilePath presetFilePath = FilePath::fromUtf8(presetItem.value); + + if (argFilePath != presetFilePath) + arg = presetItem.toArgument(); + } else if (argItem.expandedValue(k) != QString::fromUtf8(presetItem.value)) { + arg = presetItem.toArgument(); + } + } else { + initialArguments.append(presetItem.toArgument()); + } + } +} + +static Utils::EnvironmentItems getEnvironmentItemsFromCMakePreset(const CMakeProject *project, + const Kit *k) + +{ + Utils::EnvironmentItems envItems; + + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); + if (presetItem.isNull()) + return envItems; + + const QString presetName = presetItem.expandedValue(k); + + PresetsDetails::ConfigurePreset configurePreset + = Utils::findOrDefault(project->presetsData().configurePresets, + [presetName](const PresetsDetails::ConfigurePreset &preset) { + return preset.name == presetName; + }); + + CMakePresets::Macros::expand(configurePreset, envItems, project->projectDirectory()); + + return envItems; +} + // ----------------------------------------------------------------------------- // CMakeBuildConfigurationPrivate: // ----------------------------------------------------------------------------- @@ -1365,7 +1526,15 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) if (qt && qt->isQmlDebuggingSupported()) cmd.addArg("-DCMAKE_CXX_FLAGS_INIT:STRING=%{" + QLatin1String(QT_QML_DEBUG_FLAG) + "}"); - m_buildSystem->setInitialCMakeArguments(cmd.splitArguments()); + CMakeProject *cmakeProject = static_cast(target->project()); + setUserConfigureEnvironmentChanges(getEnvironmentItemsFromCMakePreset(cmakeProject, k)); + + QStringList initialCMakeArguments = cmd.splitArguments(); + addCMakeConfigurePresetToInitialArguments(initialCMakeArguments, + cmakeProject, + k, + configureEnvironment()); + m_buildSystem->setInitialCMakeArguments(initialCMakeArguments); m_buildSystem->setCMakeBuildType(buildType); updateAndEmitConfigureEnvironmentChanged(); }); diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index e26d1469216..6be2257c23b 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 Canonical Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#include "cmakeconfigitem.h" #include "cmakekitinformation.h" #include "cmakeprojectconstants.h" @@ -875,6 +876,7 @@ const char CMAKE_C_TOOLCHAIN_KEY[] = "CMAKE_C_COMPILER"; const char CMAKE_CXX_TOOLCHAIN_KEY[] = "CMAKE_CXX_COMPILER"; const char CMAKE_QMAKE_KEY[] = "QT_QMAKE_EXECUTABLE"; const char CMAKE_PREFIX_PATH_KEY[] = "CMAKE_PREFIX_PATH"; +const char QTC_CMAKE_PRESET_KEY[] = "QTC_CMAKE_PRESET"; class CMakeConfigurationKitAspectWidget final : public KitAspectWidget { @@ -1120,6 +1122,23 @@ CMakeConfig CMakeConfigurationKitAspect::defaultConfiguration(const Kit *k) return config; } +void CMakeConfigurationKitAspect::setCMakePreset(Kit *k, const QString &presetName) +{ + CMakeConfig config = configuration(k); + config.prepend( + CMakeConfigItem(QTC_CMAKE_PRESET_KEY, CMakeConfigItem::INTERNAL, presetName.toUtf8())); + + setConfiguration(k, config); +} + +CMakeConfigItem CMakeConfigurationKitAspect::cmakePresetConfigItem(const ProjectExplorer::Kit *k) +{ + const CMakeConfig config = configuration(k); + return Utils::findOrDefault(config, [](const CMakeConfigItem &item) { + return item.key == QTC_CMAKE_PRESET_KEY; + }); +} + QVariant CMakeConfigurationKitAspect::defaultValue(const Kit *k) const { // FIXME: Convert preload scripts diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.h b/src/plugins/cmakeprojectmanager/cmakekitinformation.h index d68a1192034..302b5c7c1d6 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.h +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.h @@ -90,6 +90,9 @@ public: static CMakeConfig defaultConfiguration(const ProjectExplorer::Kit *k); + static void setCMakePreset(ProjectExplorer::Kit *k, const QString &presetName); + static CMakeConfigItem cmakePresetConfigItem(const ProjectExplorer::Kit *k); + // KitAspect interface ProjectExplorer::Tasks validate(const ProjectExplorer::Kit *k) const final; void setup(ProjectExplorer::Kit *k) final; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 402f3adf1be..cbee35001bd 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -15,6 +15,7 @@ #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -34,6 +35,8 @@ CMakeProject::CMakeProject(const FilePath &fileName) setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); setHasMakeInstallEquivalent(true); + + readPresets(); } CMakeProject::~CMakeProject() @@ -59,7 +62,7 @@ Tasks CMakeProject::projectIssues(const Kit *k) const ProjectImporter *CMakeProject::projectImporter() const { if (!m_projectImporter) - m_projectImporter = new CMakeProjectImporter(projectFilePath()); + m_projectImporter = new CMakeProjectImporter(projectFilePath(), m_presetsData); return m_projectImporter; } @@ -73,6 +76,100 @@ void CMakeProject::clearIssues() m_issues.clear(); } +PresetsData CMakeProject::presetsData() const +{ + return m_presetsData; +} + +Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakePresetsData, + Internal::PresetsData &cmakeUserPresetsData) +{ + Internal::PresetsData result; + result.version = cmakePresetsData.version; + result.cmakeMinimimRequired = cmakePresetsData.cmakeMinimimRequired; + + QHash configurePresets; + + // Populate the hash map with the CMakePresets + for (const PresetsDetails::ConfigurePreset &p: cmakePresetsData.configurePresets) + configurePresets.insert(p.name, p); + + auto resolveInherits = + [configurePresets](std::vector &configurePresetsList) { + for (PresetsDetails::ConfigurePreset &cp : configurePresetsList) { + if (!cp.inherits) + continue; + + for (const QString &inheritFromName : cp.inherits.value()) + if (configurePresets.contains(inheritFromName)) + cp.inheritFrom(configurePresets[inheritFromName]); + } + }; + + // First resolve the CMakePresets + resolveInherits(cmakePresetsData.configurePresets); + + // Add the CMakeUserPresets to the resolve hash map + for (const PresetsDetails::ConfigurePreset &cp : cmakeUserPresetsData.configurePresets) { + if (configurePresets.contains(cp.name)) { + TaskHub::addTask(BuildSystemTask( + Task::TaskType::Error, + tr("CMakeUserPresets.json cannot re-define the configure preset: %1").arg(cp.name), + "CMakeUserPresets.json")); + TaskHub::requestPopup(); + } else { + configurePresets.insert(cp.name, cp); + } + } + + // Then resolve the CMakeUserPresets + resolveInherits(cmakeUserPresetsData.configurePresets); + + // Get both CMakePresets and CMakeUserPresets into the result + result.configurePresets = cmakePresetsData.configurePresets; + + // std::vector doesn't have append + std::copy(cmakeUserPresetsData.configurePresets.begin(), + cmakeUserPresetsData.configurePresets.end(), + std::back_inserter(result.configurePresets)); + + return result; +} + +void CMakeProject::readPresets() +{ + auto parsePreset = [](const Utils::FilePath &presetFile) -> Internal::PresetsData { + Internal::PresetsData data; + Internal::PresetsParser parser; + + QString errorMessage; + int errorLine = -1; + + if (presetFile.exists()) { + if (parser.parse(presetFile, errorMessage, errorLine)) { + data = parser.presetsData(); + } else { + TaskHub::addTask(BuildSystemTask(Task::TaskType::Error, + tr("Failed to load %1: %2") + .arg(presetFile.fileName()) + .arg(errorMessage), + presetFile, + errorLine)); + TaskHub::requestPopup(); + } + } + return data; + }; + + const Utils::FilePath cmakePresetsJson = projectDirectory().pathAppended("CMakePresets.json"); + const Utils::FilePath cmakeUserPresetsJson = projectDirectory().pathAppended("CMakeUserPresets.json"); + + Internal::PresetsData cmakePresetsData = parsePreset(cmakePresetsJson); + Internal::PresetsData cmakeUserPresetsData = parsePreset(cmakeUserPresetsJson); + + m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData); +} + bool CMakeProject::setupTarget(Target *t) { t->updateDefaultBuildConfigurations(); diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index c0bc83d32f9..4854ed1ffc1 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -4,6 +4,7 @@ #pragma once #include "cmake_global.h" +#include "presetsparser.h" #include @@ -27,15 +28,21 @@ public: void addIssue(IssueType type, const QString &text); void clearIssues(); + Internal::PresetsData presetsData() const; + void readPresets(); + protected: bool setupTarget(ProjectExplorer::Target *t) final; private: ProjectExplorer::DeploymentKnowledge deploymentKnowledge() const override; + Internal::PresetsData combinePresets(Internal::PresetsData &cmakePresetsData, + Internal::PresetsData &cmakeUserPresetsData); mutable Internal::CMakeProjectImporter *m_projectImporter = nullptr; ProjectExplorer::Tasks m_issues; + Internal::PresetsData m_presetsData; }; } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp index 8ea3aa99a88..c487edda481 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -7,6 +7,7 @@ #include "cmakekitinformation.h" #include "cmakeprojectconstants.h" #include "cmaketoolmanager.h" +#include "presetsmacros.h" #include @@ -23,6 +24,7 @@ #include #include +#include #include #include @@ -43,6 +45,9 @@ struct DirectoryData FilePath cmakeHomeDirectory; bool hasQmlDebugging = false; + QString cmakePresetDisplayname; + QString cmakePreset; + // Kit Stuff FilePath cmakeBinary; QString generator; @@ -88,7 +93,10 @@ static QString uniqueCMakeToolDisplayName(CMakeTool &tool) // CMakeProjectImporter -CMakeProjectImporter::CMakeProjectImporter(const FilePath &path) : QtProjectImporter(path) +CMakeProjectImporter::CMakeProjectImporter(const FilePath &path, const PresetsData &presetsData) + : QtProjectImporter(path) + , m_presetsData(presetsData) + , m_presetsTempDir("qtc-cmake-presets-XXXXXXXX") { useTemporaryKitAspect(CMakeKitAspect::id(), [this](Kit *k, const QVariantList &vl) { cleanupTemporaryCMake(k, vl); }, @@ -111,11 +119,102 @@ QStringList CMakeProjectImporter::importCandidates() BuildConfiguration::Unknown); candidates << scanDirectory(shadowBuildDirectory.absolutePath(), QString()); } + + for (const auto &configPreset : m_presetsData.configurePresets) { + if (configPreset.hidden.value()) + continue; + + const QString presetDirName = m_presetsTempDir.filePath(configPreset.name).toString(); + const QDir presetDir; + presetDir.mkpath(presetDirName); + candidates << presetDirName; + } + const QStringList finalists = Utils::filteredUnique(candidates); qCInfo(cmInputLog) << "import candidates:" << finalists; return finalists; } +static CMakeConfig configurationFromPresetProbe( + const FilePath &importPath, const PresetsDetails::ConfigurePreset &configurePreset) +{ + QFile cmakeListTxt(importPath.pathAppended("CMakeLists.txt").toString()); + if (!cmakeListTxt.open(QIODevice::WriteOnly)) { + return {}; + } + + cmakeListTxt.write(QByteArray("cmake_minimum_required(VERSION 3.15)\n" + "\n" + "project(preset-probe)\n" + "\n")); + cmakeListTxt.close(); + + QtcProcess cmake; + cmake.setTimeoutS(30); + cmake.setDisableUnixTerminal(); + + Environment env = Environment::systemEnvironment(); + CMakePresets::Macros::expand(configurePreset, env, importPath); + + env.setupEnglishOutput(); + cmake.setEnvironment(env); + cmake.setTimeOutMessageBoxEnabled(false); + + const FilePath cmakeExecutable = FilePath::fromString(configurePreset.cmakeExecutable.value()); + + 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.value()); + } + + if (configurePreset.cacheVariables) { + const CMakeConfig cache = configurePreset.cacheVariables + ? configurePreset.cacheVariables.value() + : CMakeConfig(); + const FilePath cmakeMakeProgram = cache.filePathValueOf(QByteArray("CMAKE_MAKE_PROGRAM")); + const FilePath toolchainFile = cache.filePathValueOf(QByteArray("CMAKE_TOOLCHAIN_FILE")); + const QString prefixPath = cache.stringValueOf(QByteArray("CMAKE_PREFIX_PATH")); + const QString findRootPath = cache.stringValueOf(QByteArray("CMAKE_FIND_ROOT_PATH")); + const QString qtHostPath = cache.stringValueOf(QByteArray("QT_HOST_PATH")); + + if (!cmakeMakeProgram.isEmpty()) { + args.emplace_back( + QStringLiteral("-DCMAKE_MAKE_PROGRAM=%1").arg(cmakeMakeProgram.toString())); + } + if (!toolchainFile.isEmpty()) { + args.emplace_back( + QStringLiteral("-DCMAKE_TOOLCHAIN_FILE=%1").arg(toolchainFile.toString())); + } + 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)); + } + } + + qCDebug(cmInputLog) << "CMake probing for compilers: " << cmakeExecutable.toUserOutput() + << args; + cmake.setCommand({cmakeExecutable, args}); + cmake.runBlocking(); + + QString errorMessage; + const CMakeConfig config = CMakeConfig::fromFile(importPath.pathAppended( + "build/CMakeCache.txt"), + &errorMessage); + + return config; +} + static FilePath qmakeFromCMakeCache(const CMakeConfig &config) { // Qt4 way to define things (more convenient for us, so try this first;-) @@ -134,10 +233,15 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) qCDebug(cmInputLog) << "QtXCore_DIR=" << qtCMakeDir.toUserOutput(); const FilePath canQtCMakeDir = FilePath::fromString(qtCMakeDir.toFileInfo().canonicalFilePath()); qCInfo(cmInputLog) << "QtXCore_DIR (canonical)=" << canQtCMakeDir.toUserOutput(); - if (qtCMakeDir.isEmpty()) + QString prefixPath; + if (!qtCMakeDir.isEmpty()) { + prefixPath = canQtCMakeDir.parentDir().parentDir().parentDir().toString(); // Up 3 levels... + } else { + prefixPath = config.stringValueOf("CMAKE_PREFIX_PATH"); + } + qCDebug(cmInputLog) << "PrefixPath:" << prefixPath; + if (prefixPath.isEmpty()) return FilePath(); - const FilePath baseQtDir = canQtCMakeDir.parentDir().parentDir().parentDir(); // Up 3 levels... - qCDebug(cmInputLog) << "BaseQtDir:" << baseQtDir.toUserOutput(); // Run a CMake project that would do qmake probing TemporaryDirectory qtcQMakeProbeDir("qtc-cmake-qmake-probe-XXXXXXXX"); @@ -204,9 +308,9 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) args.push_back(QStringLiteral("-DCMAKE_MAKE_PROGRAM=%1").arg(cmakeMakeProgram.toString())); } if (toolchainFile.isEmpty()) { - args.push_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(baseQtDir.toString())); + args.push_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(prefixPath)); } else { - args.push_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(baseQtDir.toString())); + args.push_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(prefixPath)); args.push_back(QStringLiteral("-DCMAKE_TOOLCHAIN_FILE=%1").arg(toolchainFile.toString())); } if (!hostPath.isEmpty()) { @@ -279,19 +383,102 @@ static QVector extractToolChainsFromCache(const CMakeConfi QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, QString *warningMessage) const { + QList result; qCInfo(cmInputLog) << "Examining directory:" << importPath.toUserOutput(); + + if (importPath.isChildOf(m_presetsTempDir.path())) { + auto data = std::make_unique(); + + const QString presetName = importPath.fileName(); + + PresetsDetails::ConfigurePreset configurePreset + = Utils::findOrDefault(m_presetsData.configurePresets, + [presetName](const PresetsDetails::ConfigurePreset &preset) { + return preset.name == presetName; + }); + + 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().toString(); + } + + data->cmakeBinary = Utils::FilePath::fromString(configurePreset.cmakeExecutable.value()); + if (configurePreset.generator) + data->generator = configurePreset.generator.value(); + + if (configurePreset.architecture && configurePreset.architecture.value().value) + data->platform = configurePreset.architecture.value().value.value(); + + if (configurePreset.toolset && configurePreset.toolset.value().value) + data->toolset = configurePreset.toolset.value().value.value(); + + QString binaryDir = importPath.toString(); + if (configurePreset.binaryDir) { + binaryDir = configurePreset.binaryDir.value(); + CMakePresets::Macros::expand(configurePreset, + Environment::systemEnvironment(), + projectDirectory(), + binaryDir); + } + + data->buildDirectory = Utils::FilePath::fromString(binaryDir); + + const CMakeConfig cache = configurePreset.cacheVariables + ? configurePreset.cacheVariables.value() + : CMakeConfig(); + data->cmakeBuildType = cache.valueOf("CMAKE_BUILD_TYPE"); + if (data->cmakeBuildType.isEmpty()) + data->cmakeBuildType = "Debug"; + + data->sysroot = cache.filePathValueOf("CMAKE_SYSROOT"); + + CMakeConfig config; + if (cache.valueOf("CMAKE_C_COMPILER").isEmpty() + && cache.valueOf("CMAKE_CXX_COMPILER").isEmpty()) { + QApplication::setOverrideCursor(Qt::WaitCursor); + config = configurationFromPresetProbe(importPath, configurePreset); + QApplication::restoreOverrideCursor(); + } else { + config = cache; + config << CMakeConfigItem("CMAKE_COMMAND", + CMakeConfigItem::PATH, + configurePreset.cmakeExecutable.value().toUtf8()); + if (configurePreset.generator) + config << CMakeConfigItem("CMAKE_GENERATOR", + CMakeConfigItem::STRING, + configurePreset.generator.value().toUtf8()); + } + + const FilePath qmake = qmakeFromCMakeCache(config); + if (!qmake.isEmpty()) + data->qt = findOrCreateQtVersion(qmake); + + // ToolChains: + data->toolChains = extractToolChainsFromCache(config); + + result.push_back(static_cast(data.release())); + return result; + } + const FilePath cacheFile = importPath.pathAppended("CMakeCache.txt"); if (!cacheFile.exists()) { qCDebug(cmInputLog) << cacheFile.toUserOutput() << "does not exist, returning."; - return { }; + 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 { }; + return result; } QByteArrayList buildConfigurationTypes = {config.valueOf("CMAKE_BUILD_TYPE")}; @@ -301,7 +488,6 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, buildConfigurationTypes = buildConfigurationTypesString.split(';'); } - QList result; for (auto const &buildType: qAsConst(buildConfigurationTypes)) { auto data = std::make_unique(); @@ -374,6 +560,12 @@ bool CMakeProjectImporter::matchKit(void *directoryData, const Kit *k) const } } + if (!data->cmakePreset.isEmpty()) { + auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); + if (data->cmakePreset != presetConfigItem.expandedValue(k)) + return false; + } + qCDebug(cmInputLog) << k->displayName() << "matches directoryData for" << data->buildDirectory.toUserOutput(); return true; @@ -395,6 +587,13 @@ Kit *CMakeProjectImporter::createKit(void *directoryData) const CMakeGeneratorKitAspect::setPlatform(k, data->platform); CMakeGeneratorKitAspect::setToolset(k, data->toolset); + if (!data->cmakePresetDisplayname.isEmpty()) { + k->setUnexpandedDisplayName( + QString("%1 (CMake preset)").arg(data->cmakePresetDisplayname)); + + CMakeConfigurationKitAspect::setCMakePreset(k, data->cmakePreset); + } + SysRootKitAspect::setSysRoot(k, data->sysroot); for (const ToolChainDescription &cmtcd : data->toolChains) { diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h index 62e58c853e9..d4cedd6a727 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h @@ -3,6 +3,9 @@ #pragma once +#include "presetsparser.h" +#include "utils/temporarydirectory.h" + #include namespace CMakeProjectManager { @@ -16,7 +19,7 @@ class CMakeProjectImporter : public QtSupport::QtProjectImporter Q_DECLARE_TR_FUNCTIONS(CMakeProjectManager::Internal::CMakeProjectImporter) public: - CMakeProjectImporter(const Utils::FilePath &path); + CMakeProjectImporter(const Utils::FilePath &path, const Internal::PresetsData &presetsData); QStringList importCandidates() final; @@ -37,6 +40,9 @@ private: void cleanupTemporaryCMake(ProjectExplorer::Kit *k, const QVariantList &vl); void persistTemporaryCMake(ProjectExplorer::Kit *k, const QVariantList &vl); + + Internal::PresetsData m_presetsData; + Utils::TemporaryDirectory m_presetsTempDir; }; } // namespace Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 077b700919e..4573a68b715 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -75,6 +75,10 @@ QtcPlugin { "fileapiparser.h", "fileapireader.cpp", "fileapireader.h", + "presetsparser.cpp", + "presetsparser.h", + "presetsmacros.cpp", + "presetsmacros.h", "projecttreehelper.cpp", "projecttreehelper.h" ] diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp new file mode 100644 index 00000000000..c5a140f4601 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp @@ -0,0 +1,131 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "presetsmacros.h" +#include "presetsparser.h" + +#include +#include +#include + +#include + +namespace CMakeProjectManager::Internal::CMakePresets::Macros { + +static void expandAllButEnv(const PresetsDetails::ConfigurePreset &configurePreset, + const Utils::FilePath &sourceDirectory, + QString &value) +{ + value.replace("${dollar}", "$"); + + value.replace("${sourceDir}", sourceDirectory.toString()); + value.replace("${sourceParentDir}", sourceDirectory.parentDir().toString()); + value.replace("${sourceDirName}", sourceDirectory.fileName()); + + value.replace("${presetName}", configurePreset.name); + if (configurePreset.generator) + value.replace("${generator}", configurePreset.generator.value()); +} + +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + Utils::Environment &env, + const Utils::FilePath &sourceDirectory) +{ + const QHash presetEnv = configurePreset.environment + ? configurePreset.environment.value() + : QHash(); + for (auto it = presetEnv.constKeyValueBegin(); it != presetEnv.constKeyValueEnd(); ++it) { + const QString key = it->first; + QString value = it->second; + + expandAllButEnv(configurePreset, sourceDirectory, value); + + QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) { + if (match.captured(2) != key) + value.replace(match.captured(1), presetEnv.value(match.captured(2))); + else + value.replace(match.captured(1), ""); + } + + QString sep; + bool append = true; + if (key.compare("PATH", Qt::CaseInsensitive) == 0) { + sep = Utils::OsSpecificAspects::pathListSeparator(env.osType()); + const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive); + if (index != 0) + append = false; + value.replace("$penv{PATH}", "", Qt::CaseInsensitive); + } + + QRegularExpression penvRegex(R"((\$penv\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : penvRegex.globalMatch(value)) + value.replace(match.captured(1), env.value(match.captured(2))); + + if (append) + env.appendOrSet(key, value, sep); + else + env.prependOrSet(key, value, sep); + } +} + +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + Utils::EnvironmentItems &envItems, + const Utils::FilePath &sourceDirectory) +{ + const QHash presetEnv = configurePreset.environment + ? configurePreset.environment.value() + : QHash(); + + for (auto it = presetEnv.constKeyValueBegin(); it != presetEnv.constKeyValueEnd(); ++it) { + const QString key = it->first; + QString value = it->second; + + expandAllButEnv(configurePreset, sourceDirectory, value); + + QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) { + if (match.captured(2) != key) + value.replace(match.captured(1), presetEnv.value(match.captured(2))); + else + value.replace(match.captured(1), ""); + } + + auto operation = Utils::EnvironmentItem::Operation::SetEnabled; + if (key.compare("PATH", Qt::CaseInsensitive) == 0) { + operation = Utils::EnvironmentItem::Operation::Append; + const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive); + if (index != 0) + operation = Utils::EnvironmentItem::Operation::Prepend; + value.replace("$penv{PATH}", "", Qt::CaseInsensitive); + } + + QRegularExpression penvRegex(R"((\$penv\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : penvRegex.globalMatch(value)) + value.replace(match.captured(1), QString("${%1}").arg(match.captured(2))); + + envItems.emplace_back(Utils::EnvironmentItem(key, value, operation)); + } +} + +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + const Utils::Environment &env, + const Utils::FilePath &sourceDirectory, + QString &value) +{ + expandAllButEnv(configurePreset, sourceDirectory, value); + + const QHash presetEnv = configurePreset.environment + ? configurePreset.environment.value() + : QHash(); + + QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) + value.replace(match.captured(1), presetEnv.value(match.captured(2))); + + QRegularExpression penvRegex(R"((\$penv\{(\w+)\}))"); + for (const QRegularExpressionMatch &match : penvRegex.globalMatch(value)) + value.replace(match.captured(1), env.value(match.captured(2))); +} + +} // namespace CMakeProjectManager::Internal::CMakePresets::Macros diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.h b/src/plugins/cmakeprojectmanager/presetsmacros.h new file mode 100644 index 00000000000..49f738ac784 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/presetsmacros.h @@ -0,0 +1,46 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Utils { +class Environment; +class FilePath; +} + +namespace CMakeProjectManager::Internal { + +namespace PresetsDetails { +class ConfigurePreset; +} + +namespace CMakePresets::Macros { +/** + * Expands the CMakePresets Macros using Utils::Environment as target and source for parent environment values. + * $penv{PATH} is taken from Utils::Environment + */ +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + Utils::Environment &env, + const Utils::FilePath &sourceDirectory); + +/** + * Expands the CMakePresets Macros using Utils::Environment as target + * $penv{PATH} is replaced with Qt Creator macros ${PATH} + */ +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + Utils::EnvironmentItems &envItems, + const Utils::FilePath &sourceDirectory); + +/** + * Expands the CMakePresets macros inside the @value QString parameter. + */ +void expand(const PresetsDetails::ConfigurePreset &configurePreset, + const Utils::Environment &env, + const Utils::FilePath &sourceDirectory, + QString &value); + +} // namespace CMakePresets::Macros + +} // namespace CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp new file mode 100644 index 00000000000..cf760d509e3 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp @@ -0,0 +1,281 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "presetsparser.h" + +#include +#include +#include +#include + +namespace CMakeProjectManager { +namespace Internal { + +bool parseVersion(const QJsonValue &jsonValue, int &version) +{ + if (jsonValue.isNull()) + return false; + + const int invalidVersion = -1; + version = jsonValue.toInt(invalidVersion); + return version != invalidVersion; +} + +bool parseCMakeMinimumRequired(const QJsonValue &jsonValue, QVersionNumber &versionNumber) +{ + if (jsonValue.isNull() || !jsonValue.isObject()) + return false; + + QJsonObject object = jsonValue.toObject(); + versionNumber = QVersionNumber(object.value("major").toInt(), + object.value("minor").toInt(), + object.value("patch").toInt()); + + return true; +} + +bool parseConfigurePresets(const QJsonValue &jsonValue, + std::vector &configurePresets) +{ + // The whole section is optional + if (jsonValue.isNull()) + return true; + + if (!jsonValue.isArray()) + return false; + + const QJsonArray configurePresetsArray = jsonValue.toArray(); + for (const QJsonValue &presetJson : configurePresetsArray) { + if (!presetJson.isObject()) + continue; + + QJsonObject object = presetJson.toObject(); + PresetsDetails::ConfigurePreset preset; + + preset.name = object.value("name").toString(); + preset.hidden = object.value("hidden").toBool(); + + QJsonValue inherits = object.value("inherits"); + if (!inherits.isNull()) { + preset.inherits = QStringList(); + if (inherits.isArray()) { + const QJsonArray inheritsArray = inherits.toArray(); + for (const QJsonValue &inheritsValue : inheritsArray) + preset.inherits.value() << inheritsValue.toString(); + } else { + QString inheritsValue = inherits.toString(); + if (!inheritsValue.isEmpty()) + preset.inherits.value() << inheritsValue; + } + } + if (object.contains("displayName")) + preset.displayName = object.value("displayName").toString(); + if (object.contains("description")) + preset.description = object.value("description").toString(); + if (object.contains("generator")) + preset.generator = object.value("generator").toString(); + if (object.contains("binaryDir")) + preset.binaryDir = object.value("binaryDir").toString(); + if (object.contains("cmakeExecutable")) + preset.cmakeExecutable = object.value("cmakeExecutable").toString(); + + const QJsonObject cacheVariablesObj = object.value("cacheVariables").toObject(); + for (const QString &cacheKey : cacheVariablesObj.keys()) { + if (!preset.cacheVariables) + preset.cacheVariables = CMakeConfig(); + + QJsonValue cacheValue = cacheVariablesObj.value(cacheKey); + if (cacheValue.isObject()) { + QJsonObject cacheVariableObj = cacheValue.toObject(); + CMakeConfigItem item; + item.key = cacheKey.toUtf8(); + item.type = CMakeConfigItem::typeStringToType( + cacheVariableObj.value("type").toString().toUtf8()); + item.value = cacheVariableObj.value("type").toString().toUtf8(); + preset.cacheVariables.value() << item; + + } else { + preset.cacheVariables.value() + << CMakeConfigItem(cacheKey.toUtf8(), cacheValue.toString().toUtf8()); + } + } + + const QJsonObject environmentObj = object.value("environment").toObject(); + for (const QString &envKey : environmentObj.keys()) { + if (!preset.environment) + preset.environment = QHash(); + + QJsonValue envValue = environmentObj.value(envKey); + preset.environment.value().insert(envKey, envValue.toString()); + } + + const QJsonObject warningsObj = object.value("warnings").toObject(); + if (!warningsObj.isEmpty()) { + preset.warnings = PresetsDetails::Warnings(); + + if (warningsObj.contains("dev")) + preset.warnings->dev = warningsObj.value("dev").toBool(); + if (warningsObj.contains("deprecated")) + preset.warnings->deprecated = warningsObj.value("deprecated").toBool(); + if (warningsObj.contains("uninitialized")) + preset.warnings->uninitialized = warningsObj.value("uninitialized").toBool(); + if (warningsObj.contains("unusedCli")) + preset.warnings->unusedCli = warningsObj.value("unusedCli").toBool(); + if (warningsObj.contains("systemVars")) + preset.warnings->systemVars = warningsObj.value("systemVars").toBool(); + } + + const QJsonObject errorsObj = object.value("errors").toObject(); + if (!errorsObj.isEmpty()) { + preset.errors = PresetsDetails::Errors(); + + if (errorsObj.contains("dev")) + preset.errors->dev = errorsObj.value("dev").toBool(); + if (errorsObj.contains("deprecated")) + preset.errors->deprecated = errorsObj.value("deprecated").toBool(); + } + + const QJsonObject debugObj = object.value("debug").toObject(); + if (!debugObj.isEmpty()) { + preset.debug = PresetsDetails::Debug(); + + if (debugObj.contains("output")) + preset.debug->output = debugObj.value("output").toBool(); + if (debugObj.contains("tryCompile")) + preset.debug->tryCompile = debugObj.value("tryCompile").toBool(); + if (debugObj.contains("find")) + preset.debug->find = debugObj.value("find").toBool(); + } + + const QJsonObject architectureObj = object.value("architecture").toObject(); + if (!architectureObj.isEmpty()) { + preset.architecture = PresetsDetails::ValueStrategyPair(); + + if (architectureObj.contains("value")) + preset.architecture->value = architectureObj.value("value").toString(); + if (architectureObj.contains("strategy")) { + const QString strategy = architectureObj.value("strategy").toString(); + if (strategy == "set") + preset.architecture->strategy = PresetsDetails::ValueStrategyPair::Strategy::set; + if (strategy == "external") + preset.architecture->strategy + = PresetsDetails::ValueStrategyPair::Strategy::external; + } + } + + const QJsonObject toolsetObj = object.value("toolset").toObject(); + if (!toolsetObj.isEmpty()) { + preset.toolset = PresetsDetails::ValueStrategyPair(); + + if (toolsetObj.contains("value")) + preset.toolset->value = toolsetObj.value("value").toString(); + if (toolsetObj.contains("strategy")) { + const QString strategy = toolsetObj.value("strategy").toString(); + if (strategy == "set") + preset.toolset->strategy = PresetsDetails::ValueStrategyPair::Strategy::set; + if (strategy == "external") + preset.toolset->strategy = PresetsDetails::ValueStrategyPair::Strategy::external; + } + } + + configurePresets.emplace_back(preset); + } + + return true; +} + +const PresetsData &PresetsParser::presetsData() const +{ + return m_presetsData; +} + +bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage, int &errorLine) +{ + const std::optional jsonContents = jsonFile.fileContents(); + if (!jsonContents) { + errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal", + "Failed to read %1 file") + .arg(jsonFile.fileName()); + return false; + } + + QJsonParseError error; + const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContents.value(), &error); + if (jsonDoc.isNull()) { + errorLine = 1; + for (int i = 0; i < error.offset; ++i) + if (jsonContents.value().at(i) == '\n') + ++errorLine; + errorMessage = error.errorString(); + return false; + } + + if (!jsonDoc.isObject()) { + errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal", + "Invalid %1 file") + .arg(jsonFile.fileName()); + return false; + } + + QJsonObject root = jsonDoc.object(); + + if (!parseVersion(root.value("version"), m_presetsData.version)) { + errorMessage = QCoreApplication::translate("CMakeProjectManager::Internal", + "Invalid \"version\" in %1 file") + .arg(jsonFile.fileName()); + return false; + } + + // optional + parseCMakeMinimumRequired(root.value("cmakeMinimumRequired"), + m_presetsData.cmakeMinimimRequired); + + // optional + if (!parseConfigurePresets(root.value("configurePresets"), m_presetsData.configurePresets)) { + errorMessage = QCoreApplication::translate( + "CMakeProjectManager::Internal", + "Invalid \"configurePresets\" section in %1 file").arg(jsonFile.fileName()); + return false; + } + + return true; +} + +void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other) +{ + if (!vendor && other.vendor) + vendor = other.vendor; + + if (!generator && other.generator) + generator = other.generator; + + if (!architecture && other.architecture) + architecture = other.architecture; + + if (!toolset && other.toolset) + toolset = other.toolset; + + if (!binaryDir && other.binaryDir) + binaryDir = other.binaryDir; + + if (!cmakeExecutable && other.cmakeExecutable) + cmakeExecutable = other.cmakeExecutable; + + if (!cacheVariables && other.cacheVariables) + cacheVariables = other.cacheVariables; + + if (!environment && other.environment) + environment = other.environment; + + if (!warnings && other.warnings) + warnings = other.warnings; + + if (!errors && other.errors) + errors = other.errors; + + if (!debug && other.debug) + debug = other.debug; +} + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/presetsparser.h b/src/plugins/cmakeprojectmanager/presetsparser.h new file mode 100644 index 00000000000..0f4700f382f --- /dev/null +++ b/src/plugins/cmakeprojectmanager/presetsparser.h @@ -0,0 +1,91 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include "cmakeconfigitem.h" + +#include +#include +#include + +namespace CMakeProjectManager { +namespace Internal { + +namespace PresetsDetails { + +class ValueStrategyPair { +public: + std::optional value; + enum class Strategy : bool { set, external }; + std::optional strategy; +}; + +class Warnings { +public: + std::optional dev; + std::optional deprecated; + std::optional uninitialized = false; + std::optional unusedCli = true; + std::optional systemVars = false; +}; + +class Errors { +public: + std::optional dev; + std::optional deprecated; +}; + +class Debug { +public: + std::optional output = false; + std::optional tryCompile = false; + std::optional find = false; +}; + +class ConfigurePreset { +public: + void inheritFrom(const ConfigurePreset &other); + + QString name; + std::optional hidden = false; + std::optional inherits; + std::optional> vendor; + std::optional displayName; + std::optional description; + std::optional generator; + std::optional architecture; + std::optional toolset; + std::optional binaryDir; + std::optional cmakeExecutable; + std::optional cacheVariables; + std::optional> environment; + std::optional warnings; + std::optional errors; + std::optional debug; +}; + +} // namespace PresetsDetails + +class PresetsData +{ +public: + int version = 0; + QVersionNumber cmakeMinimimRequired; + QHash vendor; + std::vector configurePresets; +}; + +class PresetsParser +{ + PresetsData m_presetsData; +public: + bool parse(Utils::FilePath const &jsonFile, QString &errorMessage, int &errorLine); + + const PresetsData &presetsData() const; +}; + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/tests/manual/cmakepresets/CMakeLists.txt b/tests/manual/cmakepresets/CMakeLists.txt new file mode 100644 index 00000000000..612d89d3c47 --- /dev/null +++ b/tests/manual/cmakepresets/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmakepresets VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# QtCreator supports the following variables for Android, which are identical to qmake Android variables. +# Check https://doc.qt.io/qt/deployment-android.html for more information. +# They need to be set before the find_package( ...) calls below. + +#if(ANDROID) +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") +# if (ANDROID_ABI STREQUAL "armeabi-v7a") +# set(ANDROID_EXTRA_LIBS +# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so +# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) +# endif() +#endif() + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(cmakepresets + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +else() + if(ANDROID) + add_library(cmakepresets SHARED + ${PROJECT_SOURCES} + ) + else() + add_executable(cmakepresets WIN32 + ${PROJECT_SOURCES} + ) + endif() +endif() + +set_target_properties(cmakepresets PROPERTIES FOLDER qtc_runnable) + +target_link_libraries(cmakepresets PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +set_target_properties(cmakepresets PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(cmakepresets) +endif() diff --git a/tests/manual/cmakepresets/CMakePresets.json b/tests/manual/cmakepresets/CMakePresets.json new file mode 100644 index 00000000000..74f4c1ba22e --- /dev/null +++ b/tests/manual/cmakepresets/CMakePresets.json @@ -0,0 +1,44 @@ +{ + "version": 1, + "cmakeMinimumRequired": { + "major": 3, + "minor": 19, + "patch": 0 + }, + "configurePresets": [ + { + "name": "mingw", + "displayName": "MinGW 11.2.0", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build-${presetName}-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_PREFIX_PATH": "c:/Qt/6.3.1/mingw_64" + }, + "environment": { + "PATH": "c:/mingw64/bin;$penv{PATH}" + }, + "debug" : { + "find" : true + } + }, + { + "name": "mingw-make", + "displayName": "MinGW 11.2.0 Makefiles", + "generator": "MinGW Makefiles", + "inherits" : "mingw" + }, + { + "name": "visualc", + "displayName": "Visual C++ 2019 x64", + "generator": "Visual Studio 16 2019", + "binaryDir": "${sourceDir}/build-${presetName}", + "architecture" : { + "value": "x64" + }, + "cacheVariables": { + "CMAKE_PREFIX_PATH": "c:/Qt/6.3.1/msvc2019_64" + } + } + ] +} diff --git a/tests/manual/cmakepresets/main.cpp b/tests/manual/cmakepresets/main.cpp new file mode 100644 index 00000000000..07e552ae40b --- /dev/null +++ b/tests/manual/cmakepresets/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/tests/manual/cmakepresets/mainwindow.cpp b/tests/manual/cmakepresets/mainwindow.cpp new file mode 100644 index 00000000000..b629f972a9b --- /dev/null +++ b/tests/manual/cmakepresets/mainwindow.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "mainwindow.h" +#include "./ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + diff --git a/tests/manual/cmakepresets/mainwindow.h b/tests/manual/cmakepresets/mainwindow.h new file mode 100644 index 00000000000..38395521b05 --- /dev/null +++ b/tests/manual/cmakepresets/mainwindow.h @@ -0,0 +1,24 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/tests/manual/cmakepresets/mainwindow.ui b/tests/manual/cmakepresets/mainwindow.ui new file mode 100644 index 00000000000..b232854ba81 --- /dev/null +++ b/tests/manual/cmakepresets/mainwindow.ui @@ -0,0 +1,22 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + +