Bladeren bron

file: add `COPY_FILE` subcommand

The `file(COPY)` subcommand is overloaded and busy for such a simple
operation. Instead, make a simpler subcommand with error handling
support.
Ben Boeckel 4 jaren geleden
bovenliggende
commit
088444211e
33 gewijzigde bestanden met toevoegingen van 270 en 0 verwijderingen
  1. 23 0
      Help/command/file.rst
  2. 4 0
      Help/release/dev/file-COPY_FILE.rst
  3. 92 0
      Source/cmFileCommand.cxx
  4. 1 0
      Tests/RunCMake/file/COPY_FILE-arg-missing-result.txt
  5. 3 0
      Tests/RunCMake/file/COPY_FILE-arg-missing-stderr.txt
  6. 1 0
      Tests/RunCMake/file/COPY_FILE-arg-missing.cmake
  7. 1 0
      Tests/RunCMake/file/COPY_FILE-arg-unknown-result.txt
  8. 5 0
      Tests/RunCMake/file/COPY_FILE-arg-unknown-stderr.txt
  9. 1 0
      Tests/RunCMake/file/COPY_FILE-arg-unknown.cmake
  10. 1 0
      Tests/RunCMake/file/COPY_FILE-dir-to-file-capture-stdout.txt
  11. 8 0
      Tests/RunCMake/file/COPY_FILE-dir-to-file-capture.cmake
  12. 1 0
      Tests/RunCMake/file/COPY_FILE-dir-to-file-fail-result.txt
  13. 6 0
      Tests/RunCMake/file/COPY_FILE-dir-to-file-fail-stderr.txt
  14. 4 0
      Tests/RunCMake/file/COPY_FILE-dir-to-file-fail.cmake
  15. 1 0
      Tests/RunCMake/file/COPY_FILE-dirlink-to-file-capture-stdout.txt
  16. 8 0
      Tests/RunCMake/file/COPY_FILE-dirlink-to-file-capture.cmake
  17. 1 0
      Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail-result.txt
  18. 6 0
      Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail-stderr.txt
  19. 4 0
      Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail.cmake
  20. 9 0
      Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-capture.cmake
  21. 1 0
      Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail-result.txt
  22. 6 0
      Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail-stderr.txt
  23. 5 0
      Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail.cmake
  24. 12 0
      Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-no-overwrite.cmake
  25. 9 0
      Tests/RunCMake/file/COPY_FILE-file-replace.cmake
  26. 1 0
      Tests/RunCMake/file/COPY_FILE-file-to-dir-capture-stdout.txt
  27. 9 0
      Tests/RunCMake/file/COPY_FILE-file-to-dir-capture.cmake
  28. 1 0
      Tests/RunCMake/file/COPY_FILE-file-to-dir-fail-result.txt
  29. 6 0
      Tests/RunCMake/file/COPY_FILE-file-to-dir-fail-stderr.txt
  30. 5 0
      Tests/RunCMake/file/COPY_FILE-file-to-dir-fail.cmake
  31. 10 0
      Tests/RunCMake/file/COPY_FILE-file-to-file.cmake
  32. 10 0
      Tests/RunCMake/file/COPY_FILE-link-to-file.cmake
  33. 15 0
      Tests/RunCMake/file/RunCMakeTest.cmake

+ 23 - 0
Help/command/file.rst

@@ -39,6 +39,7 @@ Synopsis
   `Filesystem`_
     file({`GLOB`_ | `GLOB_RECURSE`_} <out-var> [...] [<globbing-expr>...])
     file(`RENAME`_ <oldname> <newname> [...])
+    file(`COPY_FILE`_ <oldname> <newname> [...])
     file({`REMOVE`_ | `REMOVE_RECURSE`_ } [<files>...])
     file(`MAKE_DIRECTORY`_ [<dir>...])
     file({`COPY`_ | `INSTALL`_} <file>... DESTINATION <dir> [...])
