Ver código fonte

file(INSTALL): Add FOLLOW_SYMLINK_CHAIN argument

Kyle Edwards 6 anos atrás
pai
commit
e3ff7ced63

+ 27 - 0
Help/command/file.rst

@@ -311,6 +311,7 @@ Create the given directories and their parents as needed.
        [FILE_PERMISSIONS <permissions>...]
        [DIRECTORY_PERMISSIONS <permissions>...]
        [NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
+       [FOLLOW_SYMLINK_CHAIN]
        [FILES_MATCHING]
        [[PATTERN <pattern> | REGEX <regex>]
         [EXCLUDE] [PERMISSIONS <permissions>...]] [...])
@@ -324,6 +325,32 @@ at the destination with the same timestamp.  Copying preserves input
 permissions unless explicit permissions or ``NO_SOURCE_PERMISSIONS``
 are given (default is ``USE_SOURCE_PERMISSIONS``).
 
+If ``FOLLOW_SYMLINK_CHAIN`` is specified, ``COPY`` will recursively resolve
+the symlinks at the paths given until a real file is found, and install
+a corresponding symlink in the destination for each symlink encountered. For
+each symlink that is installed, the resolution is stripped of the directory,
+leaving only the filename, meaning that the new symlink points to a file in
+the same directory as the symlink. This feature is useful on some Unix systems,
+where libraries are installed as a chain of symlinks with version numbers, with
+less specific versions pointing to more specific versions.
+``FOLLOW_SYMLINK_CHAIN`` will install all of these symlinks and the library
+itself into the destination directory. For example, if you have the following
+directory structure:
+
+* ``/opt/foo/lib/libfoo.so.1.2.3``
+* ``/opt/foo/lib/libfoo.so.1.2 -> libfoo.so.1.2.3``
+* ``/opt/foo/lib/libfoo.so.1 -> libfoo.so.1.2``
+* ``/opt/foo/lib/libfoo.so -> libfoo.so.1``
+
+and you do:
+
+.. code-block:: cmake
+
+  file(COPY /opt/foo/lib/libfoo.so DESTINATION lib FOLLOW_SYMLINK_CHAIN)
+
+This will install all of the symlinks and ``libfoo.so.1.2.3`` itself into
+``lib``.
+
 See the :command:`install(DIRECTORY)` command for documentation of
 permissions, ``FILES_MATCHING``, ``PATTERN``, ``REGEX``, and
 ``EXCLUDE`` options.  Copying directories preserves the structure

+ 6 - 0
Help/release/dev/file-install-follow-symlink-chain.rst

@@ -0,0 +1,6 @@
+file-install-follow-symlink-chain
+---------------------------------
+
+* The :command:`file(INSTALL)` command learned a new argument,
+  ``FOLLOW_SYMLINK_CHAIN``, which can be used to recursively resolve and
+  install symlinks.

+ 64 - 7
Source/cmFileCopier.cxx

@@ -31,6 +31,7 @@ cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name)
   , UseGivenPermissionsFile(false)
   , UseGivenPermissionsDir(false)
   , UseSourcePermissions(true)
+  , FollowSymlinkChain(false)
   , Doing(DoingNone)
 {
 }
