Browse Source

file(RENAME): Add option to capture error message on failure

Brad King 4 years ago
parent
commit
c61292726c

+ 9 - 2
Help/command/file.rst

@@ -38,7 +38,7 @@ Synopsis
 
   `Filesystem`_
     file({`GLOB`_ | `GLOB_RECURSE`_} <out-var> [...] [<globbing-expr>...])
-    file(`RENAME`_ <oldname> <newname>)
+    file(`RENAME`_ <oldname> <newname> [...])
     file({`REMOVE`_ | `REMOVE_RECURSE`_ } [<files>...])
     file(`MAKE_DIRECTORY`_ [<dir>...])
     file({`COPY`_ | `INSTALL`_} <file>... DESTINATION <dir> [...])
@@ -665,11 +665,18 @@ Examples of recursive globbing include::
 
 .. code-block:: cmake
 
-  file(RENAME <oldname> <newname>)
+  file(RENAME <oldname> <newname>
+       [RESULT <result>])
 
 Move a file or directory within a filesystem from ``<oldname>`` to
 ``<newname>``, replacing the destination atomically.
 
+The options are:
+
+``RESULT <result>``
+  Set ``<result>`` variable to ``0`` on success or an error message otherwise.
+  If ``RESULT`` is not specified and the operation fails, an error is emitted.
+
 .. _REMOVE:
 .. _REMOVE_RECURSE:
 

+ 5 - 0
Help/release/dev/file-RENAME.rst

@@ -0,0 +1,5 @@
+file-RENAME
+-----------
+
+* The :command:`file(RENAME)` command learned to optionally capture
+  failure in a result variable.

+ 34 - 7
Source/cmFileCommand.cxx

@@ -1313,8 +1313,9 @@ bool HandleRelativePathCommand(std::vector<std::string> const& args,
 bool HandleRename(std::vector<std::string> const& args,
                   cmExecutionStatus& status)
 {
-  if (args.size() != 3) {
-    status.SetError("RENAME given incorrect number of arguments.");
+  if (args.size() < 3) {
+    status.SetError("RENAME must be called with at least two additional "
+                    "arguments");
     return false;
   }
 
@@ -1330,13 +1331,39 @@ bool HandleRename(std::vector<std::string> const& args,
       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
   }
 
-  if (!cmSystemTools::RenameFile(oldname, newname)) {
-    std::string err = cmSystemTools::GetLastSystemError();
-    status.SetError(cmStrCat("RENAME failed to rename\n  ", oldname,
-                             "\nto\n  ", newname, "\nbecause: ", err, "\n"));
+  struct Arguments
+  {
+    std::string Result;
+  };
+
+  static auto const parser =
+    cmArgumentParser<Arguments>{}.Bind("RESULT"_s, &Arguments::Result);
+
+  std::vector<std::string> unconsumedArgs;
+  Arguments const arguments =
+    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
+  if (!unconsumedArgs.empty()) {
+    status.SetError("RENAME unknown argument:\n  " + unconsumedArgs.front());
     return false;
   }
-  return true;
+
+  std::string err;
+  switch (cmSystemTools::RenameFile(oldname, newname, &err)) {
+    case cmSystemTools::RenameResult::Success:
+      if (!arguments.Result.empty()) {
+        status.GetMakefile().AddDefinition(arguments.Result, "0");
+      }
+      return true;
+    case cmSystemTools::RenameResult::Failure:
+      if (!arguments.Result.empty()) {
+        status.GetMakefile().AddDefinition(arguments.Result, err);
+        return true;
+      }
+      break;
+  }
+  status.SetError(cmStrCat("RENAME failed to rename\n  ", oldname, "\nto\n  ",
+                           newname, "\nbecause: ", err, "\n"));
+  return false;
 }
 
 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,

+ 1 - 0
Tests/RunCMake/file/RENAME-arg-missing-result.txt

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

+ 3 - 0
Tests/RunCMake/file/RENAME-arg-missing-stderr.txt

@@ -0,0 +1,3 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/RENAME-arg-missing.cmake:1 \(file\):
+  file RENAME must be called with at least two additional arguments$

+ 1 - 0
Tests/RunCMake/file/RENAME-arg-missing.cmake

@@ -0,0 +1 @@
+file(RENAME "old")

+ 1 - 0
Tests/RunCMake/file/RENAME-arg-unknown-result.txt

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

+ 5 - 0
Tests/RunCMake/file/RENAME-arg-unknown-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/RENAME-arg-unknown.cmake:1 \(file\):
+  file RENAME unknown argument:
+
+    unknown$

+ 1 - 0
Tests/RunCMake/file/RENAME-arg-unknown.cmake

@@ -0,0 +1 @@
+file(RENAME "old" "new" unknown)

+ 1 - 0
Tests/RunCMake/file/RENAME-file-to-dir-capture-stdout.txt

@@ -0,0 +1 @@
+^-- file\(RENAME\) failed with result: [A-Za-z]

+ 9 - 0
Tests/RunCMake/file/RENAME-file-to-dir-capture.cmake

@@ -0,0 +1,9 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${oldname}" "")
+file(MAKE_DIRECTORY "${newname}")
+file(RENAME "${oldname}" "${newname}" RESULT result)
+message(STATUS "file(RENAME) failed with result: ${result}")
+if(NOT EXISTS "${oldname}")
+  message(FATAL_ERROR "The old name does not still exist:\n ${oldname}")
+endif()

+ 1 - 0
Tests/RunCMake/file/RENAME-file-to-dir-fail-result.txt

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

+ 13 - 0
Tests/RunCMake/file/RENAME-file-to-dir-fail-stderr.txt

@@ -0,0 +1,13 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/RENAME-file-to-dir-fail.cmake:[0-9] \(file\):
+  file RENAME failed to rename
+
+    [^
+]*/Tests/RunCMake/file/RENAME-file-to-dir-fail-build/input
+
+  to
+
+    [^
+]*/Tests/RunCMake/file/RENAME-file-to-dir-fail-build/output
+
+  because: [A-Za-z]

+ 5 - 0
Tests/RunCMake/file/RENAME-file-to-dir-fail.cmake

@@ -0,0 +1,5 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${oldname}" "")
+file(MAKE_DIRECTORY "${newname}")
+file(RENAME "${oldname}" "${newname}")

+ 10 - 0
Tests/RunCMake/file/RENAME-file-to-file.cmake

@@ -0,0 +1,10 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${oldname}" "")
+file(RENAME "${oldname}" "${newname}")
+if(EXISTS "${oldname}")
+  message(FATAL_ERROR "The old name still exists:\n ${oldname}")
+endif()
+if(NOT EXISTS "${newname}")
+  message(FATAL_ERROR "The new name does not exist:\n ${newname}")
+endif()

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

@@ -50,6 +50,12 @@ run_cmake(SIZE-error-does-not-exist)
 
 run_cmake(REMOVE-empty)
 
+run_cmake_script(RENAME-file-to-file)
+run_cmake_script(RENAME-file-to-dir-capture)
+run_cmake_script(RENAME-file-to-dir-fail)
+run_cmake_script(RENAME-arg-missing)
+run_cmake_script(RENAME-arg-unknown)
+
 # tests are valid both for GLOB and GLOB_RECURSE
 run_cmake(GLOB-sort-dedup)
 run_cmake(GLOB-error-LIST_DIRECTORIES-not-boolean)