浏览代码

Merge topic 'autogen-exe-per-config'

596e84b0d5 NinjaMultiConfig: Update tests for the new dependency change
7c39dabdbc Autogen: AUTO*_EXECUTABLE: add support for per-config values

Acked-by: Kitware Robot <[email protected]>
Tested-by: buildbot <[email protected]>
Merge-request: !8919
Brad King 1 年之前
父节点
当前提交
c75047709f
共有 33 个文件被更改,包括 1089 次插入235 次删除
  1. 2 0
      Auxiliary/vim/syntax/cmake.vim
  2. 1 0
      Help/manual/cmake-properties.7.rst
  3. 1 0
      Help/manual/cmake-variables.7.rst
  4. 22 0
      Help/prop_tgt/AUTOGEN_BETTER_GRAPH_MULTI_CONFIG.rst
  5. 10 0
      Help/variable/CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG.rst
  6. 17 0
      Source/cmQtAutoGen.h
  7. 73 18
      Source/cmQtAutoGenGlobalInitializer.cxx
  8. 7 4
      Source/cmQtAutoGenGlobalInitializer.h
  9. 275 97
      Source/cmQtAutoGenInitializer.cxx
  10. 13 22
      Source/cmQtAutoGenInitializer.h
  11. 3 1
      Source/cmQtAutoGenerator.cxx
  12. 7 1
      Source/cmQtAutoGenerator.h
  13. 46 10
      Source/cmQtAutoMocUic.cxx
  14. 2 1
      Source/cmQtAutoMocUic.h
  15. 49 9
      Source/cmQtAutoRcc.cxx
  16. 2 1
      Source/cmQtAutoRcc.h
  17. 1 0
      Source/cmTarget.cxx
  18. 6 2
      Source/cmcmd.cxx
  19. 321 47
      Tests/RunCMake/Autogen/RunCMakeTest.cmake
  20. 4 0
      Tests/RunCMake/Autogen/data.qrc
  21. 5 0
      Tests/RunCMake/Autogen/example.cpp
  22. 12 0
      Tests/RunCMake/Autogen/example.h
  23. 5 0
      Tests/RunCMake/Autogen/example_ui.cpp
  24. 14 0
      Tests/RunCMake/Autogen/example_ui.h
  25. 4 0
      Tests/RunCMake/Autogen/exe.cpp
  26. 48 0
      Tests/RunCMake/Autogen/exe_common.h
  27. 10 0
      Tests/RunCMake/Autogen/exe_debug.cpp
  28. 10 0
      Tests/RunCMake/Autogen/exe_release.cpp
  29. 10 0
      Tests/RunCMake/Autogen/exe_relwithdebinfo.cpp
  30. 24 0
      Tests/RunCMake/Autogen/uiA.ui
  31. 0 7
      Tests/RunCMake/NinjaMultiConfig/QtX-debug-in-release-graph-build-check.cmake
  32. 26 10
      Tests/RunCMake/NinjaMultiConfig/QtX.cmake
  33. 59 5
      Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake

+ 2 - 0
Auxiliary/vim/syntax/cmake.vim

@@ -78,6 +78,7 @@ syn keyword cmakeProperty contained
             \ AUTOGEN_TARGETS_FOLDER
             \ AUTOGEN_TARGET_DEPENDS
             \ AUTOGEN_USE_SYSTEM_INCLUDE
+            \ AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
             \ AUTOMOC
             \ AUTOMOC_COMPILER_PREDEFINES
             \ AUTOMOC_DEPEND_FILTERS
@@ -766,6 +767,7 @@ syn keyword cmakeVariable contained
             \ CMAKE_ASM_STANDARD_REQUIRED
             \ CMAKE_ASM_SUPPORTED
             \ CMAKE_ASM_VISIBILITY_PRESET
+            \ CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
             \ CMAKE_AUTOGEN_COMMAND_LINE_LENGTH_MAX
             \ CMAKE_AUTOGEN_ORIGIN_DEPENDS
             \ CMAKE_AUTOGEN_PARALLEL

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -129,6 +129,7 @@ Properties on Targets
    /prop_tgt/ARCHIVE_OUTPUT_DIRECTORY_CONFIG
    /prop_tgt/ARCHIVE_OUTPUT_NAME
    /prop_tgt/ARCHIVE_OUTPUT_NAME_CONFIG
+   /prop_tgt/AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
    /prop_tgt/AUTOGEN_BUILD_DIR
    /prop_tgt/AUTOGEN_COMMAND_LINE_LENGTH_MAX
    /prop_tgt/AUTOGEN_ORIGIN_DEPENDS

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -389,6 +389,7 @@ Variables that Control the Build
    /variable/CMAKE_APPLE_SILICON_PROCESSOR
    /variable/CMAKE_ARCHIVE_OUTPUT_DIRECTORY
    /variable/CMAKE_ARCHIVE_OUTPUT_DIRECTORY_CONFIG
+   /variable/CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
    /variable/CMAKE_AUTOGEN_COMMAND_LINE_LENGTH_MAX
    /variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS
    /variable/CMAKE_AUTOGEN_PARALLEL

+ 22 - 0
Help/prop_tgt/AUTOGEN_BETTER_GRAPH_MULTI_CONFIG.rst

@@ -0,0 +1,22 @@
+AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
+---------------------------------
+
+.. versionadded:: 3.29
+
+``AUTOGEN_BETTER_GRAPH_MULTI_CONFIG`` is a boolean property that can be set
+on a target to have better dependency graph for multi-configuration generators.
+When this property is enabled, ``CMake`` will generate more per-config targets.
+Thus, the dependency graph will be more accurate for multi-configuration
+generators and some recompilations will be avoided.
+
+If the Qt version is 6.8 or newer, this property is enabled by default.
+If the Qt version is older than 6.8, this property is disabled by default.
+Consult the Qt documentation to check if the property can be enabled for older
+Qt versions.
+
+See the :manual:`cmake-qt(7)` manual for more information on using CMake
+with Qt.
+
+This property is initialized by the
+:variable:`CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG` variable if it is set when
+a target is created.

+ 10 - 0
Help/variable/CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG.rst

@@ -0,0 +1,10 @@
+CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG
+---------------------------------------
+
+.. versionadded:: 3.29
+
+This variable is used to initialize the
+:prop_tgt:`AUTOGEN_BETTER_GRAPH_MULTI_CONFIG` property on all targets as they
+are created.  See that target property for additional information.
+
+By default ``CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG`` is unset.

+ 17 - 0
Source/cmQtAutoGen.h

