Skip to content

Commit 36b7b6b

Browse files
committed
[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. (cherry picked from commit 584f8cc)
1 parent 2f98a9d commit 36b7b6b

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
@@ -120,6 +120,14 @@ struct ModuleDeps {
120120
/// Whether current working directory is ignored.
121121
bool IgnoreCWD;
122122

123+
/// Whether this module is fully composed of file & module inputs from
124+
/// locations likely to stay the same across the active development and build
125+
/// cycle. For example, when all those input paths only resolve in Sysroot.
126+
///
127+
/// External paths, as opposed to virtual file paths, are always used
128+
/// for computing this value.
129+
bool IsInStableDirectories;
130+
123131
/// The path to the modulemap file which defines this module.
124132
///
125133
/// This can be used to explicitly build this module. This file will
@@ -234,6 +242,9 @@ class ModuleDepCollectorPP final : public PPCallbacks {
234242
llvm::DenseSet<const Module *> &AddedModules);
235243
void addAffectingClangModule(const Module *M, ModuleDeps &MD,
236244
llvm::DenseSet<const Module *> &AddedModules);
245+
246+
/// Add discovered module dependency for the given module.
247+
void addOneModuleDep(const Module *M, const ModuleID ID, ModuleDeps &MD);
237248
};
238249

239250
/// Collects modular and non-modular dependencies of the main file by attaching
@@ -335,6 +346,13 @@ void resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
335346
const LangOptions &LangOpts,
336347
CodeGenOptions &CGOpts);
337348

349+
/// Determine if \c Input can be resolved within a stable directory.
350+
///
351+
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
352+
/// \param Input Path to evaluate.
353+
bool isPathInStableDir(const ArrayRef<StringRef> Directories,
354+
const StringRef Input);
355+
338356
} // end namespace dependencies
339357
} // end namespace tooling
340358
} // end namespace clang

clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,32 @@ static void optimizeCWD(CowCompilerInvocation &BuildInvocation, StringRef CWD) {
162162
}
163163
}
164164

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

