فهرست منبع

add_custom_{command,target}: Add COMMAND_EXPAND_LISTS option

This option allows lists generated by generator expressions to be expanded.

Closes: #15935
Ed Branch 9 سال پیش
والد
کامیت
7c8ab7ddc8

+ 10 - 1
Help/command/add_custom_command.rst

@@ -21,7 +21,8 @@ The first signature is for adding a custom command to produce an output::
                      [WORKING_DIRECTORY dir]
                      [COMMENT comment]
                      [DEPFILE depfile]
-                     [VERBATIM] [APPEND] [USES_TERMINAL])
+                     [VERBATIM] [APPEND] [USES_TERMINAL]
+                     [COMMAND_EXPAND_LISTS])
 
 This defines a command to generate specified ``OUTPUT`` file(s).
 A target created in the same directory (``CMakeLists.txt`` file)
@@ -122,6 +123,14 @@ The options are:
   Arguments to ``DEPENDS`` may use
   :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
+``COMMAND_EXPAND_LISTS``
+  Lists in ``COMMAND`` arguments will be expanded, including those
+  created with
+  :manual:`generator expressions <cmake-generator-expressions(7)>`,
+  allowing ``COMMAND`` arguments such as
+  ``${CC} "-I$<JOIN:$<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>,;-I>" foo.cc``
+  to be properly expanded.
+
 ``IMPLICIT_DEPENDS``
   Request scanning of implicit dependencies of an input file.
   The language given specifies the programming language whose

+ 9 - 0
Help/command/add_custom_target.rst

@@ -12,6 +12,7 @@ Add a target with no output so it will always be built.
                     [WORKING_DIRECTORY dir]
                     [COMMENT comment]
                     [VERBATIM] [USES_TERMINAL]
+                    [COMMAND_EXPAND_LISTS]
                     [SOURCES src1 [src2...]])
 
 Adds a target with the given name that executes the given commands.
@@ -88,6 +89,14 @@ The options are:
   Use the :command:`add_dependencies` command to add dependencies
   on other targets.
 
+``COMMAND_EXPAND_LISTS``
+  Lists in ``COMMAND`` arguments will be expanded, including those
+  created with
+  :manual:`generator expressions <cmake-generator-expressions(7)>`,
+  allowing ``COMMAND`` arguments such as
+  ``${CC} "-I$<JOIN:$<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>,;-I>" foo.cc``
+  to be properly expanded.
+
 ``SOURCES``
   Specify additional source files to be included in the custom target.
   Specified source files will be added to IDE project files for

+ 7 - 0
Help/release/dev/expand_custom_commands.rst

@@ -0,0 +1,7 @@
+expand_custom_commands
+----------------------
+
+* The commands :command:`add_custom_command` and :command:`add_custom_target`
+  learned the option ``COMMAND_EXPAND_LISTS`` which causes lists in the
+  ``COMMAND`` argument to be expanded, including lists created by generator
+  expressions.

+ 7 - 2
Source/cmAddCustomCommandCommand.cxx