@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <cm/string_view>
@@ -16,6 +17,22 @@
 class cmQtAutoGen
 {
 public:
+  /** String value with per configuration variants.  */
+  class ConfigString
+  {
+  public:
+    std::string Default;
+    std::unordered_map<std::string, std::string> Config;
+  };
+
+  /** String values with per configuration variants.  */
+  template <typename C>
+  class ConfigStrings
+  {
+  public:
+    C Default;
+    std::unordered_map<std::string, C> Config;
+  };
   /** Integer version.  */
   struct IntegerVersion
   {

+ 73 - 18
Source/cmQtAutoGenGlobalInitializer.cxx

@@ -213,24 +213,81 @@ void cmQtAutoGenGlobalInitializer::AddToGlobalAutoRcc(
   }
 }
 
-cmQtAutoGen::CompilerFeaturesHandle
+cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle>
 cmQtAutoGenGlobalInitializer::GetCompilerFeatures(
-  std::string const& generator, std::string const& executable,
-  std::string& error)
+  std::string const& generator, cmQtAutoGen::ConfigString const& executable,
+  std::string& error, bool const isMultiConfig, bool UseBetterGraph)
 {
+  cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle> res;
+  if (isMultiConfig && UseBetterGraph) {
+    for (auto const& config : executable.Config) {
+      auto const exe = config.second;
+      // Check if we have cached features
+      {
+        auto it = this->CompilerFeatures_.Config[config.first].find(exe);
+        if (it != this->CompilerFeatures_.Config[config.first].end()) {
+          res.Config[config.first] = it->second;
+          continue;
+        }
+      }
+
+      // Check if the executable exists
+      if (!cmSystemTools::FileExists(exe, true)) {
+        error = cmStrCat("The \"", generator, "\" executable ",
+                         cmQtAutoGen::Quoted(exe), " does not exist.");
+        res.Config[config.first] = {};
+        continue;
+      }
+
+      // Test the executable
+      std::string stdOut;
+      {
+        std::string stdErr;
+        std::vector<std::string> command;
+        command.emplace_back(exe);
+        command.emplace_back("-h");
+        int retVal = 0;
+        const bool runResult = cmSystemTools::RunSingleCommand(
+          command, &stdOut, &stdErr, &retVal, nullptr,
+          cmSystemTools::OUTPUT_NONE, cmDuration::zero(),
+          cmProcessOutput::Auto);
+        if (!runResult) {
+          error = cmStrCat("Test run of \"", generator, "\" executable ",
+                           cmQtAutoGen::Quoted(exe), " failed.\n",
+                           cmQtAutoGen::QuotedCommand(command), '\n', stdOut,
+                           '\n', stdErr);
+          res.Config[config.first] = {};
+          continue;
+        }
+      }
+
+      // Create valid handle
+      res.Config[config.first] =
+        std::make_shared<cmQtAutoGen::CompilerFeatures>();
+      res.Config[config.first]->HelpOutput = std::move(stdOut);
+
+      // Register compiler features
+      this->CompilerFeatures_.Config[config.first].emplace(
+        exe, res.Config[config.first]);
+    }
+    return res;
+  }
+
   // Check if we have cached features
   {
-    auto it = this->CompilerFeatures_.find(executable);
-    if (it != this->CompilerFeatures_.end()) {
-      return it->second;
+    auto it = this->CompilerFeatures_.Default.find(executable.Default);
+    if (it != this->CompilerFeatures_.Default.end()) {
+      res.Default = it->second;
+      return res;
     }
   }
 
   // Check if the executable exists
-  if (!cmSystemTools::FileExists(executable, true)) {
-    error = cmStrCat("The \"", generator, "\" executable ",
-                     cmQtAutoGen::Quoted(executable), " does not exist.");
-    return cmQtAutoGen::CompilerFeaturesHandle();
+  if (!cmSystemTools::FileExists(executable.Default, true)) {
+    error =
+      cmStrCat("The \"", generator, "\" executable ",
+               cmQtAutoGen::Quoted(executable.Default), " does not exist.");
+    return cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle>();
   }
 
   // Test the executable
@@ -238,7 +295,7 @@ cmQtAutoGenGlobalInitializer::GetCompilerFeatures(
   {
     std::string stdErr;
     std::vector<std::string> command;
-    command.emplace_back(executable);
+    command.emplace_back(executable.Default);
     command.emplace_back("-h");
     int retVal = 0;
     const bool runResult = cmSystemTools::RunSingleCommand(
@@ -246,20 +303,18 @@ cmQtAutoGenGlobalInitializer::GetCompilerFeatures(
       cmDuration::zero(), cmProcessOutput::Auto);
     if (!runResult) {
       error = cmStrCat("Test run of \"", generator, "\" executable ",
-                       cmQtAutoGen::Quoted(executable), " failed.\n",
+                       cmQtAutoGen::Quoted(executable.Default), " failed.\n",
                        cmQtAutoGen::QuotedCommand(command), '\n', stdOut, '\n',
                        stdErr);
-      return cmQtAutoGen::CompilerFeaturesHandle();
+      return cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle>();
     }
   }
 
-  // Create valid handle
-  cmQtAutoGen::CompilerFeaturesHandle res =
-    std::make_shared<cmQtAutoGen::CompilerFeatures>();
-  res->HelpOutput = std::move(stdOut);
+  res.Default = std::make_shared<cmQtAutoGen::CompilerFeatures>();
+  res.Default->HelpOutput = std::move(stdOut);
 
   // Register compiler features
-  this->CompilerFeatures_.emplace(executable, res);
+  this->CompilerFeatures_.Default.emplace(executable.Default, res.Default);
 
   return res;
 }

+ 7 - 4
Source/cmQtAutoGenGlobalInitializer.h

@@ -66,14 +66,17 @@ private:
   void AddToGlobalAutoRcc(cmLocalGenerator* localGen,
                           std::string const& targetName);
 
-  cmQtAutoGen::CompilerFeaturesHandle GetCompilerFeatures(
-    std::string const& generator, std::string const& executable,
-    std::string& error);
+  cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle>
+  GetCompilerFeatures(std::string const& generator,
+                      cmQtAutoGen::ConfigString const& executable,
+                      std::string& error, bool isMultiConfig,
+                      bool UseBetterGraph);
 
   std::vector<std::unique_ptr<cmQtAutoGenInitializer>> Initializers_;
   std::map<cmLocalGenerator*, std::string> GlobalAutoGenTargets_;
   std::map<cmLocalGenerator*, std::string> GlobalAutoRccTargets_;
-  std::unordered_map<std::string, cmQtAutoGen::CompilerFeaturesHandle>
+  cmQtAutoGen::ConfigStrings<
+    std::unordered_map<std::string, cmQtAutoGen::CompilerFeaturesHandle>>
     CompilerFeatures_;
   Keywords const Keywords_;
 };

+ 275 - 97
Source/cmQtAutoGenInitializer.cxx

@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmQtAutoGenInitializer.h"
 
+#include <array>
 #include <cstddef>
 #include <deque>
 #include <initializer_list>
@@ -17,6 +18,7 @@
 #include <cm/algorithm>
 #include <cm/iterator>
 #include <cm/memory>
+#include <cm/string_view>
 #include <cmext/algorithm>
 #include <cmext/string_view>
 
@@ -301,15 +303,22 @@ bool InfoWriter::Save(std::string const& filename)
   return fileStream.Close();
 }
 
-void AddAutogenExecutableToDependencies(
-  cmQtAutoGenInitializer::GenVarsT const& genVars,
-  std::vector<std::string>& dependencies)
+cmQtAutoGen::ConfigStrings<std::vector<std::string>> generateListOptions(
+  cmQtAutoGen::ConfigStrings<cmQtAutoGen::CompilerFeaturesHandle> const&
+    executableFeatures,
+  bool IsMultiConfig)
 {
-  if (genVars.ExecutableTarget != nullptr) {
-    dependencies.push_back(genVars.ExecutableTarget->Target->GetName());
-  } else if (!genVars.Executable.empty()) {
-    dependencies.push_back(genVars.Executable);
+  cmQtAutoGen::ConfigStrings<std::vector<std::string>> tempListOptions;
+  if (IsMultiConfig) {
+    for (auto const& executableFeature : executableFeatures.Config) {
+      tempListOptions.Config[executableFeature.first] =
+        executableFeature.second->ListOptions;
+    }
+  } else {
+    tempListOptions.Default = executableFeatures.Default->ListOptions;
   }
+
+  return tempListOptions;
 }
 
 } // End of unnamed namespace
@@ -334,6 +343,42 @@ cmQtAutoGenInitializer::cmQtAutoGenInitializer(
   this->Rcc.GlobalTarget = globalAutoRccTarget;
   this->CrossConfig =
     !this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty();
+  this->UseBetterGraph =
+    this->GenTarget->GetProperty("AUTOGEN_BETTER_GRAPH_MULTI_CONFIG").IsSet()
+    ? this->GenTarget->GetProperty("AUTOGEN_BETTER_GRAPH_MULTI_CONFIG").IsOn()
+    : (this->QtVersion >= IntegerVersion(6, 8));
+  // AUTOGEN_BETTER_GRAPH_MULTI_CONFIG is set explicitly because it is read by
+  // the qt library
+  this->GenTarget->Target->SetProperty("AUTOGEN_BETTER_GRAPH_MULTI_CONFIG",
+                                       this->UseBetterGraph ? "ON" : "OFF");
+}
+
+void cmQtAutoGenInitializer::AddAutogenExecutableToDependencies(
+  cmQtAutoGenInitializer::GenVarsT const& genVars,
+  std::vector<std::string>& dependencies) const
+{
+  if (genVars.ExecutableTarget != nullptr) {
+    dependencies.push_back(genVars.ExecutableTarget->Target->GetName());
+  } else if (this->MultiConfig && this->UseBetterGraph) {
+    cm::string_view const& configGenexWithCommandConfig =
+      "$<COMMAND_CONFIG:$<$<CONFIG:";
+    cm::string_view const& configGenex = "$<$<CONFIG:";
+    cm::string_view const& configGenexEnd = ">";
+    cm::string_view const& configGenexEndWithCommandConfig = ">>";
+    auto genexBegin =
+      this->CrossConfig ? configGenexWithCommandConfig : configGenex;
+    auto genexEnd =
+      this->CrossConfig ? configGenexEndWithCommandConfig : configGenexEnd;
+    for (auto const& config : genVars.Executable.Config) {
+      auto executableWithConfig =
+        cmStrCat(genexBegin, config.first, ">:", config.second, genexEnd);
+      dependencies.emplace_back(std::move(executableWithConfig));
+    }
+  } else {
+    if (!genVars.Executable.Default.empty()) {
+      dependencies.push_back(genVars.Executable.Default);
+    }
+  }
 }
 
 bool cmQtAutoGenInitializer::InitCustomTargets()
@@ -811,18 +856,30 @@ bool cmQtAutoGenInitializer::InitRcc()
       return false;
     }
     // Evaluate test output on demand
-    CompilerFeatures& features = *this->Rcc.ExecutableFeatures;
-    if (!features.Evaluated) {
-      // Look for list options
-      if (this->QtVersion.Major == 5 || this->QtVersion.Major == 6) {
-        if (features.HelpOutput.find("--list") != std::string::npos) {
-          features.ListOptions.emplace_back("--list");
-        } else if (features.HelpOutput.find("-list") != std::string::npos) {
-          features.ListOptions.emplace_back("-list");
+    auto& features = this->Rcc.ExecutableFeatures;
+    auto checkAndAddOptions = [this](CompilerFeaturesHandle& feature) {
+      if (!feature->Evaluated) {
+        // Look for list options
+        if (this->QtVersion.Major == 5 || this->QtVersion.Major == 6) {
+          static std::array<std::string, 2> const listOptions{ { "--list",
+                                                                 "-list" } };
+          for (std::string const& opt : listOptions) {
+            if (feature->HelpOutput.find(opt) != std::string::npos) {
+              feature->ListOptions.emplace_back(opt);
+              break;
+            }
+          }
         }
+        // Evaluation finished
+        feature->Evaluated = true;
       }
-      // Evaluation finished
-      features.Evaluated = true;
+    };
+    if (this->MultiConfig && this->UseBetterGraph) {
+      for (auto const& config : this->ConfigsList) {
+        checkAndAddOptions(features.Config[config]);
+      }
+    } else {
+      checkAndAddOptions(features.Default);
     }
   }
 
@@ -1158,8 +1215,14 @@ bool cmQtAutoGenInitializer::InitScanFiles()
       // Path checksum
       qrc.QrcPathChecksum = this->PathCheckSum.getPart(qrc.QrcFile);
       // Output file name
-      qrc.OutputFile = cmStrCat(this->Dir.Build, '/', qrc.QrcPathChecksum,
-                                "/qrc_", qrc.QrcName, ".cpp");
+      if (this->MultiConfig && !this->GlobalGen->IsXcode() &&
+          this->UseBetterGraph) {
+        qrc.OutputFile = cmStrCat(this->Dir.Build, '/', qrc.QrcPathChecksum,
+                                  "_$<CONFIG>", "/qrc_", qrc.QrcName, ".cpp");
+      } else {
+        qrc.OutputFile = cmStrCat(this->Dir.Build, '/', qrc.QrcPathChecksum,
+                                  "/qrc_", qrc.QrcName, ".cpp");
+      }
       std::string const base = cmStrCat(this->Dir.Info, "/AutoRcc_",
                                         qrc.QrcName, '_', qrc.QrcPathChecksum);
       qrc.LockFile = cmStrCat(base, "_Lock.lock");
@@ -1191,11 +1254,25 @@ bool cmQtAutoGenInitializer::InitScanFiles()
     for (Qrc& qrc : this->Rcc.Qrcs) {
       if (!qrc.Generated) {
         std::string error;
-        RccLister const lister(this->Rcc.Executable,
-                               this->Rcc.ExecutableFeatures->ListOptions);
-        if (!lister.list(qrc.QrcFile, qrc.Resources, error)) {
-          cmSystemTools::Error(error);
-          return false;
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            RccLister const lister(
+              this->Rcc.Executable.Config[config],
+              this->Rcc.ExecutableFeatures.Config[config]->ListOptions);
+            if (!lister.list(qrc.QrcFile, qrc.Resources.Config[config],
+                             error)) {
+              cmSystemTools::Error(error);
+              return false;
+            }
+          }
+        } else {
+          RccLister const lister(
+            this->Rcc.Executable.Default,
+            this->Rcc.ExecutableFeatures.Default->ListOptions);
+          if (!lister.list(qrc.QrcFile, qrc.Resources.Default, error)) {
+            cmSystemTools::Error(error);
+            return false;
+          }
         }
       }
     }
@@ -1223,8 +1300,9 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
   if (this->Moc.Enabled) {
     this->AddGeneratedSource(this->Moc.CompilationFile, this->Moc, true);
     if (useDepfile) {
-      if (this->MultiConfig && this->CrossConfig &&
-          this->GlobalGen->GetName().find("Ninja") != std::string::npos) {
+      if (this->CrossConfig &&
+          this->GlobalGen->GetName().find("Ninja") != std::string::npos &&
+          !this->UseBetterGraph) {
         // Make all mocs_compilation_<CONFIG>.cpp files byproducts of the
         // ${target}_autogen/timestamp custom command.
         // We cannot just use Moc.CompilationFileGenex here, because that
@@ -1267,28 +1345,11 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
   // Compose command lines
   // FIXME: Take advantage of our per-config mocs_compilation_$<CONFIG>.cpp
   // instead of fiddling with the include directories
-  std::vector<std::string> configs;
-  this->GlobalGen->GetQtAutoGenConfigs(configs);
+
   bool constexpr stdPipesUTF8 = true;
   cmCustomCommandLines commandLines;
-  if (!this->CrossConfig) {
-    std::string autogenInfoFileConfig;
-    if (this->MultiConfig) {
-      autogenInfoFileConfig = "$<CONFIG>";
-    } else {
-      autogenInfoFileConfig = configs[0];
-    }
-    commandLines.push_back(cmMakeCommandLine(
-      { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
-        this->AutogenTarget.InfoFile, autogenInfoFileConfig }));
-
-  } else {
-    for (auto const& config : configs) {
-      commandLines.push_back(cmMakeCommandLine(
-        { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
-          this->AutogenTarget.InfoFile, config }));
-    }
-  }
+  AddCMakeProcessToCommandLines(this->AutogenTarget.InfoFile, "cmake_autogen",
+                                commandLines);
 
   // Use PRE_BUILD on demand
   bool usePRE_BUILD = false;
@@ -1456,18 +1517,47 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
 
       AddAutogenExecutableToDependencies(this->Moc, dependencies);
       AddAutogenExecutableToDependencies(this->Uic, dependencies);
-
+      std::string outputFile;
+      std::string depFile;
       // Create the custom command that outputs the timestamp file.
-      const char timestampFileName[] = "timestamp";
-      const std::string outputFile =
-        cmStrCat(this->Dir.Build, "/", timestampFileName);
-      this->AutogenTarget.DepFile = cmStrCat(this->Dir.Build, "/deps");
-      this->AutogenTarget.DepFileRuleName =
-        cmStrCat(this->Dir.RelativeBuild, "/", timestampFileName);
-      commandLines.push_back(cmMakeCommandLine(
-        { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile }));
-
-      this->AddGeneratedSource(outputFile, this->Moc);
+      if (this->MultiConfig && this->UseBetterGraph) {
+        // create timestamp file with $<CONFIG> in the name so that
+        // every cmake_autogen target has its own timestamp file
+        std::string const configView = "$<CONFIG>";
+        std::string const timestampFileWithoutConfig = "timestamp_";
+        std::string const depFileWithoutConfig =
+          cmStrCat(this->Dir.Build, "/deps_");
+        std::string const timestampFileName =
+          timestampFileWithoutConfig + configView;
+        outputFile = cmStrCat(this->Dir.Build, "/", timestampFileName);
+        auto const depFileWithConfig =
+          cmStrCat(depFileWithoutConfig, configView);
+        depFile = depFileWithConfig;
+        commandLines.push_back(cmMakeCommandLine(
+          { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile }));
+
+        ConfigString outputFileWithConfig;
+        for (std::string const& config : this->ConfigsList) {
+          auto tempTimestampFileName = timestampFileWithoutConfig + config;
+          auto tempDepFile = depFileWithoutConfig + config;
+          outputFileWithConfig.Config[config] = tempTimestampFileName;
+          this->AutogenTarget.DepFileRuleName.Config[config] =
+            cmStrCat(this->Dir.RelativeBuild, "/", tempTimestampFileName);
+          this->AutogenTarget.DepFile.Config[config] = tempDepFile;
+        }
+        this->AddGeneratedSource(outputFileWithConfig, this->Moc);
+      } else {
+        cm::string_view const timestampFileName = "timestamp";
+        outputFile = cmStrCat(this->Dir.Build, "/", timestampFileName);
+        this->AutogenTarget.DepFile.Default =
+          cmStrCat(this->Dir.Build, "/deps");
+        depFile = this->AutogenTarget.DepFile.Default;
+        this->AutogenTarget.DepFileRuleName.Default =
+          cmStrCat(this->Dir.RelativeBuild, "/", timestampFileName);
+        commandLines.push_back(cmMakeCommandLine(
+          { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile }));
+        this->AddGeneratedSource(outputFile, this->Moc);
+      }
       cc = cm::make_unique<cmCustomCommand>();
       cc->SetOutputs(outputFile);
       cc->SetByproducts(timestampByproducts);
@@ -1476,14 +1566,11 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
       cc->SetComment(autogenComment.c_str());
       cc->SetWorkingDirectory(this->Dir.Work.c_str());
       cc->SetEscapeOldStyle(false);
-      cc->SetDepfile(this->AutogenTarget.DepFile);
+      cc->SetDepfile(depFile);
       cc->SetStdPipesUTF8(stdPipesUTF8);
       this->LocalGen->AddCustomCommandToOutput(std::move(cc));
-
-      // Alter variables for the autogen target which now merely wraps the
-      // custom command
       dependencies.clear();
-      dependencies.emplace_back(outputFile);
+      dependencies.emplace_back(std::move(outputFile));
       commandLines.clear();
       autogenComment.clear();
     }
@@ -1535,6 +1622,36 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
   return true;
 }
 
+void cmQtAutoGenInitializer::AddCMakeProcessToCommandLines(
+  std::string const& infoFile, std::string const& processName,
+  cmCustomCommandLines& commandLines)
+{
+  if (this->CrossConfig && this->UseBetterGraph) {
+    commandLines.push_back(cmMakeCommandLine(
+      { cmSystemTools::GetCMakeCommand(), "-E", processName, infoFile,
+        "$<CONFIG>", "$<COMMAND_CONFIG:$<CONFIG>>" }));
+  } else if ((this->MultiConfig && this->GlobalGen->IsXcode()) ||
+             this->CrossConfig) {
+    for (std::string const& config : this->ConfigsList) {
+      commandLines.push_back(
+        cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E",
+                            processName, infoFile, config }));
+    }
+  } else {
+    std::string autoInfoFileConfig;
+    if (this->MultiConfig) {
+      autoInfoFileConfig = "$<CONFIG>";
+    } else {
+      std::vector<std::string> configs;
+      this->GlobalGen->GetQtAutoGenConfigs(configs);
+      autoInfoFileConfig = configs[0];
+    }
+    commandLines.push_back(
+      cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", processName,
+                          infoFile, autoInfoFileConfig }));
+  }
+}
+
 bool cmQtAutoGenInitializer::InitRccTargets()
 {
   for (Qrc const& qrc : this->Rcc.Qrcs) {
@@ -1555,18 +1672,7 @@ bool cmQtAutoGenInitializer::InitRccTargets()
     ccDepends.push_back(qrc.InfoFile);
 
     cmCustomCommandLines commandLines;
-    if (this->MultiConfig) {
-      // Build for all configurations
-      for (std::string const& config : this->ConfigsList) {
-        commandLines.push_back(
-          cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E",
-                              "cmake_autorcc", qrc.InfoFile, config }));
-      }
-    } else {
-      commandLines.push_back(
-        cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E",
-                            "cmake_autorcc", qrc.InfoFile, "$<CONFIG>" }));
-    }
+    AddCMakeProcessToCommandLines(qrc.InfoFile, "cmake_autorcc", commandLines);
 
     std::string const ccComment =
       cmStrCat("Automatic RCC for ",
@@ -1617,13 +1723,28 @@ bool cmQtAutoGenInitializer::InitRccTargets()
       // Create custom rcc command
       {
         // Add the resource files to the dependencies
-        for (std::string const& fileName : qrc.Resources) {
-          // Add resource file to the custom command dependencies
-          ccDepends.push_back(fileName);
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            // Add resource file to the custom command dependencies
+            auto resourceFilesWithConfig = cmStrCat(
+              "$<$<CONFIG:", config,
+              ">:", cmList{ qrc.Resources.Config.at(config) }.to_string(),
+              ">");
+            ccDepends.emplace_back(std::move(resourceFilesWithConfig));
+          }
+        } else {
+          for (std::string const& fileName : qrc.Resources.Default) {
+            // Add resource file to the custom command dependencies
+            ccDepends.push_back(fileName);
+          }
         }
+
         if (!this->Rcc.ExecutableTargetName.empty()) {
           ccDepends.push_back(this->Rcc.ExecutableTargetName);
         }
+
+        AddAutogenExecutableToDependencies(this->Rcc, ccDepends);
+
         cc->SetOutputs(ccOutput);
         cc->SetDepends(ccDepends);
         this->LocalGen->AddCustomCommandToOutput(std::move(cc));
@@ -1723,6 +1844,8 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
 
   // General
   info.SetBool("MULTI_CONFIG", this->MultiConfig);
+  info.SetBool("CROSS_CONFIG", this->CrossConfig);
+  info.SetBool("USE_BETTER_GRAPH", this->UseBetterGraph);
   info.SetUInt("PARALLEL", this->AutogenTarget.Parallel);
 #ifdef _WIN32
   info.SetUInt("AUTOGEN_COMMAND_LINE_LENGTH_MAX",
@@ -1740,14 +1863,14 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
 
   info.SetUInt("QT_VERSION_MAJOR", this->QtVersion.Major);
   info.SetUInt("QT_VERSION_MINOR", this->QtVersion.Minor);
-  info.Set("QT_MOC_EXECUTABLE", this->Moc.Executable);
-  info.Set("QT_UIC_EXECUTABLE", this->Uic.Executable);
+  info.SetConfig("QT_MOC_EXECUTABLE", this->Moc.Executable);
+  info.SetConfig("QT_UIC_EXECUTABLE", this->Uic.Executable);
 
   info.Set("CMAKE_EXECUTABLE", cmSystemTools::GetCMakeCommand());
   info.SetConfig("SETTINGS_FILE", this->AutogenTarget.SettingsFile);
   info.SetConfig("PARSE_CACHE_FILE", this->AutogenTarget.ParseCacheFile);
-  info.Set("DEP_FILE", this->AutogenTarget.DepFile);
-  info.Set("DEP_FILE_RULE_NAME", this->AutogenTarget.DepFileRuleName);
+  info.SetConfig("DEP_FILE", this->AutogenTarget.DepFile);
+  info.SetConfig("DEP_FILE_RULE_NAME", this->AutogenTarget.DepFileRuleName);
   info.SetArray("CMAKE_LIST_FILES", this->Makefile->GetListFiles());
   info.SetArray("HEADER_EXTENSIONS",
                 this->Makefile->GetCMakeInstance()->GetHeaderExtensions());
@@ -1874,6 +1997,8 @@ bool cmQtAutoGenInitializer::SetupWriteRccInfo()
 
     // General
     info.SetBool("MULTI_CONFIG", this->MultiConfig);
+    info.SetBool("CROSS_CONFIG", this->CrossConfig);
+    info.SetBool("USE_BETTER_GRAPH", this->UseBetterGraph);
     info.SetUInt("VERBOSITY", this->Verbosity);
     info.Set("GENERATOR", this->GlobalGen->GetName());
 
@@ -1890,16 +2015,17 @@ bool cmQtAutoGenInitializer::SetupWriteRccInfo()
     info.SetConfig("INCLUDE_DIR", this->Dir.Include);
 
     // rcc executable
-    info.Set("RCC_EXECUTABLE", this->Rcc.Executable);
-    info.SetArray("RCC_LIST_OPTIONS",
-                  this->Rcc.ExecutableFeatures->ListOptions);
+    info.SetConfig("RCC_EXECUTABLE", this->Rcc.Executable);
+    info.SetConfigArray(
+      "RCC_LIST_OPTIONS",
+      generateListOptions(this->Rcc.ExecutableFeatures, this->MultiConfig));
 
     // qrc file
     info.Set("SOURCE", qrc.QrcFile);
     info.Set("OUTPUT_CHECKSUM", qrc.QrcPathChecksum);
     info.Set("OUTPUT_NAME", cmSystemTools::GetFilenameName(qrc.OutputFile));
     info.SetArray("OPTIONS", qrc.Options);
-    info.SetArray("INPUTS", qrc.Resources);
+    info.SetConfigArray("INPUTS", qrc.Resources);
 
     info.Save(qrc.InfoFile);
   }
@@ -2244,16 +2370,32 @@ bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars,
         cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
         cmGeneratorExpression ge(*this->Makefile->GetCMakeInstance(), lfbt);
         std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(val);
-        genVars.Executable = cge->Evaluate(this->LocalGen, "");
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            genVars.Executable.Config[config] =
+              cge->Evaluate(this->LocalGen, config);
+          }
+        } else {
+          genVars.Executable.Default = cge->Evaluate(this->LocalGen, "");
+        }
       }
