Explorar o código

Merge topic 'command_file_link'

0f08ed8936 cmSystemTools: Silence CreateLink and CreateSymlink errors
593d986470 Tests: Avoid cross-device links in CREATE_LINK test
9a3d85cfc5 Tests: Skip symlink tests on Windows
e68ea269d7 Tests: CREATE_LINK subcommand negative test case
45aa9c65a1 Tests: file CREATE_LINK subcommand test cases
8bb7562f1a Help: Add documentation for file(CREATE_LINK) subcommand
81650e488c cmFileCommand: Add CREATE_LINK subcommand

Acked-by: Kitware Robot <[email protected]>
Merge-request: !2759
Brad King %!s(int64=7) %!d(string=hai) anos
pai
achega
d704cc3407

+ 23 - 0
Help/command/file.rst

@@ -27,6 +27,7 @@ Synopsis
     file({`COPY`_ | `INSTALL`_} <file>... DESTINATION <dir> [...])
     file(`SIZE`_ <filename> <out-var>)
     file(`READ_SYMLINK`_ <filename> <out-var>)
+    file(`CREATE_LINK`_ <file> <new-file> [...])
 
   `Path Conversion`_
     file(`RELATIVE_PATH`_ <out-var> <directory> <file>)
@@ -368,6 +369,28 @@ could do something like this:
     set(result "${dir}/${result}")
   endif()
 
+.. _CREATE_LINK:
+
+.. code-block:: cmake
+
+  file(CREATE_LINK <file> <new-file>
+       [RESULT <result>] [COPY_ON_ERROR] [SYMBOLIC])
+
+Create a link to ``<file>`` at ``<new-file>``.
+
+It is a hard link by default. This can be changed to symbolic links by
+using ``SYMBOLIC``.  The original file needs to exist for hard links.
+
+The ``<result>`` variable, if specified, gets the status of the operation.
+It is set to ``0`` in case of success. Otherwise, it contains the error
+generated. In case of failures, if ``RESULT`` is not specified, a fatal error
+is emitted.
+
+Specifying ``COPY_ON_ERROR`` enables copying the file as a fallback if
+creating the link fails.
+
+Overwrites the ``<new-file>`` if it exists.
+
 Path Conversion
 ^^^^^^^^^^^^^^^
 

+ 119 - 0
Source/cmFileCommand.cxx

@@ -185,6 +185,9 @@ bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
   if (subCommand == "READ_SYMLINK") {
     return this->HandleReadSymlinkCommand(args);
   }
+  if (subCommand == "CREATE_LINK") {
+    return this->HandleCreateLinkCommand(args);
+  }
 
   std::string e = "does not recognize sub-command " + subCommand;
   this->SetError(e);
@@ -3670,3 +3673,119 @@ bool cmFileCommand::HandleReadSymlinkCommand(
 
   return true;
 }
