diff --git a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h index 6ee5b08277bb3..325f15fef0174 100644 --- a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h +++ b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h @@ -142,12 +142,25 @@ struct ModuleDeps { /// on, not including transitive dependencies. std::vector PrebuiltModuleDeps; - /// A list of module identifiers this module directly depends on, not - /// including transitive dependencies. + /// This struct contains information about a single dependency. + struct DepInfo { + /// Identifies the dependency. + ModuleID ID; + + /// Indicates if the module that has this dependency exports it or not. + bool Exported = false; + + bool operator<(const DepInfo &Other) const { + return std::tie(ID, Exported) < std::tie(Other.ID, Other.Exported); + } + }; + + /// A list of DepsInfo containing information about modules this module + /// directly depends on, not including transitive dependencies. /// /// This may include modules with a different context hash when it can be /// determined that the differences are benign for this compilation. - std::vector ClangModuleDeps; + std::vector ClangModuleDeps; /// The CASID for the module input dependency tree, if any. std::optional CASFileSystemRootID; @@ -245,7 +258,8 @@ class ModuleDepCollectorPP final : public PPCallbacks { llvm::DenseSet &AddedModules); /// Add discovered module dependency for the given module. - void addOneModuleDep(const Module *M, const ModuleID ID, ModuleDeps &MD); + void addOneModuleDep(const Module *M, bool Exported, const ModuleID ID, + ModuleDeps &MD); }; /// Collects modular and non-modular dependencies of the main file by attaching @@ -322,16 +336,16 @@ class ModuleDepCollector final : public DependencyCollector { /// Collect module map files for given modules. llvm::DenseSet - collectModuleMapFiles(ArrayRef ClangModuleDeps) const; + collectModuleMapFiles(ArrayRef ClangModuleDeps) const; /// Add module map files to the invocation, if needed. void addModuleMapFiles(CompilerInvocation &CI, - ArrayRef ClangModuleDeps) const; + ArrayRef ClangModuleDeps) const; /// Add module files (pcm) to the invocation, if needed. void addModuleFiles(CompilerInvocation &CI, - ArrayRef ClangModuleDeps) const; + ArrayRef ClangModuleDeps) const; void addModuleFiles(CowCompilerInvocation &CI, - ArrayRef ClangModuleDeps) const; + ArrayRef ClangModuleDeps) const; /// Add paths that require looking up outputs to the given dependencies. void addOutputPaths(CowCompilerInvocation &CI, ModuleDeps &Deps); diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 0d784865faa73..b5e4d055ec680 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -393,10 +393,10 @@ ModuleDepCollector::getInvocationAdjustedForModuleBuildWithoutOutputs( } llvm::DenseSet ModuleDepCollector::collectModuleMapFiles( - ArrayRef ClangModuleDeps) const { + ArrayRef ClangModuleDeps) const { llvm::DenseSet ModuleMapFiles; - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); assert(MD && "Inconsistent dependency info"); // TODO: Track ClangModuleMapFile as `FileEntryRef`. auto FE = ScanInstance.getFileManager().getFile(MD->ClangModuleMapFile); @@ -407,21 +407,23 @@ llvm::DenseSet ModuleDepCollector::collectModuleMapFiles( } void ModuleDepCollector::addModuleMapFiles( - CompilerInvocation &CI, ArrayRef ClangModuleDeps) const { + CompilerInvocation &CI, + ArrayRef ClangModuleDeps) const { if (Service.shouldEagerLoadModules()) return; // Only pcm is needed for eager load. - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); assert(MD && "Inconsistent dependency info"); CI.getFrontendOpts().ModuleMapFiles.push_back(MD->ClangModuleMapFile); } } void ModuleDepCollector::addModuleFiles( - CompilerInvocation &CI, ArrayRef ClangModuleDeps) const { - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + CompilerInvocation &CI, + ArrayRef ClangModuleDeps) const { + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); std::string PCMPath = Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile); @@ -434,14 +436,15 @@ void ModuleDepCollector::addModuleFiles( CI.getFrontendOpts().ModuleFiles.push_back(std::move(PCMPath)); else CI.getHeaderSearchOpts().PrebuiltModuleFiles.insert( - {MID.ModuleName, std::move(PCMPath)}); + {Info.ID.ModuleName, std::move(PCMPath)}); } } void ModuleDepCollector::addModuleFiles( - CowCompilerInvocation &CI, ArrayRef ClangModuleDeps) const { - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + CowCompilerInvocation &CI, + ArrayRef ClangModuleDeps) const { + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); std::string PCMPath = Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile); @@ -454,7 +457,7 @@ void ModuleDepCollector::addModuleFiles( CI.getMutFrontendOpts().ModuleFiles.push_back(std::move(PCMPath)); else CI.getMutHeaderSearchOpts().PrebuiltModuleFiles.insert( - {MID.ModuleName, std::move(PCMPath)}); + {Info.ID.ModuleName, std::move(PCMPath)}); } } @@ -484,10 +487,10 @@ void ModuleDepCollector::applyDiscoveredDependencies(CompilerInvocation &CI) { CI.getFrontendOpts().ModuleMapFiles.emplace_back( CurrentModuleMap->getNameAsRequested()); - SmallVector DirectDeps; + SmallVector DirectDeps; for (const auto &KV : ModularDeps) if (DirectModularDeps.contains(KV.first)) - DirectDeps.push_back(KV.second->ID); + DirectDeps.push_back({KV.second->ID, /* Exported = */ false}); // TODO: Report module maps the same way it's done for modular dependencies. addModuleMapFiles(CI, DirectDeps); @@ -636,9 +639,9 @@ static std::string getModuleContextHash(const ModuleDeps &MD, // example, case-insensitive paths to modulemap files. Usually such a case // would indicate a missed optimization to canonicalize, but it may be // difficult to canonicalize all cases when there is a VFS. - for (const auto &ID : MD.ClangModuleDeps) { - HashBuilder.add(ID.ModuleName); - HashBuilder.add(ID.ContextHash); + for (const auto &Info : MD.ClangModuleDeps) { + HashBuilder.add(Info.ID.ModuleName); + HashBuilder.add(Info.ID.ContextHash); } HashBuilder.add(EagerLoadModules); @@ -1017,9 +1020,10 @@ void ModuleDepCollectorPP::addAllSubmoduleDeps( }); } -void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID, - ModuleDeps &MD) { - MD.ClangModuleDeps.push_back(ID); +void ModuleDepCollectorPP::addOneModuleDep(const Module *M, bool Exported, + const ModuleID ID, ModuleDeps &MD) { + MD.ClangModuleDeps.push_back({ID, Exported}); + if (MD.IsInStableDirectories) MD.IsInStableDirectories = MDC.ModularDeps[M]->IsInStableDirectories; } @@ -1027,12 +1031,19 @@ void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID, void ModuleDepCollectorPP::addModuleDep( const Module *M, ModuleDeps &MD, llvm::DenseSet &AddedModules) { + SmallVector ExportedModulesVector; + M->getExportedModules(ExportedModulesVector); + llvm::DenseSet ExportedModulesSet( + ExportedModulesVector.begin(), ExportedModulesVector.end()); for (const Module *Import : M->Imports) { - if (Import->getTopLevelModule() != M->getTopLevelModule() && + const Module *ImportedTopLevelModule = Import->getTopLevelModule(); + if (ImportedTopLevelModule != M->getTopLevelModule() && !MDC.isPrebuiltModule(Import)) { - if (auto ImportID = handleTopLevelModule(Import->getTopLevelModule())) - if (AddedModules.insert(Import->getTopLevelModule()).second) - addOneModuleDep(Import->getTopLevelModule(), *ImportID, MD); + if (auto ImportID = handleTopLevelModule(ImportedTopLevelModule)) + if (AddedModules.insert(ImportedTopLevelModule).second) { + bool Exported = ExportedModulesSet.contains(ImportedTopLevelModule); + addOneModuleDep(ImportedTopLevelModule, Exported, *ImportID, MD); + } } } } @@ -1056,7 +1067,7 @@ void ModuleDepCollectorPP::addAffectingClangModule( !MDC.isPrebuiltModule(Affecting)) { if (auto ImportID = handleTopLevelModule(Affecting)) if (AddedModules.insert(Affecting).second) - addOneModuleDep(Affecting, *ImportID, MD); + addOneModuleDep(Affecting, /* Exported = */ false, *ImportID, MD); } } } diff --git a/clang/test/ClangScanDeps/export.c b/clang/test/ClangScanDeps/export.c new file mode 100644 index 0000000000000..a68747c9e6fe0 --- /dev/null +++ b/clang/test/ClangScanDeps/export.c @@ -0,0 +1,133 @@ +// Test correctly reporting what a module exports during dependency scanning. +// Module A depends on modules B, C and D, but only exports B and C. +// Module E depends on modules B, C and D, and exports all of them. + +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json +// RUN: clang-scan-deps -compilation-database \ +// RUN: %t/cdb.json -format experimental-full > %t/deps.db +// RUN: cat %t/deps.db | sed 's:\\\\\?:/:g' | FileCheck %s + +//--- cdb.json.template +[ + { + "directory": "DIR", + "command": "clang -c DIR/test.c -I DIR/AH -I DIR/BH -I DIR/CH -I DIR/DH -I DIR/EH -fmodules -fmodules-cache-path=DIR/cache", + "file": "DIR/test.c" + }, +] + +//--- AH/A.h +#include "B.h" +#include "C.h" +#include "D.h" + +int funcA(); + +//--- AH/module.modulemap +module A { + header "A.h" + + export B + export C +} + +//--- BH/B.h +//--- BH/module.modulemap +module B { + header "B.h" +} + +//--- CH/C.h +//--- CH/module.modulemap +module C { + header "C.h" +} + +//--- DH/D.h +//--- DH/module.modulemap +module D { + header "D.h" +} + +//--- EH/E.h +#include "B.h" +#include "C.h" +#include "D.h" + +//--- EH/module.modulemap +module E { + header "E.h" + export * +} + +//--- test.c +#include "A.h" +#include "E.h" + +int test1() { + return funcA(); +} + +// CHECK: { +// CHECK-NEXT: "modules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_B:.*]]", +// CHECK-NEXT: "module-name": "B", +// CHECK-NEXT: "exported": "true" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_C:.*]]", +// CHECK-NEXT: "module-name": "C", +// CHECK-NEXT: "exported": "true" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_D:.*]]", +// CHECK-NEXT: "module-name": "D" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "clang-modulemap-file":{{.*}}, +// CHECK-NEXT: "command-line": [ +// CHECK: ], +// CHECK: "name": "A" +// CHECK-NEXT: } +// CHECK: { +// CHECK: "name": "B" +// CHECK: } +// CHECK: { +// CHECK: "name": "C" +// CHECK: } +// CHECK: { +// CHECK: "name": "D" +// CHECK: } +// CHECK: { +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_B]]", +// CHECK-NEXT: "module-name": "B", +// CHECK-NEXT: "exported": "true" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_C]]", +// CHECK-NEXT: "module-name": "C", +// CHECK-NEXT: "exported": "true" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_MOD_D]]", +// CHECK-NEXT: "module-name": "D", +// CHECK-NEXT: "exported": "true" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "clang-modulemap-file":{{.*}}, +// CHECK-NEXT: "command-line": [ +// CHECK: ], +// CHECK: "name": "E" +// CHECK-NEXT: } +// CHECK: ] +// CHECK: } + + + diff --git a/clang/test/ClangScanDeps/optimize-vfs-pch-tree.m b/clang/test/ClangScanDeps/optimize-vfs-pch-tree.m index 0c2ea25c40907..3c6a2a1b74e2f 100644 --- a/clang/test/ClangScanDeps/optimize-vfs-pch-tree.m +++ b/clang/test/ClangScanDeps/optimize-vfs-pch-tree.m @@ -54,6 +54,7 @@ // CHECK-NEXT: { // CHECK-NEXT: "context-hash": "{{.*}}", // CHECK-NEXT: "module-name": "E" +// CHECK-NEXT: "exported": "true" // CHECK-NEXT: } // CHECK-NEXT: ], // CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/modules/D/module.modulemap", diff --git a/clang/test/ClangScanDeps/optimize-vfs-pch.m b/clang/test/ClangScanDeps/optimize-vfs-pch.m index 0b5cb08d365ee..190a0509644ab 100644 --- a/clang/test/ClangScanDeps/optimize-vfs-pch.m +++ b/clang/test/ClangScanDeps/optimize-vfs-pch.m @@ -54,7 +54,8 @@ // CHECK-NEXT: "clang-module-deps": [ // CHECK-NEXT: { // CHECK-NEXT: "context-hash": "{{.*}}", -// CHECK-NEXT: "module-name": "E" +// CHECK-NEXT: "module-name": "E", +// CHECK-NEXT: "exported": "true" // CHECK-NEXT: } // CHECK-NEXT: ], // CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/modules/D/module.modulemap", diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp index aa40ba4c28bac..e1d54413d5305 100644 --- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -601,16 +601,32 @@ static auto toJSONStrings(llvm::json::OStream &JOS, Container &&Strings) { }; } +static auto toJSONModuleID(llvm::json::OStream &JOS, StringRef ContextHash, + StringRef ModuleName, bool Exported) { + return JOS.object([&] { + JOS.attribute("context-hash", StringRef(ContextHash)); + JOS.attribute("module-name", StringRef(ModuleName)); + if (Exported) + JOS.attribute("exported", StringRef("true")); + }); +} + // Technically, we don't need to sort the dependency list to get determinism. // Leaving these be will simply preserve the import order. static auto toJSONSorted(llvm::json::OStream &JOS, std::vector V) { llvm::sort(V); return [&JOS, V = std::move(V)] { - for (const ModuleID &MID : V) - JOS.object([&] { - JOS.attribute("context-hash", StringRef(MID.ContextHash)); - JOS.attribute("module-name", StringRef(MID.ModuleName)); - }); + for (const auto &MID : V) + toJSONModuleID(JOS, MID.ContextHash, MID.ModuleName, false); + }; +} + +static auto toJSONSorted(llvm::json::OStream &JOS, + std::vector V) { + llvm::sort(V); + return [&JOS, V = std::move(V)] { + for (const ModuleDeps::DepInfo &MID : V) + toJSONModuleID(JOS, MID.ID.ContextHash, MID.ID.ModuleName, MID.Exported); }; } diff --git a/clang/tools/libclang/CDependencies.cpp b/clang/tools/libclang/CDependencies.cpp index 4195b6c609bf7..99f5153bc04bd 100644 --- a/clang/tools/libclang/CDependencies.cpp +++ b/clang/tools/libclang/CDependencies.cpp @@ -292,8 +292,8 @@ static CXErrorCode getFullDependencies(DependencyScanningWorker *Worker, MD.forEachFileDep([&](StringRef File) { FileDeps.emplace_back(File); }); M.FileDeps = cxstring::createSet(FileDeps); std::vector Modules; - for (ModuleID &MID : MD.ClangModuleDeps) - Modules.push_back(MID.ModuleName + ":" + MID.ContextHash); + for (const auto &DepInfo : MD.ClangModuleDeps) + Modules.push_back(DepInfo.ID.ModuleName + ":" + DepInfo.ID.ContextHash); M.ModuleDeps = cxstring::createSet(Modules); M.BuildArguments = cxstring::createSet(MD.getBuildArguments()); } @@ -622,8 +622,8 @@ clang_experimental_DepGraphModule_getModuleDeps(CXDepGraphModule CXDepMod) { const ModuleDeps &ModDeps = *unwrap(CXDepMod)->ModDeps; std::vector Modules; Modules.reserve(ModDeps.ClangModuleDeps.size()); - for (const ModuleID &MID : ModDeps.ClangModuleDeps) - Modules.push_back(MID.ModuleName + ":" + MID.ContextHash); + for (const auto &DepInfo : ModDeps.ClangModuleDeps) + Modules.push_back(DepInfo.ID.ModuleName + ":" + DepInfo.ID.ContextHash); return unwrap(CXDepMod)->StrMgr.createCStringsOwned(std::move(Modules)); }