-      if (genVars.Executable.empty() && !ignoreMissingTarget) {
+
+      if (genVars.Executable.Default.empty() &&
+          genVars.Executable.Config.empty() && !ignoreMissingTarget) {
         print_err(prop + " evaluates to an empty value");
         return false;
       }
 
       // Create empty compiler features.
-      genVars.ExecutableFeatures =
-        std::make_shared<cmQtAutoGen::CompilerFeatures>();
+      if (this->MultiConfig && this->UseBetterGraph) {
+        for (auto const& config : this->ConfigsList) {
+          genVars.ExecutableFeatures.Config[config] =
+            std::make_shared<cmQtAutoGen::CompilerFeatures>();
+        }
+      } else {
+        genVars.ExecutableFeatures.Default =
+          std::make_shared<cmQtAutoGen::CompilerFeatures>();
+      }
       return true;
     }
   }
@@ -2278,15 +2420,39 @@ bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars,
       genVars.ExecutableTargetName = targetName;
       genVars.ExecutableTarget = genTarget;
       if (genTarget->IsImported()) {
-        genVars.Executable = genTarget->ImportedGetLocation("");
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            genVars.Executable.Config[config] =
+              genTarget->ImportedGetLocation(config);
+          }
+        } else {
+          genVars.Executable.Default =
+            genTarget->ImportedGetLocation(this->ConfigDefault);
+        }
+
       } else {
-        genVars.Executable = genTarget->GetLocation("");
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            genVars.Executable.Config[config] = genTarget->GetLocation(config);
+          }
+        } else {
+          genVars.Executable.Default =
+            genTarget->GetLocation(this->ConfigDefault);
+        }
       }
     } else {
       if (ignoreMissingTarget) {
         // Create empty compiler features.
-        genVars.ExecutableFeatures =
-          std::make_shared<cmQtAutoGen::CompilerFeatures>();
+        if (this->MultiConfig && this->UseBetterGraph) {
+          for (auto const& config : this->ConfigsList) {
+            genVars.ExecutableFeatures.Config[config] =
+              std::make_shared<cmQtAutoGen::CompilerFeatures>();
+          }
+        } else {
+          genVars.ExecutableFeatures.Default =
+            std::make_shared<cmQtAutoGen::CompilerFeatures>();
+        }
+
         return true;
       }
       print_err(cmStrCat("Could not find ", executable, " executable target ",
@@ -2299,10 +2465,22 @@ bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars,
   {
     std::string err;
     genVars.ExecutableFeatures = this->GlobalInitializer->GetCompilerFeatures(
-      executable, genVars.Executable, err);
-    if (!genVars.ExecutableFeatures) {
-      print_err(err);
-      return false;
+      executable, genVars.Executable, err, this->MultiConfig,
+      this->UseBetterGraph);
+    if (this->MultiConfig && this->UseBetterGraph) {
+      for (auto const& config : this->ConfigsList) {
+        if (!genVars.ExecutableFeatures.Config[config]) {
+          if (!genVars.ExecutableFeatures.Config[config]) {
+            print_err(err);
+            return false;
+          }
+        }
+      }
+    } else {
+      if (!genVars.ExecutableFeatures.Default) {
+        print_err(err);
+        return false;
+      }
     }
   }
 

+ 13 - 22
Source/cmQtAutoGenInitializer.h

@@ -19,6 +19,7 @@
 #include "cmFilePathChecksum.h"
 #include "cmQtAutoGen.h"
 
+class cmCustomCommandLines;
 class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmLocalGenerator;
@@ -33,23 +34,6 @@ class cmTarget;
 class cmQtAutoGenInitializer : public cmQtAutoGen
 {
 public:
-  /** String value with per configuration variants.  */
-  class ConfigString
-  {
-  public:
-    std::string Default;
-    std::unordered_map<std::string, std::string> Config;
-  };
-
-  /** String values with per configuration variants.  */
-  template <typename C>
-  class ConfigStrings
-  {
-  public:
-    C Default;
-    std::unordered_map<std::string, C> Config;
-  };
-
   /** rcc job.  */
   class Qrc
   {
@@ -64,7 +48,7 @@ public:
     bool Generated = false;
     bool Unique = false;
     std::vector<std::string> Options;
-    std::vector<std::string> Resources;
+    ConfigStrings<std::vector<std::string>> Resources;
   };
 
   /** moc and/or uic file.  */
@@ -91,8 +75,8 @@ public:
     // Executable
     std::string ExecutableTargetName;
     cmGeneratorTarget* ExecutableTarget = nullptr;
-    std::string Executable;
-    CompilerFeaturesHandle ExecutableFeatures;
+    ConfigString Executable;
+    ConfigStrings<CompilerFeaturesHandle> ExecutableFeatures;
 
     GenVarsT(GenT gen)
       : Gen(gen)
@@ -142,6 +126,9 @@ private:
                           GenVarsT const& genVars, bool prepend = false);
   void AddToSourceGroup(std::string const& fileName,
                         cm::string_view genNameUpper);
+  void AddCMakeProcessToCommandLines(std::string const& infoFile,
+                                     std::string const& processName,
+                                     cmCustomCommandLines& commandLines);
   void AddCleanFile(std::string const& fileName);
 
   void ConfigFileNames(ConfigString& configString, cm::string_view prefix,
@@ -156,6 +143,9 @@ private:
                        bool ignoreMissingTarget) const;
 
   void handleSkipPch(cmSourceFile* sf);
+  void AddAutogenExecutableToDependencies(
+    cmQtAutoGenInitializer::GenVarsT const& genVars,
+    std::vector<std::string>& dependencies) const;
 
   cmQtAutoGenGlobalInitializer* GlobalInitializer = nullptr;
   cmGeneratorTarget* GenTarget = nullptr;
@@ -169,6 +159,7 @@ private:
   unsigned int Verbosity = 0;
   bool MultiConfig = false;
   bool CrossConfig = false;
+  bool UseBetterGraph = false;
   bool CMP0071Accept = false;
   bool CMP0071Warn = false;
   bool CMP0100Accept = false;
@@ -205,8 +196,8 @@ private:
     bool DependOrigin = false;
     std::set<std::string> DependFiles;
     std::set<cmTarget*> DependTargets;
-    std::string DepFile;
-    std::string DepFileRuleName;
+    ConfigString DepFile;
+    ConfigString DepFileRuleName;
     // Sources to process
     std::unordered_map<cmSourceFile*, MUFileHandle> Headers;
     std::unordered_map<cmSourceFile*, MUFileHandle> Sources;

+ 3 - 1
Source/cmQtAutoGenerator.cxx

@@ -430,10 +430,12 @@ std::string cmQtAutoGenerator::MessagePath(cm::string_view path) const
   return cmQtAutoGen::Quoted(res);
 }
 
-bool cmQtAutoGenerator::Run(cm::string_view infoFile, cm::string_view config)
+bool cmQtAutoGenerator::Run(cm::string_view infoFile, cm::string_view config,
+                            cm::string_view executableConfig)
 {
   // Info config
   this->InfoConfig_ = std::string(config);
+  this->ExecutableConfig_ = std::string(executableConfig);
 
   // Info file
   this->InfoFile_ = std::string(infoFile);

+ 7 - 1
Source/cmQtAutoGenerator.h

@@ -90,6 +90,10 @@ public:
   std::string const& InfoDir() const { return this->InfoDir_; }
   cmFileTime const& InfoFileTime() const { return this->InfoFileTime_; }
   std::string const& InfoConfig() const { return this->InfoConfig_; }
+  std::string const& ExecutableConfig() const
+  {
+    return this->ExecutableConfig_;
+  }
 
   // -- Info file parsing
   /** Info file reader class. */
@@ -151,7 +155,8 @@ public:
   std::string MessagePath(cm::string_view path) const;
 
   // -- Run
-  bool Run(cm::string_view infoFile, cm::string_view config);
+  bool Run(cm::string_view infoFile, cm::string_view config,
+           cm::string_view executableConfig);
 
 protected:
   // -- Abstract processing interface
@@ -170,6 +175,7 @@ private:
   std::string InfoDir_;
   cmFileTime InfoFileTime_;
   std::string InfoConfig_;
+  std::string ExecutableConfig_;
   // -- Directories
   ProjectDirsT ProjectDirs_;
 };

+ 46 - 10
Source/cmQtAutoMocUic.cxx

@@ -171,6 +171,8 @@ public:
     // -- Attributes
     // - Config
     bool MultiConfig = false;
+    bool CrossConfig = false;
+    bool UseBetterGraph = false;
     IntegerVersion QtVersion = { 4, 0 };
     unsigned int ThreadCount = 0;
     unsigned int MaxCommandLineLength =
@@ -2380,6 +2382,9 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
 {
   // -- Required settings
   if (!info.GetBool("MULTI_CONFIG", this->BaseConst_.MultiConfig, true) ||
+      !info.GetBool("CROSS_CONFIG", this->BaseConst_.CrossConfig, true) ||
+      !info.GetBool("USE_BETTER_GRAPH", this->BaseConst_.UseBetterGraph,
+                    true) ||
       !info.GetUInt("QT_VERSION_MAJOR", this->BaseConst_.QtVersion.Major,
                     true) ||
       !info.GetUInt("QT_VERSION_MINOR", this->BaseConst_.QtVersion.Minor,
@@ -2396,19 +2401,49 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
                       true) ||
       !info.GetStringConfig("PARSE_CACHE_FILE",
                             this->BaseConst_.ParseCacheFile, true) ||
-      !info.GetString("DEP_FILE", this->BaseConst_.DepFile, false) ||
-      !info.GetString("DEP_FILE_RULE_NAME", this->BaseConst_.DepFileRuleName,
-                      false) ||
       !info.GetStringConfig("SETTINGS_FILE", this->SettingsFile_, true) ||
       !info.GetArray("CMAKE_LIST_FILES", this->BaseConst_.ListFiles, true) ||
       !info.GetArray("HEADER_EXTENSIONS", this->BaseConst_.HeaderExtensions,
-                     true) ||
-      !info.GetString("QT_MOC_EXECUTABLE", this->MocConst_.Executable,
-                      false) ||
-      !info.GetString("QT_UIC_EXECUTABLE", this->UicConst_.Executable,
-                      false)) {
+                     true)) {
     return false;
   }
+  if (this->BaseConst().UseBetterGraph) {
+    if (!info.GetStringConfig("DEP_FILE", this->BaseConst_.DepFile, false) ||
+        !info.GetStringConfig("DEP_FILE_RULE_NAME",
+                              this->BaseConst_.DepFileRuleName, false)) {
+      return false;
+    }
+
+    if (this->BaseConst_.CrossConfig) {
+      std::string const mocExecutableWithConfig =
+        "QT_MOC_EXECUTABLE_" + this->ExecutableConfig();
+      std::string const uicExecutableWithConfig =
+        "QT_UIC_EXECUTABLE_" + this->ExecutableConfig();
+      if (!info.GetString(mocExecutableWithConfig, this->MocConst_.Executable,
+                          false) ||
+          !info.GetString(uicExecutableWithConfig, this->UicConst_.Executable,
+                          false)) {
+        return false;
+      }
+    } else {
+      if (!info.GetStringConfig("QT_MOC_EXECUTABLE",
+                                this->MocConst_.Executable, false) ||
+          !info.GetStringConfig("QT_UIC_EXECUTABLE",
+                                this->UicConst_.Executable, false)) {
+        return false;
+      }
+    }
+  } else {
+    if (!info.GetString("QT_MOC_EXECUTABLE", this->MocConst_.Executable,
+                        false) ||
+        !info.GetString("QT_UIC_EXECUTABLE", this->UicConst_.Executable,
+                        false) ||
+        !info.GetString("DEP_FILE", this->BaseConst_.DepFile, false) ||
+        !info.GetString("DEP_FILE_RULE_NAME", this->BaseConst_.DepFileRuleName,
+                        false)) {
+      return false;
+    }
+  }
 
   // -- Checks
   if (!this->BaseConst_.CMakeExecutableTime.Load(
@@ -3075,7 +3110,8 @@ std::string cmQtAutoMocUicT::AbsoluteIncludePath(
 
 } // End of unnamed namespace
 
-bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config)
+bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config,
+                    cm::string_view executableConfig)
 {
-  return cmQtAutoMocUicT().Run(infoFile, config);
+  return cmQtAutoMocUicT().Run(infoFile, config, executableConfig);
 }

+ 2 - 1
Source/cmQtAutoMocUic.h

@@ -10,4 +10,5 @@
  * Process AUTOMOC and AUTOUIC
  * @return true on success
  */
-bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config);
+bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config,
+                    cm::string_view executableConfig);

