xref: /freebsd/contrib/llvm-project/clang/lib/InstallAPI/DirectoryScanner.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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