Skip to content

[clang][Dependency Scanning] Report What a Module Exports during Scanning #137421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ struct ModuleID {
}
};

struct ExtendedModuleID {
ModuleID ID;
bool Exported;

bool operator<(const ExtendedModuleID &Other) const {
return std::tie(ID, Exported) < std::tie(Other.ID, Other.Exported);
}
};

/// P1689ModuleInfo - Represents the needed information of standard C++20
/// modules for P1689 format.
struct P1689ModuleInfo {
Expand Down Expand Up @@ -183,7 +192,7 @@ struct ModuleDeps {
///
/// This may include modules with a different context hash when it can be
/// determined that the differences are benign for this compilation.
std::vector<ModuleID> ClangModuleDeps;
std::vector<ExtendedModuleID> ClangModuleDeps;

/// The set of libraries or frameworks to link against when
/// an entity from this module is used.
Expand Down Expand Up @@ -270,7 +279,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
Expand Down Expand Up @@ -352,16 +362,16 @@ class ModuleDepCollector final : public DependencyCollector {

/// Collect module map files for given modules.
llvm::DenseSet<const FileEntry *>
collectModuleMapFiles(ArrayRef<ModuleID> ClangModuleDeps) const;
collectModuleMapFiles(ArrayRef<ExtendedModuleID> ClangModuleDeps) const;

/// Add module map files to the invocation, if needed.
void addModuleMapFiles(CompilerInvocation &CI,
ArrayRef<ModuleID> ClangModuleDeps) const;
ArrayRef<ExtendedModuleID> ClangModuleDeps) const;
/// Add module files (pcm) to the invocation, if needed.
void addModuleFiles(CompilerInvocation &CI,
ArrayRef<ModuleID> ClangModuleDeps) const;
ArrayRef<ExtendedModuleID> ClangModuleDeps) const;
void addModuleFiles(CowCompilerInvocation &CI,
ArrayRef<ModuleID> ClangModuleDeps) const;
ArrayRef<ExtendedModuleID> ClangModuleDeps) const;

/// Add paths that require looking up outputs to the given dependencies.
void addOutputPaths(CowCompilerInvocation &CI, ModuleDeps &Deps);
Expand Down
63 changes: 36 additions & 27 deletions clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,10 @@ ModuleDepCollector::getInvocationAdjustedForModuleBuildWithoutOutputs(
}

llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles(
ArrayRef<ModuleID> ClangModuleDeps) const {
ArrayRef<ExtendedModuleID> ClangModuleDeps) const {
llvm::DenseSet<const FileEntry *> ModuleMapFiles;
for (const ModuleID &MID : ClangModuleDeps) {
ModuleDeps *MD = ModuleDepsByID.lookup(MID);
for (const auto &MID : ClangModuleDeps) {
ModuleDeps *MD = ModuleDepsByID.lookup(MID.ID);
assert(MD && "Inconsistent dependency info");
// TODO: Track ClangModuleMapFile as `FileEntryRef`.
auto FE = ScanInstance.getFileManager().getOptionalFileRef(
Expand All @@ -404,44 +404,45 @@ llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles(
}

void ModuleDepCollector::addModuleMapFiles(
CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const {
CompilerInvocation &CI, ArrayRef<ExtendedModuleID> 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 &MID : ClangModuleDeps) {
ModuleDeps *MD = ModuleDepsByID.lookup(MID.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<ExtendedModuleID> ClangModuleDeps) const {
for (const auto &MID : ClangModuleDeps) {
ModuleDeps *MD = ModuleDepsByID.lookup(MID.ID);
std::string PCMPath =
Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile);

if (Service.shouldEagerLoadModules())
CI.getFrontendOpts().ModuleFiles.push_back(std::move(PCMPath));
else
CI.getHeaderSearchOpts().PrebuiltModuleFiles.insert(
{MID.ModuleName, std::move(PCMPath)});
{MID.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<ExtendedModuleID> ClangModuleDeps) const {
for (const auto &MID : ClangModuleDeps) {
ModuleDeps *MD = ModuleDepsByID.lookup(MID.ID);
std::string PCMPath =
Controller.lookupModuleOutput(*MD, ModuleOutputKind::ModuleFile);

if (Service.shouldEagerLoadModules())
CI.getMutFrontendOpts().ModuleFiles.push_back(std::move(PCMPath));
else
CI.getMutHeaderSearchOpts().PrebuiltModuleFiles.insert(
{MID.ModuleName, std::move(PCMPath)});
{MID.ID.ModuleName, std::move(PCMPath)});
}
}

Expand Down Expand Up @@ -471,10 +472,10 @@ void ModuleDepCollector::applyDiscoveredDependencies(CompilerInvocation &CI) {
CI.getFrontendOpts().ModuleMapFiles.emplace_back(
CurrentModuleMap->getNameAsRequested());

SmallVector<ModuleID> DirectDeps;
SmallVector<ExtendedModuleID> 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);
Expand Down Expand Up @@ -598,9 +599,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 &EMID : MD.ClangModuleDeps) {
HashBuilder.add(EMID.ID.ModuleName);
HashBuilder.add(EMID.ID.ContextHash);
}

HashBuilder.add(EagerLoadModules);
Expand Down Expand Up @@ -924,22 +925,30 @@ 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;
}

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);
}
}
}
}
Expand All @@ -963,7 +972,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);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions clang/test/ClangScanDeps/diagnostics.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module mod { header "mod.h" }
// CHECK-NEXT: "[[PREFIX]]/mod.h"
// CHECK-NEXT: ],
// CHECK-NEXT: "link-libraries": [],
// CHECK-NEXT: "clang-modules-exported": [],
// CHECK-NEXT: "name": "mod"
// CHECK-NEXT: }
// CHECK-NEXT: ],
Expand Down
164 changes: 164 additions & 0 deletions clang/test/ClangScanDeps/export.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// 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: },
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C:.*]]",
// CHECK-NEXT: "module-name": "C"
// 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-NEXT: "context-hash":{{.*}}
// CHECK-NEXT: "file-deps": [
// CHECK: ],
// CHECK-NEXT: "link-libraries": [],
// CHECK-NEXT: "clang-modules-exported": [
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_B]]",
// CHECK-NEXT: "module-name": "B"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C]]",
// CHECK-NEXT: "module-name": "C"
// CHECK-NEXT: }
// CHECK-NOT: {
// CHECK-NOT: "context-hash": "[[HASH_MOD_D]]",
// CHECK-NOT: "module-name": "D"
// CHECK-NOT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "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: },
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C]]",
// CHECK-NEXT: "module-name": "C"
// 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-NEXT: "context-hash":{{.*}}
// CHECK-NEXT: "file-deps": [
// CHECK: ],
// CHECK-NEXT: "link-libraries": [],
// CHECK-NEXT: "clang-modules-exported": [
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_B]]",
// CHECK-NEXT: "module-name": "B"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_C]]",
// CHECK-NEXT: "module-name": "C"
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: "context-hash": "[[HASH_MOD_D]]",
// CHECK-NEXT: "module-name": "D"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "name": "E"
// CHECK-NEXT: }
// CHECK: ]
// CHECK: }



Loading
Loading