+ 49 - 9
Source/cmQtAutoRcc.cxx

@@ -35,6 +35,11 @@ public:
 private:
   // -- Utility
   bool IsMultiConfig() const { return this->MultiConfig_; }
+  std::string const& GetGenerator() const { return this->Generator_; }
+  bool IsXcode() const
+  {
+    return this->GetGenerator().find("Xcode") != std::string::npos;
+  }
   std::string MultiConfigOutput() const;
 
   // -- Abstract processing interface
@@ -53,6 +58,9 @@ private:
 
   // -- Config settings
   bool MultiConfig_ = false;
+  bool CrossConfig_ = false;
+  bool UseBetterGraph_ = false;
+  std::string Generator_;
   // -- Directories
   std::string AutogenBuildDir_;
   std::string IncludeDir_;
@@ -92,26 +100,57 @@ bool cmQtAutoRccT::InitFromInfo(InfoT const& info)
 {
   // -- Required settings
   if (!info.GetBool("MULTI_CONFIG", this->MultiConfig_, true) ||
+      !info.GetString("GENERATOR", this->Generator_, true) ||
+      !info.GetBool("CROSS_CONFIG", this->CrossConfig_, true) ||
+      !info.GetBool("USE_BETTER_GRAPH", this->UseBetterGraph_, true) ||
       !info.GetString("BUILD_DIR", this->AutogenBuildDir_, true) ||
       !info.GetStringConfig("INCLUDE_DIR", this->IncludeDir_, true) ||
-      !info.GetString("RCC_EXECUTABLE", this->RccExecutable_, true) ||
-      !info.GetArray("RCC_LIST_OPTIONS", this->RccListOptions_, false) ||
+      !info.GetArrayConfig("RCC_LIST_OPTIONS", this->RccListOptions_, false) ||
       !info.GetString("LOCK_FILE", this->LockFile_, true) ||
       !info.GetStringConfig("SETTINGS_FILE", this->SettingsFile_, true) ||
       !info.GetString("SOURCE", this->QrcFile_, true) ||
       !info.GetString("OUTPUT_CHECKSUM", this->RccPathChecksum_, true) ||
       !info.GetString("OUTPUT_NAME", this->RccFileName_, true) ||
-      !info.GetArray("OPTIONS", this->Options_, false) ||
-      !info.GetArray("INPUTS", this->Inputs_, false)) {
+      !info.GetArray("OPTIONS", this->Options_, false)) {
     return false;
   }
+  if (this->UseBetterGraph_) {
+    if (!info.GetArrayConfig("INPUTS", this->Inputs_, false)) {
+      return false;
+    }
+    if (this->CrossConfig_) {
+      std::string const rccExecutableWithConfig =
+        "RCC_EXECUTABLE_" + this->ExecutableConfig();
+      if (!info.GetString(rccExecutableWithConfig, this->RccExecutable_,
+                          true)) {
+        return false;
+      }
+    } else {
+      if (!info.GetStringConfig("RCC_EXECUTABLE", this->RccExecutable_,
+                                true)) {
+        return false;
+      }
+    }
+  } else {
+    if (!info.GetString("RCC_EXECUTABLE", this->RccExecutable_, true) ||
+        !info.GetArray("RCC_LIST_OPTIONS", this->RccListOptions_, false) ||
+        !info.GetArray("INPUTS", this->Inputs_, false)) {
+      return false;
+    }
+  }
 
   // -- Derive information
   this->QrcFileName_ = cmSystemTools::GetFilenameName(this->QrcFile_);
   this->QrcFileDir_ = cmSystemTools::GetFilenamePath(this->QrcFile_);
-  this->RccFilePublic_ =
-    cmStrCat(this->AutogenBuildDir_, '/', this->RccPathChecksum_, '/',
-             this->RccFileName_);
+  if (IsMultiConfig() && !this->IsXcode() && this->UseBetterGraph_) {
+    this->RccFilePublic_ =
+      cmStrCat(this->AutogenBuildDir_, '/', this->RccPathChecksum_, "_",
+               this->InfoConfig(), '/', this->RccFileName_);
+  } else {
+    this->RccFilePublic_ =
+      cmStrCat(this->AutogenBuildDir_, '/', this->RccPathChecksum_, '/',
+               this->RccFileName_);
+  }
 
   // rcc output file name
   if (this->IsMultiConfig()) {
@@ -520,7 +559,8 @@ bool cmQtAutoRccT::GenerateWrapper()
 
 } // End of unnamed namespace
 
-bool cmQtAutoRcc(cm::string_view infoFile, cm::string_view config)
+bool cmQtAutoRcc(cm::string_view infoFile, cm::string_view config,
+                 cm::string_view executableConfig)
 {
-  return cmQtAutoRccT().Run(infoFile, config);
+  return cmQtAutoRccT().Run(infoFile, config, executableConfig);
 }

+ 2 - 1
Source/cmQtAutoRcc.h

@@ -10,4 +10,5 @@
  * Process AUTORCC
  * @return true on success
  */
-bool cmQtAutoRcc(cm::string_view infoFile, cm::string_view config);
+bool cmQtAutoRcc(cm::string_view infoFile, cm::string_view config,
+                 cm::string_view executableConfig);

+ 1 - 0
Source/cmTarget.cxx

@@ -556,6 +556,7 @@ TargetProperty const StaticTargetProperties[] = {
   { "AUTOGEN_ORIGIN_DEPENDS"_s, IC::CanCompileSources },
   { "AUTOGEN_PARALLEL"_s, IC::CanCompileSources },
   { "AUTOGEN_USE_SYSTEM_INCLUDE"_s, IC::CanCompileSources },
+  { "AUTOGEN_BETTER_GRAPH_MULTI_CONFIG"_s, IC::CanCompileSources },
   // -- moc
   { "AUTOMOC_DEPEND_FILTERS"_s, IC::CanCompileSources },
   // -- C++

+ 6 - 2
Source/cmcmd.cxx

@@ -1443,13 +1443,17 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
     if ((args[1] == "cmake_autogen") && (args.size() >= 4)) {
       cm::string_view const infoFile = args[2];
       cm::string_view const config = args[3];
-      return cmQtAutoMocUic(infoFile, config) ? 0 : 1;
+      cm::string_view const executableConfig =
+        (args.size() >= 5) ? cm::string_view(args[4]) : cm::string_view();
+      return cmQtAutoMocUic(infoFile, config, executableConfig) ? 0 : 1;
     }
     if ((args[1] == "cmake_autorcc") && (args.size() >= 3)) {
       cm::string_view const infoFile = args[2];
       cm::string_view const config =
         (args.size() > 3) ? cm::string_view(args[3]) : cm::string_view();
-      return cmQtAutoRcc(infoFile, config) ? 0 : 1;
+      cm::string_view const executableConfig =
+        (args.size() >= 5) ? cm::string_view(args[4]) : cm::string_view();
+      return cmQtAutoRcc(infoFile, config, executableConfig) ? 0 : 1;
     }
 #endif
 

+ 321 - 47
Tests/RunCMake/Autogen/RunCMakeTest.cmake

@@ -128,65 +128,339 @@ if (DEFINED with_qt_version)
       if(QtCore_VERSION VERSION_GREATER_EQUAL 5.15.0)
         if (RunCMake_GENERATOR MATCHES "Ninja Multi-Config")
           set(config_list Debug Release RelWithDebInfo)
+          set(use_better_graph_list ON OFF)
         else()
           set(config_list single-config)
+          set(use_better_graph_list OFF)
         endif()
-        foreach(config IN ITEMS ${config_list})
-          block()
-            if (config STREQUAL "single-config")
-              set(config_suffix "")
-            else()
-              set(config_suffix "_${config}")
-            endif()
-            set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/QtAutoMocDeps${config_suffix}-build)
-            run_cmake(QtAutoMocDeps)
-            set(RunCMake_TEST_NO_CLEAN 1)
-            # Build the project.
-            if (config STREQUAL "single-config")
-              set(config_param "")
-            else()
-              set(config_param "--config ${config}")
-            endif()
-            run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
-            # Touch just the library source file, which shouldn't cause a rerun of AUTOMOC
-            # for app_with_qt target.
-            file(TOUCH "${RunCMake_SOURCE_DIR}/simple_lib.cpp")
-
-            # Build and assert that AUTOMOC was not run for app_with_qt, sub_exe_1 and sub_exe_2.
-            run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
-            unset(RunCMake_TEST_VARIANT_DESCRIPTION)
-            unset(RunCMake_TEST_NOT_EXPECT_stdout)
 
-            macro(check_file_exists file)
-              if (EXISTS "${file}")
-                set(check_result "PASSED")
-                set(message_type "STATUS")
+        foreach(use_better_graph IN ITEMS ${use_better_graph_list})
+          foreach(config IN ITEMS ${config_list})
+            block()
+              if (config STREQUAL "single-config")
+                set(config_suffix "")
+              else()
+                set(config_path "_${config}")
+                if (use_better_graph)
+                  set(config_suffix "_${config}")
+                endif()
+              endif()
+
+              set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/QtAutoMocDeps${config_path}-build)
+              run_cmake_with_options(QtAutoMocDeps ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=${use_better_graph})
+              set(RunCMake_TEST_NO_CLEAN 1)
+              # Build the project.
+              if (config STREQUAL "single-config")
+                set(config_param "")
               else()
