From 8d9069e5b667e1b6d7f1eaea52ae88eb31d7d0df Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Mon, 14 Nov 2022 15:49:37 -0500 Subject: cmake -E copy: Add support for -t argument Fixes: #23543 --- Help/manual/cmake.1.rst | 14 ++++-- Help/release/dev/cmake-E-copy-t-arg.rst | 4 ++ Source/cmCommandLineArgument.h | 11 +++++ Source/cmcmd.cxx | 57 +++++++++++++++++++--- .../E_copy-t-argument-target-is-file-result.txt | 1 + .../E_copy-t-argument-target-is-file-stderr.txt | 1 + Tests/RunCMake/CommandLine/RunCMakeTest.cmake | 6 +++ 7 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 Help/release/dev/cmake-E-copy-t-arg.rst create mode 100644 Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-result.txt create mode 100644 Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-stderr.txt diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index daa2e58..dc51383 100644 --- a/Help/manual/cmake.1.rst +++ b/Help/manual/cmake.1.rst @@ -848,17 +848,21 @@ Available commands are: .. program:: cmake-E -.. option:: copy ... +.. option:: copy ... , copy -t ... Copy files to ```` (either file or directory). - If multiple files are specified, the ```` must be - directory and it must exist. Wildcards are not supported. - ``copy`` does follow symlinks. That means it does not copy symlinks, - but the files or directories it point to. + If multiple files are specified, or if ``-t`` is specified, the + ```` must be directory and it must exist. If ``-t`` is not + specified, the last argument is assumed to be the ````. + Wildcards are not supported. ``copy`` does follow symlinks. That means it + does not copy symlinks, but the files or directories it point to. .. versionadded:: 3.5 Support for multiple input files. + .. versionadded:: 3.26 + Support for ``-t`` argument. + .. option:: copy_directory ... Copy content of ``...`` directories to ```` directory. diff --git a/Help/release/dev/cmake-E-copy-t-arg.rst b/Help/release/dev/cmake-E-copy-t-arg.rst new file mode 100644 index 0000000..ca897d3 --- /dev/null +++ b/Help/release/dev/cmake-E-copy-t-arg.rst @@ -0,0 +1,4 @@ +cmake-E-copy-t-arg +------------------ + +* The :option:`cmake -E copy ` argument now supports a ``-t`` argument. diff --git a/Source/cmCommandLineArgument.h b/Source/cmCommandLineArgument.h index 33c91bc..003e972 100644 --- a/Source/cmCommandLineArgument.h +++ b/Source/cmCommandLineArgument.h @@ -2,6 +2,8 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #pragma once +#include + #include "cmStringAlgorithms.h" #include "cmSystemTools.h" @@ -250,6 +252,15 @@ private: return true; }; } + + static std::function + generateSetToValue(cm::optional& value1) + { + return [&value1](const std::string& arg, CallState&&...) -> bool { + value1 = arg; + return true; + }; + } }; std::string extract_single_value(std::string const& input, diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 67394f9..06bceb4 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -2,11 +2,15 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmcmd.h" +#include + +#include #include #include #include +#include "cmCommandLineArgument.h" #include "cmConsoleBuf.h" #include "cmDuration.h" #include "cmGlobalGenerator.h" @@ -640,20 +644,59 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, if (args.size() > 1) { // Copy file if (args[1] == "copy" && args.size() > 3) { + using CommandArgument = + cmCommandLineArgument; + + cm::optional targetArg; + std::vector argParsers{ + { "-t", CommandArgument::Values::One, + CommandArgument::setToValue(targetArg) }, + }; + + std::vector files; + for (decltype(args.size()) i = 2; i < args.size(); i++) { + const std::string& arg = args[i]; + bool matched = false; + for (auto const& m : argParsers) { + if (m.matches(arg)) { + matched = true; + if (m.parse(arg, i, args)) { + break; + } + return 1; // failed to parse + } + } + if (!matched) { + files.push_back(arg); + } + } + // If multiple source files specified, // then destination must be directory - if ((args.size() > 4) && - (!cmSystemTools::FileIsDirectory(args.back()))) { - std::cerr << "Error: Target (for copy command) \"" << args.back() + if (files.size() > 2 && !targetArg) { + targetArg = files.back(); + files.pop_back(); + } + if (targetArg && (!cmSystemTools::FileIsDirectory(*targetArg))) { + std::cerr << "Error: Target (for copy command) \"" << *targetArg << "\" is not a directory.\n"; return 1; } + if (!targetArg) { + if (files.size() < 2) { + std::cerr + << "Error: No files or target specified (for copy command).\n"; + return 1; + } + targetArg = files.back(); + files.pop_back(); + } // 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 (!cmsys::SystemTools::CopyFileAlways(arg, args.back())) { - std::cerr << "Error copying file \"" << arg << "\" to \"" - << args.back() << "\".\n"; + for (auto const& file : files) { + if (!cmsys::SystemTools::CopyFileAlways(file, *targetArg)) { + std::cerr << "Error copying file \"" << file << "\" to \"" + << *targetArg << "\".\n"; return_value = true; } } diff --git a/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-result.txt b/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-stderr.txt b/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-stderr.txt new file mode 100644 index 0000000..9504216 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-stderr.txt @@ -0,0 +1 @@ +^Error: Target \(for copy command\).* is not a directory.$ diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake index 327b772..c89922a 100644 --- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake @@ -574,6 +574,12 @@ run_cmake_command(E_copy-three-source-files-target-is-file ${CMAKE_COMMAND} -E copy ${in}/f1.txt ${in}/f2.txt ${in}/f3.txt ${out}/f1.txt) run_cmake_command(E_copy-two-good-and-one-bad-source-files-target-is-directory ${CMAKE_COMMAND} -E copy ${in}/f1.txt ${in}/not_existing_file.bad ${in}/f3.txt ${out}) +run_cmake_command(E_copy-t-argument + ${CMAKE_COMMAND} -E copy ${in}/f1.txt -t ${out} ${in}/f3.txt) +run_cmake_command(E_copy-t-argument-target-is-file + ${CMAKE_COMMAND} -E copy ${in}/f1.txt -t ${out}/f1.txt ${in}/f3.txt) +run_cmake_command(E_copy-t-argument-no-source-files + ${CMAKE_COMMAND} -E copy -t ${out}) run_cmake_command(E_copy_if_different-one-source-directory-target-is-directory ${CMAKE_COMMAND} -E copy_if_different ${in}/f1.txt ${out}) run_cmake_command(E_copy_if_different-three-source-files-target-is-directory -- cgit v0.12