From 021cf4f4f716e560e418df9230bc034ea1149f0f Mon Sep 17 00:00:00 2001 From: Zack Galbreath Date: Wed, 22 Apr 2026 16:05:23 -0400 Subject: [PATCH] cmake --build --preset: Relax CWD constraint Prior to this commit, cmake --build --preset
 needed to be called
from the source directory. We now look up the source directory in
CMakeCache.txt when a build directory is specified on the command line.

Issue: #27075
---
 Help/manual/cmake.1.rst                       | 18 +++++++----
 Help/release/dev/cmake-build-preset-dir.rst   |  5 +++
 Source/cmake.cxx                              | 14 +++++++--
 ...irectoryOverride-build-override-stderr.txt |  6 ++--
 .../BuildPresetFromBinaryDir.cmake            |  0
 .../CMakePresetsBuild/RunCMakeTest.cmake      | 31 +++++++++++++++++++
 6 files changed, 63 insertions(+), 11 deletions(-)
 create mode 100644 Help/release/dev/cmake-build-preset-dir.rst
 create mode 100644 Tests/RunCMake/CMakePresetsBuild/BuildPresetFromBinaryDir.cmake

diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index 4ce3f7ed21..eef6f6dc39 100644
--- a/Help/manual/cmake.1.rst
+++ b/Help/manual/cmake.1.rst
@@ -778,8 +778,8 @@ project binary tree:
 
 .. code-block:: shell
 
-  cmake --build              [] [-- ]
-  cmake --build --preset  [] [-- ]
+  cmake --build                      [] [-- ]
+  cmake --build [] --preset  [] [-- ]
 
 This abstracts a native build tool's command-line interface with the
 following options:
@@ -795,17 +795,23 @@ following options:
 
   Use a build preset to specify build options. The project binary directory
   is inferred from the ``configurePreset`` key unless a directory is specified
-  after ``--build``. The current working directory must contain CMake preset
-  files. See :manual:`preset ` for more details.
+  after ``--build``.  See :manual:`preset ` for more details.
 
   .. versionadded:: 4.3
     ``cmake --build`` now supports specifying a build directory and
     preset together.
 
+  .. versionchanged:: 4.4
+    ``cmake --build  --preset`` no longer needs to be called from the
+    directory containing the CMake presets files.
+
 .. option:: --list-presets
 
-  Lists the available build presets. The current working directory must
-  contain CMake preset files.
+  Lists the available build presets.
+
+  .. versionchanged:: 4.4
+    ``cmake --build  --list-presets`` no longer needs to be called from
+    the directory containing the CMake presets files.
 
 .. option:: -j [], --parallel []
 
diff --git a/Help/release/dev/cmake-build-preset-dir.rst b/Help/release/dev/cmake-build-preset-dir.rst
new file mode 100644
index 0000000000..4422487c9b
--- /dev/null
+++ b/Help/release/dev/cmake-build-preset-dir.rst
@@ -0,0 +1,5 @@
+cmake-build-preset-dir
+----------------------
+
+* :manual:`cmake(1)`  now supports ``--build  --preset 
``
+  regardless of the current working directory.
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 3598518cd0..d7d7bc1ac3 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -3909,9 +3909,17 @@ int cmake::Build(cmBuildArgs buildArgs, std::vector targets,
 
 #if !defined(CMAKE_BOOTSTRAP)
   if (!presetName.empty() || listPresets) {
-    this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
-    this->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
-
+    // If the binary directory was specified, use it to find
+    // the source directory so we can locate the presets file.
+    if (!buildArgs.binaryDir.empty() &&
+        this->SetDirectoriesFromFile(buildArgs.binaryDir)) {
+      // HomeDirectory is now the source directory (found in CMakeCache.txt)
+    } else {
+      // Otherwise we assume this command was called from the source directory.
+      this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
+      this->SetHomeOutputDirectory(
+        cmSystemTools::GetLogicalWorkingDirectory());
+    }
     cmCMakePresetsGraph settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
     if (result != true) {
diff --git a/Tests/RunCMake/CMakePresetsBuild/BuildDirectoryOverride-build-override-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/BuildDirectoryOverride-build-override-stderr.txt
index fbd9416468..86204c2a50 100644
--- a/Tests/RunCMake/CMakePresetsBuild/BuildDirectoryOverride-build-override-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/BuildDirectoryOverride-build-override-stderr.txt
@@ -1,2 +1,4 @@
-^Error: [^
-]*/Tests/RunCMake/build2 is not a directory$
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/build2:
+File not found: [^
+]*/Tests/RunCMake/build2/CMakePresets.json$
diff --git a/Tests/RunCMake/CMakePresetsBuild/BuildPresetFromBinaryDir.cmake b/Tests/RunCMake/CMakePresetsBuild/BuildPresetFromBinaryDir.cmake
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake
index 5479343721..92378d35e9 100644
--- a/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake
@@ -90,3 +90,34 @@ set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
 
 run_cmake_build_presets(ConfigurePresetUnreachable "x" "x" "")
 set(CMakePresetsBuild_BUILD_ONLY 0)
+
+# Verify that `cmake --build  --preset 
` works when
+# the current working directory is not the source directory.
+block()
+  set(name "BuildPresetFromBinaryDir")
+  set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_TEST_SOURCE_DIR}/build/default")
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  set(CASE_NAME "${name}")
+  set(CASE_SOURCE_DIR "${RunCMake_SOURCE_DIR}")
+  configure_file("${RunCMake_SOURCE_DIR}/CMakeLists.txt.in"
+                 "${RunCMake_TEST_SOURCE_DIR}/CMakeLists.txt" @ONLY)
+  configure_file("${RunCMake_SOURCE_DIR}/ListPresets.json.in"
+                 "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" @ONLY)
+
+  # Configure using the "default" preset.
+  set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}")
+  run_cmake_command("${name}-configure"
+    "${CMAKE_COMMAND}" "--preset" "default")
+
+  # Verify calling `cmake --build  --preset 
` from the
+  # binary directory.
+  set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  run_cmake_command("${name}-build"
+    "${CMAKE_COMMAND}" "--build" "${RunCMake_TEST_BINARY_DIR}"
+    "--preset" "build-default")
+endblock()