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