diff options
author | Brad King <brad.king@kitware.com> | 2020-10-06 14:49:44 (GMT) |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2020-10-06 14:49:59 (GMT) |
commit | 8c4c658ab062f2920448a02fe3a5d42fe3d48876 (patch) | |
tree | 97656d990c21802ec181b9269d63500bb461437c /Source | |
parent | a12da6fb5ece6baf3b4540d8204b8b29dde92538 (diff) | |
parent | 6f8fdc686c0e0710a9c1981c66359a205cc127dd (diff) | |
download | CMake-8c4c658ab062f2920448a02fe3a5d42fe3d48876.zip CMake-8c4c658ab062f2920448a02fe3a5d42fe3d48876.tar.gz CMake-8c4c658ab062f2920448a02fe3a5d42fe3d48876.tar.bz2 |
Merge topic 'cmake-presets'
6f8fdc686c .gitignore: Add CMakeUserPresets.json
1d25760198 Help: Add presets documentation and release notes
a4382f72d7 CMake GUI: Add presets functionality
8617479061 CMake: Add presets functionality
06128cf949 Presets: Add cmCMakePresetsFile class
5a36542086 Refactor: Add allowArch parameter to cmake::CreateGlobalGenerator()
3059e6aed7 cmJSONHelpers: Add new Bind() function
8682d1b7b2 CMake GUI Tests: Increase default sleep time
Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Michael Hirsch, Ph.D. <michael@scivision.dev>
Merge-request: !5169
Diffstat (limited to 'Source')
30 files changed, 2059 insertions, 70 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index d0d9459..1e16e17 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -184,6 +184,8 @@ set(SRCS cmCLocaleEnvironmentScope.cxx cmCMakePath.h cmCMakePath.cxx + cmCMakePresetsFile.cxx + cmCMakePresetsFile.h cmCommandArgumentParserHelper.cxx cmCommonTargetGenerator.cxx cmCommonTargetGenerator.h diff --git a/Source/QtDialog/CMakeLists.txt b/Source/QtDialog/CMakeLists.txt index 5fd0e89..394762a 100644 --- a/Source/QtDialog/CMakeLists.txt +++ b/Source/QtDialog/CMakeLists.txt @@ -92,6 +92,12 @@ set(SRCS QCMake.h QCMakeCacheView.cxx QCMakeCacheView.h + QCMakePreset.cxx + QCMakePreset.h + QCMakePresetComboBox.cxx + QCMakePresetComboBox.h + QCMakePresetItemModel.cxx + QCMakePresetItemModel.h QCMakeWidgets.cxx QCMakeWidgets.h RegexExplorer.cxx @@ -116,6 +122,8 @@ qt5_wrap_cpp(MOC_SRCS FirstConfigure.h QCMake.h QCMakeCacheView.h + QCMakePresetComboBox.h + QCMakePresetItemModel.h QCMakeWidgets.h RegexExplorer.h WarningMessagesDialog.h diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx index 37c1f15..a5b2f34 100644 --- a/Source/QtDialog/CMakeSetup.cxx +++ b/Source/QtDialog/CMakeSetup.cxx @@ -32,7 +32,8 @@ static const char* cmDocumentationUsage[][2] = { " cmake-gui [options]\n" " cmake-gui [options] <path-to-source>\n" " cmake-gui [options] <path-to-existing-build>\n" - " cmake-gui [options] -S <path-to-source> -B <path-to-build>\n" }, + " cmake-gui [options] -S <path-to-source> -B <path-to-build>\n" + " cmake-gui [options] -S <path-to-source> --preset=<preset-name>\n" }, { nullptr, nullptr } }; @@ -147,6 +148,7 @@ int main(int argc, char** argv) QStringList args = QApplication::arguments(); std::string binaryDirectory; std::string sourceDirectory; + std::string presetName; for (int i = 1; i < args.size(); ++i) { const QString& arg = args[i]; if (arg.startsWith("-S")) { @@ -185,11 +187,28 @@ int main(int argc, char** argv) binaryDirectory = cmSystemTools::CollapseFullPath(path.toLocal8Bit().data()); cmSystemTools::ConvertToUnixSlashes(binaryDirectory); + } else if (arg.startsWith("--preset=")) { + QString preset = arg.mid(cmStrLen("--preset=")); + if (preset.isEmpty()) { + std::cerr << "No preset specified for --preset" << std::endl; + return 1; + } + presetName = preset.toLocal8Bit().data(); } } - if (!sourceDirectory.empty() && !binaryDirectory.empty()) { + if (!sourceDirectory.empty() && + (!binaryDirectory.empty() || !presetName.empty())) { dialog.setSourceDirectory(QString::fromLocal8Bit(sourceDirectory.c_str())); - dialog.setBinaryDirectory(QString::fromLocal8Bit(binaryDirectory.c_str())); + if (!binaryDirectory.empty()) { + dialog.setBinaryDirectory( + QString::fromLocal8Bit(binaryDirectory.c_str())); + if (!presetName.empty()) { + dialog.setStartupBinaryDirectory(true); + } + } + if (!presetName.empty()) { + dialog.setDeferredPreset(QString::fromLocal8Bit(presetName.c_str())); + } } else { if (args.count() == 2) { std::string filePath = diff --git a/Source/QtDialog/CMakeSetupDialog.cxx b/Source/QtDialog/CMakeSetupDialog.cxx index df22028..acd32ec 100644 --- a/Source/QtDialog/CMakeSetupDialog.cxx +++ b/Source/QtDialog/CMakeSetupDialog.cxx @@ -21,8 +21,10 @@ #include <QSettings> #include <QShortcut> #include <QStatusBar> +#include <QString> #include <QToolButton> #include <QUrl> +#include <QVector> #ifdef QT_WINEXTRAS # include <QWinTaskbarButton> @@ -263,6 +265,8 @@ void CMakeSetupDialog::initialize() &CMakeSetupDialog::onBinaryDirectoryChanged); QObject::connect(this->SourceDirectory, &QLineEdit::textChanged, this, &CMakeSetupDialog::onSourceDirectoryChanged); + QObject::connect(this->Preset, &QCMakePresetComboBox::presetChanged, this, + &CMakeSetupDialog::onBuildPresetChanged); QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::sourceDirChanged, this, @@ -270,6 +274,13 @@ void CMakeSetupDialog::initialize() QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::binaryDirChanged, this, &CMakeSetupDialog::updateBinaryDirectory); + QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetsChanged, + this, &CMakeSetupDialog::updatePresets); + QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetChanged, + this, &CMakeSetupDialog::updatePreset); + QObject::connect(this->CMakeThread->cmakeInstance(), + &QCMake::presetLoadError, this, + &CMakeSetupDialog::showPresetLoadError); QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::progressChanged, this, @@ -314,9 +325,15 @@ void CMakeSetupDialog::initialize() QObject::connect(this->WarnUninitializedAction, &QAction::triggered, this->CMakeThread->cmakeInstance(), &QCMake::setWarnUninitializedMode); + QObject::connect(this->CMakeThread->cmakeInstance(), + &QCMake::warnUninitializedModeChanged, + this->WarnUninitializedAction, &QAction::setChecked); - if (!this->SourceDirectory->text().isEmpty() || - !this->BinaryDirectory->lineEdit()->text().isEmpty()) { + if (!this->SourceDirectory->text().isEmpty() && + !this->DeferredPreset.isNull()) { + this->onSourceDirectoryChanged(this->SourceDirectory->text()); + } else if (!this->SourceDirectory->text().isEmpty() || + !this->BinaryDirectory->lineEdit()->text().isEmpty()) { this->onSourceDirectoryChanged(this->SourceDirectory->text()); this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text()); } else { @@ -671,6 +688,41 @@ void CMakeSetupDialog::updateBinaryDirectory(const QString& dir) } } +void CMakeSetupDialog::updatePresets(const QVector<QCMakePreset>& presets) +{ + if (this->Preset->presets() != presets) { + this->Preset->blockSignals(true); + this->Preset->setPresets(presets); + this->Preset->blockSignals(false); + } + + this->Preset->setHidden(presets.isEmpty()); + this->PresetLabel->setHidden(presets.isEmpty()); + + if (!this->DeferredPreset.isNull()) { + this->Preset->setPresetName(this->DeferredPreset); + this->DeferredPreset = QString{}; + } +} + +void CMakeSetupDialog::updatePreset(const QString& name) +{ + if (this->Preset->presetName() != name) { + this->Preset->blockSignals(true); + this->Preset->setPresetName(name); + this->Preset->blockSignals(false); + } +} + +void CMakeSetupDialog::showPresetLoadError( + const QString& dir, cmCMakePresetsFile::ReadFileResult result) +{ + QMessageBox::warning( + this, "Error Reading CMake Presets", + QString::fromLocal8Bit("Could not read presets from %1: %2") + .arg(dir, cmCMakePresetsFile::ResultToString(result))); +} + void CMakeSetupDialog::doBinaryBrowse() { QString dir = QFileDialog::getExistingDirectory( @@ -686,6 +738,11 @@ void CMakeSetupDialog::setBinaryDirectory(const QString& dir) this->BinaryDirectory->setEditText(dir); } +void CMakeSetupDialog::setStartupBinaryDirectory(bool startup) +{ + this->StartupBinaryDirectory = startup; +} + void CMakeSetupDialog::onSourceDirectoryChanged(const QString& dir) { this->Output->clear(); @@ -711,11 +768,24 @@ void CMakeSetupDialog::onBinaryDirectoryChanged(const QString& dir) Q_ARG(QString, dir)); } +void CMakeSetupDialog::onBuildPresetChanged(const QString& name) +{ + QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setPreset", + Qt::QueuedConnection, Q_ARG(QString, name), + Q_ARG(bool, !this->StartupBinaryDirectory)); + this->StartupBinaryDirectory = false; +} + void CMakeSetupDialog::setSourceDirectory(const QString& dir) { this->SourceDirectory->setText(dir); } +void CMakeSetupDialog::setDeferredPreset(const QString& preset) +{ + this->DeferredPreset = preset; +} + void CMakeSetupDialog::showProgress(const QString& /*msg*/, float percent) { percent = (percent * ProgressFactor) + ProgressOffset; @@ -753,6 +823,7 @@ void CMakeSetupDialog::setEnabledState(bool enabled) this->CacheValues->cacheModel()->setEditEnabled(enabled); this->SourceDirectory->setEnabled(enabled); this->BrowseSourceDirectoryButton->setEnabled(enabled); + this->Preset->setEnabled(enabled); this->BinaryDirectory->setEnabled(enabled); this->BrowseBinaryDirectoryButton->setEnabled(enabled); this->ReloadCacheAction->setEnabled(enabled); @@ -777,6 +848,17 @@ bool CMakeSetupDialog::setupFirstConfigure() // restore from settings dialog.loadFromSettings(); + auto presetData = this->Preset->currentData(); + if (presetData.isValid()) { + auto preset = presetData.value<QCMakePreset>(); + dialog.setCurrentGenerator(preset.generator); + if (preset.setGenConfig) { + dialog.setPlatform(preset.architecture); + dialog.setToolset(preset.toolset); + } + dialog.setCompilerOption(CompilerOption::DefaultNative); + } + if (dialog.exec() == QDialog::Accepted) { dialog.saveToSettings(); this->CMakeThread->cmakeInstance()->setGenerator(dialog.getGenerator()); diff --git a/Source/QtDialog/CMakeSetupDialog.h b/Source/QtDialog/CMakeSetupDialog.h index d752ef2..f0cc929 100644 --- a/Source/QtDialog/CMakeSetupDialog.h +++ b/Source/QtDialog/CMakeSetupDialog.h @@ -5,12 +5,15 @@ #include <memory> #include "QCMake.h" +#include "QCMakePreset.h" #include <QEventLoop> #include <QMainWindow> #include <QThread> +#include <QVector> #include "ui_CMakeSetupDialog.h" +class QCMakePresetItemModel; class QCMakeThread; class CMakeCacheModel; class QProgressBar; @@ -33,6 +36,8 @@ public: public slots: void setBinaryDirectory(const QString& dir); void setSourceDirectory(const QString& dir); + void setDeferredPreset(const QString& preset); + void setStartupBinaryDirectory(bool startup); protected slots: void initialize(); @@ -52,6 +57,10 @@ protected slots: void doDeleteCache(); void updateSourceDirectory(const QString& dir); void updateBinaryDirectory(const QString& dir); + void updatePresets(const QVector<QCMakePreset>& presets); + void updatePreset(const QString& name); + void showPresetLoadError(const QString& dir, + cmCMakePresetsFile::ReadFileResult result); void showProgress(const QString& msg, float percent); void setEnabledState(bool); bool setupFirstConfigure(); @@ -62,6 +71,7 @@ protected slots: void saveBuildPaths(const QStringList&); void onBinaryDirectoryChanged(const QString& dir); void onSourceDirectoryChanged(const QString& dir); + void onBuildPresetChanged(const QString& name); void setCacheModified(); void removeSelectedCacheEntries(); void selectionChanged(); @@ -113,6 +123,8 @@ protected: QAction* WarnUninitializedAction; QAction* InstallForCommandLineAction; State CurrentState; + QString DeferredPreset; + bool StartupBinaryDirectory = false; QTextCharFormat ErrorFormat; QTextCharFormat MessageFormat; diff --git a/Source/QtDialog/CMakeSetupDialog.ui b/Source/QtDialog/CMakeSetupDialog.ui index 5feee91..afb25eb 100644 --- a/Source/QtDialog/CMakeSetupDialog.ui +++ b/Source/QtDialog/CMakeSetupDialog.ui @@ -44,7 +44,7 @@ <number>6</number> </property> <item row="0" column="0"> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="SourceLabel"> <property name="text"> <string>Where is the source code:</string> </property> @@ -61,13 +61,23 @@ </widget> </item> <item row="1" column="0"> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="PresetLabel"> <property name="text"> - <string>Where to build the binaries:</string> + <string>Preset:</string> </property> </widget> </item> <item row="1" column="1"> + <widget class="QCMakePresetComboBox" name="Preset"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="BinaryLabel"> + <property name="text"> + <string>Where to build the binaries:</string> + </property> + </widget> + </item> + <item row="2" column="1"> <widget class="QComboBox" name="BinaryDirectory"> <property name="sizePolicy"> <sizepolicy hsizetype="Ignored" vsizetype="Fixed"> @@ -83,7 +93,7 @@ </property> </widget> </item> - <item row="1" column="2"> + <item row="2" column="2"> <widget class="QPushButton" name="BrowseBinaryDirectoryButton"> <property name="text"> <string>Browse &Build...</string> @@ -367,6 +377,11 @@ <extends>QTreeView</extends> <header>QCMakeCacheView.h</header> </customwidget> + <customwidget> + <class>QCMakePresetComboBox</class> + <extends>QComboBox</extends> + <header>QCMakePresetComboBox.h</header> + </customwidget> </customwidgets> <resources> <include location="CMakeSetup.qrc"/> diff --git a/Source/QtDialog/FirstConfigure.cxx b/Source/QtDialog/FirstConfigure.cxx index 918f137..10360bb 100644 --- a/Source/QtDialog/FirstConfigure.cxx +++ b/Source/QtDialog/FirstConfigure.cxx @@ -145,6 +145,36 @@ void StartCompilerSetup::setCurrentGenerator(const QString& gen) } } +void StartCompilerSetup::setPlatform(const QString& platform) +{ + this->PlatformOptions->setCurrentText(platform); +} + +void StartCompilerSetup::setToolset(const QString& toolset) +{ + this->Toolset->setText(toolset); +} + +void StartCompilerSetup::setCompilerOption(CompilerOption option) +{ + std::size_t index = 0; + switch (option) { + case CompilerOption::DefaultNative: + index = 0; + break; + case CompilerOption::SpecifyNative: + index = 1; + break; + case CompilerOption::ToolchainFile: + index = 2; + break; + case CompilerOption::Options: + index = 3; + break; + } + this->CompilerSetupOptions[index]->setChecked(true); +} + QString StartCompilerSetup::getGenerator() const { return this->GeneratorOptions->currentText(); @@ -482,6 +512,26 @@ void FirstConfigure::setGenerators( this->mStartCompilerSetupPage->setGenerators(gens); } +void FirstConfigure::setCurrentGenerator(const QString& gen) +{ + this->mStartCompilerSetupPage->setCurrentGenerator(gen); +} + +void FirstConfigure::setPlatform(const QString& platform) +{ + this->mStartCompilerSetupPage->setPlatform(platform); +} + +void FirstConfigure::setToolset(const QString& toolset) +{ + this->mStartCompilerSetupPage->setToolset(toolset); +} + +void FirstConfigure::setCompilerOption(CompilerOption option) +{ + this->mStartCompilerSetupPage->setCompilerOption(option); +} + QString FirstConfigure::getGenerator() const { return this->mStartCompilerSetupPage->getGenerator(); @@ -503,7 +553,7 @@ void FirstConfigure::loadFromSettings() // restore generator settings.beginGroup("Settings/StartPath"); QString lastGen = settings.value("LastGenerator").toString(); - this->mStartCompilerSetupPage->setCurrentGenerator(lastGen); + this->setCurrentGenerator(lastGen); settings.endGroup(); // restore compiler setup @@ -550,7 +600,7 @@ void FirstConfigure::loadFromSettings() // this prevents them from being taken from environment, while the // generator is taken from application settings if (!mDefaultGenerator.isEmpty()) { - this->mStartCompilerSetupPage->setCurrentGenerator(mDefaultGenerator); + this->setCurrentGenerator(mDefaultGenerator); } } diff --git a/Source/QtDialog/FirstConfigure.h b/Source/QtDialog/FirstConfigure.h index ca5f52e..5844f3a 100644 --- a/Source/QtDialog/FirstConfigure.h +++ b/Source/QtDialog/FirstConfigure.h @@ -22,6 +22,14 @@ enum FirstConfigurePages Done }; +enum class CompilerOption +{ + DefaultNative, + SpecifyNative, + ToolchainFile, + Options, +}; + //! the first page that gives basic options for what compilers setup to choose //! from class StartCompilerSetup : public QWizardPage @@ -33,6 +41,9 @@ public: ~StartCompilerSetup(); void setGenerators(std::vector<cmake::GeneratorInfo> const& gens); void setCurrentGenerator(const QString& gen); + void setToolset(const QString& toolset); + void setPlatform(const QString& platform); + void setCompilerOption(CompilerOption option); QString getGenerator() const; QString getToolset() const; QString getPlatform() const; @@ -167,6 +178,10 @@ public: ~FirstConfigure(); void setGenerators(std::vector<cmake::GeneratorInfo> const& gens); + void setCurrentGenerator(const QString& gen); + void setToolset(const QString& toolset); + void setPlatform(const QString& platform); + void setCompilerOption(CompilerOption option); QString getGenerator() const; QString getPlatform() const; QString getToolset() const; diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx index 974c545..9017a63 100644 --- a/Source/QtDialog/QCMake.cxx +++ b/Source/QtDialog/QCMake.cxx @@ -2,10 +2,14 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "QCMake.h" +#include <algorithm> + #include <cm/memory> #include <QCoreApplication> #include <QDir> +#include <QString> +#include <QVector> #include "cmExternalMakefileProjectGenerator.h" #include "cmGlobalGenerator.h" @@ -19,12 +23,15 @@ QCMake::QCMake(QObject* p) : QObject(p) + , StartEnvironment(QProcessEnvironment::systemEnvironment()) , Environment(QProcessEnvironment::systemEnvironment()) { this->WarnUninitializedMode = false; qRegisterMetaType<QCMakeProperty>(); qRegisterMetaType<QCMakePropertyList>(); qRegisterMetaType<QProcessEnvironment>(); + qRegisterMetaType<QVector<QCMakePreset>>(); + qRegisterMetaType<cmCMakePresetsFile::ReadFileResult>(); cmSystemTools::DisableRunCommandOutput(); cmSystemTools::SetRunCommandHideConsole(true); @@ -57,6 +64,17 @@ QCMake::QCMake(QObject* p) for (cmake::GeneratorInfo const& gen : generators) { this->AvailableGenerators.push_back(gen); } + + connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() { + this->loadPresets(); + if (!this->PresetName.isEmpty() && + this->CMakePresetsFile.Presets.find( + std::string(this->PresetName.toLocal8Bit())) == + this->CMakePresetsFile.Presets.end()) { + this->setPreset(QString{}); + } + }); + this->LoadPresetsTimer.start(1000); } QCMake::~QCMake() = default; @@ -73,6 +91,8 @@ void QCMake::setSourceDirectory(const QString& _dir) if (this->SourceDirectory != dir) { this->SourceDirectory = QDir::fromNativeSeparators(dir); emit this->sourceDirChanged(this->SourceDirectory); + this->loadPresets(); + this->setPreset(QString{}); } } @@ -129,6 +149,56 @@ void QCMake::setBinaryDirectory(const QString& _dir) } } +void QCMake::setPreset(const QString& name, bool setBinary) +{ + if (this->PresetName != name) { + this->PresetName = name; + emit this->presetChanged(this->PresetName); + + if (!name.isNull()) { + std::string presetName(name.toLocal8Bit()); + auto const& preset = this->CMakePresetsFile.Presets[presetName]; + auto expandedPreset = this->CMakePresetsFile.ExpandMacros(preset); + if (expandedPreset) { + if (setBinary) { + QString binaryDir = + QString::fromLocal8Bit(expandedPreset->BinaryDir.data()); + this->setBinaryDirectory(binaryDir); + } + if (expandedPreset->WarnDev) { + this->CMakeInstance->SetSuppressDevWarnings( + !*expandedPreset->WarnDev); + } + if (expandedPreset->ErrorDev) { + this->CMakeInstance->SetDevWarningsAsErrors( + *expandedPreset->ErrorDev); + } + if (expandedPreset->WarnDeprecated) { + this->CMakeInstance->SetSuppressDeprecatedWarnings( + !*expandedPreset->WarnDeprecated); + } + if (expandedPreset->ErrorDeprecated) { + this->CMakeInstance->SetDeprecatedWarningsAsErrors( + *expandedPreset->ErrorDeprecated); + } + if (expandedPreset->WarnUninitialized) { + this->WarnUninitializedMode = *expandedPreset->WarnUninitialized; + emit this->warnUninitializedModeChanged( + *expandedPreset->WarnUninitialized); + } + this->Environment = this->StartEnvironment; + for (auto const& v : expandedPreset->Environment) { + if (v.second) { + this->Environment.insert(QString::fromLocal8Bit(v.first.data()), + QString::fromLocal8Bit(v.second->data())); + } + } + } + } + emit this->propertiesChanged(this->properties()); + } +} + void QCMake::setGenerator(const QString& gen) { if (this->Generator != gen) { @@ -348,6 +418,56 @@ QCMakePropertyList QCMake::properties() const ret.append(prop); } + if (!this->PresetName.isNull()) { + std::string presetName(this->PresetName.toLocal8Bit()); + auto p = this->CMakePresetsFile.ExpandMacros( + this->CMakePresetsFile.Presets.at(presetName)); + if (p) { + for (auto const& v : p->CacheVariables) { + if (!v.second) { + continue; + } + QCMakeProperty prop; + prop.Key = QString::fromLocal8Bit(v.first.data()); + prop.Value = QString::fromLocal8Bit(v.second->Value.data()); + prop.Type = QCMakeProperty::STRING; + if (!v.second->Type.empty()) { + auto type = cmState::StringToCacheEntryType(v.second->Type); + switch (type) { + case cmStateEnums::BOOL: + prop.Type = QCMakeProperty::BOOL; + prop.Value = cmIsOn(v.second->Value); + break; + case cmStateEnums::PATH: + prop.Type = QCMakeProperty::PATH; + break; + case cmStateEnums::FILEPATH: + prop.Type = QCMakeProperty::FILEPATH; + break; + default: + prop.Type = QCMakeProperty::STRING; + break; + } + } + + // QCMakeCacheModel prefers variables earlier in the list rather than + // later, so overwrite them if they already exist rather than simply + // appending + bool found = false; + for (auto& orig : ret) { + if (orig.Key == prop.Key) { + orig = prop; + found = true; + break; + } + } + if (!found) { + ret.append(prop); + } + } + } + } + return ret; } @@ -405,6 +525,46 @@ void QCMake::setUpEnvironment() const } } +void QCMake::loadPresets() +{ + auto result = this->CMakePresetsFile.ReadProjectPresets( + this->SourceDirectory.toLocal8Bit().data(), true); + if (result != this->LastLoadPresetsResult && + result != cmCMakePresetsFile::ReadFileResult::READ_OK) { + emit this->presetLoadError(this->SourceDirectory, result); + } + this->LastLoadPresetsResult = result; + + QVector<QCMakePreset> presets; + for (auto const& name : this->CMakePresetsFile.PresetOrder) { + auto const& p = this->CMakePresetsFile.Presets[name]; + if (p.Hidden) { + continue; + } + + QCMakePreset preset; + preset.name = std::move(QString::fromLocal8Bit(p.Name.data())); + preset.displayName = + std::move(QString::fromLocal8Bit(p.DisplayName.data())); + preset.description = + std::move(QString::fromLocal8Bit(p.Description.data())); + preset.generator = std::move(QString::fromLocal8Bit(p.Generator.data())); + preset.architecture = + std::move(QString::fromLocal8Bit(p.Architecture.data())); + preset.toolset = std::move(QString::fromLocal8Bit(p.Toolset.data())); + preset.setGenConfig = !p.GeneratorConfig || + p.GeneratorConfig == cmCMakePresetsFile::CMakeGeneratorConfig::Default; + preset.enabled = std::find_if(this->AvailableGenerators.begin(), + this->AvailableGenerators.end(), + [&p](const cmake::GeneratorInfo& g) { + return g.name == p.Generator; + }) != this->AvailableGenerators.end() && + this->CMakePresetsFile.ExpandMacros(p); + presets.push_back(preset); + } + emit this->presetsChanged(presets); +} + QString QCMake::binaryDirectory() const { return this->BinaryDirectory; diff --git a/Source/QtDialog/QCMake.h b/Source/QtDialog/QCMake.h index f569951..a6751b0 100644 --- a/Source/QtDialog/QCMake.h +++ b/Source/QtDialog/QCMake.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include "cmCMakePresetsFile.h" #include "cmake.h" #ifdef _MSC_VER @@ -14,6 +15,7 @@ #include <memory> #include <vector> +#include "QCMakePreset.h" #include <QAtomicInt> #include <QList> #include <QMetaType> @@ -21,6 +23,7 @@ #include <QProcessEnvironment> #include <QString> #include <QStringList> +#include <QTimer> #include <QVariant> /// struct to represent cmake properties in Qt @@ -57,6 +60,7 @@ using QCMakePropertyList = QList<QCMakeProperty>; Q_DECLARE_METATYPE(QCMakeProperty) Q_DECLARE_METATYPE(QCMakePropertyList) Q_DECLARE_METATYPE(QProcessEnvironment) +Q_DECLARE_METATYPE(cmCMakePresetsFile::ReadFileResult) /// Qt API for CMake library. /// Wrapper like class allows for easier integration with @@ -74,6 +78,8 @@ public slots: void setSourceDirectory(const QString& dir); /// set the binary directory to build in void setBinaryDirectory(const QString& dir); + /// set the preset name to use + void setPreset(const QString& name, bool setBinary = true); /// set the desired generator to use void setGenerator(const QString& generator); /// set the desired generator to use @@ -147,6 +153,15 @@ signals: void sourceDirChanged(const QString& dir); /// signal when the binary directory changes void binaryDirChanged(const QString& dir); + /// signal when the preset list changes + void presetsChanged(const QVector<QCMakePreset>& presets); + /// signal when the selected preset changes + void presetChanged(const QString& name); + /// signal when there's an error reading the presets files + void presetLoadError(const QString& dir, + cmCMakePresetsFile::ReadFileResult error); + /// signal when uninitialized warning changes + void warnUninitializedModeChanged(bool value); /// signal for progress events void progressChanged(const QString& msg, float percent); /// signal when configure is done @@ -178,6 +193,8 @@ protected: void stderrCallback(std::string const& msg); void setUpEnvironment() const; + void loadPresets(); + bool WarnUninitializedMode; QString SourceDirectory; QString BinaryDirectory; @@ -185,7 +202,13 @@ protected: QString Platform; QString Toolset; std::vector<cmake::GeneratorInfo> AvailableGenerators; + cmCMakePresetsFile CMakePresetsFile; + cmCMakePresetsFile::ReadFileResult LastLoadPresetsResult = + cmCMakePresetsFile::ReadFileResult::READ_OK; + QString PresetName; QString CMakeExecutable; QAtomicInt InterruptFlag; + QProcessEnvironment StartEnvironment; QProcessEnvironment Environment; + QTimer LoadPresetsTimer; }; diff --git a/Source/QtDialog/QCMakePreset.cxx b/Source/QtDialog/QCMakePreset.cxx new file mode 100644 index 0000000..b10cf07 --- /dev/null +++ b/Source/QtDialog/QCMakePreset.cxx @@ -0,0 +1,50 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "QCMakePreset.h" + +bool operator==(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return lhs.name == rhs.name && lhs.displayName == rhs.displayName && + lhs.description == rhs.description && lhs.generator == rhs.generator && + lhs.architecture == rhs.architecture && lhs.toolset == rhs.toolset && + lhs.setGenConfig == rhs.setGenConfig && lhs.enabled == rhs.enabled; +} + +bool operator!=(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return !(lhs == rhs); +} + +bool operator<(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return lhs.name < rhs.name || + (lhs.name == rhs.name && + (lhs.displayName < rhs.displayName || + (lhs.displayName == rhs.displayName && + (lhs.description < rhs.description || + (lhs.description == rhs.description && + (lhs.generator < rhs.generator || + (lhs.generator == rhs.generator && + (lhs.architecture < rhs.architecture || + (lhs.architecture == rhs.architecture && + (lhs.toolset < rhs.toolset || + (lhs.toolset == rhs.toolset && + (lhs.setGenConfig < rhs.setGenConfig || + (lhs.setGenConfig == rhs.setGenConfig && + (lhs.enabled < rhs.enabled)))))))))))))); +} + +bool operator<=(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return rhs >= lhs; +} + +bool operator>(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return rhs < lhs; +} + +bool operator>=(const QCMakePreset& lhs, const QCMakePreset& rhs) +{ + return !(lhs < rhs); +} diff --git a/Source/QtDialog/QCMakePreset.h b/Source/QtDialog/QCMakePreset.h new file mode 100644 index 0000000..93d70d8 --- /dev/null +++ b/Source/QtDialog/QCMakePreset.h @@ -0,0 +1,30 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <QString> +#include <QVariant> + +#include "cmCMakePresetsFile.h" + +class QCMakePreset +{ +public: + QString name; + QString displayName; + QString description; + QString generator; + QString architecture; + QString toolset; + bool setGenConfig; + bool enabled; +}; + +bool operator==(const QCMakePreset& lhs, const QCMakePreset& rhs); +bool operator!=(const QCMakePreset& lhs, const QCMakePreset& rhs); +bool operator<(const QCMakePreset& lhs, const QCMakePreset& rhs); +bool operator<=(const QCMakePreset& lhs, const QCMakePreset& rhs); +bool operator>(const QCMakePreset& lhs, const QCMakePreset& rhs); +bool operator>=(const QCMakePreset& lhs, const QCMakePreset& rhs); + +Q_DECLARE_METATYPE(QCMakePreset) diff --git a/Source/QtDialog/QCMakePresetComboBox.cxx b/Source/QtDialog/QCMakePresetComboBox.cxx new file mode 100644 index 0000000..efadb73 --- /dev/null +++ b/Source/QtDialog/QCMakePresetComboBox.cxx @@ -0,0 +1,64 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "QCMakePresetComboBox.h" + +#include "QCMakePresetItemModel.h" + +QCMakePresetComboBox::QCMakePresetComboBox(QWidget* parent) + : QComboBox(parent) +{ + this->m_model = new QCMakePresetItemModel(this); + this->setModel(this->m_model); + + QObject::connect(this->m_model, &QCMakePresetItemModel::modelAboutToBeReset, + this, [this]() { this->m_resetting = true; }); + QObject::connect(this->m_model, &QCMakePresetItemModel::modelReset, this, + [this]() { + this->setPresetName(this->m_lastPreset); + this->m_resetting = false; + this->emitPresetChanged(); + }); + QObject::connect( + this, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), + this, [this](int /*row*/) { + if (!this->m_resetting) { + this->emitPresetChanged(); + } + }); +} + +const QVector<QCMakePreset>& QCMakePresetComboBox::presets() const +{ + return this->m_model->presets(); +} + +QString QCMakePresetComboBox::presetName() const +{ + auto preset = this->currentData(); + if (preset.canConvert<QCMakePreset>()) { + return preset.value<QCMakePreset>().name; + } + return QString{}; +} + +void QCMakePresetComboBox::setPresets(const QVector<QCMakePreset>& presets) +{ + this->m_model->setPresets(presets); +} + +void QCMakePresetComboBox::setPresetName(const QString& name) +{ + this->setCurrentIndex(this->m_model->presetNameToRow(name)); + if (this->signalsBlocked()) { + this->m_lastPreset = this->presetName(); + } +} + +void QCMakePresetComboBox::emitPresetChanged() +{ + if (this->presetName() != this->m_lastPreset) { + emit this->presetChanged(this->presetName()); + this->m_lastPreset = this->presetName(); + } +} diff --git a/Source/QtDialog/QCMakePresetComboBox.h b/Source/QtDialog/QCMakePresetComboBox.h new file mode 100644 index 0000000..d1eeffe --- /dev/null +++ b/Source/QtDialog/QCMakePresetComboBox.h @@ -0,0 +1,35 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "QCMakePreset.h" +#include <QComboBox> +#include <QObject> +#include <QString> +#include <QVector> + +class QCMakePresetItemModel; + +class QCMakePresetComboBox : public QComboBox +{ + Q_OBJECT +public: + QCMakePresetComboBox(QWidget* parent = nullptr); + + const QVector<QCMakePreset>& presets() const; + QString presetName() const; + +public slots: + void setPresets(const QVector<QCMakePreset>& presets); + void setPresetName(const QString& name); + +signals: + void presetChanged(const QString& name); + +private: + QCMakePresetItemModel* m_model; + bool m_resetting = false; + QString m_lastPreset; + + void emitPresetChanged(); +}; diff --git a/Source/QtDialog/QCMakePresetItemModel.cxx b/Source/QtDialog/QCMakePresetItemModel.cxx new file mode 100644 index 0000000..00a4e18 --- /dev/null +++ b/Source/QtDialog/QCMakePresetItemModel.cxx @@ -0,0 +1,143 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "QCMakePresetItemModel.h" + +#include <QFont> + +QCMakePresetItemModel::QCMakePresetItemModel(QObject* parent) + : QAbstractItemModel(parent) +{ +} + +QVariant QCMakePresetItemModel::data(const QModelIndex& index, int role) const +{ + switch (role) { + case Qt::AccessibleDescriptionRole: + // Separators have to return "separator" for the + // AccessibleDescriptionRole. This was determined by looking at + // QComboBoxDelegate::isSeparator() (located in qcombobox_p.h.) + if (index.internalId() == SEPARATOR_INDEX) { + return QString::fromLocal8Bit("separator"); + } + return QString{}; + case Qt::DisplayRole: { + if (index.internalId() == CUSTOM_INDEX) { + return QString::fromLocal8Bit("<custom>"); + } + if (index.internalId() == SEPARATOR_INDEX) { + return QVariant{}; + } + auto const& preset = this->m_presets[index.internalId()]; + return preset.displayName.isEmpty() ? preset.name : preset.displayName; + } + case Qt::ToolTipRole: + if (index.internalId() == CUSTOM_INDEX) { + return QString::fromLocal8Bit("Specify all settings manually"); + } + if (index.internalId() == SEPARATOR_INDEX) { + return QVariant{}; + } + return this->m_presets[index.internalId()].description; + case Qt::UserRole: + if (index.internalId() == CUSTOM_INDEX) { + return QVariant{}; + } + if (index.internalId() == SEPARATOR_INDEX) { + return QVariant{}; + } + return QVariant::fromValue(this->m_presets[index.internalId()]); + case Qt::FontRole: + if (index.internalId() == CUSTOM_INDEX) { + QFont font; + font.setItalic(true); + return font; + } + return QFont{}; + default: + return QVariant{}; + } +} + +Qt::ItemFlags QCMakePresetItemModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = + Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + if (index.internalId() != SEPARATOR_INDEX && + (index.internalId() == CUSTOM_INDEX || + this->m_presets[index.internalId()].enabled)) { + flags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + return flags; +} + +int QCMakePresetItemModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + if (this->m_presets.empty()) { + return 1; + } + return this->m_presets.size() + 2; +} + +int QCMakePresetItemModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + return 1; +} + +QModelIndex QCMakePresetItemModel::index(int row, int column, + const QModelIndex& parent) const +{ + if (parent.isValid() || column != 0 || row < 0 || + row >= this->rowCount(QModelIndex{})) { + return QModelIndex{}; + } + + if (this->m_presets.empty() || row == this->m_presets.size() + 1) { + return this->createIndex(row, column, CUSTOM_INDEX); + } + + if (row == this->m_presets.size()) { + return this->createIndex(row, column, SEPARATOR_INDEX); + } + + return this->createIndex(row, column, static_cast<quintptr>(row)); +} + +QModelIndex QCMakePresetItemModel::parent(const QModelIndex& /*index*/) const +{ + return QModelIndex{}; +} + +QVector<QCMakePreset> const& QCMakePresetItemModel::presets() const +{ + return this->m_presets; +} + +void QCMakePresetItemModel::setPresets(QVector<QCMakePreset> const& presets) +{ + this->beginResetModel(); + this->m_presets = presets; + this->endResetModel(); +} + +int QCMakePresetItemModel::presetNameToRow(const QString& name) const +{ + if (this->m_presets.empty()) { + return 0; + } + + int index = 0; + for (auto const& preset : this->m_presets) { + if (preset.name == name) { + return index; + } + index++; + } + + return this->m_presets.size() + 1; +} diff --git a/Source/QtDialog/QCMakePresetItemModel.h b/Source/QtDialog/QCMakePresetItemModel.h new file mode 100644 index 0000000..79fba29 --- /dev/null +++ b/Source/QtDialog/QCMakePresetItemModel.h @@ -0,0 +1,45 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <cm/optional> + +#include "QCMakePreset.h" +#include <QAbstractItemModel> +#include <QModelIndex> +#include <QString> +#include <QVariant> +#include <QVector> +#include <QtGlobal> + +class QObject; + +class QCMakePresetItemModel : public QAbstractItemModel +{ + Q_OBJECT +public: + QCMakePresetItemModel(QObject* parent = nullptr); + + QVariant data(const QModelIndex& index, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + int rowCount(const QModelIndex& parent = QModelIndex{}) const override; + int columnCount(const QModelIndex& parent = QModelIndex{}) const override; + + QModelIndex index(int row, int column, + const QModelIndex& parent = QModelIndex{}) const override; + QModelIndex parent(const QModelIndex& index) const override; + + QVector<QCMakePreset> const& presets() const; + + int presetNameToRow(const QString& name) const; + +public slots: + void setPresets(QVector<QCMakePreset> const& presets); + +private: + QVector<QCMakePreset> m_presets; + + static constexpr quintptr SEPARATOR_INDEX = static_cast<quintptr>(-2); + static constexpr quintptr CUSTOM_INDEX = static_cast<quintptr>(-1); +}; diff --git a/Source/cmCMakePresetsFile.cxx b/Source/cmCMakePresetsFile.cxx new file mode 100644 index 0000000..25997fd --- /dev/null +++ b/Source/cmCMakePresetsFile.cxx @@ -0,0 +1,763 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCMakePresetsFile.h" + +#include <cstdlib> +#include <functional> +#include <utility> + +#include <cmext/string_view> + +#include <cm3p/json/reader.h> +#include <cm3p/json/value.h> + +#include "cmsys/FStream.hxx" + +#include "cmJSONHelpers.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmVersion.h" + +namespace { +enum class CycleStatus +{ + Unvisited, + InProgress, + Verified, +}; + +using ReadFileResult = cmCMakePresetsFile::ReadFileResult; +using CacheVariable = cmCMakePresetsFile::CacheVariable; +using UnexpandedPreset = cmCMakePresetsFile::UnexpandedPreset; +using CMakeGeneratorConfig = cmCMakePresetsFile::CMakeGeneratorConfig; + +constexpr int MIN_VERSION = 1; +constexpr int MAX_VERSION = 1; + +struct CMakeVersion +{ + unsigned int Major = 0; + unsigned int Minor = 0; + unsigned int Patch = 0; +}; + +struct RootPresets +{ + CMakeVersion CMakeMinimumRequired; + std::vector<cmCMakePresetsFile::UnexpandedPreset> Presets; +}; + +cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error) +{ + return [error](std::nullptr_t& /*out*/, + const Json::Value* value) -> ReadFileResult { + if (!value) { + return ReadFileResult::READ_OK; + } + + if (!value->isObject()) { + return error; + } + + return ReadFileResult::READ_OK; + }; +} + +auto const VersionIntHelper = cmJSONIntHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION); + +auto const VersionHelper = cmJSONRequiredHelper<int, ReadFileResult>( + ReadFileResult::NO_VERSION, VersionIntHelper); + +auto const RootVersionHelper = + cmJSONObjectHelper<int, ReadFileResult>(ReadFileResult::READ_OK, + ReadFileResult::INVALID_ROOT) + .Bind("version"_s, VersionHelper, false); + +auto const VariableStringHelper = cmJSONStringHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE); + +auto const VariableObjectHelper = + cmJSONObjectHelper<CacheVariable, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false) + .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false) + .Bind("value"_s, &CacheVariable::Value, VariableStringHelper); + +ReadFileResult VariableHelper(cm::optional<CacheVariable>& out, + const Json::Value* value) +{ + if (value->isString()) { + out = CacheVariable{ + /*Type=*/"", + /*Value=*/value->asString(), + }; + return ReadFileResult::READ_OK; + } + if (value->isObject()) { + out.emplace(); + return VariableObjectHelper(*out, value); + } + if (value->isNull()) { + out = cm::nullopt; + return ReadFileResult::READ_OK; + } + return ReadFileResult::INVALID_VARIABLE; +} + +auto const VariablesHelper = + cmJSONMapHelper<cm::optional<CacheVariable>, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper); + +auto const PresetStringHelper = cmJSONStringHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); + +ReadFileResult EnvironmentHelper(cm::optional<std::string>& out, + const Json::Value* value) +{ + if (!value || value->isNull()) { + out = cm::nullopt; + return ReadFileResult::READ_OK; + } + if (value->isString()) { + out = value->asString(); + return ReadFileResult::READ_OK; + } + return ReadFileResult::INVALID_PRESET; +} + +auto const EnvironmentMapHelper = + cmJSONMapHelper<cm::optional<std::string>, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, + EnvironmentHelper); + +auto const PresetVectorStringHelper = + cmJSONVectorHelper<std::string, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, + PresetStringHelper); + +ReadFileResult PresetInheritsHelper(std::vector<std::string>& out, + const Json::Value* value) +{ + out.clear(); + if (!value) { + return ReadFileResult::READ_OK; + } + + if (value->isString()) { + out.push_back(value->asString()); + return ReadFileResult::READ_OK; + } + + return PresetVectorStringHelper(out, value); +} + +auto const PresetBoolHelper = cmJSONBoolHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); + +auto const PresetOptionalBoolHelper = + cmJSONOptionalHelper<bool, ReadFileResult>(ReadFileResult::READ_OK, + PresetBoolHelper); + +auto const PresetWarningsHelper = + cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("dev"_s, &UnexpandedPreset::WarnDev, PresetOptionalBoolHelper, false) + .Bind("deprecated"_s, &UnexpandedPreset::WarnDeprecated, + PresetOptionalBoolHelper, false) + .Bind("uninitialized"_s, &UnexpandedPreset::WarnUninitialized, + PresetOptionalBoolHelper, false) + .Bind("unusedCli"_s, &UnexpandedPreset::WarnUnusedCli, + PresetOptionalBoolHelper, false) + .Bind("systemVars"_s, &UnexpandedPreset::WarnSystemVars, + PresetOptionalBoolHelper, false); + +auto const PresetErrorsHelper = + cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("dev"_s, &UnexpandedPreset::ErrorDev, PresetOptionalBoolHelper, + false) + .Bind("deprecated"_s, &UnexpandedPreset::ErrorDeprecated, + PresetOptionalBoolHelper, false); + +auto const PresetDebugHelper = + cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("output"_s, &UnexpandedPreset::DebugOutput, PresetOptionalBoolHelper, + false) + .Bind("tryCompile"_s, &UnexpandedPreset::DebugTryCompile, + PresetOptionalBoolHelper, false) + .Bind("find"_s, &UnexpandedPreset::DebugFind, PresetOptionalBoolHelper, + false); + +ReadFileResult CMakeGeneratorConfigHelper( + cm::optional<CMakeGeneratorConfig>& out, const Json::Value* value) +{ + if (!value) { + out = cm::nullopt; + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "default") { + out = CMakeGeneratorConfig::Default; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "ignore") { + out = CMakeGeneratorConfig::Ignore; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const PresetHelper = + cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("name"_s, &UnexpandedPreset::Name, PresetStringHelper) + .Bind("inherits"_s, &UnexpandedPreset::Inherits, PresetInheritsHelper, + false) + .Bind("hidden"_s, &UnexpandedPreset::Hidden, PresetBoolHelper, false) + .Bind<std::nullptr_t>("vendor"_s, nullptr, + VendorHelper(ReadFileResult::INVALID_PRESET), false) + .Bind("displayName"_s, &UnexpandedPreset::DisplayName, PresetStringHelper, + false) + .Bind("description"_s, &UnexpandedPreset::Description, PresetStringHelper, + false) + .Bind("generator"_s, &UnexpandedPreset::Generator, PresetStringHelper, + false) + .Bind("architecture"_s, &UnexpandedPreset::Architecture, + PresetStringHelper, false) + .Bind("toolset"_s, &UnexpandedPreset::Toolset, PresetStringHelper, false) + .Bind("cmakeGeneratorConfig"_s, &UnexpandedPreset::GeneratorConfig, + CMakeGeneratorConfigHelper, false) + .Bind("binaryDir"_s, &UnexpandedPreset::BinaryDir, PresetStringHelper, + false) + .Bind<std::string>("cmakeExecutable"_s, nullptr, PresetStringHelper, false) + .Bind("cacheVariables"_s, &UnexpandedPreset::CacheVariables, + VariablesHelper, false) + .Bind("environment"_s, &UnexpandedPreset::Environment, + EnvironmentMapHelper, false) + .Bind("warnings"_s, PresetWarningsHelper, false) + .Bind("errors"_s, PresetErrorsHelper, false) + .Bind("debug"_s, PresetDebugHelper, false); + +auto const PresetsHelper = + cmJSONVectorHelper<UnexpandedPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, PresetHelper); + +auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION); + +auto const CMakeVersionHelper = + cmJSONObjectHelper<CMakeVersion, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false) + .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false) + .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false) + .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false); + +auto const RootPresetsHelper = + cmJSONObjectHelper<RootPresets, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false) + .Bind<int>("version"_s, nullptr, VersionHelper) + .Bind("configurePresets"_s, &RootPresets::Presets, PresetsHelper, false) + .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired, + CMakeVersionHelper, false) + .Bind<std::nullptr_t>("vendor"_s, nullptr, + VendorHelper(ReadFileResult::INVALID_ROOT), false); + +void InheritString(std::string& child, const std::string& parent) +{ + if (child.empty()) { + child = parent; + } +} + +void InheritOptionalBool(cm::optional<bool>& child, + const cm::optional<bool>& parent) +{ + if (!child) { + child = parent; + } +} + +/** + * Check preset inheritance for cycles (using a DAG check algorithm) while + * also bubbling up fields through the inheritance hierarchy, then verify + * that each preset has the required fields, either directly or through + * inheritance. + */ +ReadFileResult VisitPreset(std::map<std::string, UnexpandedPreset>& presets, + UnexpandedPreset& preset, + std::map<std::string, CycleStatus> cycleStatus) +{ + switch (cycleStatus[preset.Name]) { + case CycleStatus::InProgress: + return ReadFileResult::CYCLIC_PRESET_INHERITANCE; + case CycleStatus::Verified: + return ReadFileResult::READ_OK; + default: + break; + } + + cycleStatus[preset.Name] = CycleStatus::InProgress; + + for (auto const& i : preset.Inherits) { + auto parent = presets.find(i); + if (parent == presets.end()) { + return ReadFileResult::INVALID_PRESET; + } + + if (!preset.User && parent->second.User) { + return ReadFileResult::USER_PRESET_INHERITANCE; + } + + auto result = VisitPreset(presets, parent->second, cycleStatus); + if (result != ReadFileResult::READ_OK) { + return result; + } + + InheritString(preset.Generator, parent->second.Generator); + InheritString(preset.Architecture, parent->second.Architecture); + InheritString(preset.Toolset, parent->second.Toolset); + if (!preset.GeneratorConfig) { + preset.GeneratorConfig = parent->second.GeneratorConfig; + } + InheritString(preset.BinaryDir, parent->second.BinaryDir); + InheritOptionalBool(preset.WarnDev, parent->second.WarnDev); + InheritOptionalBool(preset.ErrorDev, parent->second.ErrorDev); + InheritOptionalBool(preset.WarnDeprecated, parent->second.WarnDeprecated); + InheritOptionalBool(preset.ErrorDeprecated, + parent->second.ErrorDeprecated); + InheritOptionalBool(preset.WarnUninitialized, + parent->second.WarnUninitialized); + InheritOptionalBool(preset.WarnUnusedCli, parent->second.WarnUnusedCli); + InheritOptionalBool(preset.WarnSystemVars, parent->second.WarnSystemVars); + for (auto const& v : parent->second.CacheVariables) { + preset.CacheVariables.insert(v); + } + for (auto const& v : parent->second.Environment) { + preset.Environment.insert(v); + } + } + + if (!preset.Hidden) { + if (preset.Generator.empty()) { + return ReadFileResult::INVALID_PRESET; + } + if (preset.BinaryDir.empty()) { + return ReadFileResult::INVALID_PRESET; + } + if (preset.WarnDev == false && preset.ErrorDev == true) { + return ReadFileResult::INVALID_PRESET; + } + if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) { + return ReadFileResult::INVALID_PRESET; + } + } + + cycleStatus[preset.Name] = CycleStatus::Verified; + return ReadFileResult::READ_OK; +} + +ReadFileResult ComputePresetInheritance( + std::map<std::string, UnexpandedPreset>& presets) +{ + std::map<std::string, CycleStatus> cycleStatus; + for (auto const& it : presets) { + cycleStatus[it.first] = CycleStatus::Unvisited; + } + + for (auto& it : presets) { + auto result = VisitPreset(presets, it.second, cycleStatus); + if (result != ReadFileResult::READ_OK) { + return result; + } + } + + return ReadFileResult::READ_OK; +} + +constexpr const char* ValidPrefixes[] = { + "", + "env", + "penv", + "vendor", +}; + +bool PrefixesValidMacroNamespace(const std::string& str) +{ + for (auto const& prefix : ValidPrefixes) { + if (cmHasPrefix(prefix, str)) { + return true; + } + } + return false; +} + +bool IsValidMacroNamespace(const std::string& str) +{ + for (auto const& prefix : ValidPrefixes) { + if (str == prefix) { + return true; + } + } + return false; +} + +bool VisitEnv(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& value, CycleStatus& status); +bool ExpandMacros(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& out); +bool ExpandMacro(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& out, const std::string& macroNamespace, + const std::string& macroName); + +bool VisitEnv(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& value, CycleStatus& status) +{ + if (status == CycleStatus::Verified) { + return true; + } + if (status == CycleStatus::InProgress) { + return false; + } + + status = CycleStatus::InProgress; + if (!ExpandMacros(file, preset, envCycles, value)) { + return false; + } + status = CycleStatus::Verified; + return true; +} + +bool ExpandMacros(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& out) +{ + std::string result; + std::string macroNamespace; + std::string macroName; + + enum class State + { + Default, + MacroNamespace, + MacroName, + } state = State::Default; + + for (auto c : out) { + switch (state) { + case State::Default: + if (c == '$') { + state = State::MacroNamespace; + } else { + result += c; + } + break; + + case State::MacroNamespace: + if (c == '{') { + if (IsValidMacroNamespace(macroNamespace)) { + state = State::MacroName; + } else { + result += '$'; + result += macroNamespace; + result += '{'; + macroNamespace.clear(); + state = State::Default; + } + } else { + macroNamespace += c; + if (!PrefixesValidMacroNamespace(macroNamespace)) { + result += '$'; + result += macroNamespace; + macroNamespace.clear(); + state = State::Default; + } + } + break; + + case State::MacroName: + if (c == '}') { + if (!ExpandMacro(file, preset, envCycles, result, macroNamespace, + macroName)) { + return false; + } + macroNamespace.clear(); + macroName.clear(); + state = State::Default; + } else { + macroName += c; + } + break; + } + } + + switch (state) { + case State::Default: + break; + case State::MacroNamespace: + result += '$'; + result += macroNamespace; + break; + case State::MacroName: + return false; + } + + out = std::move(result); + return true; +} + +bool ExpandMacro(const cmCMakePresetsFile& file, + cmCMakePresetsFile::ExpandedPreset& preset, + std::map<std::string, CycleStatus>& envCycles, + std::string& out, const std::string& macroNamespace, + const std::string& macroName) +{ + if (macroNamespace.empty()) { + if (macroName == "sourceDir") { + out += file.SourceDir; + return true; + } + if (macroName == "sourceParentDir") { + out += cmSystemTools::GetParentDirectory(file.SourceDir); + return true; + } + if (macroName == "presetName") { + out += preset.Name; + return true; + } + if (macroName == "generator") { + out += preset.Generator; + return true; + } + if (macroName == "dollar") { + out += '$'; + return true; + } + } + + if (macroNamespace == "env") { + auto v = preset.Environment.find(macroName); + if (v != preset.Environment.end() && v->second) { + if (!VisitEnv(file, preset, envCycles, *v->second, + envCycles[macroName])) { + return false; + } + out += *v->second; + return true; + } + } + + if (macroNamespace == "env" || macroNamespace == "penv") { + const char* value = std::getenv(macroName.c_str()); + if (value) { + out += value; + } + return true; + } + + // "vendor" falls through to here + return false; +} +} + +std::string cmCMakePresetsFile::GetFilename(const std::string& sourceDir) +{ + return cmStrCat(sourceDir, "/CMakePresets.json"); +} + +std::string cmCMakePresetsFile::GetUserFilename(const std::string& sourceDir) +{ + return cmStrCat(sourceDir, "/CMakeUserPresets.json"); +} + +cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets( + const std::string& sourceDir, bool allowNoFiles) +{ + bool haveOneFile = false; + this->SourceDir = sourceDir; + this->Presets.clear(); + this->PresetOrder.clear(); + + std::vector<std::string> presetOrder; + std::map<std::string, UnexpandedPreset> presetMap; + + std::string filename = GetUserFilename(this->SourceDir); + if (cmSystemTools::FileExists(filename)) { + auto result = this->ReadJSONFile(filename, presetOrder, presetMap, true); + if (result != ReadFileResult::READ_OK) { + return result; + } + haveOneFile = true; + } + + filename = GetFilename(this->SourceDir); + if (cmSystemTools::FileExists(filename)) { + auto result = this->ReadJSONFile(filename, presetOrder, presetMap, false); + if (result != ReadFileResult::READ_OK) { + return result; + } + haveOneFile = true; + } + + if (!haveOneFile) { + return allowNoFiles ? ReadFileResult::READ_OK + : ReadFileResult::FILE_NOT_FOUND; + } + + auto result = ComputePresetInheritance(presetMap); + if (result != ReadFileResult::READ_OK) { + return result; + } + + this->PresetOrder = std::move(presetOrder); + this->Presets = std::move(presetMap); + return ReadFileResult::READ_OK; +} + +const char* cmCMakePresetsFile::ResultToString(ReadFileResult result) +{ + switch (result) { + case ReadFileResult::READ_OK: + return "OK"; + case ReadFileResult::FILE_NOT_FOUND: + return "File not found"; + case ReadFileResult::JSON_PARSE_ERROR: + return "JSON parse error"; + case ReadFileResult::INVALID_ROOT: + return "Invalid root object"; + case ReadFileResult::NO_VERSION: + return "No \"version\" field"; + case ReadFileResult::INVALID_VERSION: + return "Invalid \"version\" field"; + case ReadFileResult::UNRECOGNIZED_VERSION: + return "Unrecognized \"version\" field"; + case ReadFileResult::INVALID_CMAKE_VERSION: + return "Invalid \"cmakeMinimumRequired\" field"; + case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION: + return "\"cmakeMinimumRequired\" version too new"; + case ReadFileResult::INVALID_PRESETS: + return "Invalid \"configurePresets\" field"; + case ReadFileResult::INVALID_PRESET: + return "Invalid preset"; + case ReadFileResult::INVALID_VARIABLE: + return "Invalid CMake variable definition"; + case ReadFileResult::DUPLICATE_PRESETS: + return "Duplicate presets"; + case ReadFileResult::CYCLIC_PRESET_INHERITANCE: + return "Cyclic preset inheritance"; + case ReadFileResult::USER_PRESET_INHERITANCE: + return "Project preset inherits from user preset"; + default: + return "Unknown error"; + } +} + +cm::optional<cmCMakePresetsFile::ExpandedPreset> +cmCMakePresetsFile::ExpandMacros(const UnexpandedPreset& preset) const +{ + ExpandedPreset retval{ preset }; + + std::map<std::string, CycleStatus> envCycles; + for (auto const& v : retval.Environment) { + envCycles[v.first] = CycleStatus::Unvisited; + } + + for (auto& v : retval.Environment) { + if (v.second && + !VisitEnv(*this, retval, envCycles, *v.second, envCycles[v.first])) { + return cm::nullopt; + } + } + + std::string binaryDir = preset.BinaryDir; + if (!::ExpandMacros(*this, retval, envCycles, binaryDir)) { + return cm::nullopt; + } + if (!cmSystemTools::FileIsFullPath(binaryDir)) { + binaryDir = cmStrCat(this->SourceDir, '/', binaryDir); + } + retval.BinaryDir = cmSystemTools::CollapseFullPath(binaryDir); + cmSystemTools::ConvertToUnixSlashes(retval.BinaryDir); + + for (auto& variable : retval.CacheVariables) { + if (variable.second && + !::ExpandMacros(*this, retval, envCycles, variable.second->Value)) { + return cm::nullopt; + } + } + + return cm::make_optional(retval); +} + +cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile( + const std::string& filename, std::vector<std::string>& presetOrder, + std::map<std::string, UnexpandedPreset>& presetMap, bool user) +{ + cmsys::ifstream fin(filename.c_str()); + if (!fin) { + return ReadFileResult::FILE_NOT_FOUND; + } + // If there's a BOM, toss it. + cmsys::FStream::ReadBOM(fin); + + Json::Value root; + Json::CharReaderBuilder builder; + if (!Json::parseFromStream(builder, fin, &root, nullptr)) { + return ReadFileResult::JSON_PARSE_ERROR; + } + + int v = 0; + auto result = RootVersionHelper(v, &root); + if (result != ReadFileResult::READ_OK) { + return result; + } + if (v < MIN_VERSION || v > MAX_VERSION) { + return ReadFileResult::UNRECOGNIZED_VERSION; + } + + RootPresets presets; + if ((result = RootPresetsHelper(presets, &root)) != + ReadFileResult::READ_OK) { + return result; + } + + unsigned int currentMajor = cmVersion::GetMajorVersion(); + unsigned int currentMinor = cmVersion::GetMinorVersion(); + unsigned int currentPatch = cmVersion::GetPatchVersion(); + auto const& required = presets.CMakeMinimumRequired; + if (required.Major > currentMajor || + (required.Major == currentMajor && + (required.Minor > currentMinor || + (required.Minor == currentMinor && + (required.Patch > currentPatch))))) { + return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION; + } + + for (auto& preset : presets.Presets) { + preset.User = user; + if (preset.Name.empty()) { + return ReadFileResult::INVALID_PRESET; + } + if (!presetMap.insert({ preset.Name, preset }).second) { + return ReadFileResult::DUPLICATE_PRESETS; + } + presetOrder.push_back(preset.Name); + } + + return ReadFileResult::READ_OK; +} diff --git a/Source/cmCMakePresetsFile.h b/Source/cmCMakePresetsFile.h new file mode 100644 index 0000000..70ec4c5 --- /dev/null +++ b/Source/cmCMakePresetsFile.h @@ -0,0 +1,141 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include <cm/optional> + +class cmCMakePresetsFile +{ +public: + enum class CMakeGeneratorConfig + { + Default, + Ignore, + }; + + class CacheVariable + { + public: + std::string Type; + std::string Value; + }; + + class Preset + { + public: +#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) + Preset() = default; + Preset(const Preset& /*other*/) = default; + Preset(Preset&& /*other*/) = default; + + Preset& operator=(const Preset& /*other*/) = default; + + // The move assignment operators for several STL classes did not become + // noexcept until C++17, which causes some tools to warn about this move + // assignment operator throwing an exception when it shouldn't. Disable the + // move assignment operator until C++17 is enabled. + Preset& operator=(Preset&& /*other*/) = delete; +#endif + + std::string Name; + std::vector<std::string> Inherits; + bool Hidden; + bool User; + std::string DisplayName; + std::string Description; + std::string Generator; + std::string Architecture; + std::string Toolset; + cm::optional<CMakeGeneratorConfig> GeneratorConfig; + std::string BinaryDir; + + std::map<std::string, cm::optional<CacheVariable>> CacheVariables; + std::map<std::string, cm::optional<std::string>> Environment; + + cm::optional<bool> WarnDev; + cm::optional<bool> ErrorDev; + cm::optional<bool> WarnDeprecated; + cm::optional<bool> ErrorDeprecated; + cm::optional<bool> WarnUninitialized; + cm::optional<bool> WarnUnusedCli; + cm::optional<bool> WarnSystemVars; + + cm::optional<bool> DebugOutput; + cm::optional<bool> DebugTryCompile; + cm::optional<bool> DebugFind; + }; + + class UnexpandedPreset : public Preset + { + public: + using Preset::Preset; + + UnexpandedPreset() = default; + UnexpandedPreset(const Preset& preset) + : Preset(preset) + { + } + UnexpandedPreset(Preset&& preset) + : Preset(std::move(preset)) + { + } + }; + + class ExpandedPreset : public Preset + { + public: + using Preset::Preset; + + ExpandedPreset() = default; + ExpandedPreset(const Preset& preset) + : Preset(preset) + { + } + ExpandedPreset(Preset&& preset) + : Preset(std::move(preset)) + { + } + }; + + std::string SourceDir; + std::map<std::string, UnexpandedPreset> Presets; + std::vector<std::string> PresetOrder; + + enum class ReadFileResult + { + READ_OK, + FILE_NOT_FOUND, + JSON_PARSE_ERROR, + INVALID_ROOT, + NO_VERSION, + INVALID_VERSION, + UNRECOGNIZED_VERSION, + INVALID_CMAKE_VERSION, + UNRECOGNIZED_CMAKE_VERSION, + INVALID_PRESETS, + INVALID_PRESET, + INVALID_VARIABLE, + DUPLICATE_PRESETS, + CYCLIC_PRESET_INHERITANCE, + USER_PRESET_INHERITANCE, + }; + + static std::string GetFilename(const std::string& sourceDir); + static std::string GetUserFilename(const std::string& sourceDir); + ReadFileResult ReadProjectPresets(const std::string& sourceDir, + bool allowNoFiles = false); + static const char* ResultToString(ReadFileResult result); + + cm::optional<ExpandedPreset> ExpandMacros( + const UnexpandedPreset& preset) const; + +private: + ReadFileResult ReadJSONFile( + const std::string& filename, std::vector<std::string>& presetOrder, + std::map<std::string, UnexpandedPreset>& presetMap, bool user); +}; diff --git a/Source/cmGlobalGeneratorFactory.h b/Source/cmGlobalGeneratorFactory.h index 3a7f806..d6ababb 100644 --- a/Source/cmGlobalGeneratorFactory.h +++ b/Source/cmGlobalGeneratorFactory.h @@ -25,7 +25,7 @@ public: /** Create a GlobalGenerator */ virtual std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& n, cmake* cm) const = 0; + const std::string& n, bool allowArch, cmake* cm) const = 0; /** Get the documentation entry for this factory */ virtual void GetDocumentation(cmDocumentationEntry& entry) const = 0; @@ -53,7 +53,7 @@ class cmGlobalGeneratorSimpleFactory : public cmGlobalGeneratorFactory public: /** Create a GlobalGenerator */ std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool /*allowArch*/, cmake* cm) const override { if (name != T::GetActualName()) { return std::unique_ptr<cmGlobalGenerator>(); diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx index 3805546..7794df3 100644 --- a/Source/cmGlobalVisualStudio10Generator.cxx +++ b/Source/cmGlobalVisualStudio10Generator.cxx @@ -59,7 +59,7 @@ class cmGlobalVisualStudio10Generator::Factory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { std::string genName; const char* p = cmVS10GenName(name, genName); @@ -70,7 +70,7 @@ public: return std::unique_ptr<cmGlobalGenerator>( new cmGlobalVisualStudio10Generator(cm, genName, "")); } - if (*p++ != ' ') { + if (!allowArch || *p++ != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } if (strcmp(p, "Win64") == 0) { diff --git a/Source/cmGlobalVisualStudio11Generator.cxx b/Source/cmGlobalVisualStudio11Generator.cxx index a385375..a5ffcf0 100644 --- a/Source/cmGlobalVisualStudio11Generator.cxx +++ b/Source/cmGlobalVisualStudio11Generator.cxx @@ -31,7 +31,7 @@ class cmGlobalVisualStudio11Generator::Factory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { std::string genName; const char* p = cmVS11GenName(name, genName); @@ -42,7 +42,7 @@ public: return std::unique_ptr<cmGlobalGenerator>( new cmGlobalVisualStudio11Generator(cm, genName, "")); } - if (*p++ != ' ') { + if (!allowArch || *p++ != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } if (strcmp(p, "Win64") == 0) { diff --git a/Source/cmGlobalVisualStudio12Generator.cxx b/Source/cmGlobalVisualStudio12Generator.cxx index 5a27994..8bdf356 100644 --- a/Source/cmGlobalVisualStudio12Generator.cxx +++ b/Source/cmGlobalVisualStudio12Generator.cxx @@ -29,7 +29,7 @@ class cmGlobalVisualStudio12Generator::Factory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { std::string genName; const char* p = cmVS12GenName(name, genName); @@ -40,7 +40,7 @@ public: return std::unique_ptr<cmGlobalGenerator>( new cmGlobalVisualStudio12Generator(cm, genName, "")); } - if (*p++ != ' ') { + if (!allowArch || *p++ != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } if (strcmp(p, "Win64") == 0) { diff --git a/Source/cmGlobalVisualStudio14Generator.cxx b/Source/cmGlobalVisualStudio14Generator.cxx index a12b7e1..e17c6d7 100644 --- a/Source/cmGlobalVisualStudio14Generator.cxx +++ b/Source/cmGlobalVisualStudio14Generator.cxx @@ -30,7 +30,7 @@ class cmGlobalVisualStudio14Generator::Factory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { std::string genName; const char* p = cmVS14GenName(name, genName); @@ -41,7 +41,7 @@ public: return std::unique_ptr<cmGlobalGenerator>( new cmGlobalVisualStudio14Generator(cm, genName, "")); } - if (*p++ != ' ') { + if (!allowArch || *p++ != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } if (strcmp(p, "Win64") == 0) { diff --git a/Source/cmGlobalVisualStudio9Generator.cxx b/Source/cmGlobalVisualStudio9Generator.cxx index 9f73c15..2339a80 100644 --- a/Source/cmGlobalVisualStudio9Generator.cxx +++ b/Source/cmGlobalVisualStudio9Generator.cxx @@ -16,7 +16,7 @@ class cmGlobalVisualStudio9Generator::Factory : public cmGlobalGeneratorFactory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { if (strncmp(name.c_str(), vs9generatorName, sizeof(vs9generatorName) - 1) != 0) { @@ -29,7 +29,7 @@ public: new cmGlobalVisualStudio9Generator(cm, name, "")); } - if (p[0] != ' ') { + if (!allowArch || p[0] != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx index 4b877ce..95357e7 100644 --- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx +++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx @@ -140,7 +140,7 @@ class cmGlobalVisualStudioVersionedGenerator::Factory15 { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool allowArch, cmake* cm) const override { std::string genName; const char* p = cmVS15GenName(name, genName); @@ -152,7 +152,7 @@ public: new cmGlobalVisualStudioVersionedGenerator( cmGlobalVisualStudioGenerator::VS15, cm, genName, "")); } - if (*p++ != ' ') { + if (!allowArch || *p++ != ' ') { return std::unique_ptr<cmGlobalGenerator>(); } if (strcmp(p, "Win64") == 0) { @@ -234,7 +234,7 @@ class cmGlobalVisualStudioVersionedGenerator::Factory16 { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override + const std::string& name, bool /*allowArch*/, cmake* cm) const override { std::string genName; const char* p = cmVS16GenName(name, genName); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index f1a1ce6..952a179 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -135,7 +135,7 @@ class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory { public: std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name, cmake* cm) const override; + const std::string& name, bool allowArch, cmake* cm) const override; void GetDocumentation(cmDocumentationEntry& entry) const override { @@ -197,6 +197,7 @@ std::unique_ptr<cmGlobalGeneratorFactory> cmGlobalXCodeGenerator::NewFactory() std::unique_ptr<cmGlobalGenerator> cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(const std::string& name, + bool /*allowArch*/, cmake* cm) const { if (name != GetActualName()) { diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h index 2da2a03..a63347d 100644 --- a/Source/cmJSONHelpers.h +++ b/Source/cmJSONHelpers.h @@ -29,6 +29,9 @@ public: template <typename M, typename F> cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func, bool required = true); + template <typename F> + cmJSONObjectHelper& Bind(const cm::string_view& name, F func, + bool required = true); E operator()(T& out, const Json::Value* value) const; @@ -87,6 +90,14 @@ cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind( } template <typename T, typename E> +template <typename F> +cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind( + const cm::string_view& name, F func, bool required) +{ + return this->BindPrivate(name, MemberFunction(func), required); +} + +template <typename T, typename E> cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::BindPrivate( const cm::string_view& name, MemberFunction&& func, bool required) { diff --git a/Source/cmake.cxx b/Source/cmake.cxx index d9c6b7a..2a14b03 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -13,6 +13,7 @@ #include <utility> #include <cm/memory> +#include <cm/optional> #include <cm/string_view> #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(CMAKE_BOOT_MINGW) # include <cm/iterator> @@ -27,6 +28,7 @@ #include "cm_sys_stat.h" +#include "cmCMakePresetsFile.h" #include "cmCommands.h" #include "cmDocumentation.h" #include "cmDocumentationEntry.h" @@ -286,6 +288,97 @@ void cmake::CleanupCommandsAndMacros() this->CurrentSnapshot.SetDefaultDefinitions(); } +#ifndef CMAKE_BOOTSTRAP +void cmake::SetWarningFromPreset(const std::string& name, + const cm::optional<bool>& warning, + const cm::optional<bool>& error) +{ + if (warning) { + if (*warning) { + this->DiagLevels[name] = std::max(this->DiagLevels[name], DIAG_WARN); + } else { + this->DiagLevels[name] = DIAG_IGNORE; + } + } + if (error) { + if (*error) { + this->DiagLevels[name] = DIAG_ERROR; + } else { + this->DiagLevels[name] = std::min(this->DiagLevels[name], DIAG_WARN); + } + } +} + +void cmake::ProcessPresetVariables() +{ + for (auto const& var : this->UnprocessedPresetVariables) { + if (!var.second) { + continue; + } + cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED; + if (!var.second->Type.empty()) { + type = cmState::StringToCacheEntryType(var.second->Type); + } + this->ProcessCacheArg(var.first, var.second->Value, type); + } +} + +void cmake::PrintPresetVariables() +{ + bool first = true; + for (auto const& var : this->UnprocessedPresetVariables) { + if (!var.second) { + continue; + } + cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED; + if (!var.second->Type.empty()) { + type = cmState::StringToCacheEntryType(var.second->Type); + } + if (first) { + std::cout << "Preset CMake variables:\n\n"; + first = false; + } + std::cout << " " << var.first; + if (type != cmStateEnums::UNINITIALIZED) { + std::cout << ':' << cmState::CacheEntryTypeToString(type); + } + std::cout << "=\"" << var.second->Value << "\"\n"; + } + if (!first) { + std::cout << '\n'; + } + this->UnprocessedPresetVariables.clear(); +} + +void cmake::ProcessPresetEnvironment() +{ + for (auto const& var : this->UnprocessedPresetEnvironment) { + if (var.second) { + cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second)); + } + } +} + +void cmake::PrintPresetEnvironment() +{ + bool first = true; + for (auto const& var : this->UnprocessedPresetEnvironment) { + if (!var.second) { + continue; + } + if (first) { + std::cout << "Preset environment variables:\n\n"; + first = false; + } + std::cout << " " << var.first << "=\"" << *var.second << "\"\n"; + } + if (!first) { + std::cout << '\n'; + } + this->UnprocessedPresetEnvironment.clear(); +} +#endif + // Parse the args bool cmake::SetCacheArgs(const std::vector<std::string>& args) { @@ -308,28 +401,10 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) std::string value; cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED; if (cmState::ParseCacheEntry(entry, var, value, type)) { - // The value is transformed if it is a filepath for example, so - // we can't compare whether the value is already in the cache until - // after we call AddCacheEntry. - bool haveValue = false; - std::string cachedValue; - if (this->WarnUnusedCli) { - if (cmProp v = this->State->GetInitializedCacheValue(var)) { - haveValue = true; - cachedValue = *v; - } - } - - this->AddCacheEntry(var, value.c_str(), - "No help, variable specified on the command line.", - type); - - if (this->WarnUnusedCli) { - if (!haveValue || - cachedValue != *this->State->GetInitializedCacheValue(var)) { - this->WatchUnusedCli(var); - } - } +#ifndef CMAKE_BOOTSTRAP + this->UnprocessedPresetVariables.erase(var); +#endif + this->ProcessCacheArg(var, value, type); } else { cmSystemTools::Error("Parse error in command line argument: " + arg + "\n" + "Should be: VAR:type=value\n"); @@ -409,6 +484,9 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) // now remove them from the cache for (std::string const& currentEntry : entriesToDelete) { +#ifndef CMAKE_BOOTSTRAP + this->UnprocessedPresetVariables.erase(currentEntry); +#endif this->State->RemoveCacheEntry(currentEntry); } } else if (cmHasLiteralPrefix(arg, "-C")) { @@ -462,6 +540,33 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) return true; } +void cmake::ProcessCacheArg(const std::string& var, const std::string& value, + cmStateEnums::CacheEntryType type) +{ + // The value is transformed if it is a filepath for example, so + // we can't compare whether the value is already in the cache until + // after we call AddCacheEntry. + bool haveValue = false; + std::string cachedValue; + if (this->WarnUnusedCli) { + if (cmProp v = this->State->GetInitializedCacheValue(var)) { + haveValue = true; + cachedValue = *v; + } + } + + this->AddCacheEntry(var, value.c_str(), + "No help, variable specified on the command line.", + type); + + if (this->WarnUnusedCli) { + if (!haveValue || + cachedValue != *this->State->GetInitializedCacheValue(var)) { + this->WatchUnusedCli(var); + } + } +} + void cmake::ReadListFile(const std::vector<std::string>& args, const std::string& path) { @@ -625,6 +730,8 @@ void cmake::SetArgs(const std::vector<std::string>& args) #if !defined(CMAKE_BOOTSTRAP) std::string profilingFormat; std::string profilingOutput; + std::string presetName; + bool listPresets = false; #endif for (unsigned int i = 1; i < args.size(); ++i) { std::string const& arg = args[i]; @@ -830,19 +937,9 @@ void cmake::SetArgs(const std::vector<std::string>& args) } value = args[i]; } - auto gen = this->CreateGlobalGenerator(value); - if (!gen) { - std::string kdevError; - if (value.find("KDevelop3", 0) != std::string::npos) { - kdevError = "\nThe KDevelop3 generator is not supported anymore."; - } - - cmSystemTools::Error( - cmStrCat("Could not create named generator ", value, kdevError)); - this->PrintGeneratorList(); + if (!this->CreateAndSetGlobalGenerator(value, true)) { return; } - this->SetGlobalGenerator(std::move(gen)); #if !defined(CMAKE_BOOTSTRAP) } else if (cmHasLiteralPrefix(arg, "--profiling-format")) { profilingFormat = arg.substr(strlen("--profiling-format=")); @@ -856,6 +953,13 @@ void cmake::SetArgs(const std::vector<std::string>& args) if (profilingOutput.empty()) { cmSystemTools::Error("No path specified for --profiling-output"); } + } else if (cmHasLiteralPrefix(arg, "--preset")) { + presetName = arg.substr(strlen("--preset=")); + if (presetName.empty()) { + cmSystemTools::Error("No preset specified for --preset"); + } + } else if (cmHasLiteralPrefix(arg, "--list-presets")) { + listPresets = true; #endif } // no option assume it is the path to the source or an existing build @@ -915,6 +1019,91 @@ void cmake::SetArgs(const std::vector<std::string>& args) if (!haveBinaryDir) { this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory()); } + +#if !defined(CMAKE_BOOTSTRAP) + if (listPresets || !presetName.empty()) { + cmCMakePresetsFile settingsFile; + auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory()); + if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) { + cmSystemTools::Error( + cmStrCat("Could not read presets from ", this->GetHomeDirectory(), + ": ", cmCMakePresetsFile::ResultToString(result))); + return; + } + if (listPresets) { + this->PrintPresetList(settingsFile); + return; + } + auto preset = settingsFile.Presets.find(presetName); + if (preset == settingsFile.Presets.end()) { + cmSystemTools::Error(cmStrCat("No such preset in ", + this->GetHomeDirectory(), ": \"", + presetName, '"')); + this->PrintPresetList(settingsFile); + return; + } + if (preset->second.Hidden) { + cmSystemTools::Error(cmStrCat("Cannot use hidden preset in ", + this->GetHomeDirectory(), ": \"", + presetName, '"')); + this->PrintPresetList(settingsFile); + return; + } + auto expandedPreset = settingsFile.ExpandMacros(preset->second); + if (!expandedPreset) { + cmSystemTools::Error(cmStrCat("Could not evaluate preset \"", + preset->second.Name, + "\": Invalid macro expansion")); + return; + } + + if (!haveBinaryDir) { + this->SetHomeOutputDirectory(expandedPreset->BinaryDir); + } + if (!this->GlobalGenerator) { + if (!this->CreateAndSetGlobalGenerator(expandedPreset->Generator, + false)) { + return; + } + } + this->UnprocessedPresetVariables = expandedPreset->CacheVariables; + this->UnprocessedPresetEnvironment = expandedPreset->Environment; + + if (!expandedPreset->GeneratorConfig || + expandedPreset->GeneratorConfig == + cmCMakePresetsFile::CMakeGeneratorConfig::Default) { + if (!this->GeneratorPlatformSet) { + this->SetGeneratorPlatform(expandedPreset->Architecture); + } + if (!this->GeneratorToolsetSet) { + this->SetGeneratorToolset(expandedPreset->Toolset); + } + } + + this->SetWarningFromPreset("dev", expandedPreset->WarnDev, + expandedPreset->ErrorDev); + this->SetWarningFromPreset("deprecated", expandedPreset->WarnDeprecated, + expandedPreset->ErrorDeprecated); + if (expandedPreset->WarnUninitialized == true) { + this->SetWarnUninitialized(true); + } + if (expandedPreset->WarnUnusedCli == false) { + this->SetWarnUnusedCli(false); + } + if (expandedPreset->WarnSystemVars == true) { + this->SetCheckSystemVars(true); + } + if (expandedPreset->DebugOutput == true) { + this->SetDebugOutputOn(true); + } + if (expandedPreset->DebugTryCompile == true) { + this->DebugTryCompileOn(); + } + if (expandedPreset->DebugFind == true) { + this->SetDebugFindOutputOn(true); + } + } +#endif } cmake::LogLevel cmake::StringToLogLevel(const std::string& levelStr) @@ -1217,7 +1406,7 @@ createExtraGenerator( } std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator( - const std::string& gname) + const std::string& gname, bool allowArch) { std::pair<std::unique_ptr<cmExternalMakefileProjectGenerator>, std::string> extra = createExtraGenerator(this->ExtraGenerators, gname); @@ -1227,7 +1416,7 @@ std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator( std::unique_ptr<cmGlobalGenerator> generator; for (const auto& g : this->Generators) { - generator = g->CreateGlobalGenerator(name, this); + generator = g->CreateGlobalGenerator(name, allowArch, this); if (generator) { break; } @@ -1240,6 +1429,79 @@ std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator( return generator; } +bool cmake::CreateAndSetGlobalGenerator(const std::string& name, + bool allowArch) +{ + auto gen = this->CreateGlobalGenerator(name, allowArch); + if (!gen) { + std::string kdevError; + std::string vsError; + if (name.find("KDevelop3", 0) != std::string::npos) { + kdevError = "\nThe KDevelop3 generator is not supported anymore."; + } + if (!allowArch && cmHasLiteralPrefix(name, "Visual Studio ") && + name.length() >= cmStrLen("Visual Studio xx xxxx ")) { + vsError = "\nUsing platforms in Visual Studio generator names is not " + "supported in CMakePresets.json."; + } + + cmSystemTools::Error( + cmStrCat("Could not create named generator ", name, kdevError, vsError)); + this->PrintGeneratorList(); + return false; + } + + this->SetGlobalGenerator(std::move(gen)); + return true; +} + +#ifndef CMAKE_BOOTSTRAP +void cmake::PrintPresetList(const cmCMakePresetsFile& file) const +{ + std::vector<GeneratorInfo> generators; + this->GetRegisteredGenerators(generators, false); + + std::vector<cmCMakePresetsFile::UnexpandedPreset> presets; + for (auto const& p : file.PresetOrder) { + auto const& preset = file.Presets.at(p); + if (!preset.Hidden && + std::find_if(generators.begin(), generators.end(), + [&preset](const GeneratorInfo& info) { + return info.name == preset.Generator; + }) != generators.end() && + file.ExpandMacros(preset)) { + presets.push_back(preset); + } + } + + if (presets.empty()) { + return; + } + + std::cout << "Available presets:\n\n"; + + auto longestPresetName = + std::max_element(presets.begin(), presets.end(), + [](const cmCMakePresetsFile::UnexpandedPreset& a, + const cmCMakePresetsFile::UnexpandedPreset& b) { + return a.Name.length() < b.Name.length(); + }); + auto longestLength = longestPresetName->Name.length(); + + for (auto const& preset : presets) { + std::cout << " \"" << preset.Name << '"'; + auto const& description = preset.DisplayName; + if (!description.empty()) { + for (std::size_t i = 0; i < longestLength - preset.Name.length(); ++i) { + std::cout << ' '; + } + std::cout << " - " << description; + } + std::cout << '\n'; + } +} +#endif + void cmake::SetHomeDirectory(const std::string& dir) { this->State->SetSourceDirectory(dir); @@ -1803,6 +2065,9 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) if (cmSystemTools::GetErrorOccuredFlag()) { return -1; } + if (this->GetWorkingMode() == HELP_MODE) { + return 0; + } // Log the trace format version to the desired output if (this->GetTrace()) { @@ -1831,11 +2096,19 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) this->AddCMakePaths(); } +#ifndef CMAKE_BOOTSTRAP + this->ProcessPresetVariables(); + this->ProcessPresetEnvironment(); +#endif // Add any cache args if (!this->SetCacheArgs(args)) { cmSystemTools::Error("Problem processing arguments. Aborting.\n"); return -1; } +#ifndef CMAKE_BOOTSTRAP + this->PrintPresetVariables(); + this->PrintPresetEnvironment(); +#endif // In script mode we terminate after running the script. if (this->GetWorkingMode() != NORMAL_MODE) { diff --git a/Source/cmake.h b/Source/cmake.h index 63635cb..262d673 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -27,7 +27,11 @@ #include "cmStateTypes.h" #if !defined(CMAKE_BOOTSTRAP) +# include <cm/optional> + # include <cm3p/json/value.h> + +# include "cmCMakePresetsFile.h" #endif class cmExternalMakefileProjectGeneratorFactory; @@ -88,13 +92,22 @@ public: enum WorkingMode { NORMAL_MODE, ///< Cmake runs to create project files - /** \brief Script mode (started by using -P). - * - * In script mode there is no generator and no cache. Also, - * languages are not enabled, so add_executable and things do - * nothing. - */ + + /** \brief Script mode (started by using -P). + * + * In script mode there is no generator and no cache. Also, + * languages are not enabled, so add_executable and things do + * nothing. + */ SCRIPT_MODE, + + /** \brief Help mode + * + * Used to print help for things that can only be determined after finding + * the source directory, for example, the list of presets. + */ + HELP_MODE, + /** \brief A pkg-config like mode * * In this mode cmake just searches for a package and prints the results to @@ -219,7 +232,15 @@ public: //! Create a GlobalGenerator std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( - const std::string& name); + const std::string& name, bool allowArch = true); + + //! Create a GlobalGenerator and set it as our own + bool CreateAndSetGlobalGenerator(const std::string& name, bool allowArch); + +#ifndef CMAKE_BOOTSTRAP + //! Print list of presets + void PrintPresetList(const cmCMakePresetsFile& file) const; +#endif //! Return the global generator assigned to this instance of cmake cmGlobalGenerator* GetGlobalGenerator() @@ -329,9 +350,22 @@ public: bool GetIsInTryCompile() const; void SetIsInTryCompile(bool b); +#ifndef CMAKE_BOOTSTRAP + void SetWarningFromPreset(const std::string& name, + const cm::optional<bool>& warning, + const cm::optional<bool>& error); + void ProcessPresetVariables(); + void PrintPresetVariables(); + void ProcessPresetEnvironment(); + void PrintPresetEnvironment(); +#endif + //! Parse command line arguments that might set cache values bool SetCacheArgs(const std::vector<std::string>&); + void ProcessCacheArg(const std::string& var, const std::string& value, + cmStateEnums::CacheEntryType type); + using ProgressCallbackType = std::function<void(const std::string&, float)>; /** * Set the function used by GUIs to receive progress updates @@ -625,6 +659,12 @@ private: std::unique_ptr<cmFileTimeCache> FileTimeCache; std::string GraphVizFile; InstalledFilesMap InstalledFiles; +#ifndef CMAKE_BOOTSTRAP + std::map<std::string, cm::optional<cmCMakePresetsFile::CacheVariable>> + UnprocessedPresetVariables; + std::map<std::string, cm::optional<std::string>> + UnprocessedPresetEnvironment; +#endif #if !defined(CMAKE_BOOTSTRAP) std::unique_ptr<cmVariableWatch> VariableWatch; @@ -664,6 +704,8 @@ private: #define CMAKE_STANDARD_OPTIONS_TABLE \ { "-S <path-to-source>", "Explicitly specify a source directory." }, \ { "-B <path-to-build>", "Explicitly specify a build directory." }, \ + { "--preset=<preset-name>", "Explicitly specify a preset." }, \ + { "--list-presets", "List available presets." }, \ { "-C <initial-cache>", "Pre-load a script to populate the cache." }, \ { "-D <var>[:<type>]=<value>", "Create or update a cmake cache entry." }, \ { "-U <globbing_expr>", "Remove matching entries from CMake cache." }, \ diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index 4600fc5..c769227 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -49,7 +49,8 @@ const char* cmDocumentationUsage[][2] = { { nullptr, " cmake [options] <path-to-source>\n" " cmake [options] <path-to-existing-build>\n" - " cmake [options] -S <path-to-source> -B <path-to-build>" }, + " cmake [options] -S <path-to-source> -B <path-to-build>\n" + " cmake [options] -S <path-to-source> --preset=<preset-name>" }, { nullptr, "Specify a source directory to (re-)generate a build system for " "it in the current working directory. Specify an existing build " @@ -253,6 +254,9 @@ int do_cmake(int ac, char const* const* av) } else if (cmHasLiteralPrefix(av[i], "--find-package")) { workingMode = cmake::FIND_PACKAGE_MODE; args.emplace_back(av[i]); + } else if (strcmp(av[i], "--list-presets") == 0) { + workingMode = cmake::HELP_MODE; + args.emplace_back(av[i]); } else { args.emplace_back(av[i]); } @@ -269,6 +273,7 @@ int do_cmake(int ac, char const* const* av) cmState::Mode mode = cmState::Unknown; switch (workingMode) { case cmake::NORMAL_MODE: + case cmake::HELP_MODE: mode = cmState::Project; break; case cmake::SCRIPT_MODE: |