Преглед изворни кода

Ninja: Add multi-config variant

Co-Authored-by: vector-of-bool <[email protected]>
Kyle Edwards пре 6 година
родитељ
комит
5a8a9f7229
54 измењених фајлова са 1264 додато и 416 уклоњено
  1. 15 10
      Source/cmCommonTargetGenerator.cxx
  2. 10 4
      Source/cmCommonTargetGenerator.h
  3. 6 2
      Source/cmCustomCommandGenerator.cxx
  4. 2 1
      Source/cmExportTryCompileFileGenerator.cxx
  5. 6 4
      Source/cmGeneratorTarget.cxx
  6. 1 0
      Source/cmGeneratorTarget.h
  7. 3 1
      Source/cmGlobalGenerator.cxx
  8. 6 0
      Source/cmGlobalGenerator.h
  9. 475 126
      Source/cmGlobalNinjaGenerator.cxx
  10. 168 18
      Source/cmGlobalNinjaGenerator.h
  11. 22 8
      Source/cmLinkLineComputer.cxx
  12. 2 0
      Source/cmLinkLineComputer.h
  13. 8 1
      Source/cmLocalGenerator.cxx
  14. 1 0
      Source/cmLocalGenerator.h
  15. 73 18
      Source/cmLocalNinjaGenerator.cxx
  16. 7 2
      Source/cmLocalNinjaGenerator.h
  17. 3 2
      Source/cmMakefile.cxx
  18. 110 46
      Source/cmNinjaNormalTargetGenerator.cxx
  19. 2 1
      Source/cmNinjaNormalTargetGenerator.h
  20. 123 79
      Source/cmNinjaTargetGenerator.cxx
  21. 44 23
      Source/cmNinjaTargetGenerator.h
  22. 30 6
      Source/cmNinjaUtilityTargetGenerator.cxx
  23. 18 5
      Source/cmOutputConverter.cxx
  24. 6 3
      Source/cmOutputConverter.h
  25. 10 3
      Source/cmQtAutoGenInitializer.cxx
  26. 10 0
      Source/cmState.cxx
  27. 3 0
      Source/cmState.h
  28. 8 1
      Source/cmTarget.cxx
  29. 2 1
      Source/cmTarget.h
  30. 1 0
      Source/cmake.cxx
  31. 4 3
      Tests/CMakeLists.txt
  32. 1 1
      Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake
  33. 1 1
      Tests/FortranModules/Executable/CMakeLists.txt
  34. 1 1
      Tests/IncludeDirectories/CMakeLists.txt
  35. 7 1
      Tests/InterfaceLibrary/CMakeLists.txt
  36. 10 4
      Tests/LinkDirectory/External/CMakeLists.txt
  37. 2 2
      Tests/OutDir/CMakeLists.txt
  38. 3 3
      Tests/OutDir/OutDir.cmake
  39. 1 1
      Tests/RunCMake/CMakeLists.txt
  40. 1 1
      Tests/RunCMake/CPack/CPackTestHelpers.cmake
  41. 2 1
      Tests/RunCMake/CPack/tests/EXTERNAL/expected-json-1.0.txt
  42. 1 1
      Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
  43. 1 1
      Tests/RunCMake/FileAPI/check_index.py
  44. 12 12
      Tests/RunCMake/FileAPI/codemodel-v2-check.py
  45. 3 0
      Tests/RunCMake/Ninja/RunCMakeTest.cmake
  46. 12 5
      Tests/RunCMake/RuntimePath/Relative.cmake
  47. 9 3
      Tests/RunCMake/RuntimePath/RunCMakeTest.cmake
  48. 1 1
      Tests/RunCMake/RuntimePath/SymlinkImplicitCheck.cmake
  49. 1 1
      Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake
  50. 2 1
      Tests/RunCMake/ctest_labels_for_subprojects/CTestScriptVariableCommandLine-stderr.txt
  51. 2 2
      Tests/RunCMake/file/RunCMakeTest.cmake
  52. 1 1
      Tests/RunCMake/get_property/RunCMakeTest.cmake
  53. 2 2
      Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath-stderr.txt
  54. 9 2
      Tests/RunCMake/target_link_options/LINKER_expansion.cmake

+ 15 - 10
Source/cmCommonTargetGenerator.cxx

