Răsfoiți Sursa

Merge topic 'ninja_build_class'

7d9e66a405 Ninja: Remove non cmNinjaBuild based WriteBuild method
ccf9507956 Ninja: Use cmNinjaBuild class for WriteBuild
df06c8d792 Ninja: Use cmNinjaBuild class for WriteBuild
409922f695 Ninja: Use cmNinjaBuild class for WriteBuild
7fd3811400 Ninja: Use cmNinjaBuild class for WriteBuild
834ec4ebfe Ninja: Use cmNinjaBuild class for WriteBuild
465d6d7f9b Ninja: Use cmNinjaBuild class for WriteBuild
4c9e99e1f9 Ninja: Use cmNinjaBuild class for WriteBuild
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !3403
Brad King 6 ani în urmă
părinte
comite
73472408c5

+ 251 - 257
Source/cmGlobalNinjaGenerator.cxx

@@ -127,113 +127,107 @@ std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
   return result;
 }
 
-void cmGlobalNinjaGenerator::WriteBuild(
-  std::ostream& os, const std::string& comment, const std::string& rule,
-  const cmNinjaDeps& outputs, const cmNinjaDeps& implicitOuts,
-  const cmNinjaDeps& explicitDeps, const cmNinjaDeps& implicitDeps,
-  const cmNinjaDeps& orderOnlyDeps, const cmNinjaVars& variables,
-  const std::string& rspfile, int cmdLineLimit, bool* usedResponseFile)
+void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
+                                        cmNinjaBuild const& build,
+                                        int cmdLineLimit,
+                                        bool* usedResponseFile)
 {
   // Make sure there is a rule.
-  if (rule.empty()) {
+  if (build.Rule.empty()) {
     cmSystemTools::Error("No rule for WriteBuild! called with comment: " +
-                         comment);
+                         build.Comment);
     return;
   }
 
   // Make sure there is at least one output file.
-  if (outputs.empty()) {
+  if (build.Outputs.empty()) {
     cmSystemTools::Error(
-      "No output files for WriteBuild! called with comment: " + comment);
+      "No output files for WriteBuild! called with comment: " + build.Comment);
     return;
   }
 
-  cmGlobalNinjaGenerator::WriteComment(os, comment);
-
-  std::string arguments;
-
-  // TODO: Better formatting for when there are multiple input/output files.
+  cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
 
-  // Write explicit dependencies.
-  for (std::string const& explicitDep : explicitDeps) {
-    arguments += " " + EncodePath(explicitDep);
-  }
-
-  // Write implicit dependencies.
-  if (!implicitDeps.empty()) {
-    arguments += " |";
-    for (std::string const& implicitDep : implicitDeps) {
-      arguments += " " + EncodePath(implicitDep);
+  // Write output files.
+  std::string buildStr("build");
+  {
+    // Write explicit outputs
+    for (std::string const& output : build.Outputs) {
+      buildStr += " " + EncodePath(output);
+      if (this->ComputingUnknownDependencies) {
+        this->CombinedBuildOutputs.insert(output);
+      }
     }
-  }
-
-  // Write order-only dependencies.
-  if (!orderOnlyDeps.empty()) {
-    arguments += " ||";
-    for (std::string const& orderOnlyDep : orderOnlyDeps) {
-      arguments += " " + EncodePath(orderOnlyDep);
+    // Write implicit outputs
+    if (!build.ImplicitOuts.empty()) {
+      buildStr += " |";
+      for (std::string const& implicitOut : build.ImplicitOuts) {
+        buildStr += " " + EncodePath(implicitOut);
+      }
     }
+    buildStr += ":";
+
+    // Write the rule.
+    buildStr += " ";
+    buildStr += build.Rule;
   }
 
-  arguments += "\n";
+  std::string arguments;
+  {
+    // TODO: Better formatting for when there are multiple input/output files.
 
-  std::string build;
+    // Write explicit dependencies.
+    for (std::string const& explicitDep : build.ExplicitDeps) {
+      arguments += " " + EncodePath(explicitDep);
+    }
 
-  // Write outputs files.
-  build += "build";
-  for (std::string const& output : outputs) {
-    build += " " + EncodePath(output);
-    if (this->ComputingUnknownDependencies) {
-      this->CombinedBuildOutputs.insert(output);
+    // Write implicit dependencies.
+    if (!build.ImplicitDeps.empty()) {
+      arguments += " |";
+      for (std::string const& implicitDep : build.ImplicitDeps) {
+        arguments += " " + EncodePath(implicitDep);
+      }
     }
-  }
-  if (!implicitOuts.empty()) {
-    build += " |";
-    for (std::string const& implicitOut : implicitOuts) {
-      build += " " + EncodePath(implicitOut);
+
+    // Write order-only dependencies.
+    if (!build.OrderOnlyDeps.empty()) {
+      arguments += " ||";
+      for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
+        arguments += " " + EncodePath(orderOnlyDep);
+      }
     }
-  }
-  build += ":";
 
-  // Write the rule.
-  build += " " + rule;
+    arguments += "\n";
+  }
 
   // Write the variables bound to this build statement.
-  std::ostringstream variable_assignments;
-  for (auto const& variable : variables) {
-    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, variable.first,
-                                          variable.second, "", 1);
-  }
+  std::string assignments;
+  {
+    std::ostringstream variable_assignments;
+    for (auto const& variable : build.Variables) {
+      cmGlobalNinjaGenerator::WriteVariable(
+        variable_assignments, variable.first, variable.second, "", 1);
+    }
 
-  // check if a response file rule should be used
-  std::string buildstr = build;
-  std::string assignments = variable_assignments.str();
-  bool useResponseFile = false;
-  if (cmdLineLimit < 0 ||
-      (cmdLineLimit > 0 &&
-       (arguments.size() + buildstr.size() + assignments.size() + 1000) >
-         static_cast<size_t>(cmdLineLimit))) {
-    variable_assignments.str(std::string());
-    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
-                                          rspfile, "", 1);
-    assignments += variable_assignments.str();
-    useResponseFile = true;
-  }
-  if (usedResponseFile) {
-    *usedResponseFile = useResponseFile;
+    // check if a response file rule should be used
+    assignments = variable_assignments.str();
+    bool useResponseFile = false;
+    if (cmdLineLimit < 0 ||
+        (cmdLineLimit > 0 &&
+         (arguments.size() + buildStr.size() + assignments.size() + 1000) >
+           static_cast<size_t>(cmdLineLimit))) {
+      variable_assignments.str(std::string());
+      cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
+                                            build.RspFile, "", 1);
+      assignments += variable_assignments.str();
+      useResponseFile = true;
+    }
+    if (usedResponseFile) {
+      *usedResponseFile = useResponseFile;
+    }
   }
 
-  os << buildstr << arguments << assignments;
-}
-
-void cmGlobalNinjaGenerator::WritePhonyBuild(
-  std::ostream& os, const std::string& comment, const cmNinjaDeps& outputs,
-  const cmNinjaDeps& explicitDeps, const cmNinjaDeps& implicitDeps,
-  const cmNinjaDeps& orderOnlyDeps, const cmNinjaVars& variables)
-{
-  this->WriteBuild(os, comment, "phony", outputs,
-                   /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
-                   orderOnlyDeps, variables);
+  os << buildStr << arguments << assignments << "\n";
 }
 
 void cmGlobalNinjaGenerator::AddCustomCommandRule()
@@ -249,40 +243,47 @@ 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& deps,
-  const cmNinjaDeps& orderOnly)
+  const cmNinjaDeps& outputs, const cmNinjaDeps& explicitDeps,
+  const cmNinjaDeps& orderOnlyDeps)
 {
-  std::string cmd = command; // NOLINT(*)
-#ifdef _WIN32
-  if (cmd.empty())
-    // TODO Shouldn't an empty command be handled by ninja?
-    cmd = "cmd.exe /c";
-#endif
-
   this->AddCustomCommandRule();
 
-  cmNinjaVars vars;
-  vars["COMMAND"] = cmd;
-  vars["DESC"] = EncodeLiteral(description);
-  if (restat) {
-    vars["restat"] = "1";
-  }
-  if (uses_terminal && SupportsConsolePool()) {
-    vars["pool"] = "console";
-  } else if (!job_pool.empty()) {
-    vars["pool"] = job_pool;
-  }
-  if (!depfile.empty()) {
-    vars["depfile"] = depfile;
+  {
+    cmNinjaBuild build("CUSTOM_COMMAND");
+    build.Comment = comment;
+    build.Outputs = outputs;
+    build.ExplicitDeps = explicitDeps;
+    build.OrderOnlyDeps = orderOnlyDeps;
+
+    cmNinjaVars& vars = build.Variables;
+    {
+      std::string cmd = command; // NOLINT(*)
+#ifdef _WIN32
+      if (cmd.empty())
+        // TODO Shouldn't an empty command be handled by ninja?
+        cmd = "cmd.exe /c";
+#endif
+      vars["COMMAND"] = std::move(cmd);
+    }
+    vars["DESC"] = EncodeLiteral(description);
+    if (restat) {
+      vars["restat"] = "1";
+    }
+    if (uses_terminal && SupportsConsolePool()) {
+      vars["pool"] = "console";
+    } else if (!job_pool.empty()) {
+      vars["pool"] = job_pool;
+    }
+    if (!depfile.empty()) {
+      vars["depfile"] = depfile;
+    }
+    this->WriteBuild(*this->BuildFileStream, build);
   }
-  this->WriteBuild(*this->BuildFileStream, comment, "CUSTOM_COMMAND", outputs,
-                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
-                   orderOnly, vars);
 
   if (this->ComputingUnknownDependencies) {
     // we need to track every dependency that comes in, since we are trying
     // to find dependencies that are side effects of build commands
-    for (std::string const& dep : deps) {
+    for (std::string const& dep : explicitDeps) {
       this->CombinedCustomCommandExplicitDependencies.insert(dep);
     }
   }
@@ -297,20 +298,16 @@ void cmGlobalNinjaGenerator::AddMacOSXContentRule()
   this->AddRule(rule);
 }
 
-void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(const std::string& input,
-                                                     const std::string& output)
+void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
+                                                     std::string output)
 {
   this->AddMacOSXContentRule();
-
-  cmNinjaDeps outputs;
-  outputs.push_back(output);
-  cmNinjaDeps deps;
-  deps.push_back(input);
-  cmNinjaVars vars;
-
-  this->WriteBuild(*this->BuildFileStream, "", "COPY_OSX_CONTENT", outputs,
-                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
-                   cmNinjaDeps(), cmNinjaVars());
+  {
+    cmNinjaBuild build("COPY_OSX_CONTENT");
+    build.Outputs.push_back(std::move(output));
+    build.ExplicitDeps.push_back(std::move(input));
+    this->WriteBuild(*this->BuildFileStream, build);
+  }
 }
 
 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
@@ -1083,6 +1080,8 @@ void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
   cmGlobalNinjaGenerator::WriteDivider(os);
   os << "# Target aliases.\n\n";
 
+  cmNinjaBuild build("phony");
+  build.Outputs.emplace_back("");
   for (auto const& ta : TargetAliases) {
     // Don't write ambiguous aliases.
     if (!ta.second) {
@@ -1095,10 +1094,13 @@ void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
       continue;
     }
 
-    cmNinjaDeps deps;
-    this->AppendTargetOutputs(ta.second, deps);
-
-    this->WritePhonyBuild(os, "", cmNinjaDeps(1, ta.first), deps);
+    // Outputs
+    build.Outputs[0] = ta.first;
+    // Explicit depdendencies
+    build.ExplicitDeps.clear();
+    this->AppendTargetOutputs(ta.second, build.ExplicitDeps);
+    // Write
+    this->WriteBuild(os, build);
   }
 }
 
