Refactor C++ module dependency logic into a new utility

This change refactors the C++ module dependency logic out of
ninja_c_binary_target_writer.cc and into a new utility,
ninja_module_writer_util.cc and .h. This consolidation makes the code
cleaner and prepares it for reuse in other areas, like the
compile_commands_writer in
https://gn-review.googlesource.com/c/gn/+/19840

Bug: 443228626
Change-Id: Ide370ddde6baee588aae6e658e0f1cb550fb6e1d
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/19880
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index d3ecfdc..5d161e4 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -736,6 +736,7 @@
         'src/gn/ninja_create_bundle_target_writer.cc',
         'src/gn/ninja_generated_file_target_writer.cc',
         'src/gn/ninja_group_target_writer.cc',
+        'src/gn/ninja_module_writer_util.cc',
         'src/gn/ninja_outputs_writer.cc',
         'src/gn/ninja_rust_binary_target_writer.cc',
         'src/gn/ninja_target_command_util.cc',
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index 83a95d0..94580e9 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -19,6 +19,7 @@
 #include "gn/escape.h"
 #include "gn/filesystem_utils.h"
 #include "gn/general_tool.h"
+#include "gn/ninja_module_writer_util.h"
 #include "gn/ninja_target_command_util.h"
 #include "gn/ninja_utils.h"
 #include "gn/pool.h"
@@ -28,29 +29,6 @@
 #include "gn/substitution_writer.h"
 #include "gn/target.h"
 
-struct ModuleDep {
-  ModuleDep(const SourceFile* modulemap,
-            const std::string& module_name,
-            const OutputFile& pcm,
-            bool is_self)
-      : modulemap(modulemap),
-        module_name(module_name),
-        pcm(pcm),
-        is_self(is_self) {}
-
-  // The input module.modulemap source file.
-  const SourceFile* modulemap;
-
-  // The internal module name, in GN this is the target's label.
-  std::string module_name;
-
-  // The compiled version of the module.
-  OutputFile pcm;
-
-  // Is this the module for the current target.
-  bool is_self;
-};
-
 namespace {
 
 // Returns the proper escape options for writing compiler and linker flags.
@@ -75,63 +53,6 @@
   return "";
 }
 
-const SourceFile* GetModuleMapFromTargetSources(const Target* target) {
-  for (const SourceFile& sf : target->sources()) {
-    if (sf.IsModuleMapType())
-      return &sf;
-  }
-  return nullptr;
-}
-
-std::vector<ModuleDep> GetModuleDepsInformation(
-    const Target* target,
-    const ResolvedTargetData& resolved) {
-  std::vector<ModuleDep> ret;
-  // Use a set to keep track of added PCM files to ensure uniqueness.
-  std::set<OutputFile> added_pcms;
-
-  auto add_if_new = [&added_pcms, &ret](const Target* t, bool is_self) {
-    const SourceFile* modulemap = GetModuleMapFromTargetSources(t);
-    if (!modulemap)  // Not a module or no .modulemap file.
-      return;
-
-    std::string label;
-    CHECK(SubstitutionWriter::GetTargetSubstitution(
-        t, &SubstitutionLabelNoToolchain, &label));
-
-    const char* tool_type;
-    std::vector<OutputFile> modulemap_outputs;
-    CHECK(
-        t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs));
-    // Must be only one .pcm from .modulemap.
-    CHECK(modulemap_outputs.size() == 1u);
-    const OutputFile& pcm_file = modulemap_outputs[0];
-
-    if (added_pcms.insert(pcm_file).second) {
-      ret.emplace_back(modulemap, label, pcm_file, is_self);
-    }
-  };
-
-  if (target->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
-    add_if_new(target, true);
-  }
-
-  // Process direct dependencies and their publicly inherited modules.
-  for (const auto& pairs : resolved.GetModuleDepsInformation(target)) {
-    const Target* dep = pairs.target();
-    if (dep->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
-      add_if_new(dep, false);
-    }
-  }
-
-  // Sort by pcm path for deterministic output.
-  std::sort(ret.begin(), ret.end(), [](const ModuleDep& a, const ModuleDep& b) {
-    return a.pcm < b.pcm;
-  });
-
-  return ret;
-}
-
 }  // namespace
 
 NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
