瀏覽代碼

cmFileCommand: Add CREATE_LINK subcommand

This brings the functionality of `cmake -E create_symlink` and more to scripts.

The default behavior is to create hard links.
The `SYMBOLIC` argument can be used to create symlinks instead.

The `COPY_ON_ERROR` argument enables a fallback to copying the file in case the link fails.

The `RESULT <var>` retrieves the error message generated by the system.
It is set to "0" on success.

Fixes: #16926
Tushar Maheshwari 6 年之前
父節點
當前提交
81650e488c
共有 4 個文件被更改,包括 143 次插入0 次删除
  1. 121 0
      Source/cmFileCommand.cxx
  2. 1 0
      Source/cmFileCommand.h
  3. 16 0
      Source/cmSystemTools.cxx
  4. 5 0
      Source/cmSystemTools.h

+ 121 - 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,121 @@ 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);
+  } else {
+    completed = cmSystemTools::CreateLink(fileName, newFileName);
+  }
+
+  if (!completed) {
+    // The link method did not succeed. Get the error message.
+    result = "Link failed: " + cmSystemTools::GetLastSystemError();
+
+    // Check if copy-on-error is enabled in the arguments.
+    if (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";
+  }
+
+  if (!resultVar.empty()) {
+    this->Makefile->AddDefinition(resultVar, result.c_str());
+    return true;
+  }
+
+  return completed;
+}

+ 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,

+ 16 - 0
Source/cmSystemTools.cxx

@@ -3134,3 +3134,19 @@ bool cmSystemTools::CreateSymlink(const std::string& origName,
 
   return true;
 }
+
+bool cmSystemTools::CreateLink(const std::string& origName,
+                               const std::string& newName)
+{
+  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);
+    cmSystemTools::Error(e.c_str());
+    return false;
+  }
+
+  return true;
+}

+ 5 - 0
Source/cmSystemTools.h

@@ -530,6 +530,11 @@ public:
   static bool CreateSymlink(const std::string& origName,
                             const std::string& newName);
 
+  /** Create a hard link if the platform supports it.  Returns whether
+      creation succeeded. */
+  static bool CreateLink(const std::string& origName,
+                         const std::string& newName);
+
 private:
   static bool s_ForceUnixPaths;
   static bool s_RunCommandHideConsole;