clang 20.0.0git
DirectoryScanner.cpp
Go to the documentation of this file.
1//===- DirectoryScanner.cpp -----------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "llvm/ADT/StringRef.h"
11#include "llvm/ADT/StringSwitch.h"
12#include "llvm/TextAPI/DylibReader.h"
13
14using namespace llvm;
15using namespace llvm::MachO;
16
17namespace clang::installapi {
18
20 HeaderSeq Headers;
21 for (const Library &Lib : Libraries)
22 llvm::append_range(Headers, Lib.Headers);
23 return Headers;
24}
25
26llvm::Error DirectoryScanner::scan(StringRef Directory) {
27 if (Mode == ScanMode::ScanFrameworks)
28 return scanForFrameworks(Directory);
29
30 return scanForUnwrappedLibraries(Directory);
31}
32
33llvm::Error DirectoryScanner::scanForUnwrappedLibraries(StringRef Directory) {
34 // Check some known sub-directory locations.
35 auto GetDirectory = [&](const char *Sub) -> OptionalDirectoryEntryRef {
36 SmallString<PATH_MAX> Path(Directory);
37 sys::path::append(Path, Sub);
39 };
40
41 auto DirPublic = GetDirectory("usr/include");
42 auto DirPrivate = GetDirectory("usr/local/include");
43 if (!DirPublic && !DirPrivate) {
44 std::error_code ec = std::make_error_code(std::errc::not_a_directory);
45 return createStringError(ec,
46 "cannot find any public (usr/include) or private "
47 "(usr/local/include) header directory");
48 }
49
50 Library &Lib = getOrCreateLibrary(Directory, Libraries);
51 Lib.IsUnwrappedDylib = true;
52
53 if (DirPublic)
54 if (Error Err = scanHeaders(DirPublic->getName(), Lib, HeaderType::Public,
55 Directory))
56 return Err;
57
58 if (DirPrivate)
59 if (Error Err = scanHeaders(DirPrivate->getName(), Lib, HeaderType::Private,
60 Directory))
61 return Err;
62
63 return Error::success();
64}
65
66static bool isFramework(StringRef Path) {
67 while (Path.back() == '/')
68 Path = Path.slice(0, Path.size() - 1);
69
70 return llvm::StringSwitch<bool>(llvm::sys::path::extension(Path))
71 .Case(".framework", true)
72 .Default(false);
73}
74
75Library &
76DirectoryScanner::getOrCreateLibrary(StringRef Path,
77 std::vector<Library> &Libs) const {
78 if (Path.consume_front(RootPath) && Path.empty())
79 Path = "/";
80
81 auto LibIt =
82 find_if(Libs, [Path](const Library &L) { return L.getPath() == Path; });
83 if (LibIt != Libs.end())
84 return *LibIt;
85
86 Libs.emplace_back(Path);
87 return Libs.back();
88}
89
90Error DirectoryScanner::scanHeaders(StringRef Path, Library &Lib,
91 HeaderType Type, StringRef BasePath,
92 StringRef ParentPath) const {
93 std::error_code ec;
94 auto &FS = FM.getVirtualFileSystem();
95 PathSeq SubDirectories;
96 for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
97 i.increment(ec)) {
98 StringRef HeaderPath = i->path();
99 if (ec)
100 return createStringError(ec, "unable to read: " + HeaderPath);
101
102 if (sys::fs::is_symlink_file(HeaderPath))
103 continue;
104
105 // Ignore tmp files from unifdef.
106 const StringRef Filename = sys::path::filename(HeaderPath);
107 if (Filename.starts_with("."))
108 continue;
109
110 // If it is a directory, remember the subdirectory.
111 if (FM.getOptionalDirectoryRef(HeaderPath))
112 SubDirectories.push_back(HeaderPath.str());
113
114 if (!isHeaderFile(HeaderPath))
115 continue;
116
117 // Skip files that do not exist. This usually happens for broken symlinks.
118 if (FS.status(HeaderPath) == std::errc::no_such_file_or_directory)
119 continue;
120
121 auto IncludeName = createIncludeHeaderName(HeaderPath);
122 Lib.addHeaderFile(HeaderPath, Type,
123 IncludeName.has_value() ? IncludeName.value() : "");
124 }
125
126 // Go through the subdirectories.
127 // Sort the sub-directory first since different file systems might have
128 // different traverse order.
129 llvm::sort(SubDirectories);
130 if (ParentPath.empty())
131 ParentPath = Path;
132 for (const StringRef Dir : SubDirectories)
133 if (Error Err = scanHeaders(Dir, Lib, Type, BasePath, ParentPath))
134 return Err;
135
136 return Error::success();
137}
138
139llvm::Error
140DirectoryScanner::scanMultipleFrameworks(StringRef Directory,
141 std::vector<Library> &Libs) const {
142 std::error_code ec;
143 auto &FS = FM.getVirtualFileSystem();
144 for (vfs::directory_iterator i = FS.dir_begin(Directory, ec), ie; i != ie;
145 i.increment(ec)) {
146 StringRef Curr = i->path();
147
148 // Skip files that do not exist. This usually happens for broken symlinks.
149 if (ec == std::errc::no_such_file_or_directory) {
150 ec.clear();
151 continue;
152 }
153 if (ec)
154 return createStringError(ec, Curr);
155
156 if (sys::fs::is_symlink_file(Curr))
157 continue;
158
159 if (isFramework(Curr)) {
160 if (!FM.getOptionalDirectoryRef(Curr))
161 continue;
162 Library &Framework = getOrCreateLibrary(Curr, Libs);
163 if (Error Err = scanFrameworkDirectory(Curr, Framework))
164 return Err;
165 }
166 }
167
168 return Error::success();
169}
170
171llvm::Error
172DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory,
173 std::vector<Library> &Libs) const {
174 if (FM.getOptionalDirectoryRef(Directory))
175 return scanMultipleFrameworks(Directory, Libs);
176
177 std::error_code ec = std::make_error_code(std::errc::not_a_directory);
178 return createStringError(ec, Directory);
179}
180
181/// FIXME: How to handle versions? For now scan them separately as independent
182/// frameworks.
183llvm::Error
184DirectoryScanner::scanFrameworkVersionsDirectory(StringRef Path,
185 Library &Lib) const {
186 std::error_code ec;
187 auto &FS = FM.getVirtualFileSystem();
188 for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
189 i.increment(ec)) {
190 const StringRef Curr = i->path();
191
192 // Skip files that do not exist. This usually happens for broken symlinks.
193 if (ec == std::errc::no_such_file_or_directory) {
194 ec.clear();
195 continue;
196 }
197 if (ec)
198 return createStringError(ec, Curr);
199
200 if (sys::fs::is_symlink_file(Curr))
201 continue;
202
203 // Each version should be a framework directory.
204 if (!FM.getOptionalDirectoryRef(Curr))
205 continue;
206
207 Library &VersionedFramework =
208 getOrCreateLibrary(Curr, Lib.FrameworkVersions);
209 if (Error Err = scanFrameworkDirectory(Curr, VersionedFramework))
210 return Err;
211 }
212
213 return Error::success();
214}
215
216llvm::Error DirectoryScanner::scanFrameworkDirectory(StringRef Path,
217 Library &Framework) const {
218 // If the framework is inside Kernel or IOKit, scan headers in the different
219 // directories separately.
220 Framework.IsUnwrappedDylib =
221 Path.contains("Kernel.framework") || Path.contains("IOKit.framework");
222
223 // Unfortunately we cannot identify symlinks in the VFS. We assume that if
224 // there is a Versions directory, then we have symlinks and directly proceed
225 // to the Versions folder.
226 std::error_code ec;
227 auto &FS = FM.getVirtualFileSystem();
228
229 for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
230 i.increment(ec)) {
231 StringRef Curr = i->path();
232 // Skip files that do not exist. This usually happens for broken symlinks.
233 if (ec == std::errc::no_such_file_or_directory) {
234 ec.clear();
235 continue;
236 }
237
238 if (ec)
239 return createStringError(ec, Curr);
240
241 if (sys::fs::is_symlink_file(Curr))
242 continue;
243
244 StringRef FileName = sys::path::filename(Curr);
245 // Scan all "public" headers.
246 if (FileName.contains("Headers")) {
247 if (Error Err = scanHeaders(Curr, Framework, HeaderType::Public, Curr))
248 return Err;
249 continue;
250 }
251 // Scan all "private" headers.
252 if (FileName.contains("PrivateHeaders")) {
253 if (Error Err = scanHeaders(Curr, Framework, HeaderType::Private, Curr))
254 return Err;
255 continue;
256 }
257 // Scan sub frameworks.
258 if (FileName.contains("Frameworks")) {
259 if (Error Err = scanSubFrameworksDirectory(Curr, Framework.SubFrameworks))
260 return Err;
261 continue;
262 }
263 // Check for versioned frameworks.
264 if (FileName.contains("Versions")) {
265 if (Error Err = scanFrameworkVersionsDirectory(Curr, Framework))
266 return Err;
267 continue;
268 }
269 }
270
271 return Error::success();
272}
273
274llvm::Error DirectoryScanner::scanForFrameworks(StringRef Directory) {
275 RootPath = "";
276
277 // Expect a certain directory structure and naming convention to find
278 // frameworks.
279 static const char *SubDirectories[] = {"System/Library/Frameworks/",
280 "System/Library/PrivateFrameworks/"};
281
282 // Check if the directory is already a framework.
283 if (isFramework(Directory)) {
284 Library &Framework = getOrCreateLibrary(Directory, Libraries);
285 if (Error Err = scanFrameworkDirectory(Directory, Framework))
286 return Err;
287 return Error::success();
288 }
289
290 // Check known sub-directory locations.
291 for (const auto *SubDir : SubDirectories) {
292 SmallString<PATH_MAX> Path(Directory);
293 sys::path::append(Path, SubDir);
294
295 if (Error Err = scanMultipleFrameworks(Path, Libraries))
296 return Err;
297 }
298
299 return Error::success();
300}
301} // namespace clang::installapi
IndirectLocalPath & Path
StringRef Filename
Definition: Format.cpp:3014
llvm::MachO::PathSeq PathSeq
Definition: MachO.h:48
llvm::vfs::FileSystem & getVirtualFileSystem() const
Definition: FileManager.h:251
OptionalDirectoryEntryRef getOptionalDirectoryRef(StringRef DirName, bool CacheFailure=true)
Get a DirectoryEntryRef if it exists, without doing anything on error.
Definition: FileManager.h:175
llvm::Error scan(StringRef Directory)
Scan for all input files throughout directory.
static HeaderSeq getHeaders(ArrayRef< Library > Libraries)
Get all the header files in libraries.
The DirectoryScanner for collecting library files on the file system.
Definition: Context.h:20
@ ScanFrameworks
Scanning Framework directory.
std::vector< HeaderFile > HeaderSeq
Definition: HeaderFile.h:150
@ Public
Represents declarations accessible to all clients.
@ Private
Represents declarations accessible to a disclosed set of clients.
static bool isFramework(StringRef Path)
std::optional< std::string > createIncludeHeaderName(const StringRef FullPath)
Assemble expected way header will be included by clients.
Definition: HeaderFile.cpp:19
bool isHeaderFile(StringRef Path)
Determine if Path is a header file.
Definition: HeaderFile.cpp:39
Diagnostic wrappers for TextAPI types for error reporting.
Definition: Dominators.h:30