Skip to content

Commit 584f8cc

Browse files
authored
[clang][DependencyScanning] Track modules that resolve from "stable" locations (llvm#130634)
That patch tracks whether all the file & module dependencies of a module resolve to a stable location. This information will later be queried by build systems for determining where to store the accompanying pcms.
1 parent 5bf3f08 commit 584f8cc

File tree

5 files changed

+326
-3
lines changed

5 files changed

+326
-3
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ struct ModuleDeps {
114114
/// Whether this is a "system" module.
115115
bool IsSystem;
116116

117+
/// Whether this module is fully composed of file & module inputs from
118+
/// locations likely to stay the same across the active development and build
119+
/// cycle. For example, when all those input paths only resolve in Sysroot.
120+
///
121+
/// External paths, as opposed to virtual file paths, are always used
122+
/// for computing this value.
123+
bool IsInStableDirectories;
124+
117125
/// The path to the modulemap file which defines this module.
118126
///
119127
/// This can be used to explicitly build this module. This file will
@@ -219,6 +227,9 @@ class ModuleDepCollectorPP final : public PPCallbacks {
219227
llvm::DenseSet<const Module *> &AddedModules);
220228
void addAffectingClangModule(const Module *M, ModuleDeps &MD,
221229
llvm::DenseSet<const Module *> &AddedModules);
230+
231+
/// Add discovered module dependency for the given module.
232+
void addOneModuleDep(const Module *M, const ModuleID ID, ModuleDeps &MD);
222233
};
223234

224235
/// Collects modular and non-modular dependencies of the main file by attaching
@@ -320,6 +331,13 @@ void resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
320331
const LangOptions &LangOpts,
321332
CodeGenOptions &CGOpts);
322333

334+
/// Determine if \c Input can be resolved within a stable directory.
335+
///
336+
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
337+
/// \param Input Path to evaluate.
338+
bool isPathInStableDir(const ArrayRef<StringRef> Directories,
339+
const StringRef Input);
340+
323341
} // end namespace dependencies
324342
} // end namespace tooling
325343
} // end namespace clang

clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,32 @@ static void optimizeCWD(CowCompilerInvocation &BuildInvocation, StringRef CWD) {
157157
}
158158
}
159159

160+
/// Check a subset of invocation options to determine whether the current
161+
/// context can safely be considered as stable.
162+
static bool areOptionsInStableDir(CowCompilerInvocation &BuildInvocation,
163+
const ArrayRef<StringRef> StableDirs) {
164+
const auto &HSOpts = BuildInvocation.getHeaderSearchOpts();
165+
assert(isPathInStableDir(StableDirs, HSOpts.Sysroot) &&
166+
"Sysroots differ between module dependencies and current TU");
167+
168+
assert(isPathInStableDir(StableDirs, HSOpts.ResourceDir) &&
169+
"ResourceDirs differ between module dependencies and current TU");
170+
171+
for (const auto &Entry : HSOpts.UserEntries) {
172+
if (!Entry.IgnoreSysRoot)
173+
continue;
174+
if (!isPathInStableDir(StableDirs, Entry.Path))
175+
return false;
176+
}
177+
178+
for (const auto &SysPrefix : HSOpts.SystemHeaderPrefixes) {
179+
if (!isPathInStableDir(StableDirs, SysPrefix.Prefix))
180+
return false;
181+
}
182+
183+
return true;
184+
}
185+
160186
static std::vector<std::string> splitString(std::string S, char Separator) {
161187
SmallVector<StringRef> Segments;
162188
StringRef(S).split(Segments, Separator, /*MaxSplit=*/-1, /*KeepEmpty=*/false);
@@ -212,6 +238,25 @@ void dependencies::resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
212238
}
213239
}
214240