@@ -99,15 +99,15 @@ void cmCommonTargetGenerator::AppendFortranFormatFlags(
 std::string cmCommonTargetGenerator::GetFlags(const std::string& l,
                                               const std::string& config)
 {
-  auto i = this->FlagsByLanguage.find(l);
-  if (i == this->FlagsByLanguage.end()) {
+  auto i = this->Configs[config].FlagsByLanguage.find(l);
+  if (i == this->Configs[config].FlagsByLanguage.end()) {
     std::string flags;
 
     this->LocalCommonGenerator->GetTargetCompileFlags(this->GeneratorTarget,
                                                       config, l, flags);
 
     ByLanguageMap::value_type entry(l, flags);
-    i = this->FlagsByLanguage.insert(entry).first;
+    i = this->Configs[config].FlagsByLanguage.insert(entry).first;
   }
   return i->second;
 }
@@ -115,8 +115,8 @@ std::string cmCommonTargetGenerator::GetFlags(const std::string& l,
 std::string cmCommonTargetGenerator::GetDefines(const std::string& l,
                                                 const std::string& config)
 {
-  auto i = this->DefinesByLanguage.find(l);
-  if (i == this->DefinesByLanguage.end()) {
+  auto i = this->Configs[config].DefinesByLanguage.find(l);
+  if (i == this->Configs[config].DefinesByLanguage.end()) {
     std::set<std::string> defines;
     this->LocalCommonGenerator->GetTargetDefines(this->GeneratorTarget, config,
                                                  l, defines);
@@ -125,7 +125,7 @@ std::string cmCommonTargetGenerator::GetDefines(const std::string& l,
     this->LocalCommonGenerator->JoinDefines(defines, definesString, l);
 
     ByLanguageMap::value_type entry(l, definesString);
-    i = this->DefinesByLanguage.insert(entry).first;
+    i = this->Configs[config].DefinesByLanguage.insert(entry).first;
   }
   return i->second;
 }
@@ -133,12 +133,12 @@ std::string cmCommonTargetGenerator::GetDefines(const std::string& l,
 std::string cmCommonTargetGenerator::GetIncludes(std::string const& l,
                                                  const std::string& config)
 {
-  auto i = this->IncludesByLanguage.find(l);
-  if (i == this->IncludesByLanguage.end()) {
+  auto i = this->Configs[config].IncludesByLanguage.find(l);
+  if (i == this->Configs[config].IncludesByLanguage.end()) {
     std::string includes;
     this->AddIncludeFlags(includes, l, config);
     ByLanguageMap::value_type entry(l, includes);
-    i = this->IncludesByLanguage.insert(entry).first;
+    i = this->Configs[config].IncludesByLanguage.insert(entry).first;
   }
   return i->second;
 }
@@ -182,7 +182,12 @@ std::string cmCommonTargetGenerator::ComputeTargetCompilePDB(
   if (compilePdbPath.empty()) {
     // Match VS default: `$(IntDir)vc$(PlatformToolsetVersion).pdb`.
     // A trailing slash tells the toolchain to add its default file name.
-    compilePdbPath = this->GeneratorTarget->GetSupportDirectory() + "/";
+    compilePdbPath = this->GeneratorTarget->GetSupportDirectory();
+    if (this->GlobalCommonGenerator->IsMultiConfig()) {
+      compilePdbPath += "/";
+      compilePdbPath += config;
+    }
+    compilePdbPath += "/";
     if (this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY) {
       // Match VS default for static libs: `$(IntDir)$(ProjectName).pdb`.
       compilePdbPath += this->GeneratorTarget->GetName();

+ 10 - 4
Source/cmCommonTargetGenerator.h

@@ -51,18 +51,24 @@ protected:
   void AppendOSXVerFlag(std::string& flags, const std::string& lang,
                         const char* name, bool so);
 
-  using ByLanguageMap = std::map<std::string, std::string>;
   std::string GetFlags(const std::string& l, const std::string& config);
-  ByLanguageMap FlagsByLanguage;
   std::string GetDefines(const std::string& l, const std::string& config);
-  ByLanguageMap DefinesByLanguage;
   std::string GetIncludes(std::string const& l, const std::string& config);
-  ByLanguageMap IncludesByLanguage;
   std::string GetManifests(const std::string& config);
 
   std::vector<std::string> GetLinkedTargetDirectories(
     const std::string& config) const;
   std::string ComputeTargetCompilePDB(const std::string& config) const;
+
+private:
+  using ByLanguageMap = std::map<std::string, std::string>;
+  struct ByConfig
+  {
+    ByLanguageMap FlagsByLanguage;
+    ByLanguageMap DefinesByLanguage;
+    ByLanguageMap IncludesByLanguage;
+  };
+  std::map<std::string, ByConfig> Configs;
 };
 
 #endif

+ 6 - 2
Source/cmCustomCommandGenerator.cxx

@@ -201,7 +201,9 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c,
       if (this->OldStyle) {
         cmd += escapeForShellOldStyle(emulator[j]);
       } else {
-        cmd += this->LG->EscapeForShell(emulator[j], this->MakeVars);
+        cmd +=
+          this->LG->EscapeForShell(emulator[j], this->MakeVars, false, false,
+                                   this->MakeVars && this->LG->IsNinjaMulti());
       }
     }
 
@@ -222,7 +224,9 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c,
     if (this->OldStyle) {
       cmd += escapeForShellOldStyle(arg);
     } else {
-      cmd += this->LG->EscapeForShell(arg, this->MakeVars);
+      cmd +=
+        this->LG->EscapeForShell(arg, this->MakeVars, false, false,
+                                 this->MakeVars && this->LG->IsNinjaMulti());
     }
   }
 }

+ 2 - 1
Source/cmExportTryCompileFileGenerator.cxx

@@ -70,7 +70,8 @@ std::string cmExportTryCompileFileGenerator::FindTargets(
   std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(prop);
 
   cmTarget dummyHead("try_compile_dummy_exe", cmStateEnums::EXECUTABLE,
-                     cmTarget::VisibilityNormal, tgt->Target->GetMakefile());
+                     cmTarget::VisibilityNormal, tgt->Target->GetMakefile(),
+                     true);
 
   cmGeneratorTarget gDummyHead(&dummyHead, tgt->GetLocalGenerator());
 

+ 6 - 4
Source/cmGeneratorTarget.cxx

@@ -649,6 +649,7 @@ void cmGeneratorTarget::ClearSourcesCache()
   this->KindedSourcesMap.clear();
   this->LinkImplementationLanguageIsContextDependent = true;
   this->Objects.clear();
+  this->VisitedConfigsForObjects.clear();
 }
 
 void cmGeneratorTarget::AddSourceCommon(const std::string& src, bool before)
@@ -738,7 +739,7 @@ void cmGeneratorTarget::GetObjectSources(
 {
   IMPLEMENT_VISIT(SourceKindObjectSource);
 
-  if (!this->Objects.empty()) {
+  if (this->VisitedConfigsForObjects.count(config)) {
     return;
   }
 
@@ -747,16 +748,17 @@ void cmGeneratorTarget::GetObjectSources(
   }
 
   this->LocalGenerator->ComputeObjectFilenames(this->Objects, this);
+  this->VisitedConfigsForObjects.insert(config);
 }
 
 void cmGeneratorTarget::ComputeObjectMapping()
 {
-  if (!this->Objects.empty()) {
+  auto const& configs = this->Makefile->GetGeneratorConfigs();
+  std::set<std::string> configSet(configs.begin(), configs.end());
+  if (configSet == this->VisitedConfigsForObjects) {
     return;
   }
 
-  std::vector<std::string> const& configs =
-    this->Makefile->GetGeneratorConfigs();
   for (std::string const& c : configs) {
     std::vector<cmSourceFile const*> sourceFiles;
     this->GetObjectSources(sourceFiles, c);

+ 1 - 0
Source/cmGeneratorTarget.h

@@ -767,6 +767,7 @@ private:
   };
   using SourceEntriesType = std::map<cmSourceFile const*, SourceEntry>;
   SourceEntriesType SourceDepends;
+  mutable std::set<std::string> VisitedConfigsForObjects;
   mutable std::map<cmSourceFile const*, std::string> Objects;
   std::set<cmSourceFile const*> ExplicitObjectName;
   mutable std::map<std::string, std::vector<std::string>> SystemIncludesCache;

+ 3 - 1
Source/cmGlobalGenerator.cxx

@@ -2476,6 +2476,7 @@ void cmGlobalGenerator::AddGlobalTarget_EditCache(
   }
   GlobalTargetInfo gti;
   gti.Name = editCacheTargetName;
+  gti.PerConfig = false;
   cmCustomCommandLine singleLine;
 
   // Use generator preference for the edit_cache rule if it is defined.
@@ -2510,6 +2511,7 @@ void cmGlobalGenerator::AddGlobalTarget_RebuildCache(
   gti.Name = rebuildCacheTargetName;
   gti.Message = "Running CMake to regenerate build system...";
   gti.UsesTerminal = true;
+  gti.PerConfig = false;
   cmCustomCommandLine singleLine;
   singleLine.push_back(cmSystemTools::GetCMakeCommand());
   singleLine.push_back("-S$(CMAKE_SOURCE_DIR)");
@@ -2654,7 +2656,7 @@ cmTarget cmGlobalGenerator::CreateGlobalTarget(GlobalTargetInfo const& gti,
 {
   // Package
   cmTarget target(gti.Name, cmStateEnums::GLOBAL_TARGET,
-                  cmTarget::VisibilityNormal, mf);
+                  cmTarget::VisibilityNormal, mf, gti.PerConfig);
   target.SetProperty("EXCLUDE_FROM_ALL", "TRUE");
 
   std::vector<std::string> no_outputs;

+ 6 - 0
Source/cmGlobalGenerator.h

@@ -479,6 +479,11 @@ public:
 
   int RecursionDepth;
 
+  virtual void GetQtAutoGenConfigs(std::vector<std::string>& configs) const
+  {
+    configs.emplace_back("$<CONFIG>");
+  }
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
@@ -527,6 +532,7 @@ protected:
     std::vector<std::string> Depends;
     std::string WorkingDir;
     bool UsesTerminal = false;
+    bool PerConfig = true;
   };
 
   void CreateDefaultGlobalTargets(std::vector<GlobalTargetInfo>& targets);

+ 475 - 126
Source/cmGlobalNinjaGenerator.cxx

@@ -115,6 +115,11 @@ std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
   std::string result = lit;
   cmSystemTools::ReplaceString(result, "$", "$$");
   cmSystemTools::ReplaceString(result, "\n", "$\n");
+  if (this->IsMultiConfig()) {
+    cmSystemTools::ReplaceString(result,
+                                 cmStrCat('$', this->GetCMakeCFGIntDir()),
+                                 this->GetCMakeCFGIntDir());
+  }
   return result;
 }
 
@@ -249,8 +254,8 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
   const std::string& command, const std::string& description,
   const std::string& comment, const std::string& depfile,
   const std::string& job_pool, bool uses_terminal, bool restat,
-  const cmNinjaDeps& outputs, const cmNinjaDeps& explicitDeps,
-  const cmNinjaDeps& orderOnlyDeps)
+  const cmNinjaDeps& outputs, const std::string& config,
+  const cmNinjaDeps& explicitDeps, const cmNinjaDeps& orderOnlyDeps)
 {
   this->AddCustomCommandRule();
 
@@ -283,7 +288,11 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
     if (!depfile.empty()) {
       vars["depfile"] = depfile;
     }
-    this->WriteBuild(*this->BuildFileStream, build);
+    if (config.empty()) {
+      this->WriteBuild(*this->GetCommonFileStream(), build);
+    } else {
+      this->WriteBuild(*this->GetConfigFileStream(config), build);
+    }
   }
 
   if (this->ComputingUnknownDependencies) {
@@ -305,14 +314,15 @@ void cmGlobalNinjaGenerator::AddMacOSXContentRule()
 }
 
 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
-                                                     std::string output)
+                                                     std::string output,
+                                                     const std::string& config)
 {
   this->AddMacOSXContentRule();
   {
     cmNinjaBuild build("COPY_OSX_CONTENT");
     build.Outputs.push_back(std::move(output));
     build.ExplicitDeps.push_back(std::move(input));
-    this->WriteBuild(*this->BuildFileStream, build);
+    this->WriteBuild(*this->GetConfigFileStream(config), build);
   }
 }
 
@@ -473,14 +483,16 @@ void cmGlobalNinjaGenerator::Generate()
                                            msg.str());
     return;
   }
-  if (!this->OpenBuildFileStream()) {
+  if (!this->OpenBuildFileStreams()) {
     return;
   }
   if (!this->OpenRulesFileStream()) {
     return;
   }
 
-  this->TargetDependsClosures.clear();
+  for (auto& it : this->Configs) {
+    it.second.TargetDependsClosures.clear();
+  }
 
   this->InitOutputPathPrefix();
   this->TargetAll = this->NinjaOutputPath("all");
@@ -496,19 +508,26 @@ void cmGlobalNinjaGenerator::Generate()
   this->cmGlobalGenerator::Generate();
 
   this->WriteAssumedSourceDependencies();
-  this->WriteTargetAliases(*this->BuildFileStream);
-  this->WriteFolderTargets(*this->BuildFileStream);
-  this->WriteUnknownExplicitDependencies(*this->BuildFileStream);
-  this->WriteBuiltinTargets(*this->BuildFileStream);
+  this->WriteTargetAliases(*this->GetCommonFileStream());
+  this->WriteFolderTargets(*this->GetCommonFileStream());
+  this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
+  this->WriteBuiltinTargets(*this->GetCommonFileStream());
 
   if (cmSystemTools::GetErrorOccuredFlag()) {
     this->RulesFileStream->setstate(std::ios::failbit);
-    this->BuildFileStream->setstate(std::ios::failbit);
+    for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
+      this->GetConfigFileStream(config)->setstate(std::ios::failbit);
+    }
+    this->GetCommonFileStream()->setstate(std::ios::failbit);
   }
 
   this->CloseCompileCommandsStream();
   this->CloseRulesFileStream();
-  this->CloseBuildFileStream();
+  this->CloseBuildFileStreams();
+
+  if (!this->WriteDefaultBuildFile()) {
+    return;
+  }
 }
 
 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
@@ -612,6 +631,17 @@ bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
 void cmGlobalNinjaGenerator::EnableLanguage(
   std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
 {
+  if (this->IsMultiConfig()) {
+    if (!mf->GetDefinition("CMAKE_CONFIGURATION_TYPES")) {
+      mf->AddCacheDefinition(
+        "CMAKE_CONFIGURATION_TYPES", "Debug;Release;MinSizeRel;RelWithDebInfo",
+        "Semicolon separated list of supported configuration types, only "
+        "supports Debug, Release, MinSizeRel, and RelWithDebInfo, anything "
+        "else will be ignored",
+        cmStateEnums::STRING);
+    }
+  }
+
   this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
   for (std::string const& l : langs) {
     if (l == "NONE") {
@@ -653,7 +683,7 @@ std::vector<cmGlobalGenerator::GeneratedMakeCommand>
 cmGlobalNinjaGenerator::GenerateBuildCommand(
   const std::string& makeProgram, const std::string& /*projectName*/,
   const std::string& /*projectDir*/,
-  std::vector<std::string> const& targetNames, const std::string& /*config*/,
+  std::vector<std::string> const& targetNames, const std::string& config,
   bool /*fast*/, int jobs, bool verbose,
   std::vector<std::string> const& makeOptions)
 {
@@ -669,6 +699,9 @@ cmGlobalNinjaGenerator::GenerateBuildCommand(
     makeCommand.Add("-j", std::to_string(jobs));
   }
 
+  this->AppendNinjaFileArgument(makeCommand,
+                                config.empty() ? "Debug" : config);
+
   makeCommand.Add(makeOptions.begin(), makeOptions.end());
   for (const auto& tname : targetNames) {
     if (!tname.empty()) {
@@ -710,44 +743,53 @@ void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
   cmGeneratorTarget* gt) const
 {
   // Compute full path to object file directory for this target.
-  std::string dir =
-    cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(), '/',
-             gt->LocalGenerator->GetTargetDirectory(gt), '/');
+  std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
+                             '/', gt->LocalGenerator->GetTargetDirectory(gt),
+                             '/', this->GetCMakeCFGIntDir(), '/');
   gt->ObjectDirectory = dir;
 }
 
 // Private methods
 
-bool cmGlobalNinjaGenerator::OpenBuildFileStream()
+bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
 {
-  // Compute Ninja's build file path.
-  std::string buildFilePath =
-    cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/',
-             cmGlobalNinjaGenerator::NINJA_BUILD_FILE);
+  if (!this->OpenFileStream(this->BuildFileStream,
+                            cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
+    return false;
+  }
+
+  // Write a comment about this file.
+  *this->BuildFileStream
+    << "# This file contains all the build statements describing the\n"
+    << "# compilation DAG.\n\n";
+
+  return true;
+}
 
+bool cmGlobalNinjaGenerator::OpenFileStream(
+  std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
+{
   // Get a stream where to generate things.
-  if (!this->BuildFileStream) {
-    this->BuildFileStream = cm::make_unique<cmGeneratedFileStream>(
-      buildFilePath, false, this->GetMakefileEncoding());
-    if (!(*this->BuildFileStream)) {
+  if (!stream) {
+    // Compute Ninja's build file path.
+    std::string path =
+      cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
+    stream = cm::make_unique<cmGeneratedFileStream>(
+      path, false, this->GetMakefileEncoding());
+    if (!(*stream)) {
       // An error message is generated by the constructor if it cannot
       // open the file.
       return false;
     }
-  }
-
-  // Write the do not edit header.
-  this->WriteDisclaimer(*this->BuildFileStream);
 
-  // Write a comment about this file.
-  *this->BuildFileStream
-    << "# This file contains all the build statements describing the\n"
-    << "# compilation DAG.\n\n";
+    // Write the do not edit header.
+    this->WriteDisclaimer(*stream);
+  }
 
   return true;
 }
 
-void cmGlobalNinjaGenerator::CloseBuildFileStream()
+void cmGlobalNinjaGenerator::CloseBuildFileStreams()
 {
   if (this->BuildFileStream) {
     this->BuildFileStream.reset();
@@ -758,25 +800,11 @@ void cmGlobalNinjaGenerator::CloseBuildFileStream()
 
 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
 {
-  // Compute Ninja's build file path.
-  std::string rulesFilePath =
-    cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/',
-             cmGlobalNinjaGenerator::NINJA_RULES_FILE);
-
-  // Get a stream where to generate things.
-  if (!this->RulesFileStream) {
-    this->RulesFileStream = cm::make_unique<cmGeneratedFileStream>(
-      rulesFilePath, false, this->GetMakefileEncoding());
-    if (!(*this->RulesFileStream)) {
-      // An error message is generated by the constructor if it cannot
-      // open the file.
-      return false;
-    }
+  if (!this->OpenFileStream(this->RulesFileStream,
+                            cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
+    return false;
   }
 
-  // Write the do not edit header.
-  this->WriteDisclaimer(*this->RulesFileStream);
-
   // Write comment about this file.
   /* clang-format off */
   *this->RulesFileStream
@@ -834,9 +862,10 @@ std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
     .first->second;
 }
 
-void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName)
+void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
+                                                    const std::string& config)
 {
-  this->AdditionalCleanFiles.emplace(std::move(fileName));
+  this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
 }
 
 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
@@ -904,14 +933,16 @@ void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
                             "Assume dependencies for generated source file.",
                             /*depfile*/ "", /*job_pool*/ "",
                             /*uses_terminal*/ false,
-                            /*restat*/ true, cmNinjaDeps(1, asd.first),
+                            /*restat*/ true, cmNinjaDeps(1, asd.first), "",
                             cmNinjaDeps(), orderOnlyDeps);
   }
 }
 
-std::string OrderDependsTargetForTarget(cmGeneratorTarget const* target)
+std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
+  cmGeneratorTarget const* target, const std::string& config)
 {
-  return "cmake_object_order_depends_target_" + target->GetName();
+  return "cmake_object_order_depends_target_" + target->GetName() + "_" +
+    config;
 }
 
 void cmGlobalNinjaGenerator::AppendTargetOutputs(
@@ -929,7 +960,7 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
     case cmStateEnums::STATIC_LIBRARY:
     case cmStateEnums::MODULE_LIBRARY: {
       if (depends == DependOnTargetOrdering) {
-        outputs.push_back(OrderDependsTargetForTarget(target));
+        outputs.push_back(OrderDependsTargetForTarget(target, config));
         break;
       }
     }
@@ -941,7 +972,7 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
     }
     case cmStateEnums::OBJECT_LIBRARY: {
       if (depends == DependOnTargetOrdering) {
-        outputs.push_back(OrderDependsTargetForTarget(target));
+        outputs.push_back(OrderDependsTargetForTarget(target, config));
         break;
       }
     }
@@ -951,7 +982,11 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
       std::string path =
         target->GetLocalGenerator()->GetCurrentBinaryDirectory() +
         std::string("/") + target->GetName();
-      outputs.push_back(this->ConvertToNinjaPath(path));
+      std::string output = this->ConvertToNinjaPath(path);
+      if (target->Target->IsPerConfig()) {
+        output = this->BuildAlias(output, config);
+      }
+      outputs.push_back(output);
       break;
     }
 
@@ -962,7 +997,8 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
 
 void cmGlobalNinjaGenerator::AppendTargetDepends(
   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
-  const std::string& config, cmNinjaTargetDepends depends)
+  const std::string& config, const std::string& fileConfig,
+  cmNinjaTargetDepends depends)
 {
   if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
     // These depend only on other CMake-provided targets, e.g. "all".
@@ -970,7 +1006,7 @@ void cmGlobalNinjaGenerator::AppendTargetDepends(
       std::string d =
         target->GetLocalGenerator()->GetCurrentBinaryDirectory() + "/" +
         util.Value;
-      outputs.push_back(this->ConvertToNinjaPath(d));
+      outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
     }
   } else {
     cmNinjaDeps outs;
@@ -979,7 +1015,14 @@ void cmGlobalNinjaGenerator::AppendTargetDepends(
       if (targetDep->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
         continue;
       }
-      this->AppendTargetOutputs(targetDep, outs, config, depends);
+      // For some reason, object libraries show up as "utility" dependencies
+      // even though they're used for linking. Treat them as link dependencies.
+      if (targetDep.IsUtil() &&
+          targetDep->GetType() != cmStateEnums::OBJECT_LIBRARY) {
+        this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
+      } else {
+        this->AppendTargetOutputs(targetDep, outs, config, depends);
+      }
     }
     std::sort(outs.begin(), outs.end());
     cmAppend(outputs, outs);
@@ -1001,9 +1044,10 @@ void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
 {
 
   // try to locate the target in the cache
-  auto find = this->TargetDependsClosures.lower_bound(target);
+  auto find = this->Configs[config].TargetDependsClosures.lower_bound(target);
 
-  if (find == this->TargetDependsClosures.end() || find->first != target) {
+  if (find == this->Configs[config].TargetDependsClosures.end() ||
+      find->first != target) {
     // 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
@@ -1020,8 +1064,8 @@ void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
       // Collect the dependent targets for _this_ target
       this->AppendTargetDependsClosure(dep_target, this_outs, config, false);
     }
-    find = this->TargetDependsClosures.emplace_hint(find, target,
-                                                    std::move(this_outs));
+    find = this->Configs[config].TargetDependsClosures.emplace_hint(
+      find, target, std::move(this_outs));
   }
 
   // now fill the outputs of the final result from the newly generated cache
@@ -1040,21 +1084,39 @@ void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
                                             cmGeneratorTarget* target,
                                             const std::string& config)
 {
-  std::string buildAlias = this->NinjaOutputPath(alias);
+  std::string outputPath = this->NinjaOutputPath(alias);
+  std::string buildAlias = this->BuildAlias(outputPath, config);
   cmNinjaDeps outputs;
   this->AppendTargetOutputs(target, outputs, config);
-  // Mark the target's outputs as ambiguous to ensure that no other target uses
-  // the output as an alias.
+  // Mark the target's outputs as ambiguous to ensure that no other target
+  // uses the output as an alias.
   for (std::string const& output : outputs) {
-    TargetAliases[output] = nullptr;
+    this->TargetAliases[output].GeneratorTarget = nullptr;
+    for (const std::string& config2 :
+         this->Makefiles.front()->GetGeneratorConfigs()) {
+      this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
+    }
   }
 
   // Insert the alias into the map.  If the alias was already present in the
   // map and referred to another target, mark it as ambiguous.
-  std::pair<TargetAliasMap::iterator, bool> newAlias =
-    TargetAliases.insert(std::make_pair(buildAlias, target));
-  if (newAlias.second && newAlias.first->second != target) {
-    newAlias.first->second = nullptr;
+  TargetAlias ta;
+  ta.GeneratorTarget = target;
+  ta.Config = config;
+  std::pair<TargetAliasMap::iterator, bool> newAliasGlobal =
+    this->TargetAliases.insert(std::make_pair(buildAlias, ta));
+  if (newAliasGlobal.second &&
+      newAliasGlobal.first->second.GeneratorTarget != target) {
+    newAliasGlobal.first->second.GeneratorTarget = nullptr;
+  }
+  if (config != "all") {
+    std::pair<TargetAliasMap::iterator, bool> newAliasConfig =
+      this->Configs[config].TargetAliases.insert(
+        std::make_pair(outputPath, ta));
+    if (newAliasConfig.second &&
+        newAliasConfig.first->second.GeneratorTarget != target) {
+      newAliasConfig.first->second.GeneratorTarget = nullptr;
+    }
   }
 }
 
@@ -1064,10 +1126,10 @@ void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
   os << "# Target aliases.\n\n";
 
   cmNinjaBuild build("phony");
-  build.Outputs.emplace_back("");
-  for (auto const& ta : TargetAliases) {
+  build.Outputs.emplace_back();
+  for (auto const& ta : this->TargetAliases) {
     // Don't write ambiguous aliases.
-    if (!ta.second) {
+    if (!ta.second.GeneratorTarget) {
       continue;
     }
 
@@ -1077,20 +1139,41 @@ void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
       continue;
     }
 
-    // Outputs
-    build.Outputs[0] = ta.first;
-
-    std::vector<std::string> configs;
-    ta.second->Makefile->GetConfigurations(configs);
-    if (configs.empty()) {
-      configs.emplace_back();
+    build.Outputs.front() = ta.first;
+    build.ExplicitDeps.clear();
+    if (ta.second.Config == "all") {
+      for (auto const& config :
+           ta.second.GeneratorTarget->Makefile->GetGeneratorConfigs()) {
+        this->AppendTargetOutputs(ta.second.GeneratorTarget,
+                                  build.ExplicitDeps, config);
+      }
+    } else {
+      this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
+                                ta.second.Config);
     }
-    for (auto const& config : configs) {
-      // Explicit dependencies
-      build.ExplicitDeps.clear();
-      this->AppendTargetOutputs(ta.second, build.ExplicitDeps, config);
-      // Write
-      this->WriteBuild(os, build);
+    this->WriteBuild(os, build);
+  }
+
+  if (this->IsMultiConfig()) {
+    for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs()) {
+      for (auto const& ta : this->Configs[config].TargetAliases) {
+        // Don't write ambiguous aliases.
+        if (!ta.second.GeneratorTarget) {
+          continue;
+        }
+
+        // Don't write alias if there is a already a custom command with
+        // matching output
+        if (this->HasCustomCommandOutput(ta.first)) {
+          continue;
+        }
+
+        build.Outputs.front() = ta.first;
+        build.ExplicitDeps.clear();
+        this->AppendTargetOutputs(ta.second.GeneratorTarget,
+                                  build.ExplicitDeps, config);
+        this->WriteBuild(*this->GetConfigFileStream(config), build);
+      }
     }
   }
 }
@@ -1108,31 +1191,58 @@ void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
     cmGlobalNinjaGenerator::WriteDivider(os);
     std::string const& currentBinaryDir = it.first;
     DirectoryTarget const& dt = it.second;
+    std::vector<std::string> configs;
+    dt.LG->GetMakefile()->GetConfigurations(configs, true);
+    if (configs.empty()) {
+      configs.emplace_back();
+    }
 
     // Setup target
+    cmNinjaDeps configDeps;
     build.Comment = "Folder: " + currentBinaryDir;
-    build.Outputs.emplace_back(
-      this->ConvertToNinjaPath(currentBinaryDir + "/all"));
-    for (DirectoryTarget::Target const& t : dt.Targets) {
-      if (!t.ExcludeFromAll) {
-        std::vector<std::string> configs;
-        dt.LG->GetMakefile()->GetConfigurations(configs, true);
-        if (configs.empty()) {
-          configs.emplace_back();
-        }
-        for (auto const& config : configs) {
+    build.Outputs.emplace_back();
+    for (auto const& config : configs) {
+      build.ExplicitDeps.clear();
+      build.Outputs.front() = this->BuildAlias(
+        this->ConvertToNinjaPath(currentBinaryDir + "/all"), config);
+      configDeps.emplace_back(build.Outputs.front());
+      for (DirectoryTarget::Target const& t : dt.Targets) {
+        if (!t.ExcludeFromAll) {
           this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config);
         }
       }
+      for (DirectoryTarget::Dir const& d : dt.Children) {
+        if (!d.ExcludeFromAll) {
+          build.ExplicitDeps.emplace_back(this->BuildAlias(
+            this->ConvertToNinjaPath(d.Path + "/all"), config));
+        }
+      }
+      // Write target
+      this->WriteBuild(os, build);
     }
-    for (DirectoryTarget::Dir const& d : dt.Children) {
-      if (!d.ExcludeFromAll) {
-        build.ExplicitDeps.emplace_back(
-          this->ConvertToNinjaPath(d.Path + "/all"));
+
+    // Add shortcut target
+    if (this->IsMultiConfig()) {
+      for (auto const& config : configs) {
+        build.ExplicitDeps = { this->BuildAlias(
+          this->ConvertToNinjaPath(currentBinaryDir + "/all"), config) };
+        build.Outputs.front() =
+          this->ConvertToNinjaPath(currentBinaryDir + "/all");
+        this->WriteBuild(*this->GetConfigFileStream(config), build);
       }
     }
-    // Write target
-    this->WriteBuild(os, build);
+
+    // Add target for all configs
+    if (this->IsMultiConfig()) {
+      build.ExplicitDeps.clear();
+      for (auto const& config : configs) {
+        build.ExplicitDeps.push_back(this->BuildAlias(
+          this->ConvertToNinjaPath(currentBinaryDir + "/all"), config));
+      }
+      build.Outputs.front() = this->BuildAlias(
+        this->ConvertToNinjaPath(currentBinaryDir + "/all"), "all");
+      this->WriteBuild(*this->GetCommonFileStream(), build);
+    }
   }
 }
 
@@ -1265,10 +1375,13 @@ void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
   cmGlobalNinjaGenerator::WriteDivider(os);
   os << "# Built-in targets\n\n";
 
-  this->WriteTargetDefault(os);
   this->WriteTargetRebuildManifest(os);
   this->WriteTargetClean(os);
   this->WriteTargetHelp(os);
+
+  for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
+    this->WriteTargetDefault(*this->GetConfigFileStream(config));
+  }
 }
 
 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
@@ -1305,7 +1418,7 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
 
   cmNinjaBuild reBuild("RERUN_CMAKE");
   reBuild.Comment = "Re-run CMake if any of its inputs changed.";
-  reBuild.Outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
+  this->AddRebuildManifestOutputs(reBuild.Outputs);
 
   for (const auto& localGen : this->LocalGenerators) {
     for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
@@ -1435,9 +1548,23 @@ bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
   std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
   std::string cleanScriptAbs =
     cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
+  std::vector<std::string> configs;
+  this->Makefiles[0]->GetConfigurations(configs, true);
+  if (configs.empty()) {
+    configs.emplace_back();
+  }
 
   // Check if there are additional files to clean
-  if (this->AdditionalCleanFiles.empty()) {
+  bool empty = true;
+  for (auto const& config : configs) {
+    auto const it = this->Configs.find(config);
+    if (it != this->Configs.end() &&
+        !it->second.AdditionalCleanFiles.empty()) {
+      empty = false;
+      break;
+    }
+  }
+  if (empty) {
     // Remove cmake clean script file if it exists
     cmSystemTools::RemoveFile(cleanScriptAbs);
     return false;
@@ -1449,14 +1576,23 @@ bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
     if (!fout) {
       return false;
     }
-    fout << "# Additional clean files\n\n";
-    fout << "file(REMOVE_RECURSE\n";
-    for (std::string const& acf : this->AdditionalCleanFiles) {
-      fout << "  "
-           << cmOutputConverter::EscapeForCMake(ConvertToNinjaPath(acf))
-           << '\n';
+    fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
+    for (auto const& config : configs) {
+      auto const it = this->Configs.find(config);
+      if (it != this->Configs.end() &&
+          !it->second.AdditionalCleanFiles.empty()) {
+        fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
+             << config << "\")\n";
+        fout << "  file(REMOVE_RECURSE\n";
+        for (std::string const& acf : it->second.AdditionalCleanFiles) {
+          fout << "    "
+               << cmOutputConverter::EscapeForCMake(ConvertToNinjaPath(acf))
+               << '\n';
+        }
+        fout << "  )\n";
+        fout << "endif()\n";
+      }
     }
-    fout << ")\n";
   }
   // Register clean script file
   lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
@@ -1465,7 +1601,7 @@ bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
   {
     cmNinjaRule rule("CLEAN_ADDITIONAL");
     rule.Command = cmStrCat(
-      CMakeCmd(), " -P ",
+      CMakeCmd(), " -DCONFIG=$CONFIG -P ",
       lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
                                  cmOutputConverter::SHELL));
     rule.Description = "Cleaning additional files...";
@@ -1477,9 +1613,19 @@ bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
   {
     cmNinjaBuild build("CLEAN_ADDITIONAL");
     build.Comment = "Clean additional files.";
-    build.Outputs.push_back(
-      this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
-    WriteBuild(os, build);
+    build.Outputs.emplace_back();
+    for (auto const& config : configs) {
+      build.Outputs.front() = this->BuildAlias(
+        this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
+      build.Variables["CONFIG"] = config;
+      WriteBuild(os, build);
+    }
+    if (this->IsMultiConfig()) {
+      build.Outputs.front() =
+        this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
+      build.Variables["CONFIG"] = "";
+      WriteBuild(os, build);
+    }
   }
   // Return success
   return true;
@@ -1494,22 +1640,91 @@ void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
   // Write rule
   {
     cmNinjaRule rule("CLEAN");
-    rule.Command = NinjaCmd() + " -t clean";
+    rule.Command = NinjaCmd() + " $FILE_ARG -t clean $TARGETS";
     rule.Description = "Cleaning all built files...";
     rule.Comment = "Rule for cleaning all built files.";
     WriteRule(*this->RulesFileStream, rule);
   }
 
+  auto const configs = this->Makefiles.front()->GetGeneratorConfigs();
+
   // Write build
   {
     cmNinjaBuild build("CLEAN");
     build.Comment = "Clean all the built files.";
-    build.Outputs.push_back(this->NinjaOutputPath(this->GetCleanTargetName()));
-    if (additionalFiles) {
-      build.ExplicitDeps.push_back(
-        this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
+    build.Outputs.emplace_back();
+
+    for (auto const& config : configs) {
+      build.Outputs.front() = this->BuildAlias(
+        this->NinjaOutputPath(this->GetCleanTargetName()), config);
+      if (this->IsMultiConfig()) {
+        build.Variables["TARGETS"] =
+          cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
+                   " ", GetByproductsForCleanTargetName());
+      }
+      build.ExplicitDeps.clear();
+      if (additionalFiles) {
+        build.ExplicitDeps.push_back(this->BuildAlias(
+          this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
+          config));
+      }
+      for (auto const& fileConfig : configs) {
+        if (this->IsMultiConfig()) {
+          build.Variables["FILE_ARG"] = cmStrCat(
+            "-f ", cmGlobalNinjaMultiGenerator::GetNinjaFilename(fileConfig));
+        }
+        this->WriteBuild(*this->GetConfigFileStream(fileConfig), build);
+      }
+    }
+
+    if (this->IsMultiConfig()) {
+      build.Outputs.front() = this->BuildAlias(
+        this->NinjaOutputPath(this->GetCleanTargetName()), "all");
+      build.ExplicitDeps.clear();
+
+      if (additionalFiles) {
+        build.ExplicitDeps.push_back(
+          this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
+      }
+
+      build.Variables["TARGETS"] = "";
+
+      for (auto const& fileConfig : configs) {
+        build.Variables["FILE_ARG"] = cmStrCat(
+          "-f ", cmGlobalNinjaMultiGenerator::GetNinjaFilename(fileConfig));
+        this->WriteBuild(*this->GetConfigFileStream(fileConfig), build);
+      }
+    }
+  }
+
+  if (this->IsMultiConfig()) {
+    cmNinjaBuild build("phony");
+    build.Outputs.emplace_back(
+      this->NinjaOutputPath(this->GetCleanTargetName()));
+    build.ExplicitDeps.emplace_back();
+
+    for (auto const& config : configs) {
+      build.ExplicitDeps.front() = this->BuildAlias(
+        this->NinjaOutputPath(this->GetCleanTargetName()), config);
+      this->WriteBuild(*this->GetConfigFileStream(config), build);
     }
+  }
+
+  // Write byproducts
+  if (this->IsMultiConfig()) {
+    cmNinjaBuild build("phony");
+    build.Comment = "Clean byproducts.";
+    build.Outputs.emplace_back(
+      this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
+    build.ExplicitDeps = this->ByproductsForCleanTarget;
     WriteBuild(os, build);
+
+    for (auto const& config : configs) {
+      build.Outputs.front() = this->BuildAlias(
+        this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
+      build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
+      WriteBuild(os, build);
+    }
   }
 }
 
@@ -2016,3 +2231,137 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
   }
   return 0;
 }
+
+void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
+  const std::string& prefix, const std::string& config,
+  const std::string& suffix, std::string& dir)
+{
+  if (!config.empty() && this->IsMultiConfig()) {
+    dir += prefix;
+    dir += config;
+    dir += suffix;
+  }
+}
+
+const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE = "common.ninja";
+const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
+
+cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
+  : cmGlobalNinjaGenerator(cm)
+{
+  cm->GetState()->SetIsGeneratorMultiConfig(true);
+  cm->GetState()->SetNinjaMulti(true);
+}
+
+void cmGlobalNinjaMultiGenerator::GetDocumentation(cmDocumentationEntry& entry)
+{
+  entry.Name = cmGlobalNinjaMultiGenerator::GetActualName();
+  entry.Brief = "Generates build-<Config>.ninja files.";
+}
+
+std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
+  const std::string& str, const std::string& config) const
+{
+  std::string result = str;
+  cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
+  return result;
+}
+
+bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
+{
+  if (!this->OpenFileStream(this->CommonFileStream,
+                            cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
+    return false;
+  }
+
+  // Write a comment about this file.
+  *this->CommonFileStream
+    << "# This file contains build statements common to all "
+       "configurations.\n\n";
+
+  for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
+    if (!this->OpenFileStream(this->ConfigFileStreams[config],
+                              GetNinjaFilename(config))) {
+      return false;
+    }
+
+    // Write a comment about this file.
+    *this->ConfigFileStreams[config]
+      << "# This file contains build statements specific to the \"" << config
+      << "\"\n# configuration.\n\n";
+  }
+
+  return true;
+}
+
+void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
+{
+  if (this->CommonFileStream) {
+    this->CommonFileStream.reset();
+  } else {
+    cmSystemTools::Error("Common file stream was not open.");
+  }
+
+  for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
+    if (this->ConfigFileStreams[config]) {
+      this->ConfigFileStreams[config].reset();
+    } else {
+      cmSystemTools::Error(
+        cmStrCat("Config file stream for \"", config, "\" was not open."));
+    }
+  }
+}
+
+void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
+  GeneratedMakeCommand& command, const std::string& config) const
+{
+  command.Add("-f");
+  command.Add(GetNinjaFilename(config));
+}
+
+std::string cmGlobalNinjaMultiGenerator::GetNinjaFilename(
+  const std::string& config)
+{
+  return cmStrCat("build-", config,
+                  cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
+}
+
+void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
+  cmNinjaDeps& outputs) const
+{
+  for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs()) {
+    outputs.push_back(this->NinjaOutputPath(GetNinjaFilename(config)));
+  }
+  if (this->Makefiles.front()->GetDefinition(
+        "CMAKE_NINJA_MULTI_DEFAULT_BUILD_TYPE")) {
+    outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
+  }
+}
+
+void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
+  std::vector<std::string>& configs) const
+{
+  auto const oldSize = configs.size();
+  this->Makefiles.front()->GetConfigurations(configs);
+  if (configs.size() == oldSize) {
+    configs.emplace_back();
+  }
+}
+
+bool cmGlobalNinjaMultiGenerator::WriteDefaultBuildFile()
+{
+  auto const* defaultConfig = this->Makefiles.front()->GetDefinition(
+    "CMAKE_NINJA_MULTI_DEFAULT_BUILD_TYPE");
+  if (defaultConfig) {
+    std::unique_ptr<cmGeneratedFileStream> defaultStream;
+    if (!this->OpenFileStream(defaultStream, NINJA_BUILD_FILE)) {
+      return false;
+    }
+    *defaultStream << "# This file is a convenience file generated by\n"
+                   << "# CMAKE_NINJA_MULTI_DEFAULT_BUILD_TYPE.\n\n"
+                   << "include " << this->GetNinjaFilename(defaultConfig)
+                   << "\n";
+  }
+
+  return true;
+}

+ 168 - 18
Source/cmGlobalNinjaGenerator.h

@@ -22,6 +22,7 @@
 #include "cmGlobalGeneratorFactory.h"
 #include "cmNinjaTypes.h"
 #include "cmPolicies.h"
+#include "cmStringAlgorithms.h"
 
 class cmCustomCommand;
 class cmGeneratorTarget;
@@ -73,7 +74,7 @@ public:
   static void WriteDivider(std::ostream& os);
 
   static std::string EncodeRuleName(std::string const& name);
-  static std::string EncodeLiteral(const std::string& lit);
+  std::string EncodeLiteral(const std::string& lit);
   std::string EncodePath(const std::string& path);
 
   cmLinkLineComputer* CreateLinkLineComputer(
@@ -111,11 +112,12 @@ public:
     const std::string& command, const std::string& description,
     const std::string& comment, const std::string& depfile,
     const std::string& pool, bool uses_terminal, bool restat,
-    const cmNinjaDeps& outputs,
+    const cmNinjaDeps& outputs, const std::string& config,
     const cmNinjaDeps& explicitDeps = cmNinjaDeps(),
     const cmNinjaDeps& orderOnlyDeps = cmNinjaDeps());
 
-  void WriteMacOSXContentBuild(std::string input, std::string output);
+  void WriteMacOSXContentBuild(std::string input, std::string output,
+                               const std::string& config);
 
   /**
    * Write a rule statement to @a os.
@@ -205,7 +207,13 @@ public:
   }
   const char* GetCleanTargetName() const override { return "clean"; }
 
-  cmGeneratedFileStream* GetBuildFileStream() const
+  virtual cmGeneratedFileStream* GetConfigFileStream(
+    const std::string& /*config*/) const
+  {
+    return this->BuildFileStream.get();
+  }
+
+  virtual cmGeneratedFileStream* GetCommonFileStream() const
   {
     return this->BuildFileStream.get();
   }
@@ -232,12 +240,17 @@ public:
   MapToNinjaPathImpl MapToNinjaPath() { return { this }; }
 
   // -- Additional clean files
-  void AddAdditionalCleanFile(std::string fileName);
+  void AddAdditionalCleanFile(std::string fileName, const std::string& config);
   const char* GetAdditionalCleanTargetName() const
   {
     return "CMakeFiles/clean.additional";
   }
 
+  static const char* GetByproductsForCleanTargetName()
+  {
+    return "CMakeFiles/cmake_byproducts_for_clean_target";
+  }
+
   void AddCXXCompileCommand(const std::string& commandLine,
                             const std::string& sourceFile);
 
@@ -261,9 +274,9 @@ public:
 
   /// Called when we have seen the given custom command.  Returns true
   /// if we has seen it before.
-  bool SeenCustomCommand(cmCustomCommand const* cc)
+  bool SeenCustomCommand(cmCustomCommand const* cc, const std::string& config)
   {
-    return !this->CustomCommands.insert(cc).second;
+    return !this->Configs[config].CustomCommands.insert(cc).second;
   }
 
   /// Called when we have seen the given custom command output.
@@ -285,13 +298,16 @@ public:
     ASD.insert(deps.begin(), deps.end());
   }
 
+  static std::string OrderDependsTargetForTarget(
+    cmGeneratorTarget const* target, const std::string& config);
+
   void AppendTargetOutputs(
     cmGeneratorTarget const* target, cmNinjaDeps& outputs,
     const std::string& config,
     cmNinjaTargetDepends depends = DependOnTargetArtifact);
   void AppendTargetDepends(
     cmGeneratorTarget const* target, cmNinjaDeps& outputs,
-    const std::string& config,
+    const std::string& config, const std::string& fileConfig,
     cmNinjaTargetDepends depends = DependOnTargetArtifact);
   void AppendTargetDependsClosure(cmGeneratorTarget const* target,
                                   cmNinjaDeps& outputs,
@@ -300,6 +316,21 @@ public:
                                   cmNinjaOuts& outputs,
                                   const std::string& config, bool omit_self);
 
+  void AppendDirectoryForConfig(const std::string& prefix,
+                                const std::string& config,
+                                const std::string& suffix,
+                                std::string& dir) override;
+
+  virtual void AppendNinjaFileArgument(GeneratedMakeCommand& /*command*/,
+                                       const std::string& /*config*/) const
+  {
+  }
+
+  virtual void AddRebuildManifestOutputs(cmNinjaDeps& outputs) const
+  {
+    outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
+  }
+
   int GetRuleCmdLength(const std::string& name) { return RuleCmdLength[name]; }
 
   void AddTargetAlias(const std::string& alias, cmGeneratorTarget* target,
@@ -336,11 +367,39 @@ public:
                        std::vector<std::string> const& linked_target_dirs,
                        std::string const& arg_lang);
 
+  virtual std::string BuildAlias(const std::string& alias,
+                                 const std::string& /*config*/) const
+  {
+    return alias;
+  }
+
+  virtual std::string ConfigDirectory(const std::string& /*config*/) const
+  {
+    return "";
+  }
+
+  cmNinjaDeps& GetByproductsForCleanTarget()
+  {
+    return this->ByproductsForCleanTarget;
+  }
+
+  cmNinjaDeps& GetByproductsForCleanTarget(const std::string& config)
+  {
+    return this->Configs[config].ByproductsForCleanTarget;
+  }
+
 protected:
   void Generate() override;
 
   bool CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const override { return true; }
 
+  virtual bool OpenBuildFileStreams();
+  virtual void CloseBuildFileStreams();
+  virtual bool WriteDefaultBuildFile() { return true; }
+
+  bool OpenFileStream(std::unique_ptr<cmGeneratedFileStream>& stream,
+                      const std::string& name);
+
 private:
   std::string GetEditCacheCommand() const override;
   bool FindMakeProgram(cmMakefile* mf) override;
@@ -349,9 +408,6 @@ private:
                       cmMakefile* mf) const override;
   bool CheckFortran(cmMakefile* mf) const;
 
-  bool OpenBuildFileStream();
-  void CloseBuildFileStream();
-
   void CloseCompileCommandsStream();
 
   bool OpenRulesFileStream();
@@ -396,9 +452,6 @@ private:
 
   bool UsingGCCOnWindows = false;
 
-  /// The set of custom commands we have seen.
-  std::set<cmCustomCommand const*> CustomCommands;
-
   /// The set of custom command outputs we have seen.
   std::set<std::string> CustomCommandOutputs;
 
@@ -417,11 +470,14 @@ private:
   /// The mapping from source file to assumed dependencies.
   std::map<std::string, std::set<std::string>> AssumedSourceDependencies;
 
-  using TargetAliasMap = std::map<std::string, cmGeneratorTarget*>;
+  struct TargetAlias
+  {
+    cmGeneratorTarget* GeneratorTarget;
+    std::string Config;
+  };
+  using TargetAliasMap = std::map<std::string, TargetAlias>;
   TargetAliasMap TargetAliases;
 
-  std::map<cmGeneratorTarget const*, cmNinjaOuts> TargetDependsClosures;
-
   /// the local cache for calls to ConvertToNinjaPath
   mutable std::unordered_map<std::string, std::string> ConvertToNinjaPathCache;
 
@@ -439,7 +495,101 @@ private:
   std::string OutputPathPrefix;
   std::string TargetAll;
   std::string CMakeCacheFile;
-  std::set<std::string> AdditionalCleanFiles;
+
+  struct ByConfig
+  {
+    std::set<std::string> AdditionalCleanFiles;
+
+    /// The set of custom commands we have seen.
+    std::set<cmCustomCommand const*> CustomCommands;
+
+    std::map<cmGeneratorTarget const*, cmNinjaOuts> TargetDependsClosures;
+
+    TargetAliasMap TargetAliases;
+
+    cmNinjaDeps ByproductsForCleanTarget;
+  };
+  std::map<std::string, ByConfig> Configs;
+
+  cmNinjaDeps ByproductsForCleanTarget;
+};
+
+class cmGlobalNinjaMultiGenerator : public cmGlobalNinjaGenerator
+{
+public:
+  /// The default name of Ninja's common file. Typically: common.ninja.
+  static const char* NINJA_COMMON_FILE;
+  /// The default file extension to use for per-config Ninja files.
+  static const char* NINJA_FILE_EXTENSION;
+
+  cmGlobalNinjaMultiGenerator(cmake* cm);
+  bool IsMultiConfig() const override { return true; }
+  static cmGlobalGeneratorFactory* NewFactory()
+  {
+    return new cmGlobalGeneratorSimpleFactory<cmGlobalNinjaMultiGenerator>();
+  }
+
+  static void GetDocumentation(cmDocumentationEntry& entry);
+
+  std::string GetName() const override
+  {
+    return cmGlobalNinjaMultiGenerator::GetActualName();
+  }
+
+  static std::string GetActualName() { return "Ninja Multi-Config"; }
+
+  std::string BuildAlias(const std::string& alias,
+                         const std::string& config) const override
+  {
+    if (config.empty()) {
+      return alias;
+    }
+    return cmStrCat(alias, ":", config);
+  }
+
+  std::string ConfigDirectory(const std::string& config) const override
+  {
+    if (!config.empty()) {
+      return cmStrCat('/', config);
+    }
+    return "";
+  }
+
+  const char* GetCMakeCFGIntDir() const override { return "${CONFIGURATION}"; }
+
+  std::string ExpandCFGIntDir(const std::string& str,
+                              const std::string& config) const override;
+
+  cmGeneratedFileStream* GetConfigFileStream(
+    const std::string& config) const override
+  {
+    return this->ConfigFileStreams.at(config).get();
+  }
+
+  cmGeneratedFileStream* GetCommonFileStream() const override
+  {
+    return this->CommonFileStream.get();
+  }
+
+  void AppendNinjaFileArgument(GeneratedMakeCommand& command,
+                               const std::string& config) const override;
+
+  static std::string GetNinjaFilename(const std::string& config);
+
+  void AddRebuildManifestOutputs(cmNinjaDeps& outputs) const override;
+
+  void GetQtAutoGenConfigs(std::vector<std::string>& configs) const override;
+
+  bool WriteDefaultBuildFile() override;
+
+protected:
+  bool OpenBuildFileStreams() override;
+  void CloseBuildFileStreams() override;
+
+private:
+  std::map<std::string, std::unique_ptr<cmGeneratedFileStream>>
+    ConfigFileStreams;
+  std::unique_ptr<cmGeneratedFileStream> CommonFileStream;
 };
 
 #endif // ! cmGlobalNinjaGenerator_h

+ 22 - 8
Source/cmLinkLineComputer.cxx

@@ -23,6 +23,7 @@ cmLinkLineComputer::cmLinkLineComputer(cmOutputConverter* outputConverter,
   , OutputConverter(outputConverter)
   , ForResponse(false)
   , UseWatcomQuote(false)
+  , UseNinjaMulti(false)
   , Relink(false)
 {
 }
@@ -34,6 +35,11 @@ void cmLinkLineComputer::SetUseWatcomQuote(bool useWatcomQuote)
   this->UseWatcomQuote = useWatcomQuote;
 }
 
+void cmLinkLineComputer::SetUseNinjaMulti(bool useNinjaMulti)
+{
+  this->UseNinjaMulti = useNinjaMulti;
+}
+
 void cmLinkLineComputer::SetForResponse(bool forResponse)
 {
   this->ForResponse = forResponse;
@@ -106,10 +112,14 @@ void cmLinkLineComputer::ComputeLinkLibs(
 
 std::string cmLinkLineComputer::ConvertToOutputFormat(std::string const& input)
 {
-  cmOutputConverter::OutputFormat shellFormat = (this->ForResponse)
-    ? cmOutputConverter::RESPONSE
-    : ((this->UseWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
-                              : cmOutputConverter::SHELL);
+  cmOutputConverter::OutputFormat shellFormat = cmOutputConverter::SHELL;
+  if (this->ForResponse) {
+    shellFormat = cmOutputConverter::RESPONSE;
+  } else if (this->UseWatcomQuote) {
+    shellFormat = cmOutputConverter::WATCOMQUOTE;
+  } else if (this->UseNinjaMulti) {
+    shellFormat = cmOutputConverter::NINJAMULTI;
+  }
 
   return this->OutputConverter->ConvertToOutputFormat(input, shellFormat);
 }
@@ -117,10 +127,14 @@ std::string cmLinkLineComputer::ConvertToOutputFormat(std::string const& input)
 std::string cmLinkLineComputer::ConvertToOutputForExisting(
   std::string const& input)
 {
-  cmOutputConverter::OutputFormat shellFormat = (this->ForResponse)
-    ? cmOutputConverter::RESPONSE
-    : ((this->UseWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
-                              : cmOutputConverter::SHELL);
+  cmOutputConverter::OutputFormat shellFormat = cmOutputConverter::SHELL;
+  if (this->ForResponse) {
+    shellFormat = cmOutputConverter::RESPONSE;
+  } else if (this->UseWatcomQuote) {
+    shellFormat = cmOutputConverter::WATCOMQUOTE;
+  } else if (this->UseNinjaMulti) {
+    shellFormat = cmOutputConverter::NINJAMULTI;
+  }
 
   return this->OutputConverter->ConvertToOutputForExisting(input, shellFormat);
 }

+ 2 - 0
Source/cmLinkLineComputer.h

@@ -28,6 +28,7 @@ public:
   cmLinkLineComputer& operator=(cmLinkLineComputer const&) = delete;
 
   void SetUseWatcomQuote(bool useWatcomQuote);
+  void SetUseNinjaMulti(bool useNinjaMulti);
   void SetForResponse(bool forResponse);
   void SetRelink(bool relink);
 
@@ -69,6 +70,7 @@ protected:
 
   bool ForResponse;
   bool UseWatcomQuote;
+  bool UseNinjaMulti;
   bool Relink;
 };
 

+ 8 - 1
Source/cmLocalGenerator.cxx

@@ -2392,7 +2392,9 @@ void cmLocalGenerator::AppendFlags(
 void cmLocalGenerator::AppendFlagEscape(std::string& flags,
                                         const std::string& rawFlag) const
 {
-  this->AppendFlags(flags, this->EscapeForShell(rawFlag));
+  this->AppendFlags(
+    flags,
+    this->EscapeForShell(rawFlag, false, false, false, this->IsNinjaMulti()));
 }
 
 void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
@@ -3207,6 +3209,11 @@ bool cmLocalGenerator::IsNMake() const
   return this->GetState()->UseNMake();
 }
 
+bool cmLocalGenerator::IsNinjaMulti() const
+{
+  return this->GetState()->UseNinjaMulti();
+}
+
 std::string cmLocalGenerator::GetObjectFileNameWithoutTarget(
   const cmSourceFile& source, std::string const& dir_max,
   bool* hasSourceExtension, char const* customOutputExtension)

+ 1 - 0
Source/cmLocalGenerator.h

@@ -466,6 +466,7 @@ public:
   bool IsWatcomWMake() const;
   bool IsMinGWMake() const;
   bool IsNMake() const;
+  bool IsNinjaMulti() const;
 
   void IssueMessage(MessageType t, std::string const& text) const;
 

+ 73 - 18
Source/cmLocalNinjaGenerator.cxx

@@ -29,6 +29,7 @@
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTarget.h"
 #include "cmake.h"
 
 cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
@@ -61,7 +62,12 @@ void cmLocalNinjaGenerator::Generate()
     this->HomeRelativeOutputPath.clear();
   }
 
-  this->WriteProcessedMakefile(this->GetBuildFileStream());
+  if (this->GetGlobalGenerator()->IsMultiConfig()) {
+    for (auto const& config : this->GetConfigNames()) {
+      this->WriteProcessedMakefile(this->GetConfigFileStream(config));
+    }
+  }
+  this->WriteProcessedMakefile(this->GetCommonFileStream());
 #ifdef NINJA_GEN_VERBOSE_FILES
   this->WriteProcessedMakefile(this->GetRulesFileStream());
 #endif
@@ -88,8 +94,12 @@ void cmLocalNinjaGenerator::Generate()
     }
     auto tg = cmNinjaTargetGenerator::New(target.get());
     if (tg) {
-      for (auto const& config : this->GetConfigNames()) {
-        tg->Generate(config);
+      if (target->Target->IsPerConfig()) {
+        for (auto const& config : this->GetConfigNames()) {
+          tg->Generate(config);
+        }
+      } else {
+        tg->Generate("");
       }
     }
   }
@@ -144,9 +154,15 @@ std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
 
 // Private methods.
 
-cmGeneratedFileStream& cmLocalNinjaGenerator::GetBuildFileStream() const
+cmGeneratedFileStream& cmLocalNinjaGenerator::GetConfigFileStream(
+  const std::string& config) const
+{
+  return *this->GetGlobalNinjaGenerator()->GetConfigFileStream(config);
+}
+
+cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const
 {
-  return *this->GetGlobalNinjaGenerator()->GetBuildFileStream();
+  return *this->GetGlobalNinjaGenerator()->GetCommonFileStream();
 }
 
 cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
@@ -166,10 +182,22 @@ cmake* cmLocalNinjaGenerator::GetCMakeInstance()
 
 void cmLocalNinjaGenerator::WriteBuildFileTop()
 {
-  // For the build file.
-  this->WriteProjectHeader(this->GetBuildFileStream());
-  this->WriteNinjaRequiredVersion(this->GetBuildFileStream());
-  this->WriteNinjaFilesInclusion(this->GetBuildFileStream());
+  this->WriteProjectHeader(this->GetCommonFileStream());
+
+  if (this->GetGlobalGenerator()->IsMultiConfig()) {
+    for (auto const& config : this->GetConfigNames()) {
+      auto& stream = this->GetConfigFileStream(config);
+      this->WriteProjectHeader(stream);
+      this->WriteNinjaRequiredVersion(stream);
+      this->WriteNinjaConfigurationVariable(stream, config);
+      this->WriteNinjaFilesInclusionConfig(stream);
+    }
+  } else {
+    this->WriteNinjaRequiredVersion(this->GetCommonFileStream());
+    this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(),
+                                          this->GetConfigNames().front());
+  }
+  this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
 
   // For the rule file.
   this->WriteProjectHeader(this->GetRulesFileStream());
@@ -211,6 +239,14 @@ void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
      << std::endl;
 }
 
+void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable(
+  std::ostream& os, const std::string& config)
+{
+  cmGlobalNinjaGenerator::WriteVariable(
+    os, "CONFIGURATION", config,
+    "Set configuration variable for custom commands.");
+}
+
 void cmLocalNinjaGenerator::WritePools(std::ostream& os)
 {
   cmGlobalNinjaGenerator::WriteDivider(os);
@@ -240,7 +276,21 @@ void cmLocalNinjaGenerator::WritePools(std::ostream& os)
   }
 }
 
