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