Selaa lähdekoodia

Merge topic 'custom-command-output-genex-nmc'

1526ae3aba Tests: Add cases for Ninja Multi-Config cross-config custom commands
dcf9f4d2f7 Ninja Multi-Config: Add support for cross-config custom commands
15467f12f7 cmLocalGenerator: Adopt custom target 'force' output name generation
7b64b0cd5a cmLocalGenerator: Refactor custom command generator construction
d29da8ed3e cmMakefile: Simplify custom target 'force' output name generation
2b1cc175ee Help: Clarify version adding add_custom_{command,target} OUTPUT genex support

Acked-by: Kitware Robot <[email protected]>
Merge-request: !5612
Brad King 4 vuotta sitten
vanhempi
sitoutus
34469a4f71
83 muutettua tiedostoa jossa 1273 lisäystä ja 255 poistoa
  1. 45 32
      Help/command/add_custom_command.rst
  2. 12 2
      Help/command/add_custom_target.rst
  3. 44 0
      Help/generator/Ninja Multi-Config.rst
  4. 18 0
      Help/manual/cmake-generator-expressions.7.rst
  5. 5 0
      Help/release/dev/custom-command-output-genex.rst
  6. 110 22
      Source/cmCustomCommandGenerator.cxx
  7. 9 2
      Source/cmCustomCommandGenerator.h
  8. 0 7
      Source/cmCustomCommandTypes.h
  9. 9 8
      Source/cmGeneratorTarget.cxx
  10. 83 14
      Source/cmGlobalNinjaGenerator.cxx
  11. 42 3
      Source/cmGlobalNinjaGenerator.h
  12. 33 8
      Source/cmLocalGenerator.cxx
  13. 9 1
      Source/cmLocalGenerator.h
  14. 227 106
      Source/cmLocalNinjaGenerator.cxx
  15. 11 3
      Source/cmLocalNinjaGenerator.h
  16. 2 27
      Source/cmMakefile.cxx
  17. 0 5
      Source/cmMakefile.h
  18. 42 11
      Source/cmNinjaUtilityTargetGenerator.cxx
  19. 4 0
      Source/cmNinjaUtilityTargetGenerator.h
  20. 4 4
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_tgt.json
  21. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex-debug-in-release-graph-ninja-stdout.txt
  22. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex-debug-ninja-stdout.txt
  23. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_cmd-debug-in-release-graph-ninja-stdout.txt
  24. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_cmd-debug-ninja-stdout.txt
  25. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_out-debug-in-release-graph-ninja-stdout.txt
  26. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_out-debug-ninja-stdout.txt
  27. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_raw-debug-in-release-graph-ninja-stdout.txt
  28. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_raw-debug-ninja-stdout.txt
  29. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend-debug-in-release-graph-ninja-stdout.txt
  30. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend-debug-ninja-stdout.txt
  31. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_cmd-debug-in-release-graph-ninja-stdout.txt
  32. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_cmd-debug-ninja-stdout.txt
  33. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_out-debug-in-release-graph-ninja-stdout.txt
  34. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_out-debug-ninja-stdout.txt
  35. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex-debug-in-release-graph-ninja-stdout.txt
  36. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex-debug-ninja-stdout.txt
  37. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex_out-debug-in-release-graph-ninja-stdout.txt
  38. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex_out-debug-ninja-stdout.txt
  39. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-in-release-graph-ninja-result.txt
  40. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-in-release-graph-ninja-stderr.txt
  41. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-ninja-stdout.txt
  42. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-in-release-graph-ninja-result.txt
  43. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-in-release-graph-ninja-stderr.txt
  44. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-ninja-stdout.txt
  45. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-in-release-graph-ninja-result.txt
  46. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-in-release-graph-ninja-stderr.txt
  47. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-ninja-stdout.txt
  48. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-in-release-graph-ninja-result.txt
  49. 1 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-in-release-graph-ninja-stderr.txt
  50. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-ninja-stdout.txt
  51. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_raw-debug-in-release-graph-ninja-stdout.txt
  52. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_raw-debug-ninja-stdout.txt
  53. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend-debug-in-release-graph-ninja-stdout.txt
  54. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend-debug-ninja-stdout.txt
  55. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_cmd-debug-in-release-graph-ninja-stdout.txt
  56. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_cmd-debug-ninja-stdout.txt
  57. 5 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_out-debug-in-release-graph-ninja-stdout.txt
  58. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_out-debug-ninja-stdout.txt
  59. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex-debug-in-release-graph-ninja-stdout.txt
  60. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex-debug-ninja-stdout.txt
  61. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex_out-debug-in-release-graph-ninja-stdout.txt
  62. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex_out-debug-ninja-stdout.txt
  63. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_raw-debug-in-release-graph-ninja-stdout.txt
  64. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_raw-debug-ninja-stdout.txt
  65. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-target_no_cross_byproduct-debug-in-release-graph-ninja-stdout.txt
  66. 4 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-target_no_cross_byproduct-debug-ninja-stdout.txt
  67. 167 0
      Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex.cmake
  68. 96 0
      Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
  69. 21 0
      Tests/RunCMake/NinjaMultiConfig/echo.c
  70. 20 0
      Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt
  71. 2 0
      Tests/RunCMake/add_custom_command/BadByproduct.cmake
  72. 1 0
      Tests/RunCMake/add_custom_command/BadCommand-result.txt
  73. 21 0
      Tests/RunCMake/add_custom_command/BadCommand-stderr.txt
  74. 3 0
      Tests/RunCMake/add_custom_command/BadCommand.cmake
  75. 20 0
      Tests/RunCMake/add_custom_command/BadOutput-stderr.txt
  76. 2 0
      Tests/RunCMake/add_custom_command/BadOutput.cmake
  77. 1 0
      Tests/RunCMake/add_custom_command/RunCMakeTest.cmake
  78. 20 0
      Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt
  79. 2 0
      Tests/RunCMake/add_custom_target/BadByproduct.cmake
  80. 1 0
      Tests/RunCMake/add_custom_target/BadCommand-result.txt
  81. 21 0
      Tests/RunCMake/add_custom_target/BadCommand-stderr.txt
  82. 4 0
      Tests/RunCMake/add_custom_target/BadCommand.cmake
  83. 1 0
      Tests/RunCMake/add_custom_target/RunCMakeTest.cmake

+ 45 - 32
Help/command/add_custom_command.rst

@@ -79,8 +79,9 @@ The options are:
   The :ref:`Makefile Generators` will remove ``BYPRODUCTS`` and other
   :prop_sf:`GENERATED` files during ``make clean``.
 
-  Since CMake 3.20, arguments to ``BYPRODUCTS`` may use
-  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+  .. versionadded:: 3.20
+    Arguments to ``BYPRODUCTS`` may use
+    :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
 ``COMMAND``
   Specify the command-line(s) to execute at build time.
@@ -229,8 +230,9 @@ The options are:
   as a file on disk it should be marked with the :prop_sf:`SYMBOLIC`
   source file property.
 
-  Since CMake 3.20, arguments to ``OUTPUT`` may use
-  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+  .. versionadded:: 3.20
+    Arguments to ``OUTPUT`` may use
+    :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
 ``USES_TERMINAL``
   .. versionadded:: 3.2
@@ -291,23 +293,24 @@ adds a custom command to run ``someTool`` to generate ``out.c`` and then
 compile the generated source as part of a library.  The generation rule
 will re-run whenever ``in.txt`` changes.
 
-Since CMake 3.20, one may use generator expressions to specify
-per-configuration outputs.  For example, the code:
+.. versionadded:: 3.20
+  One may use generator expressions to specify per-configuration outputs.
+  For example, the code:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  add_custom_command(
-    OUTPUT "out-$<CONFIG>.c"
-    COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
-                     -o "out-$<CONFIG>.c"
-                     -c "$<CONFIG>"
-    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
-    VERBATIM)
-  add_library(myLib "out-$<CONFIG>.c")
+    add_custom_command(
+      OUTPUT "out-$<CONFIG>.c"
+      COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
+                       -o "out-$<CONFIG>.c"
+                       -c "$<CONFIG>"
+      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
+      VERBATIM)
+    add_library(myLib "out-$<CONFIG>.c")
 
-adds a custom command to run ``someTool`` to generate ``out-<config>.c``,
-where ``<config>`` is the build configuration, and then compile the generated
-source as part of a library.
+  adds a custom command to run ``someTool`` to generate ``out-<config>.c``,
+  where ``<config>`` is the build configuration, and then compile the generated
+  source as part of a library.
 
 Build Events
 ^^^^^^^^^^^^
@@ -377,20 +380,30 @@ For example, the code:
 will run ``someHasher`` to produce a ``.hash`` file next to the executable
 after linking.
 
-Since CMake 3.20, one may use generator expressions to specify
-per-configuration byproducts.  For example, the code:
+.. versionadded:: 3.20
+  One may use generator expressions to specify per-configuration byproducts.
+  For example, the code:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  add_library(myPlugin MODULE myPlugin.c)
-  add_custom_command(
-    TARGET myPlugin POST_BUILD
-    COMMAND someHasher -i "$<TARGET_FILE:myPlugin>"
-                       --as-code "myPlugin-hash-$<CONFIG>.c"
-    BYPRODUCTS "myPlugin-hash-$<CONFIG>.c"
-    VERBATIM)
-  add_executable(myExe myExe.c "myPlugin-hash-$<CONFIG>.c")
+    add_library(myPlugin MODULE myPlugin.c)
+    add_custom_command(
+      TARGET myPlugin POST_BUILD
+      COMMAND someHasher -i "$<TARGET_FILE:myPlugin>"
+                         --as-code "myPlugin-hash-$<CONFIG>.c"
+      BYPRODUCTS "myPlugin-hash-$<CONFIG>.c"
+      VERBATIM)
+    add_executable(myExe myExe.c "myPlugin-hash-$<CONFIG>.c")
+
+  will run ``someHasher`` after linking ``myPlugin``, e.g. to produce a ``.c``
+  file containing code to check the hash of ``myPlugin`` that the ``myExe``
+  executable can use to verify it before loading.
+
+Ninja Multi-Config
+^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.20
 
-will run ``someHasher`` after linking ``myPlugin``, e.g. to produce a ``.c``
-file containing code to check the hash of ``myPlugin`` that the ``myExe``
-executable can use to verify it before loading.
+  ``add_custom_command`` supports the :generator:`Ninja Multi-Config`
+  generator's cross-config capabilities. See the generator documentation
+  for more information.

+ 12 - 2
Help/command/add_custom_target.rst

@@ -54,8 +54,9 @@ The options are:
   The :ref:`Makefile Generators` will remove ``BYPRODUCTS`` and other
   :prop_sf:`GENERATED` files during ``make clean``.
 
-  Since CMake 3.20, arguments to ``BYPRODUCTS`` may use
-  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+  .. versionadded:: 3.20
+    Arguments to ``BYPRODUCTS`` may use
+    :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
 ``COMMAND``
   Specify the command-line(s) to execute at build time.