@@ -1107,13 +1109,22 @@ void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
   cmGlobalNinjaGenerator::WriteDivider(os);
   os << "# Folder targets.\n\n";
 
+  std::string const& rootBinaryDir =
+    this->LocalGenerators[0]->GetBinaryDirectory();
+
   std::map<std::string, cmNinjaDeps> targetsPerFolder;
   for (cmLocalGenerator const* lg : this->LocalGenerators) {
-    const std::string currentBinaryFolder(
+    std::string const& currentBinaryFolder(
       lg->GetStateSnapshot().GetDirectory().GetCurrentBinary());
+
+    // Do not generate a rule for the root binary dir.
+    if (currentBinaryFolder == rootBinaryDir) {
+      continue;
+    }
+
     // The directory-level rule should depend on the target-level rules
     // for all targets in the directory.
-    targetsPerFolder[currentBinaryFolder] = cmNinjaDeps();
+    cmNinjaDeps& folderTargets = targetsPerFolder[currentBinaryFolder];
     for (auto gt : lg->GetGeneratorTargets()) {
       cmStateEnums::TargetType const type = gt->GetType();
       if ((type == cmStateEnums::EXECUTABLE ||
@@ -1123,37 +1134,34 @@ void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
            type == cmStateEnums::OBJECT_LIBRARY ||
            type == cmStateEnums::UTILITY) &&
           !gt->GetPropertyAsBool("EXCLUDE_FROM_ALL")) {
-        targetsPerFolder[currentBinaryFolder].push_back(gt->GetName());
+        folderTargets.push_back(gt->GetName());
       }
     }
 
     // The directory-level rule should depend on the directory-level
     // rules of the subdirectories.
     for (cmStateSnapshot const& state : lg->GetStateSnapshot().GetChildren()) {
-      std::string const currentBinaryDir =
+      std::string const& currentBinaryDir =
         state.GetDirectory().GetCurrentBinary();
-
-      targetsPerFolder[currentBinaryFolder].push_back(
+      folderTargets.push_back(
         this->ConvertToNinjaPath(currentBinaryDir + "/all"));
     }
   }
 
-  std::string const rootBinaryDir =
-    this->LocalGenerators[0]->GetBinaryDirectory();
-  for (auto const& it : targetsPerFolder) {
-    cmGlobalNinjaGenerator::WriteDivider(os);
-    std::string const& currentBinaryDir = it.first;
+  if (!targetsPerFolder.empty()) {
+    cmNinjaBuild build("phony");
+    build.Outputs.emplace_back("");
+    for (auto& it : targetsPerFolder) {
+      cmGlobalNinjaGenerator::WriteDivider(os);
+      std::string const& currentBinaryDir = it.first;
 
-    // Do not generate a rule for the root binary dir.
-    if (rootBinaryDir.length() >= currentBinaryDir.length()) {
-      continue;
+      // Setup target
+      build.Comment = "Folder: " + currentBinaryDir;
+      build.Outputs[0] = this->ConvertToNinjaPath(currentBinaryDir + "/all");
+      build.ExplicitDeps = std::move(it.second);
+      // Write target
+      this->WriteBuild(os, build);
     }
-
-    std::string const comment = "Folder: " + currentBinaryDir;
-    cmNinjaDeps output(1);
-    output.push_back(this->ConvertToNinjaPath(currentBinaryDir + "/all"));
-
-    this->WritePhonyBuild(os, comment, output, it.second);
   }
 }
 
@@ -1234,23 +1242,26 @@ void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
                       knownDependencies.begin(), knownDependencies.end(),
                       std::back_inserter(unknownExplicitDepends));
 
-  std::string const rootBuildDirectory =
-    this->GetCMakeInstance()->GetHomeOutputDirectory();
-  bool const inSourceBuild =
-    (rootBuildDirectory == this->GetCMakeInstance()->GetHomeDirectory());
   std::vector<std::string> warnExplicitDepends;
-  for (std::string const& i : unknownExplicitDepends) {
-    // verify the file is in the build directory
-    std::string const absDepPath =
-      cmSystemTools::CollapseFullPath(i, rootBuildDirectory);
-    bool const inBuildDir =
-      cmSystemTools::IsSubDirectory(absDepPath, rootBuildDirectory);
-    if (inBuildDir) {
-      cmNinjaDeps deps(1, i);
-      this->WritePhonyBuild(os, "", deps, cmNinjaDeps());
-      if (this->PolicyCMP0058 == cmPolicies::WARN && !inSourceBuild &&
-          warnExplicitDepends.size() < 10) {
-        warnExplicitDepends.push_back(i);
+  if (!unknownExplicitDepends.empty()) {
+    cmake* cmk = this->GetCMakeInstance();
+    std::string const& buildRoot = cmk->GetHomeOutputDirectory();
+    bool const inSource = (buildRoot == cmk->GetHomeDirectory());
+    bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
+    cmNinjaBuild build("phony");
+    build.Outputs.emplace_back("");
+    for (std::string const& ued : unknownExplicitDepends) {
+      // verify the file is in the build directory
+      std::string const absDepPath =
+        cmSystemTools::CollapseFullPath(ued, buildRoot);
+      if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
+        // Generate phony build statement
+        build.Outputs[0] = ued;
+        this->WriteBuild(os, build);
+        // Add to warning on demand
+        if (warn && warnExplicitDepends.size() < 10) {
+          warnExplicitDepends.push_back(ued);
+        }
       }
     }
   }
@@ -1291,14 +1302,14 @@ void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
 
 void cmGlobalNinjaGenerator::WriteTargetAll(std::ostream& os)
 {
-  cmNinjaDeps outputs;
-  outputs.push_back(this->TargetAll);
-
-  this->WritePhonyBuild(os, "The main all target.", outputs,
-                        this->AllDependencies);
+  cmNinjaBuild build("phony");
+  build.Comment = "The main all target.";
+  build.Outputs.push_back(this->TargetAll);
+  build.ExplicitDeps = this->AllDependencies;
+  this->WriteBuild(os, build);
 
   if (!this->HasOutputPathPrefix()) {
-    cmGlobalNinjaGenerator::WriteDefault(os, outputs,
+    cmGlobalNinjaGenerator::WriteDefault(os, build.Outputs,
                                          "Make the all target the default.");
   }
 }
@@ -1325,20 +1336,21 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
     WriteRule(*this->RulesFileStream, rule);
   }
 
-  cmNinjaDeps implicitDeps;
-  cmNinjaDeps explicitDeps;
+  cmNinjaBuild reBuild("RERUN_CMAKE");
+  reBuild.Comment = "Re-run CMake if any of its inputs changed.";
+  reBuild.Outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
+
   for (cmLocalGenerator* localGen : this->LocalGenerators) {
     for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
-      implicitDeps.push_back(this->ConvertToNinjaPath(fi));
+      reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
     }
   }
-  implicitDeps.push_back(this->CMakeCacheFile);
+  reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
 
-  cmNinjaVars variables;
   // Use 'console' pool to get non buffered output of the CMake re-run call
   // Available since Ninja 1.5
   if (SupportsConsolePool()) {
-    variables["pool"] = "console";
+    reBuild.Variables["pool"] = "console";
   }
 
   cmake* cm = this->GetCMakeInstance();
@@ -1355,29 +1367,28 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
       this->WriteRule(*this->RulesFileStream, rule);
     }
 
-    std::string verifyForce = cm->GetGlobVerifyScript() + "_force";
-    cmNinjaDeps verifyForceDeps(1, this->NinjaOutputPath(verifyForce));
-
-    this->WritePhonyBuild(os, "Phony target to force glob verification run.",
-                          verifyForceDeps, cmNinjaDeps());
+    cmNinjaBuild phonyBuild("phony");
+    phonyBuild.Comment = "Phony target to force glob verification run.";
+    phonyBuild.Outputs.push_back(cm->GetGlobVerifyScript() + "_force");
+    this->WriteBuild(os, phonyBuild);
 
-    variables["restat"] = "1";
+    reBuild.Variables["restat"] = "1";
     std::string const verifyScriptFile =
       this->NinjaOutputPath(cm->GetGlobVerifyScript());
     std::string const verifyStampFile =
       this->NinjaOutputPath(cm->GetGlobVerifyStamp());
-    this->WriteBuild(os,
-                     "Re-run CMake to check if globbed directories changed.",
-                     "VERIFY_GLOBS",
-                     /*outputs=*/cmNinjaDeps(1, verifyStampFile),
-                     /*implicitOuts=*/cmNinjaDeps(),
-                     /*explicitDeps=*/cmNinjaDeps(),
-                     /*implicitDeps=*/verifyForceDeps,
-                     /*orderOnlyDeps=*/cmNinjaDeps(), variables);
-
-    variables.erase("restat");
-    implicitDeps.push_back(verifyScriptFile);
-    explicitDeps.push_back(verifyStampFile);
+    {
+      cmNinjaBuild vgBuild("VERIFY_GLOBS");
+      vgBuild.Comment =
+        "Re-run CMake to check if globbed directories changed.";
+      vgBuild.Outputs.push_back(verifyStampFile);
+      vgBuild.ImplicitDeps = phonyBuild.Outputs;
+      vgBuild.Variables = reBuild.Variables;
+      this->WriteBuild(os, vgBuild);
+    }
+    reBuild.Variables.erase("restat");
+    reBuild.ImplicitDeps.push_back(verifyScriptFile);
+    reBuild.ExplicitDeps.push_back(verifyStampFile);
   } else if (!this->SupportsManifestRestat() &&
              cm->DoWriteGlobVerifyTarget()) {
     std::ostringstream msg;
@@ -1395,25 +1406,23 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
                                            msg.str());
   }
 
-  std::sort(implicitDeps.begin(), implicitDeps.end());
-  implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()),
-                     implicitDeps.end());
-
-  std::string const ninjaBuildFile = this->NinjaOutputPath(NINJA_BUILD_FILE);
-  this->WriteBuild(os, "Re-run CMake if any of its inputs changed.",
-                   "RERUN_CMAKE",
-                   /*outputs=*/cmNinjaDeps(1, ninjaBuildFile),
-                   /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
-                   /*orderOnlyDeps=*/cmNinjaDeps(), variables);
+  std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
+  reBuild.ImplicitDeps.erase(
+    std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
+    reBuild.ImplicitDeps.end());
 
