Propagate module dependencies transitively via public_deps

When using C++ modules, dependencies must be treated as transitive. If a
target `A` has a `public_deps` on module `B`, and `B` in turn has a
`public_deps` on module `C`, any target that depends on `A` must also be
provided with the dependency on `C`.

Previously, GN did not propagate these module dependencies recursively.
This meant the dependency on `C` was not passed to `A`'s dependents,
which could lead to compilation failures. This behavior is incorrect, as
dependencies between C++ modules are analogous to transitive header
inclusions.

This change fixes this by recursively traversing the `public_deps` graph
in `ResolvedTargetData` to collect the complete set of transitive module
dependencies. This ensures that all module dependencies are correctly
passed to the compiler.

Bug: 40440396
Change-Id: Ib7b0e09477705708c92e6981b651c0eb9d6617e3
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/19100
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index 6e27c68..cdfce61 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -87,10 +87,13 @@
     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 = [&ret](const Target* t, bool is_self) {
+  auto add_if_new = [&added_pcms, &ret](const Target* t, bool is_self) {
     const SourceFile* modulemap = GetModuleMapFromTargetSources(t);
-    CHECK(modulemap);
+    if (!modulemap)  // Not a module or no .modulemap file.
+      return;
 
     std::string label;
     CHECK(SubstitutionWriter::GetTargetSubstitution(
@@ -102,20 +105,30 @@
         t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs));
     // Must be only one .pcm from .modulemap.
     CHECK(modulemap_outputs.size() == 1u);
-    ret.emplace_back(modulemap, label, modulemap_outputs[0], is_self);
+    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(target, true);
+    add_if_new(target, true);
   }
 
-  for (const Target* dep : resolved.GetLinkedDeps(target)) {
-    // Having a .modulemap source means that the dependency is modularized.
+  // 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(dep, false);
+      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;
 }
 
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index ce6d72b..80f453d 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -2616,6 +2616,7 @@
   target2.sources().push_back(SourceFile("//stuff/b.h"));
   target2.source_types_used().Set(SourceFile::SOURCE_CPP);
   target2.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  target2.public_deps().push_back(LabelTargetPair(&target));
   target2.SetToolchain(&module_toolchain);
   ASSERT_TRUE(target2.OnResolved(&err));
 
@@ -2629,21 +2630,21 @@
 include_dirs =
 cflags =
 cflags_cc =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libb.b.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
 label = //stuff$:b
 root_out_dir = withmodules
 target_out_dir = obj/stuff
 target_output_name = libb
 
-build obj/stuff/libb.b.pcm: cxx_module ../../stuff/b.modulemap
+build obj/stuff/libb.b.pcm: cxx_module ../../stuff/b.modulemap | obj/blah/liba.a.pcm
   source_file_part = b.modulemap
   source_name_part = b
-build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/stuff/libb.b.pcm
+build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
   source_file_part = b.cc
   source_name_part = b
 
-build obj/stuff/libb.a: alink obj/stuff/libb.b.o
+build obj/stuff/libb.a: alink obj/stuff/libb.b.o || obj/blah/liba.a
   arflags =
   output_extension =
   output_dir =
@@ -2658,7 +2659,7 @@
   target3.visibility().SetPublic();
   target3.sources().push_back(SourceFile("//stuff/c.modulemap"));
   target3.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
-  target3.private_deps().push_back(LabelTargetPair(&target));
+  target3.public_deps().push_back(LabelTargetPair(&target2));
   target3.SetToolchain(&module_toolchain);
   ASSERT_TRUE(target3.OnResolved(&err));
 
@@ -2673,18 +2674,18 @@
 include_dirs =
 cflags =
 cflags_cc =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libc.c.pcm -fmodule-file=obj/blah/liba.a.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm -fmodule-file=obj/stuff/libc.c.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
 label = //things$:c
 root_out_dir = withmodules
 target_out_dir = obj/things
 target_output_name = libc
 
-build obj/stuff/libc.c.pcm: cxx_module ../../stuff/c.modulemap | obj/blah/liba.a.pcm
+build obj/stuff/libc.c.pcm: cxx_module ../../stuff/c.modulemap | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
   source_file_part = c.modulemap
   source_name_part = c
 
-build obj/things/libc.a: alink || obj/blah/liba.a
+build obj/things/libc.a: alink || obj/stuff/libb.a obj/blah/liba.a
   arflags =
   output_extension =
   output_dir =
@@ -2699,7 +2700,6 @@
   depender.sources().push_back(SourceFile("//zap/x.cc"));
   depender.sources().push_back(SourceFile("//zap/y.cc"));
   depender.source_types_used().Set(SourceFile::SOURCE_CPP);
-  depender.private_deps().push_back(LabelTargetPair(&target));
   depender.private_deps().push_back(LabelTargetPair(&target2));
   depender.SetToolchain(&module_toolchain);
   ASSERT_TRUE(depender.OnResolved(&err));
@@ -2728,7 +2728,7 @@
   source_file_part = y.cc
   source_name_part = y
 
-build withmodules/c: link obj/zap/c.x.o obj/zap/c.y.o obj/blah/liba.a obj/stuff/libb.a
+build withmodules/c: link obj/zap/c.x.o obj/zap/c.y.o obj/stuff/libb.a obj/blah/liba.a
   ldflags =
   libs =
   frameworks =
diff --git a/src/gn/resolved_target_data.cc b/src/gn/resolved_target_data.cc
index 7c3e79f..30433b0 100644
--- a/src/gn/resolved_target_data.cc
+++ b/src/gn/resolved_target_data.cc
@@ -172,6 +172,39 @@
   }
 }
 