-void cmLocalNinjaGenerator::WriteNinjaFilesInclusion(std::ostream& os)
+void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os)
+{
+  cmGlobalNinjaGenerator::WriteDivider(os);
+  os << "# Include auxiliary files.\n"
+     << "\n";
+  cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
+  std::string const ninjaCommonFile =
+    ng->NinjaOutputPath(cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE);
+  std::string const commonFilePath = ng->EncodePath(ninjaCommonFile);
+  cmGlobalNinjaGenerator::WriteInclude(os, commonFilePath,
+                                       "Include common file.");
+  os << "\n";
+}
+
+void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
 {
   cmGlobalNinjaGenerator::WriteDivider(os);
   os << "# Include auxiliary files.\n"
@@ -278,10 +328,11 @@ void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
 void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
                                                 cmNinjaDeps& outputs,
                                                 const std::string& config,
+                                                const std::string& fileConfig,
                                                 cmNinjaTargetDepends depends)
 {
   this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config,
-                                                       depends);
+                                                       fileConfig, depends);
 }
 
 void cmLocalNinjaGenerator::AppendCustomCommandDeps(
@@ -425,6 +476,8 @@ std::string cmLocalNinjaGenerator::BuildCommandLine(
 void cmLocalNinjaGenerator::AppendCustomCommandLines(
   cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
 {
+  auto* gg = this->GetGlobalNinjaGenerator();
+
   if (ccg.GetNumberOfCommands() > 0) {
     std::string wd = ccg.GetWorkingDirectory();
     if (wd.empty()) {
@@ -446,8 +499,10 @@ void cmLocalNinjaGenerator::AppendCustomCommandLines(
 
   for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
     cmdLines.push_back(launcher +
-                       this->ConvertToOutputFormat(ccg.GetCommand(i),
-                                                   cmOutputConverter::SHELL));
+                       this->ConvertToOutputFormat(
+                         ccg.GetCommand(i),
+                         gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
+                                             : cmOutputConverter::SHELL));
 
     std::string& cmd = cmdLines.back();
     ccg.AppendArguments(i, cmd);
@@ -459,7 +514,7 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
   const std::string& config)
 {
   cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
-  if (gg->SeenCustomCommand(cc)) {
+  if (gg->SeenCustomCommand(cc, config)) {
     return;
   }
 
@@ -505,7 +560,7 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
     build.Outputs = std::move(ninjaOutputs);
     build.ExplicitDeps = std::move(ninjaDeps);
     build.OrderOnlyDeps = orderOnlyDeps;
-    gg->WriteBuild(this->GetBuildFileStream(), build);
+    gg->WriteBuild(this->GetConfigFileStream(config), build);
   } else {
     std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]);
     // Hash full path to make unique.
@@ -517,8 +572,8 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
       this->BuildCommandLine(cmdLines, customStep),
       this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
       cc->GetDepfile(), cc->GetJobPool(), cc->GetUsesTerminal(),
-      /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, ninjaDeps,
-      orderOnlyDeps);
+      /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, config,
+      ninjaDeps, orderOnlyDeps);
   }
 }
 
@@ -625,7 +680,7 @@ void cmLocalNinjaGenerator::AdditionalCleanFiles(const std::string& config)
     for (std::string const& cleanFile : cleanFiles) {
       // Support relative paths
       gg->AddAdditionalCleanFile(
-        cmSystemTools::CollapseFullPath(cleanFile, binaryDir));
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
     }
   }
 }