-  cmNinjaDeps missingInputs;
-  std::set_difference(std::make_move_iterator(implicitDeps.begin()),
-                      std::make_move_iterator(implicitDeps.end()),
-                      CustomCommandOutputs.begin(), CustomCommandOutputs.end(),
-                      std::back_inserter(missingInputs));
+  this->WriteBuild(os, reBuild);
 
-  this->WritePhonyBuild(os, "A missing CMake input file is not an error.",
-                        missingInputs, cmNinjaDeps());
+  {
+    cmNinjaBuild build("phony");
+    build.Comment = "A missing CMake input file is not an error.";
+    std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
+                        std::make_move_iterator(reBuild.ImplicitDeps.end()),
+                        CustomCommandOutputs.begin(),
+                        CustomCommandOutputs.end(),
+                        std::back_inserter(build.Outputs));
+    this->WriteBuild(os, build);
+  }
 }
 
 std::string cmGlobalNinjaGenerator::CMakeCmd() const
@@ -1500,16 +1509,11 @@ bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
 
   // Write build
   {
-    cmNinjaDeps outputs;
-    outputs.emplace_back(
+    cmNinjaBuild build("CLEAN_ADDITIONAL");
+    build.Comment = "Clean additional files.";
+    build.Outputs.push_back(
       this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
-    WriteBuild(os, "Clean additional files.", "CLEAN_ADDITIONAL",
-               /*outputs=*/outputs,
-               /*implicitOuts=*/cmNinjaDeps(),
-               /*explicitDeps=*/cmNinjaDeps(),
-               /*implicitDeps=*/cmNinjaDeps(),
-               /*orderOnlyDeps=*/cmNinjaDeps(),
-               /*variables=*/cmNinjaVars());
+    WriteBuild(os, build);
   }
   // Return success
   return true;
@@ -1532,20 +1536,14 @@ void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
 
   // Write build
   {
-    cmNinjaDeps explicitDeps;
+    cmNinjaBuild build("CLEAN");
+    build.Comment = "Clean all the built files.";
+    build.Outputs.push_back(this->NinjaOutputPath(this->GetCleanTargetName()));
     if (additionalFiles) {
-      explicitDeps.emplace_back(
+      build.ExplicitDeps.push_back(
         this->NinjaOutputPath(this->GetAdditionalCleanTargetName()));
     }
-    cmNinjaDeps outputs;
-    outputs.emplace_back(this->NinjaOutputPath(this->GetCleanTargetName()));
-    WriteBuild(os, "Clean all the built files.", "CLEAN",
-               /*outputs=*/outputs,
-               /*implicitOuts=*/cmNinjaDeps(),
-               /*explicitDeps=*/explicitDeps,
-               /*implicitDeps=*/cmNinjaDeps(),
-               /*orderOnlyDeps=*/cmNinjaDeps(),
-               /*variables=*/cmNinjaVars());
+    WriteBuild(os, build);
   }
 }
 
@@ -1558,13 +1556,12 @@ void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
     rule.Comment = "Rule for printing all primary targets available.";
     WriteRule(*this->RulesFileStream, rule);
   }
-  WriteBuild(os, "Print all primary targets available.", "HELP",
-             /*outputs=*/cmNinjaDeps(1, this->NinjaOutputPath("help")),
-             /*implicitOuts=*/cmNinjaDeps(),
-             /*explicitDeps=*/cmNinjaDeps(),
-             /*implicitDeps=*/cmNinjaDeps(),
-             /*orderOnlyDeps=*/cmNinjaDeps(),
-             /*variables=*/cmNinjaVars());
+  {
+    cmNinjaBuild build("HELP");
+    build.Comment = "Print all primary targets available.";
+    build.Outputs.push_back(this->NinjaOutputPath("help"));
+    WriteBuild(os, build);
+  }
 }
 
 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
@@ -1937,32 +1934,29 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
   cmGeneratedFileStream ddf(arg_dd);
   ddf << "ninja_dyndep_version = 1.0\n";
 