@@ -249,6 +250,9 @@ bool cmFileCopier::CheckKeyword(std::string const& arg)
     this->Doing = DoingPattern;
   } else if (arg == "REGEX") {
     this->Doing = DoingRegex;
+  } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
+    this->FollowSymlinkChain = true;
+    this->Doing = DoingNone;
   } else if (arg == "EXCLUDE") {
     // Add this property to the current match rule.
     if (this->CurrentMatchRule) {
@@ -464,16 +468,69 @@ bool cmFileCopier::Install(const std::string& fromFile,
   if (cmSystemTools::SameFile(fromFile, toFile)) {
     return true;
   }
-  if (cmSystemTools::FileIsSymlink(fromFile)) {
-    return this->InstallSymlink(fromFile, toFile);
+
+  std::string newFromFile = fromFile;
+  std::string newToFile = toFile;
+
+  if (this->FollowSymlinkChain &&
+      !this->InstallSymlinkChain(newFromFile, newToFile)) {
+    return false;
   }
-  if (cmSystemTools::FileIsDirectory(fromFile)) {
-    return this->InstallDirectory(fromFile, toFile, match_properties);
+
+  if (cmSystemTools::FileIsSymlink(newFromFile)) {
+    return this->InstallSymlink(newFromFile, newToFile);
   }
-  if (cmSystemTools::FileExists(fromFile)) {
-    return this->InstallFile(fromFile, toFile, match_properties);
+  if (cmSystemTools::FileIsDirectory(newFromFile)) {
+    return this->InstallDirectory(newFromFile, newToFile, match_properties);
   }
-  return this->ReportMissing(fromFile);
+  if (cmSystemTools::FileExists(newFromFile)) {
+    return this->InstallFile(newFromFile, newToFile, match_properties);
+  }
+  return this->ReportMissing(newFromFile);
+}
+
+bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
+                                       std::string& toFile)
+{
+  std::string newFromFile;
+  std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
+  while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
+    if (!cmSystemTools::FileIsFullPath(newFromFile)) {
+      std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
+      newFromFile = fromFilePath + "/" + newFromFile;
+    }
+
+    std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
+
+    bool copy = true;
+    if (!this->Always) {
+      std::string oldSymlinkTarget;
+      if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+        if (symlinkTarget == oldSymlinkTarget) {
+          copy = false;
+        }
+      }
+    }
+
+    this->ReportCopy(toFile, TypeLink, copy);
+
+    if (copy) {
+      cmSystemTools::RemoveFile(toFile);
+      cmSystemTools::MakeDirectory(toFilePath);
+
+      if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
+        std::ostringstream e;
+        e << this->Name << " cannot create symlink \"" << toFile << "\".";
+        this->FileCommand->SetError(e.str());
+        return false;
+      }
+    }
+
+    fromFile = newFromFile;
+    toFile = toFilePath + "/" + symlinkTarget;
+  }
+
+  return true;
 }
 
 bool cmFileCopier::InstallSymlink(const std::string& fromFile,

+ 2 - 0
Source/cmFileCopier.h

@@ -64,6 +64,7 @@ protected:
   // Translate an argument to a permissions bit.
   bool CheckPermissions(std::string const& arg, mode_t& permissions);
 
+  bool InstallSymlinkChain(std::string& fromFile, std::string& toFile);
   bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
   bool InstallFile(const std::string& fromFile, const std::string& toFile,
                    MatchProperties match_properties);
@@ -86,6 +87,7 @@ protected:
   bool UseGivenPermissionsFile;
   bool UseGivenPermissionsDir;
   bool UseSourcePermissions;
+  bool FollowSymlinkChain;
   std::string Destination;
   std::string FilesFromDir;
   std::vector<std::string> Files;

+ 168 - 0
Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN.cmake

@@ -0,0 +1,168 @@
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/dest1")
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file1.txt")
+file(CREATE_LINK file1.txt "${CMAKE_BINARY_DIR}/file1.txt.sym" SYMBOLIC)
+file(TOUCH "${CMAKE_BINARY_DIR}/dest1/file1.txt.sym")
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file2.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file2")
+file(CREATE_LINK ../file2.txt "${CMAKE_BINARY_DIR}/file2/file2.txt.sym" SYMBOLIC)
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file3.txt")
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file3.txt" "${CMAKE_BINARY_DIR}/file3.txt.sym" SYMBOLIC)
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file4.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file4")
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file4.txt" "${CMAKE_BINARY_DIR}/file4/file4.txt.sym" SYMBOLIC)
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file5.txt")
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file6.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file6/file6")
+file(CREATE_LINK file6.txt "${CMAKE_BINARY_DIR}/file6.txt.sym.1" SYMBOLIC)
+file(CREATE_LINK ../file6.txt.sym.1 "${CMAKE_BINARY_DIR}/file6/file6.txt.sym.2" SYMBOLIC)
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file6/file6.txt.sym.2" "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.3" SYMBOLIC)
+file(CREATE_LINK file6.txt.sym.3 "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4" SYMBOLIC)
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file7.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file7")
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file8.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file8")
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file8/../file8.txt" "${CMAKE_BINARY_DIR}/file8/file8.txt.sym" SYMBOLIC)
+
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file9")
+file(TOUCH "${CMAKE_BINARY_DIR}/file9/file9.txt")
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file9" "${CMAKE_BINARY_DIR}/file9.sym" SYMBOLIC)
+
+file(TOUCH "${CMAKE_BINARY_DIR}/file10.txt")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file10")
+file(CREATE_LINK "." "${CMAKE_BINARY_DIR}/file10/file10" SYMBOLIC)
+file(CREATE_LINK "${CMAKE_BINARY_DIR}/file10/file10/../file10.txt" "${CMAKE_BINARY_DIR}/file10/file10.txt.sym" SYMBOLIC)
+
+file(INSTALL
+  "${CMAKE_BINARY_DIR}/file1.txt.sym"
+  DESTINATION "${CMAKE_BINARY_DIR}/dest1"
+  FOLLOW_SYMLINK_CHAIN
+  )
+
+file(INSTALL
+  "${CMAKE_BINARY_DIR}/file1.txt.sym"
+  "${CMAKE_BINARY_DIR}/file2/file2.txt.sym"
+  "${CMAKE_BINARY_DIR}/file3.txt.sym"
+  "${CMAKE_BINARY_DIR}/file4/file4.txt.sym"
+  "${CMAKE_BINARY_DIR}/file5.txt"
+  "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4"
+  "${CMAKE_BINARY_DIR}/file8/file8.txt.sym"
+  "${CMAKE_BINARY_DIR}/file7/../file7.txt"
+  "${CMAKE_BINARY_DIR}/file8.txt"
+  "${CMAKE_BINARY_DIR}/file9.sym/file9.txt"
+  "${CMAKE_BINARY_DIR}/file10/file10/file10.txt.sym"
+  DESTINATION "${CMAKE_BINARY_DIR}/dest2"
+  FOLLOW_SYMLINK_CHAIN
+  )
+
+set(resolved_file1.txt.sym file1.txt)
+set(resolved_file10.txt.sym file10.txt)
+set(resolved_file2.txt.sym file2.txt)
+set(resolved_file3.txt.sym file3.txt)
+set(resolved_file4.txt.sym file4.txt)
+set(resolved_file6.txt.sym.1 file6.txt)
+set(resolved_file6.txt.sym.2 file6.txt.sym.1)
+set(resolved_file6.txt.sym.3 file6.txt.sym.2)
+set(resolved_file6.txt.sym.4 file6.txt.sym.3)
+set(resolved_file8.txt.sym file8.txt)
+set(syms)
+foreach(f
+  file1.txt
+  file1.txt.sym
+  file10.txt
+  file10.txt.sym
+  file2.txt
+  file2.txt.sym
+  file3.txt
+  file3.txt.sym
+  file4.txt
+  file4.txt.sym
+  file5.txt
+  file6.txt
+  file6.txt.sym.1
+  file6.txt.sym.2
+  file6.txt.sym.3
+  file6.txt.sym.4
+  file7.txt
+  file8.txt
+  file8.txt.sym
+  file9.txt
+  )
+  string(REPLACE "." "\\." r "${f}")
+  list(APPEND syms "[^;]*/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/dest2/${r}")
+  set(filename "${CMAKE_BINARY_DIR}/dest2/${f}")
+  if(DEFINED resolved_${f})
+    file(READ_SYMLINK "${filename}" resolved)
+    if(NOT resolved STREQUAL "${resolved_${f}}")
+      message(SEND_ERROR "Expected symlink resolution for ${f}: ${resolved_${f}}\nActual resolution: ${resolved}")
+    endif()
+  elseif(NOT EXISTS "${filename}" OR IS_SYMLINK "${filename}" OR IS_DIRECTORY "${filename}")
+    message(SEND_ERROR "${f} should be a regular file")
+  endif()
+endforeach()
+
+file(GLOB_RECURSE actual_syms LIST_DIRECTORIES true "${CMAKE_BINARY_DIR}/dest2/*")
+if(NOT actual_syms MATCHES "^${syms}$")
+  message(SEND_ERROR "Expected files:\n\n  ^${syms}$\n\nActual files:\n\n  ${actual_syms}")
+endif()
+
+file(INSTALL
+  "${CMAKE_BINARY_DIR}/file1.txt.sym"
+  "${CMAKE_BINARY_DIR}/file2/file2.txt.sym"
+  "${CMAKE_BINARY_DIR}/file3.txt.sym"
+  "${CMAKE_BINARY_DIR}/file4/file4.txt.sym"
+  "${CMAKE_BINARY_DIR}/file5.txt"
+  "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4"
+  "${CMAKE_BINARY_DIR}/file8/file8.txt.sym"
+  "${CMAKE_BINARY_DIR}/file7/../file7.txt"
+  "${CMAKE_BINARY_DIR}/file8.txt"
+  "${CMAKE_BINARY_DIR}/file9.sym/file9.txt"
+  "${CMAKE_BINARY_DIR}/file10/file10/file10.txt.sym"
+  DESTINATION "${CMAKE_BINARY_DIR}/dest3"
+  )
+
+set(resolved_file1.txt.sym [[^file1\.txt$]])
+set(resolved_file10.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file10/file10/\.\./file10\.txt$]])
+set(resolved_file2.txt.sym [[^\.\./file2\.txt$]])
+set(resolved_file3.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file3\.txt$]])
+set(resolved_file4.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file4\.txt$]])
+set(resolved_file6.txt.sym.4 [[^file6\.txt\.sym\.3$]])
+set(resolved_file8.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file8/\.\./file8\.txt$]])
+set(syms)
+foreach(f
+  file1.txt.sym
+  file10.txt.sym
+  file2.txt.sym
+  file3.txt.sym
+  file4.txt.sym
+  file5.txt
+  file6.txt.sym.4
+  file7.txt
+  file8.txt
+  file8.txt.sym
+  file9.txt
+  )
+  string(REPLACE "." "\\." r "${f}")
+  list(APPEND syms "[^;]*/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/dest3/${r}")
+  set(filename "${CMAKE_BINARY_DIR}/dest3/${f}")
+  if(DEFINED resolved_${f})
+    file(READ_SYMLINK "${filename}" resolved)
+    if(NOT resolved MATCHES "${resolved_${f}}")
+      message(SEND_ERROR "Expected symlink resolution for ${f}: ${resolved_${f}}\nActual resolution: ${resolved}")
+    endif()
+  elseif(NOT EXISTS "${filename}" OR IS_SYMLINK "${filename}" OR IS_DIRECTORY "${filename}")
+    message(SEND_ERROR "${f} should be a regular file")
+  endif()
+endforeach()
+
+file(GLOB_RECURSE actual_syms LIST_DIRECTORIES true "${CMAKE_BINARY_DIR}/dest3/*")
+if(NOT actual_syms MATCHES "^${syms}$")
+  message(SEND_ERROR "Expected files:\n\n  ^${syms}$\n\nActual files:\n\n  ${actual_syms}")
+endif()

+ 1 - 0
Tests/RunCMake/file/RunCMakeTest.cmake

@@ -64,6 +64,7 @@ if(NOT WIN32 OR CYGWIN)
   run_cmake(READ_SYMLINK)
   run_cmake(READ_SYMLINK-noexist)
   run_cmake(READ_SYMLINK-notsymlink)
+  run_cmake(INSTALL-FOLLOW_SYMLINK_CHAIN)
 endif()
 
 if(RunCMake_GENERATOR STREQUAL "Ninja")