@@ -167,3 +168,12 @@ The options are:
   .. versionadded:: 3.13
     Arguments to ``WORKING_DIRECTORY`` may use
     :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+Ninja Multi-Config
+^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.20
+
+  ``add_custom_target`` supports the :generator:`Ninja Multi-Config`
+  generator's cross-config capabilities. See the generator documentation
+  for more information.

+ 44 - 0
Help/generator/Ninja Multi-Config.rst

@@ -86,3 +86,47 @@ used to generate ``generated.c``, which would be used to build the ``Debug``
 configuration of ``generated``. This is useful for running a release-optimized
 version of a generator utility while still building the debug version of the
 targets built with the generated code.
+
+Custom Commands
+^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.20
+
+The ``Ninja Multi-Config`` generator adds extra capabilities to
+:command:`add_custom_command` and :command:`add_custom_target` through its
+cross-config mode. The ``COMMAND``, ``DEPENDS``, and ``WORKING_DIRECTORY``
+arguments can be evaluated in the context of either the "command config" (the
+"native" configuration of the ``build-<Config>.ninja`` file in use) or the
+"output config" (the configuration used to evaluate the ``OUTPUT`` and
+``BYPRODUCTS``).
+
+If either ``OUTPUT`` or ``BYPRODUCTS`` names a path that is common to
+more than one configuration (e.g. it does not use any generator expressions),
+all arguments are evaluated in the command config by default.
+If all ``OUTPUT`` and ``BYPRODUCTS`` paths are unique to each configuration
+(e.g. by using the ``$<CONFIG>`` generator expression), the first argument of
+``COMMAND`` is still evaluated in the command config by default, while all
+subsequent arguments, as well as the arguments to ``DEPENDS`` and
+``WORKING_DIRECTORY``, are evaluated in the output config. These defaults can
+be overridden with the ``$<OUTPUT_CONFIG:...>`` and ``$<COMMAND_CONFIG:...>``
+generator-expressions. Note that if a target is specified by its name in
+``DEPENDS``, or as the first argument of ``COMMAND``, it is always evaluated
+in the command config, even if it is wrapped in ``$<OUTPUT_CONFIG:...>``
+(because its plain name is not a generator expression).
+
+As an example, consider the following:
+
+.. code-block:: cmake
+
+  add_custom_command(
+    OUTPUT "$<CONFIG>.txt"
+    COMMAND generator "$<CONFIG>.txt" "$<OUTPUT_CONFIG:$<CONFIG>>" "$<COMMAND_CONFIG:$<CONFIG>>"
+    DEPENDS tgt1 "$<TARGET_FILE:tgt2>" "$<OUTPUT_CONFIG:$<TARGET_FILE:tgt3>>" "$<COMMAND_CONFIG:$<TARGET_FILE:tgt4>>"
+    )
+
+Assume that ``generator``, ``tgt1``, ``tgt2``, ``tgt3``, and ``tgt4`` are all
+executable targets, and assume that ``$<CONFIG>.txt`` is built in the ``Debug``
+output config using the ``Release`` command config. The ``Release`` build of
+the ``generator`` target is called with ``Debug.txt Debug Release`` as
+arguments. The command depends on the ``Release`` builds of ``tgt1`` and
+``tgt4``, and the ``Debug`` builds of ``tgt2`` and ``tgt3``.

+ 18 - 0
Help/manual/cmake-generator-expressions.7.rst

@@ -816,6 +816,24 @@ Output-Related Expressions
   ``;`` on Windows).  Be sure to enclose the argument containing this genex
   in double quotes in CMake source code so that ``;`` does not split arguments.
 
+``$<OUTPUT_CONFIG:...>``
+  .. versionadded:: 3.20
+
+  Only valid in :command:`add_custom_command` and :command:`add_custom_target`
+  as the outer-most generator expression in an argument.
+  With the :generator:`Ninja Multi-Config` generator, generator expressions
+  in ``...`` are evaluated using the custom command's "output config".
+  With other generators, the content of ``...`` is evaluated normally.
+
+``$<COMMAND_CONFIG:...>``
+  .. versionadded:: 3.20
+
+  Only valid in :command:`add_custom_command` and :command:`add_custom_target`
+  as the outer-most generator expression in an argument.
+  With the :generator:`Ninja Multi-Config` generator, generator expressions
+  in ``...`` are evaluated using the custom command's "command config".
+  With other generators, the content of ``...`` is evaluated normally.
+
 Debugging
 =========
 

+ 5 - 0
Help/release/dev/custom-command-output-genex.rst

@@ -4,3 +4,8 @@ custom-command-output-genex
 * :command:`add_custom_command` and :command:`add_custom_target` now
   support :manual:`generator expressions <cmake-generator-expressions(7)>`
   in their ``OUTPUT`` and ``BYPRODUCTS`` options.
+
+  Their ``COMMAND``, ``WORKING_DIRECTORY``, and ``DEPENDS`` options gained
+  support for new generator expressions ``$<COMMAND_CONFIG:...>`` and
+  ``$<OUTPUT_CONFIG:...>`` that control cross-config handling when using
+  the :generator:`Ninja Multi-Config` generator.

+ 110 - 22
Source/cmCustomCommandGenerator.cxx

@@ -7,7 +7,9 @@
 #include <utility>
 
 #include <cm/optional>
+#include <cm/string_view>
 #include <cmext/algorithm>
+#include <cmext/string_view>
 
 #include "cmCryptoHash.h"
 #include "cmCustomCommand.h"
@@ -24,15 +26,95 @@
 #include "cmTransformDepfile.h"
 
 namespace {
+std::string EvaluateSplitConfigGenex(
+  cm::string_view input, cmGeneratorExpression const& ge, cmLocalGenerator* lg,
+  bool useOutputConfig, std::string const& outputConfig,
+  std::string const& commandConfig,
+  std::set<BT<std::pair<std::string, bool>>>* utils = nullptr)
+{
+  std::string result;
+
+  while (!input.empty()) {
+    // Copy non-genex content directly to the result.
+    std::string::size_type pos = input.find("$<");
+    result += input.substr(0, pos);
+    if (pos == std::string::npos) {
+      break;
+    }
+    input = input.substr(pos);
+
+    // Find the balanced end of this regex.
+    size_t nestingLevel = 1;
+    for (pos = 2; pos < input.size(); ++pos) {
+      cm::string_view cur = input.substr(pos);
+      if (cmHasLiteralPrefix(cur, "$<")) {
+        ++nestingLevel;
+        ++pos;
+        continue;
+      }
+      if (cmHasLiteralPrefix(cur, ">")) {
+        --nestingLevel;
+        if (nestingLevel == 0) {
+          ++pos;
+          break;
+        }
+      }
+    }
+
+    // Split this genex from following input.
+    cm::string_view genex = input.substr(0, pos);
+    input = input.substr(pos);
+
+    // Convert an outer COMMAND_CONFIG or OUTPUT_CONFIG to the matching config.
+    std::string const* config =
+      useOutputConfig ? &outputConfig : &commandConfig;
+    if (nestingLevel == 0) {
+      static cm::string_view const COMMAND_CONFIG = "$<COMMAND_CONFIG:"_s;
+      static cm::string_view const OUTPUT_CONFIG = "$<OUTPUT_CONFIG:"_s;
+      if (cmHasPrefix(genex, COMMAND_CONFIG)) {
+        genex.remove_prefix(COMMAND_CONFIG.size());
+        genex.remove_suffix(1);
+        useOutputConfig = false;
+        config = &commandConfig;
+      } else if (cmHasPrefix(genex, OUTPUT_CONFIG)) {
+        genex.remove_prefix(OUTPUT_CONFIG.size());
+        genex.remove_suffix(1);
+        useOutputConfig = true;
+        config = &outputConfig;
+      }
+    }
+
+    // Evaluate this genex in the selected configuration.
+    std::unique_ptr<cmCompiledGeneratorExpression> cge =
+      ge.Parse(std::string(genex));
+    result += cge->Evaluate(lg, *config);
+
+    // Record targets referenced by the genex.
+    if (utils) {
+      // FIXME: What is the proper condition for a cross-dependency?
+      bool const cross = !useOutputConfig;
+      for (cmGeneratorTarget* gt : cge->GetTargets()) {
+        utils->emplace(BT<std::pair<std::string, bool>>(
+          { gt->GetName(), cross }, cge->GetBacktrace()));
+      }
+    }
+  }
+
+  return result;
+}
+
 std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths,
                                          cmGeneratorExpression const& ge,
                                          cmLocalGenerator* lg,
-                                         std::string const& config)
+                                         std::string const& outputConfig,
+                                         std::string const& commandConfig)
 {
   std::vector<std::string> depends;
   for (std::string const& p : paths) {
-    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
-    std::string const& ep = cge->Evaluate(lg, config);
+    std::string const& ep =
+      EvaluateSplitConfigGenex(p, ge, lg, /*useOutputConfig=*/true,
+                               /*outputConfig=*/outputConfig,
+                               /*commandConfig=*/commandConfig);
     cm::append(depends, cmExpandedList(ep));
   }
   for (std::string& p : depends) {
@@ -59,12 +141,12 @@ std::vector<std::string> EvaluateOutputs(std::vector<std::string> const& paths,
 }
 }
 
-cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
-                                                   std::string config,
-                                                   cmLocalGenerator* lg,
-                                                   bool transformDepfile)
+cmCustomCommandGenerator::cmCustomCommandGenerator(
+  cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg,
+  bool transformDepfile, cm::optional<std::string> crossConfig)
   : CC(&cc)
-  , Config(std::move(config))
+  , OutputConfig(crossConfig ? *crossConfig : config)
+  , CommandConfig(std::move(config))
   , LG(lg)
   , OldStyle(cc.GetEscapeOldStyle())
   , MakeVars(cc.GetEscapeAllowMakeVars())
@@ -75,18 +157,20 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
   const cmCustomCommandLines& cmdlines = this->CC->GetCommandLines();
   for (cmCustomCommandLine const& cmdline : cmdlines) {
     cmCustomCommandLine argv;
+    // For the command itself, we default to the COMMAND_CONFIG.
+    bool useOutputConfig = false;
     for (std::string const& clarg : cmdline) {
-      std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(clarg);
-      std::string parsed_arg = cge->Evaluate(this->LG, this->Config);
-      for (cmGeneratorTarget* gt : cge->GetTargets()) {
-        this->Utilities.emplace(BT<std::pair<std::string, bool>>(
-          { gt->GetName(), true }, cge->GetBacktrace()));
-      }
+      std::string parsed_arg = EvaluateSplitConfigGenex(
+        clarg, ge, this->LG, useOutputConfig, this->OutputConfig,
+        this->CommandConfig, &this->Utilities);
       if (this->CC->GetCommandExpandLists()) {
         cm::append(argv, cmExpandedList(parsed_arg));
       } else {
         argv.push_back(std::move(parsed_arg));
       }
+
+      // For remaining arguments, we default to the OUTPUT_CONFIG.
+      useOutputConfig = true;
     }
 
     if (!argv.empty()) {
@@ -94,8 +178,10 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
       // collect the target to add a target-level dependency on it.
       cmGeneratorTarget* gt = this->LG->FindGeneratorTargetToUse(argv.front());
       if (gt && gt->GetType() == cmStateEnums::EXECUTABLE) {
+        // FIXME: What is the proper condition for a cross-dependency?
+        bool const cross = true;
         this->Utilities.emplace(BT<std::pair<std::string, bool>>(
-          { gt->GetName(), true }, cc.GetBacktrace()));
+          { gt->GetName(), cross }, cc.GetBacktrace()));
       }
     } else {
       // Later code assumes at least one entry exists, but expanding
@@ -137,16 +223,18 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
     this->CommandLines.push_back(std::move(argv));
   }
 
-  this->Outputs = EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->Config);
+  this->Outputs =
+    EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->OutputConfig);
   this->Byproducts =
-    EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->Config);
-  this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG, this->Config);
+    EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->OutputConfig);
+  this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG,
+                                  this->OutputConfig, this->CommandConfig);
 
   const std::string& workingdirectory = this->CC->GetWorkingDirectory();
   if (!workingdirectory.empty()) {
-    std::unique_ptr<cmCompiledGeneratorExpression> cge =
-      ge.Parse(workingdirectory);
-    this->WorkingDirectory = cge->Evaluate(this->LG, this->Config);
+    this->WorkingDirectory =
+      EvaluateSplitConfigGenex(workingdirectory, ge, this->LG, true,
+                               this->OutputConfig, this->CommandConfig);
     // Convert working directory to a full path.
     if (!this->WorkingDirectory.empty()) {
       std::string const& build_dir = this->LG->GetCurrentBinaryDirectory();
@@ -203,7 +291,7 @@ const char* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const
       (target->IsImported() ||
        target->GetProperty("CROSSCOMPILING_EMULATOR") ||
        !this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING"))) {
-    return target->GetLocation(this->Config).c_str();
+    return target->GetLocation(this->CommandConfig).c_str();
   }
   return nullptr;
 }

+ 9 - 2
Source/cmCustomCommandGenerator.h

@@ -9,6 +9,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmCustomCommandLines.h"
 #include "cmListFileCache.h"
 
@@ -18,7 +20,8 @@ class cmLocalGenerator;
 class cmCustomCommandGenerator
 {
   cmCustomCommand const* CC;
-  std::string Config;
+  std::string OutputConfig;
+  std::string CommandConfig;
   cmLocalGenerator* LG;
   bool OldStyle;
   bool MakeVars;
@@ -36,7 +39,8 @@ class cmCustomCommandGenerator
 
 public:
   cmCustomCommandGenerator(cmCustomCommand const& cc, std::string config,
-                           cmLocalGenerator* lg, bool transformDepfile = true);
+                           cmLocalGenerator* lg, bool transformDepfile = true,
+                           cm::optional<std::string> crossConfig = {});
   cmCustomCommandGenerator(const cmCustomCommandGenerator&) = delete;
   cmCustomCommandGenerator(cmCustomCommandGenerator&&) = default;
   cmCustomCommandGenerator& operator=(const cmCustomCommandGenerator&) =
@@ -55,4 +59,7 @@ public:
   bool HasOnlyEmptyCommandLines() const;
   std::string GetFullDepfile() const;
   std::string GetInternalDepfile() const;
+
+  const std::string& GetOutputConfig() const { return this->OutputConfig; }
+  const std::string& GetCommandConfig() const { return this->CommandConfig; }
 };

+ 0 - 7
Source/cmCustomCommandTypes.h

@@ -27,10 +27,3 @@ enum class cmObjectLibraryCommands
   Reject,
   Accept
 };
-
-/** Utility target output source file name.  */
-struct cmUtilityOutput
-{
-  std::string Name;
-  std::string NameCMP0049;
-};

+ 9 - 8
Source/cmGeneratorTarget.cxx

@@ -3064,7 +3064,7 @@ bool cmTargetTraceDependencies::IsUtility(std::string const& dep)
     } else {
       // The original name of the dependency was not a full path.  It
       // must name a target, so add the target-level dependency.
-      this->GeneratorTarget->Target->AddUtility(util, false);
+      this->GeneratorTarget->Target->AddUtility(util, true);
       return true;
     }
   }
@@ -3079,15 +3079,16 @@ void cmTargetTraceDependencies::CheckCustomCommand(cmCustomCommand const& cc)
   std::set<std::string> depends;
   for (std::string const& config :
        this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) {
-    cmCustomCommandGenerator ccg(cc, config, this->LocalGenerator);
+    for (cmCustomCommandGenerator const& ccg :
+         this->LocalGenerator->MakeCustomCommandGenerators(cc, config)) {
+      // Collect target-level dependencies referenced in command lines.
+      for (auto const& util : ccg.GetUtilities()) {
+        this->GeneratorTarget->Target->AddUtility(util);
+      }
 
-    // Collect target-level dependencies referenced in command lines.
-    for (auto const& util : ccg.GetUtilities()) {
-      this->GeneratorTarget->Target->AddUtility(util);
+      // Collect file-level dependencies referenced in DEPENDS.
+      depends.insert(ccg.GetDepends().begin(), ccg.GetDepends().end());
     }
-
-    // Collect file-level dependencies referenced in DEPENDS.
-    depends.insert(ccg.GetDepends().begin(), ccg.GetDepends().end());
   }
 
   // Queue file-level dependencies.

+ 83 - 14
Source/cmGlobalNinjaGenerator.cxx

@@ -56,6 +56,52 @@ std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
 #endif
 
+bool operator==(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return lhs.Target == rhs.Target && lhs.Config == rhs.Config &&
+    lhs.GenexOutput == rhs.GenexOutput;
+}
+
+bool operator!=(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return !(lhs == rhs);
+}
+
+bool operator<(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return lhs.Target < rhs.Target ||
+    (lhs.Target == rhs.Target &&
+     (lhs.Config < rhs.Config ||
+      (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput)));
+}
+
+bool operator>(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return rhs < lhs;
+}
+
+bool operator<=(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return !(lhs > rhs);
+}
+
+bool operator>=(
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
+  const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
+{
+  return rhs <= lhs;
+}
+
 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
 {
   for (int i = 0; i < count; ++i) {
@@ -1194,23 +1240,30 @@ void cmGlobalNinjaGenerator::AppendTargetDepends(
 
 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
-  const std::string& config)
+  const std::string& config, const std::string& fileConfig, bool genexOutput)
 {
   cmNinjaOuts outs;
-  this->AppendTargetDependsClosure(target, outs, config, true);
+  this->AppendTargetDependsClosure(target, outs, config, fileConfig,
+                                   genexOutput, true);
   cm::append(outputs, outs);
 }
 
 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
   cmGeneratorTarget const* target, cmNinjaOuts& outputs,
-  const std::string& config, bool omit_self)
+  const std::string& config, const std::string& fileConfig, bool genexOutput,
+  bool omit_self)
 {
 
   // try to locate the target in the cache
-  auto find = this->Configs[config].TargetDependsClosures.lower_bound(target);
+  ByConfig::TargetDependsClosureKey key{
+    target,
+    config,
+    genexOutput,
+  };
+  auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key);
 
-  if (find == this->Configs[config].TargetDependsClosures.end() ||
-      find->first != target) {
+  if (find == this->Configs[fileConfig].TargetDependsClosures.end() ||
+      find->first != key) {
     // We now calculate the closure outputs by inspecting the dependent
     // targets recursively.
     // For that we have to distinguish between a local result set that is only
@@ -1220,18 +1273,27 @@ void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
     cmNinjaOuts this_outs; // this will be the new cache entry
 
     for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
-      if (!dep_target->IsInBuildSystem() ||
-          (target->GetType() != cmStateEnums::UTILITY &&
-           dep_target->GetType() != cmStateEnums::UTILITY &&
-           this->EnableCrossConfigBuild() && !dep_target.IsCross())) {
+      if (!dep_target->IsInBuildSystem()) {
+        continue;
+      }
+
+      if (!this->IsSingleConfigUtility(target) &&
+          !this->IsSingleConfigUtility(dep_target) &&
+          this->EnableCrossConfigBuild() && !dep_target.IsCross() &&
+          !genexOutput) {
         continue;
       }
 
-      // Collect the dependent targets for _this_ target
-      this->AppendTargetDependsClosure(dep_target, this_outs, config, false);
+      if (dep_target.IsCross()) {
+        this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig,
+                                         fileConfig, genexOutput, false);
+      } else {
+        this->AppendTargetDependsClosure(dep_target, this_outs, config,
+                                         fileConfig, genexOutput, false);
+      }
     }
-    find = this->Configs[config].TargetDependsClosures.emplace_hint(
-      find, target, std::move(this_outs));
+    find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint(
+      find, key, std::move(this_outs));
   }
 
   // now fill the outputs of the final result from the newly generated cache
@@ -2478,6 +2540,13 @@ std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
   return result;
 }
 
+bool cmGlobalNinjaGenerator::IsSingleConfigUtility(
+  cmGeneratorTarget const* target) const
+{
+  return target->GetType() == cmStateEnums::UTILITY &&
+    !this->PerConfigUtilityTargets.count(target->GetName());
+}
+
 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
   "CMakeFiles/common.ninja";
 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";

+ 42 - 3
Source/cmGlobalNinjaGenerator.h

@@ -330,10 +330,14 @@ public:
                            cmNinjaTargetDepends depends);
   void AppendTargetDependsClosure(cmGeneratorTarget const* target,
                                   cmNinjaDeps& outputs,
-                                  const std::string& config);
+                                  const std::string& config,
+                                  const std::string& fileConfig,
+                                  bool genexOutput);
   void AppendTargetDependsClosure(cmGeneratorTarget const* target,
                                   cmNinjaOuts& outputs,
-                                  const std::string& config, bool omit_self);
+                                  const std::string& config,
+                                  const std::string& fileConfig,
+                                  bool genexOutput, bool omit_self);
 
   void AppendDirectoryForConfig(const std::string& prefix,
                                 const std::string& config,
@@ -429,6 +433,18 @@ public:
     return this->DefaultConfigs;
   }
 
+  const std::set<std::string>& GetPerConfigUtilityTargets() const
+  {
+    return this->PerConfigUtilityTargets;
+  }
+
+  void AddPerConfigUtilityTarget(const std::string& name)
+  {
+    this->PerConfigUtilityTargets.insert(name);
+  }
+
+  bool IsSingleConfigUtility(cmGeneratorTarget const* target) const;
+
 protected:
   void Generate() override;
 
@@ -522,6 +538,9 @@ private:
   /// The mapping from source file to assumed dependencies.
   std::map<std::string, std::set<std::string>> AssumedSourceDependencies;
 
