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