Просмотр исходного кода

cmake -E copy: Add support for -t argument

Fixes: #23543
Kyle Edwards 3 лет назад
Родитель
Сommit
8d9069e5b6

+ 9 - 5
Help/manual/cmake.1.rst

@@ -848,17 +848,21 @@ Available commands are:
 
 .. program:: cmake-E
 
-.. option:: copy <file>... <destination>
+.. option:: copy <file>... <destination>, copy -t <destination> <file>...
 
   Copy files to ``<destination>`` (either file or directory).
-  If multiple files are specified, the ``<destination>`` 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
+  ``<destination>`` must be directory and it must exist. If ``-t`` is not
+  specified, the last argument is assumed to be the ``<destination>``.
+  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 <dir>... <destination>
 
   Copy content of ``<dir>...`` directories to ``<destination>`` directory.

+ 4 - 0
Help/release/dev/cmake-E-copy-t-arg.rst

@@ -0,0 +1,4 @@
+cmake-E-copy-t-arg
+------------------
+
+* The :option:`cmake -E copy <cmake-E copy>` argument now supports a ``-t`` argument.

+ 11 - 0
Source/cmCommandLineArgument.h

@@ -2,6 +2,8 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include <cm/optional>
+
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
@@ -250,6 +252,15 @@ private:
         return true;
       };
     }
+
+    static std::function<bool(const std::string&, CallState...)>
+    generateSetToValue(cm::optional<std::string>& value1)
+    {
+      return [&value1](const std::string& arg, CallState&&...) -> bool {
+        value1 = arg;
+        return true;
+      };
+    }
   };
 
   std::string extract_single_value(std::string const& input,

+ 50 - 7
Source/cmcmd.cxx

@@ -2,11 +2,15 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmcmd.h"
 
+#include <functional>
+
+#include <cm/optional>
 #include <cmext/algorithm>
 
 #include <cm3p/uv.h>
 #include <fcntl.h>
 
+#include "cmCommandLineArgument.h"
 #include "cmConsoleBuf.h"
 #include "cmDuration.h"
 #include "cmGlobalGenerator.h"
@@ -640,20 +644,59 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
   if (args.size() > 1) {
     // Copy file
     if (args[1] == "copy" && args.size() > 3) {
+      using CommandArgument =
+        cmCommandLineArgument<bool(const std::string& value)>;
+
+      cm::optional<std::string> targetArg;
+      std::vector<CommandArgument> argParsers{
+        { "-t", CommandArgument::Values::One,
+          CommandArgument::setToValue(targetArg) },
+      };
+
+      std::vector<std::string> 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;
         }
       }

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CommandLine/E_copy-t-argument-target-is-file-stderr.txt

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

+ 6 - 0
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