+ 7 - 2
Source/cmLocalNinjaGenerator.h

@@ -68,6 +68,7 @@ public:
                            const std::string& config);
   void AppendTargetDepends(
     cmGeneratorTarget* target, cmNinjaDeps& outputs, const std::string& config,
+    const std::string& fileConfig,
     cmNinjaTargetDepends depends = DependOnTargetArtifact);
 
   void AddCustomCommandTarget(cmCustomCommand const* cc,
@@ -85,13 +86,17 @@ protected:
     bool forceFullPaths = false) override;
 
 private:
-  cmGeneratedFileStream& GetBuildFileStream() const;
+  cmGeneratedFileStream& GetConfigFileStream(const std::string& config) const;
+  cmGeneratedFileStream& GetCommonFileStream() const;
   cmGeneratedFileStream& GetRulesFileStream() const;
 
   void WriteBuildFileTop();
   void WriteProjectHeader(std::ostream& os);
   void WriteNinjaRequiredVersion(std::ostream& os);
-  void WriteNinjaFilesInclusion(std::ostream& os);
+  void WriteNinjaConfigurationVariable(std::ostream& os,
+                                       const std::string& config);
+  void WriteNinjaFilesInclusionConfig(std::ostream& os);
+  void WriteNinjaFilesInclusionCommon(std::ostream& os);
   void WriteProcessedMakefile(std::ostream& os);
   void WritePools(std::ostream& os);
 

+ 3 - 2
Source/cmMakefile.cxx

@@ -1992,7 +1992,8 @@ cmTarget* cmMakefile::AddNewTarget(cmStateEnums::TargetType type,
 {
   auto it =
     this->Targets
-      .emplace(name, cmTarget(name, type, cmTarget::VisibilityNormal, this))
+      .emplace(name,
+               cmTarget(name, type, cmTarget::VisibilityNormal, this, true))
       .first;
   this->OrderedTargets.push_back(&it->second);
   this->GetGlobalGenerator()->IndexTarget(&it->second);
@@ -4162,7 +4163,7 @@ cmTarget* cmMakefile::AddImportedTarget(const std::string& name,
     new cmTarget(name, type,
                  global ? cmTarget::VisibilityImportedGlobally
                         : cmTarget::VisibilityImported,
-                 this));
+                 this, true));
 
   // Add to the set of available imported targets.
   this->ImportedTargets[name] = target.get();

+ 110 - 46
Source/cmNinjaNormalTargetGenerator.cxx

@@ -70,7 +70,11 @@ void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
   this->WriteLanguagesRules(config);
 
   // Write the build statements
-  this->WriteObjectBuildStatements(config);
+  bool firstForConfig = true;
+  for (auto const& fileConfig : this->GetConfigNames()) {
+    this->WriteObjectBuildStatements(config, fileConfig, firstForConfig);
+    firstForConfig = false;
+  }
 
   if (this->GetGeneratorTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) {
     this->WriteObjectLibStatement(config);
@@ -78,8 +82,14 @@ void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
     // If this target has cuda language link inputs, and we need to do
     // device linking
     this->WriteDeviceLinkStatement(config);
-    this->WriteLinkStatement(config);
+    firstForConfig = true;
+    for (auto const& fileConfig : this->GetConfigNames()) {
+      this->WriteLinkStatement(config, fileConfig, firstForConfig);
+      firstForConfig = false;
+    }
   }
+  this->GetGlobalGenerator()->AddTargetAlias(
+    this->GetTargetName(), this->GetGeneratorTarget(), "all");
 
   // Find ADDITIONAL_CLEAN_FILES
   this->AdditionalCleanFiles(config);
@@ -107,7 +117,7 @@ void cmNinjaNormalTargetGenerator::WriteLanguagesRules(
     }
   }
   for (std::string const& language : languages) {
-    this->WriteLanguageRules(language);
+    this->WriteLanguageRules(language, config);
   }
 }
 
@@ -138,7 +148,8 @@ std::string cmNinjaNormalTargetGenerator::LanguageLinkerRule(
     cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()) +
     "_LINKER__" +
     cmGlobalNinjaGenerator::EncodeRuleName(
-           this->GetGeneratorTarget()->GetName());
+           this->GetGeneratorTarget()->GetName()) +
+    "_" + config;
 }
 
 std::string cmNinjaNormalTargetGenerator::LanguageLinkerDeviceRule(
@@ -148,7 +159,8 @@ std::string cmNinjaNormalTargetGenerator::LanguageLinkerDeviceRule(
     cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()) +
     "_DEVICE_LINKER__" +
     cmGlobalNinjaGenerator::EncodeRuleName(
-           this->GetGeneratorTarget()->GetName());
+           this->GetGeneratorTarget()->GetName()) +
+    "_" + config;
 }
 
 struct cmNinjaRemoveNoOpCommands
@@ -571,11 +583,11 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
   this->DeviceLinkObject = targetOutputReal;
 
   // Write comments.
-  cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
+  cmGlobalNinjaGenerator::WriteDivider(this->GetCommonFileStream());
   const cmStateEnums::TargetType targetType = genTarget->GetType();
-  this->GetBuildFileStream() << "# Device Link build statements for "
-                             << cmState::GetTargetTypeName(targetType)
-                             << " target " << this->GetTargetName() << "\n\n";
+  this->GetCommonFileStream() << "# Device Link build statements for "
+                              << cmState::GetTargetTypeName(targetType)
+                              << " target " << this->GetTargetName() << "\n\n";
 
   // Compute the comment.
   cmNinjaBuild build(this->LanguageLinkerDeviceRule(config));
@@ -587,7 +599,7 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
   // Compute outputs.
   build.Outputs.push_back(targetOutputReal);
   // Compute specific libraries to link with.
-  build.ExplicitDeps = this->GetObjects();
+  build.ExplicitDeps = this->GetObjects(config);
   build.ImplicitDeps =
     this->ComputeLinkDeps(this->TargetLinkLanguage(config), config);
 
@@ -609,6 +621,7 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
       this->GetLocalGenerator()->GetStateSnapshot().GetDirectory(),
       globalGen));
   linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
+  linkLineComputer->SetUseNinjaMulti(globalGen->IsMultiConfig());
 
   localGen.GetTargetFlags(
     linkLineComputer.get(), config, vars["LINK_LIBRARIES"], vars["FLAGS"],
@@ -616,8 +629,7 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
 
   this->addPoolNinjaVariable("JOB_POOL_LINK", genTarget, vars);
 
-  vars["LINK_FLAGS"] =
-    cmGlobalNinjaGenerator::EncodeLiteral(vars["LINK_FLAGS"]);
+  vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
 
   vars["MANIFESTS"] = this->GetManifests(config);
 
@@ -661,18 +673,21 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
     EnsureParentDirectoryExists(impLibPath);
   }
 
-  const std::string objPath = GetGeneratorTarget()->GetSupportDirectory();
+  const std::string objPath =
+    cmStrCat(GetGeneratorTarget()->GetSupportDirectory(),
+             globalGen->ConfigDirectory(config));
+
   vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
     this->ConvertToNinjaPath(objPath), cmOutputConverter::SHELL);
   EnsureDirectoryExists(objPath);
 
   this->SetMsvcTargetPdbVariable(vars, config);
 
+  std::string& linkLibraries = vars["LINK_LIBRARIES"];
+  std::string& link_path = vars["LINK_PATH"];
   if (globalGen->IsGCCOnWindows()) {
     // ar.exe can't handle backslashes in rsp files (implicitly used by gcc)
-    std::string& linkLibraries = vars["LINK_LIBRARIES"];
     std::replace(linkLibraries.begin(), linkLibraries.end(), '\\', '/');
-    std::string& link_path = vars["LINK_PATH"];
     std::replace(link_path.begin(), link_path.end(), '\\', '/');
   }
 
@@ -686,18 +701,19 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
                                            genTarget->GetName() + ".rsp");
 
   // Gather order-only dependencies.
-  this->GetLocalGenerator()->AppendTargetDepends(this->GetGeneratorTarget(),
-                                                 build.OrderOnlyDeps, config);
+  this->GetLocalGenerator()->AppendTargetDepends(
+    this->GetGeneratorTarget(), build.OrderOnlyDeps, config, config);
 
   // Write the build statement for this target.
   bool usedResponseFile = false;