@@ -683,6 +684,28 @@ The options are:
   If ``RESULT <result>`` is used, the result variable will be
   set to ``NO_REPLACE``.  Otherwise, an error is emitted.
 
+.. _COPY_FILE:
+
+.. code-block:: cmake
+
+  file(COPY_FILE <oldname> <newname>
+       [RESULT <result>]
+       [ONLY_IF_DIFFERENT])
+
+Copy a file from ``<oldname>`` to ``<newname>``. Directories are not
+supported. Symlinks are ignored and ``<oldfile>``'s content is read and
+written to ``<newname>`` as a new file.
+
+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.
+
+``ONLY_IF_DIFFERENT``
+  If the ``<newname>`` path already exists, do not replace it if it is the
+  same as ``<oldname>``. Otherwise, an error is emitted.
+
 .. _REMOVE:
 .. _REMOVE_RECURSE:
 

+ 4 - 0
Help/release/dev/file-COPY_FILE.rst

@@ -0,0 +1,4 @@
+file-COPY_ONLY
+--------------
+
+* The :command:`file(COPY_FILE)` command was added to copy a file to another.

+ 92 - 0
Source/cmFileCommand.cxx

@@ -1379,6 +1379,97 @@ bool HandleRename(std::vector<std::string> const& args,
   return false;
 }
 
+bool HandleCopyFile(std::vector<std::string> const& args,
+                    cmExecutionStatus& status)
+{
+  if (args.size() < 3) {
+    status.SetError("COPY_FILE must be called with at least two additional "
+                    "arguments");
+    return false;
+  }
+
+  // Compute full path for old and new names.
+  std::string oldname = args[1];
+  if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
+    oldname =
+      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
+  }
+  std::string newname = args[2];
+  if (!cmsys::SystemTools::FileIsFullPath(newname)) {
+    newname =
+      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
+  }
+
+  struct Arguments
+  {
+    bool OnlyIfDifferent = false;
+    std::string Result;
+  };
+
+  static auto const parser =
+    cmArgumentParser<Arguments>{}
+      .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
+      .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("COPY_FILE unknown argument:\n  " +
+                    unconsumedArgs.front());
+    return false;
+  }
+
+  bool result = true;
+  if (cmsys::SystemTools::FileIsDirectory(oldname)) {
+    if (!arguments.Result.empty()) {
+      status.GetMakefile().AddDefinition(arguments.Result,
+                                         "cannot copy a directory");
+    } else {
+      status.SetError(
+        cmStrCat("COPY_FILE cannot copy a directory\n  ", oldname));
+      result = false;
+    }
+    return result;
+  }
+  if (cmsys::SystemTools::FileIsDirectory(newname)) {
+    if (!arguments.Result.empty()) {
+      status.GetMakefile().AddDefinition(arguments.Result,
+                                         "cannot copy to a directory");
+    } else {
+      status.SetError(
+        cmStrCat("COPY_FILE cannot copy to a directory\n  ", newname));
+      result = false;
+    }
+    return result;
+  }
+
+  cmSystemTools::CopyWhen when;
+  if (arguments.OnlyIfDifferent) {
+    when = cmSystemTools::CopyWhen::OnlyIfDifferent;
+  } else {
+    when = cmSystemTools::CopyWhen::Always;
+  }
+
+  std::string err;
+  if (cmSystemTools::CopySingleFile(oldname, newname, when, &err) ==
+      cmSystemTools::CopyResult::Success) {
+    if (!arguments.Result.empty()) {
+      status.GetMakefile().AddDefinition(arguments.Result, "0");
+    }
+  } else {
+    if (!arguments.Result.empty()) {
+      status.GetMakefile().AddDefinition(arguments.Result, err);
+    } else {
+      status.SetError(cmStrCat("COPY_FILE failed to copy\n  ", oldname,
+                               "\nto\n  ", newname, "\nbecause: ", err, "\n"));
+      result = false;
+    }
+  }
+
+  return result;
+}
+
 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
                       cmExecutionStatus& status)
 {
@@ -3609,6 +3700,7 @@ bool cmFileCommand(std::vector<std::string> const& args,
     { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
     { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
     { "RENAME"_s, HandleRename },
+    { "COPY_FILE"_s, HandleCopyFile },
     { "REMOVE"_s, HandleRemove },
     { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
     { "COPY"_s, HandleCopyCommand },

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1 @@
+^-- file\(COPY_FILE\) failed with result: cannot copy a directory

+ 8 - 0
Tests/RunCMake/file/COPY_FILE-dir-to-file-capture.cmake

@@ -0,0 +1,8 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(MAKE_DIRECTORY "${oldname}")
+file(COPY_FILE "${oldname}" "${newname}" RESULT result)
+message(STATUS "file(COPY_FILE) failed with result: ${result}")
+if(EXISTS "${newname}")
+  message(FATAL_ERROR "The new name exists:\n ${newname}")
+endif()

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

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

+ 6 - 0
Tests/RunCMake/file/COPY_FILE-dir-to-file-fail-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/COPY_FILE-dir-to-file-fail.cmake:[0-9] \(file\):
+  file COPY_FILE cannot copy a directory
+
+    [^
+]*/Tests/RunCMake/file/COPY_FILE-dir-to-file-fail-build/input

+ 4 - 0
Tests/RunCMake/file/COPY_FILE-dir-to-file-fail.cmake

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

+ 1 - 0
Tests/RunCMake/file/COPY_FILE-dirlink-to-file-capture-stdout.txt

@@ -0,0 +1 @@
+^-- file\(COPY_FILE\) failed with result: cannot copy a directory

+ 8 - 0
Tests/RunCMake/file/COPY_FILE-dirlink-to-file-capture.cmake

@@ -0,0 +1,8 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(MAKE_DIRECTORY "${oldname}")
+file(COPY_FILE "${oldname}" "${newname}" RESULT result)
+message(STATUS "file(COPY_FILE) failed with result: ${result}")
+if(EXISTS "${newname}")
+  message(FATAL_ERROR "The new name exists:\n ${newname}")
+endif()

+ 1 - 0
Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail-result.txt

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

+ 6 - 0
Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail.cmake:[0-9] \(file\):
+  file COPY_FILE cannot copy a directory
+
+    [^
+]*/Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail-build/input

+ 4 - 0
Tests/RunCMake/file/COPY_FILE-dirlink-to-file-fail.cmake

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

+ 9 - 0
Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-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(COPY_FILE "${oldname}" "${newname}" RESULT result ONLY_IF_DIFFERENT)
+message(STATUS "file(COPY_FILE) failed with result: ${result}")
+if(NOT EXISTS "${oldname}")
+  message(FATAL_ERROR "The old name still does not exist:\n ${oldname}")
+endif()

+ 1 - 0
Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail-result.txt

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

+ 6 - 0
Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail.cmake:[0-9] \(file\):
+  file COPY_FILE cannot copy to a directory
+
+    [^
+]*/Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-fail-build/output

+ 5 - 0
Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-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(COPY_FILE "${oldname}" "${newname}" ONLY_IF_DIFFERENT)

+ 12 - 0
Tests/RunCMake/file/COPY_FILE-file-ONLY_IF_DIFFERENT-no-overwrite.cmake

@@ -0,0 +1,12 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${oldname}" "")
+execute_process(COMMAND "${CMAKE_COMMAND} -E sleep 1")
+file(WRITE "${newname}" "")
+file(TIMESTAMP "${newname}" before_copy UTC)
+file(COPY_FILE "${oldname}" "${newname}" RESULT result ONLY_IF_DIFFERENT)
+file(TIMESTAMP "${newname}" after_copy UTC)
+if (NOT before_copy STREQUAL after_copy)
+  message(FATAL_ERROR
+    "${newname} was modified even though ONLY_IF_DIFFERENT was specified")
+endif ()

+ 9 - 0
Tests/RunCMake/file/COPY_FILE-file-replace.cmake

@@ -0,0 +1,9 @@
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${oldname}" "a")
+file(WRITE "${newname}" "b")
+file(COPY_FILE "${oldname}" "${newname}")
+file(READ "${newname}" new)
+if(NOT "${new}" STREQUAL "a")
+  message(FATAL_ERROR "New name:\n  ${newname}\ndoes not contain expected content 'a'.")
+endif()

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

@@ -0,0 +1 @@
+^-- file\(COPY_FILE\) failed with result: cannot copy to a directory

+ 9 - 0
Tests/RunCMake/file/COPY_FILE-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(COPY_FILE "${oldname}" "${newname}" RESULT result)
+message(STATUS "file(COPY_FILE) failed with result: ${result}")
+if(NOT EXISTS "${oldname}")
+  message(FATAL_ERROR "The old name does not exist:\n ${oldname}")
+endif()

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

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

+ 6 - 0
Tests/RunCMake/file/COPY_FILE-file-to-dir-fail-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Tests/RunCMake/file/COPY_FILE-file-to-dir-fail.cmake:[0-9] \(file\):
+  file COPY_FILE cannot copy to a directory
+
+    [^
+]*/Tests/RunCMake/file/COPY_FILE-file-to-dir-fail-build/output

+ 5 - 0
Tests/RunCMake/file/COPY_FILE-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(COPY_FILE "${oldname}" "${newname}")

+ 10 - 0
Tests/RunCMake/file/COPY_FILE-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(COPY_FILE "${oldname}" "${newname}")
+if(NOT EXISTS "${oldname}")
+  message(FATAL_ERROR "The old name does not exist:\n ${oldname}")
+endif()
+if(NOT EXISTS "${newname}")
+  message(FATAL_ERROR "The new name does not exist:\n ${newname}")
+endif()

+ 10 - 0
Tests/RunCMake/file/COPY_FILE-link-to-file.cmake

@@ -0,0 +1,10 @@
+set(lnkname "${CMAKE_CURRENT_BINARY_DIR}/link")
+set(oldname "${CMAKE_CURRENT_BINARY_DIR}/input")
+set(newname "${CMAKE_CURRENT_BINARY_DIR}/output")
+file(WRITE "${lnkname}" "a")
+file(CREATE_LINK "${lnkname}" "${oldname}")
+file(COPY_FILE "${oldname}" "${newname}")
+file(READ "${newname}" new)
+if(NOT "${new}" STREQUAL "a")
+  message(FATAL_ERROR "New name:\n  ${newname}\ndoes not contain expected content 'a'.")
+endif()

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

@@ -50,6 +50,21 @@ run_cmake(SIZE-error-does-not-exist)
 
 run_cmake(REMOVE-empty)
 
+run_cmake_script(COPY_FILE-file-replace)
+run_cmake_script(COPY_FILE-dir-to-file-capture)
+run_cmake_script(COPY_FILE-dir-to-file-fail)
+run_cmake_script(COPY_FILE-dirlink-to-file-capture)
+run_cmake_script(COPY_FILE-dirlink-to-file-fail)
+run_cmake_script(COPY_FILE-file-to-file)
+run_cmake_script(COPY_FILE-file-to-dir-capture)
+run_cmake_script(COPY_FILE-file-to-dir-fail)
+run_cmake_script(COPY_FILE-file-ONLY_IF_DIFFERENT-capture)
+run_cmake_script(COPY_FILE-file-ONLY_IF_DIFFERENT-fail)
+run_cmake_script(COPY_FILE-file-ONLY_IF_DIFFERENT-no-overwrite)
+run_cmake_script(COPY_FILE-link-to-file)
+run_cmake_script(COPY_FILE-arg-missing)
+run_cmake_script(COPY_FILE-arg-unknown)
+
 run_cmake_script(RENAME-file-replace)
 run_cmake_script(RENAME-file-to-file)
 run_cmake_script(RENAME-file-to-dir-capture)