From b5b72b03783d366b9f908e87a08869d53d75510e Mon Sep 17 00:00:00 2001 From: Brad King Date: Thu, 16 Apr 2026 15:40:59 -0400 Subject: [PATCH 1/2] Tests/RunCMake/file: Enable READ_SYMLINK cases on Windows when possible Probe the filesystem to enable them. Co-authored-by: scivision@users.noreply.github.com --- .../RunCMake/file/READ_SYMLINK-abs-stdout.txt | 1 + Tests/RunCMake/file/READ_SYMLINK-abs.cmake | 1 + Tests/RunCMake/file/READ_SYMLINK-common.cmake | 5 +++ .../file/READ_SYMLINK-noexist-stderr.txt | 8 ++--- .../file/READ_SYMLINK-notsymlink-stderr.txt | 8 ++--- .../RunCMake/file/READ_SYMLINK-rel-stdout.txt | 1 + Tests/RunCMake/file/READ_SYMLINK-rel.cmake | 1 + Tests/RunCMake/file/READ_SYMLINK.cmake | 13 -------- Tests/RunCMake/file/RunCMakeTest.cmake | 33 +++++++++++++++++-- 9 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt create mode 100644 Tests/RunCMake/file/READ_SYMLINK-abs.cmake create mode 100644 Tests/RunCMake/file/READ_SYMLINK-common.cmake create mode 100644 Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt create mode 100644 Tests/RunCMake/file/READ_SYMLINK-rel.cmake delete mode 100644 Tests/RunCMake/file/READ_SYMLINK.cmake diff --git a/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt new file mode 100644 index 0000000000..560d92e523 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt @@ -0,0 +1 @@ +^-- target: '[^']*/Tests/RunCMake/file/READ_SYMLINK-target.txt'$ diff --git a/Tests/RunCMake/file/READ_SYMLINK-abs.cmake b/Tests/RunCMake/file/READ_SYMLINK-abs.cmake new file mode 100644 index 0000000000..6ed242cfd4 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-abs.cmake @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/READ_SYMLINK-common.cmake) diff --git a/Tests/RunCMake/file/READ_SYMLINK-common.cmake b/Tests/RunCMake/file/READ_SYMLINK-common.cmake new file mode 100644 index 0000000000..e6c6b8bbef --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-common.cmake @@ -0,0 +1,5 @@ +file(READ_SYMLINK "${link}" target) +if(CMAKE_HOST_WIN32) + string(REPLACE [[\]] [[/]] target "${target}") +endif() +message(STATUS "target: '${target}'") diff --git a/Tests/RunCMake/file/READ_SYMLINK-noexist-stderr.txt b/Tests/RunCMake/file/READ_SYMLINK-noexist-stderr.txt index 32b3e85257..4673f9a027 100644 --- a/Tests/RunCMake/file/READ_SYMLINK-noexist-stderr.txt +++ b/Tests/RunCMake/file/READ_SYMLINK-noexist-stderr.txt @@ -1,6 +1,6 @@ -^CMake Error at READ_SYMLINK-noexist\.cmake:[0-9]+ \(file\): +^CMake Error at [^ +]*/Tests/RunCMake/file/READ_SYMLINK-noexist\.cmake:[0-9]+ \(file\): file READ_SYMLINK requested of path that is not a symlink: - .*/Tests/RunCMake/file/READ_SYMLINK-noexist-build/rel\.sym -Call Stack \(most recent call first\): - CMakeLists\.txt:[0-9]+ \(include\)$ + [^ +]*/Tests/RunCMake/file/READ_SYMLINK-noexist-build/rel\.sym$ diff --git a/Tests/RunCMake/file/READ_SYMLINK-notsymlink-stderr.txt b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-stderr.txt index 63e32ed676..2486ca32b1 100644 --- a/Tests/RunCMake/file/READ_SYMLINK-notsymlink-stderr.txt +++ b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-stderr.txt @@ -1,6 +1,6 @@ -^CMake Error at READ_SYMLINK-notsymlink\.cmake:[0-9]+ \(file\): +^CMake Error at [^ +]*Tests/RunCMake/file/READ_SYMLINK-notsymlink\.cmake:[0-9]+ \(file\): file READ_SYMLINK requested of path that is not a symlink: - .*/Tests/RunCMake/file/READ_SYMLINK-notsymlink-build/rel\.sym -Call Stack \(most recent call first\): - CMakeLists\.txt:[0-9]+ \(include\)$ + [^ +]*/Tests/RunCMake/file/READ_SYMLINK-notsymlink-build/rel\.sym$ diff --git a/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt new file mode 100644 index 0000000000..8d33973806 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt @@ -0,0 +1 @@ +^-- target: 'READ_SYMLINK-target.txt'$ diff --git a/Tests/RunCMake/file/READ_SYMLINK-rel.cmake b/Tests/RunCMake/file/READ_SYMLINK-rel.cmake new file mode 100644 index 0000000000..6ed242cfd4 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-rel.cmake @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/READ_SYMLINK-common.cmake) diff --git a/Tests/RunCMake/file/READ_SYMLINK.cmake b/Tests/RunCMake/file/READ_SYMLINK.cmake deleted file mode 100644 index 865a2e931e..0000000000 --- a/Tests/RunCMake/file/READ_SYMLINK.cmake +++ /dev/null @@ -1,13 +0,0 @@ -execute_process(COMMAND - ${CMAKE_COMMAND} -E create_symlink "test.txt" "${CMAKE_CURRENT_BINARY_DIR}/rel.sym") -file(READ_SYMLINK "${CMAKE_CURRENT_BINARY_DIR}/rel.sym" result) -if(NOT result STREQUAL "test.txt") - message(SEND_ERROR "Relative symlink is \"${result}\", should be \"test.txt\"") -endif() - -execute_process(COMMAND - ${CMAKE_COMMAND} -E create_symlink "${CMAKE_CURRENT_BINARY_DIR}/test.txt" "${CMAKE_CURRENT_BINARY_DIR}/abs.sym") -file(READ_SYMLINK "${CMAKE_CURRENT_BINARY_DIR}/abs.sym" result) -if(NOT result MATCHES "^.*/Tests/RunCMake/file/READ_SYMLINK-build/test\\.txt$") - message(SEND_ERROR "Absolute symlink is \"${result}\", should be \"*/Tests/RunCMake/file/READ_SYMLINK-build/test.txt\"") -endif() diff --git a/Tests/RunCMake/file/RunCMakeTest.cmake b/Tests/RunCMake/file/RunCMakeTest.cmake index f9c9c2322f..764f2bfbf0 100644 --- a/Tests/RunCMake/file/RunCMakeTest.cmake +++ b/Tests/RunCMake/file/RunCMakeTest.cmake @@ -86,9 +86,6 @@ if(NOT WIN32 ) run_cmake(GLOB_RECURSE-cyclic-recursion) run_cmake(INSTALL-SYMLINK) - run_cmake(READ_SYMLINK) - run_cmake(READ_SYMLINK-noexist) - run_cmake(READ_SYMLINK-notsymlink) if(NOT CYGWIN) run_cmake(INSTALL-FOLLOW_SYMLINK_CHAIN) endif() @@ -96,6 +93,36 @@ if(NOT WIN32 run_cmake(LOCK-symlink-no-truncate) endif() +# Try creating symbolic links to read below. +# If this fails on the current filesystem, we'll skip those tests. +block(PROPAGATE READ_SYMLINK-link-rel READ_SYMLINK-link-abs) + set(READ_SYMLINK-dest-rel "READ_SYMLINK-target.txt") + set(READ_SYMLINK-dest-abs "${RunCMake_BINARY_DIR}/${READ_SYMLINK-dest-rel}") + file(WRITE "${READ_SYMLINK-dest-abs}" "") + foreach(link IN ITEMS rel abs) + set(READ_SYMLINK-link-${link} "${RunCMake_BINARY_DIR}/READ_SYMLINK-link-${link}.sym") + file(REMOVE "${READ_SYMLINK-link-${link}}") + execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink "${READ_SYMLINK-dest-${link}}" "${READ_SYMLINK-link-${link}}" + OUTPUT_VARIABLE create_symlink_stdout + ERROR_VARIABLE create_symlink_stderr + RESULT_VARIABLE create_symlink_result + ) + if(NOT create_symlink_result EQUAL 0 OR NOT EXISTS "${READ_SYMLINK-link-${link}}") + set(READ_SYMLINK-link-${link} "") + endif() + endforeach() +endblock() + +if(READ_SYMLINK-link-rel) + run_cmake_script(READ_SYMLINK-rel "-Dlink=${READ_SYMLINK-link-rel}") +endif() +if(READ_SYMLINK-link-abs) + run_cmake_script(READ_SYMLINK-abs "-Dlink=${READ_SYMLINK-link-abs}") +endif() +run_cmake_script(READ_SYMLINK-noexist) +run_cmake_script(READ_SYMLINK-notsymlink) + run_cmake(REAL_PATH-non-existing) run_cmake(REAL_PATH-existing) run_cmake(REAL_PATH-unexpected-arg) From 3f3ba04d81e572ce731319481b343ce84d11de61 Mon Sep 17 00:00:00 2001 From: scivision Date: Mon, 13 Apr 2026 21:23:48 -0400 Subject: [PATCH 2/2] file(READ_SYMLINK): Add RESULT option to capture errors Closes: #27753 --- Help/command/file.rst | 16 +++++++--- Help/release/dev/read-symlink-result.rst | 5 +++ Source/cmFileCommand.cxx | 31 +++++++++++++++++-- .../RunCMake/file/READ_SYMLINK-abs-stdout.txt | 3 +- Tests/RunCMake/file/READ_SYMLINK-common.cmake | 3 +- .../READ_SYMLINK-noexist-capture-stdout.txt | 2 ++ .../file/READ_SYMLINK-noexist-capture.cmake | 5 +++ ...READ_SYMLINK-notsymlink-capture-stdout.txt | 2 ++ .../READ_SYMLINK-notsymlink-capture.cmake | 5 +++ .../RunCMake/file/READ_SYMLINK-rel-stdout.txt | 3 +- Tests/RunCMake/file/RunCMakeTest.cmake | 2 ++ 11 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 Help/release/dev/read-symlink-result.rst create mode 100644 Tests/RunCMake/file/READ_SYMLINK-noexist-capture-stdout.txt create mode 100644 Tests/RunCMake/file/READ_SYMLINK-noexist-capture.cmake create mode 100644 Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture-stdout.txt create mode 100644 Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture.cmake diff --git a/Help/command/file.rst b/Help/command/file.rst index 98c71a31e8..42b087161e 100644 --- a/Help/command/file.rst +++ b/Help/command/file.rst @@ -43,7 +43,7 @@ Synopsis file(`COPY_FILE`_ [...]) file({`COPY`_ | `INSTALL`_} ... DESTINATION [...]) file(`SIZE`_ ) - file(`READ_SYMLINK`_ ) + file(`READ_SYMLINK`_ [...]) file(`CREATE_LINK`_ [...]) file(`CHMOD`_ ... ... PERMISSIONS ... [...]) file(`CHMOD_RECURSE`_ ... ... PERMISSIONS ... [...]) @@ -577,13 +577,21 @@ Filesystem pointing to a file and is readable. .. signature:: - file(READ_SYMLINK ) + file(READ_SYMLINK [RESULT ]) .. versionadded:: 3.14 Query the symlink ```` and stores the path it points to - in the result ````. If ```` does not exist - or is not a symlink, CMake issues a fatal error. + in the result ````. + + The options are: + + ``RESULT `` + .. versionadded:: 4.4 + + Capture the status of the operation in a ```` variable. The + variable is set to ``0`` on success or an error message otherwise. + If not specified, and the operation fails, a fatal error is emitted. Note that this command returns the raw symlink path and does not resolve a relative path. The following is an example of how to ensure that an diff --git a/Help/release/dev/read-symlink-result.rst b/Help/release/dev/read-symlink-result.rst new file mode 100644 index 0000000000..62386df827 --- /dev/null +++ b/Help/release/dev/read-symlink-result.rst @@ -0,0 +1,5 @@ +read-symlink-result +------------------- + +* The :command:`file(READ_SYMLINK)` command gained a ``RESULT`` + option to capture errors without failing. diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index dba3d0425c..b0317cc1be 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -3165,7 +3165,7 @@ bool HandleSizeCommand(std::vector const& args, bool HandleReadSymlinkCommand(std::vector const& args, cmExecutionStatus& status) { - if (args.size() != 3) { + if (args.size() < 3) { status.SetError( cmStrCat(args[0], " requires a file name and output variable")); return false; @@ -3174,14 +3174,39 @@ bool HandleReadSymlinkCommand(std::vector const& args, std::string const& filename = args[1]; std::string const& outputVariable = args[2]; + struct Arguments + { + std::string Result; + }; + + static auto const parser = + cmArgumentParser{}.Bind("RESULT"_s, &Arguments::Result); + + std::vector unconsumedArgs; + Arguments const arguments = + parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs); + if (!unconsumedArgs.empty()) { + status.SetError( + cmStrCat("READ_SYMLINK unknown argument:\n ", unconsumedArgs.front())); + return false; + } + std::string result; if (!cmSystemTools::ReadSymlink(filename, result)) { - status.SetError(cmStrCat( - "READ_SYMLINK requested of path that is not a symlink:\n ", filename)); + std::string const error = cmStrCat( + "READ_SYMLINK requested of path that is not a symlink:\n ", filename); + if (!arguments.Result.empty()) { + status.GetMakefile().AddDefinition(arguments.Result, error); + return true; + } + status.SetError(error); return false; } status.GetMakefile().AddDefinition(outputVariable, result); + if (!arguments.Result.empty()) { + status.GetMakefile().AddDefinition(arguments.Result, "0"); + } return true; } diff --git a/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt index 560d92e523..048c98e11e 100644 --- a/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt +++ b/Tests/RunCMake/file/READ_SYMLINK-abs-stdout.txt @@ -1 +1,2 @@ -^-- target: '[^']*/Tests/RunCMake/file/READ_SYMLINK-target.txt'$ +^-- result: '0' +-- target: '[^']*/Tests/RunCMake/file/READ_SYMLINK-target.txt'$ diff --git a/Tests/RunCMake/file/READ_SYMLINK-common.cmake b/Tests/RunCMake/file/READ_SYMLINK-common.cmake index e6c6b8bbef..c8f7ae49a6 100644 --- a/Tests/RunCMake/file/READ_SYMLINK-common.cmake +++ b/Tests/RunCMake/file/READ_SYMLINK-common.cmake @@ -1,5 +1,6 @@ -file(READ_SYMLINK "${link}" target) +file(READ_SYMLINK "${link}" target RESULT result) if(CMAKE_HOST_WIN32) string(REPLACE [[\]] [[/]] target "${target}") endif() +message(STATUS "result: '${result}'") message(STATUS "target: '${target}'") diff --git a/Tests/RunCMake/file/READ_SYMLINK-noexist-capture-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-noexist-capture-stdout.txt new file mode 100644 index 0000000000..94d119c479 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-noexist-capture-stdout.txt @@ -0,0 +1,2 @@ +-- result: 'READ_SYMLINK requested of path that is not a symlink: + [^']*/Tests/RunCMake/file/READ_SYMLINK-noexist-capture-build/rel\.sym' diff --git a/Tests/RunCMake/file/READ_SYMLINK-noexist-capture.cmake b/Tests/RunCMake/file/READ_SYMLINK-noexist-capture.cmake new file mode 100644 index 0000000000..69947a0448 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-noexist-capture.cmake @@ -0,0 +1,5 @@ +file(READ_SYMLINK "${CMAKE_CURRENT_BINARY_DIR}/rel.sym" target RESULT result) +message(STATUS "result: '${result}'") +if(DEFINED target) + message(FATAL_ERROR "Target variable should not be defined on failure, got:\n '${target}'") +endif() diff --git a/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture-stdout.txt new file mode 100644 index 0000000000..f27f8df8d6 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture-stdout.txt @@ -0,0 +1,2 @@ +-- result: 'READ_SYMLINK requested of path that is not a symlink: + [^']*/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture-build/rel\.sym' diff --git a/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture.cmake b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture.cmake new file mode 100644 index 0000000000..69947a0448 --- /dev/null +++ b/Tests/RunCMake/file/READ_SYMLINK-notsymlink-capture.cmake @@ -0,0 +1,5 @@ +file(READ_SYMLINK "${CMAKE_CURRENT_BINARY_DIR}/rel.sym" target RESULT result) +message(STATUS "result: '${result}'") +if(DEFINED target) + message(FATAL_ERROR "Target variable should not be defined on failure, got:\n '${target}'") +endif() diff --git a/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt b/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt index 8d33973806..a28693f12e 100644 --- a/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt +++ b/Tests/RunCMake/file/READ_SYMLINK-rel-stdout.txt @@ -1 +1,2 @@ -^-- target: 'READ_SYMLINK-target.txt'$ +^-- result: '0' +-- target: 'READ_SYMLINK-target.txt'$ diff --git a/Tests/RunCMake/file/RunCMakeTest.cmake b/Tests/RunCMake/file/RunCMakeTest.cmake index 764f2bfbf0..7947045868 100644 --- a/Tests/RunCMake/file/RunCMakeTest.cmake +++ b/Tests/RunCMake/file/RunCMakeTest.cmake @@ -121,7 +121,9 @@ if(READ_SYMLINK-link-abs) run_cmake_script(READ_SYMLINK-abs "-Dlink=${READ_SYMLINK-link-abs}") endif() run_cmake_script(READ_SYMLINK-noexist) +run_cmake_script(READ_SYMLINK-noexist-capture) run_cmake_script(READ_SYMLINK-notsymlink) +run_cmake_script(READ_SYMLINK-notsymlink-capture) run_cmake(REAL_PATH-non-existing) run_cmake(REAL_PATH-existing)