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 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 "System/Library/SubFrameworks"}; 282 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)) 287 return Err; 288 return Error::success(); 289 } 290 291 // Check known sub-directory locations. 292 for (const auto *SubDir : SubDirectories) { 293 SmallString<PATH_MAX> Path(Directory); 294 sys::path::append(Path, SubDir); 295 296 if (Error Err = scanMultipleFrameworks(Path, Libraries)) 297 return Err; 298 } 299 300 return Error::success(); 301 } 302 } // namespace clang::installapi 303