Ver código fonte

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 anos atrás
pai
commit
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]
                      [WORKING_DIRECTORY dir]
                      [COMMENT comment]
                      [COMMENT comment]
                      [DEPFILE depfile]
                      [DEPFILE depfile]
-                     [VERBATIM] [APPEND] [USES_TERMINAL])
+                     [VERBATIM] [APPEND] [USES_TERMINAL]
+                     [COMMAND_EXPAND_LISTS])
 
 
 This defines a command to generate specified ``OUTPUT`` file(s).
 This defines a command to generate specified ``OUTPUT`` file(s).
 A target created in the same directory (``CMakeLists.txt`` file)
 A target created in the same directory (``CMakeLists.txt`` file)
@@ -122,6 +123,14 @@ The options are:
   Arguments to ``DEPENDS`` may use
   Arguments to ``DEPENDS`` may use
   :manual:`generator expressions <cmake-generator-expressions(7)>`.
   :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``
 ``IMPLICIT_DEPENDS``
   Request scanning of implicit dependencies of an input file.
   Request scanning of implicit dependencies of an input file.
   The language given specifies the programming language whose
   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]
                     [WORKING_DIRECTORY dir]
                     [COMMENT comment]
                     [COMMENT comment]
                     [VERBATIM] [USES_TERMINAL]
                     [VERBATIM] [USES_TERMINAL]
+                    [COMMAND_EXPAND_LISTS]
                     [SOURCES src1 [src2...]])
                     [SOURCES src1 [src2...]])
 
 
 Adds a target with the given name that executes the given commands.
 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
   Use the :command:`add_dependencies` command to add dependencies
   on other targets.
   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``
 ``SOURCES``
   Specify additional source files to be included in the custom target.
   Specify additional source files to be included in the custom target.
   Specified source files will be added to IDE project files for
   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 verbatim = false;
   bool append = false;
   bool append = false;
   bool uses_terminal = false;
   bool uses_terminal = false;
+  bool command_expand_lists = false;
   std::string implicit_depends_lang;
   std::string implicit_depends_lang;
   cmCustomCommand::ImplicitDependsList implicit_depends;
   cmCustomCommand::ImplicitDependsList implicit_depends;
 
 
