clang: Add support for C++ modulemap files

This is a first step to support Clang modules in C++ compilation. This
change adds .modulemap as a source file extension which will be compiled
by cxx_module to a .pcm.

The subsequent patch then uses a new "module_deps" entry to refer to
these modules.

Additional test reference here:
https://fuchsia-review.googlesource.com/c/fuchsia/+/412605

Change-Id: Ic42af141b11212249dc55911a42f89268537d59a
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9601
Reviewed-by: Petr Hosek <phosek@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Scott Graham <scottmg@chromium.org>
diff --git a/src/gn/binary_target_generator.cc b/src/gn/binary_target_generator.cc
index 9811a73..6c085e6 100644
--- a/src/gn/binary_target_generator.cc
+++ b/src/gn/binary_target_generator.cc
@@ -87,6 +87,7 @@
     const auto& source = target_->sources()[i];
     switch (source.type()) {
       case SourceFile::SOURCE_CPP:
+      case SourceFile::SOURCE_MODULEMAP:
       case SourceFile::SOURCE_H:
       case SourceFile::SOURCE_C:
       case SourceFile::SOURCE_M:
diff --git a/src/gn/c_tool.cc b/src/gn/c_tool.cc
index 7c06364..dc391d1 100644
--- a/src/gn/c_tool.cc
+++ b/src/gn/c_tool.cc
@@ -8,6 +8,7 @@
 
 const char* CTool::kCToolCc = "cc";
 const char* CTool::kCToolCxx = "cxx";
+const char* CTool::kCToolCxxModule = "cxx_module";
 const char* CTool::kCToolObjC = "objc";
 const char* CTool::kCToolObjCxx = "objcxx";
 const char* CTool::kCToolRc = "rc";
@@ -38,9 +39,9 @@
 }
 
 bool CTool::ValidateName(const char* name) const {
-  return name == kCToolCc || name == kCToolCxx || name == kCToolObjC ||
-         name == kCToolObjCxx || name == kCToolRc || name == kCToolAsm ||
-         name == kCToolAlink || name == kCToolSolink ||
+  return name == kCToolCc || name == kCToolCxx || name == kCToolCxxModule ||
+         name == kCToolObjC || name == kCToolObjCxx || name == kCToolRc ||
+         name == kCToolAsm || name == kCToolAlink || name == kCToolSolink ||
          name == kCToolSolinkModule || name == kCToolLink;
 }
 
@@ -217,8 +218,9 @@
 }
 
 bool CTool::ValidateSubstitution(const Substitution* sub_type) const {
-  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolObjC ||
-      name_ == kCToolObjCxx || name_ == kCToolRc || name_ == kCToolAsm)
+  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolCxxModule ||
+      name_ == kCToolObjC || name_ == kCToolObjCxx || name_ == kCToolRc ||
+      name_ == kCToolAsm)
     return IsValidCompilerSubstitution(sub_type);
   else if (name_ == kCToolAlink)
     return IsValidALinkSubstitution(sub_type);
