file: add COPY_FILE subcommand

The `file(COPY)` subcommand is overloaded and busy for such a simple
operation. Instead, make a simpler subcommand with error handling
support.
This commit is contained in:
Ben Boeckel
2021-03-04 11:50:10 -05:00
parent 100016e9cb
commit 088444211e
33 changed files with 270 additions and 0 deletions

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:

View File

@@ -0,0 +1,4 @@
file-COPY_ONLY
--------------
* The :command:`file(COPY_FILE)` command was added to copy a file to another.

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

View File

@@ -0,0 +1 @@
1

View File

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

View File

@@ -0,0 +1 @@
file(COPY_FILE "old")

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at [^
]*/Tests/RunCMake/file/COPY_FILE-arg-unknown.cmake:1 \(file\):
file COPY_FILE unknown argument:
unknown$

View File

@@ -0,0 +1 @@
file(COPY_FILE "old" "new" unknown)

View File

@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy a directory

View File

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

View File

@@ -0,0 +1 @@
1

View File

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

View File

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

View File

@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy a directory

View File

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

View File

@@ -0,0 +1 @@
1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
^-- file\(COPY_FILE\) failed with result: cannot copy to a directory

View File

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

View File

@@ -0,0 +1 @@
1

View File

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

View File

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

View File

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

View File

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

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)