302 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/InstallAPI/DirectoryScanner.h"10#include "llvm/ADT/StringRef.h"11#include "llvm/ADT/StringSwitch.h"12 13using namespace llvm;14using namespace llvm::MachO;15 16namespace clang::installapi {17 18HeaderSeq 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 25llvm::Error DirectoryScanner::scan(StringRef Directory) {26 if (Mode == ScanMode::ScanFrameworks)27 return scanForFrameworks(Directory);28 29 return scanForUnwrappedLibraries(Directory);30}31 32llvm::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 65static 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 74Library &75DirectoryScanner::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 89Error 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 have127 // 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 138llvm::Error139DirectoryScanner::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 170llvm::Error171DirectoryScanner::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 independent181/// frameworks.182llvm::Error183DirectoryScanner::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 215llvm::Error DirectoryScanner::scanFrameworkDirectory(StringRef Path,216 Library &Framework) const {217 // If the framework is inside Kernel or IOKit, scan headers in the different218 // 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 if223 // there is a Versions directory, then we have symlinks and directly proceed224 // 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 273llvm::Error DirectoryScanner::scanForFrameworks(StringRef Directory) {274 RootPath = "";275 276 // Expect a certain directory structure and naming convention to find277 // 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::installapi302