+  /// Utility targets which have per-config outputs
+  std::set<std::string> PerConfigUtilityTargets;
+
   struct TargetAlias
   {
     cmGeneratorTarget* GeneratorTarget;
@@ -561,7 +580,14 @@ private:
     /// The set of custom commands we have seen.
     std::set<cmCustomCommand const*> CustomCommands;
 
-    std::map<cmGeneratorTarget const*, cmNinjaOuts> TargetDependsClosures;
+    struct TargetDependsClosureKey
+    {
+      cmGeneratorTarget const* Target;
+      std::string Config;
+      bool GenexOutput;
+    };
+
+    std::map<TargetDependsClosureKey, cmNinjaOuts> TargetDependsClosures;
 
     TargetAliasMap TargetAliases;
 
@@ -570,6 +596,19 @@ private:
   std::map<std::string, ByConfig> Configs;
 
   cmNinjaDeps ByproductsForCleanTarget;
+
+  friend bool operator==(const ByConfig::TargetDependsClosureKey& lhs,
+                         const ByConfig::TargetDependsClosureKey& rhs);
+  friend bool operator!=(const ByConfig::TargetDependsClosureKey& lhs,
+                         const ByConfig::TargetDependsClosureKey& rhs);
+  friend bool operator<(const ByConfig::TargetDependsClosureKey& lhs,
+                        const ByConfig::TargetDependsClosureKey& rhs);
+  friend bool operator>(const ByConfig::TargetDependsClosureKey& lhs,
+                        const ByConfig::TargetDependsClosureKey& rhs);
+  friend bool operator<=(const ByConfig::TargetDependsClosureKey& lhs,
+                         const ByConfig::TargetDependsClosureKey& rhs);
+  friend bool operator>=(const ByConfig::TargetDependsClosureKey& lhs,
+                         const ByConfig::TargetDependsClosureKey& rhs);
 };
 
 class cmGlobalNinjaMultiGenerator : public cmGlobalNinjaGenerator

+ 33 - 8
Source/cmLocalGenerator.cxx

@@ -1129,9 +1129,8 @@ cmTarget* cmLocalGenerator::AddUtilityCommand(
 
   detail::AddUtilityCommand(
     *this, this->DirectoryBacktrace, cmCommandOrigin::Generator, target,
-    this->Makefile->GetUtilityOutput(target), workingDir, byproducts, depends,
-    commandLines, escapeOldStyle, comment, uses_terminal, command_expand_lists,
-    job_pool, stdPipesUTF8);
+    workingDir, byproducts, depends, commandLines, escapeOldStyle, comment,
+    uses_terminal, command_expand_lists, job_pool, stdPipesUTF8);
 
   return target;
 }
@@ -4135,7 +4134,7 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg,
 
 void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
                        cmCommandOrigin origin, cmTarget* target,
-                       const cmUtilityOutput& force, const char* workingDir,
+                       const char* workingDir,
                        const std::vector<std::string>& byproducts,
                        const std::vector<std::string>& depends,
                        const cmCustomCommandLines& commandLines,
@@ -4148,10 +4147,14 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
     comment = "";
   }
 
+  // Create the generated symbolic output name of the utility target.
+  std::string output =
+    lg.CreateUtilityOutput(target->GetName(), byproducts, lfbt);
+
   std::string no_main_dependency;
   cmImplicitDependsList no_implicit_depends;
   cmSourceFile* rule = AddCustomCommand(
-    lg, lfbt, origin, { force.Name }, byproducts, depends, no_main_dependency,
+    lg, lfbt, origin, { output }, byproducts, depends, no_main_dependency,
     no_implicit_depends, commandLines, comment, workingDir,
     /*replace=*/false, escapeOldStyle, uses_terminal, command_expand_lists,
     /*depfile=*/"", job_pool, stdPipesUTF8);
@@ -4159,9 +4162,7 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
     lg.AddTargetByproducts(target, byproducts, lfbt, origin);
   }
 
-  if (!force.NameCMP0049.empty()) {
-    target->AddSource(force.NameCMP0049);
-  }
+  target->AddSource(output);
 }
 
 std::vector<std::string> ComputeISPCObjectSuffixes(cmGeneratorTarget* target)
@@ -4254,6 +4255,30 @@ cmSourceFile* cmLocalGenerator::GetSourceFileWithOutput(
   return nullptr;
 }
 
+std::string cmLocalGenerator::CreateUtilityOutput(
+  std::string const& targetName, std::vector<std::string> const&,
+  cmListFileBacktrace const&)
+{
+  std::string force =
+    cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles/", targetName);
+  // The output is not actually created so mark it symbolic.
+  if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
+    sf->SetProperty("SYMBOLIC", "1");
+  } else {
+    cmSystemTools::Error("Could not get source file entry for " + force);
+  }
+  return force;
+}
+
+std::vector<cmCustomCommandGenerator>
+cmLocalGenerator::MakeCustomCommandGenerators(cmCustomCommand const& cc,
+                                              std::string const& config)
+{
+  std::vector<cmCustomCommandGenerator> ccgs;
+  ccgs.emplace_back(cc, config, this);
+  return ccgs;
+}
+
 std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputPaths(
   cmCompiledGeneratorExpression const& cge, std::string const& config)
 {

+ 9 - 1
Source/cmLocalGenerator.h

@@ -24,6 +24,7 @@
 
 class cmCompiledGeneratorExpression;
 class cmComputeLinkInformation;
+class cmCustomCommand;
 class cmCustomCommandGenerator;
 class cmCustomCommandLines;
 class cmGeneratorTarget;
@@ -363,6 +364,13 @@ public:
     bool command_expand_lists = false, const std::string& job_pool = "",
     bool stdPipesUTF8 = false);
 
