Browse Source

cmake -E: Add copy_if_newer and copy_directory_if_newer

Fixes: #24923
Benjamin Buch 5 months ago
parent
commit
8797912e4d

+ 25 - 0
Help/manual/cmake.1.rst

@@ -1029,6 +1029,19 @@ Available commands are:
   ``copy_directory_if_different`` does follow symlinks.
   The command fails when the source directory does not exist.
 
+.. option:: copy_directory_if_newer <dir>... <destination>
+
+  .. versionadded:: 4.2
+
+  Copy content of ``<dir>...`` directories to ``<destination>`` directory
+  if source files are newer than destination files (based on file timestamps).
+  If ``<destination>`` directory does not exist it will be created.
+
+  ``copy_directory_if_newer`` does follow symlinks.
+  The command fails when the source directory does not exist.
+  This is faster than ``copy_directory_if_different`` as it only compares
+  file timestamps instead of file contents.
+
 .. option:: copy_if_different <file>... <destination>
 
   Copy files to ``<destination>`` (either file or directory) if
@@ -1040,6 +1053,18 @@ Available commands are:
   .. versionadded:: 3.5
     Support for multiple input files.
 
+.. option:: copy_if_newer <file>... <destination>
+
+  .. versionadded:: 4.2
+
+  Copy files to ``<destination>`` (either file or directory) if
+  source files are newer than destination files (based on file timestamps).
+  If multiple files are specified, the ``<destination>`` must be
+  directory and it must exist.
+  ``copy_if_newer`` does follow symlinks.
+  This is faster than ``copy_if_different`` as it only compares
+  file timestamps instead of file contents.
+
 .. option:: create_symlink <old> <new>
 
   Create a symbolic link ``<new>`` naming ``<old>``.

+ 9 - 0
Help/release/dev/cmake-copy-if-newer.rst

@@ -0,0 +1,9 @@
+cmake-copy-if-newer
+-------------------
+
+* The :manual:`cmake(1)` command-line tool now supports
+  ``cmake -E copy_if_newer`` and ``cmake -E copy_directory_if_newer``
+  subcommands to copy files based on timestamp comparison instead of
+  content comparison. These commands copy files only if the source is
+  newer than the destination, providing better performance for build
+  systems compared to ``copy_if_different`` which compares file contents.

+ 26 - 0
Source/cmSystemTools.cxx

@@ -1571,6 +1571,18 @@ cmSystemTools::CopyResult cmSystemTools::CopySingleFile(
         return CopyResult::Success;
       }
       break;
+    case CopyWhen::OnlyIfNewer: {
+      if (!SystemTools::FileExists(newname)) {
+        break;
+      }
+      int timeResult = 0;
+      cmsys::Status timeStatus =
+        cmsys::SystemTools::FileTimeCompare(oldname, newname, &timeResult);
+      if (timeStatus.IsSuccess() && timeResult <= 0) {
+        return CopyResult::Success;
+      }
+      break;
+    }
   }
 
   mode_t perm = 0;
@@ -1632,6 +1644,20 @@ cmSystemTools::CopyResult cmSystemTools::CopySingleFile(
   return CopyResult::Success;
 }
 