-                set(check_result "FAILED")
-                set(message_type "FATAL_ERROR")
+                set(config_param "--config ${config}")
               endif()
+              run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
+              # Touch just the library source file, which shouldn't cause a rerun of AUTOMOC
+              # for app_with_qt target.
+              file(TOUCH "${RunCMake_SOURCE_DIR}/simple_lib.cpp")
+              set(RunCMake_TEST_NOT_EXPECT_stdout "Automatic MOC for target app_with_qt|\
+Automatic MOC for target sub_exe_1|\
+Automatic MOC for target sub_exe_2")
+              set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't execute AUTOMOC for 'app_with_qt', 'sub_exe_1' and 'sub_exe_2'")
+              # Build and assert that AUTOMOC was not run for app_with_qt, sub_exe_1 and sub_exe_2.
+              run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
+              unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+              unset(RunCMake_TEST_NOT_EXPECT_stdout)
 
-              message(${message_type} "QtAutoMocDeps-build-\"${file}\" was generated - ${check_result}")
-            endmacro()
+              macro(check_file_exists file)
+                if (EXISTS "${file}")
+                  set(check_result "PASSED")
+                  set(message_type "STATUS")
+                else()
+                  set(check_result "FAILED")
+                  set(message_type "FATAL_ERROR")
+                endif()
 
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/deps")
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/deps")
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/deps")
+                message(${message_type} "QtAutoMocDeps-build-\"${file}\" was generated - ${check_result}")
+              endmacro()
 
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/timestamp")
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/timestamp")
-            check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/timestamp")
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/deps${config_suffix}")
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/deps${config_suffix}")
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/deps${config_suffix}")
 
