presets: Evaluate fileDir macro ahead of schedule

Introduce a mechanism for "immediate" evaluation of preset macros as
they are defined in the current JSON file, rather than deferring until
after the preset graph is merged. This allows `${fileDir}` to always
be expanded with the path to the directory containing the file which
contains the macro, instead of the path being dependent on the preset
in which the macro is used (which could be defined and inherited from
elsewhere).

Bump to presets version 12 to provide backwards compatibility with the
old behavior.

Fixes: #26982
Relates: #27329, #27639
This commit is contained in:
Tyler Yankee committed 2026-03-17 08:16:50 -04:00
1 parent e9abf0c7c8
commit 3c314349d5
16 files changed
+254 -19

No files matched your search

+48 -7
View File
@@ -1257,12 +1257,14 @@ Macro Expansion
^^^^^^^^^^^^^^^
As mentioned above, some fields support macro expansion. Macros are
recognized in the form ``$<macro-namespace>{<macro-name>}``. 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 ``$<macro-namespace>{<macro-name>}``.
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 <ctest --parallel>` with ``<jobs>``
omitted.
``12``
.. versionadded:: 4.4
* Changes to `Macro Expansion`_
* The `${fileDir} <CMakePresets 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
======
+19
View File
@@ -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": [
+7
View File
@@ -0,0 +1,7 @@
presets-fileDir
---------------
* :manual:`cmake-presets(7)` files now support schema version 12. The
:ref:`${fileDir} <CMakePresets 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.
+31
View File
@@ -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 <typename T>
using PresetMacroExpander =
cmCMakePresetsGraphInternal::PresetMacroExpander<T>;
template <typename T>
using ImmediateMacroExpander =
cmCMakePresetsGraphInternal::ImmediateMacroExpander<T>;
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 <typename T>
bool cmCMakePresetsGraphInternal::ExpandImmediateMacros(T& preset)
{
MacroExpanderVector macroExpanders{};
macroExpanders.push_back(cm::make_unique<ImmediateMacroExpander<T>>(preset));
cm::optional<T> 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>(
BuildPreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<TestPreset>(
TestPreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<
PackagePreset>(PackagePreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<
WorkflowPreset>(WorkflowPreset&);
ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacros(
std::string& out, MacroExpanderVector const& macroExpanders, int version)
{
+44
View File
@@ -25,6 +25,7 @@ enum class ExpandMacroResult
{
Ok,
Ignore,
Defer,
Error,
};
@@ -125,6 +126,49 @@ public:
}
};
template <class T>
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 <typename T>
bool ExpandImmediateMacros(T& preset);
extern template bool ExpandImmediateMacros<
cmCMakePresetsGraph::ConfigurePreset>(cmCMakePresetsGraph::ConfigurePreset&);
extern template bool ExpandImmediateMacros<cmCMakePresetsGraph::BuildPreset>(
cmCMakePresetsGraph::BuildPreset&);
extern template bool ExpandImmediateMacros<cmCMakePresetsGraph::TestPreset>(
cmCMakePresetsGraph::TestPreset&);
extern template bool ExpandImmediateMacros<cmCMakePresetsGraph::PackagePreset>(
cmCMakePresetsGraph::PackagePreset&);
extern template bool ExpandImmediateMacros<
cmCMakePresetsGraph::WorkflowPreset>(cmCMakePresetsGraph::WorkflowPreset&);
class NullCondition : public cmCMakePresetsGraph::Condition
{
bool Evaluate(MacroExpanderVector const& /*expanders*/, int /*version*/,
+32 -1
View File
@@ -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<ConfigurePreset>(preset)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name,
&this->parseState);
return false;
}
PresetPair<ConfigurePreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
@@ -642,6 +649,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename,
return false;
}
if (!ExpandImmediateMacros<BuildPreset>(preset)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name,
&this->parseState);
return false;
}
PresetPair<BuildPreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
@@ -666,6 +679,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename,
return false;
}
if (!ExpandImmediateMacros<TestPreset>(preset)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name,
&this->parseState);
return false;
}
PresetPair<TestPreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
@@ -710,6 +729,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename,
return false;
}
if (!ExpandImmediateMacros<PackagePreset>(preset)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name,
&this->parseState);
return false;
}
PresetPair<PackagePreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
@@ -731,6 +756,12 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename,
return false;
}
if (!ExpandImmediateMacros<WorkflowPreset>(preset)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(preset.Name,
&this->parseState);
return false;
}
PresetPair<WorkflowPreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
@@ -1,3 +0,0 @@
include(${CMAKE_CURRENT_LIST_DIR}/TestVariable.cmake)
test_variable(TEST_FILE_DIR "" "${CMAKE_CURRENT_SOURCE_DIR}/subdir")
+10 -1
View File
@@ -1,6 +1,15 @@
{
"version": 4,
"include": [
"subdir/FileDir.json"
"@FileDir_include_subdir@"
],
"configurePresets": [
{
"name": "FileDirExt",
"inherits": "FileDir",
"cacheVariables": {
"PRESET": "FileDirExt"
}
}
]
}
@@ -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()
@@ -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()
@@ -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,
\^$
@@ -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,
\^
+13 -3
View File
@@ -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)
@@ -0,0 +1,13 @@
{
"version": 9,
"configurePresets": [
{
"name": "FileDirInclude",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${fileDir}/build",
"cacheVariables": {
"PRESET": "FileDirInclude"
}
}
]
}
@@ -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"
}
}
]
}
@@ -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"
}
}
]