+  virtual std::string CreateUtilityOutput(
+    std::string const& targetName, std::vector<std::string> const& byproducts,
+    cmListFileBacktrace const& bt);
+
+  virtual std::vector<cmCustomCommandGenerator> MakeCustomCommandGenerators(
+    cmCustomCommand const& cc, std::string const& config);
+
   std::vector<std::string> ExpandCustomCommandOutputPaths(
     cmCompiledGeneratorExpression const& cge, std::string const& config);
   std::vector<std::string> ExpandCustomCommandOutputGenex(
@@ -684,7 +692,7 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg,
 
 void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
                        cmCommandOrigin origin, cmTarget* target,
-                       const cmUtilityOutput& force, const char* workingDir,
+                       const char* workingDir,
                        const std::vector<std::string>& byproducts,
                        const std::vector<std::string>& depends,
                        const cmCustomCommandLines& commandLines,

+ 227 - 106
Source/cmLocalNinjaGenerator.cxx

@@ -10,6 +10,8 @@
 #include <sstream>
 #include <utility>
 
+#include <cmext/string_view>
+
 #include "cmsys/FStream.hxx"
 
 #include "cmCryptoHash.h"
@@ -567,17 +569,208 @@ void cmLocalNinjaGenerator::AppendCustomCommandLines(
 }
 
 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
-  cmCustomCommand const* cc, const cmNinjaDeps& orderOnlyDeps,
-  const std::string& config)
+  cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets,
+  const std::string& fileConfig)
 {
   cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
-  if (gg->SeenCustomCommand(cc, config)) {
+  if (gg->SeenCustomCommand(cc, fileConfig)) {
     return;
   }
 
+  auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig);
+  for (cmCustomCommandGenerator const& ccg : ccgs) {
+    cmNinjaDeps orderOnlyDeps;
+
+    // A custom command may appear on multiple targets.  However, some build
+    // systems exist where the target dependencies on some of the targets are
+    // overspecified, leading to a dependency cycle.  If we assume all target
+    // dependencies are a superset of the true target dependencies for this
+    // custom command, we can take the set intersection of all target
+    // dependencies to obtain a correct dependency list.
+    //
+    // FIXME: This won't work in certain obscure scenarios involving indirect
+    // dependencies.
+    auto j = targets.begin();
+    assert(j != targets.end());
+    this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
+      *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
+    std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
+    ++j;
+
+    for (; j != targets.end(); ++j) {
+      std::vector<std::string> jDeps;
+      std::vector<std::string> depsIntersection;
+      this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
+        *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
+      std::sort(jDeps.begin(), jDeps.end());
+      std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(),
+                            jDeps.begin(), jDeps.end(),
+                            std::back_inserter(depsIntersection));
+      orderOnlyDeps = depsIntersection;
+    }
+
+    const std::vector<std::string>& outputs = ccg.GetOutputs();
+    const std::vector<std::string>& byproducts = ccg.GetByproducts();
+
+    bool symbolic = false;
+    for (std::string const& output : outputs) {
+      if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
+        if (sf->GetPropertyAsBool("SYMBOLIC")) {
+          symbolic = true;
+          break;
+        }
+      }
+    }
+
+    cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size());
+    std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(),
+                   gg->MapToNinjaPath());
+    std::transform(byproducts.begin(), byproducts.end(),
+                   ninjaOutputs.begin() + outputs.size(),
+                   gg->MapToNinjaPath());
+
+    for (std::string const& ninjaOutput : ninjaOutputs) {
+      gg->SeenCustomCommandOutput(ninjaOutput);
+    }
+
+    cmNinjaDeps ninjaDeps;
+    this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
+
+    std::vector<std::string> cmdLines;
+    this->AppendCustomCommandLines(ccg, cmdLines);
+
+    if (cmdLines.empty()) {
+      cmNinjaBuild build("phony");
+      build.Comment = "Phony custom command for " + ninjaOutputs[0];
+      build.Outputs = std::move(ninjaOutputs);
+      build.ExplicitDeps = std::move(ninjaDeps);
+      build.OrderOnlyDeps = orderOnlyDeps;
+      gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
+    } else {
+      std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]);
+      // Hash full path to make unique.
+      customStep += '-';
+      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
+      customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7);
+
+      std::string depfile = cc->GetDepfile();
+      if (!depfile.empty()) {
+        switch (this->GetPolicyStatus(cmPolicies::CMP0116)) {
+          case cmPolicies::WARN:
+            if (this->GetCurrentBinaryDirectory() !=
+                  this->GetBinaryDirectory() ||
+                this->Makefile->PolicyOptionalWarningEnabled(
+                  "CMAKE_POLICY_WARNING_CMP0116")) {
+              this->GetCMakeInstance()->IssueMessage(
+                MessageType::AUTHOR_WARNING,
+                cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
+                cc->GetBacktrace());
+            }
+            CM_FALLTHROUGH;
+          case cmPolicies::OLD:
+            break;
+          case cmPolicies::REQUIRED_IF_USED:
+          case cmPolicies::REQUIRED_ALWAYS:
+          case cmPolicies::NEW:
+            cmSystemTools::MakeDirectory(
+              cmStrCat(this->GetBinaryDirectory(), "/CMakeFiles/d"));
+            depfile = ccg.GetInternalDepfile();
+            break;
+        }
+      }
+
+      gg->WriteCustomCommandBuild(
+        this->BuildCommandLine(cmdLines, customStep),
+        this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
+        depfile, cc->GetJobPool(), cc->GetUsesTerminal(),
+        /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, fileConfig,
+        ninjaDeps, orderOnlyDeps);
+    }
+  }
+}
+
+namespace {
+bool HasUniqueByproducts(cmLocalGenerator& lg,
+                         std::vector<std::string> const& byproducts,
+                         cmListFileBacktrace const& bt)
+{
+  std::vector<std::string> configs =
+    lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+  cmGeneratorExpression ge(bt);
+  for (std::string const& p : byproducts) {
+    if (cmGeneratorExpression::Find(p) == std::string::npos) {
+      return false;
+    }
+    std::set<std::string> seen;
+    std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
+    for (std::string const& config : configs) {
+      for (std::string const& b :
+           lg.ExpandCustomCommandOutputPaths(*cge, config)) {
+        if (!seen.insert(b).second) {
+          return false;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
+{
+  std::set<std::string> allOutputs;
+  std::set<std::string> allByproducts;
+  for (cmCustomCommandGenerator const& ccg : ccgs) {
+    for (std::string const& output : ccg.GetOutputs()) {
+      if (!allOutputs.insert(output).second) {
+        return false;
+      }
+    }
+    for (std::string const& byproduct : ccg.GetByproducts()) {
+      if (!allByproducts.insert(byproduct).second) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+}
+
+std::string cmLocalNinjaGenerator::CreateUtilityOutput(
+  std::string const& targetName, std::vector<std::string> const& byproducts,
+  cmListFileBacktrace const& bt)
+{
+  // In Ninja Multi-Config, we can only produce cross-config utility
+  // commands if all byproducts are per-config.
+  if (!this->GetGlobalGenerator()->IsMultiConfig() ||
+      !HasUniqueByproducts(*this, byproducts, bt)) {
+    return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts,
+                                                       bt);
+  }
+
+  std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(),
+                                    "/CMakeFiles/", targetName, '-');
+  // The output is not actually created so mark it symbolic.
+  for (std::string const& config :
+       this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) {
+    std::string const force = cmStrCat(base, config);
+    if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
+      sf->SetProperty("SYMBOLIC", "1");
+    } else {
+      cmSystemTools::Error("Could not get source file entry for " + force);
+    }
+  }
+  this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
+  return cmStrCat(base, "$<CONFIG>"_s);
+}
+
+std::vector<cmCustomCommandGenerator>
+cmLocalNinjaGenerator::MakeCustomCommandGenerators(
+  cmCustomCommand const& cc, std::string const& fileConfig)
+{
+  cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
+
   bool transformDepfile = false;
-  auto cmp0116 = this->GetPolicyStatus(cmPolicies::CMP0116);
-  switch (cmp0116) {
+  switch (this->GetPolicyStatus(cmPolicies::CMP0116)) {
     case cmPolicies::OLD:
     case cmPolicies::WARN:
       break;
@@ -588,84 +781,41 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
       break;
   }
 
-  cmCustomCommandGenerator ccg(*cc, config, this, transformDepfile);
+  // Start with the build graph's configuration.
+  std::vector<cmCustomCommandGenerator> ccgs;
+  ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
 
-  const std::vector<std::string>& outputs = ccg.GetOutputs();
-  const std::vector<std::string>& byproducts = ccg.GetByproducts();
-
-  bool symbolic = false;
-  for (std::string const& output : outputs) {
-    if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
-      if (sf->GetPropertyAsBool("SYMBOLIC")) {
-        symbolic = true;
-        break;
-      }
-    }
+  // Consider adding cross configurations.
+  if (!gg->EnableCrossConfigBuild()) {
+    return ccgs;
   }
 
-  cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size());
-  std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(),
-                 gg->MapToNinjaPath());
-  std::transform(byproducts.begin(), byproducts.end(),
-                 ninjaOutputs.begin() + outputs.size(), gg->MapToNinjaPath());
-
-  for (std::string const& ninjaOutput : ninjaOutputs) {
-    gg->SeenCustomCommandOutput(ninjaOutput);
+  // Outputs and byproducts must be expressed using generator expressions.
+  for (std::string const& output : cc.GetOutputs()) {
+    if (cmGeneratorExpression::Find(output) == std::string::npos) {
+      return ccgs;
+    }
+  }
+  for (std::string const& byproduct : cc.GetByproducts()) {
+    if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
+      return ccgs;
+    }
   }
 
-  cmNinjaDeps ninjaDeps;
-  this->AppendCustomCommandDeps(ccg, ninjaDeps, config);
-
-  std::vector<std::string> cmdLines;
-  this->AppendCustomCommandLines(ccg, cmdLines);
-
-  if (cmdLines.empty()) {
-    cmNinjaBuild build("phony");
-    build.Comment = "Phony custom command for " + ninjaOutputs[0];
-    build.Outputs = std::move(ninjaOutputs);
-    build.ExplicitDeps = std::move(ninjaDeps);
-    build.OrderOnlyDeps = orderOnlyDeps;
-    gg->WriteBuild(this->GetImplFileStream(config), build);
-  } else {
-    std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]);
-    // Hash full path to make unique.
-    customStep += '-';
-    cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
-    customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7);
-
-    std::string depfile = cc->GetDepfile();
-    if (!depfile.empty()) {
-      switch (cmp0116) {
-        case cmPolicies::WARN:
-          if (this->GetCurrentBinaryDirectory() !=
-                this->GetBinaryDirectory() ||
-              this->Makefile->PolicyOptionalWarningEnabled(
-                "CMAKE_POLICY_WARNING_CMP0116")) {
-            this->GetCMakeInstance()->IssueMessage(
-              MessageType::AUTHOR_WARNING,
-              cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
-              cc->GetBacktrace());
-          }
-          CM_FALLTHROUGH;
-        case cmPolicies::OLD:
-          break;
-        case cmPolicies::REQUIRED_IF_USED:
-        case cmPolicies::REQUIRED_ALWAYS:
-        case cmPolicies::NEW:
-          cmSystemTools::MakeDirectory(
-            cmStrCat(this->GetBinaryDirectory(), "/CMakeFiles/d"));
-          depfile = ccg.GetInternalDepfile();
-          break;
-      }
+  // Tentatively add the other cross configurations.
+  for (std::string const& config : gg->GetCrossConfigs(fileConfig)) {
+    if (fileConfig != config) {
+      ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config);
     }
+  }
 
-    gg->WriteCustomCommandBuild(
-      this->BuildCommandLine(cmdLines, customStep),
-      this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
-      depfile, cc->GetJobPool(), cc->GetUsesTerminal(),
-      /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, config,
-      ninjaDeps, orderOnlyDeps);
+  // If outputs and byproducts are not unique to each configuration,
+  // drop the cross configurations.
+  if (!HasUniqueOutputs(ccgs)) {
+    ccgs.erase(ccgs.begin() + 1, ccgs.end());
   }
+
+  return ccgs;
 }
 
 void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
@@ -681,42 +831,13 @@ void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
 }
 
 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
-  const std::string& config)
+  const std::string& fileConfig)
 {
   for (cmCustomCommand const* customCommand : this->CustomCommands) {
     auto i = this->CustomCommandTargets.find(customCommand);
     assert(i != this->CustomCommandTargets.end());
 
-    // A custom command may appear on multiple targets.  However, some build
-    // systems exist where the target dependencies on some of the targets are
-    // overspecified, leading to a dependency cycle.  If we assume all target
-    // dependencies are a superset of the true target dependencies for this
-    // custom command, we can take the set intersection of all target
-    // dependencies to obtain a correct dependency list.
-    //
-    // FIXME: This won't work in certain obscure scenarios involving indirect
-    // dependencies.
-    auto j = i->second.begin();
-    assert(j != i->second.end());
-    std::vector<std::string> ccTargetDeps;
-    this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
-      *j, ccTargetDeps, config);
-    std::sort(ccTargetDeps.begin(), ccTargetDeps.end());
-    ++j;
-
-    for (; j != i->second.end(); ++j) {
-      std::vector<std::string> jDeps;
-      std::vector<std::string> depsIntersection;
-      this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(*j, jDeps,
-                                                                  config);
-      std::sort(jDeps.begin(), jDeps.end());
-      std::set_intersection(ccTargetDeps.begin(), ccTargetDeps.end(),
-                            jDeps.begin(), jDeps.end(),
-                            std::back_inserter(depsIntersection));
-      ccTargetDeps = depsIntersection;
-    }
-
-    this->WriteCustomCommandBuildStatement(i->first, ccTargetDeps, config);
+    this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
   }
 }
 

+ 11 - 3
Source/cmLocalNinjaGenerator.h

@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "cmListFileCache.h"
 #include "cmLocalCommonGenerator.h"
 #include "cmNinjaTypes.h"
 #include "cmOutputConverter.h"
@@ -70,6 +71,13 @@ public:
                            const std::string& fileConfig,
                            cmNinjaTargetDepends depends);
 
+  std::string CreateUtilityOutput(std::string const& targetName,
+                                  std::vector<std::string> const& byproducts,
+                                  cmListFileBacktrace const& bt) override;
+
+  std::vector<cmCustomCommandGenerator> MakeCustomCommandGenerators(
+    cmCustomCommand const& cc, std::string const& config) override;
+
   void AddCustomCommandTarget(cmCustomCommand const* cc,
                               cmGeneratorTarget* target);
   void AppendCustomCommandLines(cmCustomCommandGenerator const& ccg,
@@ -99,9 +107,9 @@ private:
   void WriteProcessedMakefile(std::ostream& os);
   void WritePools(std::ostream& os);
 
-  void WriteCustomCommandBuildStatement(cmCustomCommand const* cc,
-                                        const cmNinjaDeps& orderOnlyDeps,
-                                        const std::string& config);
+  void WriteCustomCommandBuildStatement(
+    cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets,
+    const std::string& config);
 
   void WriteCustomCommandBuildStatements(const std::string& config);
 

+ 2 - 27
Source/cmMakefile.cxx

@@ -1257,27 +1257,6 @@ void cmMakefile::AppendCustomCommandToOutput(
   }
 }
 
