clang  20.0.0git
DirectoryScanner.cpp
Go to the documentation of this file.
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 
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  if (Error Err = scanHeaders(Dir, Lib, Type, BasePath, ParentPath))
134  return Err;
135 
136  return Error::success();
137 }
138 
139 llvm::Error
140 DirectoryScanner::scanMultipleFrameworks(StringRef Directory,
141  std::vector<Library> &Libs) const {
142  std::error_code ec;
143  auto &FS = FM.getVirtualFileSystem();
144  for (vfs::directory_iterator i = FS.dir_begin(Directory, ec), ie; i != ie;
145  i.increment(ec)) {
146  StringRef Curr = i->path();
147 
148  // Skip files that do not exist. This usually happens for broken symlinks.
149  if (ec == std::errc::no_such_file_or_directory) {
150  ec.clear();
151  continue;
152  }
153  if (ec)
154  return createStringError(ec, Curr);
155 
156  if (sys::fs::is_symlink_file(Curr))
157  continue;
158 
159  if (isFramework(Curr)) {
160  if (!FM.getOptionalDirectoryRef(Curr))
161  continue;
162  Library &Framework = getOrCreateLibrary(Curr, Libs);
163  if (Error Err = scanFrameworkDirectory(Curr, Framework))
164  return Err;
165  }
166  }
167 
168  return Error::success();
169 }
170 
171 llvm::Error
172 DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory,
173  std::vector<Library> &Libs) const {
174  if (FM.getOptionalDirectoryRef(Directory))
175  return scanMultipleFrameworks(Directory, Libs);
176 
177  std::error_code ec = std::make_error_code(std::errc::not_a_directory);
178  return createStringError(ec, Directory);
179 }
180 
181 /// FIXME: How to handle versions? For now scan them separately as independent
182 /// frameworks.
183 llvm::Error
184 DirectoryScanner::scanFrameworkVersionsDirectory(StringRef Path,
185  Library &Lib) const {
186  std::error_code ec;
187  auto &FS = FM.getVirtualFileSystem();
188  for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
189  i.increment(ec)) {
190  const StringRef Curr = i->path();
191 
192  // Skip files that do not exist. This usually happens for broken symlinks.
193  if (ec == std::errc::no_such_file_or_directory) {
194  ec.clear();
195  continue;
196  }
197  if (ec)
198  return createStringError(ec, Curr);
199 
200  if (sys::fs::is_symlink_file(Curr))
201  continue;
202 
203  // Each version should be a framework directory.
204  if (!FM.getOptionalDirectoryRef(Curr))
205  continue;
206 
207  Library &VersionedFramework =
208  getOrCreateLibrary(Curr, Lib.FrameworkVersions);
209  if (Error Err = scanFrameworkDirectory(Curr, VersionedFramework))
210  return Err;
211  }
212 
213  return Error::success();
214 }
215 
216 llvm::Error DirectoryScanner::scanFrameworkDirectory(StringRef Path,
217  Library &Framework) const {
218  // If the framework is inside Kernel or IOKit, scan headers in the different
219  // directories separately.
220  Framework.IsUnwrappedDylib =
221  Path.contains("Kernel.framework") || Path.contains("IOKit.framework");
222 
223  // Unfortunately we cannot identify symlinks in the VFS. We assume that if
224  // there is a Versions directory, then we have symlinks and directly proceed
225  // to the Versions folder.
226  std::error_code ec;
227  auto &FS = FM.getVirtualFileSystem();
228 
229  for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
230  i.increment(ec)) {
231  StringRef Curr = i->path();
232  // Skip files that do not exist. This usually happens for broken symlinks.
233  if (ec == std::errc::no_such_file_or_directory) {
234  ec.clear();
235  continue;
236  }
237 
238  if (ec)
239  return createStringError(ec, Curr);
240 
241  if (sys::fs::is_symlink_file(Curr))
242  continue;
243 
244  StringRef FileName = sys::path::filename(Curr);
245  // Scan all "public" headers.
246  if (FileName.contains("Headers")) {
247  if (Error Err = scanHeaders(Curr, Framework, HeaderType::Public, Curr))
248  return Err;
249  continue;
250  }
251  // Scan all "private" headers.
252  if (FileName.contains("PrivateHeaders")) {
253  if (Error Err = scanHeaders(Curr, Framework, HeaderType::Private, Curr))
254  return Err;
255  continue;
256  }
257  // Scan sub frameworks.
258  if (FileName.contains("Frameworks")) {
259  if (Error Err = scanSubFrameworksDirectory(Curr, Framework.SubFrameworks))
260  return Err;
261  continue;
262  }
263  // Check for versioned frameworks.
264  if (FileName.contains("Versions")) {
265  if (Error Err = scanFrameworkVersionsDirectory(Curr, Framework))
266  return Err;
267  continue;
268  }
269  }
270 
271  return Error::success();
272 }
273 
274 llvm::Error DirectoryScanner::scanForFrameworks(StringRef Directory) {
275  RootPath = "";
276 
277  // Expect a certain directory structure and naming convention to find
278  // frameworks.
279  static const char *SubDirectories[] = {"System/Library/Frameworks/",
280  "System/Library/PrivateFrameworks/"};
281 
282  // Check if the directory is already a framework.
283  if (isFramework(Directory)) {
284  Library &Framework = getOrCreateLibrary(Directory, Libraries);
285  if (Error Err = scanFrameworkDirectory(Directory, Framework))
286  return Err;
287  return Error::success();
288  }
289 
290  // Check known sub-directory locations.
291  for (const auto *SubDir : SubDirectories) {
292  SmallString<PATH_MAX> Path(Directory);
293  sys::path::append(Path, SubDir);
294 
295  if (Error Err = scanMultipleFrameworks(Path, Libraries))
296  return Err;
297  }
298 
299  return Error::success();
300 }
301 } // namespace clang::installapi
IndirectLocalPath & Path
StringRef Filename
Definition: Format.cpp:3001
llvm::MachO::PathSeq PathSeq
Definition: MachO.h:48
std::error_code make_error_code(ParseError e)
Definition: Format.cpp:1257
The DirectoryScanner for collecting library files on the file system.
Definition: Context.h:20
@ ScanFrameworks
Scanning Framework directory.
std::vector< HeaderFile > HeaderSeq
Definition: HeaderFile.h:150
static bool isFramework(StringRef Path)
std::optional< std::string > createIncludeHeaderName(const StringRef FullPath)
Assemble expected way header will be included by clients.
Definition: HeaderFile.cpp:19
bool isHeaderFile(StringRef Path)
Determine if Path is a header file.
Definition: HeaderFile.cpp:39
Diagnostic wrappers for TextAPI types for error reporting.
Definition: Dominators.h:30