-            # Touch a header file to make sure an automoc dependency cycle is not introduced.
-            file(TOUCH "${RunCMake_SOURCE_DIR}/MyWindow.h")
-            set(RunCMake_TEST_VARIANT_DESCRIPTION "-First build after touch to detect dependency cycle")
-            run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
-            # Need to run a second time to hit the dependency cycle.
-            set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't hit dependency cycle")
-            run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
-          endblock()
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/timestamp${config_suffix}")
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/timestamp${config_suffix}")
+              check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/timestamp${config_suffix}")
+
+              # Touch a header file to make sure an automoc dependency cycle is not introduced.
+              file(TOUCH "${RunCMake_SOURCE_DIR}/MyWindow.h")
+              set(RunCMake_TEST_VARIANT_DESCRIPTION "-First build after touch to detect dependency cycle")
+              run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
+              # Need to run a second time to hit the dependency cycle.
+              set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't hit dependency cycle")
+              run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
+            endblock()
+          endforeach()
         endforeach()
       endif()
     endblock()
   endif()
+
+  function(run_make_program dir)
+    execute_process(
+      COMMAND "${RunCMake_MAKE_PROGRAM}" ${ARGN}
+      WORKING_DIRECTORY "${dir}"
+      OUTPUT_VARIABLE make_program_stdout
+      ERROR_VARIABLE make_program_stderr
+      RESULT_VARIABLE make_program_result
+      )
+      if (NOT DEFINED RunMakeProgram_expected_result)
+        set(RunMakeProgram_expected_result 0)
+      endif()
+      if(NOT "${make_program_result}" MATCHES "${RunMakeProgram_expected_result}")
+        message(STATUS "
+============ beginning of ${RunCMake_MAKE_PROGRAM}'s stdout ============
+${make_program_stdout}
+=============== end of ${RunCMake_MAKE_PROGRAM}'s stdout ===============
+")
+        message(STATUS "
+============ beginning of ${RunCMake_MAKE_PROGRAM}'s stderr ============
+${make_program_stderr}
+=============== end of ${RunCMake_MAKE_PROGRAM}'s stderr ===============
+")
+        message(FATAL_ERROR
+                "top ${RunCMake_MAKE_PROGRAM} build failed exited with status ${make_program_result}")
+    endif()
+    set(make_program_stdout "${make_program_stdout}" PARENT_SCOPE)
+  endfunction(run_make_program)
+
+  function(count_substring STRING SUBSTRING COUNT_VAR)
+      string(LENGTH "${STRING}" STRING_LENGTH)
+      string(LENGTH "${SUBSTRING}" SUBSTRING_LENGTH)
+      if (SUBSTRING_LENGTH EQUAL 0)
+          message(FATAL_ERROR "SUBSTRING_LENGTH is 0")
+      endif()
+
+      if (STRING_LENGTH EQUAL 0)
+          message(FATAL_ERROR "STRING_LENGTH is 0")
+      endif()
+
+      if (STRING_LENGTH LESS SUBSTRING_LENGTH)
+          message(FATAL_ERROR "STRING_LENGTH is less than SUBSTRING_LENGTH")
+      endif()
+
+      set(COUNT 0)
+      string(FIND "${STRING}" "${SUBSTRING}" SUBSTRING_START)
+      while(SUBSTRING_START GREATER_EQUAL 0)
+          math(EXPR COUNT "${COUNT} + 1")
+          math(EXPR SUBSTRING_START "${SUBSTRING_START} + ${SUBSTRING_LENGTH}")
+          string(SUBSTRING "${STRING}" ${SUBSTRING_START} -1 STRING)
+          string(FIND "${STRING}" "${SUBSTRING}" SUBSTRING_START)
+      endwhile()
+
+      set(${COUNT_VAR} ${COUNT} PARENT_SCOPE)
+  endfunction()
+
+  function(expect_only_once make_program_stdout expected_output test_name)
+    count_substring("${make_program_stdout}" "${expected_output}" count)
+    if(NOT count EQUAL 1)
+      message(STATUS "${test_name}-expect_only_once - FAILED")
+      message(FATAL_ERROR "Expected to find ${expected_output} exactly once in ${make_program_stdout} but found ${count} occurrences of ${expected_output}")
+    else()
+      message(STATUS "${test_name}-expect_only_once - PASSED")
+    endif()
+  endfunction()
+
+  function(expect_n_times string_to_check expected_output expected_count test_name)
+    count_substring("${string_to_check}" "${expected_output}" count)
+    if(NOT count EQUAL ${expected_count})
+      message(STATUS "${test_name}-expect_${expected_count}_times - FAILED")
+      message(FATAL_ERROR "Expected to find ${expected_output} exactly ${expected_count} times in ${string_to_check} but found ${count} occurrences of ${expected_output}")
+    else()
+      message(STATUS "${test_name}-expect_${expected_count}_times - PASSED")
+    endif()
+  endfunction()
+
+  function(not_expect make_program_stdout unexpected_output test_name)
+    count_substring("${make_program_stdout}" "${unexpected_output}" count)
+    if(NOT count EQUAL 0)
+      message(STATUS "${test_name}-not_expect - FAILED")
+      message(FATAL_ERROR "Expected to find ${unexpected_output} exactly 0 times in ${make_program_stdout} but found ${count} occurrences of ${unexpected_output}")
+    else()
+      message(STATUS "${test_name}-not_expect - PASSED")
+    endif()
+  endfunction()
+
+  if (QtCore_VERSION VERSION_GREATER_EQUAL 5.15.0)
+    foreach(exe IN ITEMS Moc Uic Rcc)
+      if(RunCMake_GENERATOR MATCHES "Ninja Multi-Config")
+        block()
+          set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
+          set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-multi-config-build)
+          run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
+          unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+          set(RunCMake_TEST_NO_CLEAN 1)
+          foreach(config IN ITEMS Debug Release RelWithDebInfo)
+            block()
+              set(RunCMake_TEST_EXPECT_stdout ".*running_exe_${config}*")
+              set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-expect_running_exe_${config}")
+              run_cmake_command(Auto${exe}ExecutableConfig-multi-config-build ${CMAKE_COMMAND} --build . --config ${config})
+            endblock()
+          endforeach()
+          set(RunCMake_TEST_EXPECT_stdout "ninja: no work to do")
+          foreach(config IN ITEMS Debug Release RelWithDebInfo)
+            block()
+            set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-expect_no_work_to_do")
+              run_cmake_command(Auto${exe}ExecutableConfig-multi-config-build ${CMAKE_COMMAND} --build . --config ${config})
+            endblock()
+          endforeach()
+        endblock()
+        block()
+          set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-build)
+          run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
+          foreach(config IN ITEMS Debug Release RelWithDebInfo)
+            block()
+              run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${config}.ninja)
+
+              set(expected_output "running_exe_${config}")
+              expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig-${config}-${expected_output}")
+
+              foreach(sub_config IN ITEMS Debug Release RelWithDebInfo)
+                if(NOT sub_config STREQUAL config)
+                  set(unexpected_output "running_exe_${sub_config}")
+                  not_expect("${make_program_stdout}" "${unexpected_output}" "Auto${exe}ExecutableConfig-${config}-${unexpected_output}")
+                endif()
+              endforeach()
+
+              if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
+                set(expected_output "cmake_autogen")
+              else()
+                set(expected_output "cmake_autorcc")
+              endif()
+              expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig-${config}-${expected_output}")
+            endblock()
+          endforeach()
+        endblock()
+        block()
+          foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
+            foreach(target_config IN ITEMS Debug Release RelWithDebInfo)
+              block()
+                set(TEST_SUFFIX "-CrossConfig-${ninja_config}-${target_config}")
+                set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig${TEST_SUFFIX}-build)
+                set(RunCMake_TEST_VARIANT_DESCRIPTION ${TEST_SUFFIX})
+                run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_CROSS_CONFIGS=all -DCMAKE_DEFAULT_BUILD_TYPE=${ninja_config} -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
+                unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+
+                run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${ninja_config}.ninja dummy:${target_config})
+
+                set(expected_output "running_exe_${ninja_config}")
+                expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${expected_output}")
+
+                foreach(sub_config IN ITEMS Debug Release RelWithDebInfo)
+                  if(NOT sub_config STREQUAL ninja_config)
+                    set(unexpected_output "running_exe_${sub_config}")
+                    not_expect("${make_program_stdout}" "${unexpected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${unexpected_output}")
+                  endif()
+                endforeach()
+
+                if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
+                  set(expected_output "cmake_autogen")
+                else()
+                  set(expected_output "cmake_autorcc")
+                endif()
+                expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${expected_output}")
+              endblock()
+            endforeach()
+          endforeach()
+        endblock()
+        block()
+          foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
+            set(TEST_SUFFIX "-CrossConfig-${ninja_config}-all-all")
+            set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig${TEST_SUFFIX}-build)
+            set(RunCMake_TEST_VARIANT_DESCRIPTION ${TEST_SUFFIX})
+            run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_CROSS_CONFIGS=all -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
+            unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+            run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${ninja_config}.ninja all:all)
+          endforeach()
+        endblock()
+      elseif (RunCMake_GENERATOR MATCHES "Ninja|Make")
+        block()
+          set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-build)
+          foreach(config IN ITEMS Debug Release RelWithDebInfo)
+            block()
+              set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}")
+              run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_BUILD_TYPE=${config} -DCMAKE_AUTOGEN_VERBOSE=ON)
+              unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+              set(RunCMake_TEST_NO_CLEAN 1)
+              set(RunCMake_TEST_EXPECT_stdout ".*running_exe_${config}*")
+              run_cmake_command(Auto${exe}ExecutableConfig-${config}-build ${CMAKE_COMMAND} --build .)
+            endblock()
+          endforeach()
+        endblock()
+      endif()
+    endforeach()
+  endif()
+
+  # Visual Studio specific dependency tests
+  if (RunCMake_GENERATOR MATCHES "Visual Studio")
+      foreach(exe IN ITEMS Moc Uic Rcc)
+          block()
+            set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${exe}Example-build)
+            set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
+            run_cmake_with_options(${exe}Example ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON)
+            unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+            set(RunCMake_TEST_NO_CLEAN 1)
+            foreach(config IN ITEMS Debug Release RelWithDebInfo)
+              block()
+                set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-first-build")
+                run_cmake_command(${exe}Example-build ${CMAKE_COMMAND} --build . --config ${config})
+              endblock()
+            endforeach()
+            foreach(config IN ITEMS Debug Release RelWithDebInfo)
+              block()
+                if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
+                  set(RunCMake_TEST_NOT_EXPECT_stdout "Auto${exe}")
+                  set(not_expect_descripton "Auto${exe}")
+                else ()
+                  set(RunCMake_TEST_NOT_EXPECT_stdout "Auto${exe}")
+                  set(not_expect_descripton "Auto${exe}")
+                endif()
+                set(RunCMake_TEST_VARIANT_DESCRIPTION "-second-build-${config}_expect_no_${not_expect_descripton}")
+                run_cmake_command(${exe}Example-build ${CMAKE_COMMAND} --build . --config ${config})
+              endblock()
+            endforeach()
+          endblock()
+      endforeach()
+  endif()
+
+  if (RunCMake_GENERATOR MATCHES "Xcode")
+    foreach(exe IN ITEMS Moc Uic Rcc)
+      block()
+        set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${exe}Example-build)
+        set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
+        set(RunCMake_TEST_EXPECT_stderr ".*")
+        run_cmake_with_options(${exe}Example ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON)
+        set(RunCMake_TEST_NO_CLEAN 1)
+        set(RunCMake_MAKE_PROGRAM ${CMAKE_COMMAND})
+        run_make_program(${RunCMake_TEST_BINARY_DIR}  --build . --config Debug)
+        if (exe STREQUAL "Moc")
+          set(expected_count 16)
+        elseif (exe STREQUAL "Uic")
+          set(expected_count 4)
+        else()
+          set(expected_count 12)
+        endif()
+        expect_n_times("${make_program_stdout}" "Auto${exe}:" ${expected_count} "${exe}Example-build-Auto${exe}")
+        expect_n_times("${make_program_stdout}" "Auto${exe}:" ${expected_count} "${exe}Example-build-Auto${exe}")
+
+        if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
+          expect_n_times("${make_program_stdout}" "AutoGen:" 20 "${exe}Example-build-AutoGen:")
+        endif()
+
+        foreach(config IN ITEMS Debug Release RelWithDebInfo)
+          block()
+            run_make_program(${RunCMake_TEST_BINARY_DIR} --build . --config ${config})
+            not_expect("${make_program_stdout}" "Auto${exe}" "${exe}Example-${config}_Auto${exe}")
+            not_expect("${make_program_stdout}" "AutoGen:" "${exe}Example-${config}_AutoGen")
+          endblock()
+        endforeach()
+      endblock()
+    endforeach()
+  endif()
 endif ()

+ 4 - 0
Tests/RunCMake/Autogen/data.qrc

@@ -0,0 +1,4 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/res/affine">
+</qresource>
+</RCC>

+ 5 - 0
Tests/RunCMake/Autogen/example.cpp

@@ -0,0 +1,5 @@
+#include "example.h"
+
+Example::Example()
+{
+}

+ 12 - 0
Tests/RunCMake/Autogen/example.h

@@ -0,0 +1,12 @@
+#ifndef EXAMPLE_H
+#define EXAMPLE_H
+
+#include <QObject>
+
+class Example : public QObject
+{
+  Q_OBJECT
+  Example();
+};
+
+#endif

+ 5 - 0
Tests/RunCMake/Autogen/example_ui.cpp

@@ -0,0 +1,5 @@
+#include "example_ui.h"
+
+Example::Example()
+{
+}

+ 14 - 0
Tests/RunCMake/Autogen/example_ui.h

@@ -0,0 +1,14 @@
+#ifndef EXAMPLE_UI_H
+#define EXAMPLE_UI_H
+
+#include <QObject>
+
+#include "ui_uiA.h"
+
+class Example : public QObject
+{
+  Q_OBJECT
+  Example();
+};
+
+#endif

+ 4 - 0
Tests/RunCMake/Autogen/exe.cpp