-  for (cmDyndepObjectInfo const& object : objects) {
-    std::string const ddComment;
-    std::string const ddRule = "dyndep";
-    cmNinjaDeps ddOutputs;
-    cmNinjaDeps ddImplicitOuts;
-    cmNinjaDeps ddExplicitDeps;
-    cmNinjaDeps ddImplicitDeps;
-    cmNinjaDeps ddOrderOnlyDeps;
-    cmNinjaVars ddVars;
-
-    ddOutputs.push_back(object.Object);
-    for (std::string const& p : object.Provides) {
-      ddImplicitOuts.push_back(this->ConvertToNinjaPath(mod_files[p]));
-    }
-    for (std::string const& r : object.Requires) {
-      std::map<std::string, std::string>::iterator m = mod_files.find(r);
-      if (m != mod_files.end()) {
-        ddImplicitDeps.push_back(this->ConvertToNinjaPath(m->second));
+  {
+    cmNinjaBuild build("dyndep");
+    build.Outputs.emplace_back("");
+    for (cmDyndepObjectInfo const& object : objects) {
+      build.Outputs[0] = object.Object;
+      build.ImplicitOuts.clear();
+      for (std::string const& p : object.Provides) {
+        build.ImplicitOuts.push_back(this->ConvertToNinjaPath(mod_files[p]));
+      }
+      build.ImplicitDeps.clear();
+      for (std::string const& r : object.Requires) {
+        auto mit = mod_files.find(r);
+        if (mit != mod_files.end()) {
+          build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
+        }
+      }
+      build.Variables.clear();
+      if (!object.Provides.empty()) {
+        build.Variables.emplace("restat", "1");
       }
-    }
-    if (!object.Provides.empty()) {
-      ddVars["restat"] = "1";
-    }
 
-    this->WriteBuild(ddf, ddComment, ddRule, ddOutputs, ddImplicitOuts,
-                     ddExplicitDeps, ddImplicitDeps, ddOrderOnlyDeps, ddVars);
+      this->WriteBuild(ddf, build);
+    }
   }
 
   // Store the map of modules provided by this target in a file for

+ 11 - 31
Source/cmGlobalNinjaGenerator.h

@@ -101,41 +101,21 @@ public:
   bool IsIPOSupported() const override { return true; }
 
   /**
-   * Write a build statement to @a os with the @a comment using
-   * the @a rule the list of @a outputs files and inputs.
-   * It also writes the variables bound to this build statement.
+   * Write a build statement @a build to @a os.
    * @warning no escaping of any kind is done here.
    */
-  void WriteBuild(std::ostream& os, const std::string& comment,
-                  const std::string& rule, const cmNinjaDeps& outputs,
-                  const cmNinjaDeps& implicitOuts,
-                  const cmNinjaDeps& explicitDeps,
-                  const cmNinjaDeps& implicitDeps,
-                  const cmNinjaDeps& orderOnlyDeps,
-                  const cmNinjaVars& variables,
-                  const std::string& rspfile = std::string(),
+  void WriteBuild(std::ostream& os, cmNinjaBuild const& build,
                   int cmdLineLimit = 0, bool* usedResponseFile = nullptr);
 
-  /**
-   * Helper to write a build statement with the special 'phony' rule.
-   */
-  void WritePhonyBuild(std::ostream& os, const std::string& comment,
-                       const cmNinjaDeps& outputs,
-                       const cmNinjaDeps& explicitDeps,
-                       const cmNinjaDeps& implicitDeps = cmNinjaDeps(),
-                       const cmNinjaDeps& orderOnlyDeps = cmNinjaDeps(),
-                       const cmNinjaVars& variables = cmNinjaVars());
-
-  void WriteCustomCommandBuild(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& deps = cmNinjaDeps(),
-                               const cmNinjaDeps& orderOnly = cmNinjaDeps());
-  void WriteMacOSXContentBuild(const std::string& input,
-                               const std::string& output);
+  void WriteCustomCommandBuild(
+    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& explicitDeps = cmNinjaDeps(),
+    const cmNinjaDeps& orderOnlyDeps = cmNinjaDeps());
+
+  void WriteMacOSXContentBuild(std::string input, std::string output);
 
   /**
    * Write a rule statement to @a os.

+ 18 - 14
Source/cmLocalNinjaGenerator.cxx

@@ -454,7 +454,8 @@ void cmLocalNinjaGenerator::AppendCustomCommandLines(
 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
   cmCustomCommand const* cc, const cmNinjaDeps& orderOnlyDeps)
 {
-  if (this->GetGlobalNinjaGenerator()->SeenCustomCommand(cc)) {
+  cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
+  if (gg->SeenCustomCommand(cc)) {
     return;
   }
 
@@ -462,13 +463,12 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
 
   const std::vector<std::string>& outputs = ccg.GetOutputs();
   const std::vector<std::string>& byproducts = ccg.GetByproducts();
-  cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size()), ninjaDeps;
 
   bool symbolic = false;
   for (std::string const& output : outputs) {
     if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
-      symbolic = sf->GetPropertyAsBool("SYMBOLIC");
-      if (symbolic) {
+      if (sf->GetPropertyAsBool("SYMBOLIC")) {
+        symbolic = true;
         break;
       }
     }
@@ -479,25 +479,29 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
     file of each imported target that has an add_dependencies pointing \
     at us.  How to know which ExternalProject step actually provides it?
 #endif
+  cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size());
   std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(),
-                 this->GetGlobalNinjaGenerator()->MapToNinjaPath());
+                 gg->MapToNinjaPath());
   std::transform(byproducts.begin(), byproducts.end(),
-                 ninjaOutputs.begin() + outputs.size(),
-                 this->GetGlobalNinjaGenerator()->MapToNinjaPath());
-  this->AppendCustomCommandDeps(ccg, ninjaDeps);
+                 ninjaOutputs.begin() + outputs.size(), gg->MapToNinjaPath());
 
   for (std::string const& ninjaOutput : ninjaOutputs) {
-    this->GetGlobalNinjaGenerator()->SeenCustomCommandOutput(ninjaOutput);
+    gg->SeenCustomCommandOutput(ninjaOutput);
   }
 
+  cmNinjaDeps ninjaDeps;
+  this->AppendCustomCommandDeps(ccg, ninjaDeps);
+
   std::vector<std::string> cmdLines;
   this->AppendCustomCommandLines(ccg, cmdLines);
 
   if (cmdLines.empty()) {
-    this->GetGlobalNinjaGenerator()->WritePhonyBuild(
-      this->GetBuildFileStream(),
-      "Phony custom command for " + ninjaOutputs[0], ninjaOutputs, ninjaDeps,
-      cmNinjaDeps(), orderOnlyDeps, cmNinjaVars());
+    cmNinjaBuild build("phony");
+    build.Comment = "Phony custom command for " + ninjaOutputs[0];
+    build.Outputs = std::move(ninjaOutputs);
+    build.ExplicitDeps = std::move(ninjaDeps);
+    build.OrderOnlyDeps = orderOnlyDeps;
+    gg->WriteBuild(this->GetBuildFileStream(), build);
   } else {
     std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]);
     // Hash full path to make unique.
@@ -505,7 +509,7 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
     cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
     customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7);
 
-    this->GetGlobalNinjaGenerator()->WriteCustomCommandBuild(
+    gg->WriteCustomCommandBuild(
       this->BuildCommandLine(cmdLines, customStep),
       this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
       cc->GetDepfile(), cc->GetJobPool(), cc->GetUsesTerminal(),

+ 115 - 128
Source/cmNinjaNormalTargetGenerator.cxx

@@ -554,11 +554,12 @@ std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeLinkCmd()
 
 void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
 {
-  if (!this->GetGlobalGenerator()->GetLanguageEnabled("CUDA")) {
+  cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
+  if (!globalGen->GetLanguageEnabled("CUDA")) {
     return;
   }
 
-  cmGeneratorTarget& genTarget = *this->GetGeneratorTarget();
+  cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
 
   bool requiresDeviceLinking = requireDeviceLinking(
     *this->GeneratorTarget, *this->GetLocalGenerator(), this->ConfigName);
@@ -576,39 +577,39 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
 
   std::string const cfgName = this->GetConfigName();
   std::string const targetOutputReal = ConvertToNinjaPath(
-    genTarget.ObjectDirectory + "cmake_device_link" + objExt);
+    genTarget->ObjectDirectory + "cmake_device_link" + objExt);
 
   std::string const targetOutputImplib = ConvertToNinjaPath(
-    genTarget.GetFullPath(cfgName, cmStateEnums::ImportLibraryArtifact));
+    genTarget->GetFullPath(cfgName, cmStateEnums::ImportLibraryArtifact));
 
   this->DeviceLinkObject = targetOutputReal;
 
   // Write comments.
   cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
-  const cmStateEnums::TargetType targetType = genTarget.GetType();
+  const cmStateEnums::TargetType targetType = genTarget->GetType();
   this->GetBuildFileStream() << "# Device Link build statements for "
                              << cmState::GetTargetTypeName(targetType)
                              << " target " << this->GetTargetName() << "\n\n";
 
   // Compute the comment.
-  std::ostringstream comment;
-  comment << "Link the " << this->GetVisibleTypeName() << " "
-          << targetOutputReal;
+  cmNinjaBuild build(this->LanguageLinkerDeviceRule());
+  build.Comment = "Link the ";
+  build.Comment += this->GetVisibleTypeName();
+  build.Comment += " ";
+  build.Comment += targetOutputReal;
 
-  cmNinjaDeps emptyDeps;
-  cmNinjaVars vars;
+  cmNinjaVars& vars = build.Variables;
 
   // Compute outputs.
-  cmNinjaDeps outputs;
-  outputs.push_back(targetOutputReal);
+  build.Outputs.push_back(targetOutputReal);
   // Compute specific libraries to link with.
-  cmNinjaDeps explicitDeps = this->GetObjects();
-  cmNinjaDeps implicitDeps = this->ComputeLinkDeps(this->TargetLinkLanguage);
+  build.ExplicitDeps = this->GetObjects();
+  build.ImplicitDeps = this->ComputeLinkDeps(this->TargetLinkLanguage);
 
   std::string frameworkPath;
   std::string linkPath;
 
-  std::string createRule = genTarget.GetCreateRuleVariable(
+  std::string createRule = genTarget->GetCreateRuleVariable(
     this->TargetLinkLanguage, this->GetConfigName());
   const bool useWatcomQuote =
     this->GetMakefile()->IsOn(createRule + "_USE_WATCOM_QUOTE");
@@ -621,14 +622,14 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
     new cmNinjaLinkLineDeviceComputer(
       this->GetLocalGenerator(),
       this->GetLocalGenerator()->GetStateSnapshot().GetDirectory(),
-      this->GetGlobalGenerator()));
+      globalGen));
   linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
 
   localGen.GetTargetFlags(
     linkLineComputer.get(), this->GetConfigName(), vars["LINK_LIBRARIES"],
-    vars["FLAGS"], vars["LINK_FLAGS"], frameworkPath, linkPath, &genTarget);
+    vars["FLAGS"], vars["LINK_FLAGS"], frameworkPath, linkPath, genTarget);
 
-  this->addPoolNinjaVariable("JOB_POOL_LINK", &genTarget, vars);
+  this->addPoolNinjaVariable("JOB_POOL_LINK", genTarget, vars);
 
   vars["LINK_FLAGS"] =
     cmGlobalNinjaGenerator::EncodeLiteral(vars["LINK_FLAGS"]);
@@ -642,18 +643,18 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
   // code between the Makefile executable and library generators.
   if (targetType == cmStateEnums::EXECUTABLE) {
     std::string t = vars["FLAGS"];
-    localGen.AddArchitectureFlags(t, &genTarget, cudaLinkLanguage, cfgName);
+    localGen.AddArchitectureFlags(t, genTarget, cudaLinkLanguage, cfgName);
     vars["FLAGS"] = t;
   } else {
     std::string t = vars["ARCH_FLAGS"];
-    localGen.AddArchitectureFlags(t, &genTarget, cudaLinkLanguage, cfgName);
+    localGen.AddArchitectureFlags(t, genTarget, cudaLinkLanguage, cfgName);
     vars["ARCH_FLAGS"] = t;
     t.clear();
-    localGen.AddLanguageFlagsForLinking(t, &genTarget, cudaLinkLanguage,
+    localGen.AddLanguageFlagsForLinking(t, genTarget, cudaLinkLanguage,
                                         cfgName);
     vars["LANGUAGE_COMPILE_FLAGS"] = t;
   }
-  if (this->GetGeneratorTarget()->HasSOName(cfgName)) {
+  if (genTarget->HasSOName(cfgName)) {
     vars["SONAME_FLAG"] =
       this->GetMakefile()->GetSONameFlag(this->TargetLinkLanguage);
     vars["SONAME"] = this->TargetNames.SharedObject;
@@ -681,7 +682,7 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
 
   this->SetMsvcTargetPdbVariable(vars);
 
-  if (this->GetGlobalGenerator()->IsGCCOnWindows()) {
+  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(), '\\', '/');
@@ -689,46 +690,43 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement()
     std::replace(link_path.begin(), link_path.end(), '\\', '/');
   }
 
-  cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
-
   // Device linking currently doesn't support response files so
   // do not check if the user has explicitly forced a response file.
   int const commandLineLengthLimit =
     static_cast<int>(cmSystemTools::CalculateCommandLineLengthLimit()) -
-    globalGen.GetRuleCmdLength(this->LanguageLinkerDeviceRule());
+    globalGen->GetRuleCmdLength(this->LanguageLinkerDeviceRule());
 
-  const std::string rspfile = this->ConvertToNinjaPath(
-    std::string("CMakeFiles/") + genTarget.GetName() + ".rsp");
+  build.RspFile = this->ConvertToNinjaPath(std::string("CMakeFiles/") +
+                                           genTarget->GetName() + ".rsp");
 
   // Gather order-only dependencies.
-  cmNinjaDeps orderOnlyDeps;
   this->GetLocalGenerator()->AppendTargetDepends(this->GetGeneratorTarget(),
-                                                 orderOnlyDeps);
+                                                 build.OrderOnlyDeps);
 
   // Write the build statement for this target.
   bool usedResponseFile = false;
-  globalGen.WriteBuild(this->GetBuildFileStream(), comment.str(),
-                       this->LanguageLinkerDeviceRule(), outputs,
-                       /*implicitOuts=*/cmNinjaDeps(), explicitDeps,
-                       implicitDeps, orderOnlyDeps, vars, rspfile,
-                       commandLineLengthLimit, &usedResponseFile);
+  globalGen->WriteBuild(this->GetBuildFileStream(), build,
+                        commandLineLengthLimit, &usedResponseFile);
   this->WriteDeviceLinkRule(false);
 }
 
 void cmNinjaNormalTargetGenerator::WriteLinkStatement()
 {
-  cmGeneratorTarget& gt = *this->GetGeneratorTarget();
+  cmMakefile* mf = this->GetMakefile();
+  cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
+  cmGeneratorTarget* gt = this->GetGeneratorTarget();
+
   const std::string cfgName = this->GetConfigName();
-  std::string targetOutput = ConvertToNinjaPath(gt.GetFullPath(cfgName));
+  std::string targetOutput = ConvertToNinjaPath(gt->GetFullPath(cfgName));
   std::string targetOutputReal = ConvertToNinjaPath(
-    gt.GetFullPath(cfgName, cmStateEnums::RuntimeBinaryArtifact,
-                   /*realname=*/true));
+    gt->GetFullPath(cfgName, cmStateEnums::RuntimeBinaryArtifact,
+                    /*realname=*/true));
   std::string targetOutputImplib = ConvertToNinjaPath(
-    gt.GetFullPath(cfgName, cmStateEnums::ImportLibraryArtifact));
+    gt->GetFullPath(cfgName, cmStateEnums::ImportLibraryArtifact));
 
-  if (gt.IsAppBundleOnApple()) {
+  if (gt->IsAppBundleOnApple()) {
     // Create the app bundle
-    std::string outpath = gt.GetDirectory(cfgName);
+    std::string outpath = gt->GetDirectory(cfgName);
     this->OSXBundleGenerator->CreateAppBundle(this->TargetNames.Output,
                                               outpath);
 
@@ -741,34 +739,34 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     targetOutputReal += "/";
     targetOutputReal += this->TargetNames.Real;
     targetOutputReal = this->ConvertToNinjaPath(targetOutputReal);
-  } else if (gt.IsFrameworkOnApple()) {
+  } else if (gt->IsFrameworkOnApple()) {
     // Create the library framework.
     this->OSXBundleGenerator->CreateFramework(this->TargetNames.Output,
-                                              gt.GetDirectory(cfgName));
-  } else if (gt.IsCFBundleOnApple()) {
+                                              gt->GetDirectory(cfgName));
+  } else if (gt->IsCFBundleOnApple()) {
     // Create the core foundation bundle.
     this->OSXBundleGenerator->CreateCFBundle(this->TargetNames.Output,
-                                             gt.GetDirectory(cfgName));
+                                             gt->GetDirectory(cfgName));
   }
 
   // Write comments.
   cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
-  const cmStateEnums::TargetType targetType = gt.GetType();
+  const cmStateEnums::TargetType targetType = gt->GetType();
   this->GetBuildFileStream()
     << "# Link build statements for " << cmState::GetTargetTypeName(targetType)
     << " target " << this->GetTargetName() << "\n\n";
 
-  cmNinjaDeps emptyDeps;
-  cmNinjaVars vars;
+  cmNinjaBuild linkBuild(this->LanguageLinkerRule());
+  cmNinjaVars& vars = linkBuild.Variables;
 
   // Compute the comment.
-  std::ostringstream comment;
-  comment << "Link the " << this->GetVisibleTypeName() << " "
-          << targetOutputReal;
+  linkBuild.Comment = "Link the ";
+  linkBuild.Comment += this->GetVisibleTypeName();
+  linkBuild.Comment += " ";
+  linkBuild.Comment += targetOutputReal;
 
   // Compute outputs.