-cmUtilityOutput cmMakefile::GetUtilityOutput(cmTarget* target)
-{
-  std::string force = cmStrCat(this->GetCurrentBinaryDirectory(),
-                               "/CMakeFiles/", target->GetName());
-  std::string forceCMP0049 = target->GetSourceCMP0049(force);
-  {
-    cmSourceFile* sf = nullptr;
-    if (!forceCMP0049.empty()) {
-      sf = this->GetOrCreateSource(forceCMP0049, false,
-                                   cmSourceFileLocationKind::Known);
-    }
-    // The output is not actually created so mark it symbolic.
-    if (sf) {
-      sf->SetProperty("SYMBOLIC", "1");
-    } else {
-      cmSystemTools::Error("Could not get source file entry for " + force);
-    }
-  }
-  return { std::move(force), std::move(forceCMP0049) };
-}
-
 cmTarget* cmMakefile::AddUtilityCommand(
   const std::string& utilityName, bool excludeFromAll, const char* workingDir,
   const std::vector<std::string>& byproducts,
@@ -1294,10 +1273,6 @@ cmTarget* cmMakefile::AddUtilityCommand(
     return target;
   }
 
-  // Get the output name of the utility target and mark it generated.
-  cmUtilityOutput force = this->GetUtilityOutput(target);
-  this->GetOrCreateGeneratedSource(force.Name);
-
   // Always create the byproduct sources and mark them generated.
   this->CreateGeneratedOutputs(byproducts);
 
@@ -1310,8 +1285,8 @@ cmTarget* cmMakefile::AddUtilityCommand(
     [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) {
       BacktraceGuard guard(this->Backtrace, lfbt);
       detail::AddUtilityCommand(lg, lfbt, cmCommandOrigin::Project, target,
-                                force, GetCStrOrNull(workingStr), byproducts,
-                                depends, commandLines, escapeOldStyle,
+                                GetCStrOrNull(workingStr), byproducts, depends,
+                                commandLines, escapeOldStyle,
                                 GetCStrOrNull(commentStr), uses_terminal,
                                 command_expand_lists, job_pool, stdPipesUTF8);
     });

+ 0 - 5
Source/cmMakefile.h

@@ -242,11 +242,6 @@ public:
                           const std::vector<std::string>& srcs,
                           bool excludeFromAll = false);
 
-  /**
-   * Return the utility target output source file name and the CMP0049 name.
-   */
-  cmUtilityOutput GetUtilityOutput(cmTarget* target);
-
   /**
    * Dispatch adding a utility to the build.  A utility target is a command
    * that is run every time the target is built.

+ 42 - 11
Source/cmNinjaUtilityTargetGenerator.cxx

@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <array>
 #include <iterator>
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
@@ -33,6 +34,23 @@ cmNinjaUtilityTargetGenerator::cmNinjaUtilityTargetGenerator(
 cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() = default;
 
 void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
+{
+  for (auto const& fileConfig : this->GetConfigNames()) {
+    if (!this->GetGlobalGenerator()
+           ->GetCrossConfigs(fileConfig)
+           .count(config)) {
+      continue;
+    }
+    if (fileConfig != config &&
+        this->GetGeneratorTarget()->GetType() == cmStateEnums::GLOBAL_TARGET) {
+      continue;
+    }
+    this->WriteUtilBuildStatements(config, fileConfig);
+  }
+}
+
+void cmNinjaUtilityTargetGenerator::WriteUtilBuildStatements(
+  std::string const& config, std::string const& fileConfig)
 {
   cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator();
   cmLocalNinjaGenerator* lg = this->GetLocalGenerator();
@@ -40,7 +58,7 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
 
   std::string configDir;
   if (genTarget->Target->IsPerConfig()) {
-    configDir = gg->ConfigDirectory(config);
+    configDir = gg->ConfigDirectory(fileConfig);
   }
   std::string utilCommandName =
     cmStrCat(lg->GetCurrentBinaryDirectory(), "/CMakeFiles", configDir, "/",
@@ -60,8 +78,8 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
 
     for (std::vector<cmCustomCommand> const* cmdList : cmdLists) {
       for (cmCustomCommand const& ci : *cmdList) {
-        cmCustomCommandGenerator ccg(ci, config, lg);
-        lg->AppendCustomCommandDeps(ccg, deps, config);
+        cmCustomCommandGenerator ccg(ci, fileConfig, lg);
+        lg->AppendCustomCommandDeps(ccg, deps, fileConfig);
         lg->AppendCustomCommandLines(ccg, commands);
         std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
         std::transform(ccByproducts.begin(), ccByproducts.end(),
@@ -103,13 +121,19 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
     std::copy(util_outputs.begin(), util_outputs.end(),
               std::back_inserter(gg->GetByproductsForCleanTarget()));
   }
-  lg->AppendTargetDepends(genTarget, deps, config, config,
+  // TODO: Does this need an output config?
+  // Does this need to go in impl-<config>.ninja?
+  lg->AppendTargetDepends(genTarget, deps, config, fileConfig,
                           DependOnTargetArtifact);
 
   if (commands.empty()) {
     phonyBuild.Comment = "Utility command for " + this->GetTargetName();
     phonyBuild.ExplicitDeps = std::move(deps);
-    gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
+    if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
+      gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild);
+    } else {
+      gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
+    }
   } else {
     std::string command =
       lg->BuildCommandLine(commands, "utility", this->GeneratorTarget);
@@ -145,15 +169,22 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
     std::string ccConfig;
     if (genTarget->Target->IsPerConfig() &&
         genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
-      ccConfig = config;
+      ccConfig = fileConfig;
+    }
+    if (config == fileConfig ||
+        gg->GetPerConfigUtilityTargets().count(genTarget->GetName())) {
+      gg->WriteCustomCommandBuild(
+        command, desc, "Utility command for " + this->GetTargetName(),
+        /*depfile*/ "", /*job_pool*/ "", uses_terminal,
+        /*restat*/ true, util_outputs, ccConfig, deps);
     }
-    gg->WriteCustomCommandBuild(command, desc,
-                                "Utility command for " + this->GetTargetName(),
-                                /*depfile*/ "", /*job_pool*/ "", uses_terminal,
-                                /*restat*/ true, util_outputs, ccConfig, deps);
 
     phonyBuild.ExplicitDeps.push_back(utilCommandName);
-    gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
+    if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
+      gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild);
+    } else {
+      gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
+    }
   }
 
   // Find ADDITIONAL_CLEAN_FILES

+ 4 - 0
Source/cmNinjaUtilityTargetGenerator.h

@@ -17,4 +17,8 @@ public:
   ~cmNinjaUtilityTargetGenerator() override;
 
   void Generate(const std::string& config) override;
+
+private:
+  void WriteUtilBuildStatements(std::string const& config,
+                                std::string const& fileConfig);
 };

+ 4 - 4
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_tgt.json

@@ -7,7 +7,7 @@
     "isGeneratorProvided": null,
     "sources": [
         {
-            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/custom/CMakeFiles/custom_tgt$",
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/custom/CMakeFiles/custom_tgt(-(Debug|Release|RelWithDebInfo|MinSizeRel))?$",
             "isGenerated": true,
             "sourceGroupName": "",
             "compileGroupLanguage": null,
@@ -27,7 +27,7 @@
             ]
         },
         {
-            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/(custom/)?CMakeFiles/([0-9a-f]+/)?custom_tgt\\.rule$",
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/(custom/)?CMakeFiles/([0-9a-f]+/)?custom_tgt(-\\(CONFIG\\))?\\.rule$",
             "isGenerated": true,
             "sourceGroupName": "CMake Rules",
             "compileGroupLanguage": null,
@@ -45,13 +45,13 @@
         {
             "name": "",
             "sourcePaths": [
-                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/custom/CMakeFiles/custom_tgt$"
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/custom/CMakeFiles/custom_tgt(-(Debug|Release|RelWithDebInfo|MinSizeRel))?$"
             ]
         },
         {
             "name": "CMake Rules",
             "sourcePaths": [
-                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/(custom/)?CMakeFiles/([0-9a-f]+/)?custom_tgt\\.rule$"
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/(custom/)?CMakeFiles/([0-9a-f]+/)?custom_tgt(-\\(CONFIG\\))?\\.rule$"
             ]
         }
     ],

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_Debug\.txt
+depend_echo_genex_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_Debug\.txt
+depend_echo_genex_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_cmd-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_cmd_Debug\.txt
+depend_echo_genex_cmd_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_cmd-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_cmd_Debug\.txt
+depend_echo_genex_cmd_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_out-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_out_Debug\.txt
+depend_echo_genex_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_genex_out-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_genex_out_Debug\.txt
+depend_echo_genex_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_raw-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_raw_Debug\.txt
+depend_echo_raw_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-depend_echo_raw-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating depend_echo_raw_Debug\.txt
+depend_echo_raw_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] Generating echo_depend_Debug\.txt
+echo_depend_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] Generating echo_depend_Debug\.txt
+echo_depend_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_cmd-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Release\.txt
+depend_Release\.txt
+\[2/2\] Generating echo_depend_cmd_Debug\.txt
+echo_depend_cmd_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_cmd-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] Generating echo_depend_cmd_Debug\.txt
+echo_depend_cmd_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_out-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] Generating echo_depend_out_Debug\.txt
+echo_depend_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_depend_out-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] Generating echo_depend_out_Debug\.txt
+echo_depend_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] Generating echo_genex_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release[\/]echo(\.exe)?' 'Release' 'echo_genex_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_genex_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_genex_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex_out-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_genex_out_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_genex_out_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_genex_out-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_genex_out_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_genex_out_Debug\.txt'$

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-in-release-graph-ninja-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-in-release-graph-ninja-stderr.txt

@@ -0,0 +1 @@
+ninja: error: 'echo_no_cross_byproduct_Debug\.txt', needed by 'CMakeFiles/echo_no_cross_byproduct-Debug', missing and no known rule to make it

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_no_cross_byproduct_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_no_cross_byproduct_Debug\.txt'$

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-in-release-graph-ninja-result.txt

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

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-in-release-graph-ninja-stderr.txt

@@ -0,0 +1 @@
+ninja: error: 'echo_no_cross_byproduct_if_Debug\.txt', needed by 'CMakeFiles/echo_no_cross_byproduct_if-Debug', missing and no known rule to make it

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_byproduct_if-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_no_cross_byproduct_if_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_no_cross_byproduct_if_Debug\.txt'$

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-in-release-graph-ninja-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-in-release-graph-ninja-stderr.txt

@@ -0,0 +1 @@
+ninja: error: 'echo_no_cross_output_Debug\.txt', needed by 'CMakeFiles/echo_no_cross_output-Debug', missing and no known rule to make it

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_no_cross_output\.txt, echo_no_cross_output_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_no_cross_output_Debug\.txt'$

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-in-release-graph-ninja-result.txt

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

+ 1 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-in-release-graph-ninja-stderr.txt

@@ -0,0 +1 @@
+ninja: error: 'echo_no_cross_output_if_Debug\.txt', needed by 'CMakeFiles/echo_no_cross_output_if-Debug', missing and no known rule to make it

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_no_cross_output_if-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_no_cross_output_a\.txt, echo_no_cross_output_if_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_no_cross_output_if_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_raw-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] Generating echo_raw_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release[\/]echo(\.exe)?' 'Debug' 'echo_raw_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_raw-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] Generating echo_raw_Debug\.txt
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_raw_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] echo_target_depend
+echo_target_depend_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] echo_target_depend
+echo_target_depend_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_cmd-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Release\.txt
+depend_Release\.txt
+\[2/2\] echo_target_depend_cmd
+echo_target_depend_cmd_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_cmd-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] echo_target_depend_cmd
+echo_target_depend_cmd_Debug\.txt$

