CMakePresets: Add include field

Fixes: #21331
This commit is contained in:
Kyle Edwards
2021-12-22 16:49:38 -05:00
parent a239f23a98
commit 26a5512c0f
42 changed files with 351 additions and 7 deletions

View File

@@ -12,9 +12,10 @@ Introduction
One problem that CMake users often face is sharing settings with other people
for common ways to configure a project. This may be done to support CI builds,
or for users who frequently use the same build. CMake supports two files,
or for users who frequently use the same build. CMake supports two main files,
``CMakePresets.json`` and ``CMakeUserPresets.json``, that allow users to
specify common configure options and share them with others.
specify common configure options and share them with others. CMake also
supports files included with the ``include`` field.
``CMakePresets.json`` and ``CMakeUserPresets.json`` live in the project's root
directory. They both have exactly the same format, and both are optional
@@ -26,6 +27,21 @@ builds. ``CMakePresets.json`` may be checked into a version control system, and
is using Git, ``CMakePresets.json`` may be tracked, and
``CMakeUserPresets.json`` should be added to the ``.gitignore``.
``CMakePresets.json`` and ``CMakeUserPresets.json`` can include other files
with the ``include`` field in file version ``4`` and later. Files included by
these files can also include other files. If a preset file contains presets
that inherit from presets in another file, the file must include the other file
either directly or indirectly. Include cycles are not allowed among files (if
``a.json`` includes ``b.json``, ``b.json`` cannot include ``a.json``). However,
a file may be included multiple times from the same file or from different
files. If ``CMakePresets.json`` and ``CMakeUserPresets.json`` are both present,
``CMakeUserPresets.json`` implicitly includes ``CMakePresets.json``, even with
no ``include`` field, in all versions of the format. Files directly or
indirectly included from ``CMakePresets.json`` must be inside the project
directory. This restriction does not apply to ``CMakeUserPresets.json`` and
files that it includes, unless those files are also included by
``CMakePresets.json``.
Format
======
@@ -39,7 +55,7 @@ The root object recognizes the following fields:
``version``
A required integer representing the version of the JSON schema.
The supported versions are ``1``, ``2``, and ``3``.
The supported versions are ``1``, ``2``, ``3``, and ``4``.
``cmakeMinimumRequired``
@@ -82,6 +98,12 @@ The root object recognizes the following fields:
An optional array of `Test Preset`_ objects.
This is allowed in preset files specifying version ``2`` or above.
``include``
An optional array of strings representing files to include. If the filenames
are not absolute, they are considered relative to the current file.
This is allowed in preset files specifying version ``4`` or above.
Configure Preset
^^^^^^^^^^^^^^^^

View File

@@ -1,5 +1,5 @@
{
"version": 3,
"version": 4,
"cmakeMinimumRequired": {
"major": 3,
"minor": 21,

View File

@@ -42,6 +42,21 @@
"testPresets": { "$ref": "#/definitions/testPresetsV3"}
},
"additionalProperties": false
},
{
"properties": {
"version": {
"const": 4,
"description": "A required integer representing the version of the JSON schema."
},
"cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"},
"vendor": { "$ref": "#/definitions/vendor" },
"configurePresets": { "$ref": "#/definitions/configurePresetsV3"},
"buildPresets": { "$ref": "#/definitions/buildPresetsV3"},
"testPresets": { "$ref": "#/definitions/testPresetsV3"},
"include": { "$ref": "#/definitions/include"}
},
"additionalProperties": false
}
],
"required": [
@@ -1235,6 +1250,13 @@
"description": "Null indicates that the condition always evaluates to true and is not inherited."
}
]
},
"include": {
"type": "array",
"description": "An optional array of strings representing files to include. If the filenames are not absolute, they are considered relative to the current file.",
"items": {
"type": "string"
}
}
}
}

View File

@@ -0,0 +1,6 @@
cmake-presets-include
---------------------
* :manual:`cmake-presets(7)` files now support schema version ``4``.
* :manual:`cmake-presets(7)` files now have an optional ``include`` field,
which allows the files to include other files.

View File

