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