+ 5 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_out-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,5 @@
+^(Recompacting log\.\.\.
+)?\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] echo_target_depend_out
+echo_target_depend_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_depend_out-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/2\] Generating depend_Debug\.txt
+depend_Debug\.txt
+\[2/2\] echo_target_depend_out
+echo_target_depend_out_Debug\.txt$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] echo_target_genex
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release[\/]echo(\.exe)?' 'Release' 'echo_target_genex_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] echo_target_genex
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_target_genex_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex_out-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] echo_target_genex_out
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_target_genex_out_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_genex_out-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] echo_target_genex_out
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_target_genex_out_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_raw-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] echo_target_raw
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release[\/]echo(\.exe)?' 'Debug' 'echo_target_raw_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-echo_target_raw-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] echo_target_raw
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'echo_target_raw_Debug\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-target_no_cross_byproduct-debug-in-release-graph-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Release[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Release[\/]echo(\.exe)?
+\[3/3\] target_no_cross_byproduct
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Release[\/]echo(\.exe)?' 'Release' 'target_no_cross_byproduct\.txt'$

+ 4 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex-target_no_cross_byproduct-debug-ninja-stdout.txt

@@ -0,0 +1,4 @@
+^\[1/3\] Building C object CMakeFiles[\/]echo.dir[\/]Debug[\/]echo\.c\.(o|obj)
+\[2/3\] Linking C executable Debug[\/]echo(\.exe)?
+\[3/3\] target_no_cross_byproduct
+'[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug'\$ '[^']*[\/]Tests[\/]RunCMake[\/]NinjaMultiConfig[\/]CustomCommandOutputGenex-build[\/]Debug[\/]echo(\.exe)?' 'Debug' 'target_no_cross_byproduct\.txt'$

+ 167 - 0
Tests/RunCMake/NinjaMultiConfig/CustomCommandOutputGenex.cmake

