1 //===- DirectoryScanner.cpp -----------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang/InstallAPI/DirectoryScanner.h"
10 #include "llvm/ADT/StringRef.h"
11 #include "llvm/ADT/StringSwitch.h"
12 #include "llvm/TextAPI/DylibReader.h"
15 using namespace llvm::MachO
;
17 namespace clang::installapi
{
19 HeaderSeq
DirectoryScanner::getHeaders(ArrayRef
<Library
> Libraries
) {
21 for (const Library
&Lib
: Libraries
)
22 llvm::append_range(Headers
, Lib
.Headers
);
26 llvm::Error
DirectoryScanner::scan(StringRef Directory
) {
27 if (Mode
== ScanMode::ScanFrameworks
)
28 return scanForFrameworks(Directory
);
30 return scanForUnwrappedLibraries(Directory
);
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
);
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");
50 Library
&Lib
= getOrCreateLibrary(Directory
, Libraries
);
51 Lib
.IsUnwrappedDylib
= true;
54 if (Error Err
= scanHeaders(DirPublic
->getName(), Lib
, HeaderType::Public
,
59 if (Error Err
= scanHeaders(DirPrivate
->getName(), Lib
, HeaderType::Private
,
63 return Error::success();
66 static bool isFramework(StringRef Path
) {
67 while (Path
.back() == '/')
68 Path
= Path
.slice(0, Path
.size() - 1);
70 return llvm::StringSwitch
<bool>(llvm::sys::path::extension(Path
))
71 .Case(".framework", true)
76 DirectoryScanner::getOrCreateLibrary(StringRef Path
,
77 std::vector
<Library
> &Libs
) const {
78 if (Path
.consume_front(RootPath
) && Path
.empty())
82 find_if(Libs
, [Path
](const Library
&L
) { return L
.getPath() == Path
; });
83 if (LibIt
!= Libs
.end())
86 Libs
.emplace_back(Path
);
90 Error
DirectoryScanner::scanHeaders(StringRef Path
, Library
&Lib
,
91 HeaderType Type
, StringRef BasePath
,
92 StringRef ParentPath
) const {
94 auto &FS
= FM
.getVirtualFileSystem();
95 PathSeq SubDirectories
;
96 for (vfs::directory_iterator i
= FS
.dir_begin(Path
, ec
), ie
; i
!= ie
;
98 StringRef HeaderPath
= i
->path();
100 return createStringError(ec
, "unable to read: " + HeaderPath
);
102 if (sys::fs::is_symlink_file(HeaderPath
))
105 // Ignore tmp files from unifdef.
106 const StringRef Filename
= sys::path::filename(HeaderPath
);
107 if (Filename
.starts_with("."))
110 // If it is a directory, remember the subdirectory.
111 if (FM
.getOptionalDirectoryRef(HeaderPath
))
112 SubDirectories
.push_back(HeaderPath
.str());
114 if (!isHeaderFile(HeaderPath
))
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
)
121 auto IncludeName
= createIncludeHeaderName(HeaderPath
);
122 Lib
.addHeaderFile(HeaderPath
, Type
,
123 IncludeName
.has_value() ? IncludeName
.value() : "");
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())
132 for (const StringRef Dir
: SubDirectories
)
133 if (Error Err
= scanHeaders(Dir
, Lib
, Type
, BasePath
, ParentPath
))
136 return Error::success();
140 DirectoryScanner::scanMultipleFrameworks(StringRef Directory
,
141 std::vector
<Library
> &Libs
) const {
143 auto &FS
= FM
.getVirtualFileSystem();
144 for (vfs::directory_iterator i
= FS
.dir_begin(Directory
, ec
), ie
; i
!= ie
;
146 StringRef Curr
= i
->path();
148 // Skip files that do not exist. This usually happens for broken symlinks.
149 if (ec
== std::errc::no_such_file_or_directory
) {
154 return createStringError(ec
, Curr
);
156 if (sys::fs::is_symlink_file(Curr
))
159 if (isFramework(Curr
)) {
160 if (!FM
.getOptionalDirectoryRef(Curr
))
162 Library
&Framework
= getOrCreateLibrary(Curr
, Libs
);
163 if (Error Err
= scanFrameworkDirectory(Curr
, Framework
))
168 return Error::success();
172 DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory
,
173 std::vector
<Library
> &Libs
) const {
174 if (FM
.getOptionalDirectoryRef(Directory
))
175 return scanMultipleFrameworks(Directory
, Libs
);
177 std::error_code ec
= std::make_error_code(std::errc::not_a_directory
);
178 return createStringError(ec
, Directory
);
181 /// FIXME: How to handle versions? For now scan them separately as independent
184 DirectoryScanner::scanFrameworkVersionsDirectory(StringRef Path
,
185 Library
&Lib
) const {
187 auto &FS
= FM
.getVirtualFileSystem();
188 for (vfs::directory_iterator i
= FS
.dir_begin(Path
, ec
), ie
; i
!= ie
;
190 const StringRef Curr
= i
->path();
192 // Skip files that do not exist. This usually happens for broken symlinks.
193 if (ec
== std::errc::no_such_file_or_directory
) {
198 return createStringError(ec
, Curr
);
200 if (sys::fs::is_symlink_file(Curr
))
203 // Each version should be a framework directory.
204 if (!FM
.getOptionalDirectoryRef(Curr
))
207 Library
&VersionedFramework
=
208 getOrCreateLibrary(Curr
, Lib
.FrameworkVersions
);
209 if (Error Err
= scanFrameworkDirectory(Curr
, VersionedFramework
))
213 return Error::success();
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");
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.
227 auto &FS
= FM
.getVirtualFileSystem();
229 for (vfs::directory_iterator i
= FS
.dir_begin(Path
, ec
), ie
; i
!= ie
;
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
) {
239 return createStringError(ec
, Curr
);
241 if (sys::fs::is_symlink_file(Curr
))
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
))
251 // Scan all "private" headers.
252 if (FileName
.contains("PrivateHeaders")) {
253 if (Error Err
= scanHeaders(Curr
, Framework
, HeaderType::Private
, Curr
))
257 // Scan sub frameworks.
258 if (FileName
.contains("Frameworks")) {
259 if (Error Err
= scanSubFrameworksDirectory(Curr
, Framework
.SubFrameworks
))
263 // Check for versioned frameworks.
264 if (FileName
.contains("Versions")) {
265 if (Error Err
= scanFrameworkVersionsDirectory(Curr
, Framework
))
271 return Error::success();
274 llvm::Error
DirectoryScanner::scanForFrameworks(StringRef Directory
) {
277 // Expect a certain directory structure and naming convention to find
279 static const char *SubDirectories
[] = {"System/Library/Frameworks/",
280 "System/Library/PrivateFrameworks/",
281 "System/Library/SubFrameworks"};
283 // Check if the directory is already a framework.
284 if (isFramework(Directory
)) {
285 Library
&Framework
= getOrCreateLibrary(Directory
, Libraries
);
286 if (Error Err
= scanFrameworkDirectory(Directory
, Framework
))
288 return Error::success();
291 // Check known sub-directory locations.
292 for (const auto *SubDir
: SubDirectories
) {
293 SmallString
<PATH_MAX
> Path(Directory
);
294 sys::path::append(Path
, SubDir
);
296 if (Error Err
= scanMultipleFrameworks(Path
, Libraries
))
300 return Error::success();
302 } // namespace clang::installapi