diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index 8bbee0fa8f..c97efa0e45 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -1257,12 +1257,14 @@ Macro Expansion ^^^^^^^^^^^^^^^ As mentioned above, some fields support macro expansion. Macros are -recognized in the form ``${}``. All macros are -evaluated in the context of the preset being used, even if the macro is in a -field that was inherited from another preset. For example, if the ``Base`` -preset sets variable ``PRESET_NAME`` to ``${presetName}``, and the -``Derived`` preset inherits from ``Base``, ``PRESET_NAME`` will be set to -``Derived``. +recognized in the form ``${}``. + +In general, macros are evaluated in the context of the preset being used, even +if the macro is in a field that was inherited from another preset. For example, +if the ``Base`` preset sets variable ``PRESET_NAME`` to ``${presetName}``, and +the ``Derived`` preset inherits from ``Base``, ``PRESET_NAME`` will be set to +``Derived``. The ``${fileDir}`` macro as of preset version ``12`` is an +exception to this rule. It is an error to not put a closing brace at the end of a macro name. For example, ``${sourceDir`` is invalid. A dollar sign (``$``) followed by @@ -1304,9 +1306,38 @@ Recognized macros include: .. _`CMakePresets fileDir`: ``${fileDir}`` - Path to the directory containing the preset file which contains the macro. + Path to the directory containing the preset file which defines the preset + being used. + This is allowed in preset files specifying version ``4`` or above. + In preset files specifying version ``12`` or above, this *always* expands to + the directory of the current preset file containing the macro, regardless + of the preset being used. + + For example, consider the following scenario. + + * ``/path/to/CMakePresets.json`` includes + ``/path/to/subdir/CMakePresets.json``. + * ``/path/to/subdir/CMakePresets.json`` defines preset ``Base``, which + sets variable ``MY_DIR`` to ``${fileDir}``. + * ``/path/to/CMakePresets.json`` defines preset ``Derived``, and + ``Derived`` inherits from ``Base``. + + Under preset versions 4-11, ``MY_DIR`` will be set to ``/path/to/`` when + using the ``Base`` preset, and ``/path/to/subdir/`` when using the + ``Derived`` preset. + When ``/path/to/subdir/CMakePresets.json`` specifies version ``12`` or + above, ``MY_DIR`` will always be set to ``/path/to/subdir/``, regardless of + the preset being used. + + .. note:: + + Since the ``${fileDir}`` macro in version 12 is expanded in the context of + the current preset file, it is the version of the current file, rather than + the version of the root file containing the preset being used, which + enables this alternative behavior. + ``${dollar}`` A literal dollar sign (``$``). @@ -1472,6 +1503,16 @@ they were added and a summary of the new features and changes is given below. representing :option:`--parallel ` with ```` omitted. + ``12`` + .. versionadded:: 4.4 + + * Changes to `Macro Expansion`_ + + * The `${fileDir} `_ macro now always expands to + the directory of preset file containing the ``${fileDir}`` macro, + regardless of whether it is inherited by another preset in a different + directory. + Schema ====== diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json index 6b60967e8c..c414ae5e98 100644 --- a/Help/manual/presets/schema.json +++ b/Help/manual/presets/schema.json @@ -180,6 +180,25 @@ "include": { "$ref": "#/definitions/include" } }, "additionalProperties": false + }, + { + "properties": { + "$schema": { "$ref": "#/definitions/$schema" }, + "$comment": { "$ref": "#/definitions/$comment" }, + "version": { + "const": 12, + "description": "A required integer representing the version of the JSON schema." + }, + "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequiredV10" }, + "vendor": { "$ref": "#/definitions/vendor" }, + "configurePresets": { "$ref": "#/definitions/configurePresetsV10" }, + "buildPresets": { "$ref": "#/definitions/buildPresetsV11" }, + "testPresets": { "$ref": "#/definitions/testPresetsV11" }, + "packagePresets": { "$ref": "#/definitions/packagePresetsV10" }, + "workflowPresets": { "$ref": "#/definitions/workflowPresetsV10" }, + "include": { "$ref": "#/definitions/include" } + }, + "additionalProperties": false } ], "required": [ diff --git a/Help/release/dev/presets-fileDir.rst b/Help/release/dev/presets-fileDir.rst new file mode 100644 index 0000000000..45f41d29a5 --- /dev/null +++ b/Help/release/dev/presets-fileDir.rst @@ -0,0 +1,7 @@ +presets-fileDir +--------------- + +* :manual:`cmake-presets(7)` files now support schema version 12. The + :ref:`${fileDir} ` macro now always expands to the + directory of the preset file containing the ``${fileDir}`` macro, regardless + of whether it is inherited by another preset in a different directory. diff --git a/Source/cmCMakePresetsGraph.cxx b/Source/cmCMakePresetsGraph.cxx index 1959f52116..a3fb908977 100644 --- a/Source/cmCMakePresetsGraph.cxx +++ b/Source/cmCMakePresetsGraph.cxx @@ -27,6 +27,8 @@ case ExpandMacroResult::Ignore: \ out.reset(); \ return true; \ + case ExpandMacroResult::Defer: \ + CM_FALLTHROUGH; \ case ExpandMacroResult::Ok: \ break; \ } \ @@ -54,6 +56,9 @@ using BaseMacroExpander = cmCMakePresetsGraphInternal::BaseMacroExpander; template using PresetMacroExpander = cmCMakePresetsGraphInternal::PresetMacroExpander; +template +using ImmediateMacroExpander = + cmCMakePresetsGraphInternal::ImmediateMacroExpander; using cmCMakePresetsGraphInternal::ExpandMacros; void InheritString(std::string& child, std::string const& parent) @@ -453,6 +458,8 @@ bool ExpandMacros(cmCMakePresetsGraph* graph, T const& preset, case ExpandMacroResult::Ignore: out.reset(); return true; + case ExpandMacroResult::Defer: + CM_FALLTHROUGH; case ExpandMacroResult::Ok: break; } @@ -498,6 +505,30 @@ ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status, } } +template +bool cmCMakePresetsGraphInternal::ExpandImmediateMacros(T& preset) +{ + MacroExpanderVector macroExpanders{}; + macroExpanders.push_back(cm::make_unique>(preset)); + cm::optional out = preset; + bool result = CheckExpandMacros(preset, out, macroExpanders); + if (out.has_value()) { + preset = out.value(); + }; + return result; +} + +template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros< + ConfigurePreset>(ConfigurePreset&); +template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros( + BuildPreset&); +template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros( + TestPreset&); +template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros< + PackagePreset>(PackagePreset&); +template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros< + WorkflowPreset>(WorkflowPreset&); + ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacros( std::string& out, MacroExpanderVector const& macroExpanders, int version) { diff --git a/Source/cmCMakePresetsGraphInternal.h b/Source/cmCMakePresetsGraphInternal.h index 02c99eeb72..82d4514209 100644 --- a/Source/cmCMakePresetsGraphInternal.h +++ b/Source/cmCMakePresetsGraphInternal.h @@ -25,6 +25,7 @@ enum class ExpandMacroResult { Ok, Ignore, + Defer, Error, }; @@ -125,6 +126,49 @@ public: } }; +template +class ImmediateMacroExpander : public MacroExpander +{ + T const& Preset; + +public: + ImmediateMacroExpander(T const& preset) + : Preset(preset) + { + } + ExpandMacroResult operator()(std::string const& macroNamespace, + std::string const& macroName, + std::string& macroOut, + int version) const override + { + if (macroNamespace.empty()) { + if (macroName == "fileDir") { + if (version < 12) { + return ExpandMacroResult::Defer; + } + macroOut += + cmSystemTools::GetParentDirectory(Preset.OriginFile->Filename); + return ExpandMacroResult::Ok; + } + } + return ExpandMacroResult::Defer; + } +}; + +template +bool ExpandImmediateMacros(T& preset); + +extern template bool ExpandImmediateMacros< + cmCMakePresetsGraph::ConfigurePreset>(cmCMakePresetsGraph::ConfigurePreset&); +extern template bool ExpandImmediateMacros( + cmCMakePresetsGraph::BuildPreset&); +extern template bool ExpandImmediateMacros( + cmCMakePresetsGraph::TestPreset&); +extern template bool ExpandImmediateMacros( + cmCMakePresetsGraph::PackagePreset&); +extern template bool ExpandImmediateMacros< + cmCMakePresetsGraph::WorkflowPreset>(cmCMakePresetsGraph::WorkflowPreset&); + class NullCondition : public cmCMakePresetsGraph::Condition { bool Evaluate(MacroExpanderVector const& /*expanders*/, int /*version*/, diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index d141b10086..55fd2a14d7 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -38,9 +38,10 @@ using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander; using MacroExpanderVector = cmCMakePresetsGraphInternal::MacroExpanderVector; using cmCMakePresetsGraphInternal::BaseMacroExpander; using cmCMakePresetsGraphInternal::ExpandMacros; +using cmCMakePresetsGraphInternal::ExpandImmediateMacros; constexpr int MIN_VERSION = 1; -constexpr int MAX_VERSION = 11; +constexpr int MAX_VERSION = 12; struct CMakeVersion { @@ -591,6 +592,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename, return false; } + if (!ExpandImmediateMacros(preset)) { + cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name, + &this->parseState); + return false; + } + PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; @@ -642,6 +649,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename, return false; } + if (!ExpandImmediateMacros(preset)) { + cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name, + &this->parseState); + return false; + } + PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; @@ -666,6 +679,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename, return false; } + if (!ExpandImmediateMacros(preset)) { + cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name, + &this->parseState); + return false; + } + PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; @@ -710,6 +729,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename, return false; } + if (!ExpandImmediateMacros(preset)) { + cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name, + &this->parseState); + return false; + } + PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; @@ -731,6 +756,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename, return false; } + if (!ExpandImmediateMacros(preset)) { + cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name, + &this->parseState); + return false; + } + PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; diff --git a/Tests/RunCMake/CMakePresets/FileDir.cmake b/Tests/RunCMake/CMakePresets/FileDir.cmake deleted file mode 100644 index c7298cdaed..0000000000 --- a/Tests/RunCMake/CMakePresets/FileDir.cmake +++ /dev/null @@ -1,3 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/TestVariable.cmake) - -test_variable(TEST_FILE_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir") diff --git a/Tests/RunCMake/CMakePresets/FileDir.json.in b/Tests/RunCMake/CMakePresets/FileDir.json.in index 899e5f302b..b7418535d4 100644 --- a/Tests/RunCMake/CMakePresets/FileDir.json.in +++ b/Tests/RunCMake/CMakePresets/FileDir.json.in @@ -1,6 +1,15 @@ { "version": 4, "include": [ - "subdir/FileDir.json" + "@FileDir_include_subdir@" + ], + "configurePresets": [ + { + "name": "FileDirExt", + "inherits": "FileDir", + "cacheVariables": { + "PRESET": "FileDirExt" + } + } ] } diff --git a/Tests/RunCMake/CMakePresets/FileDirv12.cmake b/Tests/RunCMake/CMakePresets/FileDirv12.cmake new file mode 100644 index 0000000000..04fb6e9b8f --- /dev/null +++ b/Tests/RunCMake/CMakePresets/FileDirv12.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/TestVariable.cmake) + +if(NOT "${PRESET}" STREQUAL "FileDirInclude") + test_variable(CMAKE_BINARY_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir/build") + test_variable(TEST_FILE_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir") +endif() diff --git a/Tests/RunCMake/CMakePresets/FileDirv4.cmake b/Tests/RunCMake/CMakePresets/FileDirv4.cmake new file mode 100644 index 0000000000..32f516c9cb --- /dev/null +++ b/Tests/RunCMake/CMakePresets/FileDirv4.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/TestVariable.cmake) + +if (PRESET STREQUAL "FileDir") + test_variable(CMAKE_BINARY_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir/build") + test_variable(TEST_FILE_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir") +elseif (PRESET STREQUAL "FileDirExt") + test_variable(CMAKE_BINARY_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/build") + test_variable(TEST_FILE_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}") +endif() diff --git a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt index ba962b44f4..4a9f6e4f84 100644 --- a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt +++ b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt @@ -1,5 +1,5 @@ ^CMake Error: Could not read presets from [^ ]*/Tests/RunCMake/CMakePresets/HighVersion: -CMakePresets\.json:2: Unrecognized "version" 1000: must be >=1 and <=11 +CMakePresets\.json:2: Unrecognized "version" 1000: must be >=1 and <=12 "version": 1000, \^$ diff --git a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt index 5ed2857ab8..880058fc75 100644 --- a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt +++ b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt @@ -1,5 +1,5 @@ ^CMake Error: Could not read presets from [^ ]*/Tests/RunCMake/CMakePresets/LowVersion: -CMakePresets\.json:2: Unrecognized "version" 0: must be >=1 and <=11 +CMakePresets\.json:2: Unrecognized "version" 0: must be >=1 and <=12 "version": 0, \^ diff --git a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake index 6ffd7e5b90..80999b7bcb 100644 --- a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake +++ b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake @@ -377,10 +377,20 @@ run_cmake_presets(HostSystemNameFuture) # Test ${fileDir} macro set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/FileDir.json.in") +set(FileDir_include_subdir "subdir/FileDirv4.json") set(CMakePresets_EXTRA_FILES - "${RunCMake_SOURCE_DIR}/subdir/FileDir.json.in" - ) -run_cmake_presets(FileDir) + "${RunCMake_SOURCE_DIR}/${FileDir_include_subdir}.in" +) +run_cmake_presets(FileDirv4 --preset FileDir) +run_cmake_presets(FileDirv4 --preset FileDirExt) +set(FileDir_include_subdir "subdir/FileDirv12.json") +set(CMakePresets_EXTRA_FILES + "${RunCMake_SOURCE_DIR}/${FileDir_include_subdir}.in" + "${RunCMake_SOURCE_DIR}/subdir/FileDirInclude.json.in" +) +run_cmake_presets(FileDirv12 --preset FileDir) +run_cmake_presets(FileDirv12 --preset FileDirExt) +run_cmake_presets(FileDirv12 --preset FileDirInclude) unset(CMakePresets_EXTRA_FILES) set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/FileDirFuture.json.in") run_cmake_presets(FileDirFuture) diff --git a/Tests/RunCMake/CMakePresets/subdir/FileDirInclude.json.in b/Tests/RunCMake/CMakePresets/subdir/FileDirInclude.json.in new file mode 100644 index 0000000000..f7dd5972a3 --- /dev/null +++ b/Tests/RunCMake/CMakePresets/subdir/FileDirInclude.json.in @@ -0,0 +1,13 @@ +{ + "version": 9, + "configurePresets": [ + { + "name": "FileDirInclude", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${fileDir}/build", + "cacheVariables": { + "PRESET": "FileDirInclude" + } + } + ] +} diff --git a/Tests/RunCMake/CMakePresets/subdir/FileDirv12.json.in b/Tests/RunCMake/CMakePresets/subdir/FileDirv12.json.in new file mode 100644 index 0000000000..ed20764f81 --- /dev/null +++ b/Tests/RunCMake/CMakePresets/subdir/FileDirv12.json.in @@ -0,0 +1,17 @@ +{ + "version": 12, + "include": [ + "${fileDir}/FileDirInclude.json" + ], + "configurePresets": [ + { + "name": "FileDir", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${fileDir}/build", + "cacheVariables": { + "TEST_FILE_DIR": "${fileDir}", + "PRESET": "FileDir" + } + } + ] +} diff --git a/Tests/RunCMake/CMakePresets/subdir/FileDir.json.in b/Tests/RunCMake/CMakePresets/subdir/FileDirv4.json.in similarity index 60% rename from Tests/RunCMake/CMakePresets/subdir/FileDir.json.in rename to Tests/RunCMake/CMakePresets/subdir/FileDirv4.json.in index 00282a7b81..e055c4e30e 100644 --- a/Tests/RunCMake/CMakePresets/subdir/FileDir.json.in +++ b/Tests/RunCMake/CMakePresets/subdir/FileDirv4.json.in @@ -4,9 +4,10 @@ { "name": "FileDir", "generator": "@RunCMake_GENERATOR@", - "binaryDir": "${sourceDir}/build", + "binaryDir": "${fileDir}/build", "cacheVariables": { - "TEST_FILE_DIR": "${fileDir}" + "TEST_FILE_DIR": "${fileDir}", + "PRESET": "FileDir" } } ]