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