-  cmNinjaDeps outputs;
-  outputs.push_back(targetOutputReal);
+  linkBuild.Outputs.push_back(targetOutputReal);
 
   if (this->TargetLinkLanguage == "Swift") {
     vars["SWIFT_LIBRARY_NAME"] = [this]() -> std::string {
@@ -777,12 +775,11 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
       return targetNames.Base;
     }();
 
-    vars["SWIFT_MODULE_NAME"] = [this]() -> std::string {
-      if (const char* name =
-            this->GetGeneratorTarget()->GetProperty("Swift_MODULE_NAME")) {
+    vars["SWIFT_MODULE_NAME"] = [gt]() -> std::string {
+      if (const char* name = gt->GetProperty("Swift_MODULE_NAME")) {
         return name;
       }
-      return this->GetGeneratorTarget()->GetName();
+      return gt->GetName();
     }();
 
     vars["SWIFT_MODULE"] = [this](const std::string& module) -> std::string {
@@ -806,7 +803,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
 
     vars["SWIFT_OUTPUT_FILE_MAP"] =
       this->GetLocalGenerator()->ConvertToOutputFormat(
-        this->ConvertToNinjaPath(gt.GetSupportDirectory() +
+        this->ConvertToNinjaPath(gt->GetSupportDirectory() +
                                  "/output-file-map.json"),
         cmOutputConverter::SHELL);
 
@@ -835,35 +832,31 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   }
 
   // Compute specific libraries to link with.
-  cmNinjaDeps explicitDeps;
   if (this->TargetLinkLanguage == "Swift") {
     std::vector<cmSourceFile const*> sources;
-    this->GetGeneratorTarget()->GetObjectSources(sources,
-                                                 this->GetConfigName());
+    gt->GetObjectSources(sources, this->GetConfigName());
     for (const auto& source : sources) {
-      outputs.push_back(
+      linkBuild.Outputs.push_back(
         this->ConvertToNinjaPath(this->GetObjectFilePath(source)));
-      explicitDeps.push_back(
+      linkBuild.ExplicitDeps.push_back(
         this->ConvertToNinjaPath(this->GetSourceFilePath(source)));
     }
 
-    outputs.push_back(vars["SWIFT_MODULE"]);
+    linkBuild.Outputs.push_back(vars["SWIFT_MODULE"]);
   } else {
-    explicitDeps = this->GetObjects();
+    linkBuild.ExplicitDeps = this->GetObjects();
   }
-  cmNinjaDeps implicitDeps = this->ComputeLinkDeps(this->TargetLinkLanguage);
+  linkBuild.ImplicitDeps = this->ComputeLinkDeps(this->TargetLinkLanguage);
 
   if (!this->DeviceLinkObject.empty()) {
-    explicitDeps.push_back(this->DeviceLinkObject);
+    linkBuild.ExplicitDeps.push_back(this->DeviceLinkObject);
   }
 
-  cmMakefile* mf = this->GetMakefile();
-
   std::string frameworkPath;
   std::string linkPath;
 
   std::string createRule =
-    gt.GetCreateRuleVariable(this->TargetLinkLanguage, this->GetConfigName());
+    gt->GetCreateRuleVariable(this->TargetLinkLanguage, this->GetConfigName());
   bool useWatcomQuote = mf->IsOn(createRule + "_USE_WATCOM_QUOTE");
   cmLocalNinjaGenerator& localGen = *this->GetLocalGenerator();
 
@@ -871,14 +864,14 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     localGen.ConvertToOutputFormat(targetOutputReal, cmOutputConverter::SHELL);
 
   std::unique_ptr<cmLinkLineComputer> linkLineComputer(
-    this->GetGlobalGenerator()->CreateLinkLineComputer(
+    globalGen->CreateLinkLineComputer(
       this->GetLocalGenerator(),
       this->GetLocalGenerator()->GetStateSnapshot().GetDirectory()));
   linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
 
   localGen.GetTargetFlags(linkLineComputer.get(), this->GetConfigName(),
                           vars["LINK_LIBRARIES"], vars["FLAGS"],
-                          vars["LINK_FLAGS"], frameworkPath, linkPath, &gt);
+                          vars["LINK_FLAGS"], frameworkPath, linkPath, gt);
 
   // Add OS X version flags, if any.
   if (this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY ||
@@ -889,7 +882,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
                            "CURRENT", false);
   }
 
-  this->addPoolNinjaVariable("JOB_POOL_LINK", &gt, vars);
+  this->addPoolNinjaVariable("JOB_POOL_LINK", gt, vars);
 
   this->AddModuleDefinitionFlag(linkLineComputer.get(), vars["LINK_FLAGS"]);
   vars["LINK_FLAGS"] =
@@ -899,7 +892,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
 
   vars["LINK_PATH"] = frameworkPath + linkPath;
   std::string lwyuFlags;
-  if (gt.GetPropertyAsBool("LINK_WHAT_YOU_USE")) {
+  if (gt->GetPropertyAsBool("LINK_WHAT_YOU_USE")) {
     lwyuFlags = " -Wl,--no-as-needed";
   }
 
@@ -908,24 +901,23 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   // code between the Makefile executable and library generators.
   if (targetType == cmStateEnums::EXECUTABLE) {
     std::string t = vars["FLAGS"];
-    localGen.AddArchitectureFlags(t, &gt, TargetLinkLanguage, cfgName);
+    localGen.AddArchitectureFlags(t, gt, TargetLinkLanguage, cfgName);
     t += lwyuFlags;
     vars["FLAGS"] = t;
   } else {
     std::string t = vars["ARCH_FLAGS"];
-    localGen.AddArchitectureFlags(t, &gt, TargetLinkLanguage, cfgName);
+    localGen.AddArchitectureFlags(t, gt, TargetLinkLanguage, cfgName);
     vars["ARCH_FLAGS"] = t;
     t.clear();
     t += lwyuFlags;
-    localGen.AddLanguageFlagsForLinking(t, &gt, TargetLinkLanguage, cfgName);
+    localGen.AddLanguageFlagsForLinking(t, gt, TargetLinkLanguage, cfgName);
     vars["LANGUAGE_COMPILE_FLAGS"] = t;
   }
-  if (this->GetGeneratorTarget()->HasSOName(cfgName)) {
+  if (gt->HasSOName(cfgName)) {
     vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage);
     vars["SONAME"] = this->TargetNames.SharedObject;
     if (targetType == cmStateEnums::SHARED_LIBRARY) {
-      std::string install_dir =
-        this->GetGeneratorTarget()->GetInstallNameDirForBuildTree(cfgName);
+      std::string install_dir = gt->GetInstallNameDirForBuildTree(cfgName);
       if (!install_dir.empty()) {
         vars["INSTALLNAME_DIR"] = localGen.ConvertToOutputFormat(
           install_dir, cmOutputConverter::SHELL);
@@ -940,7 +932,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
       targetOutputImplib, cmOutputConverter::SHELL);
     vars["TARGET_IMPLIB"] = impLibPath;
     EnsureParentDirectoryExists(impLibPath);
-    if (gt.HasImportLibrary(cfgName)) {
+    if (gt->HasImportLibrary(cfgName)) {
       byproducts.push_back(targetOutputImplib);
     }
   }
@@ -951,7 +943,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     std::string prefix;
     std::string base;
     std::string suffix;
-    this->GetGeneratorTarget()->GetFullNameComponents(prefix, base, suffix);
+    gt->GetFullNameComponents(prefix, base, suffix);
     std::string dbg_suffix = ".dbg";
     // TODO: Where to document?
     if (mf->GetDefinition("CMAKE_DEBUG_SYMBOL_SUFFIX")) {
@@ -960,12 +952,12 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     vars["TARGET_PDB"] = base + suffix + dbg_suffix;
   }
 
-  const std::string objPath = GetGeneratorTarget()->GetSupportDirectory();
+  const std::string objPath = gt->GetSupportDirectory();
   vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
     this->ConvertToNinjaPath(objPath), cmOutputConverter::SHELL);
   EnsureDirectoryExists(objPath);
 
-  if (this->GetGlobalGenerator()->IsGCCOnWindows()) {
+  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(), '\\', '/');
@@ -974,8 +966,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   }
 
   const std::vector<cmCustomCommand>* cmdLists[3] = {
-    &gt.GetPreBuildCommands(), &gt.GetPreLinkCommands(),
-    &gt.GetPostBuildCommands()
+    &gt->GetPreBuildCommands(), &gt->GetPreLinkCommands(),
+    &gt->GetPostBuildCommands()
   };
 
   std::vector<std::string> preLinkCmdLines, postBuildCmdLines;
@@ -995,7 +987,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
 
   // maybe create .def file from list of objects
   cmGeneratorTarget::ModuleDefinitionInfo const* mdi =
-    gt.GetModuleDefinitionInfo(this->GetConfigName());
+    gt->GetModuleDefinitionInfo(this->GetConfigName());
   if (mdi && mdi->DefFileGenerated) {
     std::string cmakeCommand =
       this->GetLocalGenerator()->ConvertToOutputFormat(
@@ -1027,8 +1019,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     }
   }
   // If we have any PRE_LINK commands, we need to go back to CMAKE_BINARY_DIR
-  // for
-  // the link commands.
+  // for the link commands.
   if (!preLinkCmdLines.empty()) {
     const std::string homeOutDir = localGen.ConvertToOutputFormat(
       localGen.GetBinaryDirectory(), cmOutputConverter::SHELL);
@@ -1042,14 +1033,13 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
 
   cmNinjaVars symlinkVars;
   bool const symlinkNeeded =
-    (targetOutput != targetOutputReal && !gt.IsFrameworkOnApple());
+    (targetOutput != targetOutputReal && !gt->IsFrameworkOnApple());
   if (!symlinkNeeded) {
     vars["POST_BUILD"] = postBuildCmdLine;
   } else {
     vars["POST_BUILD"] = cmGlobalNinjaGenerator::SHELL_NOOP;
     symlinkVars["POST_BUILD"] = postBuildCmdLine;
   }
-  cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
 
   bool const lang_supports_response =
     !(this->TargetLinkLanguage == "RC" || this->TargetLinkLanguage == "CUDA");
@@ -1057,44 +1047,41 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   if (!lang_supports_response || !this->ForceResponseFile()) {
     commandLineLengthLimit =
       static_cast<int>(cmSystemTools::CalculateCommandLineLengthLimit()) -
-      globalGen.GetRuleCmdLength(this->LanguageLinkerRule());
+      globalGen->GetRuleCmdLength(linkBuild.Rule);
   }
 
-  const std::string rspfile = this->ConvertToNinjaPath(
-    std::string("CMakeFiles/") + gt.GetName() + ".rsp");
+  linkBuild.RspFile = this->ConvertToNinjaPath(std::string("CMakeFiles/") +
+                                               gt->GetName() + ".rsp");
 
   // Gather order-only dependencies.
-  cmNinjaDeps orderOnlyDeps;
-  this->GetLocalGenerator()->AppendTargetDepends(this->GetGeneratorTarget(),
-                                                 orderOnlyDeps);
+  this->GetLocalGenerator()->AppendTargetDepends(gt, linkBuild.OrderOnlyDeps);
 
   // Ninja should restat after linking if and only if there are byproducts.
   vars["RESTAT"] = byproducts.empty() ? "" : "1";
 
   for (std::string const& o : byproducts) {
-    this->GetGlobalGenerator()->SeenCustomCommandOutput(o);
-    outputs.push_back(o);
+    globalGen->SeenCustomCommandOutput(o);
+    linkBuild.Outputs.push_back(o);
   }
 
   // Write the build statement for this target.
   bool usedResponseFile = false;
-  globalGen.WriteBuild(this->GetBuildFileStream(), comment.str(),
-                       this->LanguageLinkerRule(), outputs,
-                       /*implicitOuts=*/cmNinjaDeps(), explicitDeps,
-                       implicitDeps, orderOnlyDeps, vars, rspfile,
-                       commandLineLengthLimit, &usedResponseFile);
+  globalGen->WriteBuild(this->GetBuildFileStream(), linkBuild,
+                        commandLineLengthLimit, &usedResponseFile);
   this->WriteLinkRule(usedResponseFile);
 
   if (symlinkNeeded) {
     if (targetType == cmStateEnums::EXECUTABLE) {
-      globalGen.WriteBuild(
-        this->GetBuildFileStream(),
-        "Create executable symlink " + targetOutput,
-        "CMAKE_SYMLINK_EXECUTABLE", cmNinjaDeps(1, targetOutput),
-        /*implicitOuts=*/cmNinjaDeps(), cmNinjaDeps(1, targetOutputReal),
-        emptyDeps, emptyDeps, symlinkVars);
+      cmNinjaBuild build("CMAKE_SYMLINK_EXECUTABLE");
+      build.Comment = "Create executable symlink " + targetOutput;
+      build.Outputs.push_back(targetOutput);
+      build.ExplicitDeps.push_back(targetOutputReal);
+      build.Variables = std::move(symlinkVars);
+      globalGen->WriteBuild(this->GetBuildFileStream(), build);
     } else {
-      cmNinjaDeps symlinks;
+      cmNinjaBuild build("CMAKE_SYMLINK_LIBRARY");
+      build.Comment = "Create library symlink " + targetOutput;
+
       std::string const soName = this->ConvertToNinjaPath(
         this->GetTargetFilePath(this->TargetNames.SharedObject));
       // If one link has to be created.
@@ -1104,32 +1091,32 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
             soName, cmOutputConverter::SHELL);
       } else {
         symlinkVars["SONAME"].clear();
-        symlinks.push_back(soName);
+        build.Outputs.push_back(soName);
       }
-      symlinks.push_back(targetOutput);
-      globalGen.WriteBuild(
-        this->GetBuildFileStream(), "Create library symlink " + targetOutput,
-        "CMAKE_SYMLINK_LIBRARY", symlinks,
-        /*implicitOuts=*/cmNinjaDeps(), cmNinjaDeps(1, targetOutputReal),
-        emptyDeps, emptyDeps, symlinkVars);
+      build.Outputs.push_back(targetOutput);
+      build.ExplicitDeps.push_back(targetOutputReal);
+      build.Variables = std::move(symlinkVars);
+
+      globalGen->WriteBuild(this->GetBuildFileStream(), build);
     }
   }
 
   // Add aliases for the file name and the target name.
-  globalGen.AddTargetAlias(this->TargetNames.Output, &gt);
-  globalGen.AddTargetAlias(this->GetTargetName(), &gt);
+  globalGen->AddTargetAlias(this->TargetNames.Output, gt);
+  globalGen->AddTargetAlias(this->GetTargetName(), gt);
 }
 
 void cmNinjaNormalTargetGenerator::WriteObjectLibStatement()
 {
   // Write a phony output that depends on all object files.
-  cmNinjaDeps outputs;
-  this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
-                                                 outputs);
-  cmNinjaDeps depends = this->GetObjects();
-  this->GetGlobalGenerator()->WritePhonyBuild(
-    this->GetBuildFileStream(), "Object library " + this->GetTargetName(),
-    outputs, depends);
+  {
+    cmNinjaBuild build("phony");
+    build.Comment = "Object library " + this->GetTargetName();
+    this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
+                                                   build.Outputs);
+    build.ExplicitDeps = this->GetObjects();
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
+  }
 
   // Add aliases for the target name.
   this->GetGlobalGenerator()->AddTargetAlias(this->GetTargetName(),

+ 131 - 143
Source/cmNinjaTargetGenerator.cxx

@@ -796,97 +796,102 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements()
 
   const std::string& config =
     this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
-  std::vector<cmSourceFile const*> customCommands;
-  this->GeneratorTarget->GetCustomCommands(customCommands, config);
-  for (cmSourceFile const* sf : customCommands) {
-    cmCustomCommand const* cc = sf->GetCustomCommand();
-    this->GetLocalGenerator()->AddCustomCommandTarget(
-      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);
+  {
+    std::vector<cmSourceFile const*> customCommands;
+    this->GeneratorTarget->GetCustomCommands(customCommands, config);
+    for (cmSourceFile const* sf : customCommands) {
+      cmCustomCommand const* cc = sf->GetCustomCommand();
+      this->GetLocalGenerator()->AddCustomCommandTarget(
+        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);
+    }
   }
-  std::vector<cmSourceFile const*> headerSources;
-  this->GeneratorTarget->GetHeaderSources(headerSources, config);
-  this->OSXBundleGenerator->GenerateMacOSXContentStatements(
-    headerSources, this->MacOSXContentGenerator.get());
-  std::vector<cmSourceFile const*> extraSources;
-  this->GeneratorTarget->GetExtraSources(extraSources, config);
-  this->OSXBundleGenerator->GenerateMacOSXContentStatements(
-    extraSources, this->MacOSXContentGenerator.get());
-  std::vector<cmSourceFile const*> externalObjects;
-  this->GeneratorTarget->GetExternalObjects(externalObjects, config);
-  for (cmSourceFile const* sf : externalObjects) {
-    this->Objects.push_back(this->GetSourceFilePath(sf));
+  {
+    std::vector<cmSourceFile const*> headerSources;
+    this->GeneratorTarget->GetHeaderSources(headerSources, config);
+    this->OSXBundleGenerator->GenerateMacOSXContentStatements(
+      headerSources, this->MacOSXContentGenerator.get());
   }
-
-  cmNinjaDeps orderOnlyDeps;
-  this->GetLocalGenerator()->AppendTargetDepends(
-    this->GeneratorTarget, orderOnlyDeps, DependOnTargetOrdering);
-
-  // Add order-only dependencies on other files associated with the target.
-  cmAppend(orderOnlyDeps, this->ExtraFiles);
-
-  // Add order-only dependencies on custom command outputs.
-  for (cmCustomCommand const* cc : this->CustomCommands) {
-    cmCustomCommandGenerator ccg(*cc, this->GetConfigName(),
-                                 this->GetLocalGenerator());
-    const std::vector<std::string>& ccoutputs = ccg.GetOutputs();
-    const std::vector<std::string>& ccbyproducts = ccg.GetByproducts();
-    std::transform(ccoutputs.begin(), ccoutputs.end(),
-                   std::back_inserter(orderOnlyDeps), MapToNinjaPath());
-    std::transform(ccbyproducts.begin(), ccbyproducts.end(),
-                   std::back_inserter(orderOnlyDeps), MapToNinjaPath());
+  {
+    std::vector<cmSourceFile const*> extraSources;
+    this->GeneratorTarget->GetExtraSources(extraSources, config);
+    this->OSXBundleGenerator->GenerateMacOSXContentStatements(
+      extraSources, this->MacOSXContentGenerator.get());
   }
-
-  std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
-  orderOnlyDeps.erase(std::unique(orderOnlyDeps.begin(), orderOnlyDeps.end()),
-                      orderOnlyDeps.end());
-
-  // The phony target must depend on at least one input or ninja will explain
-  // that "output ... of phony edge with no inputs doesn't exist" and consider
-  // the phony output "dirty".
-  if (orderOnlyDeps.empty()) {
-    // Any path that always exists will work here.  It would be nice to
-    // use just "." but that is not supported by Ninja < 1.7.
-    std::string tgtDir;
-    tgtDir += this->LocalGenerator->GetCurrentBinaryDirectory();
-    tgtDir += "/";
-    tgtDir += this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
-    orderOnlyDeps.push_back(this->ConvertToNinjaPath(tgtDir));
+  {
+    std::vector<cmSourceFile const*> externalObjects;
+    this->GeneratorTarget->GetExternalObjects(externalObjects, config);
+    for (cmSourceFile const* sf : externalObjects) {
+      this->Objects.push_back(this->GetSourceFilePath(sf));
+    }
   }
 
   {
-    cmNinjaDeps orderOnlyTarget;
-    orderOnlyTarget.push_back(this->OrderDependsTargetForTarget());
-    this->GetGlobalGenerator()->WritePhonyBuild(
-      this->GetBuildFileStream(),
-      "Order-only phony target for " + this->GetTargetName(), orderOnlyTarget,
-      cmNinjaDeps(), cmNinjaDeps(), orderOnlyDeps);
+    cmNinjaBuild build("phony");
+    build.Comment = "Order-only phony target for " + this->GetTargetName();
+    build.Outputs.push_back(this->OrderDependsTargetForTarget());
+
+    cmNinjaDeps& orderOnlyDeps = build.OrderOnlyDeps;
+    this->GetLocalGenerator()->AppendTargetDepends(
+      this->GeneratorTarget, orderOnlyDeps, DependOnTargetOrdering);
+
+    // Add order-only dependencies on other files associated with the target.
+    cmAppend(orderOnlyDeps, this->ExtraFiles);
+
+    // Add order-only dependencies on custom command outputs.
+    for (cmCustomCommand const* cc : this->CustomCommands) {
+      cmCustomCommandGenerator ccg(*cc, this->GetConfigName(),
+                                   this->GetLocalGenerator());
+      const std::vector<std::string>& ccoutputs = ccg.GetOutputs();
+      const std::vector<std::string>& ccbyproducts = ccg.GetByproducts();
+      std::transform(ccoutputs.begin(), ccoutputs.end(),
+                     std::back_inserter(orderOnlyDeps), MapToNinjaPath());
+      std::transform(ccbyproducts.begin(), ccbyproducts.end(),
+                     std::back_inserter(orderOnlyDeps), MapToNinjaPath());
+    }
+
+    std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
+    orderOnlyDeps.erase(
+      std::unique(orderOnlyDeps.begin(), orderOnlyDeps.end()),
+      orderOnlyDeps.end());
+
+    // The phony target must depend on at least one input or ninja will explain
+    // that "output ... of phony edge with no inputs doesn't exist" and
+    // consider the phony output "dirty".
+    if (orderOnlyDeps.empty()) {
+      // Any path that always exists will work here.  It would be nice to
+      // use just "." but that is not supported by Ninja < 1.7.
+      std::string tgtDir;
+      tgtDir += this->LocalGenerator->GetCurrentBinaryDirectory();
+      tgtDir += "/";
+      tgtDir +=
+        this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
+      orderOnlyDeps.push_back(this->ConvertToNinjaPath(tgtDir));
+    }
+
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
   }
-  std::vector<cmSourceFile const*> objectSources;
-  this->GeneratorTarget->GetObjectSources(objectSources, config);
-  for (cmSourceFile const* sf : objectSources) {
-    this->WriteObjectBuildStatement(sf);
+
+  {
+    std::vector<cmSourceFile const*> objectSources;
+    this->GeneratorTarget->GetObjectSources(objectSources, config);
+    for (cmSourceFile const* sf : objectSources) {
+      this->WriteObjectBuildStatement(sf);
+    }
   }
 
   for (auto const& langDDIFiles : this->DDIFiles) {
     std::string const& language = langDDIFiles.first;
     cmNinjaDeps const& ddiFiles = langDDIFiles.second;
 
-    std::string const ddComment;
-    std::string const ddRule = this->LanguageDyndepRule(language);
-    cmNinjaDeps ddOutputs;
-    cmNinjaDeps ddImplicitOuts;
-    cmNinjaDeps const& ddExplicitDeps = ddiFiles;
-    cmNinjaDeps ddImplicitDeps;
-    cmNinjaDeps ddOrderOnlyDeps;
-    cmNinjaVars ddVars;
+    cmNinjaBuild build(this->LanguageDyndepRule(language));
+    build.Outputs.push_back(this->GetDyndepFilePath(language));
+    build.ExplicitDeps = ddiFiles;
 
     this->WriteTargetDependInfo(language);
 
-    ddOutputs.push_back(this->GetDyndepFilePath(language));
-
     // Make sure dyndep files for all our dependencies have already
     // been generated so that the '<LANG>Modules.json' files they
     // produced as side-effects are available for us to read.
@@ -896,11 +901,9 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements()
     // refactoring the Ninja generator to generate targets in
     // dependency order so that we can collect the needed information.
     this->GetLocalGenerator()->AppendTargetDepends(
-      this->GeneratorTarget, ddOrderOnlyDeps, DependOnTargetArtifact);
+      this->GeneratorTarget, build.OrderOnlyDeps, DependOnTargetArtifact);
 
-    this->GetGlobalGenerator()->WriteBuild(
-      this->GetBuildFileStream(), ddComment, ddRule, ddOutputs, ddImplicitOuts,
-      ddExplicitDeps, ddImplicitDeps, ddOrderOnlyDeps, ddVars);
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
   }
 
   this->GetBuildFileStream() << "\n";
@@ -946,7 +949,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   int const commandLineLengthLimit =
     ((lang_supports_response && this->ForceResponseFile())) ? -1 : 0;
 
-  cmNinjaVars vars;
+  cmNinjaBuild objBuild(this->LanguageCompilerRule(language));
+  cmNinjaVars& vars = objBuild.Variables;
   vars["FLAGS"] = this->ComputeFlagsForObject(source, language);
   vars["DEFINES"] = this->ComputeDefines(source, language);
   vars["INCLUDES"] = this->ComputeIncludes(source, language);
@@ -977,32 +981,26 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     language, sourceFileName, objectDir, objectFileName, objectFileDir,
     vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"]);
 
-  std::string comment;
-  std::string rule = this->LanguageCompilerRule(language);
-
-  cmNinjaDeps outputs;
-  outputs.push_back(objectFileName);
+  objBuild.Outputs.push_back(objectFileName);
   // Add this object to the list of object files.
   this->Objects.push_back(objectFileName);
 
-  cmNinjaDeps explicitDeps;
-  explicitDeps.push_back(sourceFileName);
+  objBuild.ExplicitDeps.push_back(sourceFileName);
 
-  cmNinjaDeps implicitDeps;
   if (const char* objectDeps = source->GetProperty("OBJECT_DEPENDS")) {
-    std::vector<std::string> depList;
-    cmSystemTools::ExpandListArgument(objectDeps, depList);
+    std::vector<std::string> depList =
+      cmSystemTools::ExpandedListArgument(objectDeps);
     for (std::string& odi : depList) {
       if (cmSystemTools::FileIsFullPath(odi)) {
         odi = cmSystemTools::CollapseFullPath(odi);
       }
     }
     std::transform(depList.begin(), depList.end(),
-                   std::back_inserter(implicitDeps), MapToNinjaPath());
+                   std::back_inserter(objBuild.ImplicitDeps),
+                   MapToNinjaPath());
   }
 
-  cmNinjaDeps orderOnlyDeps;
-  orderOnlyDeps.push_back(this->OrderDependsTargetForTarget());
+  objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget());
 
   // 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
@@ -1012,8 +1010,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
       !source->GetPropertyAsBool("__CMAKE_GENERATED_BY_CMAKE") &&
       !source->GetCustomCommand() &&
       !this->GetGlobalGenerator()->HasCustomCommandOutput(sourceFileName)) {
-    this->GetGlobalGenerator()->AddAssumedSourceDependencies(sourceFileName,
-                                                             orderOnlyDeps);
+    this->GetGlobalGenerator()->AddAssumedSourceDependencies(
+      sourceFileName, objBuild.OrderOnlyDeps);
   }
 
   // For some cases we need to generate a ninja dyndep file.
@@ -1022,39 +1020,34 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   // For some cases we do an explicit preprocessor invocation.
   bool const explicitPP = this->NeedExplicitPreprocessing(language);
   if (explicitPP) {
-    bool const compilePP = this->UsePreprocessedSource(language);
-    std::string const ppComment;
-    std::string const ppRule = this->LanguagePreprocessRule(language);
-    cmNinjaDeps ppOutputs;
-    cmNinjaDeps ppImplicitOuts;
-    cmNinjaDeps ppExplicitDeps;
-    cmNinjaDeps ppImplicitDeps;
-    cmNinjaDeps ppOrderOnlyDeps;
-    cmNinjaVars ppVars;
+    cmNinjaBuild ppBuild(this->LanguagePreprocessRule(language));
 
     std::string const ppFileName =
       this->ConvertToNinjaPath(this->GetPreprocessedFilePath(source));
-    ppOutputs.push_back(ppFileName);
+    ppBuild.Outputs.push_back(ppFileName);
+
+    ppBuild.RspFile = ppFileName + ".rsp";
 
+    bool const compilePP = this->UsePreprocessedSource(language);
     if (compilePP) {
       // Move compilation dependencies to the preprocessing build statement.
-      std::swap(ppExplicitDeps, explicitDeps);
-      std::swap(ppImplicitDeps, implicitDeps);
-      std::swap(ppOrderOnlyDeps, orderOnlyDeps);
-      std::swap(ppVars["IN_ABS"], vars["IN_ABS"]);
+      std::swap(ppBuild.ExplicitDeps, objBuild.ExplicitDeps);
+      std::swap(ppBuild.ImplicitDeps, objBuild.ImplicitDeps);
+      std::swap(ppBuild.OrderOnlyDeps, objBuild.OrderOnlyDeps);
+      std::swap(ppBuild.Variables["IN_ABS"], vars["IN_ABS"]);
 
       // The actual compilation will now use the preprocessed source.
-      explicitDeps.push_back(ppFileName);
+      objBuild.ExplicitDeps.push_back(ppFileName);
     } else {
       // Copy compilation dependencies to the preprocessing build statement.
-      ppExplicitDeps = explicitDeps;
-      ppImplicitDeps = implicitDeps;
-      ppOrderOnlyDeps = orderOnlyDeps;
-      ppVars["IN_ABS"] = vars["IN_ABS"];
+      ppBuild.ExplicitDeps = objBuild.ExplicitDeps;
+      ppBuild.ImplicitDeps = objBuild.ImplicitDeps;
+      ppBuild.OrderOnlyDeps = objBuild.OrderOnlyDeps;
+      ppBuild.Variables["IN_ABS"] = vars["IN_ABS"];
     }
 
     // Preprocessing and compilation generally use the same flags.
-    ppVars["FLAGS"] = vars["FLAGS"];
+    ppBuild.Variables["FLAGS"] = vars["FLAGS"];
 
     if (compilePP) {
       // In case compilation requires flags that are incompatible with
@@ -1066,16 +1059,16 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
 
     if (compilePP) {
       // Move preprocessor definitions to the preprocessor build statement.
-      std::swap(ppVars["DEFINES"], vars["DEFINES"]);
+      std::swap(ppBuild.Variables["DEFINES"], vars["DEFINES"]);
     } else {
       // Copy preprocessor definitions to the preprocessor build statement.
-      ppVars["DEFINES"] = vars["DEFINES"];
+      ppBuild.Variables["DEFINES"] = vars["DEFINES"];
     }
 
     // Copy include directories to the preprocessor build statement.  The
     // Fortran compilation build statement still needs them for the INCLUDE
     // directive.
-    ppVars["INCLUDES"] = vars["INCLUDES"];
+    ppBuild.Variables["INCLUDES"] = vars["INCLUDES"];
 
     if (compilePP) {
       // Prepend source file's original directory as an include directory
@@ -1092,8 +1085,9 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     }
 
     // Explicit preprocessing always uses a depfile.
-    ppVars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
-      objectFileName + ".pp.d", cmOutputConverter::SHELL);
+    ppBuild.Variables["DEP_FILE"] =
+      this->GetLocalGenerator()->ConvertToOutputFormat(
+        objectFileName + ".pp.d", cmOutputConverter::SHELL);
     if (compilePP) {
       // The actual compilation does not need a depfile because it
       // depends on the already-preprocessed source.
@@ -1103,28 +1097,24 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     if (needDyndep) {
       // Tell dependency scanner the object file that will result from
       // compiling the source.
-      ppVars["OBJ_FILE"] = objectFileName;
+      ppBuild.Variables["OBJ_FILE"] = objectFileName;
 
       // Tell dependency scanner where to store dyndep intermediate results.
       std::string const ddiFile = objectFileName + ".ddi";
-      ppVars["DYNDEP_INTERMEDIATE_FILE"] = ddiFile;
-      ppImplicitOuts.push_back(ddiFile);
+      ppBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile;
+      ppBuild.ImplicitOuts.push_back(ddiFile);
       this->DDIFiles[language].push_back(ddiFile);
     }
 
     this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
-                               ppVars);
-
-    std::string const ppRspFile = ppFileName + ".rsp";
+                               ppBuild.Variables);
 
-    this->GetGlobalGenerator()->WriteBuild(
-      this->GetBuildFileStream(), ppComment, ppRule, ppOutputs, ppImplicitOuts,
-      ppExplicitDeps, ppImplicitDeps, ppOrderOnlyDeps, ppVars, ppRspFile,
-      commandLineLengthLimit);
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), ppBuild,
+                                           commandLineLengthLimit);
   }
   if (needDyndep) {
     std::string const dyndep = this->GetDyndepFilePath(language);
-    orderOnlyDeps.push_back(dyndep);
+    objBuild.OrderOnlyDeps.push_back(dyndep);
     vars["dyndep"] = dyndep;
   }
 
@@ -1140,25 +1130,23 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
 
   this->SetMsvcTargetPdbVariable(vars);
 
-  std::string const rspfile = objectFileName + ".rsp";
+  objBuild.RspFile = objectFileName + ".rsp";
 
   if (language == "Swift") {
     this->EmitSwiftDependencyInfo(source);
   } else {
-    this->GetGlobalGenerator()->WriteBuild(
-      this->GetBuildFileStream(), comment, rule, outputs,
-      /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
-      orderOnlyDeps, vars, rspfile, commandLineLengthLimit);
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(),
+                                           objBuild, commandLineLengthLimit);
   }
 
   if (const char* objectOutputs = source->GetProperty("OBJECT_OUTPUTS")) {
-    std::vector<std::string> outputList;
-    cmSystemTools::ExpandListArgument(objectOutputs, outputList);
-    std::transform(outputList.begin(), outputList.end(), outputList.begin(),
-                   MapToNinjaPath());
-    this->GetGlobalGenerator()->WritePhonyBuild(this->GetBuildFileStream(),
-                                                "Additional output files.",
-                                                outputList, outputs);
+    cmNinjaBuild build("phony");
+    build.Comment = "Additional output files.";
+    build.Outputs = cmSystemTools::ExpandedListArgument(objectOutputs);
+    std::transform(build.Outputs.begin(), build.Outputs.end(),
+                   build.Outputs.begin(), MapToNinjaPath());
+    build.ExplicitDeps = objBuild.Outputs;
+    this->GetGlobalGenerator()->WriteBuild(this->GetBuildFileStream(), build);
   }
 }
 