@@ -36,6 +36,7 @@ bool cmAddCustomCommandCommand::InitialPass(
   bool verbatim = false;
   bool append = false;
   bool uses_terminal = false;
+  bool command_expand_lists = false;
   std::string implicit_depends_lang;
   cmCustomCommand::ImplicitDependsList implicit_depends;
 
@@ -92,6 +93,8 @@ bool cmAddCustomCommandCommand::InitialPass(
       append = true;
     } else if (copy == "USES_TERMINAL") {
       uses_terminal = true;
+    } else if (copy == "COMMAND_EXPAND_LISTS") {
+      command_expand_lists = true;
     } else if (copy == "TARGET") {
       doing = doing_target;
     } else if (copy == "ARGS") {
@@ -281,12 +284,14 @@ bool cmAddCustomCommandCommand::InitialPass(
     std::vector<std::string> no_depends;
     this->Makefile->AddCustomCommandToTarget(
       target, byproducts, no_depends, commandLines, cctype, comment,
-      working.c_str(), escapeOldStyle, uses_terminal, depfile);
+      working.c_str(), escapeOldStyle, uses_terminal, depfile,
+      command_expand_lists);
   } else if (target.empty()) {
     // Target is empty, use the output.
     this->Makefile->AddCustomCommandToOutput(
       output, byproducts, depends, main_dependency, commandLines, comment,
-      working.c_str(), false, escapeOldStyle, uses_terminal, depfile);
+      working.c_str(), false, escapeOldStyle, uses_terminal,
+      command_expand_lists, depfile);
 
     // Add implicit dependency scanning requests if any were given.
     if (!implicit_depends.empty()) {

+ 12 - 1
Source/cmAddCustomTargetCommand.cxx

@@ -47,6 +47,7 @@ bool cmAddCustomTargetCommand::InitialPass(
   std::string working_directory;
   bool verbatim = false;
   bool uses_terminal = false;
+  bool command_expand_lists = false;
   std::string comment_buffer;
   const char* comment = CM_NULLPTR;
   std::vector<std::string> sources;
@@ -90,6 +91,9 @@ bool cmAddCustomTargetCommand::InitialPass(
     } else if (copy == "USES_TERMINAL") {
       doing = doing_nothing;
       uses_terminal = true;
+    } else if (copy == "COMMAND_EXPAND_LISTS") {
+      doing = doing_nothing;
+      command_expand_lists = true;
     } else if (copy == "COMMENT") {
       doing = doing_comment;
     } else if (copy == "COMMAND") {
@@ -221,12 +225,19 @@ bool cmAddCustomTargetCommand::InitialPass(
       "USES_TERMINAL may not be specified without any COMMAND");
     return true;
   }
+  if (commandLines.empty() && command_expand_lists) {
+    this->Makefile->IssueMessage(
+      cmake::FATAL_ERROR,
+      "COMMAND_EXPAND_LISTS may not be specified without any COMMAND");
+    return true;
+  }
 
   // Add the utility target to the makefile.
   bool escapeOldStyle = !verbatim;
   cmTarget* target = this->Makefile->AddUtilityCommand(
     targetName, excludeFromAll, working_directory.c_str(), byproducts, depends,
-    commandLines, escapeOldStyle, comment, uses_terminal);
+    commandLines, escapeOldStyle, comment, uses_terminal,
+    command_expand_lists);
 
   // Add additional user-specified source files to the target.
   target->AddSources(sources);

+ 12 - 0
Source/cmCustomCommand.cxx

@@ -13,6 +13,7 @@ cmCustomCommand::cmCustomCommand()
   this->EscapeOldStyle = true;
   this->EscapeAllowMakeVars = false;
   this->UsesTerminal = false;
+  this->CommandExpandLists = false;
 }
 
 cmCustomCommand::cmCustomCommand(cmMakefile const* mf,
@@ -32,6 +33,7 @@ cmCustomCommand::cmCustomCommand(cmMakefile const* mf,
   , HaveComment(comment != CM_NULLPTR)
   , EscapeAllowMakeVars(false)
   , EscapeOldStyle(true)
+  , CommandExpandLists(false)
 {
   if (mf) {
     this->Backtrace = mf->GetBacktrace();
@@ -127,6 +129,16 @@ void cmCustomCommand::SetUsesTerminal(bool b)
   this->UsesTerminal = b;
 }
 
+bool cmCustomCommand::GetCommandExpandLists() const
+{
+  return this->CommandExpandLists;
+}
+
+void cmCustomCommand::SetCommandExpandLists(bool b)
+{
+  this->CommandExpandLists = b;
+}
+
 const std::string& cmCustomCommand::GetDepfile() const
 {
   return this->Depfile;

+ 5 - 0
Source/cmCustomCommand.h

@@ -85,6 +85,10 @@ public:
   bool GetUsesTerminal() const;
   void SetUsesTerminal(bool b);
 
+  /** Set/Get whether lists in command lines should be expanded. */
+  bool GetCommandExpandLists() const;
+  void SetCommandExpandLists(bool b);
+
   /** Set/Get the depfile (used by the Ninja generator) */
   const std::string& GetDepfile() const;
   void SetDepfile(const std::string& depfile);
@@ -103,6 +107,7 @@ private:
   bool EscapeAllowMakeVars;
   bool EscapeOldStyle;
   bool UsesTerminal;
+  bool CommandExpandLists;
 };
 
 #endif

+ 23 - 9
Source/cmCustomCommandGenerator.cxx

@@ -26,6 +26,24 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
   , GE(new cmGeneratorExpression(cc.GetBacktrace()))
   , DependsDone(false)
 {
+  const cmCustomCommandLines& cmdlines = this->CC.GetCommandLines();
+  for (cmCustomCommandLines::const_iterator cmdline = cmdlines.begin();
+       cmdline != cmdlines.end(); ++cmdline) {
+    cmCustomCommandLine argv;
+    for (cmCustomCommandLine::const_iterator clarg = cmdline->begin();
+         clarg != cmdline->end(); ++clarg) {
+      CM_AUTO_PTR<cmCompiledGeneratorExpression> cge = this->GE->Parse(*clarg);
+      std::string parsed_arg = cge->Evaluate(this->LG, this->Config);
+      if (this->CC.GetCommandExpandLists()) {
+        std::vector<std::string> ExpandedArg;
+        cmSystemTools::ExpandListArgument(parsed_arg, ExpandedArg);
+        argv.insert(argv.end(), ExpandedArg.begin(), ExpandedArg.end());
+      } else {
+        argv.push_back(parsed_arg);
+      }
+    }
+    this->CommandLines.push_back(argv);
+  }
 }
 
 cmCustomCommandGenerator::~cmCustomCommandGenerator()
@@ -44,7 +62,7 @@ const char* cmCustomCommandGenerator::GetCrossCompilingEmulator(
   if (!this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
     return CM_NULLPTR;
   }
-  std::string const& argv0 = this->CC.GetCommandLines()[c][0];
+  std::string const& argv0 = this->CommandLines[c][0];
   cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
   if (target && target->GetType() == cmStateEnums::EXECUTABLE &&
       !target->IsImported()) {
@@ -55,7 +73,7 @@ const char* cmCustomCommandGenerator::GetCrossCompilingEmulator(
 
 const char* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const
 {
-  std::string const& argv0 = this->CC.GetCommandLines()[c][0];
+  std::string const& argv0 = this->CommandLines[c][0];
   cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(argv0);
   if (target && target->GetType() == cmStateEnums::EXECUTABLE &&
       (target->IsImported() ||
@@ -75,11 +93,7 @@ std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const
     return std::string(location);
   }
 
-  std::string const& argv0 = this->CC.GetCommandLines()[c][0];
-  CM_AUTO_PTR<cmCompiledGeneratorExpression> cge = this->GE->Parse(argv0);
-  std::string exe = cge->Evaluate(this->LG, this->Config);
-
-  return exe;
+  return this->CommandLines[c][0];
 }
 
 std::string escapeForShellOldStyle(const std::string& str)
@@ -114,7 +128,7 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c,
   if (this->GetCrossCompilingEmulator(c) != CM_NULLPTR) {
     offset = 0;
   }
-  cmCustomCommandLine const& commandLine = this->CC.GetCommandLines()[c];
+  cmCustomCommandLine const& commandLine = this->CommandLines[c];
   for (unsigned int j = offset; j < commandLine.size(); ++j) {
     std::string arg;
     if (const char* location =
@@ -123,7 +137,7 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c,
       // so transform the latter now.
       arg = location;
     } else {
-      arg = this->GE->Parse(commandLine[j])->Evaluate(this->LG, this->Config);
+      arg = commandLine[j];
     }
     cmd += " ";
     if (this->OldStyle) {

+ 2 - 0
Source/cmCustomCommandGenerator.h

@@ -3,6 +3,7 @@
 #ifndef cmCustomCommandGenerator_h
 #define cmCustomCommandGenerator_h
 
+#include "cmCustomCommandLines.h"
 #include <cmConfigure.h> // IWYU pragma: keep
 
 #include <string>
@@ -22,6 +23,7 @@ class cmCustomCommandGenerator
   cmGeneratorExpression* GE;
   mutable bool DependsDone;
   mutable std::vector<std::string> Depends;
+  cmCustomCommandLines CommandLines;
 
   const char* GetCrossCompilingEmulator(unsigned int c) const;
   const char* GetArgv0Location(unsigned int c) const;

+ 14 - 8
Source/cmMakefile.cxx

@@ -692,7 +692,7 @@ void cmMakefile::AddCustomCommandToTarget(
   const std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
   const char* comment, const char* workingDir, bool escapeOldStyle,
-  bool uses_terminal, const std::string& depfile)
+  bool uses_terminal, const std::string& depfile, bool command_expand_lists)
 {
   // Find the target to which to add the custom command.
   cmTargets::iterator ti = this->Targets.find(target);
@@ -764,6 +764,7 @@ void cmMakefile::AddCustomCommandToTarget(
   cc.SetEscapeOldStyle(escapeOldStyle);
   cc.SetEscapeAllowMakeVars(true);
   cc.SetUsesTerminal(uses_terminal);
+  cc.SetCommandExpandLists(command_expand_lists);
   cc.SetDepfile(depfile);
   switch (type) {
     case cmTarget::PRE_BUILD:
@@ -784,7 +785,7 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
   const std::vector<std::string>& depends, const std::string& main_dependency,
   const cmCustomCommandLines& commandLines, const char* comment,
   const char* workingDir, bool replace, bool escapeOldStyle,
-  bool uses_terminal, const std::string& depfile)
+  bool uses_terminal, bool command_expand_lists, const std::string& depfile)
 {
   // Make sure there is at least one output.
   if (outputs.empty()) {
@@ -878,6 +879,7 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
     cc->SetEscapeOldStyle(escapeOldStyle);
     cc->SetEscapeAllowMakeVars(true);
     cc->SetUsesTerminal(uses_terminal);
+    cc->SetCommandExpandLists(command_expand_lists);
     cc->SetDepfile(depfile);
     file->SetCustomCommand(cc);
     this->UpdateOutputToSourceMap(outputs, file);
@@ -916,14 +918,16 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
   const std::string& output, const std::vector<std::string>& depends,
   const std::string& main_dependency, const cmCustomCommandLines& commandLines,
   const char* comment, const char* workingDir, bool replace,
-  bool escapeOldStyle, bool uses_terminal, const std::string& depfile)
+  bool escapeOldStyle, bool uses_terminal, bool command_expand_lists,
+  const std::string& depfile)
 {
   std::vector<std::string> outputs;
   outputs.push_back(output);
   std::vector<std::string> no_byproducts;
   return this->AddCustomCommandToOutput(
     outputs, no_byproducts, depends, main_dependency, commandLines, comment,
-    workingDir, replace, escapeOldStyle, uses_terminal, depfile);
+    workingDir, replace, escapeOldStyle, uses_terminal, command_expand_lists,
+    depfile);
 }
 
 void cmMakefile::AddCustomCommandOldStyle(
@@ -1018,12 +1022,13 @@ cmTarget* cmMakefile::AddUtilityCommand(
   const std::string& utilityName, bool excludeFromAll,
   const char* workingDirectory, const std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, bool escapeOldStyle,
-  const char* comment, bool uses_terminal)
+  const char* comment, bool uses_terminal, bool command_expand_lists)
 {
   std::vector<std::string> no_byproducts;
   return this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory,
                                  no_byproducts, depends, commandLines,
-                                 escapeOldStyle, comment, uses_terminal);
+                                 escapeOldStyle, comment, uses_terminal,
+                                 command_expand_lists);
 }
 
 cmTarget* cmMakefile::AddUtilityCommand(
@@ -1031,7 +1036,7 @@ cmTarget* cmMakefile::AddUtilityCommand(
   const char* workingDirectory, const std::vector<std::string>& byproducts,
   const std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, bool escapeOldStyle,
-  const char* comment, bool uses_terminal)
+  const char* comment, bool uses_terminal, bool command_expand_lists)
 {
   // Create a target instance for this utility.
   cmTarget* target = this->AddNewTarget(cmStateEnums::UTILITY, utilityName);
@@ -1055,7 +1060,8 @@ cmTarget* cmMakefile::AddUtilityCommand(
     bool no_replace = false;
     this->AddCustomCommandToOutput(
       forced, byproducts, depends, no_main_dependency, commandLines, comment,
-      workingDirectory, no_replace, escapeOldStyle, uses_terminal);
+      workingDirectory, no_replace, escapeOldStyle, uses_terminal,
+      command_expand_lists);
     cmSourceFile* sf = target->AddSourceCMP0049(force);
 
     // The output is not actually created so mark it symbolic.

+ 10 - 5
Source/cmMakefile.h

@@ -125,7 +125,8 @@ public:
     const std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
     const char* comment, const char* workingDir, bool escapeOldStyle = true,
-    bool uses_terminal = false, const std::string& depfile = "");
+    bool uses_terminal = false, const std::string& depfile = "",
+    bool command_expand_lists = false);
   cmSourceFile* AddCustomCommandToOutput(
     const std::vector<std::string>& outputs,
     const std::vector<std::string>& byproducts,
@@ -133,13 +134,15 @@ public:
     const std::string& main_dependency,
     const cmCustomCommandLines& commandLines, const char* comment,
     const char* workingDir, bool replace = false, bool escapeOldStyle = true,
-    bool uses_terminal = false, const std::string& depfile = "");
+    bool uses_terminal = false, bool command_expand_lists = false,
+    const std::string& depfile = "");
   cmSourceFile* AddCustomCommandToOutput(
     const std::string& output, const std::vector<std::string>& depends,
     const std::string& main_dependency,
     const cmCustomCommandLines& commandLines, const char* comment,
     const char* workingDir, bool replace = false, bool escapeOldStyle = true,
-    bool uses_terminal = false, const std::string& depfile = "");
+    bool uses_terminal = false, bool command_expand_lists = false,
+    const std::string& depfile = "");
   void AddCustomCommandOldStyle(const std::string& target,
                                 const std::vector<std::string>& outputs,
                                 const std::vector<std::string>& depends,
@@ -182,13 +185,15 @@ public:
     const std::string& utilityName, bool excludeFromAll,
     const char* workingDirectory, const std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, bool escapeOldStyle = true,
-    const char* comment = CM_NULLPTR, bool uses_terminal = false);
+    const char* comment = CM_NULLPTR, bool uses_terminal = false,
+    bool command_expand_lists = false);
   cmTarget* AddUtilityCommand(
     const std::string& utilityName, bool excludeFromAll,
     const char* workingDirectory, const std::vector<std::string>& byproducts,
     const std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, bool escapeOldStyle = true,
-    const char* comment = CM_NULLPTR, bool uses_terminal = false);
+    const char* comment = CM_NULLPTR, bool uses_terminal = false,
+    bool command_expand_lists = false);
 
   /**
    * Add a subdirectory to the build.

+ 21 - 0
Tests/CustomCommand/CMakeLists.txt

@@ -513,3 +513,24 @@ add_custom_target(UseConsoleTarget ALL
   VERBATIM
   USES_TERMINAL
 )
+
+# Test COMMAND_EXPAND_LISTS
+set(cmp_args "1ARG=COMMAND_EXPAND_LISTS" "2ARG=test" "3ARG=outfile"
+  "4ARG=content")
+set(AARGS "")
+foreach(arg IN LISTS cmp_args)
+  list(APPEND AARGS "-DA${arg}")
+endforeach()
+
+set(gen_file "expand_custom_command.phony")
+add_custom_command(
+  OUTPUT "${gen_file}"
+  COMMAND ${CMAKE_COMMAND} ${AARGS}
+    "-DB$<JOIN:$<TARGET_PROPERTY:command_expand_lists,CMPARGS>,;-DB>"
+    "-P" "${CMAKE_CURRENT_SOURCE_DIR}/compare_options.cmake"
+    COMMAND_EXPAND_LISTS
+  VERBATIM
+)
+set_property(SOURCE "${gen_file}" PROPERTY SYMBOLIC ON)
+add_custom_target(command_expand_lists ALL DEPENDS "${gen_file}")
+set_property(TARGET command_expand_lists PROPERTY CMPARGS "${cmp_args}")

+ 14 - 0
Tests/CustomCommand/compare_options.cmake

@@ -0,0 +1,14 @@
+set(range 1 2 3 4 5 6 7 8 9 10)
+set(aargs "")
+set(bargs "")
+foreach(n IN LISTS range)
+  set(aval "${A${n}ARG}")
+  set(bval "${B${n}ARG}")
+  if(aval OR bval)
+    list(APPEND aargs "\"${aval}\"")
+    list(APPEND bargs "\"${bval}\"")
+  endif()
+endforeach()
+if(NOT "${aargs}" STREQUAL "${bargs}")
+  message(FATAL_ERROR "COMPARE_OPTIONS: \n\t${aargs} != \n\t${bargs}")
+endif()