1feed66f3SCyndy Ishida //===- DirectoryScanner.cpp -----------------------------------------------===// 2feed66f3SCyndy Ishida // 3feed66f3SCyndy Ishida // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4feed66f3SCyndy Ishida // See https://llvm.org/LICENSE.txt for license information. 5feed66f3SCyndy Ishida // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6feed66f3SCyndy Ishida // 7feed66f3SCyndy Ishida //===----------------------------------------------------------------------===// 8feed66f3SCyndy Ishida 9feed66f3SCyndy Ishida #include "clang/InstallAPI/DirectoryScanner.h" 10feed66f3SCyndy Ishida #include "llvm/ADT/StringRef.h" 11feed66f3SCyndy Ishida #include "llvm/ADT/StringSwitch.h" 12feed66f3SCyndy Ishida #include "llvm/TextAPI/DylibReader.h" 13feed66f3SCyndy Ishida 14feed66f3SCyndy Ishida using namespace llvm; 15feed66f3SCyndy Ishida using namespace llvm::MachO; 16feed66f3SCyndy Ishida 17feed66f3SCyndy Ishida namespace clang::installapi { 18feed66f3SCyndy Ishida 19feed66f3SCyndy Ishida HeaderSeq DirectoryScanner::getHeaders(ArrayRef<Library> Libraries) { 20feed66f3SCyndy Ishida HeaderSeq Headers; 21feed66f3SCyndy Ishida for (const Library &Lib : Libraries) 22feed66f3SCyndy Ishida llvm::append_range(Headers, Lib.Headers); 23feed66f3SCyndy Ishida return Headers; 24feed66f3SCyndy Ishida } 25feed66f3SCyndy Ishida 26feed66f3SCyndy Ishida llvm::Error DirectoryScanner::scan(StringRef Directory) { 27feed66f3SCyndy Ishida if (Mode == ScanMode::ScanFrameworks) 28feed66f3SCyndy Ishida return scanForFrameworks(Directory); 29feed66f3SCyndy Ishida 30feed66f3SCyndy Ishida return scanForUnwrappedLibraries(Directory); 31feed66f3SCyndy Ishida } 32feed66f3SCyndy Ishida 33feed66f3SCyndy Ishida llvm::Error DirectoryScanner::scanForUnwrappedLibraries(StringRef Directory) { 34feed66f3SCyndy Ishida // Check some known sub-directory locations. 35feed66f3SCyndy Ishida auto GetDirectory = [&](const char *Sub) -> OptionalDirectoryEntryRef { 36feed66f3SCyndy Ishida SmallString<PATH_MAX> Path(Directory); 37feed66f3SCyndy Ishida sys::path::append(Path, Sub); 38feed66f3SCyndy Ishida return FM.getOptionalDirectoryRef(Path); 39feed66f3SCyndy Ishida }; 40feed66f3SCyndy Ishida 41feed66f3SCyndy Ishida auto DirPublic = GetDirectory("usr/include"); 42feed66f3SCyndy Ishida auto DirPrivate = GetDirectory("usr/local/include"); 43feed66f3SCyndy Ishida if (!DirPublic && !DirPrivate) { 44feed66f3SCyndy Ishida std::error_code ec = std::make_error_code(std::errc::not_a_directory); 45feed66f3SCyndy Ishida return createStringError(ec, 46feed66f3SCyndy Ishida "cannot find any public (usr/include) or private " 47feed66f3SCyndy Ishida "(usr/local/include) header directory"); 48feed66f3SCyndy Ishida } 49feed66f3SCyndy Ishida 50feed66f3SCyndy Ishida Library &Lib = getOrCreateLibrary(Directory, Libraries); 51feed66f3SCyndy Ishida Lib.IsUnwrappedDylib = true; 52feed66f3SCyndy Ishida 53feed66f3SCyndy Ishida if (DirPublic) 54feed66f3SCyndy Ishida if (Error Err = scanHeaders(DirPublic->getName(), Lib, HeaderType::Public, 55feed66f3SCyndy Ishida Directory)) 56feed66f3SCyndy Ishida return Err; 57feed66f3SCyndy Ishida 58feed66f3SCyndy Ishida if (DirPrivate) 59feed66f3SCyndy Ishida if (Error Err = scanHeaders(DirPrivate->getName(), Lib, HeaderType::Private, 60feed66f3SCyndy Ishida Directory)) 61feed66f3SCyndy Ishida return Err; 62feed66f3SCyndy Ishida 63feed66f3SCyndy Ishida return Error::success(); 64feed66f3SCyndy Ishida } 65feed66f3SCyndy Ishida 66feed66f3SCyndy Ishida static bool isFramework(StringRef Path) { 67feed66f3SCyndy Ishida while (Path.back() == '/') 68feed66f3SCyndy Ishida Path = Path.slice(0, Path.size() - 1); 69feed66f3SCyndy Ishida 70feed66f3SCyndy Ishida return llvm::StringSwitch<bool>(llvm::sys::path::extension(Path)) 71feed66f3SCyndy Ishida .Case(".framework", true) 72feed66f3SCyndy Ishida .Default(false); 73feed66f3SCyndy Ishida } 74feed66f3SCyndy Ishida 75feed66f3SCyndy Ishida Library & 76feed66f3SCyndy Ishida DirectoryScanner::getOrCreateLibrary(StringRef Path, 77feed66f3SCyndy Ishida std::vector<Library> &Libs) const { 78feed66f3SCyndy Ishida if (Path.consume_front(RootPath) && Path.empty()) 79feed66f3SCyndy Ishida Path = "/"; 80feed66f3SCyndy Ishida 81feed66f3SCyndy Ishida auto LibIt = 82feed66f3SCyndy Ishida find_if(Libs, [Path](const Library &L) { return L.getPath() == Path; }); 83feed66f3SCyndy Ishida if (LibIt != Libs.end()) 84feed66f3SCyndy Ishida return *LibIt; 85feed66f3SCyndy Ishida 86feed66f3SCyndy Ishida Libs.emplace_back(Path); 87feed66f3SCyndy Ishida return Libs.back(); 88feed66f3SCyndy Ishida } 89feed66f3SCyndy Ishida 90feed66f3SCyndy Ishida Error DirectoryScanner::scanHeaders(StringRef Path, Library &Lib, 91feed66f3SCyndy Ishida HeaderType Type, StringRef BasePath, 92feed66f3SCyndy Ishida StringRef ParentPath) const { 93feed66f3SCyndy Ishida std::error_code ec; 94feed66f3SCyndy Ishida auto &FS = FM.getVirtualFileSystem(); 95feed66f3SCyndy Ishida PathSeq SubDirectories; 96feed66f3SCyndy Ishida for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie; 97feed66f3SCyndy Ishida i.increment(ec)) { 98feed66f3SCyndy Ishida StringRef HeaderPath = i->path(); 99feed66f3SCyndy Ishida if (ec) 100feed66f3SCyndy Ishida return createStringError(ec, "unable to read: " + HeaderPath); 101feed66f3SCyndy Ishida 102feed66f3SCyndy Ishida if (sys::fs::is_symlink_file(HeaderPath)) 103feed66f3SCyndy Ishida continue; 104feed66f3SCyndy Ishida 105feed66f3SCyndy Ishida // Ignore tmp files from unifdef. 106feed66f3SCyndy Ishida const StringRef Filename = sys::path::filename(HeaderPath); 107feed66f3SCyndy Ishida if (Filename.starts_with(".")) 108feed66f3SCyndy Ishida continue; 109feed66f3SCyndy Ishida 110feed66f3SCyndy Ishida // If it is a directory, remember the subdirectory. 111feed66f3SCyndy Ishida if (FM.getOptionalDirectoryRef(HeaderPath)) 112feed66f3SCyndy Ishida SubDirectories.push_back(HeaderPath.str()); 113feed66f3SCyndy Ishida 114feed66f3SCyndy Ishida if (!isHeaderFile(HeaderPath)) 115feed66f3SCyndy Ishida continue; 116feed66f3SCyndy Ishida 117feed66f3SCyndy Ishida // Skip files that do not exist. This usually happens for broken symlinks. 118feed66f3SCyndy Ishida if (FS.status(HeaderPath) == std::errc::no_such_file_or_directory) 119feed66f3SCyndy Ishida continue; 120feed66f3SCyndy Ishida 121feed66f3SCyndy Ishida auto IncludeName = createIncludeHeaderName(HeaderPath); 122feed66f3SCyndy Ishida Lib.addHeaderFile(HeaderPath, Type, 123feed66f3SCyndy Ishida IncludeName.has_value() ? IncludeName.value() : ""); 124feed66f3SCyndy Ishida } 125feed66f3SCyndy Ishida 126feed66f3SCyndy Ishida // Go through the subdirectories. 127feed66f3SCyndy Ishida // Sort the sub-directory first since different file systems might have 128feed66f3SCyndy Ishida // different traverse order. 129feed66f3SCyndy Ishida llvm::sort(SubDirectories); 130feed66f3SCyndy Ishida if (ParentPath.empty()) 131feed66f3SCyndy Ishida ParentPath = Path; 132feed66f3SCyndy Ishida for (const StringRef Dir : SubDirectories) 1334ed543d4SCyndy Ishida if (Error Err = scanHeaders(Dir, Lib, Type, BasePath, ParentPath)) 1344ed543d4SCyndy Ishida return Err; 135feed66f3SCyndy Ishida 136feed66f3SCyndy Ishida return Error::success(); 137feed66f3SCyndy Ishida } 138feed66f3SCyndy Ishida 139feed66f3SCyndy Ishida llvm::Error 140feed66f3SCyndy Ishida DirectoryScanner::scanMultipleFrameworks(StringRef Directory, 141feed66f3SCyndy Ishida std::vector<Library> &Libs) const { 142feed66f3SCyndy Ishida std::error_code ec; 143feed66f3SCyndy Ishida auto &FS = FM.getVirtualFileSystem(); 144feed66f3SCyndy Ishida for (vfs::directory_iterator i = FS.dir_begin(Directory, ec), ie; i != ie; 145feed66f3SCyndy Ishida i.increment(ec)) { 146feed66f3SCyndy Ishida StringRef Curr = i->path(); 147feed66f3SCyndy Ishida 148feed66f3SCyndy Ishida // Skip files that do not exist. This usually happens for broken symlinks. 149feed66f3SCyndy Ishida if (ec == std::errc::no_such_file_or_directory) { 150feed66f3SCyndy Ishida ec.clear(); 151feed66f3SCyndy Ishida continue; 152feed66f3SCyndy Ishida } 153feed66f3SCyndy Ishida if (ec) 154feed66f3SCyndy Ishida return createStringError(ec, Curr); 155feed66f3SCyndy Ishida 156feed66f3SCyndy Ishida if (sys::fs::is_symlink_file(Curr)) 157feed66f3SCyndy Ishida continue; 158feed66f3SCyndy Ishida 159feed66f3SCyndy Ishida if (isFramework(Curr)) { 160feed66f3SCyndy Ishida if (!FM.getOptionalDirectoryRef(Curr)) 161feed66f3SCyndy Ishida continue; 162feed66f3SCyndy Ishida Library &Framework = getOrCreateLibrary(Curr, Libs); 163feed66f3SCyndy Ishida if (Error Err = scanFrameworkDirectory(Curr, Framework)) 164feed66f3SCyndy Ishida return Err; 165feed66f3SCyndy Ishida } 166feed66f3SCyndy Ishida } 167feed66f3SCyndy Ishida 168feed66f3SCyndy Ishida return Error::success(); 169feed66f3SCyndy Ishida } 170feed66f3SCyndy Ishida 171feed66f3SCyndy Ishida llvm::Error 172feed66f3SCyndy Ishida DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory, 173feed66f3SCyndy Ishida std::vector<Library> &Libs) const { 174feed66f3SCyndy Ishida if (FM.getOptionalDirectoryRef(Directory)) 175feed66f3SCyndy Ishida return scanMultipleFrameworks(Directory, Libs); 176feed66f3SCyndy Ishida 177feed66f3SCyndy Ishida std::error_code ec = std::make_error_code(std::errc::not_a_directory); 178feed66f3SCyndy Ishida return createStringError(ec, Directory); 179feed66f3SCyndy Ishida } 180feed66f3SCyndy Ishida 181feed66f3SCyndy Ishida /// FIXME: How to handle versions? For now scan them separately as independent 182feed66f3SCyndy Ishida /// frameworks. 183feed66f3SCyndy Ishida llvm::Error 184feed66f3SCyndy Ishida DirectoryScanner::scanFrameworkVersionsDirectory(StringRef Path, 185feed66f3SCyndy Ishida Library &Lib) const { 186feed66f3SCyndy Ishida std::error_code ec; 187feed66f3SCyndy Ishida auto &FS = FM.getVirtualFileSystem(); 188feed66f3SCyndy Ishida for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie; 189feed66f3SCyndy Ishida i.increment(ec)) { 190feed66f3SCyndy Ishida const StringRef Curr = i->path(); 191feed66f3SCyndy Ishida 192feed66f3SCyndy Ishida // Skip files that do not exist. This usually happens for broken symlinks. 193feed66f3SCyndy Ishida if (ec == std::errc::no_such_file_or_directory) { 194feed66f3SCyndy Ishida ec.clear(); 195feed66f3SCyndy Ishida continue; 196feed66f3SCyndy Ishida } 197feed66f3SCyndy Ishida if (ec) 198feed66f3SCyndy Ishida return createStringError(ec, Curr); 199feed66f3SCyndy Ishida 200feed66f3SCyndy Ishida if (sys::fs::is_symlink_file(Curr)) 201feed66f3SCyndy Ishida continue; 202feed66f3SCyndy Ishida 203feed66f3SCyndy Ishida // Each version should be a framework directory. 204feed66f3SCyndy Ishida if (!FM.getOptionalDirectoryRef(Curr)) 205feed66f3SCyndy Ishida continue; 206feed66f3SCyndy Ishida 207feed66f3SCyndy Ishida Library &VersionedFramework = 208feed66f3SCyndy Ishida getOrCreateLibrary(Curr, Lib.FrameworkVersions); 209feed66f3SCyndy Ishida if (Error Err = scanFrameworkDirectory(Curr, VersionedFramework)) 210feed66f3SCyndy Ishida return Err; 211feed66f3SCyndy Ishida } 212feed66f3SCyndy Ishida 213feed66f3SCyndy Ishida return Error::success(); 214feed66f3SCyndy Ishida } 215feed66f3SCyndy Ishida 216feed66f3SCyndy Ishida llvm::Error DirectoryScanner::scanFrameworkDirectory(StringRef Path, 217feed66f3SCyndy Ishida Library &Framework) const { 218feed66f3SCyndy Ishida // If the framework is inside Kernel or IOKit, scan headers in the different 219feed66f3SCyndy Ishida // directories separately. 220feed66f3SCyndy Ishida Framework.IsUnwrappedDylib = 221feed66f3SCyndy Ishida Path.contains("Kernel.framework") || Path.contains("IOKit.framework"); 222feed66f3SCyndy Ishida 223feed66f3SCyndy Ishida // Unfortunately we cannot identify symlinks in the VFS. We assume that if 224feed66f3SCyndy Ishida // there is a Versions directory, then we have symlinks and directly proceed 225feed66f3SCyndy Ishida // to the Versions folder. 226feed66f3SCyndy Ishida std::error_code ec; 227feed66f3SCyndy Ishida auto &FS = FM.getVirtualFileSystem(); 228feed66f3SCyndy Ishida 229feed66f3SCyndy Ishida for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie; 230feed66f3SCyndy Ishida i.increment(ec)) { 231feed66f3SCyndy Ishida StringRef Curr = i->path(); 232feed66f3SCyndy Ishida // Skip files that do not exist. This usually happens for broken symlinks. 233feed66f3SCyndy Ishida if (ec == std::errc::no_such_file_or_directory) { 234feed66f3SCyndy Ishida ec.clear(); 235feed66f3SCyndy Ishida continue; 236feed66f3SCyndy Ishida } 237feed66f3SCyndy Ishida 238feed66f3SCyndy Ishida if (ec) 239feed66f3SCyndy Ishida return createStringError(ec, Curr); 240feed66f3SCyndy Ishida 241feed66f3SCyndy Ishida if (sys::fs::is_symlink_file(Curr)) 242feed66f3SCyndy Ishida continue; 243feed66f3SCyndy Ishida 244feed66f3SCyndy Ishida StringRef FileName = sys::path::filename(Curr); 245feed66f3SCyndy Ishida // Scan all "public" headers. 246feed66f3SCyndy Ishida if (FileName.contains("Headers")) { 247feed66f3SCyndy Ishida if (Error Err = scanHeaders(Curr, Framework, HeaderType::Public, Curr)) 248feed66f3SCyndy Ishida return Err; 249feed66f3SCyndy Ishida continue; 250feed66f3SCyndy Ishida } 251feed66f3SCyndy Ishida // Scan all "private" headers. 252feed66f3SCyndy Ishida if (FileName.contains("PrivateHeaders")) { 253feed66f3SCyndy Ishida if (Error Err = scanHeaders(Curr, Framework, HeaderType::Private, Curr)) 254feed66f3SCyndy Ishida return Err; 255feed66f3SCyndy Ishida continue; 256feed66f3SCyndy Ishida } 257feed66f3SCyndy Ishida // Scan sub frameworks. 258feed66f3SCyndy Ishida if (FileName.contains("Frameworks")) { 259feed66f3SCyndy Ishida if (Error Err = scanSubFrameworksDirectory(Curr, Framework.SubFrameworks)) 260feed66f3SCyndy Ishida return Err; 261feed66f3SCyndy Ishida continue; 262feed66f3SCyndy Ishida } 263feed66f3SCyndy Ishida // Check for versioned frameworks. 264feed66f3SCyndy Ishida if (FileName.contains("Versions")) { 265feed66f3SCyndy Ishida if (Error Err = scanFrameworkVersionsDirectory(Curr, Framework)) 266feed66f3SCyndy Ishida return Err; 267feed66f3SCyndy Ishida continue; 268feed66f3SCyndy Ishida } 269feed66f3SCyndy Ishida } 270feed66f3SCyndy Ishida 271feed66f3SCyndy Ishida return Error::success(); 272feed66f3SCyndy Ishida } 273feed66f3SCyndy Ishida 274feed66f3SCyndy Ishida llvm::Error DirectoryScanner::scanForFrameworks(StringRef Directory) { 275feed66f3SCyndy Ishida RootPath = ""; 276feed66f3SCyndy Ishida 277feed66f3SCyndy Ishida // Expect a certain directory structure and naming convention to find 278feed66f3SCyndy Ishida // frameworks. 279feed66f3SCyndy Ishida static const char *SubDirectories[] = {"System/Library/Frameworks/", 280*2d48489cSCyndy Ishida "System/Library/PrivateFrameworks/", 281*2d48489cSCyndy Ishida "System/Library/SubFrameworks"}; 282feed66f3SCyndy Ishida 283feed66f3SCyndy Ishida // Check if the directory is already a framework. 284feed66f3SCyndy Ishida if (isFramework(Directory)) { 285feed66f3SCyndy Ishida Library &Framework = getOrCreateLibrary(Directory, Libraries); 286feed66f3SCyndy Ishida if (Error Err = scanFrameworkDirectory(Directory, Framework)) 287feed66f3SCyndy Ishida return Err; 288feed66f3SCyndy Ishida return Error::success(); 289feed66f3SCyndy Ishida } 290feed66f3SCyndy Ishida 291feed66f3SCyndy Ishida // Check known sub-directory locations. 292feed66f3SCyndy Ishida for (const auto *SubDir : SubDirectories) { 293feed66f3SCyndy Ishida SmallString<PATH_MAX> Path(Directory); 294feed66f3SCyndy Ishida sys::path::append(Path, SubDir); 295feed66f3SCyndy Ishida 296feed66f3SCyndy Ishida if (Error Err = scanMultipleFrameworks(Path, Libraries)) 297feed66f3SCyndy Ishida return Err; 298feed66f3SCyndy Ishida } 299feed66f3SCyndy Ishida 300feed66f3SCyndy Ishida return Error::success(); 301feed66f3SCyndy Ishida } 302feed66f3SCyndy Ishida } // namespace clang::installapi 303