+ 20 - 0
Source/cmNinjaTypes.h

@@ -41,4 +41,24 @@ public:
   bool Generator = false;
 };
 
+class cmNinjaBuild
+{
+public:
+  cmNinjaBuild() = default;
+  cmNinjaBuild(std::string rule)
+    : Rule(std::move(rule))
+  {
+  }
+
+  std::string Comment;
+  std::string Rule;
+  cmNinjaDeps Outputs;
+  cmNinjaDeps ImplicitOuts;
+  cmNinjaDeps ExplicitDeps;
+  cmNinjaDeps ImplicitDeps;
+  cmNinjaDeps OrderOnlyDeps;
+  cmNinjaVars Variables;
+  std::string RspFile;
+};
+
 #endif // ! cmNinjaTypes_h

+ 64 - 70
Source/cmNinjaUtilityTargetGenerator.cxx

@@ -16,8 +16,10 @@
 #include "cmSystemTools.h"
 
 #include <algorithm>
+#include <array>
 #include <iterator>
 #include <string>
+#include <utility>
 #include <vector>
 
 cmNinjaUtilityTargetGenerator::cmNinjaUtilityTargetGenerator(
@@ -30,74 +32,74 @@ cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() = default;
 
 void cmNinjaUtilityTargetGenerator::Generate()
 {
-  std::string utilCommandName =
-    this->GetLocalGenerator()->GetCurrentBinaryDirectory();
+  cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator();
+  cmLocalNinjaGenerator* lg = this->GetLocalGenerator();
+  cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
+
+  std::string utilCommandName = lg->GetCurrentBinaryDirectory();
   utilCommandName += "/CMakeFiles";
   utilCommandName += "/";
   utilCommandName += this->GetTargetName() + ".util";
   utilCommandName = this->ConvertToNinjaPath(utilCommandName);
 
+  cmNinjaBuild phonyBuild("phony");
   std::vector<std::string> commands;
-  cmNinjaDeps deps, outputs, util_outputs(1, utilCommandName);
-
-  const std::vector<cmCustomCommand>* cmdLists[2] = {
-    &this->GetGeneratorTarget()->GetPreBuildCommands(),
-    &this->GetGeneratorTarget()->GetPostBuildCommands()
-  };
+  cmNinjaDeps deps, util_outputs(1, utilCommandName);
 
   bool uses_terminal = false;
-
-  for (unsigned i = 0; i != 2; ++i) {
-    for (cmCustomCommand const& ci : *cmdLists[i]) {
-      cmCustomCommandGenerator ccg(ci, this->GetConfigName(),
-                                   this->GetLocalGenerator());
-      this->GetLocalGenerator()->AppendCustomCommandDeps(ccg, deps);
-      this->GetLocalGenerator()->AppendCustomCommandLines(ccg, commands);
-      std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
-      std::transform(ccByproducts.begin(), ccByproducts.end(),
-                     std::back_inserter(util_outputs), MapToNinjaPath());
-      if (ci.GetUsesTerminal()) {
-        uses_terminal = true;
+  {
+    std::array<std::vector<cmCustomCommand> const*, 2> const cmdLists = {
+      { &genTarget->GetPreBuildCommands(), &genTarget->GetPostBuildCommands() }
+    };
+
+    for (std::vector<cmCustomCommand> const* cmdList : cmdLists) {
+      for (cmCustomCommand const& ci : *cmdList) {
+        cmCustomCommandGenerator ccg(ci, this->GetConfigName(), lg);
+        lg->AppendCustomCommandDeps(ccg, deps);
+        lg->AppendCustomCommandLines(ccg, commands);
+        std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
+        std::transform(ccByproducts.begin(), ccByproducts.end(),
+                       std::back_inserter(util_outputs), MapToNinjaPath());
+        if (ci.GetUsesTerminal()) {
+          uses_terminal = true;
+        }
       }
     }
   }
 
-  std::vector<cmSourceFile*> sources;
-  std::string config =
-    this->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
-  this->GetGeneratorTarget()->GetSourceFiles(sources, config);
-  for (cmSourceFile const* source : sources) {
-    if (cmCustomCommand const* cc = source->GetCustomCommand()) {
-      cmCustomCommandGenerator ccg(*cc, this->GetConfigName(),
-                                   this->GetLocalGenerator());
-      this->GetLocalGenerator()->AddCustomCommandTarget(
-        cc, this->GetGeneratorTarget());
-
-      // Depend on all custom command outputs.
-      const std::vector<std::string>& ccOutputs = ccg.GetOutputs();
-      const std::vector<std::string>& ccByproducts = ccg.GetByproducts();
-      std::transform(ccOutputs.begin(), ccOutputs.end(),
-                     std::back_inserter(deps), MapToNinjaPath());
-      std::transform(ccByproducts.begin(), ccByproducts.end(),
-                     std::back_inserter(deps), MapToNinjaPath());
+  {
+    std::string const& config =
+      this->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
+    std::vector<cmSourceFile*> sources;
+    genTarget->GetSourceFiles(sources, config);
+    for (cmSourceFile const* source : sources) {
+      if (cmCustomCommand const* cc = source->GetCustomCommand()) {
+        cmCustomCommandGenerator ccg(*cc, this->GetConfigName(), lg);
+        lg->AddCustomCommandTarget(cc, genTarget);
+
+        // Depend on all custom command outputs.
+        const std::vector<std::string>& ccOutputs = ccg.GetOutputs();
+        const std::vector<std::string>& ccByproducts = ccg.GetByproducts();
+        std::transform(ccOutputs.begin(), ccOutputs.end(),
+                       std::back_inserter(deps), MapToNinjaPath());
+        std::transform(ccByproducts.begin(), ccByproducts.end(),
+                       std::back_inserter(deps), MapToNinjaPath());
+      }
     }
   }
 
-  this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
-                                                 outputs);
-  this->GetLocalGenerator()->AppendTargetDepends(this->GetGeneratorTarget(),
-                                                 deps);
+  lg->AppendTargetOutputs(genTarget, phonyBuild.Outputs);
+  lg->AppendTargetDepends(genTarget, deps);
 
   if (commands.empty()) {
-    this->GetGlobalGenerator()->WritePhonyBuild(
-      this->GetBuildFileStream(),
-      "Utility command for " + this->GetTargetName(), outputs, deps);
+    phonyBuild.Comment = "Utility command for " + this->GetTargetName();
+    phonyBuild.ExplicitDeps = std::move(deps);
+    gg->WriteBuild(this->GetBuildFileStream(), phonyBuild);
   } else {
-    std::string command = this->GetLocalGenerator()->BuildCommandLine(
-      commands, "utility", this->GeneratorTarget);
-    const char* echoStr =
-      this->GetGeneratorTarget()->GetProperty("EchoString");
+    std::string command =
+      lg->BuildCommandLine(commands, "utility", this->GeneratorTarget);
     std::string desc;
+    const char* echoStr = genTarget->GetProperty("EchoString");
     if (echoStr) {
       desc = echoStr;
     } else {
@@ -108,18 +110,12 @@ void cmNinjaUtilityTargetGenerator::Generate()
     // makefile vars.
     cmSystemTools::ReplaceString(
       command, "$(CMAKE_SOURCE_DIR)",
-      this->GetLocalGenerator()
-        ->ConvertToOutputFormat(
-          this->GetLocalGenerator()->GetSourceDirectory(),
-          cmOutputConverter::SHELL)
-        .c_str());
+      lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
+                                cmOutputConverter::SHELL));
     cmSystemTools::ReplaceString(
       command, "$(CMAKE_BINARY_DIR)",
-      this->GetLocalGenerator()
-        ->ConvertToOutputFormat(
-          this->GetLocalGenerator()->GetBinaryDirectory(),
-          cmOutputConverter::SHELL)
-        .c_str());
+      lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
+                                cmOutputConverter::SHELL));
     cmSystemTools::ReplaceString(command, "$(ARGS)", "");
 
     if (command.find('$') != std::string::npos) {
@@ -127,24 +123,22 @@ void cmNinjaUtilityTargetGenerator::Generate()
     }
 
     for (std::string const& util_output : util_outputs) {
-      this->GetGlobalGenerator()->SeenCustomCommandOutput(util_output);
+      gg->SeenCustomCommandOutput(util_output);
     }
 
-    this->GetGlobalGenerator()->WriteCustomCommandBuild(
-      command, desc, "Utility command for " + this->GetTargetName(),
-      /*depfile*/ "", /*job_pool*/ "", uses_terminal,
-      /*restat*/ true, util_outputs, deps);
+    gg->WriteCustomCommandBuild(command, desc,
+                                "Utility command for " + this->GetTargetName(),
+                                /*depfile*/ "", /*job_pool*/ "", uses_terminal,
+                                /*restat*/ true, util_outputs, deps);
 
-    this->GetGlobalGenerator()->WritePhonyBuild(
-      this->GetBuildFileStream(), "", outputs,
-      cmNinjaDeps(1, utilCommandName));
+    phonyBuild.ExplicitDeps.push_back(utilCommandName);
+    gg->WriteBuild(this->GetBuildFileStream(), phonyBuild);
   }
 
   // Add an alias for the logical target name regardless of what directory
   // contains it.  Skip this for GLOBAL_TARGET because they are meant to
   // be per-directory and have one at the top-level anyway.
-  if (this->GetGeneratorTarget()->GetType() != cmStateEnums::GLOBAL_TARGET) {
-    this->GetGlobalGenerator()->AddTargetAlias(this->GetTargetName(),
-                                               this->GetGeneratorTarget());
+  if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) {
+    gg->AddTargetAlias(this->GetTargetName(), genTarget);
   }
 }