-  globalGen->WriteBuild(this->GetBuildFileStream(), build,
+  globalGen->WriteBuild(this->GetCommonFileStream(), build,
                         commandLineLengthLimit, &usedResponseFile);
   this->WriteDeviceLinkRule(usedResponseFile, config);
 }
 
 void cmNinjaNormalTargetGenerator::WriteLinkStatement(
-  const std::string& config)
+  const std::string& config, const std::string& fileConfig,
+  bool firstForConfig)
 {
   cmMakefile* mf = this->GetMakefile();
   cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
@@ -710,6 +726,27 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
   std::string targetOutputImplib = ConvertToNinjaPath(
     gt->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
 
+  if (config != fileConfig) {
+    if (targetOutput == ConvertToNinjaPath(gt->GetFullPath(fileConfig))) {
+      return;
+    }
+    if (targetOutputReal ==
+        ConvertToNinjaPath(gt->GetFullPath(fileConfig,
+                                           cmStateEnums::RuntimeBinaryArtifact,
+                                           /*realname=*/true))) {
+      return;
+    }
+    if (!gt->GetFullName(config, cmStateEnums::ImportLibraryArtifact)
+           .empty() &&
+        !gt->GetFullName(fileConfig, cmStateEnums::ImportLibraryArtifact)
+           .empty() &&
+        targetOutputImplib ==
+          ConvertToNinjaPath(gt->GetFullPath(
+            fileConfig, cmStateEnums::ImportLibraryArtifact))) {
+      return;
+    }
+  }
+
   auto const tgtNames = this->TargetNames(config);
   if (gt->IsAppBundleOnApple()) {
     // Create the app bundle
@@ -733,9 +770,9 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
   }
 
   // Write comments.
-  cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
+  cmGlobalNinjaGenerator::WriteDivider(this->GetConfigFileStream(fileConfig));
   const cmStateEnums::TargetType targetType = gt->GetType();
-  this->GetBuildFileStream()
+  this->GetConfigFileStream(fileConfig)
     << "# Link build statements for " << cmState::GetTargetTypeName(targetType)
     << " target " << this->GetTargetName() << "\n\n";
 
@@ -748,6 +785,9 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
 
   // Compute outputs.
   linkBuild.Outputs.push_back(targetOutputReal);
+  if (firstForConfig) {
+    globalGen->GetByproductsForCleanTarget(config).push_back(targetOutputReal);
+  }
 
   if (this->TargetLinkLanguage(config) == "Swift") {
     vars["SWIFT_LIBRARY_NAME"] = [this, config]() -> std::string {
@@ -817,14 +857,14 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     gt->GetObjectSources(sources, config);
     for (const auto& source : sources) {
       linkBuild.Outputs.push_back(
-        this->ConvertToNinjaPath(this->GetObjectFilePath(source)));
+        this->ConvertToNinjaPath(this->GetObjectFilePath(source, config)));
       linkBuild.ExplicitDeps.push_back(
         this->ConvertToNinjaPath(this->GetSourceFilePath(source)));
     }
 
     linkBuild.Outputs.push_back(vars["SWIFT_MODULE"]);
   } else {
-    linkBuild.ExplicitDeps = this->GetObjects();
+    linkBuild.ExplicitDeps = this->GetObjects(config);
   }
   linkBuild.ImplicitDeps =
     this->ComputeLinkDeps(this->TargetLinkLanguage(config), config);
@@ -849,6 +889,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
       this->GetLocalGenerator(),
       this->GetLocalGenerator()->GetStateSnapshot().GetDirectory()));
   linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
+  linkLineComputer->SetUseNinjaMulti(globalGen->IsMultiConfig());
 
   localGen.GetTargetFlags(linkLineComputer.get(), config,
                           vars["LINK_LIBRARIES"], vars["FLAGS"],
@@ -868,8 +909,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
 
   this->AddModuleDefinitionFlag(linkLineComputer.get(), vars["LINK_FLAGS"],
                                 config);
-  vars["LINK_FLAGS"] =
-    cmGlobalNinjaGenerator::EncodeLiteral(vars["LINK_FLAGS"]);
+  vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
 
   vars["MANIFESTS"] = this->GetManifests(config);
 
@@ -920,6 +960,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     EnsureParentDirectoryExists(impLibPath);
     if (gt->HasImportLibrary(config)) {
       byproducts.push_back(targetOutputImplib);
+      if (firstForConfig) {
+        globalGen->GetByproductsForCleanTarget(config).push_back(
+          targetOutputImplib);
+      }
     }
   }
 
@@ -938,16 +982,17 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     vars["TARGET_PDB"] = base + suffix + dbg_suffix;
   }
 
-  const std::string objPath = gt->GetSupportDirectory();
+  const std::string objPath =
+    cmStrCat(gt->GetSupportDirectory(), globalGen->ConfigDirectory(config));
   vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
     this->ConvertToNinjaPath(objPath), cmOutputConverter::SHELL);
   EnsureDirectoryExists(objPath);
 
+  std::string& linkLibraries = vars["LINK_LIBRARIES"];
+  std::string& link_path = vars["LINK_PATH"];
   if (globalGen->IsGCCOnWindows()) {
     // ar.exe can't handle backslashes in rsp files (implicitly used by gcc)
-    std::string& linkLibraries = vars["LINK_LIBRARIES"];
     std::replace(linkLibraries.begin(), linkLibraries.end(), '\\', '/');
-    std::string& link_path = vars["LINK_PATH"];
     std::replace(link_path.begin(), link_path.end(), '\\', '/');
   }
 
@@ -958,17 +1003,24 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
 
   std::vector<std::string> preLinkCmdLines;
   std::vector<std::string> postBuildCmdLines;
-  std::vector<std::string>* cmdLineLists[3] = { &preLinkCmdLines,
-                                                &preLinkCmdLines,
-                                                &postBuildCmdLines };
-
-  for (unsigned i = 0; i != 3; ++i) {
-    for (cmCustomCommand const& cc : *cmdLists[i]) {
-      cmCustomCommandGenerator ccg(cc, config, this->GetLocalGenerator());
-      localGen.AppendCustomCommandLines(ccg, *cmdLineLists[i]);
-      std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
-      std::transform(ccByproducts.begin(), ccByproducts.end(),
-                     std::back_inserter(byproducts), MapToNinjaPath());
+
+  if (config == fileConfig) {
+    std::vector<std::string>* cmdLineLists[3] = { &preLinkCmdLines,
+                                                  &preLinkCmdLines,
+                                                  &postBuildCmdLines };
+
+    for (unsigned i = 0; i != 3; ++i) {
+      for (cmCustomCommand const& cc : *cmdLists[i]) {
+        cmCustomCommandGenerator ccg(cc, config, this->GetLocalGenerator());
+        localGen.AppendCustomCommandLines(ccg, *cmdLineLists[i]);
+        std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
+        std::transform(ccByproducts.begin(), ccByproducts.end(),
+                       std::back_inserter(byproducts), MapToNinjaPath());
+        std::transform(
+          ccByproducts.begin(), ccByproducts.end(),
+          std::back_inserter(globalGen->GetByproductsForCleanTarget()),
+          MapToNinjaPath());
+      }
     }
   }
 
@@ -1000,7 +1052,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     cmGeneratedFileStream fout(obj_list_file);
 
     if (mdi->WindowsExportAllSymbols) {
-      cmNinjaDeps objs = this->GetObjects();
+      cmNinjaDeps objs = this->GetObjects(config);
       for (std::string const& obj : objs) {
         if (cmHasLiteralSuffix(obj, ".obj")) {
           fout << obj << "\n";
@@ -1058,7 +1110,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
 
   // Gather order-only dependencies.
   this->GetLocalGenerator()->AppendTargetDepends(gt, linkBuild.OrderOnlyDeps,
-                                                 config);
+                                                 config, fileConfig);
 
   // Add order-only dependencies on versioning symlinks of shared libs we link.
   if (!this->GeneratorTarget->IsDLLPlatform()) {
@@ -1090,7 +1142,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
 
   // Write the build statement for this target.
   bool usedResponseFile = false;
-  globalGen->WriteBuild(this->GetBuildFileStream(), linkBuild,
+  globalGen->WriteBuild(this->GetConfigFileStream(fileConfig), linkBuild,
                         commandLineLengthLimit, &usedResponseFile);
   this->WriteLinkRule(usedResponseFile, config);
 
@@ -1099,9 +1151,12 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
       cmNinjaBuild build("CMAKE_SYMLINK_EXECUTABLE");
       build.Comment = "Create executable symlink " + targetOutput;
       build.Outputs.push_back(targetOutput);
+      if (firstForConfig) {
+        globalGen->GetByproductsForCleanTarget(config).push_back(targetOutput);
+      }
       build.ExplicitDeps.push_back(targetOutputReal);
       build.Variables = std::move(symlinkVars);
-      globalGen->WriteBuild(this->GetBuildFileStream(), build);
+      globalGen->WriteBuild(this->GetConfigFileStream(fileConfig), build);
     } else {
       cmNinjaBuild build("CMAKE_SYMLINK_LIBRARY");
       build.Comment = "Create library symlink " + targetOutput;
@@ -1116,12 +1171,18 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
       } else {
         symlinkVars["SONAME"].clear();
         build.Outputs.push_back(soName);
+        if (firstForConfig) {
+          globalGen->GetByproductsForCleanTarget(config).push_back(soName);
+        }
       }
       build.Outputs.push_back(targetOutput);
+      if (firstForConfig) {
+        globalGen->GetByproductsForCleanTarget(config).push_back(targetOutput);
+      }
       build.ExplicitDeps.push_back(targetOutputReal);
       build.Variables = std::move(symlinkVars);
 
-      globalGen->WriteBuild(this->GetBuildFileStream(), build);
+      globalGen->WriteBuild(this->GetConfigFileStream(fileConfig), build);
     }
   }
 
@@ -1139,8 +1200,11 @@ void cmNinjaNormalTargetGenerator::WriteObjectLibStatement(
     build.Comment = "Object library " + this->GetTargetName();
     this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
                                                    build.Outputs, config);
-    build.ExplicitDeps = this->GetObjects();
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
+    this->GetLocalGenerator()->AppendTargetOutputs(
+      this->GetGeneratorTarget(),
+      this->GetGlobalGenerator()->GetByproductsForCleanTarget(config), config);
+    build.ExplicitDeps = this->GetObjects(config);
+    this->GetGlobalGenerator()->WriteBuild(this->GetCommonFileStream(), build);
   }
 
   // Add aliases for the target name.

+ 2 - 1
Source/cmNinjaNormalTargetGenerator.h

@@ -29,7 +29,8 @@ private:
   void WriteLinkRule(bool useResponseFile, const std::string& config);
   void WriteDeviceLinkRule(bool useResponseFile, const std::string& config);
 
-  void WriteLinkStatement(const std::string& config);
+  void WriteLinkStatement(const std::string& config,
+                          const std::string& fileConfig, bool firstForConfig);
   void WriteDeviceLinkStatement(const std::string& config);
 
   void WriteObjectLibStatement(const std::string& config);

+ 123 - 79
Source/cmNinjaTargetGenerator.cxx

@@ -68,9 +68,15 @@ cmNinjaTargetGenerator::cmNinjaTargetGenerator(cmGeneratorTarget* target)
 
 cmNinjaTargetGenerator::~cmNinjaTargetGenerator() = default;
 
-cmGeneratedFileStream& cmNinjaTargetGenerator::GetBuildFileStream() const
+cmGeneratedFileStream& cmNinjaTargetGenerator::GetConfigFileStream(
+  const std::string& config) const
+{
+  return *this->GetGlobalGenerator()->GetConfigFileStream(config);
+}
+
+cmGeneratedFileStream& cmNinjaTargetGenerator::GetCommonFileStream() const
 {
-  return *this->GetGlobalGenerator()->GetBuildFileStream();
+  return *this->GetGlobalGenerator()->GetCommonFileStream();
 }
 
 cmGeneratedFileStream& cmNinjaTargetGenerator::GetRulesFileStream() const
@@ -84,17 +90,19 @@ cmGlobalNinjaGenerator* cmNinjaTargetGenerator::GetGlobalGenerator() const
 }
 
 std::string cmNinjaTargetGenerator::LanguageCompilerRule(
-  const std::string& lang) const
+  const std::string& lang, const std::string& config) const
 {
   return lang + "_COMPILER__" +
-    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName());
+    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()) +
+    "_" + config;
 }
 
 std::string cmNinjaTargetGenerator::LanguagePreprocessRule(
-  std::string const& lang) const
+  std::string const& lang, const std::string& config) const
 {
   return lang + "_PREPROCESS__" +
-    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName());
+    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()) +
+    "_" + config;
 }
 
 bool cmNinjaTargetGenerator::NeedExplicitPreprocessing(
@@ -117,10 +125,11 @@ bool cmNinjaTargetGenerator::CompilePreprocessedSourceWithDefines(
 }
 
 std::string cmNinjaTargetGenerator::LanguageDyndepRule(
-  const std::string& lang) const
+  const std::string& lang, const std::string& config) const
 {
   return lang + "_DYNDEP__" +
-    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName());
+    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()) +
+    "_" + config;
 }
 
 bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang) const
@@ -128,9 +137,11 @@ bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang) const
   return lang == "Fortran";
 }
 
-std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget()
+std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
+  const std::string& config)
 {
-  return "cmake_object_order_depends_target_" + this->GetTargetName();
+  return cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
+    this->GeneratorTarget, config);
 }
 
 // TODO: Most of the code is picked up from
@@ -240,6 +251,11 @@ std::string cmNinjaTargetGenerator::ComputeDefines(cmSourceFile const* source,
   cmGeneratorExpressionInterpreter genexInterpreter(
     this->LocalGenerator, config, this->GeneratorTarget, language);
 
+  // Seriously??
+  if (this->GetGlobalGenerator()->IsMultiConfig()) {
+    defines.insert(cmStrCat("CMAKE_INTDIR=\"", config, '"'));
+  }
+
   const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
   if (const char* compile_defs = source->GetProperty(COMPILE_DEFINITIONS)) {
     this->LocalGenerator->AppendDefines(
@@ -333,7 +349,7 @@ std::string cmNinjaTargetGenerator::GetSourceFilePath(
 }
 
 std::string cmNinjaTargetGenerator::GetObjectFilePath(
-  cmSourceFile const* source) const
+  cmSourceFile const* source, const std::string& config) const
 {
   std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
   if (!path.empty()) {
@@ -341,13 +357,14 @@ std::string cmNinjaTargetGenerator::GetObjectFilePath(
   }
   std::string const& objectName = this->GeneratorTarget->GetObjectName(source);
   path += this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
+  path += this->GetGlobalGenerator()->ConfigDirectory(config);
   path += "/";
   path += objectName;
   return path;
 }
 
 std::string cmNinjaTargetGenerator::GetPreprocessedFilePath(
-  cmSourceFile const* source) const
+  cmSourceFile const* source, const std::string& config) const
 {
   // Choose an extension to compile already-preprocessed source.
   std::string ppExt = source->GetExtension();
@@ -377,19 +394,21 @@ std::string cmNinjaTargetGenerator::GetPreprocessedFilePath(
     path += "/";
   }
   path += this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
+  path += this->GetGlobalGenerator()->ConfigDirectory(config);
   path += "/";
   path += ppName;
   return path;
 }
 
 std::string cmNinjaTargetGenerator::GetDyndepFilePath(
-  std::string const& lang) const
+  std::string const& lang, const std::string& config) const
 {
   std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
   if (!path.empty()) {
     path += "/";
   }
   path += this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
+  path += this->GetGlobalGenerator()->ConfigDirectory(config);
   path += "/";
   path += lang;
   path += ".dd";
@@ -397,12 +416,13 @@ std::string cmNinjaTargetGenerator::GetDyndepFilePath(
 }
 
 std::string cmNinjaTargetGenerator::GetTargetDependInfoPath(
-  std::string const& lang) const
+  std::string const& lang, const std::string& config) const
 {
   std::string path =
     cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/',
              this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
-             '/', lang, "DependInfo.json");
+             this->GetGlobalGenerator()->ConfigDirectory(config), '/', lang,
+             "DependInfo.json");
   return path;
 }
 
@@ -460,15 +480,17 @@ bool cmNinjaTargetGenerator::SetMsvcTargetPdbVariable(
   return false;
 }
 
-void cmNinjaTargetGenerator::WriteLanguageRules(const std::string& language)
+void cmNinjaTargetGenerator::WriteLanguageRules(const std::string& language,
+                                                const std::string& config)
 {
 #ifdef NINJA_GEN_VERBOSE_FILES
   this->GetRulesFileStream() << "# Rules for language " << language << "\n\n";
 #endif
-  this->WriteCompileRule(language);
+  this->WriteCompileRule(language, config);
 }
 
-void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
+void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
+                                              const std::string& config)
 {
   cmRulePlaceholderExpander::RuleVariables vars;
   vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
@@ -509,7 +531,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
     this->GetLocalGenerator()->CreateRulePlaceholderExpander());
 
   std::string const tdi = this->GetLocalGenerator()->ConvertToOutputFormat(
-    ConvertToNinjaPath(this->GetTargetDependInfoPath(lang)),
+    ConvertToNinjaPath(this->GetTargetDependInfoPath(lang, config)),
     cmLocalGenerator::SHELL);
 
   std::string launcher;
@@ -524,7 +546,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
       cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL);
 
   if (explicitPP) {
-    cmNinjaRule rule(this->LanguagePreprocessRule(lang));
+    cmNinjaRule rule(this->LanguagePreprocessRule(lang, config));
     // Explicit preprocessing always uses a depfile.
     rule.DepType = ""; // no deps= for multiple outputs
     rule.DepFile = "$DEP_FILE";
@@ -604,7 +626,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
 
   if (needDyndep) {
     // Write the rule for ninja dyndep file generation.
-    cmNinjaRule rule(this->LanguageDyndepRule(lang));
+    cmNinjaRule rule(this->LanguageDyndepRule(lang, config));
     // Command line length is almost always limited -> use response file for
     // dyndep rules
     rule.RspFile = "$out.rsp";
@@ -628,7 +650,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
     this->GetGlobalGenerator()->AddRule(rule);
   }
 
-  cmNinjaRule rule(this->LanguageCompilerRule(lang));
+  cmNinjaRule rule(this->LanguageCompilerRule(lang, config));
   // If using a response file, move defines, includes, and flags into it.
   if (!responseFlag.empty()) {
     rule.RspFile = "$RSP_FILE";
@@ -788,11 +810,12 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
 }
 
 void cmNinjaTargetGenerator::WriteObjectBuildStatements(
-  const std::string& config)
+  const std::string& config, const std::string& fileConfig,
+  bool firstForConfig)
 {
   // Write comments.
-  cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
-  this->GetBuildFileStream()
+  cmGlobalNinjaGenerator::WriteDivider(this->GetConfigFileStream(fileConfig));
+  this->GetConfigFileStream(fileConfig)
     << "# Object build statements for "
     << cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType())
     << " target " << this->GetTargetName() << "\n\n";
@@ -806,7 +829,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
         cc, this->GetGeneratorTarget());
       // Record the custom commands for this target. The container is used
       // in WriteObjectBuildStatement when called in a loop below.
-      this->CustomCommands.push_back(cc);
+      this->Configs[config].CustomCommands.push_back(cc);
     }
   }
   {
@@ -821,16 +844,17 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     this->OSXBundleGenerator->GenerateMacOSXContentStatements(
       extraSources, this->MacOSXContentGenerator.get(), config);
   }