@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}

+ 48 - 0
Tests/RunCMake/Autogen/exe_common.h

@@ -0,0 +1,48 @@
+#ifndef EXE_COMMON_H
+#define EXE_COMMON_H
+
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <vector>
+
+inline int runRealExe(const int argc, char** argv)
+{
+  std::vector<std::string> args;
+  std::string realMocPath;
+  std::string const pathArg = "EXE_PATH=";
+  std::string cmd;
+  if (argc > 1) {
+    for (int i = 1; i < argc; ++i) {
+      std::string const arg = argv[i];
+      if (arg.find(pathArg) != std::string::npos) {
+        realMocPath = arg.substr(pathArg.length());
+        // if EXE_PATH contains spaces, wrap it in quotes
+        if (realMocPath.find(" ") != std::string::npos) {
+          realMocPath = "\"" + realMocPath + "\"";
+        }
+      } else {
+        args.push_back(arg);
+      }
+    }
+  }
+#ifdef _WIN32
+  cmd += "cmd /C \"";
+#endif
+  cmd += realMocPath + " ";
+  for (auto arg : args) {
+    // if arg contains spaces, wrap it in quotes
+    if (arg.find(' ') != std::string::npos) {
+      cmd += " \"" + arg + "\"";
+    } else {
+      cmd += " " + arg;
+    }
+  }
+#ifdef _WIN32
+  cmd += "\"";
+#endif
+  std::cout << "Running real exe:" << cmd << std::endl;
+  return std::system(cmd.c_str());
+}
+
+#endif

+ 10 - 0
Tests/RunCMake/Autogen/exe_debug.cpp

@@ -0,0 +1,10 @@
+#include <fstream>
+#include <iostream>
+
+#include "exe_common.h"
+
+int main(int argc, char* argv[])
+{
+  std::cout << "running_exe_Debug\n";
+  return runRealExe(argc, argv);
+}

+ 10 - 0
Tests/RunCMake/Autogen/exe_release.cpp

@@ -0,0 +1,10 @@
+#include <fstream>
+#include <iostream>
+
+#include "exe_common.h"
+
+int main(int argc, char* argv[])
+{
+  std::cout << "running_exe_Release\n";
+  return runRealExe(argc, argv);
+}

+ 10 - 0
Tests/RunCMake/Autogen/exe_relwithdebinfo.cpp

@@ -0,0 +1,10 @@
+#include <fstream>
+#include <iostream>
+
+#include "exe_common.h"
+
+int main(int argc, char* argv[])
+{
+  std::cout << "running_exe_RelWithDebInfo\n";
+  return runRealExe(argc, argv);
+}

+ 24 - 0
Tests/RunCMake/Autogen/uiA.ui

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UiA</class>
+ <widget class="QWidget" name="UiA">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QTreeView" name="treeView"/>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 0 - 7
Tests/RunCMake/NinjaMultiConfig/QtX-debug-in-release-graph-build-check.cmake

@@ -1,7 +0,0 @@
-check_files("${RunCMake_TEST_BINARY_DIR}"
-  INCLUDE
-    ${AUTOGEN_FILES}
-
-    ${TARGET_FILE_exe_Debug}
-    ${TARGET_OBJECT_FILES_exe_Debug}
-  )

+ 26 - 10
Tests/RunCMake/NinjaMultiConfig/QtX.cmake

@@ -19,16 +19,32 @@ if(${QtX}Core_VERSION VERSION_GREATER_EQUAL "5.15.0")
   set(moc_writes_depfiles 1)
 endif()
 
-set(autogen_files)
-if(moc_writes_depfiles)
-  list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/deps")
-  list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/timestamp")
-endif()
-foreach(c IN LISTS CMAKE_CONFIGURATION_TYPES)
-  list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/mocs_compilation_${c}.cpp")
-  list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include_${c}/moc_qt5.cpp")
+foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
+  set(config_suffix "_${CONFIG}")
+  if (CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG)
+    set(autogen_files_config_suffix "${config_suffix}")
+  endif()
+  set(autogen_files)
   if(moc_writes_depfiles)
-    list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include_${c}/moc_qt5.cpp.d")
+    list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/deps${autogen_files_config_suffix}")
+    list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/timestamp${autogen_files_config_suffix}")
   endif()
+  if (CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG)
+    list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/mocs_compilation${config_suffix}.cpp")
+    list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include${config_suffix}/moc_qt5.cpp")
+    if(moc_writes_depfiles)
+      list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include${config_suffix}/moc_qt5.cpp.d")
+    endif()
+  else()
+    foreach(c IN LISTS CMAKE_CONFIGURATION_TYPES)
+      list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/mocs_compilation_${c}.cpp")
+      list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include_${c}/moc_qt5.cpp")
+      if(moc_writes_depfiles)
+        list(APPEND autogen_files "${CMAKE_BINARY_DIR}/exe_autogen/include_${c}/moc_qt5.cpp.d")
+      endif()
+    endforeach()
+  endif()
+
+  file(APPEND "${CMAKE_BINARY_DIR}/target_files.cmake" "set(AUTOGEN_FILES${config_suffix} [==[${autogen_files}]==])\n")
+  unset(autogen_files)
 endforeach()
-file(APPEND "${CMAKE_BINARY_DIR}/target_files.cmake" "set(AUTOGEN_FILES [==[${autogen_files}]==])\n")

+ 59 - 5
Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake

@@ -488,11 +488,65 @@ if(CMake_TEST_Qt_version)
     "-D${QtX}Core_DIR=${${QtX}Core_DIR}"
     "-DCMAKE_PREFIX_PATH:STRING=${CMAKE_PREFIX_PATH}"
   )
-  run_cmake_configure(QtX)
-  unset(RunCMake_TEST_OPTIONS)
-  include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
-  run_cmake_build(QtX debug-in-release-graph Release exe:Debug)
+
+  foreach(use_better_graph IN ITEMS ON OFF)
+    foreach(target_config IN ITEMS Debug Release RelWithDebInfo)
+      foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
+        block()
+          set(target_config_suffix "_${target_config}")
+          if (use_better_graph)
+            set(autogen_files_config_suffix "${target_config_suffix}")
+          else()
+            set(autogen_files_config_suffix "")
+          endif()
+          set(prefix "QtX")
+          set(case "${target_config}-in-${ninja_config}-better-graph-${use_better_graph}")
+          set(test_path "${prefix}-${case}")
+          set(RunCMake_TEST_VARIANT_DESCRIPTION "-${case}-configure")
+          set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test_path}-build)
+          run_cmake_with_options(QtX ${RunCMake_TEST_OPTIONS}
+            "-DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=${use_better_graph}")
+          unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+          include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+          run_cmake_build(${test_path} "" ${ninja_config} exe:${target_config})
+          check_files("${RunCMake_TEST_BINARY_DIR}"
+          INCLUDE
+            "${AUTOGEN_FILES${target_config_suffix}}"
+            "${TARGET_FILE_exe${target_config_suffix}}"
+            "${TARGET_OBJECT_FILES_exe${target_config_suffix}}"
+          )
+          if (DEFINED RunCMake_TEST_FAILED AND NOT RunCMake_TEST_FAILED STREQUAL "")
+            message(FATAL_ERROR "RunCMake_TEST_FAILED:${RunCMake_TEST_FAILED}")
+          else()
+            message(STATUS "${test_path}-check-files - PASSED")
+          endif()
+
+          check_file_contents("${RunCMake_TEST_BINARY_DIR}/exe_autogen/deps${autogen_files_config_suffix}"
+                              "exe_autogen/timestamp${autogen_files_config_suffix}")
+          if (DEFINED RunCMake_TEST_FAILED AND NOT RunCMake_TEST_FAILED STREQUAL "")
+            message(FATAL_ERROR "RunCMake_TEST_FAILED:${RunCMake_TEST_FAILED}")
+          endif()
+        endblock()
+      endforeach()
+    endforeach()
+  endforeach()
+
   if(CMake_TEST_${QtX}Core_Version VERSION_GREATER_EQUAL 5.15.0)
-    run_ninja(QtX automoc-check build-Debug.ninja -t query exe_autogen/timestamp)
+    foreach(use_better_graph IN ITEMS ON OFF)
+      foreach(target_config IN ITEMS Debug Release RelWithDebInfo)
+        foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
+          set(prefix "QtX")
+          set(case "${target_config}-in-${ninja_config}-better-graph-${use_better_graph}")
+          set(test_path "${prefix}-${case}")
+          if (use_better_graph)
+            set(autogen_files_config_suffix "_${target_config}")
+          else()
+            set(autogen_files_config_suffix "")
+          endif()
+          set(RunCMake_TEST_VARIANT_DESCRIPTION "-automoc-check")
+          run_ninja(${test_path} "" build-${ninja_config}.ninja -t query exe_autogen/timestamp${autogen_files_config_suffix})
+        endforeach()
+      endforeach()
+    endforeach()
   endif()
 endif()