Skip to content

Commit 7da77c4

Browse files
committed
[clang][Dependency Scanning] Report What a Module Exports during Scanning (llvm#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 (cherry picked from commit ea1bfbf)
1 parent 5842d95 commit 7da77c4

File tree

7 files changed

+221
-45
lines changed

7 files changed

+221
-45
lines changed

clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h

+22-8
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,25 @@ struct ModuleDeps {
142142
/// on, not including transitive dependencies.
143143
std::vector<PrebuiltModuleDep> PrebuiltModuleDeps;
144144

145-
/// A list of module identifiers this module directly depends on, not
146-
/// including transitive dependencies.
145+
/// This struct contains information about a single dependency.
146+
struct DepInfo {
147+
/// Identifies the dependency.
148+
ModuleID ID;
149+
150+
/// Indicates if the module that has this dependency exports it or not.
151+
bool Exported = false;
152+
153+
bool operator<(const DepInfo &Other) const {
154+
return std::tie(ID, Exported) < std::tie(Other.ID, Other.Exported);
155+
}
156+
};
157+
158+
/// A list of DepsInfo containing information about modules this module
159+
/// directly depends on, not including transitive dependencies.
147160
///
148161
/// This may include modules with a different context hash when it can be
149162
/// determined that the differences are benign for this compilation.
150-
std::vector<ModuleID> ClangModuleDeps;
163+
std::vector<ModuleDeps::DepInfo> ClangModuleDeps;
151164

152165
/// The CASID for the module input dependency tree, if any.
153166
std::optional<llvm::cas::CASID> CASFileSystemRootID;
@@ -245,7 +258,8 @@ class ModuleDepCollectorPP final : public PPCallbacks {
245258
llvm::DenseSet<const Module *> &AddedModules);
246259

247260
/// Add discovered module dependency for the given module.
248-
void addOneModuleDep(const Module *M, const ModuleID ID, ModuleDeps &MD);
261+
void addOneModuleDep(const Module *M, bool Exported, const ModuleID ID,
262+
ModuleDeps &MD);
249263
};
250264

251265
/// Collects modular and non-modular dependencies of the main file by attaching
@@ -322,16 +336,16 @@ class ModuleDepCollector final : public DependencyCollector {
322336

323337
/// Collect module map files for given modules.
324338
llvm::DenseSet<const FileEntry *>
325-
collectModuleMapFiles(ArrayRef<ModuleID> ClangModuleDeps) const;
339+
collectModuleMapFiles(ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const;
326340

327341
/// Add module map files to the invocation, if needed.
328342
void addModuleMapFiles(CompilerInvocation &CI,
329-
ArrayRef<ModuleID> ClangModuleDeps) const;
343+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const;
330344
/// Add module files (pcm) to the invocation, if needed.
331345
void addModuleFiles(CompilerInvocation &CI,
332-
ArrayRef<ModuleID> ClangModuleDeps) const;
346+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const;
333347
void addModuleFiles(CowCompilerInvocation &CI,
334-
ArrayRef<ModuleID> ClangModuleDeps) const;
348+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const;
335349

336350
/// Add paths that require looking up outputs to the given dependencies.
337351
void addOutputPaths(CowCompilerInvocation &CI, ModuleDeps &Deps);

clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp

+38-27
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,10 @@ ModuleDepCollector::getInvocationAdjustedForModuleBuildWithoutOutputs(
393393
}
394394

395395
llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles(
396-
ArrayRef<ModuleID> ClangModuleDeps) const {
396+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const {
397397
llvm::DenseSet<const FileEntry *> ModuleMapFiles;
398-
for (const ModuleID &MID : ClangModuleDeps) {
399-
ModuleDeps *MD = ModuleDepsByID.lookup(MID);
398+
for (const auto &Info : ClangModuleDeps) {
399+
ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID);
400400
assert(MD && "Inconsistent dependency info");
401401
// TODO: Track ClangModuleMapFile as `FileEntryRef`.
402402
auto FE = ScanInstance.getFileManager().getFile(MD->ClangModuleMapFile);
@@ -407,21 +407,23 @@ llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles(
407407
}
408408

409409
void ModuleDepCollector::addModuleMapFiles(
410-
CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const {
410+
CompilerInvocation &CI,
411+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const {
411412
if (Service.shouldEagerLoadModules())
412413
return; // Only pcm is needed for eager load.
413414

414-
for (const ModuleID &MID : ClangModuleDeps) {
415-
ModuleDeps *MD = ModuleDepsByID.lookup(MID);
415+
for (const auto &Info : ClangModuleDeps) {
416+
ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID);
416417
assert(MD && "Inconsistent dependency info");
417418
CI.getFrontendOpts().ModuleMapFiles.push_back(MD->ClangModuleMapFile);
418419
}
419420
}
420421

421422
void ModuleDepCollector::addModuleFiles(
422-
CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const {
423-
for (const ModuleID &MID : ClangModuleDeps) {
424-
ModuleDeps *MD = ModuleDepsByID.lookup(MID);
423+
CompilerInvocation &CI,
424+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const {
425+
for (const auto &Info : ClangModuleDeps) {
426+
ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID);
425427
std::string PCMPath =
426428
Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile);
427429

@@ -434,14 +436,15 @@ void ModuleDepCollector::addModuleFiles(
434436
CI.getFrontendOpts().ModuleFiles.push_back(std::move(PCMPath));
435437
else
436438
CI.getHeaderSearchOpts().PrebuiltModuleFiles.insert(
437-
{MID.ModuleName, std::move(PCMPath)});
439+
{Info.ID.ModuleName, std::move(PCMPath)});
438440
}
439441
}
440442

441443
void ModuleDepCollector::addModuleFiles(
442-
CowCompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const {
443-
for (const ModuleID &MID : ClangModuleDeps) {
444-
ModuleDeps *MD = ModuleDepsByID.lookup(MID);
444+
CowCompilerInvocation &CI,
445+
ArrayRef<ModuleDeps::DepInfo> ClangModuleDeps) const {
446+
for (const auto &Info : ClangModuleDeps) {
447+
ModuleDeps *MD = ModuleDepsByID.lookup(Info.ID);
445448
std::string PCMPath =
446449
Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile);
447450

@@ -454,7 +457,7 @@ void ModuleDepCollector::addModuleFiles(
454457
CI.getMutFrontendOpts().ModuleFiles.push_back(std::move(PCMPath));
455458
else
456459
CI.getMutHeaderSearchOpts().PrebuiltModuleFiles.insert(
457-
{MID.ModuleName, std::move(PCMPath)});
460+
{Info.ID.ModuleName, std::move(PCMPath)});
458461
}
459462
}
460463

@@ -484,10 +487,10 @@ void ModuleDepCollector::applyDiscoveredDependencies(CompilerInvocation &CI) {
484487
CI.getFrontendOpts().ModuleMapFiles.emplace_back(
485488
CurrentModuleMap->getNameAsRequested());
486489

487-
SmallVector<ModuleID> DirectDeps;
490+
SmallVector<ModuleDeps::DepInfo> DirectDeps;
488491
for (const auto &KV : ModularDeps)
489492
if (DirectModularDeps.contains(KV.first))
490-
DirectDeps.push_back(KV.second->ID);
493+
DirectDeps.push_back({KV.second->ID, /* Exported = */ false});
491494

492495
// TODO: Report module maps the same way it's done for modular dependencies.
493496
addModuleMapFiles(CI, DirectDeps);
@@ -636,9 +639,9 @@ static std::string getModuleContextHash(const ModuleDeps &MD,
636639
// example, case-insensitive paths to modulemap files. Usually such a case
637640
// would indicate a missed optimization to canonicalize, but it may be
638641
// difficult to canonicalize all cases when there is a VFS.
639-
for (const auto &ID : MD.ClangModuleDeps) {
640-
HashBuilder.add(ID.ModuleName);
641-
HashBuilder.add(ID.ContextHash);
642+
for (const auto &Info : MD.ClangModuleDeps) {
643+
HashBuilder.add(Info.ID.ModuleName);
644+
HashBuilder.add(Info.ID.ContextHash);
642645
}
643646

644647
HashBuilder.add(EagerLoadModules);
@@ -1017,22 +1020,30 @@ void ModuleDepCollectorPP::addAllSubmoduleDeps(
10171020
});
10181021
}
10191022

1020-
void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID,
1021-
ModuleDeps &MD) {
1022-
MD.ClangModuleDeps.push_back(ID);
1023+
void ModuleDepCollectorPP::addOneModuleDep(const Module *M, bool Exported,
1024+
const ModuleID ID, ModuleDeps &MD) {
1025+
MD.ClangModuleDeps.push_back({ID, Exported});
1026+
10231027
if (MD.IsInStableDirectories)
10241028
MD.IsInStableDirectories = MDC.ModularDeps[M]->IsInStableDirectories;
10251029
}
10261030

10271031
void ModuleDepCollectorPP::addModuleDep(
10281032
const Module *M, ModuleDeps &MD,
10291033
llvm::DenseSet<const Module *> &AddedModules) {
1034+
SmallVector<Module *> ExportedModulesVector;
1035+
M->getExportedModules(ExportedModulesVector);
1036+
llvm::DenseSet<const Module *> ExportedModulesSet(
1037+
ExportedModulesVector.begin(), ExportedModulesVector.end());
10301038
for (const Module *Import : M->Imports) {
1031-
if (Import->getTopLevelModule() != M->getTopLevelModule() &&
1039+
const Module *ImportedTopLevelModule = Import->getTopLevelModule();
1040+
if (ImportedTopLevelModule != M->getTopLevelModule() &&
10321041
!MDC.isPrebuiltModule(Import)) {
1033-
if (auto ImportID = handleTopLevelModule(Import->getTopLevelModule()))
1034-
if (AddedModules.insert(Import->getTopLevelModule()).second)
1035-
addOneModuleDep(Import->getTopLevelModule(), *ImportID, MD);
1042+
if (auto ImportID = handleTopLevelModule(ImportedTopLevelModule))
1043+
if (AddedModules.insert(ImportedTopLevelModule).second) {
1044+
bool Exported = ExportedModulesSet.contains(ImportedTopLevelModule);
1045+
addOneModuleDep(ImportedTopLevelModule, Exported, *ImportID, MD);
1046+
}
10361047
}
10371048
}
10381049
}
@@ -1056,7 +1067,7 @@ void ModuleDepCollectorPP::addAffectingClangModule(
10561067
!MDC.isPrebuiltModule(Affecting)) {
10571068
if (auto ImportID = handleTopLevelModule(Affecting))
10581069
if (AddedModules.insert(Affecting).second)
1059-
addOneModuleDep(Affecting, *ImportID, MD);
1070+
addOneModuleDep(Affecting, /* Exported = */ false, *ImportID, MD);
10601071
}
10611072
}
10621073
}

clang/test/ClangScanDeps/export.c

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Test correctly reporting what a module exports during dependency scanning.
2+
// Module A depends on modules B, C and D, but only exports B and C.
3+
// Module E depends on modules B, C and D, and exports all of them.
4+
5+
// RUN: rm -rf %t
6+
// RUN: split-file %s %t
7+
// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
8+
// RUN: clang-scan-deps -compilation-database \
9+
// RUN: %t/cdb.json -format experimental-full > %t/deps.db
10+
// RUN: cat %t/deps.db | sed 's:\\\\\?:/:g' | FileCheck %s
11+
12+
//--- cdb.json.template
13+
[
14+
{
15+
"directory": "DIR",
16+
"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",
17+
"file": "DIR/test.c"
18+
},
19+
]
20+
21+
//--- AH/A.h
22+
#include "B.h"
23+
#include "C.h"
24+
#include "D.h"
25+
26+
int funcA();
27+
28+
//--- AH/module.modulemap
29+
module A {
30+
header "A.h"
31+
32+
export B
33+
export C
34+
}
35+
36+
//--- BH/B.h
37+
//--- BH/module.modulemap
38+
module B {
39+
header "B.h"
40+
}
41+
42+
//--- CH/C.h
43+
//--- CH/module.modulemap
44+
module C {
45+
header "C.h"
46+
}
47+
48+
//--- DH/D.h
49+
//--- DH/module.modulemap
50+
module D {
51+
header "D.h"
52+
}
53+
54+
//--- EH/E.h
55+
#include "B.h"
56+
#include "C.h"
57+
#include "D.h"
58+
59+
//--- EH/module.modulemap
60+
module E {
61+
header "E.h"
62+
export *
63+
}
64+
65+
//--- test.c
66+
#include "A.h"
67+
#include "E.h"
68+
69+
int test1() {
70+
return funcA();
71+
}
72+
73+
// CHECK: {
74+
// CHECK-NEXT: "modules": [
75+
// CHECK-NEXT: {
76+
// CHECK-NEXT: "clang-module-deps": [
77+
// CHECK-NEXT: {
78+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_B:.*]]",
79+
// CHECK-NEXT: "module-name": "B",
80+
// CHECK-NEXT: "exported": "true"
81+
// CHECK-NEXT: },
82+
// CHECK-NEXT: {
83+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C:.*]]",
84+
// CHECK-NEXT: "module-name": "C",
85+
// CHECK-NEXT: "exported": "true"
86+
// CHECK-NEXT: },
87+
// CHECK-NEXT: {
88+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_D:.*]]",
89+
// CHECK-NEXT: "module-name": "D"
90+
// CHECK-NEXT: }
91+
// CHECK-NEXT: ],
92+
// CHECK-NEXT: "clang-modulemap-file":{{.*}},
93+
// CHECK-NEXT: "command-line": [
94+
// CHECK: ],
95+
// CHECK: "name": "A"
96+
// CHECK-NEXT: }
97+
// CHECK: {
98+
// CHECK: "name": "B"
99+
// CHECK: }
100+
// CHECK: {
101+
// CHECK: "name": "C"
102+
// CHECK: }
103+
// CHECK: {
104+
// CHECK: "name": "D"
105+
// CHECK: }
106+
// CHECK: {
107+
// CHECK-NEXT: "clang-module-deps": [
108+
// CHECK-NEXT: {
109+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_B]]",
110+
// CHECK-NEXT: "module-name": "B",
111+
// CHECK-NEXT: "exported": "true"
112+
// CHECK-NEXT: },
113+
// CHECK-NEXT: {
114+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C]]",
115+
// CHECK-NEXT: "module-name": "C",
116+
// CHECK-NEXT: "exported": "true"
117+
// CHECK-NEXT: },
118+
// CHECK-NEXT: {
119+
// CHECK-NEXT: "context-hash": "[[HASH_MOD_D]]",
120+
// CHECK-NEXT: "module-name": "D",
121+
// CHECK-NEXT: "exported": "true"
122+
// CHECK-NEXT: }
123+
// CHECK-NEXT: ],
124+
// CHECK-NEXT: "clang-modulemap-file":{{.*}},
125+
// CHECK-NEXT: "command-line": [
126+
// CHECK: ],
127+
// CHECK: "name": "E"
128+
// CHECK-NEXT: }
129+
// CHECK: ]
130+
// CHECK: }
131+
132+
133+

