cmake -E: Add copy_if_newer and copy_directory_if_newer

Fixes: #24923
This commit is contained in:
Benjamin Buch
2025-07-27 21:37:00 +02:00
parent 4948d14c65
commit 8797912e4d
18 changed files with 132 additions and 10 deletions

View File

@@ -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>``.

View File

@@ -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.

View File

@@ -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)
{

View File

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

View File

@@ -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);

View File

@@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)