Author: Qiongsi Wu Date: 2025-04-30T14:08:02-07:00 New Revision: ea1bfbf3f6399b7d2d840722f0e87542d00f6a35
URL: https://github.com/llvm/llvm-project/commit/ea1bfbf3f6399b7d2d840722f0e87542d00f6a35 DIFF: https://github.com/llvm/llvm-project/commit/ea1bfbf3f6399b7d2d840722f0e87542d00f6a35.diff LOG: [clang][Dependency Scanning] Report What a Module Exports during Scanning (#137421) We would like to report, for a module, which direct dependencies it exports during dependency scanning. This PR implements this reporting by augmenting `ModuleDep`'s `ClangModuleDeps` variable. `ClangModuleDeps` now contains instances of `DepInfo`, which is made of a `ModuleID` and a boolean flag that indicates if a particular dependence is exported. rdar://144794793 Added: clang/test/ClangScanDeps/export.c Modified: clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp clang/test/ClangScanDeps/optimize-vfs-pch.m clang/tools/clang-scan-deps/ClangScanDeps.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h index d2d0d56e5212c..06717a64c9a78 100644 --- a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h +++ b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h @@ -178,12 +178,25 @@ struct ModuleDeps { /// on, not including transitive dependencies. std::vector<PrebuiltModuleDep> 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 diff erent context hash when it can be /// determined that the diff erences are benign for this compilation. - std::vector<ModuleID> ClangModuleDeps; + std::vector<ModuleDeps::DepInfo> ClangModuleDeps; /// The set of libraries or frameworks to link against when /// an entity from this module is used. @@ -270,7 +283,8 @@ class ModuleDepCollectorPP final : public PPCallbacks { llvm::DenseSet<const Module *> &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 @@ -352,16 +366,16 @@ class ModuleDepCollector final : public DependencyCollector { /// Collect module map files for given modules. llvm::DenseSet<const FileEntry *> - collectModuleMapFiles(ArrayRef<ModuleID> ClangModuleDeps) const; + collectModuleMapFiles(ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const; /// Add module map files to the invocation, if needed. void addModuleMapFiles(CompilerInvocation &CI, - ArrayRef<ModuleID> ClangModuleDeps) const; + ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const; /// Add module files (pcm) to the invocation, if needed. void addModuleFiles(CompilerInvocation &CI, - ArrayRef<ModuleID> ClangModuleDeps) const; + ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const; void addModuleFiles(CowCompilerInvocation &CI, - ArrayRef<ModuleID> ClangModuleDeps) const; + ArrayRef<ModuleDeps::DepInfo> 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 07856dbdba4b4..b3016f90122df 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -389,10 +389,10 @@ ModuleDepCollector::getInvocationAdjustedForModuleBuildWithoutOutputs( } llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles( - ArrayRef<ModuleID> ClangModuleDeps) const { + ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const { llvm::DenseSet<const FileEntry *> 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().getOptionalFileRef( @@ -404,21 +404,23 @@ llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles( } void ModuleDepCollector::addModuleMapFiles( - CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const { + CompilerInvocation &CI, + ArrayRef<ModuleDeps::DepInfo> 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<ModuleID> ClangModuleDeps) const { - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + CompilerInvocation &CI, + ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const { + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); std::string PCMPath = Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile); @@ -426,14 +428,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<ModuleID> ClangModuleDeps) const { - for (const ModuleID &MID : ClangModuleDeps) { - ModuleDeps *MD = ModuleDepsByID.lookup(MID); + CowCompilerInvocation &CI, + ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const { + for (const auto &Info : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID); std::string PCMPath = Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile); @@ -441,7 +444,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)}); } } @@ -471,10 +474,10 @@ void ModuleDepCollector::applyDiscoveredDependencies(CompilerInvocation &CI) { CI.getFrontendOpts().ModuleMapFiles.emplace_back( CurrentModuleMap->getNameAsRequested()); - SmallVector<ModuleID> DirectDeps; + SmallVector<ModuleDeps::DepInfo> 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); @@ -598,9 +601,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 // diff icult 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); @@ -924,9 +927,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; } @@ -934,12 +938,19 @@ void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID, void ModuleDepCollectorPP::addModuleDep( const Module *M, ModuleDeps &MD, llvm::DenseSet<const Module *> &AddedModules) { + SmallVector<Module *> ExportedModulesVector; + M->getExportedModules(ExportedModulesVector); + llvm::DenseSet<const Module *> 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); + } } } } @@ -963,7 +974,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.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 13dab6e445733..dae2b9a9fe683 100644 --- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -350,16 +350,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<ModuleID> 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<ModuleDeps::DepInfo> 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); }; } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits