xref: /freebsd/contrib/llvm-project/clang/lib/InstallAPI/DirectoryScanner.cpp (revision 27ef5d48c729defb83a8822143dc71ab17f9d68b)
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      return scanHeaders(Dir, Lib, Type, BasePath, ParentPath);
134  
135    return Error::success();
136  }
137  
138  llvm::Error
139  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
171  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
183  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  
215  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  
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