+void ResolvedTargetData::ComputeModuleDepsInformation(TargetInfo* info) const {
+  TargetPublicPairListBuilder module_deps_information;
+
+  ComputeModuleDepsInformationFor(info->deps.public_deps(), true,
+                                  &module_deps_information);
+  ComputeModuleDepsInformationFor(info->deps.private_deps(), false,
+                                  &module_deps_information);
+
+  info->module_deps_information = module_deps_information.Build();
+  info->has_module_deps_information = true;
+}
+
+void ResolvedTargetData::ComputeModuleDepsInformationFor(
+    base::span<const Target*> deps,
+    bool is_public,
+    TargetPublicPairListBuilder* module_deps_information) const {
+  for (const Target* dep : deps) {
+    if (dep->output_type() != Target::STATIC_LIBRARY &&
+        dep->output_type() != Target::SHARED_LIBRARY &&
+        dep->output_type() != Target::SOURCE_SET) {
+      continue;
+    }
+
+    module_deps_information->Append(dep, is_public);
+    const TargetInfo* dep_info = GetTargetModuleDepsInformation(dep);
+    for (const auto& pair : dep_info->module_deps_information) {
+      if (pair.is_public()) {
+        module_deps_information->Append(pair.target(), is_public);
+      }
+    }
+  }
+}
+
 void ResolvedTargetData::ComputeRustLibs(TargetInfo* info) const {
   RustLibsBuilder rust_libs;
 
diff --git a/src/gn/resolved_target_data.h b/src/gn/resolved_target_data.h
index 61483bf..a2743fc 100644
--- a/src/gn/resolved_target_data.h
+++ b/src/gn/resolved_target_data.h
@@ -110,6 +110,13 @@
     return GetTargetInheritedLibs(target)->inherited_libs;
   }
 
+  // Retrieves an ordered list of (target, is_public) pairs for all module
+  // dependencies inherited by this target.
+  const std::vector<TargetPublicPair>& GetModuleDepsInformation(
+      const Target* target) const {
+    return GetTargetModuleDepsInformation(target)->module_deps_information;
+  }
+
   // Retrieves an ordered list of (target, is_public) paris for all link-time
   // libraries for Rust-specific binary targets.
   const std::vector<TargetPublicPair>& GetRustInheritedLibraries(
@@ -146,6 +153,7 @@
     bool has_framework_info = false;
     bool has_hard_deps = false;
     bool has_inherited_libs = false;
+    bool has_module_deps_information = false;
     bool has_rust_libs = false;
     bool has_swift_values = false;
 
@@ -164,6 +172,9 @@
     // Only valid if |has_inherited_libs| is true.
     std::vector<TargetPublicPair> inherited_libs;
 
+    // Only valid if |has_module_deps_information| is true.
+    std::vector<TargetPublicPair> module_deps_information;
+
     // Only valid if |has_rust_libs| is true.
     std::vector<TargetPublicPair> rust_inherited_libs;
     std::vector<TargetPublicPair> rust_inheritable_libs;
@@ -224,6 +235,15 @@
     return info;
   }
 
+  const TargetInfo* GetTargetModuleDepsInformation(const Target* target) const {
+    TargetInfo* info = GetTargetInfo(target);
+    if (!info->has_module_deps_information) {
+      ComputeModuleDepsInformation(info);
+      DCHECK(info->has_module_deps_information);
+    }
+    return info;
+  }
+
   const TargetInfo* GetTargetRustLibs(const Target* target) const {
     TargetInfo* info = GetTargetInfo(target);
     if (!info->has_rust_libs) {
@@ -249,6 +269,7 @@
   void ComputeFrameworkInfo(TargetInfo* info) const;
   void ComputeHardDeps(TargetInfo* info) const;
   void ComputeInheritedLibs(TargetInfo* info) const;
+  void ComputeModuleDepsInformation(TargetInfo* info) const;
   void ComputeRustLibs(TargetInfo* info) const;
   void ComputeSwiftValues(TargetInfo* info) const;
 
@@ -258,6 +279,12 @@
       bool is_public,
       TargetPublicPairListBuilder* inherited_libraries) const;
 
+  // Helper function used by ComputeModuleDepsInformation().
+  void ComputeModuleDepsInformationFor(
+      base::span<const Target*> deps,
+      bool is_public,
+      TargetPublicPairListBuilder* module_deps_information) const;
+
   // Helper data structure and function used by ComputeRustLibs().
   struct RustLibsBuilder {
     TargetPublicPairListBuilder inherited;
diff --git a/src/gn/swift_values.h b/src/gn/swift_values.h
index 1dde1c2..c7f4f01 100644
--- a/src/gn/swift_values.h
+++ b/src/gn/swift_values.h
@@ -56,7 +56,7 @@
                                std::vector<SourceFile>* result) const;
 
  private:
-  // Fill informations about .swiftmodule generated by this target.
+  // Fill information about .swiftmodule generated by this target.
   bool FillModuleOutputFile(Target* target, Err* err);
 
   // Name of the optional bridge header used to import Objective-C classes.
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 53ed8ad..6cb5572 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -881,7 +881,7 @@
       bundle_data().AddBundleData(pair.ptr, is_create_bundle);
     }
 
-    // Recursive bundle_data informations from all dependencies.
+    // Recursive bundle_data information from all dependencies.
     if (pair.ptr->has_bundle_data()) {
       for (const auto* target : pair.ptr->bundle_data().forwarded_bundle_deps())
         bundle_data().AddBundleData(target, is_create_bundle);
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
index 9c960e5..4a75cb3 100644
--- a/src/gn/xcode_writer.h
+++ b/src/gn/xcode_writer.h
@@ -92,7 +92,7 @@
   // significant performance improvement).
   //
   // Extra behaviour is controlled by the |options| parameter. See comments
-  // of the Options type for more informations.
+  // of the Options type for more information.
   //
   // Returns true on success, fails on failure. |err| is set in that case.
   static bool RunAndWriteFiles(const BuildSettings* build_settings,