+
+bool cmFileCommand::HandleCreateLinkCommand(
+  std::vector<std::string> const& args)
+{
+  if (args.size() < 3) {
+    this->SetError("CREATE_LINK must be called with at least two additional "
+                   "arguments");
+    return false;
+  }
+
+  cmCommandArgumentsHelper argHelper;
+  cmCommandArgumentGroup group;
+
+  cmCAString linkArg(&argHelper, "CREATE_LINK");
+  cmCAString fileArg(&argHelper, nullptr);
+  cmCAString newFileArg(&argHelper, nullptr);
+
+  cmCAString resultArg(&argHelper, "RESULT", &group);
+  cmCAEnabler copyOnErrorArg(&argHelper, "COPY_ON_ERROR", &group);
+  cmCAEnabler symbolicArg(&argHelper, "SYMBOLIC", &group);
+
+  linkArg.Follows(nullptr);
+  fileArg.Follows(&linkArg);
+  newFileArg.Follows(&fileArg);
+  group.Follows(&newFileArg);
+
+  std::vector<std::string> unconsumedArgs;
+  argHelper.Parse(&args, &unconsumedArgs);
+
+  if (!unconsumedArgs.empty()) {
+    this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
+    return false;
+  }
+
+  std::string fileName = fileArg.GetString();
+  std::string newFileName = newFileArg.GetString();
+
+  // Output variable for storing the result.
+  const std::string& resultVar = resultArg.GetString();
+
+  // The system error message generated in the operation.
+  std::string result;
+
+  // Check if the paths are distinct.
+  if (fileName == newFileName) {
+    result = "CREATE_LINK cannot use same file and newfile";
+    if (!resultVar.empty()) {
+      this->Makefile->AddDefinition(resultVar, result.c_str());
+      return true;
+    }
+    this->SetError(result);
+    return false;
+  }
+
+  // Hard link requires original file to exist.
+  if (!symbolicArg.IsEnabled() && !cmSystemTools::FileExists(fileName)) {
+    result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
+    if (!resultVar.empty()) {
+      this->Makefile->AddDefinition(resultVar, result.c_str());
+      return true;
+    }
+    this->SetError(result);
+    return false;
+  }
+
+  // Check if the new file already exists and remove it.
+  if ((cmSystemTools::FileExists(newFileName) ||
+       cmSystemTools::FileIsSymlink(newFileName)) &&
+      !cmSystemTools::RemoveFile(newFileName)) {
+    std::ostringstream e;
+    e << "Failed to create link '" << newFileName
+      << "' because existing path cannot be removed: "
+      << cmSystemTools::GetLastSystemError() << "\n";
+
+    if (!resultVar.empty()) {
+      this->Makefile->AddDefinition(resultVar, e.str().c_str());
+      return true;
+    }
+    this->SetError(e.str());
+    return false;
+  }
+
+  // Whether the operation completed successfully.
+  bool completed = false;
+
+  // Check if the command requires a symbolic link.
+  if (symbolicArg.IsEnabled()) {
+    completed = cmSystemTools::CreateSymlink(fileName, newFileName, &result);
+  } else {
+    completed = cmSystemTools::CreateLink(fileName, newFileName, &result);
+  }
+
+  // Check if copy-on-error is enabled in the arguments.
+  if (!completed && copyOnErrorArg.IsEnabled()) {
+    completed =
+      cmSystemTools::cmCopyFile(fileName.c_str(), newFileName.c_str());
+    if (!completed) {
+      result = "Copy failed: " + cmSystemTools::GetLastSystemError();
+    }
+  }
+
+  // Check if the operation was successful.
+  if (completed) {
+    result = "0";
+  } else if (resultVar.empty()) {
+    // The operation failed and the result is not reported in a variable.
+    this->SetError(result);
+    return false;
+  }
+
+  if (!resultVar.empty()) {
+    this->Makefile->AddDefinition(resultVar, result.c_str());
+  }
+
+  return true;
+}

+ 1 - 0
Source/cmFileCommand.h

@@ -61,6 +61,7 @@ protected:
   bool HandleLockCommand(std::vector<std::string> const& args);
   bool HandleSizeCommand(std::vector<std::string> const& args);
   bool HandleReadSymlinkCommand(std::vector<std::string> const& args);
