[clang][deps] Handle modular dependencies present in PCH
authorJan Svoboda <jan_svoboda@apple.com>
Mon, 14 Jun 2021 09:30:26 +0000 (11:30 +0200)
committerJan Svoboda <jan_svoboda@apple.com>
Mon, 14 Jun 2021 09:59:35 +0000 (11:59 +0200)
When a translation unit uses a PCH and imports the same modules as the PCH, we'd prefer to resolve to those modules instead of inventing new modules and reporting them as modular dependencies. Since the PCH modules have already been built nudge the compiler to reuse them when deciding whether to build a new module and don't report them as regular modular dependencies.

Depends on D103524 & D103802.

Reviewed By: dexonsmith

Differential Revision: https://reviews.llvm.org/D103526

16 files changed:
clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h
clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h
clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h
clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp
clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp
clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
clang/test/ClangScanDeps/Inputs/modules-pch/cdb_pch.json [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/cdb_tu_with_common.json [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_1.h [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_2.h [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/mod_pch.h [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/mod_tu_with_common.h [new file with mode: 0644]
clang/test/ClangScanDeps/Inputs/modules-pch/module.modulemap
clang/test/ClangScanDeps/Inputs/modules-pch/pch.h
clang/test/ClangScanDeps/Inputs/modules-pch/tu_with_common.c [new file with mode: 0644]
clang/test/ClangScanDeps/modules-pch.c

index 89a70fb..f88dc47 100644 (file)
@@ -31,6 +31,10 @@ struct FullDependencies {
   /// directly depends on, not including transitive dependencies.
   std::vector<std::string> FileDeps;
 
+  /// A collection of prebuilt modules this translation unit directly depends
+  /// on, not including transitive dependencies.
+  std::vector<PrebuiltModuleDep> PrebuiltModuleDeps;
+
   /// A list of modules this translation unit directly depends on, not including
   /// transitive dependencies.
   ///
index 6891193..e51040d 100644 (file)
@@ -37,6 +37,8 @@ public:
   virtual void handleFileDependency(const DependencyOutputOptions &Opts,
                                     StringRef Filename) = 0;
 
+  virtual void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) = 0;
+
   virtual void handleModuleDependency(ModuleDeps MD) = 0;
 
   virtual void handleContextHash(std::string Hash) = 0;
index b8b7882..73bc41d 100644 (file)
@@ -29,6 +29,18 @@ namespace dependencies {
 
 class DependencyConsumer;
 
+/// Modular dependency that has already been built prior to the dependency scan.
+struct PrebuiltModuleDep {
+  std::string ModuleName;
+  std::string PCMFile;
+  std::string ModuleMapFile;
+
+  explicit PrebuiltModuleDep(const Module *M)
+      : ModuleName(M->getTopLevelModuleName()),
+        PCMFile(M->getASTFile()->getName()),
+        ModuleMapFile(M->PresumedModuleMapFile) {}
+};
+
 /// This is used to identify a specific module.
 struct ModuleID {
   /// The name of the module. This may include `:` for C++20 module partitions,
@@ -74,6 +86,10 @@ struct ModuleDeps {
   /// on, not including transitive dependencies.
   llvm::StringSet<> FileDeps;
 
+  /// A collection of prebuilt modular dependencies this module directly depends
+  /// on, not including transitive dependencies.
+  std::vector<PrebuiltModuleDep> PrebuiltModuleDeps;
+
   /// A list of module identifiers this module directly depends on, not
   /// including transitive dependencies.
   ///
@@ -150,9 +166,15 @@ private:
   ModuleDepCollector &MDC;
   /// Working set of direct modular dependencies.
   llvm::DenseSet<const Module *> DirectModularDeps;
+  /// Working set of direct modular dependencies that have already been built.
+  llvm::DenseSet<const Module *> DirectPrebuiltModularDeps;
 
   void handleImport(const Module *Imported);
 
+  /// Adds direct modular dependencies that have already been built to the
+  /// ModuleDeps instance.
+  void addDirectPrebuiltModuleDeps(const Module *M, ModuleDeps &MD);
+
   /// Traverses the previously collected direct modular dependencies to discover
   /// transitive modular dependencies and fills the parent \c ModuleDepCollector
   /// with both.
@@ -168,7 +190,9 @@ private:
 class ModuleDepCollector final : public DependencyCollector {
 public:
   ModuleDepCollector(std::unique_ptr<DependencyOutputOptions> Opts,
-                     CompilerInstance &I, DependencyConsumer &C);
+                     CompilerInstance &I, DependencyConsumer &C,
+                     std::map<std::string, std::string, std::less<>>
+                         OriginalPrebuiltModuleFiles);
 
   void attachToPreprocessor(Preprocessor &PP) override;
   void attachToASTReader(ASTReader &R) override;
@@ -191,6 +215,18 @@ private:
   std::unordered_map<const Module *, ModuleDeps> ModularDeps;
   /// Options that control the dependency output generation.
   std::unique_ptr<DependencyOutputOptions> Opts;
+  /// The mapping between prebuilt module names and module files that were
+  /// present in the original CompilerInvocation.
+  std::map<std::string, std::string, std::less<>> OriginalPrebuiltModuleFiles;
+
+  /// Checks whether the module is known as being prebuilt.
+  bool isPrebuiltModule(const Module *M);
+
+  /// Constructs a CompilerInvocation that can be used to build the given
+  /// module, excluding paths to discovered modular dependencies that are yet to
+  /// be built.
+  CompilerInvocation
+  makeInvocationForModuleBuildWithoutPaths(const ModuleDeps &Deps) const;
 };
 
 } // end namespace dependencies
index 196b05b..409ccd3 100644 (file)
@@ -16,10 +16,7 @@ namespace dependencies{
 std::vector<std::string> FullDependencies::getAdditionalArgs(
     std::function<StringRef(ModuleID)> LookupPCMPath,
     std::function<const ModuleDeps &(ModuleID)> LookupModuleDeps) const {
-  std::vector<std::string> Ret{
-      "-fno-implicit-modules",
-      "-fno-implicit-module-maps",
-  };
+  std::vector<std::string> Ret = getAdditionalArgsWithoutModulePaths();
 
   std::vector<std::string> PCMPaths;
   std::vector<std::string> ModMapPaths;
@@ -35,10 +32,17 @@ std::vector<std::string> FullDependencies::getAdditionalArgs(
 
 std::vector<std::string>
 FullDependencies::getAdditionalArgsWithoutModulePaths() const {
-  return {
+  std::vector<std::string> Args{
       "-fno-implicit-modules",
       "-fno-implicit-module-maps",
   };
+
+  for (const PrebuiltModuleDep &PMD : PrebuiltModuleDeps) {
+    Args.push_back("-fmodule-file=" + PMD.ModuleName + "=" + PMD.PCMFile);
+    Args.push_back("-fmodule-map-file=" + PMD.ModuleMapFile);
+  }
+
+  return Args;
 }
 
 DependencyScanningTool::DependencyScanningTool(
@@ -57,6 +61,10 @@ llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
       Dependencies.push_back(std::string(File));
     }
 
+    void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
+      // Same as `handleModuleDependency`.
+    }
+
     void handleModuleDependency(ModuleDeps MD) override {
       // These are ignored for the make format as it can't support the full
       // set of deps, and handleFileDependency handles enough for implicitly
@@ -125,6 +133,10 @@ DependencyScanningTool::getFullDependencies(
       Dependencies.push_back(std::string(File));
     }
 
+    void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
+      PrebuiltModuleDeps.emplace_back(std::move(PMD));
+    }
+
     void handleModuleDependency(ModuleDeps MD) override {
       ClangModuleDeps[MD.ID.ContextHash + MD.ID.ModuleName] = std::move(MD);
     }
@@ -146,6 +158,8 @@ DependencyScanningTool::getFullDependencies(
           FD.ClangModuleDeps.push_back(MD.ID);
       }
 
+      FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
+
       FullDependenciesResult FDR;
 
       for (auto &&M : ClangModuleDeps) {
@@ -162,6 +176,7 @@ DependencyScanningTool::getFullDependencies(
 
   private:
     std::vector<std::string> Dependencies;
+    std::vector<PrebuiltModuleDep> PrebuiltModuleDeps;
     std::unordered_map<std::string, ModuleDeps> ClangModuleDeps;
     std::string ContextHash;
     std::vector<std::string> OutputPaths;
index 0df515a..ebcd16e 100644 (file)
@@ -45,6 +45,23 @@ private:
   DependencyConsumer &C;
 };
 
+/// A listener that collects the names and paths to imported modules.
+class ImportCollectingListener : public ASTReaderListener {
+public:
+  ImportCollectingListener(
+      std::map<std::string, std::string> &PrebuiltModuleFiles)
+      : PrebuiltModuleFiles(PrebuiltModuleFiles) {}
+
+  bool needsImportVisitation() const override { return true; }
+
+  void visitImport(StringRef ModuleName, StringRef Filename) override {
+    PrebuiltModuleFiles[std::string(ModuleName)] = std::string(Filename);
+  }
+
+private:
+  std::map<std::string, std::string> &PrebuiltModuleFiles;
+};
+
 /// A clang tool that runs the preprocessor in a mode that's optimized for
 /// dependency scanning for the given compiler invocation.
 class DependencyScanningAction : public tooling::ToolAction {
@@ -103,6 +120,25 @@ public:
     Compiler.setFileManager(FileMgr);
     Compiler.createSourceManager(*FileMgr);
 
+    std::map<std::string, std::string> PrebuiltModuleFiles;
+    if (!Compiler.getPreprocessorOpts().ImplicitPCHInclude.empty()) {
+      /// Collect the modules that were prebuilt as part of the PCH.
+      ImportCollectingListener Listener(PrebuiltModuleFiles);
+      ASTReader::readASTFileControlBlock(
+          Compiler.getPreprocessorOpts().ImplicitPCHInclude,
+          Compiler.getFileManager(), Compiler.getPCHContainerReader(),
+          /*FindModuleFileExtensions=*/false, Listener,
+          /*ValidateDiagnosticOptions=*/false);
+    }
+    /// Make a backup of the original prebuilt module file arguments.
+    std::map<std::string, std::string, std::less<>> OrigPrebuiltModuleFiles =
+        Compiler.getHeaderSearchOpts().PrebuiltModuleFiles;
+    /// Configure the compiler with discovered prebuilt modules. This will
+    /// prevent the implicit build of duplicate modules and force reuse of
+    /// existing prebuilt module files instead.
+    Compiler.getHeaderSearchOpts().PrebuiltModuleFiles.insert(
+        PrebuiltModuleFiles.begin(), PrebuiltModuleFiles.end());
+
     // Create the dependency collector that will collect the produced
     // dependencies.
     //
@@ -124,7 +160,8 @@ public:
       break;
     case ScanningOutputFormat::Full:
       Compiler.addDependencyCollector(std::make_shared<ModuleDepCollector>(
-          std::move(Opts), Compiler, Consumer));
+          std::move(Opts), Compiler, Consumer,
+          std::move(OrigPrebuiltModuleFiles)));
       break;
     }
 
index 3271075..fc6ba69 100644 (file)
@@ -18,11 +18,10 @@ using namespace clang;
 using namespace tooling;
 using namespace dependencies;
 
-static CompilerInvocation
-makeInvocationForModuleBuildWithoutPaths(const ModuleDeps &Deps,
-                                         const CompilerInvocation &Invocation) {
+CompilerInvocation ModuleDepCollector::makeInvocationForModuleBuildWithoutPaths(
+    const ModuleDeps &Deps) const {
   // Make a deep copy of the invocation.
-  CompilerInvocation CI(Invocation);
+  CompilerInvocation CI(Instance.getInvocation());
 
   // Remove options incompatible with explicit module build.
   CI.getFrontendOpts().Inputs.clear();
@@ -34,6 +33,17 @@ makeInvocationForModuleBuildWithoutPaths(const ModuleDeps &Deps,
 
   CI.getLangOpts()->ImplicitModules = false;
 
+  // Report the prebuilt modules this module uses.
+  for (const auto &PrebuiltModule : Deps.PrebuiltModuleDeps) {
+    CI.getFrontendOpts().ModuleFiles.push_back(PrebuiltModule.PCMFile);
+    CI.getFrontendOpts().ModuleMapFiles.push_back(PrebuiltModule.ModuleMapFile);
+  }
+
+  // Restore the original set of prebuilt module files.
+  CI.getHeaderSearchOpts().PrebuiltModuleFiles = OriginalPrebuiltModuleFiles;
+
+  CI.getPreprocessorOpts().ImplicitPCHInclude.clear();
+
   return CI;
 }
 
@@ -148,7 +158,11 @@ void ModuleDepCollectorPP::handleImport(const Module *Imported) {
     return;
 
   const Module *TopLevelModule = Imported->getTopLevelModule();
-  DirectModularDeps.insert(TopLevelModule);
+
+  if (MDC.isPrebuiltModule(TopLevelModule))
+    DirectPrebuiltModularDeps.insert(TopLevelModule);
+  else
+    DirectModularDeps.insert(TopLevelModule);
 }
 
 void ModuleDepCollectorPP::EndOfMainFile() {
@@ -167,6 +181,9 @@ void ModuleDepCollectorPP::EndOfMainFile() {
 
   for (auto &&I : MDC.FileDeps)
     MDC.Consumer.handleFileDependency(*MDC.Opts, I);
+
+  for (auto &&I : DirectPrebuiltModularDeps)
+    MDC.Consumer.handlePrebuiltModuleDependency(PrebuiltModuleDep{I});
 }
 
 ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
@@ -206,8 +223,12 @@ ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
         MD.FileDeps.insert(IF.getFile()->getName());
       });
 
-  MD.Invocation =
-      makeInvocationForModuleBuildWithoutPaths(MD, Instance.getInvocation());
+  // Add direct prebuilt module dependencies now, so that we can use them when
+  // creating a CompilerInvocation and computing context hash for this
+  // ModuleDeps instance.
+  addDirectPrebuiltModuleDeps(M, MD);
+
+  MD.Invocation = MDC.makeInvocationForModuleBuildWithoutPaths(MD);
   MD.ID.ContextHash = MD.Invocation.getModuleHash();
 
   llvm::DenseSet<const Module *> AddedModules;
@@ -216,6 +237,14 @@ ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
   return MD.ID;
 }
 
+void ModuleDepCollectorPP::addDirectPrebuiltModuleDeps(const Module *M,
+                                                       ModuleDeps &MD) {
+  for (const Module *Import : M->Imports)
+    if (Import->getTopLevelModule() != M->getTopLevelModule())
+      if (MDC.isPrebuiltModule(Import))
+        MD.PrebuiltModuleDeps.emplace_back(Import);
+}
+
 void ModuleDepCollectorPP::addAllSubmoduleDeps(
     const Module *M, ModuleDeps &MD,
     llvm::DenseSet<const Module *> &AddedModules) {
@@ -229,7 +258,8 @@ void ModuleDepCollectorPP::addModuleDep(
     const Module *M, ModuleDeps &MD,
     llvm::DenseSet<const Module *> &AddedModules) {
   for (const Module *Import : M->Imports) {
-    if (Import->getTopLevelModule() != M->getTopLevelModule()) {
+    if (Import->getTopLevelModule() != M->getTopLevelModule() &&
+        !MDC.isPrebuiltModule(Import)) {
       ModuleID ImportID = handleTopLevelModule(Import->getTopLevelModule());
       if (AddedModules.insert(Import->getTopLevelModule()).second)
         MD.ClangModuleDeps.push_back(ImportID);
@@ -239,11 +269,25 @@ void ModuleDepCollectorPP::addModuleDep(
 
 ModuleDepCollector::ModuleDepCollector(
     std::unique_ptr<DependencyOutputOptions> Opts, CompilerInstance &I,
-    DependencyConsumer &C)
-    : Instance(I), Consumer(C), Opts(std::move(Opts)) {}
+    DependencyConsumer &C,
+    std::map<std::string, std::string, std::less<>> OriginalPrebuiltModuleFiles)
+    : Instance(I), Consumer(C), Opts(std::move(Opts)),
+      OriginalPrebuiltModuleFiles(std::move(OriginalPrebuiltModuleFiles)) {}
 
 void ModuleDepCollector::attachToPreprocessor(Preprocessor &PP) {
   PP.addPPCallbacks(std::make_unique<ModuleDepCollectorPP>(Instance, *this));
 }
 
 void ModuleDepCollector::attachToASTReader(ASTReader &R) {}
+
+bool ModuleDepCollector::isPrebuiltModule(const Module *M) {
+  std::string Name(M->getTopLevelModuleName());
+  const auto &PrebuiltModuleFiles =
+      Instance.getHeaderSearchOpts().PrebuiltModuleFiles;
+  auto PrebuiltModuleFileIt = PrebuiltModuleFiles.find(Name);
+  if (PrebuiltModuleFileIt == PrebuiltModuleFiles.end())
+    return false;
+  assert("Prebuilt module came from the expected AST file" &&
+         PrebuiltModuleFileIt->second == M->getASTFile()->getName());
+  return true;
+}
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/cdb_pch.json b/clang/test/ClangScanDeps/Inputs/modules-pch/cdb_pch.json
new file mode 100644 (file)
index 0000000..dc2fc55
--- /dev/null
@@ -0,0 +1,7 @@
+[
+  {
+    "directory": "DIR",
+    "command": "clang -x c-header DIR/pch.h -fmodules -gmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -o DIR/pch.h.gch",
+    "file": "DIR/pch.h"
+  }
+]
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/cdb_tu_with_common.json b/clang/test/ClangScanDeps/Inputs/modules-pch/cdb_tu_with_common.json
new file mode 100644 (file)
index 0000000..bc235cd
--- /dev/null
@@ -0,0 +1,7 @@
+[
+  {
+    "directory": "DIR",
+    "command": "clang -fsyntax-only DIR/tu_with_common.c -fmodules -gmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -include DIR/pch.h -o DIR/tu_with_common.o",
+    "file": "DIR/tu_with_common.c"
+  }
+]
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_1.h b/clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_1.h
new file mode 100644 (file)
index 0000000..63acd9b
--- /dev/null
@@ -0,0 +1 @@
+// mod_common_1.h
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_2.h b/clang/test/ClangScanDeps/Inputs/modules-pch/mod_common_2.h
new file mode 100644 (file)
index 0000000..cfe099a
--- /dev/null
@@ -0,0 +1 @@
+// mod_common_2.h
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/mod_pch.h b/clang/test/ClangScanDeps/Inputs/modules-pch/mod_pch.h
new file mode 100644 (file)
index 0000000..37516b2
--- /dev/null
@@ -0,0 +1,3 @@
+// mod_pch.h
+
+#include "mod_common_2.h"
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/mod_tu_with_common.h b/clang/test/ClangScanDeps/Inputs/modules-pch/mod_tu_with_common.h
new file mode 100644 (file)
index 0000000..8a493f6
--- /dev/null
@@ -0,0 +1,3 @@
+// mod_tu_with_common.h
+
+#include "mod_common_1.h"
index 405ba5c..fdec125 100644 (file)
@@ -1,3 +1,19 @@
+module ModCommon1 {
+    header "mod_common_1.h"
+}
+
+module ModCommon2 {
+    header "mod_common_2.h"
+}
+
+module ModPCH {
+    header "mod_pch.h"
+}
+
 module ModTU {
     header "mod_tu.h"
 }
+
+module ModTUWithCommon {
+    header "mod_tu_with_common.h"
+}
diff --git a/clang/test/ClangScanDeps/Inputs/modules-pch/tu_with_common.c b/clang/test/ClangScanDeps/Inputs/modules-pch/tu_with_common.c
new file mode 100644 (file)
index 0000000..a7e5cad
--- /dev/null
@@ -0,0 +1,4 @@
+// tu_with_common.c
+
+#include "mod_common_2.h"
+#include "mod_tu_with_common.h"
index 020818f..34a0043 100644 (file)
 // RUN: rm -rf %t && mkdir %t
 // RUN: cp %S/Inputs/modules-pch/* %t
 
+// Scan dependencies of the PCH:
+//
+// RUN: sed "s|DIR|%/t|g" %S/Inputs/modules-pch/cdb_pch.json > %t/cdb.json
+// RUN: echo -%t > %t/result_pch.json
+// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full \
+// RUN:   -generate-modules-path-args -module-files-dir %t/build -mode preprocess >> %t/result_pch.json
+// RUN: cat %t/result_pch.json | sed 's:\\\\\?:/:g' | FileCheck %s -check-prefix=CHECK-PCH
+//
+// CHECK-PCH:      -[[PREFIX:.*]]
+// CHECK-PCH-NEXT: {
+// CHECK-PCH-NEXT:   "modules": [
+// CHECK-PCH-NEXT:     {
+// CHECK-PCH-NEXT:       "clang-module-deps": [],
+// CHECK-PCH-NEXT:       "clang-modulemap-file": "[[PREFIX]]/module.modulemap",
+// CHECK-PCH-NEXT:       "command-line": [
+// CHECK-PCH-NEXT:         "-cc1"
+// CHECK-PCH:              "-emit-module"
+// CHECK-PCH:              "-fmodules"
+// CHECK-PCH:              "-fmodule-name=ModCommon1"
+// CHECK-PCH:              "-fno-implicit-modules"
+// CHECK-PCH:            ],
+// CHECK-PCH-NEXT:       "context-hash": "[[HASH_MOD_COMMON_1:.*]]",
+// CHECK-PCH-NEXT:       "file-deps": [
+// CHECK-PCH-NEXT:         "[[PREFIX]]/mod_common_1.h",
+// CHECK-PCH-NEXT:         "[[PREFIX]]/module.modulemap"
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "name": "ModCommon1"
+// CHECK-PCH-NEXT:     },
+// CHECK-PCH-NEXT:     {
+// CHECK-PCH-NEXT:       "clang-module-deps": [],
+// CHECK-PCH-NEXT:       "clang-modulemap-file": "[[PREFIX]]/module.modulemap",
+// CHECK-PCH-NEXT:       "command-line": [
+// CHECK-PCH-NEXT:         "-cc1"
+// CHECK-PCH:              "-emit-module"
+// CHECK-PCH:              "-fmodules"
+// CHECK-PCH:              "-fmodule-name=ModCommon2"
+// CHECK-PCH:              "-fno-implicit-modules"
+// CHECK-PCH:            ],
+// CHECK-PCH-NEXT:       "context-hash": "[[HASH_MOD_COMMON_2:.*]]",
+// CHECK-PCH-NEXT:       "file-deps": [
+// CHECK-PCH-NEXT:         "[[PREFIX]]/mod_common_2.h",
+// CHECK-PCH-NEXT:         "[[PREFIX]]/module.modulemap"
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "name": "ModCommon2"
+// CHECK-PCH-NEXT:     },
+// CHECK-PCH-NEXT:     {
+// CHECK-PCH-NEXT:       "clang-module-deps": [
+// CHECK-PCH-NEXT:         {
+// CHECK-PCH-NEXT:           "context-hash": "[[HASH_MOD_COMMON_2]]",
+// CHECK-PCH-NEXT:           "module-name": "ModCommon2"
+// CHECK-PCH-NEXT:         }
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "clang-modulemap-file": "[[PREFIX]]/module.modulemap",
+// CHECK-PCH-NEXT:       "command-line": [
+// CHECK-PCH-NEXT:         "-cc1"
+// CHECK-PCH:              "-fmodule-map-file=[[PREFIX]]/module.modulemap"
+// CHECK-PCH:              "-emit-module"
+// CHECK-PCH:              "-fmodule-file=[[PREFIX]]/build/[[HASH_MOD_COMMON_2]]/ModCommon2-{{.*}}.pcm"
+// CHECK-PCH:              "-fmodules"
+// CHECK-PCH:              "-fmodule-name=ModPCH"
+// CHECK-PCH:              "-fno-implicit-modules"
+// CHECK-PCH:            ],
+// CHECK-PCH-NEXT:       "context-hash": "[[HASH_MOD_PCH:.*]]",
+// CHECK-PCH-NEXT:       "file-deps": [
+// CHECK-PCH-NEXT:         "[[PREFIX]]/mod_pch.h",
+// CHECK-PCH-NEXT:         "[[PREFIX]]/module.modulemap"
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "name": "ModPCH"
+// CHECK-PCH-NEXT:     }
+// CHECK-PCH-NEXT:   ],
+// CHECK-PCH-NEXT:   "translation-units": [
+// CHECK-PCH-NEXT:     {
+// CHECK-PCH-NEXT:       "clang-context-hash": "[[HASH_PCH:.*]]",
+// CHECK-PCH-NEXT:       "clang-module-deps": [
+// CHECK-PCH-NEXT:         {
+// CHECK-PCH-NEXT:           "context-hash": "[[HASH_MOD_COMMON_1]]",
+// CHECK-PCH-NEXT:           "module-name": "ModCommon1"
+// CHECK-PCH-NEXT:         },
+// CHECK-PCH-NEXT:         {
+// CHECK-PCH-NEXT:           "context-hash": "[[HASH_MOD_PCH]]",
+// CHECK-PCH-NEXT:           "module-name": "ModPCH"
+// CHECK-PCH-NEXT:         }
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "command-line": [
+// CHECK-PCH-NEXT:         "-fno-implicit-modules",
+// CHECK-PCH-NEXT:         "-fno-implicit-module-maps",
+// CHECK-PCH-DAG:          "-fmodule-file=[[PREFIX]]/build/[[HASH_MOD_COMMON_1]]/ModCommon1-{{.*}}.pcm",
+// CHECK-PCH-DAG:          "-fmodule-file=[[PREFIX]]/build/[[HASH_MOD_COMMON_2]]/ModCommon2-{{.*}}.pcm",
+// CHECK-PCH-DAG:          "-fmodule-file=[[PREFIX]]/build/[[HASH_MOD_PCH]]/ModPCH-{{.*}}.pcm",
+// CHECK-PCH-NEXT:         "-fmodule-map-file=[[PREFIX]]/module.modulemap",
+// CHECK-PCH-NEXT:         "-fmodule-map-file=[[PREFIX]]/module.modulemap",
+// CHECK-PCH-NEXT:         "-fmodule-map-file=[[PREFIX]]/module.modulemap"
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "file-deps": [
+// CHECK-PCH-NEXT:         "[[PREFIX]]/pch.h"
+// CHECK-PCH-NEXT:       ],
+// CHECK-PCH-NEXT:       "input-file": "[[PREFIX]]/pch.h"
+// CHECK-PCH-NEXT:     }
+// CHECK-PCH-NEXT:   ]
+// CHECK-PCH-NEXT: }
+
 // Explicitly build the PCH:
 //
+// RUN: tail -n +2 %t/result_pch.json > %t/result_pch_stripped.json
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_pch_stripped.json \
+// RUN:   --module-name=ModCommon1 > %t/mod_common_1.cc1.rsp
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_pch_stripped.json \
+// RUN:   --module-name=ModCommon2 > %t/mod_common_2.cc1.rsp
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_pch_stripped.json \
+// RUN:   --module-name=ModPCH > %t/mod_pch.cc1.rsp
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_pch_stripped.json \
+// RUN:   --tu-index=0 > %t/pch.rsp
+//
+// RUN: %clang @%t/mod_common_1.cc1.rsp
+// RUN: %clang @%t/mod_common_2.cc1.rsp
+// RUN: %clang @%t/mod_pch.cc1.rsp
 // RUN: %clang -x c-header %t/pch.h -fmodules -gmodules -fimplicit-module-maps \
-// RUN:   -fmodules-cache-path=%t/cache -o %t/pch.h.gch
+// RUN:   -fmodules-cache-path=%t/cache -o %t/pch.h.gch @%t/pch.rsp
 
 // Scan dependencies of the TU:
 //
 // CHECK-TU-NEXT:     }
 // CHECK-TU-NEXT:   ]
 // CHECK-TU-NEXT: }
+
+// Explicitly build the TU:
+//
+// RUN: tail -n +2 %t/result_tu.json > %t/result_tu_stripped.json
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_tu_stripped.json \
+// RUN:   --module-name=ModTU > %t/mod_tu.cc1.rsp
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_tu_stripped.json \
+// RUN:   --tu-index=0 > %t/tu.rsp
+//
+// RUN: %clang @%t/mod_tu.cc1.rsp
+// RUN: %clang -fsyntax-only %t/tu.c -fmodules -gmodules -fimplicit-module-maps \
+// RUN:   -fmodules-cache-path=%t/cache -include %t/pch.h -o %t/tu.o @%t/tu.rsp
+
+// Scan dependencies of the TU that has common modules with the PCH:
+//
+// RUN: sed "s|DIR|%/t|g" %S/Inputs/modules-pch/cdb_tu_with_common.json > %t/cdb.json
+// RUN: echo -%t > %t/result_tu_with_common.json
+// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full \
+// RUN:   -generate-modules-path-args -module-files-dir %t/build -mode preprocess >> %t/result_tu_with_common.json
+// RUN: cat %t/result_tu_with_common.json | sed 's:\\\\\?:/:g' | FileCheck %s -check-prefix=CHECK-TU-WITH-COMMON
+//
+// CHECK-TU-WITH-COMMON:      -[[PREFIX:.*]]
+// CHECK-TU-WITH-COMMON-NEXT: {
+// CHECK-TU-WITH-COMMON-NEXT:   "modules": [
+// CHECK-TU-WITH-COMMON-NEXT:     {
+// CHECK-TU-WITH-COMMON-NEXT:       "clang-module-deps": [],
+// CHECK-TU-WITH-COMMON-NEXT:       "clang-modulemap-file": "[[PREFIX]]/module.modulemap",
+// CHECK-TU-WITH-COMMON-NEXT:       "command-line": [
+// CHECK-TU-WITH-COMMON-NEXT:         "-cc1",
+// CHECK-TU-WITH-COMMON:              "-fmodule-map-file=[[PREFIX]]/module.modulemap"
+// CHECK-TU-WITH-COMMON:              "-emit-module",
+// CHECK-TU-WITH-COMMON:              "-fmodule-file=[[PREFIX]]/build/{{.*}}/ModCommon1-{{.*}}.pcm",
+// CHECK-TU-WITH-COMMON:              "-fmodule-name=ModTUWithCommon",
+// CHECK-TU-WITH-COMMON:              "-fno-implicit-modules",
+// CHECK-TU-WITH-COMMON:            ],
+// CHECK-TU-WITH-COMMON-NEXT:       "context-hash": "[[HASH_MOD_TU_WITH_COMMON:.*]]",
+// CHECK-TU-WITH-COMMON-NEXT:       "file-deps": [
+// CHECK-TU-WITH-COMMON-NEXT:         "[[PREFIX]]/mod_tu_with_common.h",
+// CHECK-TU-WITH-COMMON-NEXT:         "[[PREFIX]]/module.modulemap"
+// CHECK-TU-WITH-COMMON-NEXT:       ],
+// CHECK-TU-WITH-COMMON-NEXT:       "name": "ModTUWithCommon"
+// CHECK-TU-WITH-COMMON-NEXT:     }
+// CHECK-TU-WITH-COMMON-NEXT:   ],
+// CHECK-TU-WITH-COMMON-NEXT:   "translation-units": [
+// CHECK-TU-WITH-COMMON-NEXT:     {
+// CHECK-TU-WITH-COMMON-NEXT:       "clang-context-hash": "[[HASH_TU_WITH_COMMON:.*]]",
+// CHECK-TU-WITH-COMMON-NEXT:       "clang-module-deps": [
+// CHECK-TU-WITH-COMMON-NEXT:         {
+// CHECK-TU-WITH-COMMON-NEXT:           "context-hash": "[[HASH_MOD_TU_WITH_COMMON]]",
+// CHECK-TU-WITH-COMMON-NEXT:           "module-name": "ModTUWithCommon"
+// CHECK-TU-WITH-COMMON-NEXT:         }
+// CHECK-TU-WITH-COMMON-NEXT:       ],
+// CHECK-TU-WITH-COMMON-NEXT:       "command-line": [
+// CHECK-TU-WITH-COMMON-NEXT:         "-fno-implicit-modules",
+// CHECK-TU-WITH-COMMON-NEXT:         "-fno-implicit-module-maps",
+// FIXME: Figure out why we need `=ModCommon2` here for Clang to pick up the PCM.
+// CHECK-TU-WITH-COMMON-NEXT:         "-fmodule-file=ModCommon2=[[PREFIX]]/build/{{.*}}/ModCommon2-{{.*}}.pcm",
+// CHECK-TU-WITH-COMMON-NEXT:         "-fmodule-map-file=[[PREFIX]]/module.modulemap"
+// CHECK-TU-WITH-COMMON-NEXT:         "-fmodule-file=[[PREFIX]]/build/[[HASH_MOD_TU_WITH_COMMON]]/ModTUWithCommon-{{.*}}.pcm",
+// CHECK-TU-WITH-COMMON-NEXT:         "-fmodule-map-file=[[PREFIX]]/module.modulemap"
+// CHECK-TU-WITH-COMMON-NEXT:       ],
+// CHECK-TU-WITH-COMMON-NEXT:       "file-deps": [
+// CHECK-TU-WITH-COMMON-NEXT:         "[[PREFIX]]/tu_with_common.c",
+// CHECK-TU-WITH-COMMON-NEXT:         "[[PREFIX]]/pch.h.gch"
+// CHECK-TU-WITH-COMMON-NEXT:       ],
+// CHECK-TU-WITH-COMMON-NEXT:       "input-file": "[[PREFIX]]/tu_with_common.c"
+// CHECK-TU-WITH-COMMON-NEXT:     }
+// CHECK-TU-WITH-COMMON-NEXT:   ]
+// CHECK-TU-WITH-COMMON-NEXT: }
+
+// Explicitly build the TU that has common modules with the PCH:
+//
+// RUN: tail -n +2 %t/result_tu_with_common.json > %t/result_tu_with_common_stripped.json
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_tu_with_common_stripped.json \
+// RUN:   --module-name=ModTUWithCommon > %t/mod_tu_with_common.cc1.rsp
+// RUN: %python %S/../../utils/module-deps-to-rsp.py %t/result_tu_with_common_stripped.json \
+// RUN:   --tu-index=0 > %t/tu_with_common.rsp
+//
+// RUN: %clang @%t/mod_tu_with_common.cc1.rsp
+// RUN: %clang -fsyntax-only %t/tu_with_common.c -fmodules -gmodules -fimplicit-module-maps \
+// RUN:   -fmodules-cache-path=%t/cache -include %t/pch.h -o %t/tu_with_common.o @%t/tu_with_common.rsp