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 | #include "llvm/TextAPI/DylibReader.h" |
13 | |
14 | using namespace llvm; |
15 | using namespace llvm::MachO; |
16 | |
17 | namespace clang::installapi { |
18 | |
19 | HeaderSeq DirectoryScanner::(ArrayRef<Library> Libraries) { |
20 | HeaderSeq ; |
21 | for (const Library &Lib : Libraries) |
22 | llvm::append_range(C&: Headers, R: Lib.Headers); |
23 | return Headers; |
24 | } |
25 | |
26 | llvm::Error DirectoryScanner::scan(StringRef Directory) { |
27 | if (Mode == ScanMode::ScanFrameworks) |
28 | return scanForFrameworks(Directory); |
29 | |
30 | return scanForUnwrappedLibraries(Directory); |
31 | } |
32 | |
33 | llvm::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&: Path, a: Sub); |
38 | return FM.getOptionalDirectoryRef(DirName: Path); |
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(e: std::errc::not_a_directory); |
45 | return createStringError(EC: ec, |
46 | S: "cannot find any public (usr/include) or private " |
47 | "(usr/local/include) header directory" ); |
48 | } |
49 | |
50 | Library &Lib = getOrCreateLibrary(Path: Directory, Libs&: Libraries); |
51 | Lib.IsUnwrappedDylib = true; |
52 | |
53 | if (DirPublic) |
54 | if (Error Err = scanHeaders(Path: DirPublic->getName(), Lib, Type: HeaderType::Public, |
55 | BasePath: Directory)) |
56 | return Err; |
57 | |
58 | if (DirPrivate) |
59 | if (Error Err = scanHeaders(Path: DirPrivate->getName(), Lib, Type: HeaderType::Private, |
60 | BasePath: Directory)) |
61 | return Err; |
62 | |
63 | return Error::success(); |
64 | } |
65 | |
66 | static bool isFramework(StringRef Path) { |
67 | while (Path.back() == '/') |
68 | Path = Path.slice(Start: 0, End: Path.size() - 1); |
69 | |
70 | return llvm::StringSwitch<bool>(llvm::sys::path::extension(path: Path)) |
71 | .Case(S: ".framework" , Value: true) |
72 | .Default(Value: false); |
73 | } |
74 | |
75 | Library & |
76 | DirectoryScanner::getOrCreateLibrary(StringRef Path, |
77 | std::vector<Library> &Libs) const { |
78 | if (Path.consume_front(Prefix: RootPath) && Path.empty()) |
79 | Path = "/" ; |
80 | |
81 | auto LibIt = |
82 | find_if(Range&: Libs, P: [Path](const Library &L) { return L.getPath() == Path; }); |
83 | if (LibIt != Libs.end()) |
84 | return *LibIt; |
85 | |
86 | Libs.emplace_back(args&: Path); |
87 | return Libs.back(); |
88 | } |
89 | |
90 | Error DirectoryScanner::(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(Dir: Path, EC&: ec), ie; i != ie; |
97 | i.increment(EC&: ec)) { |
98 | StringRef = i->path(); |
99 | if (ec) |
100 | return createStringError(EC: ec, S: "unable to read: " + HeaderPath); |
101 | |
102 | if (sys::fs::is_symlink_file(Path: HeaderPath)) |
103 | continue; |
104 | |
105 | // Ignore tmp files from unifdef. |
106 | const StringRef Filename = sys::path::filename(path: HeaderPath); |
107 | if (Filename.starts_with(Prefix: "." )) |
108 | continue; |
109 | |
110 | // If it is a directory, remember the subdirectory. |
111 | if (FM.getOptionalDirectoryRef(DirName: HeaderPath)) |
112 | SubDirectories.push_back(x: HeaderPath.str()); |
113 | |
114 | if (!isHeaderFile(Path: HeaderPath)) |
115 | continue; |
116 | |
117 | // Skip files that do not exist. This usually happens for broken symlinks. |
118 | if (FS.status(Path: HeaderPath) == std::errc::no_such_file_or_directory) |
119 | continue; |
120 | |
121 | auto IncludeName = createIncludeHeaderName(FullPath: HeaderPath); |
122 | Lib.addHeaderFile(FullPath: HeaderPath, Type, |
123 | IncludePath: 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(C&: SubDirectories); |
130 | if (ParentPath.empty()) |
131 | ParentPath = Path; |
132 | for (const StringRef Dir : SubDirectories) |
133 | if (Error Err = scanHeaders(Path: Dir, Lib, Type, BasePath, ParentPath)) |
134 | return Err; |
135 | |
136 | return Error::success(); |
137 | } |
138 | |
139 | llvm::Error |
140 | DirectoryScanner::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(Dir: Directory, EC&: ec), ie; i != ie; |
145 | i.increment(EC&: 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: ec, S: Curr); |
155 | |
156 | if (sys::fs::is_symlink_file(Path: Curr)) |
157 | continue; |
158 | |
159 | if (isFramework(Path: Curr)) { |
160 | if (!FM.getOptionalDirectoryRef(DirName: Curr)) |
161 | continue; |
162 | Library &Framework = getOrCreateLibrary(Path: Curr, Libs); |
163 | if (Error Err = scanFrameworkDirectory(Path: Curr, Framework)) |
164 | return Err; |
165 | } |
166 | } |
167 | |
168 | return Error::success(); |
169 | } |
170 | |
171 | llvm::Error |
172 | DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory, |
173 | std::vector<Library> &Libs) const { |
174 | if (FM.getOptionalDirectoryRef(DirName: Directory)) |
175 | return scanMultipleFrameworks(Directory, Libs); |
176 | |
177 | std::error_code ec = std::make_error_code(e: std::errc::not_a_directory); |
178 | return createStringError(EC: ec, S: Directory); |
179 | } |
180 | |
181 | /// FIXME: How to handle versions? For now scan them separately as independent |
182 | /// frameworks. |
183 | llvm::Error |
184 | DirectoryScanner::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(Dir: Path, EC&: ec), ie; i != ie; |
189 | i.increment(EC&: 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: ec, S: Curr); |
199 | |
200 | if (sys::fs::is_symlink_file(Path: Curr)) |
201 | continue; |
202 | |
203 | // Each version should be a framework directory. |
204 | if (!FM.getOptionalDirectoryRef(DirName: Curr)) |
205 | continue; |
206 | |
207 | Library &VersionedFramework = |
208 | getOrCreateLibrary(Path: Curr, Libs&: Lib.FrameworkVersions); |
209 | if (Error Err = scanFrameworkDirectory(Path: Curr, Framework&: VersionedFramework)) |
210 | return Err; |
211 | } |
212 | |
213 | return Error::success(); |
214 | } |
215 | |
216 | llvm::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(Other: "Kernel.framework" ) || Path.contains(Other: "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(Dir: Path, EC&: ec), ie; i != ie; |
230 | i.increment(EC&: 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: ec, S: Curr); |
240 | |
241 | if (sys::fs::is_symlink_file(Path: Curr)) |
242 | continue; |
243 | |
244 | StringRef FileName = sys::path::filename(path: Curr); |
245 | // Scan all "public" headers. |
246 | if (FileName.contains(Other: "Headers" )) { |
247 | if (Error Err = scanHeaders(Path: Curr, Lib&: Framework, Type: HeaderType::Public, BasePath: Curr)) |
248 | return Err; |
249 | continue; |
250 | } |
251 | // Scan all "private" headers. |
252 | if (FileName.contains(Other: "PrivateHeaders" )) { |
253 | if (Error Err = scanHeaders(Path: Curr, Lib&: Framework, Type: HeaderType::Private, BasePath: Curr)) |
254 | return Err; |
255 | continue; |
256 | } |
257 | // Scan sub frameworks. |
258 | if (FileName.contains(Other: "Frameworks" )) { |
259 | if (Error Err = scanSubFrameworksDirectory(Directory: Curr, Libs&: Framework.SubFrameworks)) |
260 | return Err; |
261 | continue; |
262 | } |
263 | // Check for versioned frameworks. |
264 | if (FileName.contains(Other: "Versions" )) { |
265 | if (Error Err = scanFrameworkVersionsDirectory(Path: Curr, Lib&: Framework)) |
266 | return Err; |
267 | continue; |
268 | } |
269 | } |
270 | |
271 | return Error::success(); |
272 | } |
273 | |
274 | llvm::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 | "System/Library/SubFrameworks" }; |
282 | |
283 | // Check if the directory is already a framework. |
284 | if (isFramework(Path: Directory)) { |
285 | Library &Framework = getOrCreateLibrary(Path: Directory, Libs&: Libraries); |
286 | if (Error Err = scanFrameworkDirectory(Path: Directory, Framework)) |
287 | return Err; |
288 | return Error::success(); |
289 | } |
290 | |
291 | // Check known sub-directory locations. |
292 | for (const auto *SubDir : SubDirectories) { |
293 | SmallString<PATH_MAX> Path(Directory); |
294 | sys::path::append(path&: Path, a: SubDir); |
295 | |
296 | if (Error Err = scanMultipleFrameworks(Directory: Path, Libs&: Libraries)) |
297 | return Err; |
298 | } |
299 | |
300 | return Error::success(); |
301 | } |
302 | } // namespace clang::installapi |
303 | |