+  bool HandleCreateLinkCommand(std::vector<std::string> const& args);
 
 private:
   void AddEvaluationFile(const std::string& inputName,

+ 28 - 2
Source/cmSystemTools.cxx

@@ -3114,7 +3114,8 @@ std::string cmSystemTools::EncodeURL(std::string const& in, bool escapeSlashes)
 }
 
 bool cmSystemTools::CreateSymlink(const std::string& origName,
-                                  const std::string& newName)
+                                  const std::string& newName,
+                                  std::string* errorMessage)
 {
   uv_fs_t req;
   int flags = 0;
@@ -3128,7 +3129,32 @@ bool cmSystemTools::CreateSymlink(const std::string& origName,
   if (err) {
     std::string e =
       "failed to create symbolic link '" + newName + "': " + uv_strerror(err);
-    cmSystemTools::Error(e.c_str());
+    if (errorMessage) {
+      *errorMessage = std::move(e);
+    } else {
+      cmSystemTools::Error(e.c_str());
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool cmSystemTools::CreateLink(const std::string& origName,
+                               const std::string& newName,
+                               std::string* errorMessage)
+{
+  uv_fs_t req;
+  int err =
+    uv_fs_link(nullptr, &req, origName.c_str(), newName.c_str(), nullptr);
+  if (err) {
+    std::string e =
+      "failed to create link '" + newName + "': " + uv_strerror(err);
+    if (errorMessage) {
+      *errorMessage = std::move(e);
+    } else {
+      cmSystemTools::Error(e.c_str());
+    }
     return false;
   }
 

+ 8 - 1
Source/cmSystemTools.h

@@ -528,7 +528,14 @@ public:
   /** Create a symbolic link if the platform supports it.  Returns whether
       creation succeeded. */
   static bool CreateSymlink(const std::string& origName,
-                            const std::string& newName);
+                            const std::string& newName,
+                            std::string* errorMessage = nullptr);
+
+  /** Create a hard link if the platform supports it.  Returns whether
+      creation succeeded. */
+  static bool CreateLink(const std::string& origName,
+                         const std::string& newName,
+                         std::string* errorMessage = nullptr);
 
 private:
   static bool s_ForceUnixPaths;

+ 11 - 0
Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake

@@ -0,0 +1,11 @@
+# Use COPY_ON_ERROR to handle the case where the source and destination
+# directory are on different devices. Cross-device links are not permitted
+# and the following command falls back to copying the file if link fails.
+file(CREATE_LINK
+  ${CMAKE_CURRENT_LIST_FILE} TestCreateLink.cmake
+  RESULT result
+  COPY_ON_ERROR
+  )
+if(NOT result STREQUAL "0")
+  message(SEND_ERROR "COPY_ON_ERROR failed: '${result}'")
+endif()

+ 4 - 0
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake

@@ -0,0 +1,4 @@
+file(CREATE_LINK does_not_exist.txt TestSymLink.txt RESULT sym_result SYMBOLIC)
+if(NOT sym_result STREQUAL "0")
+  message("Symlink fail: ${sym_result}")
+endif()

+ 4 - 0
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake

@@ -0,0 +1,4 @@
+file(CREATE_LINK ${CMAKE_CURRENT_LIST_FILE} TestSymLink.cmake RESULT sym_result SYMBOLIC)
+if(NOT sym_result STREQUAL "0")
+  message(SEND_ERROR "Symlink result='${sym_result}'")
+endif()

+ 1 - 0
Tests/RunCMake/file/CREATE_LINK-noarg-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at CREATE_LINK-noarg\.cmake:[0-9]+ \(file\):
+  file CREATE_LINK must be called with at least two additional arguments
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 1 - 0
Tests/RunCMake/file/CREATE_LINK-noarg.cmake

@@ -0,0 +1 @@
+file(CREATE_LINK ${CMAKE_CURRENT_LIST_FILE})

+ 1 - 0
Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt

@@ -0,0 +1 @@
+Hard link error: Cannot hard link 'does_not_exist.txt' as it does not exist.

+ 4 - 0
Tests/RunCMake/file/CREATE_LINK-noexist.cmake

@@ -0,0 +1,4 @@
+file(CREATE_LINK does_not_exist.txt TestLink.txt RESULT result)
+if(NOT result STREQUAL "0")
+  message("Hard link error: ${result}")
+endif()

+ 11 - 0
Tests/RunCMake/file/CREATE_LINK.cmake

@@ -0,0 +1,11 @@
+# start with a file in the same directory to avoid cross-device links
+set(test_file ${CMAKE_CURRENT_BINARY_DIR}/CreateLinkTest.txt)
+file(TOUCH ${test_file})
+
+file(CREATE_LINK
+  ${test_file} ${CMAKE_CURRENT_BINARY_DIR}/TestCreateLink.txt
+  RESULT result
+  )
+if(NOT result STREQUAL "0")
+  message(SEND_ERROR "Hard link result='${result}'")
+endif()

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

@@ -1,5 +1,9 @@
 include(RunCMake)
 
+run_cmake(CREATE_LINK)
+run_cmake(CREATE_LINK-COPY_ON_ERROR)
+run_cmake(CREATE_LINK-noarg)
+run_cmake(CREATE_LINK-noexist)
 run_cmake(DOWNLOAD-hash-mismatch)
 run_cmake(DOWNLOAD-unused-argument)
 run_cmake(DOWNLOAD-httpheader-not-set)
@@ -53,6 +57,8 @@ run_cmake_command(GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE ${CMAKE_COMMAND} -P
   ${RunCMake_SOURCE_DIR}/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE.cmake)
 
 if(NOT WIN32 OR CYGWIN)
+  run_cmake(CREATE_LINK-SYMBOLIC)
+  run_cmake(CREATE_LINK-SYMBOLIC-noexist)
   run_cmake(GLOB_RECURSE-cyclic-recursion)
   run_cmake(INSTALL-SYMLINK)
   run_cmake(READ_SYMLINK)