Browse Source

file(REAL_PATH): resolve symlinks before '..' components

Previously REAL_PATH would collapse paths before resolving any symlinks
so if `..` crossed a symlink the output from `REAL_PATH` would be wrong.

It looked like REAL_PATH did this by mistake since it was a side-effect
of ensuring we had an absolute path before resolving symlinks.
Robert Maynard 2 years ago
parent
commit
6b5f2dbbfe

+ 5 - 0
Help/command/file.rst

@@ -981,6 +981,11 @@ Path Conversion
       if ``USERPROFILE`` is not defined.  On all other platforms, only ``HOME``
       is used.
 
+  .. versionchanged:: 3.28
+
+    All symlinks are resolved before collapsing ``../`` components.
+    See policy :policy:`CMP0152`.
+
 .. signature::
   file(RELATIVE_PATH <variable> <directory> <file>)
 

+ 8 - 0
Help/manual/cmake-policies.7.rst

@@ -51,6 +51,14 @@ The :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` variable may also be used
 to determine whether to report an error on use of deprecated macros or
 functions.
 
+Policies Introduced by CMake 3.28
+=================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0152: file(REAL_PATH) resolves symlinks before collapsing ../ components.  </policy/CMP0152>
+
 Policies Introduced by CMake 3.27
 =================================
 

+ 20 - 0
Help/policy/CMP0152.rst

@@ -0,0 +1,20 @@
+CMP0152
+-------
+
+.. versionadded:: 3.28
+
+:command:`file(REAL_PATH)` resolves symlinks before collapsing ../ components.
+
+In CMake 3.27 and below, :command:`file(REAL_PATH)` collapsed any ``../``
+components in a path before resolving symlinks.  This produced incorrect
+results when the ``../`` collapsed away a symlink.
+
+The ``OLD`` behavior for this policy is to collapse ``../`` components before
+resolving symlinks.
+The ``NEW`` behavior for this policy is to resolve all symlinks before
+collapsing ``../`` components.
+
+This policy was introduced in CMake version 3.28.  Use the
+:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
+
+.. include:: DEPRECATED.txt

+ 9 - 6
Modules/FindCUDAToolkit.cmake

@@ -965,12 +965,15 @@ if(CUDAToolkit_FOUND)
   # search paths without symlinks
   if(CUDAToolkit_LIBRARY_DIR  MATCHES ".*/cuda/${CUDAToolkit_VERSION_MAJOR}.${CUDAToolkit_VERSION_MINOR}/lib64$")
     # Search location for math_libs/
-    file(REAL_PATH "${CUDAToolkit_LIBRARY_DIR}/../../../" _cmake_search_dir)
-    list(APPEND CUDAToolkit_LIBRARY_SEARCH_DIRS "${_cmake_search_dir}")
-
-    # Search location for extras like cupti
-    file(REAL_PATH "${CUDAToolkit_LIBRARY_DIR}/../" _cmake_search_dir)
-    list(APPEND CUDAToolkit_LIBRARY_SEARCH_DIRS "${_cmake_search_dir}")
+    block(SCOPE_FOR POLICIES)
+      cmake_policy(SET CMP0152 NEW)
+      file(REAL_PATH "${CUDAToolkit_LIBRARY_DIR}/../../../../../" _cmake_search_dir)
+      list(APPEND CUDAToolkit_LIBRARY_SEARCH_DIRS "${_cmake_search_dir}")
+
+      # Search location for extras like cupti
+      file(REAL_PATH "${CUDAToolkit_LIBRARY_DIR}/../../../" _cmake_search_dir)
+      list(APPEND CUDAToolkit_LIBRARY_SEARCH_DIRS "${_cmake_search_dir}")
+    endblock()
   endif()
 
   # If no `CUDAToolkit_LIBRARY_ROOT` exists set it based on CUDAToolkit_LIBRARY_DIR

+ 53 - 3
Source/cmFileCommand.cxx

@@ -30,6 +30,7 @@
 
 #include "cmArgumentParser.h"
 #include "cmArgumentParserTypes.h"
+#include "cmCMakePath.h"
 #include "cmCryptoHash.h"
 #include "cmELF.h"
 #include "cmExecutionStatus.h"
@@ -1278,9 +1279,58 @@ bool HandleRealPathCommand(std::vector<std::string> const& args,
     }
   }
 
-  auto realPath =
-    cmSystemTools::CollapseFullPath(input, *arguments.BaseDirectory);
-  realPath = cmSystemTools::GetRealPath(realPath);
+  bool warnAbout152 = false;
+  bool use152New = true;
+  cmPolicies::PolicyStatus policyStatus =
+    status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0152);
+  switch (policyStatus) {
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+    case cmPolicies::NEW:
+      break;
+    case cmPolicies::WARN:
+      use152New = false;
+      warnAbout152 = true;
+      break;
+    case cmPolicies::OLD:
+      use152New = false;
+      warnAbout152 = false;
+      break;
+  }
+
+  auto computeNewPath = [=](std::string const& in, std::string& result) {
+    auto path = cmCMakePath{ in };
+    if (path.IsRelative()) {
+      auto basePath = cmCMakePath{ *arguments.BaseDirectory };
+      path = basePath.Append(path);
+    }
+    result = cmSystemTools::GetActualCaseForPath(
+      cmSystemTools::GetRealPath(path.String()));
+  };
+
+  std::string realPath;
+  if (use152New) {
+    computeNewPath(input, realPath);
+  } else {
+    std::string oldPolicyPath =
+      cmSystemTools::CollapseFullPath(input, *arguments.BaseDirectory);
+    oldPolicyPath = cmSystemTools::GetRealPath(oldPolicyPath);
+    if (warnAbout152) {
+      computeNewPath(input, realPath);
+      if (oldPolicyPath != realPath) {
+        status.GetMakefile().IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(
+            cmPolicies::GetPolicyWarning(cmPolicies::CMP0152), '\n',
+            "From input path:\n  ", input,
+            "\nthe policy OLD behavior produces path:\n  ", oldPolicyPath,
+            "\nbut the policy NEW behavior produces path:\n  ", realPath,
+            "\nSince the policy is not set, CMake is using the OLD "
+            "behavior for compatibility."));
+      }
+    }
+    realPath = oldPolicyPath;
+  }
 
   status.GetMakefile().AddDefinition(args[2], realPath);
 

+ 5 - 1
Source/cmPolicies.h

@@ -459,7 +459,11 @@ class cmMakefile;
   SELECT(POLICY, CMP0151,                                                     \
          "AUTOMOC include directory is a system include directory by "        \
          "default.",                                                          \
-         3, 27, 0, cmPolicies::WARN)
+         3, 27, 0, cmPolicies::WARN)                                          \
+  SELECT(                                                                     \
+    POLICY, CMP0152,                                                          \
+    "file(REAL_PATH) resolves symlinks before collapsing ../ components.", 3, \
+    28, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 5 - 0
Tests/RunCMake/CMP0152/CMP0152-Common.cmake

@@ -0,0 +1,5 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/")
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/")
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin/")
+file(CREATE_LINK  "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin" "${CMAKE_CURRENT_BINARY_DIR}/dir/bin" SYMBOLIC)
+file(REAL_PATH "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/../" real_path)

+ 0 - 0
Tests/RunCMake/CMP0152/CMP0152-NEW-stdout.txt


+ 2 - 0
Tests/RunCMake/CMP0152/CMP0152-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0152 NEW)
+include(CMP0152-Common.cmake)

+ 0 - 0
Tests/RunCMake/CMP0152/CMP0152-OLD-stderr.txt


+ 2 - 0
Tests/RunCMake/CMP0152/CMP0152-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0152 OLD)
+include(CMP0152-Common.cmake)

+ 27 - 0
Tests/RunCMake/CMP0152/CMP0152-WARN-stderr.txt

@@ -0,0 +1,27 @@
+^CMake Warning \(dev\) at CMP0152-Common\.cmake:[0-9]+ \(file\):
+  Policy CMP0152 is not set: file\(REAL_PATH\) resolves symlinks before
+  collapsing \.\./ components\.  Run "cmake --help-policy CMP0152" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+
+  From input path:
+
+  [^
+]*/Tests/RunCMake/CMP0152/CMP0152-WARN-build/dir/bin/\.\./
+
+  the policy OLD behavior produces path:
+
+  [^
+]*/Tests/RunCMake/CMP0152/CMP0152-WARN-build/dir
+
+  but the policy NEW behavior produces path:
+
+  [^
+]*/Tests/RunCMake/CMP0152/CMP0152-WARN-build/dir/nested
+
+  Since the policy is not set, CMake is using the OLD behavior for
+  compatibility.
+Call Stack \(most recent call first\):
+  CMP0152-WARN\.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$

+ 2 - 0
Tests/RunCMake/CMP0152/CMP0152-WARN.cmake

@@ -0,0 +1,2 @@
+
+include(CMP0152-Common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0152/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.23)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 7 - 0
Tests/RunCMake/CMP0152/RunCMakeTest.cmake

@@ -0,0 +1,7 @@
+include(RunCMake)
+
+if(NOT CMAKE_GENERATOR_NO_COMPILER_ENV)
+  run_cmake(CMP0152-WARN)
+  run_cmake(CMP0152-OLD)
+  run_cmake(CMP0152-NEW)
+endif()

+ 4 - 0
Tests/RunCMake/CMakeLists.txt

@@ -165,6 +165,10 @@ if(GIT_EXECUTABLE)
   add_RunCMake_test(CMP0150)
 endif()
 
+if(NOT WIN32 OR CYGWIN)
+  add_RunCMake_test(CMP0152)
+endif()
+
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
 # generators ignore.  The policy will have no effect on those generators.

+ 37 - 0
Tests/RunCMake/file/REAL_PATH.cmake

@@ -13,6 +13,43 @@ if (NOT WIN32 OR CYGWIN)
   if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/test.txt")
     message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/test.txt\"")
   endif()
+
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/")
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/")
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin/")
+  file(CREATE_LINK  "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin" "${CMAKE_CURRENT_BINARY_DIR}/dir/bin" SYMBOLIC)
+
+  cmake_policy(SET CMP0152 NEW)
+  file(REAL_PATH "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/../" real_path)
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir/nested")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir/nested\"")
+  endif()
+
+  file(REAL_PATH "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/../bin" real_path)
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin\"")
+  endif()
+
+  file(REAL_PATH "dir/bin/../bin" real_path BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin\"")
+  endif()
+
+  file(REAL_PATH "../bin" real_path BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/" )
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir/nested/bin\"")
+  endif()
+
+  cmake_policy(SET CMP0152 OLD)
+  file(REAL_PATH "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/../" real_path)
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir/nested\"")
+  endif()
+  file(REAL_PATH "../" real_path BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dir/bin/")
+  if (NOT real_path STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/dir")
+    message(SEND_ERROR "real path is \"${real_path}\", should be \"${CMAKE_CURRENT_BINARY_DIR}/dir\"")
+  endif()
+
 endif()