-  {
+  if (firstForConfig) {
     const char* pchExtension =
       GetMakefile()->GetDefinition("CMAKE_PCH_EXTENSION");
 
     std::vector<cmSourceFile const*> externalObjects;
     this->GeneratorTarget->GetExternalObjects(externalObjects, config);
     for (cmSourceFile const* sf : externalObjects) {
-      const auto objectFileName = this->GetSourceFilePath(sf);
+      auto objectFileName = this->GetGlobalGenerator()->ExpandCFGIntDir(
+        this->GetSourceFilePath(sf), config);
       if (!cmSystemTools::StringEndsWith(objectFileName, pchExtension)) {
-        this->Objects.push_back(objectFileName);
+        this->Configs[config].Objects.push_back(objectFileName);
       }
     }
   }
@@ -838,17 +862,18 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
   {
     cmNinjaBuild build("phony");
     build.Comment = "Order-only phony target for " + this->GetTargetName();
-    build.Outputs.push_back(this->OrderDependsTargetForTarget());
+    build.Outputs.push_back(this->OrderDependsTargetForTarget(config));
 
     cmNinjaDeps& orderOnlyDeps = build.OrderOnlyDeps;
     this->GetLocalGenerator()->AppendTargetDepends(
-      this->GeneratorTarget, orderOnlyDeps, config, DependOnTargetOrdering);
+      this->GeneratorTarget, orderOnlyDeps, config, fileConfig,
+      DependOnTargetOrdering);
 
     // Add order-only dependencies on other files associated with the target.
-    cmAppend(orderOnlyDeps, this->ExtraFiles);
+    cmAppend(orderOnlyDeps, this->Configs[config].ExtraFiles);
 
     // Add order-only dependencies on custom command outputs.
-    for (cmCustomCommand const* cc : this->CustomCommands) {
+    for (cmCustomCommand const* cc : this->Configs[config].CustomCommands) {
       cmCustomCommandGenerator ccg(*cc, config, this->GetLocalGenerator());
       const std::vector<std::string>& ccoutputs = ccg.GetOutputs();
       const std::vector<std::string>& ccbyproducts = ccg.GetByproducts();
@@ -875,23 +900,24 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
       orderOnlyDeps.push_back(this->ConvertToNinjaPath(tgtDir));
     }
 
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
+    this->GetGlobalGenerator()->WriteBuild(
+      this->GetConfigFileStream(fileConfig), build);
   }
 
   {
     std::vector<cmSourceFile const*> objectSources;
     this->GeneratorTarget->GetObjectSources(objectSources, config);
     for (cmSourceFile const* sf : objectSources) {
-      this->WriteObjectBuildStatement(sf, config);
+      this->WriteObjectBuildStatement(sf, config, fileConfig, firstForConfig);
     }
   }
 
-  for (auto const& langDDIFiles : this->DDIFiles) {
+  for (auto const& langDDIFiles : this->Configs[config].DDIFiles) {
     std::string const& language = langDDIFiles.first;
     cmNinjaDeps const& ddiFiles = langDDIFiles.second;
 
-    cmNinjaBuild build(this->LanguageDyndepRule(language));
-    build.Outputs.push_back(this->GetDyndepFilePath(language));
+    cmNinjaBuild build(this->LanguageDyndepRule(language, config));
+    build.Outputs.push_back(this->GetDyndepFilePath(language, config));
     build.ExplicitDeps = ddiFiles;
 
     this->WriteTargetDependInfo(language, config);
@@ -904,48 +930,52 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     // our dependencies produces them.  Fixing this will require
     // refactoring the Ninja generator to generate targets in
     // dependency order so that we can collect the needed information.
-    this->GetLocalGenerator()->AppendTargetDepends(this->GeneratorTarget,
-                                                   build.OrderOnlyDeps, config,
-                                                   DependOnTargetArtifact);
+    this->GetLocalGenerator()->AppendTargetDepends(
+      this->GeneratorTarget, build.OrderOnlyDeps, config, fileConfig,
+      DependOnTargetArtifact);
 
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
+    this->GetGlobalGenerator()->WriteBuild(
+      this->GetConfigFileStream(fileConfig), build);
   }
 
-  this->GetBuildFileStream() << "\n";
+  this->GetConfigFileStream(fileConfig) << "\n";
 
-  if (!this->SwiftOutputMap.empty()) {
+  if (!this->Configs[config].SwiftOutputMap.empty()) {
     std::string const mapFilePath =
       this->GeneratorTarget->GetSupportDirectory() + "/output-file-map.json";
-    std::string const targetSwiftDepsPath = [this]() -> std::string {
+    std::string const targetSwiftDepsPath = [this, config]() -> std::string {
       cmGeneratorTarget const* target = this->GeneratorTarget;
       if (const char* name = target->GetProperty("Swift_DEPENDENCIES_FILE")) {
         return name;
       }
       return this->ConvertToNinjaPath(target->GetSupportDirectory() + "/" +
-                                      target->GetName() + ".swiftdeps");
+                                      config + "/" + target->GetName() +
+                                      ".swiftdeps");
     }();
 
     // build the global target dependencies
     // https://github.com/apple/swift/blob/master/docs/Driver.md#output-file-maps
     Json::Value deps(Json::objectValue);
     deps["swift-dependencies"] = targetSwiftDepsPath;
-    this->SwiftOutputMap[""] = deps;
+    this->Configs[config].SwiftOutputMap[""] = deps;
 
     cmGeneratedFileStream output(mapFilePath);
-    output << this->SwiftOutputMap;
+    output << this->Configs[config].SwiftOutputMap;
   }
 }
 
 void cmNinjaTargetGenerator::WriteObjectBuildStatement(
-  cmSourceFile const* source, const std::string& config)
+  cmSourceFile const* source, const std::string& config,
+  const std::string& fileConfig, bool firstForConfig)
 {
   std::string const language = source->GetLanguage();
   std::string const sourceFileName =
     language == "RC" ? source->GetFullPath() : this->GetSourceFilePath(source);
-  std::string const objectDir =
-    this->ConvertToNinjaPath(this->GeneratorTarget->GetSupportDirectory());
+  std::string const objectDir = this->ConvertToNinjaPath(
+    cmStrCat(this->GeneratorTarget->GetSupportDirectory(),
+             this->GetGlobalGenerator()->ConfigDirectory(config)));
   std::string const objectFileName =
-    this->ConvertToNinjaPath(this->GetObjectFilePath(source));
+    this->ConvertToNinjaPath(this->GetObjectFilePath(source, config));
   std::string const objectFileDir =
     cmSystemTools::GetFilenamePath(objectFileName);
 
@@ -961,7 +991,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   int const commandLineLengthLimit =
     ((lang_supports_response && this->ForceResponseFile())) ? -1 : 0;
 
-  cmNinjaBuild objBuild(this->LanguageCompilerRule(language));
+  cmNinjaBuild objBuild(this->LanguageCompilerRule(language, config));
   cmNinjaVars& vars = objBuild.Variables;
   vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config);
   vars["DEFINES"] = this->ComputeDefines(source, language, config);
@@ -993,11 +1023,13 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"]);
 
   objBuild.Outputs.push_back(objectFileName);
-  const char* pchExtension =
-    this->GetMakefile()->GetDefinition("CMAKE_PCH_EXTENSION");
-  if (!cmSystemTools::StringEndsWith(objectFileName, pchExtension)) {
-    // Add this object to the list of object files.
-    this->Objects.push_back(objectFileName);
+  if (firstForConfig) {
+    const char* pchExtension =
+      this->GetMakefile()->GetDefinition("CMAKE_PCH_EXTENSION");
+    if (!cmSystemTools::StringEndsWith(objectFileName, pchExtension)) {
+      // Add this object to the list of object files.
+      this->Configs[config].Objects.push_back(objectFileName);
+    }
   }
 
   objBuild.ExplicitDeps.push_back(sourceFileName);
@@ -1031,7 +1063,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
                    MapToNinjaPath());
   }
 
-  objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget());
+  objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
 
   // If the source file is GENERATED and does not have a custom command
   // (either attached to this source file or another one), assume that one of
@@ -1051,10 +1083,10 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   // For some cases we do an explicit preprocessor invocation.
   bool const explicitPP = this->NeedExplicitPreprocessing(language);
   if (explicitPP) {
-    cmNinjaBuild ppBuild(this->LanguagePreprocessRule(language));
+    cmNinjaBuild ppBuild(this->LanguagePreprocessRule(language, config));
 
     std::string const ppFileName =
-      this->ConvertToNinjaPath(this->GetPreprocessedFilePath(source));
+      this->ConvertToNinjaPath(this->GetPreprocessedFilePath(source, config));
     ppBuild.Outputs.push_back(ppFileName);
 
     ppBuild.RspFile = ppFileName + ".rsp";
@@ -1136,17 +1168,19 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
       std::string const ddiFile = objectFileName + ".ddi";
       ppBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile;
       ppBuild.ImplicitOuts.push_back(ddiFile);
-      this->DDIFiles[language].push_back(ddiFile);
+      if (firstForConfig) {
+        this->Configs[config].DDIFiles[language].push_back(ddiFile);
+      }
     }
 
     this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
                                ppBuild.Variables);
 
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), ppBuild,
-                                           commandLineLengthLimit);
+    this->GetGlobalGenerator()->WriteBuild(
+      this->GetConfigFileStream(fileConfig), ppBuild, commandLineLengthLimit);
   }
   if (needDyndep) {
-    std::string const dyndep = this->GetDyndepFilePath(language);
+    std::string const dyndep = this->GetDyndepFilePath(language, config);
     objBuild.OrderOnlyDeps.push_back(dyndep);
     vars["dyndep"] = dyndep;
   }
@@ -1166,10 +1200,10 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   objBuild.RspFile = objectFileName + ".rsp";
 
   if (language == "Swift") {
-    this->EmitSwiftDependencyInfo(source);
+    this->EmitSwiftDependencyInfo(source, config);
   } else {
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(),
-                                           objBuild, commandLineLengthLimit);
+    this->GetGlobalGenerator()->WriteBuild(
+      this->GetConfigFileStream(fileConfig), objBuild, commandLineLengthLimit);
   }
 
   if (const char* objectOutputs = source->GetProperty("OBJECT_OUTPUTS")) {
@@ -1179,7 +1213,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     std::transform(build.Outputs.begin(), build.Outputs.end(),
                    build.Outputs.begin(), MapToNinjaPath());
     build.ExplicitDeps = objBuild.Outputs;
-    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
+    this->GetGlobalGenerator()->WriteBuild(
+      this->GetConfigFileStream(fileConfig), build);
   }
 }
 
@@ -1225,18 +1260,18 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
     tdi_linked_target_dirs.append(l);
   }
 
-  std::string const tdin = this->GetTargetDependInfoPath(lang);
+  std::string const tdin = this->GetTargetDependInfoPath(lang, config);
   cmGeneratedFileStream tdif(tdin);
   tdif << tdi;
 }
 
 void cmNinjaTargetGenerator::EmitSwiftDependencyInfo(
-  cmSourceFile const* source)
+  cmSourceFile const* source, const std::string& config)
 {
   std::string const sourceFilePath =
     this->ConvertToNinjaPath(this->GetSourceFilePath(source));
   std::string const objectFilePath =
-    this->ConvertToNinjaPath(this->GetObjectFilePath(source));
+    this->ConvertToNinjaPath(this->GetObjectFilePath(source, config));
   std::string const swiftDepsPath = [source, objectFilePath]() -> std::string {
     if (const char* name = source->GetProperty("Swift_DEPENDENCIES_FILE")) {
       return name;
@@ -1249,10 +1284,10 @@ void cmNinjaTargetGenerator::EmitSwiftDependencyInfo(
     }
     return objectFilePath + ".dia";
   }();
-  std::string const makeDepsPath = [this, source]() -> std::string {
+  std::string const makeDepsPath = [this, source, config]() -> std::string {
     cmLocalNinjaGenerator const* local = this->GetLocalGenerator();
     std::string const objectFileName =
-      this->ConvertToNinjaPath(this->GetObjectFilePath(source));
+      this->ConvertToNinjaPath(this->GetObjectFilePath(source, config));
     std::string const objectFileDir =
       cmSystemTools::GetFilenamePath(objectFileName);
 
@@ -1273,7 +1308,7 @@ void cmNinjaTargetGenerator::EmitSwiftDependencyInfo(
   entry["dependencies"] = makeDepsPath;
   entry["swift-dependencies"] = swiftDepsPath;
   entry["diagnostics"] = swiftDiaPath;
-  SwiftOutputMap[sourceFilePath] = entry;
+  this->Configs[config].SwiftOutputMap[sourceFilePath] = entry;
 }
 
 void cmNinjaTargetGenerator::ExportObjectCompileCommand(
@@ -1362,11 +1397,20 @@ void cmNinjaTargetGenerator::AdditionalCleanFiles(const std::string& config)
     for (std::string const& cleanFile : cleanFiles) {
       // Support relative paths
       gg->AddAdditionalCleanFile(
-        cmSystemTools::CollapseFullPath(cleanFile, binaryDir));
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
     }
   }
 }
 
+cmNinjaDeps cmNinjaTargetGenerator::GetObjects(const std::string& config) const
+{
+  auto const it = this->Configs.find(config);
+  if (it != this->Configs.end()) {
+    return it->second.Objects;
+  }
+  return {};
+}
+
 void cmNinjaTargetGenerator::EnsureDirectoryExists(
   const std::string& path) const
 {
@@ -1410,11 +1454,11 @@ void cmNinjaTargetGenerator::MacOSXContentGeneratorType::operator()(
   output = this->Generator->GetGlobalGenerator()->ConvertToNinjaPath(output);
 
   // Write a build statement to copy the content into the bundle.
-  this->Generator->GetGlobalGenerator()->WriteMacOSXContentBuild(input,
-                                                                 output);
+  this->Generator->GetGlobalGenerator()->WriteMacOSXContentBuild(input, output,
+                                                                 config);
 
   // Add as a dependency to the target so that it gets called.
-  this->Generator->ExtraFiles.push_back(std::move(output));
+  this->Generator->Configs[config].ExtraFiles.push_back(std::move(output));
 }
 
 void cmNinjaTargetGenerator::addPoolNinjaVariable(

+ 44 - 23
Source/cmNinjaTargetGenerator.h

@@ -47,7 +47,8 @@ public:
 protected:
   bool SetMsvcTargetPdbVariable(cmNinjaVars&, const std::string& config) const;
 
-  cmGeneratedFileStream& GetBuildFileStream() const;
+  cmGeneratedFileStream& GetConfigFileStream(const std::string& config) const;
+  cmGeneratedFileStream& GetCommonFileStream() const;
   cmGeneratedFileStream& GetRulesFileStream() const;
 
   cmGeneratorTarget* GetGeneratorTarget() const
@@ -64,15 +65,18 @@ protected:
 
   cmMakefile* GetMakefile() const { return this->Makefile; }
 
-  std::string LanguageCompilerRule(const std::string& lang) const;
-  std::string LanguagePreprocessRule(std::string const& lang) const;
+  std::string LanguageCompilerRule(const std::string& lang,
+                                   const std::string& config) const;
+  std::string LanguagePreprocessRule(std::string const& lang,
+                                     const std::string& config) const;
   bool NeedExplicitPreprocessing(std::string const& lang) const;
-  std::string LanguageDyndepRule(std::string const& lang) const;
+  std::string LanguageDyndepRule(std::string const& lang,
+                                 const std::string& config) const;
   bool NeedDyndep(std::string const& lang) const;
   bool UsePreprocessedSource(std::string const& lang) const;
   bool CompilePreprocessedSourceWithDefines(std::string const& lang) const;
 
-  std::string OrderDependsTargetForTarget();
+  std::string OrderDependsTargetForTarget(const std::string& config);
 
   std::string ComputeOrderDependsForTarget();
 
@@ -113,16 +117,20 @@ protected:
   std::string GetSourceFilePath(cmSourceFile const* source) const;
 
   /// @return the object file path for the given @a source.
-  std::string GetObjectFilePath(cmSourceFile const* source) const;
+  std::string GetObjectFilePath(cmSourceFile const* source,
+                                const std::string& config) const;
 
   /// @return the preprocessed source file path for the given @a source.
-  std::string GetPreprocessedFilePath(cmSourceFile const* source) const;
+  std::string GetPreprocessedFilePath(cmSourceFile const* source,
+                                      const std::string& config) const;
 
   /// @return the dyndep file path for this target.
-  std::string GetDyndepFilePath(std::string const& lang) const;
+  std::string GetDyndepFilePath(std::string const& lang,
+                                const std::string& config) const;
 
   /// @return the target dependency scanner info file path
-  std::string GetTargetDependInfoPath(std::string const& lang) const;
+  std::string GetTargetDependInfoPath(std::string const& lang,
+                                      const std::string& config) const;
 
   /// @return the file path where the target named @a name is generated.
   std::string GetTargetFilePath(const std::string& name,
@@ -131,15 +139,22 @@ protected:
   /// @return the output path for the target.
   virtual std::string GetTargetOutputDir(const std::string& config) const;
 
-  void WriteLanguageRules(const std::string& language);
-  void WriteCompileRule(const std::string& language);
-  void WriteObjectBuildStatements(const std::string& config);
+  void WriteLanguageRules(const std::string& language,
+                          const std::string& config);
+  void WriteCompileRule(const std::string& language,
+                        const std::string& config);
+  void WriteObjectBuildStatements(const std::string& config,
+                                  const std::string& fileConfig,
+                                  bool firstForConfig);
   void WriteObjectBuildStatement(cmSourceFile const* source,
-                                 const std::string& config);
+                                 const std::string& config,
+                                 const std::string& fileConfig,
+                                 bool firstForConfig);
   void WriteTargetDependInfo(std::string const& lang,
                              const std::string& config);
 
-  void EmitSwiftDependencyInfo(cmSourceFile const* source);
+  void EmitSwiftDependencyInfo(cmSourceFile const* source,
+                               const std::string& config);
 
   void ExportObjectCompileCommand(
     std::string const& language, std::string const& sourceFileName,
@@ -149,7 +164,7 @@ protected:
 
   void AdditionalCleanFiles(const std::string& config);
 
-  cmNinjaDeps GetObjects() const { return this->Objects; }
+  cmNinjaDeps GetObjects(const std::string& config) const;
 
   void EnsureDirectoryExists(const std::string& dir) const;
   void EnsureParentDirectoryExists(const std::string& path) const;
@@ -183,14 +198,20 @@ protected:
 
 private:
   cmLocalNinjaGenerator* LocalGenerator;
-  /// List of object files for this target.
-  cmNinjaDeps Objects;
-  // Fortran Support
-  std::map<std::string, cmNinjaDeps> DDIFiles;
-  // Swift Support
-  Json::Value SwiftOutputMap;
-  std::vector<cmCustomCommand const*> CustomCommands;
-  cmNinjaDeps ExtraFiles;
+
+  struct ByConfig
+  {
+    /// List of object files for this target.
+    cmNinjaDeps Objects;
+    // Fortran Support
+    std::map<std::string, cmNinjaDeps> DDIFiles;
+    // Swift Support
+    Json::Value SwiftOutputMap;
+    std::vector<cmCustomCommand const*> CustomCommands;
+    cmNinjaDeps ExtraFiles;
+  };
+
+  std::map<std::string, ByConfig> Configs;
 };
 
 #endif // ! cmNinjaTargetGenerator_h

+ 30 - 6
Source/cmNinjaUtilityTargetGenerator.cxx

@@ -21,6 +21,7 @@
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTarget.h"
 
 cmNinjaUtilityTargetGenerator::cmNinjaUtilityTargetGenerator(
   cmGeneratorTarget* target)
@@ -36,8 +37,12 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
   cmLocalNinjaGenerator* lg = this->GetLocalGenerator();
   cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
 
+  std::string configDir;
+  if (genTarget->Target->IsPerConfig()) {
+    configDir = gg->ConfigDirectory(config);
+  }
   std::string utilCommandName =
-    cmStrCat(lg->GetCurrentBinaryDirectory(), "/CMakeFiles/",
+    cmStrCat(lg->GetCurrentBinaryDirectory(), "/CMakeFiles", configDir, "/",
              this->GetTargetName(), ".util");
   utilCommandName = this->ConvertToNinjaPath(utilCommandName);
 
@@ -86,13 +91,21 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
     }
   }
 
-  lg->AppendTargetOutputs(genTarget, phonyBuild.Outputs, config);
-  lg->AppendTargetDepends(genTarget, deps, config);
+  std::string outputConfig;
+  if (genTarget->Target->IsPerConfig()) {
+    outputConfig = config;
+  }
+  lg->AppendTargetOutputs(genTarget, phonyBuild.Outputs, outputConfig);
+  if (genTarget->Target->GetType() != cmStateEnums::GLOBAL_TARGET) {
+    lg->AppendTargetOutputs(genTarget, gg->GetByproductsForCleanTarget(),
+                            config);
+  }
+  lg->AppendTargetDepends(genTarget, deps, config, config);
 
   if (commands.empty()) {
     phonyBuild.Comment = "Utility command for " + this->GetTargetName();
     phonyBuild.ExplicitDeps = std::move(deps);
-    gg->WriteBuild(this->GetBuildFileStream(), phonyBuild);
+    gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
   } else {
     std::string command =
       lg->BuildCommandLine(commands, "utility", this->GeneratorTarget);
@@ -115,6 +128,7 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
       lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
                                 cmOutputConverter::SHELL));
     cmSystemTools::ReplaceString(command, "$(ARGS)", "");
+    command = gg->ExpandCFGIntDir(command, config);
 
     if (command.find('$') != std::string::npos) {
       return;
@@ -124,13 +138,18 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
       gg->SeenCustomCommandOutput(util_output);
     }
 
+    std::string ccConfig;
+    if (genTarget->Target->IsPerConfig() &&
+        genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
+      ccConfig = config;
+    }
     gg->WriteCustomCommandBuild(command, desc,
                                 "Utility command for " + this->GetTargetName(),
                                 /*depfile*/ "", /*job_pool*/ "", uses_terminal,
-                                /*restat*/ true, util_outputs, deps);
+                                /*restat*/ true, util_outputs, ccConfig, deps);
 
     phonyBuild.ExplicitDeps.push_back(utilCommandName);
-    gg->WriteBuild(this->GetBuildFileStream(), phonyBuild);
+    gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
   }
 
   // Find ADDITIONAL_CLEAN_FILES
@@ -141,5 +160,10 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config)
   // be per-directory and have one at the top-level anyway.
   if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
     gg->AddTargetAlias(this->GetTargetName(), genTarget, config);
