Commit 86174790 authored by Kyle Edwards's avatar Kyle Edwards
Browse files

CMake: Add presets functionality

parent 06128cf9
......@@ -94,7 +94,7 @@ ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
return ReadFileResult::READ_OK;
}
if (value->isObject()) {
out = CacheVariable{};
out.emplace();
return VariableObjectHelper(*out, value);
}
if (value->isNull()) {
......@@ -701,7 +701,7 @@ cmCMakePresetsFile::ExpandMacros(const UnexpandedPreset& preset) const
}
}
return retval;
return cm::make_optional(retval);
}
cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile(
......
......@@ -30,16 +30,16 @@ public:
public:
#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
Preset() = default;
Preset(const Preset& other) = default;
Preset(Preset&& other) = default;
Preset(const Preset& /*other*/) = default;
Preset(Preset&& /*other*/) = default;
Preset& operator=(const 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;
Preset& operator=(Preset&& /*other*/) = delete;
#endif
std::string Name;
......
......@@ -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)
......@@ -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);
......@@ -1801,6 +2063,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()) {
......@@ -1829,11 +2094,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) {
......
......@@ -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
......@@ -221,6 +234,14 @@ public:
std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
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." }, \
......
......@@ -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:
......
......@@ -757,6 +757,7 @@ add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
-DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
add_RunCMake_test("UnityBuild")
add_RunCMake_test(CMakePresets)
if(WIN32)
add_RunCMake_test(Win32GenEx)
......
^CMake Error at CMakeLists\.txt:[0-9]+ \(project\):
Generator
[^
]*
does not support platform specification, but platform