clang/test/ClangScanDeps/optimize-vfs-pch-tree.m

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
// CHECK-NEXT: {
5555
// CHECK-NEXT: "context-hash": "{{.*}}",
5656
// CHECK-NEXT: "module-name": "E"
57+
// CHECK-NEXT: "exported": "true"
5758
// CHECK-NEXT: }
5859
// CHECK-NEXT: ],
5960
// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/modules/D/module.modulemap",

clang/test/ClangScanDeps/optimize-vfs-pch.m

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
// CHECK-NEXT: "clang-module-deps": [
5555
// CHECK-NEXT: {
5656
// CHECK-NEXT: "context-hash": "{{.*}}",
57-
// CHECK-NEXT: "module-name": "E"
57+
// CHECK-NEXT: "module-name": "E",
58+
// CHECK-NEXT: "exported": "true"
5859
// CHECK-NEXT: }
5960
// CHECK-NEXT: ],
6061
// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/modules/D/module.modulemap",

clang/tools/clang-scan-deps/ClangScanDeps.cpp

+21-5
Original file line numberDiff line numberDiff line change
@@ -601,16 +601,32 @@ static auto toJSONStrings(llvm::json::OStream &JOS, Container &&Strings) {
601601
};
602602
}
603603

604+
static auto toJSONModuleID(llvm::json::OStream &JOS, StringRef ContextHash,
605+
StringRef ModuleName, bool Exported) {
606+
return JOS.object([&] {
607+
JOS.attribute("context-hash", StringRef(ContextHash));
608+
JOS.attribute("module-name", StringRef(ModuleName));
609+
if (Exported)
610+
JOS.attribute("exported", StringRef("true"));
611+
});
612+
}
613+
604614
// Technically, we don't need to sort the dependency list to get determinism.
605615
// Leaving these be will simply preserve the import order.
606616
static auto toJSONSorted(llvm::json::OStream &JOS, std::vector<ModuleID> V) {
607617
llvm::sort(V);
608618
return [&JOS, V = std::move(V)] {
609-
for (const ModuleID &MID : V)
610-
JOS.object([&] {
611-
JOS.attribute("context-hash", StringRef(MID.ContextHash));
612-
JOS.attribute("module-name", StringRef(MID.ModuleName));
613-
});
619+
for (const auto &MID : V)
620+
toJSONModuleID(JOS, MID.ContextHash, MID.ModuleName, false);
621+
};
622+
}
623+
624+
static auto toJSONSorted(llvm::json::OStream &JOS,
625+
std::vector<ModuleDeps::DepInfo> V) {
626+
llvm::sort(V);
627+
return [&JOS, V = std::move(V)] {
628+
for (const ModuleDeps::DepInfo &MID : V)
629+
toJSONModuleID(JOS, MID.ID.ContextHash, MID.ID.ModuleName, MID.Exported);
614630
};
615631
}
616632

0 commit comments

Comments
 (0)