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
/// 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.
///
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;
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,
/// 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.
///
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.
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;
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
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;
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(
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
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);
}
FD.ClangModuleDeps.push_back(MD.ID);
}
+ FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
+
FullDependenciesResult FDR;
for (auto &&M : ClangModuleDeps) {
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;
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 {
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.
//
break;
case ScanningOutputFormat::Full:
Compiler.addDependencyCollector(std::make_shared<ModuleDepCollector>(
- std::move(Opts), Compiler, Consumer));
+ std::move(Opts), Compiler, Consumer,
+ std::move(OrigPrebuiltModuleFiles)));
break;
}
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();
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;
}
return;
const Module *TopLevelModule = Imported->getTopLevelModule();
- DirectModularDeps.insert(TopLevelModule);
+
+ if (MDC.isPrebuiltModule(TopLevelModule))
+ DirectPrebuiltModularDeps.insert(TopLevelModule);
+ else
+ DirectModularDeps.insert(TopLevelModule);
}
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) {
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;
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) {
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);
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;
+}
--- /dev/null
+[
+ {
+ "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"
+ }
+]
--- /dev/null
+[
+ {
+ "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"
+ }
+]
--- /dev/null
+// mod_common_1.h
--- /dev/null
+// mod_common_2.h
--- /dev/null
+// mod_pch.h
+
+#include "mod_common_2.h"
--- /dev/null
+// mod_tu_with_common.h
+
+#include "mod_common_1.h"
+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"
+}
// pch.h
+
+#include "mod_common_1.h"
+#include "mod_pch.h"
--- /dev/null
+// tu_with_common.c
+
+#include "mod_common_2.h"
+#include "mod_tu_with_common.h"
// 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