@@ -996,6 +996,10 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED:
return "File version must be 2 or higher for build and test preset "
"support.";
case ReadFileResult::INCLUDE_UNSUPPORTED:
return "File version must be 4 or higher for include support";
case ReadFileResult::INVALID_INCLUDE:
return "Invalid \"include\" field";
case ReadFileResult::INVALID_CONFIGURE_PRESET:
return "Invalid \"configurePreset\" field";
case ReadFileResult::INSTALL_PREFIX_UNSUPPORTED:
@@ -1010,6 +1014,8 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
"support.";
case ReadFileResult::CYCLIC_INCLUDE:
return "Cyclic include among preset files";
case ReadFileResult::INCLUDE_OUTSIDE_PROJECT:
return "File included from outside project directory";
}
return "Unknown error";

View File

@@ -36,12 +36,15 @@ public:
PRESET_UNREACHABLE_FROM_FILE,
INVALID_MACRO_EXPANSION,
BUILD_TEST_PRESETS_UNSUPPORTED,
INCLUDE_UNSUPPORTED,
INVALID_INCLUDE,
INVALID_CONFIGURE_PRESET,
INSTALL_PREFIX_UNSUPPORTED,
INVALID_CONDITION,
CONDITION_UNSUPPORTED,
TOOLCHAIN_FILE_UNSUPPORTED,
CYCLIC_INCLUDE,
INCLUDE_OUTSIDE_PROJECT,
};
enum class ArchToolsetStrategy

View File

@@ -33,7 +33,7 @@ using TestPreset = cmCMakePresetsGraph::TestPreset;
using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
constexpr int MIN_VERSION = 1;
constexpr int MAX_VERSION = 3;
constexpr int MAX_VERSION = 4;
struct CMakeVersion
{
@@ -48,6 +48,7 @@ struct RootPresets
std::vector<cmCMakePresetsGraph::ConfigurePreset> ConfigurePresets;
std::vector<cmCMakePresetsGraph::BuildPreset> BuildPresets;
std::vector<cmCMakePresetsGraph::TestPreset> TestPresets;
std::vector<std::string> Include;
};
std::unique_ptr<cmCMakePresetsGraphInternal::NotCondition> InvertCondition(
@@ -271,6 +272,13 @@ auto const CMakeVersionHelper =
.Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
.Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
auto const IncludeHelper = cmJSONStringHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE);
auto const IncludeVectorHelper =
cmJSONVectorHelper<std::string, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper);
auto const RootPresetsHelper =
cmJSONObjectHelper<RootPresets, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false)
@@ -283,6 +291,7 @@ auto const RootPresetsHelper =
cmCMakePresetsGraphInternal::TestPresetsHelper, false)
.Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
CMakeVersionHelper, false)
.Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
.Bind<std::nullptr_t>(
"vendor"_s, nullptr,
cmCMakePresetsGraphInternal::VendorHelper(ReadFileResult::INVALID_ROOT),
@@ -413,6 +422,19 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
const std::string& filename, RootType rootType, ReadReason readReason,
std::vector<File*>& inProgressFiles, File*& file)
{
ReadFileResult result;
if (rootType == RootType::Project) {
auto normalizedFilename = cmSystemTools::CollapseFullPath(filename);
auto normalizedProjectDir =
cmSystemTools::CollapseFullPath(this->SourceDir);
if (!cmSystemTools::IsSubDirectory(normalizedFilename,
normalizedProjectDir)) {
return ReadFileResult::INCLUDE_OUTSIDE_PROJECT;
}
}
for (auto const& f : this->Files) {
if (cmSystemTools::SameFile(filename, f->Filename)) {
file = f.get();
@@ -421,6 +443,22 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
if (fileIt != inProgressFiles.end()) {
return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
}
// Check files included by this file again to make sure they're in the
// project directory.
if (rootType == RootType::Project) {
for (auto* f2 : file->ReachableFiles) {
if (!cmSystemTools::SameFile(filename, f2->Filename)) {
File* file2;
if ((result = this->ReadJSONFile(
f2->Filename, rootType, ReadReason::Included,
inProgressFiles, file2)) != ReadFileResult::READ_OK) {
return result;
}
}
}
}
return cmCMakePresetsGraph::ReadFileResult::READ_OK;
}
}
@@ -440,8 +478,7 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
}
int v = 0;
auto result = RootVersionHelper(v, &root);
if (result != ReadFileResult::READ_OK) {
if ((result = RootVersionHelper(v, &root)) != ReadFileResult::READ_OK) {
return result;
}
if (v < MIN_VERSION || v > MAX_VERSION) {
@@ -454,6 +491,11 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
}
// Support for include added in version 4.
if (v < 4 && root.isMember("include")) {
return ReadFileResult::INCLUDE_UNSUPPORTED;
}
RootPresets presets;
if ((result = RootPresetsHelper(presets, &root)) !=
ReadFileResult::READ_OK) {
@@ -571,6 +613,18 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
return ReadFileResult::READ_OK;
};
for (auto include : presets.Include) {
if (!cmSystemTools::FileIsFullPath(include)) {
auto directory = cmSystemTools::GetFilenamePath(filename);
include = cmStrCat(directory, '/', include);
}
if ((result = includeFile(include, rootType, ReadReason::Included)) !=
ReadFileResult::READ_OK) {
return result;
}
}
if (rootType == RootType::User && readReason == ReadReason::Root) {
auto cmakePresetsFilename = GetFilename(this->SourceDir);
if (cmSystemTools::FileExists(cmakePresetsFilename)) {

View File

@@ -0,0 +1,8 @@
^Not searching for unused variables given on the command line\.
Available configure presets:
"IncludeUser"
"IncludeUserCommon"
"Include"
"Subdir"
"IncludeCommon"$

View File

@@ -0,0 +1,16 @@
{
"version": 4,
"include": [
"subdir/CMakePresets.json",
"@RunCMake_TEST_SOURCE_DIR@/IncludeCommon.json"
],
"configurePresets": [
{
"name": "Include",
"inherits": [
"IncludeCommon",
"Subdir"
]
}
]
}

View File

@@ -0,0 +1,8 @@
{
"version": 3,
"configurePresets": [
{
"name": "IncludeCommon"
}
]
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeCycle: Cyclic include among preset files$

View File

@@ -0,0 +1,11 @@
{
"version": 4,
"include": [
"CMakeUserPresets.json"
],
"configurePresets": [
{
"name": "IncludeCycle"
}
]
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeCycle3Files: Cyclic include among preset files$

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"IncludeCycle3Files2.json"
]
}

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"IncludeCycle3Files3.json"
]
}

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"CMakePresets.json"
]
}

View File

@@ -0,0 +1,3 @@
{
"version": 3
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeNotFound: File not found$

View File

@@ -0,0 +1,11 @@
{
"version": 4,
"include": [
"NotFound.json"
],
"configurePresets": [
{
"name": "IncludeNotFound"
}
]
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeOutsideProject: File included from outside project directory$

View File

@@ -0,0 +1,11 @@
{
"version": 4,
"include": [
"IncludeOutsideProjectIntermediate.json"
],
"configurePresets": [
{
"name": "IncludeOutsideProject"
}
]
}

View File

@@ -0,0 +1,3 @@
{
"version": 4
}

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"@RunCMake_SOURCE_DIR@/IncludeOutsideProjectInclude.json"
]
}

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"IncludeOutsideProjectIntermediate.json"
]
}

View File

@@ -0,0 +1,15 @@
{
"version": 4,
"include": [
"IncludeUserCommon.json"
],
"configurePresets": [
{
"name": "IncludeUser",
"inherits": [
"Include",
"IncludeUserCommon"
]
}
]
}

View File

@@ -0,0 +1,8 @@
{
"version": 3,
"configurePresets": [
{
"name": "IncludeUserCommon"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"version": 4,
"include": [
"@RunCMake_SOURCE_DIR@/IncludeOutsideProjectInclude.json"
],
"configurePresets": [
{
"name": "IncludeUserOutsideProject"
}
]
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeV3: File version must be 4 or higher for include support$

View File

@@ -0,0 +1,4 @@
{
"version": 3,
"include": []
}

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/IncludeV4V3: File version must be 4 or higher for include support$

View File

@@ -0,0 +1,6 @@
{
"version": 4,
"include": [
"IncludeV4V3Extra.json"
]
}

View File

@@ -0,0 +1,4 @@
{
"version": 3,
"include": []
}

View File

@@ -44,6 +44,20 @@ function(run_cmake_presets name)
configure_file("${CMakeUserPresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" @ONLY)
endif()
set(_CMakePresets_EXTRA_FILES_OUT)
set(_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
foreach(_extra_file IN LISTS CMakePresets_EXTRA_FILES)
cmake_path(RELATIVE_PATH _extra_file
BASE_DIRECTORY "${RunCMake_SOURCE_DIR}"
OUTPUT_VARIABLE _extra_file_relative
)
string(REGEX REPLACE "\\.in$" "" _extra_file_out_relative "${_extra_file_relative}")
set(_extra_file_out "${RunCMake_TEST_SOURCE_DIR}/${_extra_file_out_relative}")
configure_file("${_extra_file}" "${_extra_file_out}")
list(APPEND _CMakePresets_EXTRA_FILES_OUT "${_extra_file_out}")
list(APPEND _CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS 0)
endforeach()
set(_s_arg -S)
if(CMakePresets_NO_S_ARG)
set(_s_arg)
@@ -319,6 +333,37 @@ run_cmake_presets(OptionalBinaryDirFieldNoS)
unset(CMakePresets_SOURCE_ARG)
unset(CMakePresets_NO_S_ARG)
# Test include field
set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
run_cmake_presets(IncludeV3)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
set(CMakePresets_EXTRA_FILES
"${RunCMake_SOURCE_DIR}/IncludeV4V3Extra.json.in"
)
set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS 1)
run_cmake_presets(IncludeV4V3)
unset(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
set(CMakePresets_EXTRA_FILES
"${RunCMake_SOURCE_DIR}/IncludeCommon.json.in"
"${RunCMake_SOURCE_DIR}/IncludeUserCommon.json.in"
"${RunCMake_SOURCE_DIR}/subdir/CMakePresets.json.in"
)
run_cmake_presets(Include --list-presets)
unset(CMakePresets_EXTRA_FILES)
run_cmake_presets(IncludeNotFound)
run_cmake_presets(IncludeCycle)
set(CMakePresets_EXTRA_FILES
"${RunCMake_SOURCE_DIR}/IncludeCycle3Files2.json.in"
"${RunCMake_SOURCE_DIR}/IncludeCycle3Files3.json.in"
)
run_cmake_presets(IncludeCycle3Files)
set(CMakePresets_EXTRA_FILES
"${RunCMake_SOURCE_DIR}/IncludeOutsideProjectIntermediate.json.in"
)
run_cmake_presets(IncludeOutsideProject)
unset(CMakePresets_EXTRA_FILES)
run_cmake_presets(IncludeUserOutsideProject)
# Test the example from the documentation
file(READ "${RunCMake_SOURCE_DIR}/../../../Help/manual/presets/example.json" _example)
string(REPLACE "\"generator\": \"Ninja\"" "\"generator\": \"@RunCMake_GENERATOR@\"" _example "${_example}")

View File

@@ -12,4 +12,11 @@ if(PYTHON_EXECUTABLE AND CMake_TEST_JSON_SCHEMA)
if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json")
validate_schema("${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" "${CMakeUserPresets_SCHEMA_EXPECTED_RESULT}")
endif()
if(NOT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS "${_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS}")
endif()
foreach(_f _r IN ZIP_LISTS _CMakePresets_EXTRA_FILES_OUT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
validate_schema("${_f}" "${_r}")
endforeach()
endif()

View File

@@ -0,0 +1,12 @@
{
"version": 4,
"include": [
"../IncludeCommon.json"
],
"configurePresets": [
{
"name": "Subdir",
"inherits": "IncludeCommon"
}
]
}