diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index 46ad589830..78b81f95b6 100644
--- a/Help/manual/cmake.1.rst
+++ b/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
...
+
+ .. versionadded:: 4.2
+
+ Copy content of ``...`` directories to ```` directory
+ if source files are newer than destination files (based on file timestamps).
+ If ```` 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 ...
Copy files to ```` (either file or directory) if
@@ -1040,6 +1053,18 @@ Available commands are:
.. versionadded:: 3.5
Support for multiple input files.
+.. option:: copy_if_newer ...
+
+ .. versionadded:: 4.2
+
+ Copy files to ```` (either file or directory) if
+ source files are newer than destination files (based on file timestamps).
+ If multiple files are specified, the ```` 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
Create a symbolic link ```` naming ````.
diff --git a/Help/release/dev/cmake-copy-if-newer.rst b/Help/release/dev/cmake-copy-if-newer.rst
new file mode 100644
index 0000000000..e9a857c90a
--- /dev/null
+++ b/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.
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index cd7fa08e58..4813db5fcb 100644
--- a/Source/cmSystemTools.cxx
+++ b/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)
{
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index 7fb2b28214..5c187ecb0f 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -167,11 +167,6 @@ public:
static bool SimpleGlob(std::string const& glob,
std::vector& 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,
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 13d4b42b6d..de3986ceb8 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/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);
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 69f64af479..78b58cc601 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -97,7 +97,9 @@ char const* const HELP_AVAILABLE_COMMANDS = R"(Available commands:
copy ... destination - copy files to destination (either file or directory)
copy_directory ... destination - copy content of ... directories to 'destination' directory
copy_directory_if_different ... destination - copy changed content of ... directories to 'destination' directory
+ copy_directory_if_newer ... destination - copy newer content of ... directories to 'destination' directory
copy_if_different ... destination - copy files if it has changed
+ copy_if_newer ... destination - copy files if source is newer than destination
echo [...] - displays arguments as text
echo_append [...] - displays arguments as text but no new line
env [--unset=NAME ...] [NAME=VALUE ...] [--] [...]
@@ -778,15 +780,45 @@ int cmcmd::ExecuteCMakeCommand(std::vector 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;
diff --git a/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-result.txt b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-stderr.txt
new file mode 100644
index 0000000000..248ebb4040
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-nonexistent-source-stderr.txt
@@ -0,0 +1 @@
+^Error copying directory from ".+" to ".+"\.$
diff --git a/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-stderr.txt
new file mode 100644
index 0000000000..10f32932ee
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_directory_if_newer-stderr.txt
@@ -0,0 +1 @@
+^$
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-result.txt b/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-stderr.txt
new file mode 100644
index 0000000000..ce1099b329
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_different-nonexistent-source-stderr.txt
@@ -0,0 +1 @@
+^Error copying file \(if different\) from ".+" to ".+"\.$
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-result.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-stderr.txt
new file mode 100644
index 0000000000..52e51bce07
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_newer-nonexistent-source-stderr.txt
@@ -0,0 +1 @@
+^Error copying file \(if newer\) from ".+" to ".+"\.$
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-one-source-directory-target-is-directory-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-one-source-directory-target-is-directory-stderr.txt
new file mode 100644
index 0000000000..10f32932ee
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_newer-one-source-directory-target-is-directory-stderr.txt
@@ -0,0 +1 @@
+^$
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-directory-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-directory-stderr.txt
new file mode 100644
index 0000000000..10f32932ee
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-directory-stderr.txt
@@ -0,0 +1 @@
+^$
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-result.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-result.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-stderr.txt b/Tests/RunCMake/CommandLine/E_copy_if_newer-three-source-files-target-is-file-stderr.txt
new file mode 100644
index 0000000000..08eab4ca14
--- /dev/null
+++ b/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\.$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 944f90213a..ae796d8fdb 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/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)