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
getHeaders(ArrayRef<Library> Libraries)19 HeaderSeq DirectoryScanner::getHeaders(ArrayRef<Library> Libraries) {
20 HeaderSeq Headers;
21 for (const Library &Lib : Libraries)
22 llvm::append_range(Headers, Lib.Headers);
23 return Headers;
24 }
25
scan(StringRef Directory)26 llvm::Error DirectoryScanner::scan(StringRef Directory) {
27 if (Mode == ScanMode::ScanFrameworks)
28 return scanForFrameworks(Directory);
29
30 return scanForUnwrappedLibraries(Directory);
31 }
32
scanForUnwrappedLibraries(StringRef Directory)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, Sub);
38 return FM.getOptionalDirectoryRef(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(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
isFramework(StringRef Path)66 static 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
75 Library &
getOrCreateLibrary(StringRef Path,std::vector<Library> & Libs) const76 DirectoryScanner::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
scanHeaders(StringRef Path,Library & Lib,HeaderType Type,StringRef BasePath,StringRef ParentPath) const90 Error 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 return scanHeaders(Dir, Lib, Type, BasePath, ParentPath);
134
135 return Error::success();
136 }
137
138 llvm::Error
scanMultipleFrameworks(StringRef Directory,std::vector<Library> & Libs) const139 DirectoryScanner::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(Directory, ec), ie; i != ie;
144 i.increment(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, Curr);
154
155 if (sys::fs::is_symlink_file(Curr))
156 continue;
157
158 if (isFramework(Curr)) {
159 if (!FM.getOptionalDirectoryRef(Curr))
160 continue;
161 Library &Framework = getOrCreateLibrary(Curr, Libs);
162 if (Error Err = scanFrameworkDirectory(Curr, Framework))
163 return Err;
164 }
165 }
166
167 return Error::success();
168 }
169
170 llvm::Error
scanSubFrameworksDirectory(StringRef Directory,std::vector<Library> & Libs) const171 DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory,
172 std::vector<Library> &Libs) const {
173 if (FM.getOptionalDirectoryRef(Directory))
174 return scanMultipleFrameworks(Directory, Libs);
175
176 std::error_code ec = std::make_error_code(std::errc::not_a_directory);
177 return createStringError(ec, Directory);
178 }
179
180 /// FIXME: How to handle versions? For now scan them separately as independent
181 /// frameworks.
182 llvm::Error
scanFrameworkVersionsDirectory(StringRef Path,Library & Lib) const183 DirectoryScanner::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(Path, ec), ie; i != ie;
188 i.increment(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, Curr);
198
199 if (sys::fs::is_symlink_file(Curr))
200 continue;
201
202 // Each version should be a framework directory.
203 if (!FM.getOptionalDirectoryRef(Curr))
204 continue;
205
206 Library &VersionedFramework =
207 getOrCreateLibrary(Curr, Lib.FrameworkVersions);
208 if (Error Err = scanFrameworkDirectory(Curr, VersionedFramework))
209 return Err;
210 }
211
212 return Error::success();
213 }
214
scanFrameworkDirectory(StringRef Path,Library & Framework) const215 llvm::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("Kernel.framework") || Path.contains("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(Path, ec), ie; i != ie;
229 i.increment(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, Curr);
239
240 if (sys::fs::is_symlink_file(Curr))
241 continue;
242
243 StringRef FileName = sys::path::filename(Curr);
244 // Scan all "public" headers.
245 if (FileName.contains("Headers")) {
246 if (Error Err = scanHeaders(Curr, Framework, HeaderType::Public, Curr))
247 return Err;
248 continue;
249 }
250 // Scan all "private" headers.
251 if (FileName.contains("PrivateHeaders")) {
252 if (Error Err = scanHeaders(Curr, Framework, HeaderType::Private, Curr))
253 return Err;
254 continue;
255 }
256 // Scan sub frameworks.
257 if (FileName.contains("Frameworks")) {
258 if (Error Err = scanSubFrameworksDirectory(Curr, Framework.SubFrameworks))
259 return Err;
260 continue;
261 }
262 // Check for versioned frameworks.
263 if (FileName.contains("Versions")) {
264 if (Error Err = scanFrameworkVersionsDirectory(Curr, Framework))
265 return Err;
266 continue;
267 }
268 }
269
270 return Error::success();
271 }
272
scanForFrameworks(StringRef Directory)273 llvm::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
281 // Check if the directory is already a framework.
282 if (isFramework(Directory)) {
283 Library &Framework = getOrCreateLibrary(Directory, Libraries);
284 if (Error Err = scanFrameworkDirectory(Directory, Framework))
285 return Err;
286 return Error::success();
287 }
288
289 // Check known sub-directory locations.
290 for (const auto *SubDir : SubDirectories) {
291 SmallString<PATH_MAX> Path(Directory);
292 sys::path::append(Path, SubDir);
293
294 if (Error Err = scanMultipleFrameworks(Path, Libraries))
295 return Err;
296 }
297
298 return Error::success();
299 }
300 } // namespace clang::installapi
301