241+
bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
242+
const StringRef Input) {
243+
auto PathStartsWith = [](StringRef Prefix, StringRef Path) {
244+
auto PrefixIt = llvm::sys::path::begin(Prefix),
245+
PrefixEnd = llvm::sys::path::end(Prefix);
246+
for (auto PathIt = llvm::sys::path::begin(Path),
247+
PathEnd = llvm::sys::path::end(Path);
248+
PrefixIt != PrefixEnd && PathIt != PathEnd; ++PrefixIt, ++PathIt) {
249+
if (*PrefixIt != *PathIt)
250+
return false;
251+
}
252+
return PrefixIt == PrefixEnd;
253+
};
254+
255+
return any_of(Directories, [&](StringRef Dir) {
256+
return !Dir.empty() && PathStartsWith(Dir, Input);
257+
});
258+
}
259+
215260
static CowCompilerInvocation
216261
makeCommonInvocationForModuleBuild(CompilerInvocation CI) {
217262
CI.resetNonModularOptions();
@@ -698,6 +743,17 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
698743

699744
MD.ID.ModuleName = M->getFullModuleName();
700745
MD.IsSystem = M->IsSystem;
746+
747+
// Start off with the assumption that this module is shareable when there
748+
// is a sysroot provided. As more dependencies are discovered, check if those
749+
// come from the provided shared directories.
750+
const llvm::SmallVector<StringRef> StableDirs = {
751+
MDC.ScanInstance.getHeaderSearchOpts().Sysroot,
752+
MDC.ScanInstance.getHeaderSearchOpts().ResourceDir};
753+
MD.IsInStableDirectories =
754+
!StableDirs[0].empty() &&
755+
(llvm::sys::path::root_directory(StableDirs[0]) != StableDirs[0]);
756+
701757
// For modules which use export_as link name, the linked product that of the
702758
// corresponding export_as-named module.
703759
if (!M->UseExportAsModuleLinkName)
@@ -739,6 +795,12 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
739795
MDC.ScanInstance.getASTReader()->visitInputFileInfos(
740796
*MF, /*IncludeSystem=*/true,
741797
[&](const serialization::InputFileInfo &IFI, bool IsSystem) {
798+
if (MD.IsInStableDirectories) {
799+
auto FullFilePath = ASTReader::ResolveImportedPath(
800+
PathBuf, IFI.UnresolvedImportedFilename, MF->BaseDirectory);
801+
MD.IsInStableDirectories =
802+
isPathInStableDir(StableDirs, *FullFilePath);
803+
}
742804
if (!(IFI.TopLevel && IFI.ModuleMap))
743805
return;
744806
if (IFI.UnresolvedImportedFilenameAsRequested.ends_with(
@@ -780,6 +842,11 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
780842
}
781843
});
782844

845+
// Check provided input paths from the invocation for determining
846+
// IsInStableDirectories.
847+
if (MD.IsInStableDirectories)
848+
MD.IsInStableDirectories = areOptionsInStableDir(CI, StableDirs);
849+
783850
MDC.associateWithContextHash(CI, IgnoreCWD, MD);
784851

785852
// Finish the compiler invocation. Requires dependencies and the context hash.
@@ -821,8 +888,13 @@ void ModuleDepCollectorPP::addModulePrebuiltDeps(
821888
for (const Module *Import : M->Imports)
822889
if (Import->getTopLevelModule() != M->getTopLevelModule())
823890
if (MDC.isPrebuiltModule(Import->getTopLevelModule()))
824-
if (SeenSubmodules.insert(Import->getTopLevelModule()).second)
891+
if (SeenSubmodules.insert(Import->getTopLevelModule()).second) {
825892
MD.PrebuiltModuleDeps.emplace_back(Import->getTopLevelModule());
893+
// Conservatively consider the module as not coming from stable
894+
// directories, as transitive dependencies from the prebuilt module
895+
// have not been determined.
896+
MD.IsInStableDirectories = false;
897+
}
826898
}
827899

828900
void ModuleDepCollectorPP::addAllSubmoduleDeps(
@@ -835,6 +907,13 @@ void ModuleDepCollectorPP::addAllSubmoduleDeps(
835907
});
836908
}
837909

910+
void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID,
911+
ModuleDeps &MD) {
912+
MD.ClangModuleDeps.push_back(ID);
913+
if (MD.IsInStableDirectories)
914+
MD.IsInStableDirectories = MDC.ModularDeps[M]->IsInStableDirectories;
915+
}
916+
838917
void ModuleDepCollectorPP::addModuleDep(
839918
const Module *M, ModuleDeps &MD,
840919
llvm::DenseSet<const Module *> &AddedModules) {
@@ -843,7 +922,7 @@ void ModuleDepCollectorPP::addModuleDep(
843922
!MDC.isPrebuiltModule(Import)) {
844923
if (auto ImportID = handleTopLevelModule(Import->getTopLevelModule()))
845924
if (AddedModules.insert(Import->getTopLevelModule()).second)
846-
MD.ClangModuleDeps.push_back(*ImportID);
925+
addOneModuleDep(Import->getTopLevelModule(), *ImportID, MD);
847926
}
848927
}
849928
}
@@ -867,7 +946,7 @@ void ModuleDepCollectorPP::addAffectingClangModule(
867946
!MDC.isPrebuiltModule(Affecting)) {
868947
if (auto ImportID = handleTopLevelModule(Affecting))
869948
if (AddedModules.insert(Affecting).second)
870-
MD.ClangModuleDeps.push_back(*ImportID);
949+
addOneModuleDep(Affecting, *ImportID, MD);
871950
}
872951
}
873952
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// This test verifies modules that are entirely comprised from stable directory inputs are captured in
2+
// dependency information.
3+
4+
// The first compilation verifies that transitive dependencies on local input are captured.
5+
// The second compilation verifies that external paths are resolved when a
6+
// vfsoverlay for determining is-in-stable-directories.
7+
8+
// REQUIRES: shell
9+
// RUN: rm -rf %t
10+
// RUN: split-file %s %t
11+
// RUN: sed -e "s|DIR|%/t|g" %t/compile-commands.json.in > %t/compile-commands.json
12+
// RUN: sed -e "s|DIR|%/t|g" %t/overlay.json.template > %t/overlay.json
13+
// RUN: clang-scan-deps -compilation-database %t/compile-commands.json \
14+
// RUN: -j 1 -format experimental-full > %t/deps.db
15+
// RUN: cat %t/deps.db | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t
16+
17+
// CHECK: "modules": [
18+
// CHECK-NEXT: {
19+
// CHECK: "is-in-stable-directories": true,
20+
// CHECK: "name": "A"
21+
22+
// Verify that there are no more occurances of sysroot.
23+
// CHECK-NOT: "is-in-stable-directories"
24+
25+
// CHECK: "name": "A"
26+
// CHECK: "USE_VFS"
27+
// CHECK: "name": "B"
28+
// CHECK: "name": "C"
29+
// CHECK: "name": "D"
30+
// CHECK: "name": "NotInSDK"
31+
32+
//--- compile-commands.json.in
33+
[
34+
{
35+
"directory": "DIR",
36+
"command": "clang -c DIR/client.c -isysroot DIR/Sysroot -IDIR/Sysroot/usr/include -IDIR/BuildDir -fmodules -fmodules-cache-path=DIR/module-cache -fimplicit-module-maps",
37+
"file": "DIR/client.c"
38+
},
39+
{
40+
"directory": "DIR",
41+
"command": "clang -c DIR/client.c -isysroot DIR/Sysroot -IDIR/Sysroot/usr/include -ivfsoverlay DIR/overlay.json -DUSE_VFS -IDIR/BuildDir -fmodules -fmodules-cache-path=DIR/module-cache -fimplicit-module-maps",
42+
"file": "DIR/client.c"
43+
}
44+
]
45+
46+
//--- overlay.json.template
47+
{
48+
"version": 0,
49+
"case-sensitive": "false",
50+
"roots": [
51+
{
52+
"external-contents": "DIR/SysrootButNotReally/A/A_vfs.h",
53+
"name": "DIR/Sysroot/usr/include/A/A_vfs.h",
54+
"type": "file"
55+
}
56+
]
57+
}
58+
59+
//--- Sysroot/usr/include/A/module.modulemap
60+
module A {
61+
umbrella "."
62+
}
63+
64+
//--- Sysroot/usr/include/A/A.h
65+
#ifdef USE_VFS
66+
#include <A/A_vfs.h>
67+
#endif
68+
typedef int A_t;
69+
70+
//--- SysrootButNotReally/A/A_vfs.h
71+
typedef int typeFromVFS;
72+
73+
//--- Sysroot/usr/include/B/module.modulemap
74+
module B [system] {
75+
umbrella "."
76+
}
77+
78+
//--- Sysroot/usr/include/B/B.h
79+
#include <C/C.h>
80+
typedef int B_t;
81+
82+
//--- Sysroot/usr/include/C/module.modulemap
83+
module C [system] {
84+
umbrella "."
85+
}
86+
87+
//--- Sysroot/usr/include/C/C.h
88+
#include <D/D.h>
89+
90+
//--- Sysroot/usr/include/D/module.modulemap
91+
module D [system] {
92+
umbrella "."
93+
}
94+
95+
// Simulate a header that will be resolved in a local directory, from a sysroot header.
96+
//--- Sysroot/usr/include/D/D.h
97+
#include <HeaderNotFoundInSDK.h>
98+
99+
//--- BuildDir/module.modulemap
100+
module NotInSDK [system] {
101+
umbrella "."
102+
}
103+
104+
//--- BuildDir/HeaderNotFoundInSDK.h
105+
typedef int local_t;
106+
107+
//--- client.c
108+
#include <A/A.h>
109+
#include <B/B.h>

0 commit comments

Comments
 (0)