+bool cmSystemTools::CopyFileIfNewer(std::string const& source,
+                                    std::string const& destination)
+{
+  return cmsys::SystemTools::CopyFileIfNewer(source, destination).IsSuccess();
+}
+
+bool cmSystemTools::CopyADirectory(std::string const& source,
+                                   std::string const& destination,
+                                   CopyWhen when)
+{
+  return cmsys::SystemTools::CopyADirectory(source, destination, when)
+    .IsSuccess();
+}
+
 bool cmSystemTools::RenameFile(std::string const& oldname,
                                std::string const& newname)
 {

+ 9 - 5
Source/cmSystemTools.h

@@ -167,11 +167,6 @@ public:
   static bool SimpleGlob(std::string const& glob,
                          std::vector<std::string>& files, int type = 0);
 
-  enum class CopyWhen
-  {
-    Always,
-    OnlyIfDifferent,
-  };
   enum class CopyInputRecent
   {
     No,
@@ -210,6 +205,15 @@ public:
                                    CopyInputRecent inputRecent,
                                    std::string* err = nullptr);
 
+  /** Copy a file if it is newer than the destination. */
+  static bool CopyFileIfNewer(std::string const& source,
+                              std::string const& destination);
+
+  /** Copy directory contents with specified copy behavior. */
+  static bool CopyADirectory(std::string const& source,
+                             std::string const& destination,
+                             CopyWhen when = CopyWhen::Always);
+
   enum class Replace
   {
     Yes,

+ 4 - 2
Source/cmVisualStudio10TargetGenerator.cxx

@@ -5614,7 +5614,8 @@ void cmVisualStudio10TargetGenerator::WriteMissingFilesWP80(Elem& e1)
   this->AddedFiles.push_back(smallLogo);
 
   std::string logo = cmStrCat(this->DefaultArtifactDir, "/Logo.png");
-  cmSystemTools::CopyAFile(cmStrCat(templateFolder, "/Logo.png"), logo, false);
+  cmSystemTools::CopyAFile(cmStrCat(templateFolder, "/Logo.png"), logo,
+                           cmSystemTools::CopyWhen::OnlyIfDifferent);
   ConvertToWindowsSlash(logo);
   Elem(e1, "Image").Attribute("Include", logo);
   this->AddedFiles.push_back(logo);
@@ -5894,7 +5895,8 @@ void cmVisualStudio10TargetGenerator::WriteCommonMissingFiles(
   this->AddedFiles.push_back(smallLogo44);
 
   std::string logo = cmStrCat(this->DefaultArtifactDir, "/Logo.png");
-  cmSystemTools::CopyAFile(cmStrCat(templateFolder, "/Logo.png"), logo, false);
+  cmSystemTools::CopyAFile(cmStrCat(templateFolder, "/Logo.png"), logo,
+                           cmSystemTools::CopyWhen::OnlyIfDifferent);
   ConvertToWindowsSlash(logo);
   Elem(e1, "Image").Attribute("Include", logo);
   this->AddedFiles.push_back(logo);

+ 35 - 3
Source/cmcmd.cxx

@@ -97,7 +97,9 @@ char const* const HELP_AVAILABLE_COMMANDS = R"(Available commands:
   copy <file>... destination  - copy files to destination (either file or directory)
   copy_directory <dir>... destination   - copy content of <dir>... directories to 'destination' directory
   copy_directory_if_different <dir>... destination   - copy changed content of <dir>... directories to 'destination' directory
+  copy_directory_if_newer <dir>... destination   - copy newer content of <dir>... directories to 'destination' directory
   copy_if_different <file>... destination  - copy files if it has changed
+  copy_if_newer <file>... destination  - copy files if source is newer than destination
   echo [<string>...]        - displays arguments as text
   echo_append [<string>...] - displays arguments as text but no new line
   env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]
@@ -778,15 +780,45 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
       return return_value;
     }
 
+    // Copy file if newer.
+    if (args[1] == "copy_if_newer" && args.size() > 3) {
+      // If multiple source files specified,
+      // then destination must be directory
+      if ((args.size() > 4) &&
+          (!cmSystemTools::FileIsDirectory(args.back()))) {
+        std::cerr << "Error: Target (for copy_if_newer command) \""
+                  << args.back() << "\" is not a directory.\n";
+        return 1;
+      }
+      // If error occurs we want to continue copying next files.
+      bool return_value = false;
+      for (auto const& arg : cmMakeRange(args).advance(2).retreat(1)) {
+        if (!cmSystemTools::CopyFileIfNewer(arg, args.back())) {
+          std::cerr << "Error copying file (if newer) from \"" << arg
+                    << "\" to \"" << args.back() << "\".\n";
+          return_value = true;
+        }
+      }
+      return return_value;
+    }
+
     // Copy directory contents
     if ((args[1] == "copy_directory" ||
-         args[1] == "copy_directory_if_different") &&
+         args[1] == "copy_directory_if_different" ||
+         args[1] == "copy_directory_if_newer") &&
         args.size() > 3) {
       // If error occurs we want to continue copying next files.
       bool return_value = false;
-      bool const copy_always = (args[1] == "copy_directory");
+
+      cmsys::SystemTools::CopyWhen when = cmsys::SystemTools::CopyWhen::Always;
+      if (args[1] == "copy_directory_if_different") {
+        when = cmsys::SystemTools::CopyWhen::OnlyIfDifferent;
+      } else if (args[1] == "copy_directory_if_newer") {
+        when = cmsys::SystemTools::CopyWhen::OnlyIfNewer;
+      }
+
       for (auto const& arg : cmMakeRange(args).advance(2).retreat(1)) {
-        if (!cmSystemTools::CopyADirectory(arg, args.back(), copy_always)) {
+        if (!cmSystemTools::CopyADirectory(arg, args.back(), when)) {
           std::cerr << "Error copying directory from \"" << arg << "\" to \""
                     << args.back() << "\".\n";
           return_value = true;

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-result.txt

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

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-stderr.txt

@@ -0,0 +1 @@
+^Error copying directory from ".+" to ".+"\.$

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_directory_if_newer-stderr.txt

@@ -0,0 +1 @@
+^$

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-result.txt

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

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-stderr.txt

@@ -0,0 +1 @@
+^Error copying file \(if different\) from ".+" to ".+"\.$

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-result.txt

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

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-stderr.txt

@@ -0,0 +1 @@
+^Error copying file \(if newer\) from ".+" to ".+"\.$

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-one-source-directory-target-is-directory-stderr.txt

@@ -0,0 +1 @@
+^$

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-directory-stderr.txt

@@ -0,0 +1 @@
+^$

+ 0 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-result.txt


+ 1 - 0
Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-stderr.txt

@@ -0,0 +1 @@
+^Error: Target \(for copy_if_newer command\).* is not a directory\.$

+ 14 - 0
Tests/RunCMake/CommandLine/RunCMakeTest.cmake

@@ -616,6 +616,16 @@ run_cmake_command(E_copy_if_different-three-source-files-target-is-directory
   ${CMAKE_COMMAND} -E copy_if_different ${in}/f1.txt ${in}/f2.txt ${in}/f3.txt ${out})
 run_cmake_command(E_copy_if_different-three-source-files-target-is-file
   ${CMAKE_COMMAND} -E copy_if_different ${in}/f1.txt ${in}/f2.txt ${in}/f3.txt ${out}/f1.txt)
+run_cmake_command(E_copy_if_different-nonexistent-source
+  ${CMAKE_COMMAND} -E copy_if_different ${in}/nonexistent.txt ${out})
+run_cmake_command(E_copy_if_newer-one-source-directory-target-is-directory
+  ${CMAKE_COMMAND} -E copy_if_newer ${in}/f1.txt ${out})
+run_cmake_command(E_copy_if_newer-three-source-files-target-is-directory
+  ${CMAKE_COMMAND} -E copy_if_newer ${in}/f1.txt ${in}/f2.txt ${in}/f3.txt ${out})
+run_cmake_command(E_copy_if_newer-three-source-files-target-is-file
+  ${CMAKE_COMMAND} -E copy_if_newer ${in}/f1.txt ${in}/f2.txt ${in}/f3.txt ${out}/f1.txt)
+run_cmake_command(E_copy_if_newer-nonexistent-source
+  ${CMAKE_COMMAND} -E copy_if_newer ${in}/nonexistent.txt ${out})
 unset(in)
 unset(out)
 
@@ -625,6 +635,10 @@ file(REMOVE_RECURSE "${out}")
 file(MAKE_DIRECTORY ${out})
 run_cmake_command(E_copy_directory_if_different
   ${CMAKE_COMMAND} -E copy_directory_if_different ${in} ${out})
+run_cmake_command(E_copy_directory_if_newer
+  ${CMAKE_COMMAND} -E copy_directory_if_newer ${in} ${out})
+run_cmake_command(E_copy_directory_if_newer-nonexistent-source
+  ${CMAKE_COMMAND} -E copy_directory_if_newer ${in}/nonexistent ${out}/target)
 unset(in)
 unset(out)