@@ -230,8 +232,9 @@
 }
 
 bool CTool::ValidateOutputSubstitution(const Substitution* sub_type) const {
-  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolObjC ||
-      name_ == kCToolObjCxx || name_ == kCToolRc || name_ == kCToolAsm)
+  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolCxxModule ||
+      name_ == kCToolObjC || name_ == kCToolObjCxx || name_ == kCToolRc ||
+      name_ == kCToolAsm)
     return IsValidCompilerOutputsSubstitution(sub_type);
   // ALink uses the standard output file patterns as other linker tools.
   else if (name_ == kCToolAlink || name_ == kCToolSolink ||
diff --git a/src/gn/c_tool.h b/src/gn/c_tool.h
index 8c5282d..01b2431 100644
--- a/src/gn/c_tool.h
+++ b/src/gn/c_tool.h
@@ -22,6 +22,7 @@
   // C compiler tools
   static const char* kCToolCc;
   static const char* kCToolCxx;
+  static const char* kCToolCxxModule;
   static const char* kCToolObjC;
   static const char* kCToolObjCxx;
   static const char* kCToolRc;
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
index ac63c2e..a6642d1 100644
--- a/src/gn/function_toolchain.cc
+++ b/src/gn/function_toolchain.cc
@@ -284,6 +284,7 @@
     Compiler tools:
       "cc": C compiler
       "cxx": C++ compiler
+      "cxx_module": C++ compiler used for Clang .modulemap files
       "objc": Objective C compiler
       "objcxx": Objective C++ compiler
       "rc": Resource compiler (Windows .rc files)
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index bc9907a..fbd6ff4 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -1477,3 +1477,39 @@
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 }
+
+TEST_F(NinjaCBinaryTargetWriterTest, ModuleMapInStaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  std::unique_ptr<Tool> cxx_module_tool =
+      Tool::CreateTool(CTool::kCToolCxxModule);
+  cxx_module_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm"));
+  setup.toolchain()->SetTool(std::move(cxx_module_tool));
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/bar.modulemap"));
+  target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.bar.pcm: cxx_module ../../foo/bar.modulemap\n"
+      "\n"
+      "build obj/foo/libbar.a: alink obj/foo/libbar.bar.pcm\n"
+      "  arflags =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
diff --git a/src/gn/source_file.cc b/src/gn/source_file.cc
index 1dacbfa..0b0e8e5 100644
--- a/src/gn/source_file.cc
+++ b/src/gn/source_file.cc
@@ -35,6 +35,8 @@
     return SourceFile::SOURCE_M;
   if (extension == "mm")
     return SourceFile::SOURCE_MM;
+  if (extension == "modulemap")
+    return SourceFile::SOURCE_MODULEMAP;
   if (extension == "rc")
     return SourceFile::SOURCE_RC;
   if (extension == "S" || extension == "s" || extension == "asm")
@@ -105,7 +107,8 @@
 }
 
 bool SourceFileTypeSet::CSourceUsed() const {
-  return empty_ || Get(SourceFile::SOURCE_CPP) || Get(SourceFile::SOURCE_H) ||
+  return empty_ || Get(SourceFile::SOURCE_CPP) ||
+         Get(SourceFile::SOURCE_MODULEMAP) || Get(SourceFile::SOURCE_H) ||
          Get(SourceFile::SOURCE_C) || Get(SourceFile::SOURCE_M) ||
          Get(SourceFile::SOURCE_MM) || Get(SourceFile::SOURCE_RC) ||
          Get(SourceFile::SOURCE_S) || Get(SourceFile::SOURCE_O) ||
diff --git a/src/gn/source_file.h b/src/gn/source_file.h
index d7ed06e..1e156fe 100644
--- a/src/gn/source_file.h
+++ b/src/gn/source_file.h
@@ -33,6 +33,7 @@
     SOURCE_H,
     SOURCE_M,
     SOURCE_MM,
+    SOURCE_MODULEMAP,
     SOURCE_S,
     SOURCE_RC,
     SOURCE_O,  // Object files can be inputs, too. Also counts .obj.
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index 09d7a1a..99ce6b0 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -1492,3 +1492,33 @@
   EXPECT_TRUE(data_dep_present.OnResolved(&err));
   EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
 }
+
+// Tests that modulemap files use the cxx_module tool.
+TEST_F(TargetTest, ModuleMap) {
+  TestWithScope setup;
+
+  Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolCxxModule);
+  CTool* cxx_module = tool->AsC();
+  cxx_module->set_outputs(
+      SubstitutionList::MakeForTest("{{source_file_part}}.pcm"));
+  toolchain.SetTool(std::move(tool));
+
+  Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.SetToolchain(&toolchain);
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const char* computed_tool_type = nullptr;
+  std::vector<OutputFile> output;
+  bool result = target.GetOutputFilesForSource(
+      SourceFile("//source/input.modulemap"), &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+  EXPECT_EQ(std::string("cxx_module"), std::string(computed_tool_type));
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  ASSERT_EQ(1u, output.size());
+  EXPECT_EQ("input.modulemap.pcm", output[0].value()) << output[0].value();
+}
diff --git a/src/gn/tool.cc b/src/gn/tool.cc
index 99b7525..9fc10ce 100644
--- a/src/gn/tool.cc
+++ b/src/gn/tool.cc
@@ -253,6 +253,8 @@
     return std::make_unique<CTool>(CTool::kCToolCc);
   else if (name == CTool::kCToolCxx)
     return std::make_unique<CTool>(CTool::kCToolCxx);
+  else if (name == CTool::kCToolCxxModule)
+    return std::make_unique<CTool>(CTool::kCToolCxxModule);
   else if (name == CTool::kCToolObjC)
     return std::make_unique<CTool>(CTool::kCToolObjC);
   else if (name == CTool::kCToolObjCxx)
@@ -308,6 +310,8 @@
       return CTool::kCToolCc;
     case SourceFile::SOURCE_CPP:
       return CTool::kCToolCxx;
+    case SourceFile::SOURCE_MODULEMAP:
+      return CTool::kCToolCxxModule;
     case SourceFile::SOURCE_M:
       return CTool::kCToolObjC;
     case SourceFile::SOURCE_MM: