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)