+  } else if (gg->IsMultiConfig() && genTarget->Target->IsPerConfig()) {
+    cmNinjaBuild phonyAlias("phony");
+    gg->AppendTargetOutputs(genTarget, phonyAlias.Outputs, "");
+    phonyAlias.ExplicitDeps = phonyBuild.Outputs;
+    gg->WriteBuild(this->GetConfigFileStream(config), phonyAlias);
   }
 }

+ 18 - 5
Source/cmOutputConverter.cxx

@@ -43,9 +43,10 @@ std::string cmOutputConverter::ConvertToOutputFormat(cm::string_view source,
 {
   std::string result(source);
   // Convert it to an output path.
-  if (output == SHELL || output == WATCOMQUOTE) {
+  if (output == SHELL || output == WATCOMQUOTE || output == NINJAMULTI) {
     result = this->ConvertDirectorySeparatorsForShell(source);
-    result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE);
+    result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE,
+                                  output == NINJAMULTI);
   } else if (output == RESPONSE) {
     result = this->EscapeForShell(result, false, false, false);
   }
@@ -79,9 +80,9 @@ static bool cmOutputConverterIsShellOperator(cm::string_view str)
   return (shellOperators.count(str) != 0);
 }
 
-std::string cmOutputConverter::EscapeForShell(cm::string_view str,
-                                              bool makeVars, bool forEcho,
-                                              bool useWatcomQuote) const
+std::string cmOutputConverter::EscapeForShell(
+  cm::string_view str, bool makeVars, bool forEcho, bool useWatcomQuote,
+  bool unescapeNinjaConfiguration) const
 {
   // Do not escape shell operators.
   if (cmOutputConverterIsShellOperator(str)) {
@@ -95,6 +96,9 @@ std::string cmOutputConverter::EscapeForShell(cm::string_view str,
   } else if (!this->LinkScriptShell) {
     flags |= Shell_Flag_Make;
   }
+  if (unescapeNinjaConfiguration) {
+    flags |= Shell_Flag_UnescapeNinjaConfiguration;
+  }
   if (makeVars) {
     flags |= Shell_Flag_AllowMakeVariables;
   }
@@ -511,5 +515,14 @@ std::string cmOutputConverter::Shell__GetArgument(cm::string_view in,
     }
   }
 
+  if (flags & Shell_Flag_UnescapeNinjaConfiguration) {
+    std::string ninjaConfigReplace;
+    if (flags & Shell_Flag_IsUnix) {
+      ninjaConfigReplace += '\\';
+    }
+    ninjaConfigReplace += "$${CONFIGURATION}";
+    cmSystemTools::ReplaceString(out, ninjaConfigReplace, "${CONFIGURATION}");
+  }
+
   return out;
 }

+ 6 - 3
Source/cmOutputConverter.h

@@ -22,6 +22,7 @@ public:
   {
     SHELL,
     WATCOMQUOTE,
+    NINJAMULTI,
     RESPONSE
   };
   std::string ConvertToOutputFormat(cm::string_view source,
@@ -70,12 +71,14 @@ public:
     /** The target shell quoting uses extra single Quotes for Watcom tools.  */
     Shell_Flag_WatcomQuote = (1 << 7),
 
-    Shell_Flag_IsUnix = (1 << 8)
+    Shell_Flag_IsUnix = (1 << 8),
+
+    Shell_Flag_UnescapeNinjaConfiguration = (1 << 9),
   };
 
   std::string EscapeForShell(cm::string_view str, bool makeVars = false,
-                             bool forEcho = false,
-                             bool useWatcomQuote = false) const;
+                             bool forEcho = false, bool useWatcomQuote = false,
+                             bool unescapeNinjaConfiguration = false) const;
 
   static std::string EscapeForCMake(cm::string_view str);
 

+ 10 - 3
Source/cmQtAutoGenInitializer.cxx

@@ -1042,9 +1042,16 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
   }
 
   // Compose command lines
-  cmCustomCommandLines commandLines = cmMakeSingleCommandLine(
-    { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
-      this->AutogenTarget.InfoFile, "$<CONFIGURATION>" });
+  // TODO: Refactor autogen to output a per-config mocs_compilation.cpp instead
+  // of fiddling with the include directories
+  std::vector<std::string> configs;
+  this->GlobalGen->GetQtAutoGenConfigs(configs);
+  cmCustomCommandLines commandLines;
+  for (auto const& config : configs) {
+    commandLines.push_back(cmMakeCommandLine(
+      { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
+        this->AutogenTarget.InfoFile, config }));
+  }
 
   // Use PRE_BUILD on demand
   bool usePRE_BUILD = false;

+ 10 - 0
Source/cmState.cxx

@@ -715,6 +715,16 @@ bool cmState::UseMSYSShell() const
   return this->MSYSShell;
 }
 
+void cmState::SetNinjaMulti(bool ninjaMulti)
+{
+  this->NinjaMulti = ninjaMulti;
+}
+
+bool cmState::UseNinjaMulti() const
+{
+  return this->NinjaMulti;
+}
+
 unsigned int cmState::GetCacheMajorVersion() const
 {
   return this->CacheManager->GetCacheMajorVersion();

+ 3 - 0
Source/cmState.h

@@ -190,6 +190,8 @@ public:
   bool UseNMake() const;
   void SetMSYSShell(bool mSYSShell);
   bool UseMSYSShell() const;
+  void SetNinjaMulti(bool ninjaMulti);
+  bool UseNinjaMulti() const;
 
   unsigned int GetCacheMajorVersion() const;
   unsigned int GetCacheMinorVersion() const;
@@ -245,6 +247,7 @@ private:
   bool MinGWMake = false;
   bool NMake = false;
   bool MSYSShell = false;
+  bool NinjaMulti = false;
   Mode CurrentMode = Unknown;
 };
 

+ 8 - 1
Source/cmTarget.cxx

@@ -176,6 +176,7 @@ public:
   bool IsImportedTarget;
   bool ImportedGloballyVisible;
   bool BuildInterfaceIncludesAppended;
+  bool PerConfig;
   std::set<BT<std::string>> Utilities;
   std::vector<cmCustomCommand> PreBuildCommands;
   std::vector<cmCustomCommand> PreLinkCommands;
@@ -213,7 +214,7 @@ public:
 };
 
 cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
-                   Visibility vis, cmMakefile* mf)
+                   Visibility vis, cmMakefile* mf, bool perConfig)
   : impl(cm::make_unique<cmTargetInternals>())
 {
   assert(mf);
@@ -229,6 +230,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     (vis == VisibilityImported || vis == VisibilityImportedGlobally);
   impl->ImportedGloballyVisible = vis == VisibilityImportedGlobally;
   impl->BuildInterfaceIncludesAppended = false;
+  impl->PerConfig = perConfig;
 
   // Check whether this is a DLL platform.
   impl->IsDLLPlatform =
@@ -1800,6 +1802,11 @@ bool cmTarget::IsImportedGloballyVisible() const
   return impl->ImportedGloballyVisible;
 }
 
+bool cmTarget::IsPerConfig() const
+{
+  return impl->PerConfig;
+}
+
 const char* cmTarget::GetSuffixVariableInternal(
   cmStateEnums::ArtifactType artifact) const
 {

+ 2 - 1
Source/cmTarget.h

@@ -44,7 +44,7 @@ public:
   };
 
   cmTarget(std::string const& name, cmStateEnums::TargetType type,
-           Visibility vis, cmMakefile* mf);
+           Visibility vis, cmMakefile* mf, bool perConfig);
 
   cmTarget(cmTarget const&) = delete;
   cmTarget(cmTarget&&) noexcept;
@@ -186,6 +186,7 @@ public:
 
   bool IsImported() const;
   bool IsImportedGloballyVisible() const;
+  bool IsPerConfig() const;
 
   bool GetMappedConfig(std::string const& desired_config, const char** loc,
                        const char** imp, std::string& suffix) const;

+ 1 - 0
Source/cmake.cxx

@@ -1919,6 +1919,7 @@ void cmake::AddDefaultGenerators()
   this->Generators.push_back(cmGlobalGhsMultiGenerator::NewFactory());
 #  endif
   this->Generators.push_back(cmGlobalNinjaGenerator::NewFactory());
+  this->Generators.push_back(cmGlobalNinjaMultiGenerator::NewFactory());
 #endif
 #if defined(CMAKE_USE_WMAKE)
   this->Generators.push_back(cmGlobalWatcomWMakeGenerator::NewFactory());

+ 4 - 3
Tests/CMakeLists.txt

@@ -1134,8 +1134,8 @@ ${CMake_SOURCE_DIR}/Utilities/Release/push.bash --dir dev -- '${CMake_BUILD_NIGH
 
       foreach(CPackDEBConfiguration IN LISTS DEB_CONFIGURATIONS_TO_TEST)
         set(CPackRun_CPackDEBConfiguration "-DCPackDEBConfiguration=${CPackDEBConfiguration}")
