[clang] [clang][scan-deps] Report a scanned TU's visible modules (PR #147969)
Cyndy Ishida via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 10 19:26:00 PDT 2025
https://github.com/cyndyishida updated https://github.com/llvm/llvm-project/pull/147969
>From b72bf3e6bf1319594aa0243ed8f32a61f2436d58 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Wed, 9 Jul 2025 21:50:44 -0700
Subject: [PATCH] [clang][scan-deps] Report a scanned TU's visible modules
---
.../DependencyScanningTool.h | 12 ++-
.../DependencyScanningWorker.h | 2 +
.../DependencyScanning/ModuleDepCollector.h | 8 ++
.../DependencyScanningTool.cpp | 22 +----
.../DependencyScanning/ModuleDepCollector.cpp | 32 ++++++-
clang/test/ClangScanDeps/visible-modules.c | 93 +++++++++++++++++++
clang/tools/clang-scan-deps/ClangScanDeps.cpp | 32 +++++--
clang/tools/clang-scan-deps/Opts.td | 3 +
8 files changed, 178 insertions(+), 26 deletions(-)
create mode 100644 clang/test/ClangScanDeps/visible-modules.c
diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h
index ee24e5d1543d3..c3601a4e73e1f 100644
--- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h
+++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h
@@ -57,6 +57,10 @@ struct TranslationUnitDeps {
/// determined that the differences are benign for this compilation.
std::vector<ModuleID> ClangModuleDeps;
+ /// A list of module names that are visible to this translation unit. This
+ /// includes both direct and transitive module dependencies.
+ std::vector<std::string> VisibleModules;
+
/// A list of the C++20 named modules this translation unit depends on.
std::vector<std::string> NamedModuleDeps;
@@ -150,7 +154,7 @@ class DependencyScanningTool {
/// Given a compilation context specified via the Clang driver command-line,
/// gather modular dependencies of module with the given name, and return the
/// information needed for explicit build.
- llvm::Expected<ModuleDepsGraph> getModuleDependencies(
+ llvm::Expected<TranslationUnitDeps> getModuleDependencies(
StringRef ModuleName, const std::vector<std::string> &CommandLine,
StringRef CWD, const llvm::DenseSet<ModuleID> &AlreadySeen,
LookupModuleOutputCallback LookupModuleOutput);
@@ -188,6 +192,10 @@ class FullDependencyConsumer : public DependencyConsumer {
DirectModuleDeps.push_back(ID);
}
+ void handleVisibleModule(std::string ModuleName) override {
+ VisibleModules.push_back(ModuleName);
+ }
+
void handleContextHash(std::string Hash) override {
ContextHash = std::move(Hash);
}
@@ -201,7 +209,6 @@ class FullDependencyConsumer : public DependencyConsumer {
}
TranslationUnitDeps takeTranslationUnitDeps();
- ModuleDepsGraph takeModuleGraphDeps();
private:
std::vector<std::string> Dependencies;
@@ -210,6 +217,7 @@ class FullDependencyConsumer : public DependencyConsumer {
std::string ModuleName;
std::vector<std::string> NamedModuleDeps;
std::vector<ModuleID> DirectModuleDeps;
+ std::vector<std::string> VisibleModules;
std::vector<Command> Commands;
std::string ContextHash;
std::vector<std::string> OutputPaths;
diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h
index 3e232c79397ce..6060e4b43312e 100644
--- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h
+++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h
@@ -59,6 +59,8 @@ class DependencyConsumer {
virtual void handleDirectModuleDependency(ModuleID MD) = 0;
+ virtual void handleVisibleModule(std::string ModuleName) = 0;
+
virtual void handleContextHash(std::string Hash) = 0;
};
diff --git a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h
index e96c49883d3c6..4136cb73f7043 100644
--- a/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h
+++ b/clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h
@@ -323,6 +323,11 @@ class ModuleDepCollector final : public DependencyCollector {
llvm::MapVector<const Module *, PrebuiltModuleDep> DirectPrebuiltModularDeps;
/// Working set of direct modular dependencies.
llvm::SetVector<const Module *> DirectModularDeps;
+ /// Working set of direct modular dependencies, as they were imported.
+ llvm::SmallPtrSet<const Module *, 32> DirectImports;
+ /// All direct and transitive visible modules.
+ llvm::StringSet<> VisibleModules;
+
/// Options that control the dependency output generation.
std::unique_ptr<DependencyOutputOptions> Opts;
/// A Clang invocation that's based on the original TU invocation and that has
@@ -337,6 +342,9 @@ class ModuleDepCollector final : public DependencyCollector {
/// Checks whether the module is known as being prebuilt.
bool isPrebuiltModule(const Module *M);
+ /// Computes all visible modules resolved from direct imports.
+ void addVisibleModules();
+
/// Adds \p Path to \c FileDeps, making it absolute if necessary.
void addFileDep(StringRef Path);
/// Adds \p Path to \c MD.FileDeps, making it absolute if necessary.
diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp
index 515211d47b348..27734ffd0e20b 100644
--- a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp
+++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp
@@ -40,6 +40,7 @@ class MakeDependencyPrinterConsumer : public DependencyConsumer {
void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {}
void handleModuleDependency(ModuleDeps MD) override {}
void handleDirectModuleDependency(ModuleID ID) override {}
+ void handleVisibleModule(std::string ModuleName) override {}
void handleContextHash(std::string Hash) override {}
void printDependencies(std::string &S) {
@@ -154,7 +155,8 @@ DependencyScanningTool::getTranslationUnitDependencies(
return Consumer.takeTranslationUnitDeps();
}
-llvm::Expected<ModuleDepsGraph> DependencyScanningTool::getModuleDependencies(
+llvm::Expected<TranslationUnitDeps>
+DependencyScanningTool::getModuleDependencies(
StringRef ModuleName, const std::vector<std::string> &CommandLine,
StringRef CWD, const llvm::DenseSet<ModuleID> &AlreadySeen,
LookupModuleOutputCallback LookupModuleOutput) {
@@ -164,7 +166,7 @@ llvm::Expected<ModuleDepsGraph> DependencyScanningTool::getModuleDependencies(
Controller, ModuleName);
if (Result)
return std::move(Result);
- return Consumer.takeModuleGraphDeps();
+ return Consumer.takeTranslationUnitDeps();
}
TranslationUnitDeps FullDependencyConsumer::takeTranslationUnitDeps() {
@@ -175,6 +177,7 @@ TranslationUnitDeps FullDependencyConsumer::takeTranslationUnitDeps() {
TU.NamedModuleDeps = std::move(NamedModuleDeps);
TU.FileDeps = std::move(Dependencies);
TU.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
+ TU.VisibleModules = std::move(VisibleModules);
TU.Commands = std::move(Commands);
for (auto &&M : ClangModuleDeps) {
@@ -190,19 +193,4 @@ TranslationUnitDeps FullDependencyConsumer::takeTranslationUnitDeps() {
return TU;
}
-ModuleDepsGraph FullDependencyConsumer::takeModuleGraphDeps() {
- ModuleDepsGraph ModuleGraph;
-
- for (auto &&M : ClangModuleDeps) {
- auto &MD = M.second;
- // TODO: Avoid handleModuleDependency even being called for modules
- // we've already seen.
- if (AlreadySeen.count(M.first))
- continue;
- ModuleGraph.push_back(std::move(MD));
- }
-
- return ModuleGraph;
-}
-
CallbackActionController::~CallbackActionController() {}
diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
index fa86d714ff69a..37f8b945d785e 100644
--- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
@@ -673,8 +673,10 @@ void ModuleDepCollectorPP::handleImport(const Module *Imported) {
if (MDC.isPrebuiltModule(TopLevelModule))
MDC.DirectPrebuiltModularDeps.insert(
{TopLevelModule, PrebuiltModuleDep{TopLevelModule}});
- else
+ else {
MDC.DirectModularDeps.insert(TopLevelModule);
+ MDC.DirectImports.insert(Imported);
+ }
}
void ModuleDepCollectorPP::EndOfMainFile() {
@@ -706,6 +708,8 @@ void ModuleDepCollectorPP::EndOfMainFile() {
if (!MDC.isPrebuiltModule(M))
MDC.DirectModularDeps.insert(M);
+ MDC.addVisibleModules();
+
for (const Module *M : MDC.DirectModularDeps)
handleTopLevelModule(M);
@@ -727,6 +731,9 @@ void ModuleDepCollectorPP::EndOfMainFile() {
MDC.Consumer.handleDirectModuleDependency(It->second->ID);
}
+ for (auto &&I : MDC.VisibleModules)
+ MDC.Consumer.handleVisibleModule(std::string(I.getKey()));
+
for (auto &&I : MDC.FileDeps)
MDC.Consumer.handleFileDependency(I);
@@ -993,6 +1000,29 @@ bool ModuleDepCollector::isPrebuiltModule(const Module *M) {
return true;
}
+void ModuleDepCollector::addVisibleModules() {
+ llvm::DenseSet<const Module *> ImportedModules;
+ auto InsertVisibleModules = [&](const Module *M) {
+ if (ImportedModules.contains(M))
+ return;
+
+ VisibleModules.insert(M->getTopLevelModuleName());
+ SmallVector<Module *> Stack;
+ M->getExportedModules(Stack);
+ while (!Stack.empty()) {
+ const Module *CurrModule = Stack.pop_back_val();
+ if (ImportedModules.contains(CurrModule))
+ continue;
+ ImportedModules.insert(CurrModule);
+ VisibleModules.insert(CurrModule->getTopLevelModuleName());
+ CurrModule->getExportedModules(Stack);
+ }
+ };
+
+ for (const Module *Import : DirectImports)
+ InsertVisibleModules(Import);
+}
+
static StringRef makeAbsoluteAndPreferred(CompilerInstance &CI, StringRef Path,
SmallVectorImpl<char> &Storage) {
if (llvm::sys::path::is_absolute(Path) &&
diff --git a/clang/test/ClangScanDeps/visible-modules.c b/clang/test/ClangScanDeps/visible-modules.c
new file mode 100644
index 0000000000000..837794646232d
--- /dev/null
+++ b/clang/test/ClangScanDeps/visible-modules.c
@@ -0,0 +1,93 @@
+// This test verifies that the modules visible to the translation unit are computed in dependency scanning.
+// "client" represents the translation unit that imports an explicit submodule, that only exports one other module.
+// Thus, the dependencies of the top level module for the submodule differ from what is visible to the TU.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s|DIR|%/t|g" %t/compile-commands.json.in > %t/compile-commands.json
+// RUN: clang-scan-deps -emit-visible-modules -compilation-database %t/compile-commands.json \
+// RUN: -j 1 -format experimental-full 2>&1 > %t/result.json
+// RUN: cat %t/result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t
+
+
+// RUN: %deps-to-rsp %t/result.json --module-name=transitive > %t/transitive.rsp
+// RUN: %deps-to-rsp %t/result.json --module-name=visible > %t/visible.rsp
+// RUN: %deps-to-rsp %t/result.json --module-name=invisible > %t/invisible.rsp
+// RUN: %deps-to-rsp %t/result.json --module-name=A > %t/A.rsp
+// RUN: %deps-to-rsp %t/result.json --tu-index=0 > %t/tu.rsp
+
+// RUN: %clang @%t/transitive.rsp
+// RUN: %clang @%t/visible.rsp
+// RUN: %clang @%t/invisible.rsp
+// RUN: %clang @%t/A.rsp
+
+// Verify compilation & scan agree with each other.
+// RUN: not %clang @%t/tu.rsp 2>&1 | FileCheck %s --check-prefix=COMPILE
+
+// CHECK: "visible-clang-modules": [
+// CHECK-NEXT: "A",
+// CHECK-NEXT: "visible"
+// CHECK-NEXT: ]
+
+// COMPILE-NOT: 'visible_t' must be declared before it is used
+// COMPILE: 'transitive_t' must be declared before it is used
+// COMPILE: 'invisible_t' must be declared before it is used
+
+//--- compile-commands.json.in
+[
+{
+ "directory": "DIR",
+ "command": "clang -c DIR/client.c -isysroot DIR/Sysroot -IDIR/Sysroot/usr/include -fmodules -fmodules-cache-path=DIR/module-cache -fimplicit-module-maps",
+ "file": "DIR/client.c"
+}
+]
+
+//--- Sysroot/usr/include/A/module.modulemap
+module A {
+ explicit module visibleToTU {
+ header "visibleToTU.h"
+ export visible
+ }
+ explicit module invisibleToTU {
+ header "invisibleToTU.h"
+ }
+}
+//--- Sysroot/usr/include/A/visibleToTU.h
+#include <visible/visible.h>
+typedef int A_visibleToTU;
+
+//--- Sysroot/usr/include/A/invisibleToTU.h
+#include <invisible/invisible.h>
+typedef int A_invisibleToTU;
+
+//--- Sysroot/usr/include/invisible/module.modulemap
+module invisible {
+ umbrella "."
+}
+
+//--- Sysroot/usr/include/invisible/invisible.h
+typedef int invisible_t;
+
+//--- Sysroot/usr/include/visible/module.modulemap
+module visible {
+ umbrella "."
+}
+
+//--- Sysroot/usr/include/visible/visible.h
+#include <transitive/transitive.h>
+typedef int visible_t;
+
+//--- Sysroot/usr/include/transitive/module.modulemap
+module transitive {
+ umbrella "."
+}
+
+//--- Sysroot/usr/include/transitive/transitive.h
+typedef int transitive_t;
+
+//--- client.c
+#include <A/visibleToTU.h>
+visible_t foo_v(void);
+// Both decls are not visible, thus should fail to actually compile.
+transitive_t foo_t(void);
+invisible_t foo_i(void);
diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp
index 8b590bd57e1a3..f10b73278381b 100644
--- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp
+++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp
@@ -94,6 +94,7 @@ static bool DeprecatedDriverCommand;
static ResourceDirRecipeKind ResourceDirRecipe;
static bool Verbose;
static bool PrintTiming;
+static bool EmitVisibleModules;
static llvm::BumpPtrAllocator Alloc;
static llvm::StringSaver Saver{Alloc};
static std::vector<const char *> CommandLine;
@@ -232,6 +233,8 @@ static void ParseArgs(int argc, char **argv) {
PrintTiming = Args.hasArg(OPT_print_timing);
+ EmitVisibleModules = Args.hasArg(OPT_emit_visible_modules);
+
Verbose = Args.hasArg(OPT_verbose);
RoundTripArgs = Args.hasArg(OPT_round_trip_args);
@@ -380,6 +383,14 @@ static auto toJSONSorted(llvm::json::OStream &JOS,
};
}
+static auto toJSONSorted(llvm::json::OStream &JOS, std::vector<std::string> V) {
+ llvm::sort(V);
+ return [&JOS, V = std::move(V)] {
+ for (const StringRef Entry : V)
+ JOS.value(Entry);
+ };
+}
+
// Thread safe.
class FullDeps {
public:
@@ -396,6 +407,7 @@ class FullDeps {
ID.NamedModule = std::move(TUDeps.ID.ModuleName);
ID.NamedModuleDeps = std::move(TUDeps.NamedModuleDeps);
ID.ClangModuleDeps = std::move(TUDeps.ClangModuleDeps);
+ ID.VisibleModules = std::move(TUDeps.VisibleModules);
ID.DriverCommandLine = std::move(TUDeps.DriverCommandLine);
ID.Commands = std::move(TUDeps.Commands);
@@ -525,6 +537,9 @@ class FullDeps {
JOS.attributeArray("file-deps",
toJSONStrings(JOS, I.FileDeps));
JOS.attribute("input-file", StringRef(I.FileName));
+ if (EmitVisibleModules)
+ JOS.attributeArray("visible-clang-modules",
+ toJSONSorted(JOS, I.VisibleModules));
});
}
} else {
@@ -545,6 +560,9 @@ class FullDeps {
JOS.attributeArray("file-deps",
toJSONStrings(JOS, I.FileDeps));
JOS.attribute("input-file", StringRef(I.FileName));
+ if (EmitVisibleModules)
+ JOS.attributeArray("visible-clang-modules",
+ toJSONSorted(JOS, I.VisibleModules));
});
}
});
@@ -596,6 +614,7 @@ class FullDeps {
std::string NamedModule;
std::vector<std::string> NamedModuleDeps;
std::vector<ModuleID> ClangModuleDeps;
+ std::vector<std::string> VisibleModules;
std::vector<std::string> DriverCommandLine;
std::vector<Command> Commands;
};
@@ -623,11 +642,12 @@ static bool handleTranslationUnitResult(
return false;
}
-static bool handleModuleResult(
- StringRef ModuleName, llvm::Expected<ModuleDepsGraph> &MaybeModuleGraph,
- FullDeps &FD, size_t InputIndex, SharedStream &OS, SharedStream &Errs) {
- if (!MaybeModuleGraph) {
- llvm::handleAllErrors(MaybeModuleGraph.takeError(),
+static bool handleModuleResult(StringRef ModuleName,
+ llvm::Expected<TranslationUnitDeps> &MaybeTUDeps,
+ FullDeps &FD, size_t InputIndex,
+ SharedStream &OS, SharedStream &Errs) {
+ if (!MaybeTUDeps) {
+ llvm::handleAllErrors(MaybeTUDeps.takeError(),
[&ModuleName, &Errs](llvm::StringError &Err) {
Errs.applyLocked([&](raw_ostream &OS) {
OS << "Error while scanning dependencies for "
@@ -637,7 +657,7 @@ static bool handleModuleResult(
});
return true;
}
- FD.mergeDeps(std::move(*MaybeModuleGraph), InputIndex);
+ FD.mergeDeps(std::move(MaybeTUDeps->ModuleGraph), InputIndex);
return false;
}
diff --git a/clang/tools/clang-scan-deps/Opts.td b/clang/tools/clang-scan-deps/Opts.td
index 9cccbb3aaf0c8..03011f9ae1f75 100644
--- a/clang/tools/clang-scan-deps/Opts.td
+++ b/clang/tools/clang-scan-deps/Opts.td
@@ -37,6 +37,9 @@ defm resource_dir_recipe : Eq<"resource-dir-recipe", "How to produce missing '-r
def print_timing : F<"print-timing", "Print timing information">;
+def emit_visible_modules
+ : F<"emit-visible-modules", "emit visible modules in primary output">;
+
def verbose : F<"v", "Use verbose output">;
def round_trip_args : F<"round-trip-args", "verify that command-line arguments are canonical by parsing and re-serializing">;
More information about the cfe-commits
mailing list