xref: /llvm-project/clang/lib/InstallAPI/DirectoryScanner.cpp (revision 2d48489cc35ec9bb1c15ff115595e62d67ca8989)
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::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 
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, 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 
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 &
76 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 
90 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     if (Error Err = scanHeaders(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(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 
171 llvm::Error
172 DirectoryScanner::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.
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(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 
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("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 
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(Directory)) {
285     Library &Framework = getOrCreateLibrary(Directory, Libraries);
286     if (Error Err = scanFrameworkDirectory(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, SubDir);
295 
296     if (Error Err = scanMultipleFrameworks(Path, Libraries))
297       return Err;
298   }
299 
300   return Error::success();
301 }
302 } // namespace clang::installapi
303