246+
bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
247+
const StringRef Input) {
248+
auto PathStartsWith = [](StringRef Prefix, StringRef Path) {
249+
auto PrefixIt = llvm::sys::path::begin(Prefix),
250+
PrefixEnd = llvm::sys::path::end(Prefix);
251+
for (auto PathIt = llvm::sys::path::begin(Path),
252+
PathEnd = llvm::sys::path::end(Path);
253+
PrefixIt != PrefixEnd && PathIt != PathEnd; ++PrefixIt, ++PathIt) {
254+
if (*PrefixIt != *PathIt)
255+
return false;
256+
}
257+
return PrefixIt == PrefixEnd;
258+
};
259+
260+
return any_of(Directories, [&](StringRef Dir) {
261+
return !Dir.empty() && PathStartsWith(Dir, Input);
262+
});
263+
}
264+
220265
static CowCompilerInvocation
221266
makeCommonInvocationForModuleBuild(CompilerInvocation CI) {
222267
CI.resetNonModularOptions();
@@ -770,6 +815,17 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
770815

771816
MD.ID.ModuleName = M->getFullModuleName();
772817
MD.IsSystem = M->IsSystem;
818+
819+
// Start off with the assumption that this module is shareable when there
820+
// is a sysroot provided. As more dependencies are discovered, check if those
821+
// come from the provided shared directories.
822+
const llvm::SmallVector<StringRef> StableDirs = {
823+
MDC.ScanInstance.getHeaderSearchOpts().Sysroot,
824+
MDC.ScanInstance.getHeaderSearchOpts().ResourceDir};
825+
MD.IsInStableDirectories =
826+
!StableDirs[0].empty() &&
827+
(llvm::sys::path::root_directory(StableDirs[0]) != StableDirs[0]);
828+
773829
// For modules which use export_as link name, the linked product that of the
774830
// corresponding export_as-named module.
775831
if (!M->UseExportAsModuleLinkName)
@@ -811,6 +867,12 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
811867
MDC.ScanInstance.getASTReader()->visitInputFileInfos(
812868
*MF, /*IncludeSystem=*/true,
813869
[&](const serialization::InputFileInfo &IFI, bool IsSystem) {
870+
if (MD.IsInStableDirectories) {
871+
auto FullFilePath = ASTReader::ResolveImportedPath(
872+
PathBuf, IFI.UnresolvedImportedFilename, MF->BaseDirectory);
873+
MD.IsInStableDirectories =
874+
isPathInStableDir(StableDirs, *FullFilePath);
875+
}
814876
if (!(IFI.TopLevel && IFI.ModuleMap))
815877
return;
816878
if (IFI.UnresolvedImportedFilenameAsRequested.ends_with(
@@ -878,6 +940,11 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
878940
}
879941

880942
MD.IgnoreCWD = IgnoreCWD;
943+
// Check provided input paths from the invocation for determining
944+
// IsInStableDirectories.
945+
if (MD.IsInStableDirectories)
946+
MD.IsInStableDirectories = areOptionsInStableDir(CI, StableDirs);
947+
881948
MDC.associateWithContextHash(CI, IgnoreCWD, MD);
882949

883950
// Finish the compiler invocation. Requires dependencies and the context hash.
@@ -929,8 +996,13 @@ void ModuleDepCollectorPP::addModulePrebuiltDeps(
929996
for (const Module *Import : M->Imports)
930997
if (Import->getTopLevelModule() != M->getTopLevelModule())
931998
if (MDC.isPrebuiltModule(Import->getTopLevelModule()))
932-
if (SeenSubmodules.insert(Import->getTopLevelModule()).second)
999+
if (SeenSubmodules.insert(Import->getTopLevelModule()).second) {
9331000
MD.PrebuiltModuleDeps.emplace_back(Import->getTopLevelModule());
1001+
// Conservatively consider the module as not coming from stable
1002+
// directories, as transitive dependencies from the prebuilt module
1003+
// have not been determined.
1004+
MD.IsInStableDirectories = false;
1005+
}
9341006
}
9351007

9361008
void ModuleDepCollectorPP::addAllSubmoduleDeps(
@@ -943,6 +1015,13 @@ void ModuleDepCollectorPP::addAllSubmoduleDeps(
9431015
});
9441016
}
9451017

1018+
void ModuleDepCollectorPP::addOneModuleDep(const Module *M, const ModuleID ID,
1019+
ModuleDeps &MD) {
1020+
MD.ClangModuleDeps.push_back(ID);
1021+
if (MD.IsInStableDirectories)
1022+
MD.IsInStableDirectories = MDC.ModularDeps[M]->IsInStableDirectories;
1023+
}
1024+
9461025
void ModuleDepCollectorPP::addModuleDep(
9471026
const Module *M, ModuleDeps &MD,
9481027
llvm::DenseSet<const Module *> &AddedModules) {
@@ -951,7 +1030,7 @@ void ModuleDepCollectorPP::addModuleDep(
9511030
!MDC.isPrebuiltModule(Import)) {
9521031
if (auto ImportID = handleTopLevelModule(Import->getTopLevelModule()))
9531032
if (AddedModules.insert(Import->getTopLevelModule()).second)
954-
MD.ClangModuleDeps.push_back(*ImportID);
1033+
addOneModuleDep(Import->getTopLevelModule(), *ImportID, MD);
9551034
}
9561035
}
9571036
}
@@ -975,7 +1054,7 @@ void ModuleDepCollectorPP::addAffectingClangModule(
9751054
!MDC.isPrebuiltModule(Affecting)) {
9761055
if (auto ImportID = handleTopLevelModule(Affecting))
9771056
if (AddedModules.insert(Affecting).second)
978-
MD.ClangModuleDeps.push_back(*ImportID);
1057+
addOneModuleDep(Affecting, *ImportID, MD);
9791058
}
9801059
}
9811060
}
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)