@@ -0,0 +1,167 @@
+enable_language(C)
+
+add_executable(echo echo.c)
+
+add_custom_command(
+  OUTPUT echo_raw_$<CONFIG>.txt
+  COMMAND echo $<CONFIG> echo_raw_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+
+add_custom_command(
+  OUTPUT echo_genex_$<IF:$<CONFIG:Debug>,Debug,$<IF:$<CONFIG:Release>,Release,$<IF:$<CONFIG:MinSizeRel>,MinSizeRel,RelWithDebInfo>>>.txt
+  COMMAND $<TARGET_FILE:echo> $<COMMAND_CONFIG:$<CONFIG>> echo_genex_$<OUTPUT_CONFIG:$<CONFIG>>.txt
+  WORKING_DIRECTORY $<OUTPUT_CONFIG:$<CONFIG>>
+  )
+
+add_custom_command(
+  OUTPUT echo_genex_out_$<CONFIG>.txt
+  COMMAND $<OUTPUT_CONFIG:$<TARGET_FILE:echo>> $<CONFIG> echo_genex_out_$<CONFIG>.txt
+  WORKING_DIRECTORY $<COMMAND_CONFIG:$<CONFIG>>
+  )
+
+add_custom_command(
+  OUTPUT depend_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo depend_$<CONFIG>.txt
+  )
+
+add_custom_command(
+  OUTPUT echo_depend_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo echo_depend_$<CONFIG>.txt
+  DEPENDS depend_$<CONFIG>.txt
+  )
+
+add_custom_command(
+  OUTPUT echo_depend_out_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo echo_depend_out_$<CONFIG>.txt
+  DEPENDS $<OUTPUT_CONFIG:depend_$<CONFIG>.txt>
+  )
+
+add_custom_command(
+  OUTPUT echo_depend_cmd_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo echo_depend_cmd_$<CONFIG>.txt
+  DEPENDS $<COMMAND_CONFIG:depend_$<CONFIG>.txt>
+  )
+
+add_custom_command(
+  OUTPUT depend_echo_raw_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo depend_echo_raw_$<CONFIG>.txt
+  DEPENDS echo
+  )
+
+add_custom_command(
+  OUTPUT depend_echo_genex_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo depend_echo_genex_$<CONFIG>.txt
+  DEPENDS $<TARGET_FILE:echo>
+  )
+
+add_custom_command(
+  OUTPUT depend_echo_genex_out_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo depend_echo_genex_out_$<CONFIG>.txt
+  DEPENDS $<OUTPUT_CONFIG:$<TARGET_FILE:echo>>
+  )
+
+add_custom_command(
+  OUTPUT depend_echo_genex_cmd_$<CONFIG>.txt
+  COMMAND ${CMAKE_COMMAND} -E echo depend_echo_genex_cmd_$<CONFIG>.txt
+  DEPENDS $<COMMAND_CONFIG:$<TARGET_FILE:echo>>
+  )
+
+# An OUTPUT that is not per-config prevents cross-config generation.
+add_custom_command(
+  OUTPUT echo_no_cross_output.txt echo_no_cross_output_$<CONFIG>.txt
+  COMMAND echo $<CONFIG> echo_no_cross_output_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+add_custom_command(
+  OUTPUT echo_no_cross_output_$<IF:$<CONFIG:Debug>,a,b>.txt echo_no_cross_output_if_$<CONFIG>.txt
+  COMMAND echo $<CONFIG> echo_no_cross_output_if_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+
+# BYPRODUCTS that are not per-config prevent cross-config generation.
+add_custom_command(
+  OUTPUT echo_no_cross_byproduct_$<CONFIG>.txt
+  BYPRODUCTS echo_no_cross_byproduct.txt
+  COMMAND echo $<CONFIG> echo_no_cross_byproduct_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+add_custom_command(
+  OUTPUT echo_no_cross_byproduct_if_$<CONFIG>.txt
+  BYPRODUCTS echo_no_cross_byproduct_$<IF:$<CONFIG:Debug>,a,b>.txt
+  COMMAND echo $<CONFIG> echo_no_cross_byproduct_if_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+
+foreach(case
+    echo_raw
+    echo_genex
+    echo_genex_out
+    echo_depend
+    echo_depend_out
+    echo_depend_cmd
+    depend
+    depend_echo_raw
+    depend_echo_genex
+    depend_echo_genex_out
+    depend_echo_genex_cmd
+    echo_no_cross_output
+    echo_no_cross_output_if
+    echo_no_cross_byproduct
+    echo_no_cross_byproduct_if
+    )
+  set_property(SOURCE
+    ${CMAKE_CURRENT_BINARY_DIR}/${case}_Debug.txt
+    ${CMAKE_CURRENT_BINARY_DIR}/${case}_Release.txt
+    ${CMAKE_CURRENT_BINARY_DIR}/${case}_MinSizeRel.txt
+    ${CMAKE_CURRENT_BINARY_DIR}/${case}_RelWithDebInfo.txt
+    PROPERTY SYMBOLIC 1)
+  add_custom_target(${case} DEPENDS ${case}_$<CONFIG>.txt)
+endforeach()
+
+add_custom_target(echo_target_raw
+  BYPRODUCTS echo_target_raw_$<CONFIG>.txt
+  COMMENT echo_target_raw
+  COMMAND echo $<CONFIG> echo_target_raw_$<CONFIG>.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )
+
+add_custom_target(echo_target_genex
+  BYPRODUCTS echo_target_genex_$<CONFIG>.txt
+  COMMENT echo_target_genex
+  COMMAND $<TARGET_FILE:echo> $<COMMAND_CONFIG:$<CONFIG>> echo_target_genex_$<OUTPUT_CONFIG:$<CONFIG>>.txt
+  WORKING_DIRECTORY $<OUTPUT_CONFIG:$<CONFIG>>
+  )
+
+add_custom_target(echo_target_genex_out
+  BYPRODUCTS echo_target_genex_out_$<CONFIG>.txt
+  COMMENT echo_target_genex_out
+  COMMAND $<OUTPUT_CONFIG:$<TARGET_FILE:echo>> $<CONFIG> echo_target_genex_out_$<CONFIG>.txt
+  WORKING_DIRECTORY $<COMMAND_CONFIG:$<CONFIG>>
+  )
+
+add_custom_target(echo_target_depend
+  COMMAND ${CMAKE_COMMAND} -E echo echo_target_depend_$<CONFIG>.txt
+  DEPENDS depend_$<CONFIG>.txt
+  COMMENT echo_target_depend
+  )
+
+add_custom_target(echo_target_depend_out
+  COMMAND ${CMAKE_COMMAND} -E echo echo_target_depend_out_$<CONFIG>.txt
+  DEPENDS $<OUTPUT_CONFIG:depend_$<CONFIG>.txt>
+  COMMENT echo_target_depend_out
+  )
+
+add_custom_target(echo_target_depend_cmd
+  COMMAND ${CMAKE_COMMAND} -E echo echo_target_depend_cmd_$<CONFIG>.txt
+  DEPENDS $<COMMAND_CONFIG:depend_$<CONFIG>.txt>
+  COMMENT echo_target_depend_cmd
+  )
+
+# BYPRODUCTS that are not per-config block cross-configs.
+add_custom_target(target_no_cross_byproduct
+  BYPRODUCTS target_no_cross_byproduct.txt
+  COMMENT target_no_cross_byproduct
+  COMMAND echo $<CONFIG> target_no_cross_byproduct.txt
+  WORKING_DIRECTORY $<CONFIG>
+  )

+ 96 - 0
Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake

@@ -242,6 +242,102 @@ run_ninja(CustomCommandsAndTargets release-leaf-exe build-Release.ninja LeafExe)
 run_cmake_build(CustomCommandsAndTargets release-clean Release clean:all)
 run_ninja(CustomCommandsAndTargets release-leaf-byproduct build-Release.ninja main.c)
 
+set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CustomCommandOutputGenex-build)
+set(RunCMake_TEST_OPTIONS "-DCMAKE_CONFIGURATION_TYPES=Debug\\;Release\\;MinSizeRel\\;RelWithDebInfo;-DCMAKE_CROSS_CONFIGS=all")
+run_cmake_configure(CustomCommandOutputGenex)
+set(RunCMake_TEST_NO_CLEAN 1)
+unset(RunCMake_TEST_OPTIONS)
+# echo_raw
+run_ninja(CustomCommandOutputGenex echo_raw-debug build-Debug.ninja echo_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_raw-debug-in-release-graph build-Release.ninja echo_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_genex
+run_ninja(CustomCommandOutputGenex echo_genex-debug build-Debug.ninja echo_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_genex-debug-in-release-graph build-Release.ninja echo_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_genex_out
+run_ninja(CustomCommandOutputGenex echo_genex_out-debug build-Debug.ninja echo_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_genex_out-debug-in-release-graph build-Release.ninja echo_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_depend*
+run_ninja(CustomCommandOutputGenex echo_depend-debug build-Debug.ninja echo_depend:Debug)
+run_ninja(CustomCommandOutputGenex echo_depend_out-debug build-Debug.ninja echo_depend_out_Debug.txt)
+run_ninja(CustomCommandOutputGenex echo_depend_cmd-debug build-Debug.ninja echo_depend_cmd_Debug.txt)
+run_ninja(CustomCommandOutputGenex echo_depend-debug-in-release-graph build-Release.ninja echo_depend:Debug)
+run_ninja(CustomCommandOutputGenex echo_depend_out-debug-in-release-graph build-Release.ninja echo_depend_out_Debug.txt)
+run_ninja(CustomCommandOutputGenex echo_depend_cmd-debug-in-release-graph build-Release.ninja echo_depend_cmd_Debug.txt)
+# depend_echo_raw
+run_ninja(CustomCommandOutputGenex depend_echo_raw-debug build-Debug.ninja depend_echo_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex depend_echo_raw-debug-in-release-graph build-Release.ninja depend_echo_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# depend_echo_genex
+run_ninja(CustomCommandOutputGenex depend_echo_genex-debug build-Debug.ninja depend_echo_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex depend_echo_genex-debug-in-release-graph build-Release.ninja depend_echo_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# depend_echo_genex_out
+run_ninja(CustomCommandOutputGenex depend_echo_genex_out-debug build-Debug.ninja depend_echo_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex depend_echo_genex_out-debug-in-release-graph build-Release.ninja depend_echo_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# depend_echo_genex_cmd
+run_ninja(CustomCommandOutputGenex depend_echo_genex_cmd-debug build-Debug.ninja depend_echo_genex_cmd:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex depend_echo_genex_cmd-debug-in-release-graph build-Release.ninja depend_echo_genex_cmd:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# no_cross_output
+run_ninja(CustomCommandOutputGenex echo_no_cross_output-debug build-Debug.ninja echo_no_cross_output:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_no_cross_output-debug-in-release-graph build-Release.ninja echo_no_cross_output:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# no_cross_output_if
+run_ninja(CustomCommandOutputGenex echo_no_cross_output_if-debug build-Debug.ninja echo_no_cross_output_if:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_no_cross_output_if-debug-in-release-graph build-Release.ninja echo_no_cross_output_if:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# no_cross_byproduct
+run_ninja(CustomCommandOutputGenex echo_no_cross_byproduct-debug build-Debug.ninja echo_no_cross_byproduct:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_no_cross_byproduct-debug-in-release-graph build-Release.ninja echo_no_cross_byproduct:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# no_cross_byproduct_if
+run_ninja(CustomCommandOutputGenex echo_no_cross_byproduct_if-debug build-Debug.ninja echo_no_cross_byproduct_if:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_no_cross_byproduct_if-debug-in-release-graph build-Release.ninja echo_no_cross_byproduct_if:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_target_raw
+run_ninja(CustomCommandOutputGenex echo_target_raw-debug build-Debug.ninja echo_target_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_target_raw-debug-in-release-graph build-Release.ninja echo_target_raw:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_target_genex
+run_ninja(CustomCommandOutputGenex echo_target_genex-debug build-Debug.ninja echo_target_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_target_genex-debug-in-release-graph build-Release.ninja echo_target_genex:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_target_genex_out
+run_ninja(CustomCommandOutputGenex echo_target_genex_out-debug build-Debug.ninja echo_target_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex echo_target_genex_out-debug-in-release-graph build-Release.ninja echo_target_genex_out:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+# echo_target_depend*
+run_ninja(CustomCommandOutputGenex echo_target_depend-debug build-Debug.ninja echo_target_depend:Debug)
+run_ninja(CustomCommandOutputGenex echo_target_depend_out-debug build-Debug.ninja echo_target_depend_out:Debug)
+run_ninja(CustomCommandOutputGenex echo_target_depend_cmd-debug build-Debug.ninja CMakeFiles/echo_target_depend_cmd-Debug) # undocumented
+run_ninja(CustomCommandOutputGenex echo_target_depend-debug-in-release-graph build-Release.ninja echo_target_depend:Debug)
+run_ninja(CustomCommandOutputGenex echo_target_depend_out-debug-in-release-graph build-Release.ninja echo_target_depend_out:Debug)
+run_ninja(CustomCommandOutputGenex echo_target_depend_cmd-debug-in-release-graph build-Release.ninja CMakeFiles/echo_target_depend_cmd-Debug) # undocumented
+# target_no_cross_*
+run_ninja(CustomCommandOutputGenex target_no_cross_byproduct-debug build-Debug.ninja target_no_cross_byproduct:Debug)
+run_ninja(CustomCommandOutputGenex clean-debug-graph build-Debug.ninja -t clean)
+run_ninja(CustomCommandOutputGenex target_no_cross_byproduct-debug-in-release-graph build-Release.ninja target_no_cross_byproduct:Debug)
+run_ninja(CustomCommandOutputGenex clean-release-graph build-Release.ninja -t clean)
+unset(RunCMake_TEST_NO_CLEAN)
+
 unset(RunCMake_TEST_BINARY_DIR)
 
 run_cmake(CustomCommandDepfile)

+ 21 - 0
Tests/RunCMake/NinjaMultiConfig/echo.c

@@ -0,0 +1,21 @@
+#include <stdio.h>
+#if defined(_WIN32)
+#  include <direct.h>
+#  define getcwd _getcwd
+#else
+#  include <unistd.h>
+#endif
+
+int main(int argc, char** argv)
+{
+  int i;
+  char cwd[1024];
+  if (getcwd(cwd, sizeof(cwd)) != NULL) {
+    printf("'%s'$", cwd);
+  }
+  for (i = 0; i < argc; ++i) {
+    printf(" '%s'", argv[i]);
+  }
+  printf("\n");
+  return 0;
+}

+ 20 - 0
Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt

@@ -44,4 +44,24 @@ CMake Error at BadByproduct.cmake:7 \(add_custom_command\):
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
 
+)+(
+CMake Error at BadByproduct.cmake:8 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OUTPUT_CONFIG:h>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+)+(
+CMake Error at BadByproduct.cmake:9 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<COMMAND_CONFIG:i>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
 )+

+ 2 - 0
Tests/RunCMake/add_custom_command/BadByproduct.cmake

@@ -5,3 +5,5 @@ add_custom_command(OUTPUT c BYPRODUCTS "a>")
 add_custom_command(OUTPUT d BYPRODUCTS "$<CONFIG>/#")
 add_custom_command(OUTPUT e BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/f)
 add_custom_command(OUTPUT f BYPRODUCTS "$<TARGET_PROPERTY:prop>")
+add_custom_command(OUTPUT h BYPRODUCTS "$<OUTPUT_CONFIG:h>")
+add_custom_command(OUTPUT i BYPRODUCTS "$<COMMAND_CONFIG:i>")

+ 1 - 0
Tests/RunCMake/add_custom_command/BadCommand-result.txt

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

+ 21 - 0
Tests/RunCMake/add_custom_command/BadCommand-stderr.txt

@@ -0,0 +1,21 @@
+(CMake Error at BadCommand.cmake:1 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OUTPUT_CONFIG:a>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+
+)+(CMake Error at BadCommand.cmake:2 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<COMMAND_CONFIG:b>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+
+)+

+ 3 - 0
Tests/RunCMake/add_custom_command/BadCommand.cmake

@@ -0,0 +1,3 @@
+add_custom_command(OUTPUT "a" COMMAND "$<1:$<OUTPUT_CONFIG:a>>")
+add_custom_command(OUTPUT "b" COMMAND "$<1:$<COMMAND_CONFIG:b>>")
+add_custom_target(drive DEPENDS "a" "b")

+ 20 - 0
Tests/RunCMake/add_custom_command/BadOutput-stderr.txt

@@ -44,4 +44,24 @@ CMake Error at BadOutput.cmake:7 \(add_custom_command\):
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
 
+)+(
+CMake Error at BadOutput.cmake:8 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OUTPUT_CONFIG:h>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+)+(
+CMake Error at BadOutput.cmake:9 \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<COMMAND_CONFIG:i>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
 )+

+ 2 - 0
Tests/RunCMake/add_custom_command/BadOutput.cmake

@@ -5,3 +5,5 @@ add_custom_command(OUTPUT "a>" COMMAND c)
 add_custom_command(OUTPUT "$<CONFIG>/#" COMMAND d)
 add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/e COMMAND f)
 add_custom_command(OUTPUT "$<TARGET_PROPERTY:prop>" COMMAND g)
+add_custom_command(OUTPUT "$<OUTPUT_CONFIG:h>" COMMAND h)
+add_custom_command(OUTPUT "$<COMMAND_CONFIG:i>" COMMAND i)

+ 1 - 0
Tests/RunCMake/add_custom_command/RunCMakeTest.cmake

@@ -6,6 +6,7 @@ run_cmake(AppendNotOutput)
 run_cmake(BadArgument)
 run_cmake(BadByproduct)
 run_cmake(BadOutput)
+run_cmake(BadCommand)
 run_cmake(GeneratedProperty)
 run_cmake(LiteralQuotes)
 run_cmake(NoArguments)

+ 20 - 0
Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt

@@ -44,4 +44,24 @@ CMake Error at BadByproduct.cmake:7 \(add_custom_target\):
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
 
+)+(
+CMake Error at BadByproduct.cmake:8 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<OUTPUT_CONFIG:n>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+)+(
+CMake Error at BadByproduct.cmake:9 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<COMMAND_CONFIG:p>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
 )+

+ 2 - 0
Tests/RunCMake/add_custom_target/BadByproduct.cmake

@@ -5,3 +5,5 @@ add_custom_target(e BYPRODUCTS "a>" COMMAND f)
 add_custom_target(g BYPRODUCTS "$<CONFIG>/#" COMMAND h)
 add_custom_target(i BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/j COMMAND k)
 add_custom_target(l BYPRODUCTS "$<TARGET_PROPERTY:prop>" COMMAND m)
+add_custom_target(n BYPRODUCTS "$<OUTPUT_CONFIG:n>" COMMAND o)
+add_custom_target(p BYPRODUCTS "$<COMMAND_CONFIG:p>" COMMAND q)

+ 1 - 0
Tests/RunCMake/add_custom_target/BadCommand-result.txt

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

+ 21 - 0
Tests/RunCMake/add_custom_target/BadCommand-stderr.txt

@@ -0,0 +1,21 @@
+(CMake Error at BadCommand.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<OUTPUT_CONFIG:a>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+
+)+(CMake Error at BadCommand.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<COMMAND_CONFIG:b>
+
+  Expression did not evaluate to a known generator expression
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+
+
+)+

+ 4 - 0
Tests/RunCMake/add_custom_target/BadCommand.cmake

@@ -0,0 +1,4 @@
+add_custom_target(drive
+  COMMAND "$<1:$<OUTPUT_CONFIG:a>>"
+  COMMAND "$<1:$<COMMAND_CONFIG:b>>"
+  )

+ 1 - 0
Tests/RunCMake/add_custom_target/RunCMakeTest.cmake

@@ -1,6 +1,7 @@
 include(RunCMake)
 
 run_cmake(BadByproduct)
+run_cmake(BadCommand)
 run_cmake(BadTargetName)
 run_cmake(ByproductsNoCommand)
 run_cmake(CommandExpandsEmpty)