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 13 using namespace llvm; 14 using namespace llvm::MachO; 15 16 namespace clang::installapi { 17 18 HeaderSeq DirectoryScanner::getHeaders(ArrayRef<Library> Libraries) { 19 HeaderSeq Headers; 20 for (const Library &Lib : Libraries) 21 llvm::append_range(Headers, Lib.Headers); 22 return Headers; 23 } 24 25 llvm::Error DirectoryScanner::scan(StringRef Directory) { 26 if (Mode == ScanMode::ScanFrameworks) 27 return scanForFrameworks(Directory); 28 29 return scanForUnwrappedLibraries(Directory); 30 } 31 32 llvm::Error DirectoryScanner::scanForUnwrappedLibraries(StringRef Directory) { 33 // Check some known sub-directory locations. 34 auto GetDirectory = [&](const char *Sub) -> OptionalDirectoryEntryRef { 35 SmallString<PATH_MAX> Path(Directory); 36 sys::path::append(Path, Sub); 37 return FM.getOptionalDirectoryRef(Path); 38 }; 39 40 auto DirPublic = GetDirectory("usr/include"); 41 auto DirPrivate = GetDirectory("usr/local/include"); 42 if (!DirPublic && !DirPrivate) { 43 std::error_code ec = std::make_error_code(std::errc::not_a_directory); 44 return createStringError(ec, 45 "cannot find any public (usr/include) or private " 46 "(usr/local/include) header directory"); 47 } 48 49 Library &Lib = getOrCreateLibrary(Directory, Libraries); 50 Lib.IsUnwrappedDylib = true; 51 52 if (DirPublic) 53 if (Error Err = scanHeaders(DirPublic->getName(), Lib, HeaderType::Public, 54 Directory)) 55 return Err; 56 57 if (DirPrivate) 58 if (Error Err = scanHeaders(DirPrivate->getName(), Lib, HeaderType::Private, 59 Directory)) 60 return Err; 61 62 return Error::success(); 63 } 64 65 static bool isFramework(StringRef Path) { 66 while (Path.back() == '/') 67 Path = Path.slice(0, Path.size() - 1); 68 69 return llvm::StringSwitch<bool>(llvm::sys::path::extension(Path)) 70 .Case(".framework", true) 71 .Default(false); 72 } 73 74 Library & 75 DirectoryScanner::getOrCreateLibrary(StringRef Path, 76 std::vector<Library> &Libs) const { 77 if (Path.consume_front(RootPath) && Path.empty()) 78 Path = "/"; 79 80 auto LibIt = 81 find_if(Libs, [Path](const Library &L) { return L.getPath() == Path; }); 82 if (LibIt != Libs.end()) 83 return *LibIt; 84 85 Libs.emplace_back(Path); 86 return Libs.back(); 87 } 88 89 Error DirectoryScanner::scanHeaders(StringRef Path, Library &Lib, 90 HeaderType Type, StringRef BasePath, 91 StringRef ParentPath) const { 92 std::error_code ec; 93 auto &FS = FM.getVirtualFileSystem(); 94 PathSeq SubDirectories; 95 for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie; 96 i.increment(ec)) { 97 StringRef HeaderPath = i->path(); 98 if (ec) 99 return createStringError(ec, "unable to read: " + HeaderPath); 100 101 if (sys::fs::is_symlink_file(HeaderPath)) 102 continue; 103 104 // Ignore tmp files from unifdef. 105 const StringRef Filename = sys::path::filename(HeaderPath); 106 if (Filename.starts_with(".")) 107 continue; 108 109 // If it is a directory, remember the subdirectory. 110 if (FM.getOptionalDirectoryRef(HeaderPath)) 111 SubDirectories.push_back(HeaderPath.str()); 112 113 if (!isHeaderFile(HeaderPath)) 114 continue; 115 116 // Skip files that do not exist. This usually happens for broken symlinks. 117 if (FS.status(HeaderPath) == std::errc::no_such_file_or_directory) 118 continue; 119 120 auto IncludeName = createIncludeHeaderName(HeaderPath); 121 Lib.addHeaderFile(HeaderPath, Type, 122 IncludeName.has_value() ? IncludeName.value() : ""); 123 } 124 125 // Go through the subdirectories. 126 // Sort the sub-directory first since different file systems might have 127 // different traverse order. 128 llvm::sort(SubDirectories); 129 if (ParentPath.empty()) 130 ParentPath = Path; 131 for (const StringRef Dir : SubDirectories) 132 if (Error Err = scanHeaders(Dir, Lib, Type, BasePath, ParentPath)) 133 return Err; 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 "System/Library/SubFrameworks"}; 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 302