-        add_test(${DEB_TEST_NAMES}-${CPackDEBConfiguration}
-          ${CMAKE_CTEST_COMMAND} -C \${CTEST_CONFIGURATION_TYPE}
+        add_test(NAME ${DEB_TEST_NAMES}-${CPackDEBConfiguration} COMMAND
+          ${CMAKE_CTEST_COMMAND} -C $<CONFIG>
           --build-and-test
               "${CMake_SOURCE_DIR}/Tests/${DEB_TEST_NAMES}"
               "${CMake_BINARY_DIR}/Tests/${DEB_TEST_NAMES}/build${CPackGen}-${CPackDEBConfiguration}"
@@ -1152,6 +1152,7 @@ ${CMake_SOURCE_DIR}/Utilities/Release/push.bash --dir dev -- '${CMake_BUILD_NIGH
               "-D${DEB_TEST_NAMES}_BINARY_DIR:PATH=${CMake_BINARY_DIR}/Tests/${DEB_TEST_NAMES}/build${CPackGen}-${CPackDEBConfiguration}"
               "${CPackRun_CPackGen}"
               "${CPackRun_CPackDEBConfiguration}"
+              "-DCONFIG=$<CONFIG>"
               -P "${CMake_SOURCE_DIR}/Tests/${DEB_TEST_NAMES}/RunCPackVerifyResult-${CPackDEBConfiguration}.cmake")
           list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${DEB_TEST_NAMES}/build${CPackGen}-${CPackDEBConfiguration}")
       endforeach()
@@ -1868,7 +1869,7 @@ ${CMake_SOURCE_DIR}/Utilities/Release/push.bash --dir dev -- '${CMake_BUILD_NIGH
   ADD_TEST_MACRO(CheckCompilerRelatedVariables CheckCompilerRelatedVariables)
 
   if("${CMAKE_GENERATOR}" MATCHES "Makefile" OR
-     "${CMAKE_GENERATOR}" MATCHES "Ninja")
+     "${CMAKE_GENERATOR}" MATCHES "^Ninja$")
     add_test(MakeClean ${CMAKE_CTEST_COMMAND}
       --build-and-test
       "${CMake_SOURCE_DIR}/Tests/MakeClean"

+ 1 - 1
Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake

@@ -45,7 +45,7 @@ function(run_cpack output_expected_file CPack_output_parent CPack_error_parent)
 
   message("config_args = ${run_cpack_deb_CONFIG_ARGS}")
   message("config_verbose = ${run_cpack_deb_CONFIG_VERBOSE}")
-  execute_process(COMMAND ${CMAKE_CPACK_COMMAND} ${run_cpack_deb_CONFIG_VERBOSE} -G ${CPackGen} ${run_cpack_deb_CONFIG_ARGS}
+  execute_process(COMMAND ${CMAKE_CPACK_COMMAND} ${run_cpack_deb_CONFIG_VERBOSE} -G ${CPackGen} -C "${CONFIG}" ${run_cpack_deb_CONFIG_ARGS}
       RESULT_VARIABLE CPack_result
       OUTPUT_VARIABLE CPack_output
       ERROR_VARIABLE CPack_error

+ 1 - 1
Tests/FortranModules/Executable/CMakeLists.txt

@@ -1,6 +1,6 @@
 include_directories(${Library_MODDIR})
 include_directories(${External_BINARY_DIR})
-link_directories(${External_BINARY_DIR})
+link_directories(${External_BINARY_DIR}/${CMAKE_CFG_INTDIR})
 
 add_executable(subdir_exe2 main.f90)
 target_link_libraries(subdir_exe2 subdir_mods subdir_mods2)

+ 1 - 1
Tests/IncludeDirectories/CMakeLists.txt

@@ -94,6 +94,6 @@ if (NOT incs STREQUAL ";/one/two")
   message(SEND_ERROR "Empty include_directories entry was not ignored.")
 endif()
 
-if(NOT CMAKE_GENERATOR STREQUAL Xcode AND NOT CMAKE_GENERATOR STREQUAL Ninja)
+if(NOT CMAKE_GENERATOR STREQUAL "Xcode" AND NOT CMAKE_GENERATOR MATCHES "Ninja")
   add_subdirectory(CMP0021)
 endif()

+ 7 - 1
Tests/InterfaceLibrary/CMakeLists.txt

@@ -3,6 +3,12 @@ cmake_minimum_required(VERSION 2.8)
 
 project(InterfaceLibrary)
 
+set(cfg_dir)
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(cfg_dir /$<CONFIG>)
+endif()
+
 add_library(iface_nodepends INTERFACE)
 target_compile_definitions(iface_nodepends INTERFACE IFACE_DEFINE)
 
@@ -32,7 +38,7 @@ add_library(item_iface INTERFACE IMPORTED)
 set_property(TARGET item_iface PROPERTY IMPORTED_LIBNAME item_real)
 add_dependencies(item_iface item_real)
 get_property(item_iface_dependencies TARGET item_iface PROPERTY MANUALLY_ADDED_DEPENDENCIES)
-link_directories(${CMAKE_CURRENT_BINARY_DIR})
+link_directories(${CMAKE_CURRENT_BINARY_DIR}${cfg_dir})
 
 add_executable(InterfaceLibrary definetestexe.cpp)
 target_link_libraries(InterfaceLibrary

+ 10 - 4
Tests/LinkDirectory/External/CMakeLists.txt

@@ -2,13 +2,19 @@ cmake_minimum_required(VERSION 2.8)
 project(LinkDirectoryExternal C)
 
 
+set(cfg_dir)
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(cfg_dir /$<CONFIG>)
+endif()
+
 add_executable(myexe2 myexe.c)
 set_property(TARGET myexe2 PROPERTY OUTPUT_NAME LinkDirectory2)
-target_link_directories(myexe2 PRIVATE lib "${CMAKE_CURRENT_SOURCE_DIR}/../lib")
+target_link_directories(myexe2 PRIVATE lib${cfg_dir} "${CMAKE_CURRENT_SOURCE_DIR}/../lib${cfg_dir}")
 target_link_libraries(myexe2 PRIVATE mylibA mylibB)
 
 add_library (mylibs INTERFACE)
-target_link_directories(mylibs INTERFACE lib "${CMAKE_CURRENT_SOURCE_DIR}/../lib")
+target_link_directories(mylibs INTERFACE lib${cfg_dir} "${CMAKE_CURRENT_SOURCE_DIR}/../lib${cfg_dir}")
 target_link_libraries(mylibs INTERFACE mylibA mylibB)
 add_executable(myexe3 myexe.c)
 set_property(TARGET myexe3 PROPERTY OUTPUT_NAME LinkDirectory3)
@@ -17,11 +23,11 @@ target_link_libraries(myexe3 PRIVATE mylibs)
 
 # Test CMP0015 OLD behavior: -L../lib
 cmake_policy(SET CMP0015 OLD)
-link_directories(../lib)
+link_directories(../lib${cfg_dir})
 
 # Test CMP0015 NEW behavior: -L${CMAKE_CURRENT_SOURCE_DIR}/lib
 cmake_policy(SET CMP0015 NEW)
-link_directories(lib)
+link_directories(lib${cfg_dir})
 
 add_executable(myexe myexe.c)
 set_property(TARGET myexe PROPERTY OUTPUT_NAME LinkDirectory)

+ 2 - 2
Tests/OutDir/CMakeLists.txt

@@ -20,7 +20,7 @@ set(top "${OutDir_BINARY_DIR}")
 foreach(config ${configs})
   foreach(type archive runtime library)
     string(TOUPPER "${type}" TYPE)
-    set(CMAKE_${TYPE}_OUTPUT_DIRECTORY_${config} "${top}/${type}")
+    set(CMAKE_${TYPE}_OUTPUT_DIRECTORY_${config} "${top}/${type}/$<CONFIG>")
     file(REMOVE_RECURSE "${top}/${type}")
   endforeach()
 endforeach()
@@ -29,7 +29,7 @@ add_subdirectory(../COnly COnly)
 
 add_custom_command(
   OUTPUT OutDir.h
-  COMMAND ${CMAKE_COMMAND} -Dtop=${top} -P ${OutDir_SOURCE_DIR}/OutDir.cmake
+  COMMAND ${CMAKE_COMMAND} -Dtop=${top} -Dcfg_dir=$<CONFIG> -P ${OutDir_SOURCE_DIR}/OutDir.cmake
   DEPENDS COnly ${OutDir_SOURCE_DIR}/OutDir.cmake
   )
 include_directories(${top})

+ 3 - 3
Tests/OutDir/OutDir.cmake

@@ -3,17 +3,17 @@ set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".a" ".so" ".sl" ".dylib" ".dll.a")
 
 find_library(TESTC1_LIB
   NAMES testc1 testc1_test_debug_postfix
-  PATHS ${top}/archive
+  PATHS ${top}/archive/${cfg_dir}
   NO_DEFAULT_PATH)
 
 find_library(TESTC2_LIB
   NAMES testc2 testc2_test_debug_postfix
-  PATHS ${top}/archive ${top}/library
+  PATHS ${top}/archive/${cfg_dir} ${top}/library/${cfg_dir}
   NO_DEFAULT_PATH)
 
 find_program(CONLY_EXE
   NAMES COnly
-  PATHS ${top}/runtime
+  PATHS ${top}/runtime/${cfg_dir}
   NO_DEFAULT_PATH)
 
 file(RELATIVE_PATH TESTC1_LIB_FILE "${top}" "${TESTC1_LIB}")

+ 1 - 1
Tests/RunCMake/CMakeLists.txt

@@ -125,7 +125,7 @@ endif()
 if(CMAKE_GENERATOR MATCHES "Make")
   add_RunCMake_test(Make -DMAKE_IS_GNU=${MAKE_IS_GNU})
 endif()
-if(CMAKE_GENERATOR STREQUAL "Ninja")
+if(CMAKE_GENERATOR MATCHES "Ninja")
   set(Ninja_ARGS
     -DCMAKE_C_OUTPUT_EXTENSION=${CMAKE_C_OUTPUT_EXTENSION}
     -DCMAKE_SHARED_LIBRARY_PREFIX=${CMAKE_SHARED_LIBRARY_PREFIX}

+ 1 - 1
Tests/RunCMake/CPack/CPackTestHelpers.cmake

@@ -75,7 +75,7 @@ function(run_cpack_test_common_ TEST_NAME types build SUBTEST_SUFFIX source PACK
     if(package_target)
       set(cpack_command_ ${CMAKE_COMMAND} --build "${RunCMake_TEST_BINARY_DIR}" --target package)
     else()
-      set(cpack_command_ ${CMAKE_CPACK_COMMAND} ${pack_params_})
+      set(cpack_command_ ${CMAKE_CPACK_COMMAND} ${pack_params_} -C Debug)
     endif()
 
     # execute cpack

+ 2 - 1
Tests/RunCMake/CPack/tests/EXTERNAL/expected-json-1.0.txt

@@ -1,5 +1,6 @@
 ^\{
-  "componentGroups" :[ ]
+  ("buildConfig" : "Debug",
+)?  "componentGroups" :[ ]
   \{
     "f12" :[ ]
     \{

+ 1 - 1
Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake

@@ -9,7 +9,7 @@ function(run_compiler_launcher lang)
   run_cmake(${lang})
 
   set(RunCMake_TEST_OUTPUT_MERGE 1)
-  if("${RunCMake_GENERATOR}" STREQUAL "Ninja")
+  if("${RunCMake_GENERATOR}" MATCHES "Ninja")
     set(verbose_args -- -v)
   endif()
   run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build . ${verbose_args})

+ 1 - 1
Tests/RunCMake/FileAPI/check_index.py

@@ -113,7 +113,7 @@ def check_cmake_generator(g):
         assert is_string(g["platform"])
     else:
         assert sorted(g.keys()) == ["multiConfig", "name"]
-    assert is_bool(g["multiConfig"], matches(name, "^(Visual Studio |Xcode$)"))
+    assert is_bool(g["multiConfig"], matches(name, "^(Visual Studio |Xcode$|Ninja Multi-Config$)"))
 
 def check_index_object(indexEntry, kind, major, minor, check):
     assert is_dict(indexEntry)

+ 12 - 12
Tests/RunCMake/FileAPI/codemodel-v2-check.py

@@ -621,12 +621,12 @@ def gen_check_directories(c, g):
         },
     ]
 
-    if matches(g, "^Visual Studio "):
+    if matches(g["name"], "^Visual Studio "):
         for e in expected:
             if e["parentSource"] is not None:
                 e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^ZERO_CHECK"), e["targetIds"])
 
-    elif g == "Xcode":
+    elif g["name"] == "Xcode":
         if ';' in os.environ.get("CMAKE_OSX_ARCHITECTURES", ""):
             for e in expected:
                 e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^(link_imported_object_exe)"), e["targetIds"])
@@ -5087,13 +5087,13 @@ def gen_check_targets(c, g, inSource):
                 for s in e["sources"]:
                     s["path"] = s["path"].replace("^.*/Tests/RunCMake/FileAPI/", "^", 1)
             if e["sourceGroups"] is not None:
-                for g in e["sourceGroups"]:
-                    g["sourcePaths"] = [p.replace("^.*/Tests/RunCMake/FileAPI/", "^", 1) for p in g["sourcePaths"]]
+                for group in e["sourceGroups"]:
+                    group["sourcePaths"] = [p.replace("^.*/Tests/RunCMake/FileAPI/", "^", 1) for p in group["sourcePaths"]]
             if e["compileGroups"] is not None:
-                for g in e["compileGroups"]:
-                    g["sourcePaths"] = [p.replace("^.*/Tests/RunCMake/FileAPI/", "^", 1) for p in g["sourcePaths"]]
+                for group in e["compileGroups"]:
+                    group["sourcePaths"] = [p.replace("^.*/Tests/RunCMake/FileAPI/", "^", 1) for p in group["sourcePaths"]]
 
-    if matches(g, "^Visual Studio "):
+    if matches(g["name"], "^Visual Studio "):
         expected = filter_list(lambda e: e["name"] not in ("ZERO_CHECK") or e["id"] == "^ZERO_CHECK::@6890427a1f51a3e7e1df$", expected)
         for e in expected:
             if e["type"] == "UTILITY":
@@ -5130,7 +5130,7 @@ def gen_check_targets(c, g, inSource):
                     if matches(d["id"], "^\\^ZERO_CHECK::@"):
                         d["id"] = "^ZERO_CHECK::@6890427a1f51a3e7e1df$"
 
-    elif g == "Xcode":
+    elif g["name"] == "Xcode":
         if ';' in os.environ.get("CMAKE_OSX_ARCHITECTURES", ""):
             expected = filter_list(lambda e: e["name"] not in ("link_imported_object_exe"), expected)
             for e in expected:
@@ -5286,12 +5286,12 @@ def gen_check_projects(c, g):
         },
     ]
 
-    if matches(g, "^Visual Studio "):
+    if matches(g["name"], "^Visual Studio "):
         for e in expected:
             if e["parentName"] is not None:
                 e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^ZERO_CHECK"), e["targetIds"])
 
-    elif g == "Xcode":
+    elif g["name"] == "Xcode":
         if ';' in os.environ.get("CMAKE_OSX_ARCHITECTURES", ""):
             for e in expected:
                 e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^(link_imported_object_exe)"), e["targetIds"])
@@ -5327,7 +5327,7 @@ def check_object_codemodel(g):
 
         inSource = os.path.dirname(o["paths"]["build"]) == o["paths"]["source"]
 
-        if matches(g, "^(Visual Studio |Xcode$)"):
+        if g["multiConfig"]:
             assert sorted([c["name"] for c in o["configurations"]]) == ["Debug", "MinSizeRel", "RelWithDebInfo", "Release"]
         else:
             assert len(o["configurations"]) == 1
@@ -5339,4 +5339,4 @@ def check_object_codemodel(g):
 
 assert is_dict(index)
 assert sorted(index.keys()) == ["cmake", "objects", "reply"]
-check_objects(index["objects"], index["cmake"]["generator"]["name"])
+check_objects(index["objects"], index["cmake"]["generator"])

+ 3 - 0
Tests/RunCMake/Ninja/RunCMakeTest.cmake

@@ -1,5 +1,8 @@
 include(RunCMake)
 
+set(RunCMake_GENERATOR "Ninja")
+set(RunCMake_GENERATOR_IS_MULTI_CONFIG 0)
+
 # Detect ninja version so we know what tests can be supported.
 execute_process(
   COMMAND "${RunCMake_MAKE_PROGRAM}" --version

+ 12 - 5
Tests/RunCMake/RuntimePath/Relative.cmake

@@ -1,5 +1,12 @@
 enable_language(C)
 
+set(cfg_up)
+set(cfg_slash /)
+if(cfg_dir)
+  set(cfg_up /..)
+  set(cfg_slash)
+endif()
+
 if(NOT CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN)
   if(CMAKE_C_PLATFORM_ID STREQUAL "Linux")
     # Sanity check for platform that is definitely known to support $ORIGIN.
@@ -45,25 +52,25 @@ CheckRpath(main "\$ORIGIN")
 add_executable(main-norel main.c)
 target_link_libraries(main-norel utils)
 set_property(TARGET main-norel PROPERTY BUILD_RPATH_USE_ORIGIN OFF)
-CheckRpath(main-norel "${CMAKE_CURRENT_BINARY_DIR}")
+CheckRpath(main-norel "${CMAKE_CURRENT_BINARY_DIR}${cfg_dir}")
 
 add_executable(mainsub main.c)
 target_link_libraries(mainsub utils)
 set_property(TARGET mainsub PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
-CheckRpath(mainsub "\$ORIGIN/../")
+CheckRpath(mainsub "\$ORIGIN${cfg_up}/..${cfg_dir}${cfg_slash}")
 
 add_executable(main-sub main.c)
 target_link_libraries(main-sub utils-sub)
-CheckRpath(main-sub "\$ORIGIN/libs")
+CheckRpath(main-sub "\$ORIGIN${cfg_up}/libs${cfg_dir}")
 
 add_executable(mainsub-sub main.c)
 target_link_libraries(mainsub-sub utils-sub)
 set_property(TARGET mainsub-sub PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
-CheckRpath(mainsub-sub "\$ORIGIN/../libs")
+CheckRpath(mainsub-sub "\$ORIGIN${cfg_up}/../libs${cfg_dir}")
 
 if(externDir)
   # Binaries linking to libraries outside the build tree should have an absolute RPATH.
   add_executable(main-extern main.c)
   target_link_libraries(main-extern utils-extern)
-  CheckRpath(main-extern "${externDir}")
+  CheckRpath(main-extern "${externDir}${cfg_dir}")
 endif()

+ 9 - 3
Tests/RunCMake/RuntimePath/RunCMakeTest.cmake

@@ -6,7 +6,9 @@ function(run_RuntimePath name)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
   set(RunCMake_TEST_NO_CLEAN 1)
   if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
-    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
+    set(RunCMake_TEST_OPTIONS -Dcfg_dir= -DCMAKE_BUILD_TYPE=Debug)
+  else()
+    set(RunCMake_TEST_OPTIONS -Dcfg_dir=/$<CONFIG>)
   endif()
   file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
@@ -14,12 +16,16 @@ function(run_RuntimePath name)
   run_cmake_command(${name}-build ${CMAKE_COMMAND} --build . --config Debug)
 endfunction()
 
+set(cfg_dir)
+if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  set(cfg_dir /Debug)
+endif()
+
 run_RuntimePath(SymlinkImplicit)
 run_cmake_command(SymlinkImplicitCheck
-  ${CMAKE_COMMAND} -Ddir=${RunCMake_BINARY_DIR}/SymlinkImplicit-build -P ${RunCMake_SOURCE_DIR}/SymlinkImplicitCheck.cmake)
+  ${CMAKE_COMMAND} -Ddir=${RunCMake_BINARY_DIR}/SymlinkImplicit-build -Dcfg_dir=${cfg_dir} -P ${RunCMake_SOURCE_DIR}/SymlinkImplicitCheck.cmake)
 
 run_RuntimePath(Relative)
-# FIXME: Run RelativeCheck (appears to be broken currently)
 
 run_RuntimePath(Genex)
 run_cmake_command(GenexCheck

+ 1 - 1
Tests/RunCMake/RuntimePath/SymlinkImplicitCheck.cmake

@@ -1,2 +1,2 @@
-file(COPY ${dir}/bin/exe DESTINATION ${dir})
+file(COPY ${dir}/bin${cfg_dir}/exe DESTINATION ${dir})
 file(RPATH_CHANGE FILE "${dir}/exe" OLD_RPATH "${dir}/libLink" NEW_RPATH "old-should-not-exist")

+ 1 - 1
Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake

@@ -11,7 +11,7 @@ string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_LIBRARY "${CMAK
 
 add_library(example SHARED LinkOptionsLib.c)
 # use LAUNCH facility to dump linker command
-set_property(TARGET example PROPERTY RULE_LAUNCH_LINK "\"${CMAKE_CURRENT_BINARY_DIR}/dump${CMAKE_EXECUTABLE_SUFFIX}\"")
+set_property(TARGET example PROPERTY RULE_LAUNCH_LINK "\"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/dump${CMAKE_EXECUTABLE_SUFFIX}\"")
 
 add_dependencies (example dump)
 

+ 2 - 1
Tests/RunCMake/ctest_labels_for_subprojects/CTestScriptVariableCommandLine-stderr.txt

@@ -1 +1,2 @@
-Unable to find executable:.*MyThirdPartyDependency/src/thirdparty
+Unable to find executable:.*MyThirdPartyDependency/src(/[^/
+]+)?/thirdparty

+ 2 - 2
Tests/RunCMake/file/RunCMakeTest.cmake

@@ -69,7 +69,7 @@ if(NOT WIN32 OR CYGWIN)
   run_cmake(INSTALL-FOLLOW_SYMLINK_CHAIN)
 endif()
 
-if(RunCMake_GENERATOR STREQUAL "Ninja")
+if(RunCMake_GENERATOR MATCHES "Ninja")
   # Detect ninja version so we know what tests can be supported.
   execute_process(
     COMMAND "${RunCMake_MAKE_PROGRAM}" --version
@@ -90,7 +90,7 @@ if(RunCMake_GENERATOR STREQUAL "Ninja")
   endif()
 endif()
 
-if(RunCMake_GENERATOR STREQUAL "Ninja" AND "${ninja_version}" VERSION_LESS 1.8)
+if(RunCMake_GENERATOR MATCHES "Ninja" AND "${ninja_version}" VERSION_LESS 1.8)
   run_cmake(GLOB_RECURSE-warn-CONFIGURE_DEPENDS-ninja-version)
 else()
   run_cmake(GLOB-warn-CONFIGURE_DEPENDS-late)

+ 1 - 1
Tests/RunCMake/get_property/RunCMakeTest.cmake

@@ -27,7 +27,7 @@ run_cmake(NoCache)
 # don't rely on RunCMake_GENERATOR_IS_MULTI_CONFIG being set correctly
 # and instead explicitly check for a match against those generators we
 # expect to be multi-config
-if(RunCMake_GENERATOR MATCHES "Visual Studio|Xcode")
+if(RunCMake_GENERATOR MATCHES "Visual Studio|Xcode|Ninja Multi-Config")
   run_cmake(IsMultiConfig)
 else()
   run_cmake(NotMultiConfig)

+ 2 - 2
Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath-stderr.txt

@@ -1,4 +1,4 @@
-^CMake Warning \(dev\) at TARGETS-FILE_RPATH_CHANGE-new_rpath\.cmake:[0-9]+ \(install\):
+CMake Warning \(dev\) at TARGETS-FILE_RPATH_CHANGE-new_rpath\.cmake:[0-9]+ \(install\):
   Policy CMP0095 is not set: RPATH entries are properly escaped in the
   intermediary CMake install script\.  Run "cmake --help-policy CMP0095" for
   policy details\.  Use the cmake_policy command to set the policy and
@@ -20,4 +20,4 @@ CMake Warning \(dev\) at TARGETS-FILE_RPATH_CHANGE-new_rpath\.cmake:[0-9]+ \(ins
   intermediary cmake_install\.cmake script\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
-This warning is for project developers\.  Use -Wno-dev to suppress it\.$
+This warning is for project developers\.  Use -Wno-dev to suppress it\.

+ 9 - 2
Tests/RunCMake/target_link_options/LINKER_expansion.cmake

@@ -1,6 +1,13 @@
 
 enable_language(C)
 
+set(cfg_dir)
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(cfg_dir /Debug)
+endif()
+set(DUMP_EXE "${CMAKE_CURRENT_BINARY_DIR}${cfg_dir}/dump${CMAKE_EXECUTABLE_SUFFIX}")
+
 add_executable(dump dump.c)
 
 # ensure no temp file will be used
@@ -13,7 +20,7 @@ add_library(linker SHARED LinkOptionsLib.c)
 target_link_options(linker PRIVATE "LINKER:-foo,bar")
 
 # use LAUNCH facility to dump linker command
-set_property(TARGET linker PROPERTY RULE_LAUNCH_LINK "\"${CMAKE_CURRENT_BINARY_DIR}/dump${CMAKE_EXECUTABLE_SUFFIX}\"")
+set_property(TARGET linker PROPERTY RULE_LAUNCH_LINK "\"${DUMP_EXE}\"")
 
 add_dependencies (linker dump)
 
@@ -23,7 +30,7 @@ add_library(linker_shell SHARED LinkOptionsLib.c)
 target_link_options(linker_shell PRIVATE "LINKER:SHELL:-foo bar")
 
 # use LAUNCH facility to dump linker command
-set_property(TARGET linker_shell PROPERTY RULE_LAUNCH_LINK "\"${CMAKE_CURRENT_BINARY_DIR}/dump${CMAKE_EXECUTABLE_SUFFIX}\"")
+set_property(TARGET linker_shell PROPERTY RULE_LAUNCH_LINK "\"${DUMP_EXE}\"")
 
 add_dependencies (linker_shell dump)