@@ -92,6 +93,8 @@ bool cmAddCustomCommandCommand::InitialPass(
       append = true;
       append = true;
     } else if (copy == "USES_TERMINAL") {
     } else if (copy == "USES_TERMINAL") {
       uses_terminal = true;
       uses_terminal = true;
+    } else if (copy == "COMMAND_EXPAND_LISTS") {
+      command_expand_lists = true;
     } else if (copy == "TARGET") {
     } else if (copy == "TARGET") {
       doing = doing_target;
       doing = doing_target;
     } else if (copy == "ARGS") {
     } else if (copy == "ARGS") {
@@ -281,12 +284,14 @@ bool cmAddCustomCommandCommand::InitialPass(
     std::vector<std::string> no_depends;
     std::vector<std::string> no_depends;
     this->Makefile->AddCustomCommandToTarget(
     this->Makefile->AddCustomCommandToTarget(
       target, byproducts, no_depends, commandLines, cctype, comment,
       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()) {
   } else if (target.empty()) {
     // Target is empty, use the output.
     // Target is empty, use the output.
     this->Makefile->AddCustomCommandToOutput(
     this->Makefile->AddCustomCommandToOutput(
       output, byproducts, depends, main_dependency, commandLines, comment,
       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.
     // Add implicit dependency scanning requests if any were given.
     if (!implicit_depends.empty()) {
     if (!implicit_depends.empty()) {

+ 12 - 1
Source/cmAddCustomTargetCommand.cxx

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

+ 12 - 0
Source/cmCustomCommand.cxx

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

+ 5 - 0
Source/cmCustomCommand.h

@@ -85,6 +85,10 @@ public:
   bool GetUsesTerminal() const;
   bool GetUsesTerminal() const;
   void SetUsesTerminal(bool b);
   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) */
   /** Set/Get the depfile (used by the Ninja generator) */
   const std::string& GetDepfile() const;
   const std::string& GetDepfile() const;
   void SetDepfile(const std::string& depfile);
   void SetDepfile(const std::string& depfile);
@@ -103,6 +107,7 @@ private:
   bool EscapeAllowMakeVars;
   bool EscapeAllowMakeVars;
   bool EscapeOldStyle;
   bool EscapeOldStyle;
   bool UsesTerminal;
   bool UsesTerminal;
+  bool CommandExpandLists;
 };
 };
 
 
 #endif
 #endif

+ 23 - 9
Source/cmCustomCommandGenerator.cxx

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

+ 2 - 0
Source/cmCustomCommandGenerator.h

@@ -3,6 +3,7 @@
 #ifndef cmCustomCommandGenerator_h
 #ifndef cmCustomCommandGenerator_h
 #define cmCustomCommandGenerator_h
 #define cmCustomCommandGenerator_h
 
 
+#include "cmCustomCommandLines.h"
 #include <cmConfigure.h> // IWYU pragma: keep
 #include <cmConfigure.h> // IWYU pragma: keep
 
 
 #include <string>
 #include <string>
@@ -22,6 +23,7 @@ class cmCustomCommandGenerator
   cmGeneratorExpression* GE;
   cmGeneratorExpression* GE;
   mutable bool DependsDone;
   mutable bool DependsDone;
   mutable std::vector<std::string> Depends;
   mutable std::vector<std::string> Depends;
+  cmCustomCommandLines CommandLines;
 
 
   const char* GetCrossCompilingEmulator(unsigned int c) const;
   const char* GetCrossCompilingEmulator(unsigned int c) const;
   const char* GetArgv0Location(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 std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
   const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
   const char* comment, const char* workingDir, bool escapeOldStyle,
   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.
   // Find the target to which to add the custom command.
   cmTargets::iterator ti = this->Targets.find(target);
   cmTargets::iterator ti = this->Targets.find(target);
@@ -764,6 +764,7 @@ void cmMakefile::AddCustomCommandToTarget(
   cc.SetEscapeOldStyle(escapeOldStyle);
   cc.SetEscapeOldStyle(escapeOldStyle);
   cc.SetEscapeAllowMakeVars(true);
   cc.SetEscapeAllowMakeVars(true);
   cc.SetUsesTerminal(uses_terminal);
   cc.SetUsesTerminal(uses_terminal);
+  cc.SetCommandExpandLists(command_expand_lists);
   cc.SetDepfile(depfile);
   cc.SetDepfile(depfile);
   switch (type) {
   switch (type) {
     case cmTarget::PRE_BUILD:
     case cmTarget::PRE_BUILD:
@@ -784,7 +785,7 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
   const std::vector<std::string>& depends, const std::string& main_dependency,
   const std::vector<std::string>& depends, const std::string& main_dependency,
   const cmCustomCommandLines& commandLines, const char* comment,
   const cmCustomCommandLines& commandLines, const char* comment,
   const char* workingDir, bool replace, bool escapeOldStyle,
   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.
   // Make sure there is at least one output.
   if (outputs.empty()) {
   if (outputs.empty()) {
@@ -878,6 +879,7 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
     cc->SetEscapeOldStyle(escapeOldStyle);
     cc->SetEscapeOldStyle(escapeOldStyle);
     cc->SetEscapeAllowMakeVars(true);
     cc->SetEscapeAllowMakeVars(true);
     cc->SetUsesTerminal(uses_terminal);
     cc->SetUsesTerminal(uses_terminal);
+    cc->SetCommandExpandLists(command_expand_lists);
     cc->SetDepfile(depfile);
     cc->SetDepfile(depfile);
     file->SetCustomCommand(cc);
     file->SetCustomCommand(cc);
     this->UpdateOutputToSourceMap(outputs, file);
     this->UpdateOutputToSourceMap(outputs, file);
@@ -916,14 +918,16 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput(
   const std::string& output, const std::vector<std::string>& depends,
   const std::string& output, const std::vector<std::string>& depends,
   const std::string& main_dependency, const cmCustomCommandLines& commandLines,
   const std::string& main_dependency, const cmCustomCommandLines& commandLines,
   const char* comment, const char* workingDir, bool replace,
   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;
   std::vector<std::string> outputs;
   outputs.push_back(output);
   outputs.push_back(output);
   std::vector<std::string> no_byproducts;
   std::vector<std::string> no_byproducts;
   return this->AddCustomCommandToOutput(
   return this->AddCustomCommandToOutput(
     outputs, no_byproducts, depends, main_dependency, commandLines, comment,
     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(
 void cmMakefile::AddCustomCommandOldStyle(
@@ -1018,12 +1022,13 @@ cmTarget* cmMakefile::AddUtilityCommand(
   const std::string& utilityName, bool excludeFromAll,
   const std::string& utilityName, bool excludeFromAll,
   const char* workingDirectory, const std::vector<std::string>& depends,
   const char* workingDirectory, const std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, bool escapeOldStyle,
   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;
   std::vector<std::string> no_byproducts;
   return this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory,
   return this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory,
                                  no_byproducts, depends, commandLines,
                                  no_byproducts, depends, commandLines,
-                                 escapeOldStyle, comment, uses_terminal);
+                                 escapeOldStyle, comment, uses_terminal,
+                                 command_expand_lists);
 }
 }
 
 
 cmTarget* cmMakefile::AddUtilityCommand(
 cmTarget* cmMakefile::AddUtilityCommand(
@@ -1031,7 +1036,7 @@ cmTarget* cmMakefile::AddUtilityCommand(
   const char* workingDirectory, const std::vector<std::string>& byproducts,
   const char* workingDirectory, const std::vector<std::string>& byproducts,
   const std::vector<std::string>& depends,
   const std::vector<std::string>& depends,
   const cmCustomCommandLines& commandLines, bool escapeOldStyle,
   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.
   // Create a target instance for this utility.
   cmTarget* target = this->AddNewTarget(cmStateEnums::UTILITY, utilityName);
   cmTarget* target = this->AddNewTarget(cmStateEnums::UTILITY, utilityName);
@@ -1055,7 +1060,8 @@ cmTarget* cmMakefile::AddUtilityCommand(
     bool no_replace = false;
     bool no_replace = false;
     this->AddCustomCommandToOutput(
     this->AddCustomCommandToOutput(
       forced, byproducts, depends, no_main_dependency, commandLines, comment,
       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);
     cmSourceFile* sf = target->AddSourceCMP0049(force);
 
 
     // The output is not actually created so mark it symbolic.
     // 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 std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
     const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type,
     const char* comment, const char* workingDir, bool escapeOldStyle = true,
     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(
   cmSourceFile* AddCustomCommandToOutput(
     const std::vector<std::string>& outputs,
     const std::vector<std::string>& outputs,
     const std::vector<std::string>& byproducts,
     const std::vector<std::string>& byproducts,
@@ -133,13 +134,15 @@ public:
     const std::string& main_dependency,
     const std::string& main_dependency,
     const cmCustomCommandLines& commandLines, const char* comment,
     const cmCustomCommandLines& commandLines, const char* comment,
     const char* workingDir, bool replace = false, bool escapeOldStyle = true,
     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(
   cmSourceFile* AddCustomCommandToOutput(
     const std::string& output, const std::vector<std::string>& depends,
     const std::string& output, const std::vector<std::string>& depends,
     const std::string& main_dependency,
     const std::string& main_dependency,
     const cmCustomCommandLines& commandLines, const char* comment,
     const cmCustomCommandLines& commandLines, const char* comment,
     const char* workingDir, bool replace = false, bool escapeOldStyle = true,
     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,
   void AddCustomCommandOldStyle(const std::string& target,
                                 const std::vector<std::string>& outputs,
                                 const std::vector<std::string>& outputs,
                                 const std::vector<std::string>& depends,
                                 const std::vector<std::string>& depends,
@@ -182,13 +185,15 @@ public:
     const std::string& utilityName, bool excludeFromAll,
     const std::string& utilityName, bool excludeFromAll,
     const char* workingDirectory, const std::vector<std::string>& depends,
     const char* workingDirectory, const std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, bool escapeOldStyle = true,
     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(
   cmTarget* AddUtilityCommand(
     const std::string& utilityName, bool excludeFromAll,
     const std::string& utilityName, bool excludeFromAll,
     const char* workingDirectory, const std::vector<std::string>& byproducts,
     const char* workingDirectory, const std::vector<std::string>& byproducts,
     const std::vector<std::string>& depends,
     const std::vector<std::string>& depends,
     const cmCustomCommandLines& commandLines, bool escapeOldStyle = true,
     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.
    * Add a subdirectory to the build.

+ 21 - 0
Tests/CustomCommand/CMakeLists.txt

@@ -513,3 +513,24 @@ add_custom_target(UseConsoleTarget ALL
   VERBATIM
   VERBATIM
   USES_TERMINAL
   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()