Merge topic 'file-COPY_FILE-subcommand'

088444211e file: add `COPY_FILE` subcommand
100016e9cb cmSystemTools: add utilities to copy a file with error handling

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !5885
This commit is contained in:
Brad King
2021-03-11 13:13:21 +00:00
committed by Kitware Robot
35 changed files with 334 additions and 0 deletions
+23
View File
@@ -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
View File
@@ -0,0 +1,4 @@
file-COPY_ONLY
--------------
* The :command:`file(COPY_FILE)` command was added to copy a file to another.
+92
View File
@@ -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 },
+46
View File
@@ -87,6 +87,7 @@
# include <unistd.h>
# include <sys/time.h>
# include <sys/types.h>
#endif
#if defined(_WIN32) && \
@@ -990,6 +991,51 @@ bool cmMoveFile(std::wstring const& oldname, std::wstring const& newname,
}
#endif
bool cmSystemTools::CopySingleFile(const std::string& oldname,
const std::string& newname)
{
return cmSystemTools::CopySingleFile(oldname, newname, CopyWhen::Always) ==
CopyResult::Success;
}
cmSystemTools::CopyResult cmSystemTools::CopySingleFile(
std::string const& oldname, std::string const& newname, CopyWhen when,
std::string* err)
{
switch (when) {
case CopyWhen::Always:
break;
case CopyWhen::OnlyIfDifferent:
if (!FilesDiffer(oldname, newname)) {
return CopyResult::Success;
}
break;
}
mode_t perm = 0;
bool perms = SystemTools::GetPermissions(oldname, perm);
// If files are the same do not copy
if (SystemTools::SameFile(oldname, newname)) {
return CopyResult::Success;
}
if (!cmsys::SystemTools::CloneFileContent(oldname, newname)) {
// if cloning did not succeed, fall back to blockwise copy
if (!cmsys::SystemTools::CopyFileContentBlockwise(oldname, newname)) {
ReportError(err);
return CopyResult::Failure;
}
}
if (perms) {
if (!SystemTools::SetPermissions(newname, perm)) {
ReportError(err);
return CopyResult::Failure;
}
}
return CopyResult::Success;
}
bool cmSystemTools::RenameFile(const std::string& oldname,
const std::string& newname)
{
+18
View File
@@ -128,6 +128,24 @@ public:
static bool SimpleGlob(const std::string& glob,
std::vector<std::string>& files, int type = 0);
enum class CopyWhen
{
Always,
OnlyIfDifferent,
};
enum class CopyResult
{
Success,
Failure,
};
/** Copy a file. */
static bool CopySingleFile(const std::string& oldname,
const std::string& newname);
static CopyResult CopySingleFile(std::string const& oldname,
std::string const& newname, CopyWhen when,
std::string* err = nullptr);
enum class Replace
{
Yes,
@@ -0,0 +1 @@
1
@@ -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
@@ -0,0 +1 @@
file(COPY_FILE "old")
@@ -0,0 +1 @@
1
@@ -0,0 +1,5 @@
^CMake Error at [^
]*/Tests/RunCMake/file/COPY_FILE-arg-unknown.cmake:1 \(file\):
file COPY_FILE unknown argument:
unknown$
@@ -0,0 +1 @@
file(COPY_FILE "old" "new" unknown)
@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy a directory
@@ -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()
@@ -0,0 +1 @@
1
@@ -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
@@ -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}")
@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy a directory
@@ -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()
@@ -0,0 +1 @@
1
@@ -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
@@ -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}")
@@ -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()
@@ -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
@@ -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)
@@ -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 ()
@@ -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()
@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy to a directory
@@ -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()
@@ -0,0 +1 @@
1
@@ -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
@@ -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}")
@@ -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()
@@ -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
View File
@@ -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)