@@ -142,7 +63,7 @@
 NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
 
 void NinjaCBinaryTargetWriter::Run() {
-  std::vector<ModuleDep> module_dep_info =
+  std::vector<ClangModuleDep> module_dep_info =
       GetModuleDepsInformation(target_, resolved());
 
   WriteCompilerVars(module_dep_info);
@@ -241,7 +162,7 @@
 }
 
 void NinjaCBinaryTargetWriter::WriteCompilerVars(
-    const std::vector<ModuleDep>& module_dep_info) {
+    const std::vector<ClangModuleDep>& module_dep_info) {
   const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
 
   WriteCCompilerVars(subst, /*indent=*/false,
@@ -263,7 +184,7 @@
 
 void NinjaCBinaryTargetWriter::WriteModuleDepsSubstitution(
     const Substitution* substitution,
-    const std::vector<ModuleDep>& module_dep_info,
+    const std::vector<ClangModuleDep>& module_dep_info,
     bool include_self) {
   if (target_->toolchain()->substitution_bits().used.count(substitution)) {
     EscapeOptions options;
@@ -442,7 +363,7 @@
     const std::vector<OutputFile>& pch_deps,
     const std::vector<OutputFile>& input_deps,
     const std::vector<OutputFile>& order_only_deps,
-    const std::vector<ModuleDep>& module_dep_info,
+    const std::vector<ClangModuleDep>& module_dep_info,
     std::vector<OutputFile>* object_files,
     std::vector<SourceFile>* other_files) {
   DCHECK(!target_->source_types_used().SwiftSourceUsed());
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h
index fd277d7..8baa434 100644
--- a/src/gn/ninja_c_binary_target_writer.h
+++ b/src/gn/ninja_c_binary_target_writer.h
@@ -11,7 +11,7 @@
 #include "gn/unique_vector.h"
 
 struct EscapeOptions;
-struct ModuleDep;
+struct ClangModuleDep;
 
 // Writes a .ninja file for a binary target type (an executable, a shared
 // library, or a static library).
@@ -26,12 +26,12 @@
   using OutputFileSet = std::set<OutputFile>;
 
   // Writes all flags for the compiler: includes, defines, cflags, etc.
-  void WriteCompilerVars(const std::vector<ModuleDep>& module_dep_info);
+  void WriteCompilerVars(const std::vector<ClangModuleDep>& module_dep_info);
 
   // Write module_deps or module_deps_no_self flags for clang modulemaps.
   void WriteModuleDepsSubstitution(
       const Substitution* substitution,
-      const std::vector<ModuleDep>& module_dep_info,
+      const std::vector<ClangModuleDep>& module_dep_info,
       bool include_self);
 
   // Writes build lines required for precompiled headers. Any generated
@@ -77,7 +77,7 @@
   void WriteSources(const std::vector<OutputFile>& pch_deps,
                     const std::vector<OutputFile>& input_deps,
                     const std::vector<OutputFile>& order_only_deps,
-                    const std::vector<ModuleDep>& module_dep_info,
+                    const std::vector<ClangModuleDep>& module_dep_info,
                     std::vector<OutputFile>* object_files,
                     std::vector<SourceFile>* other_files);
   void WriteSwiftSources(const std::vector<OutputFile>& input_deps,
diff --git a/src/gn/ninja_module_writer_util.cc b/src/gn/ninja_module_writer_util.cc
new file mode 100644
index 0000000..b08aafd
--- /dev/null
+++ b/src/gn/ninja_module_writer_util.cc
@@ -0,0 +1,78 @@
+// Copyright 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_module_writer_util.h"
+
+#include <algorithm>
+#include <set>
+
+#include "gn/resolved_target_data.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+namespace {
+
+// Returns the first source file in the target's sources that is a modulemap
+// file. Returns nullptr if no modulemap file is found.
+const SourceFile* GetModuleMapFromTargetSources(const Target* target) {
+  for (const SourceFile& sf : target->sources()) {
+    if (sf.IsModuleMapType())
+      return &sf;
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+ClangModuleDep::ClangModuleDep(const SourceFile* modulemap,
+                               const std::string& module_name,
+                               const OutputFile& pcm,
+                               bool is_self)
+    : modulemap(modulemap),
+      module_name(module_name),
+      pcm(pcm),
+      is_self(is_self) {}
+
+std::vector<ClangModuleDep> GetModuleDepsInformation(
+    const Target* target,
+    const ResolvedTargetData& resolved) {
+  std::vector<ClangModuleDep> ret;
+  // Use a set to keep track of added PCM files to ensure uniqueness.
+  std::set<OutputFile> added_pcms;
+
+  auto add_if_new = [&added_pcms, &ret](const Target* t, bool is_self) {
+    const SourceFile* modulemap = GetModuleMapFromTargetSources(t);
+    if (!modulemap)  // Not a module or no .modulemap file.
+      return;
+
+    std::string label;
+    CHECK(SubstitutionWriter::GetTargetSubstitution(
+        t, &SubstitutionLabelNoToolchain, &label));
+
+    const char* tool_type;
+    std::vector<OutputFile> modulemap_outputs;
+    CHECK(
+        t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs));
+    CHECK(modulemap_outputs.size() == 1u);  // Must be only one .pcm.
+    const OutputFile& pcm_file = modulemap_outputs[0];
+
+    if (added_pcms.insert(pcm_file).second) {
+      ret.emplace_back(modulemap, label, pcm_file, is_self);
+    }
+  };
+
+  if (target->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
+    add_if_new(target, true);
+
+  for (const auto& pair : resolved.GetModuleDepsInformation(target))
+    add_if_new(pair.target(), false);
+
+  // Sort by pcm path for deterministic output.
+  std::sort(ret.begin(), ret.end(),
+            [](const ClangModuleDep& a, const ClangModuleDep& b) {
+              return a.pcm < b.pcm;
+            });
+
+  return ret;
+}
diff --git a/src/gn/ninja_module_writer_util.h b/src/gn/ninja_module_writer_util.h
new file mode 100644
index 0000000..19a6444
--- /dev/null
+++ b/src/gn/ninja_module_writer_util.h
@@ -0,0 +1,41 @@
+// Copyright 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_MODULE_WRITER_UTIL_H_
+#define TOOLS_GN_NINJA_MODULE_WRITER_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "gn/output_file.h"
+
+class ResolvedTargetData;
+class SourceFile;
+class Target;
+
+struct ClangModuleDep {
+  ClangModuleDep(const SourceFile* modulemap,
+                 const std::string& module_name,
+                 const OutputFile& pcm,
+                 bool is_self);
+
+  // The input module.modulemap source file.
+  const SourceFile* modulemap;
+
+  // The internal module name, in GN this is the target's label.
+  std::string module_name;
+
+  // The compiled version of the module.
+  OutputFile pcm;
+
+  // Is this the module for the current target.
+  bool is_self;
+};
+
+// Gathers information about all module dependencies for a given target.
+std::vector<ClangModuleDep> GetModuleDepsInformation(
+    const Target* target,
+    const ResolvedTargetData& resolved);
+
+#endif  // TOOLS_GN_NINJA_MODULE_WRITER_UTIL_H_