1142e567cSCyndy Ishida //===-- llvm-readtapi.cpp - tapi file reader and transformer -----*- C++-*-===// 25656d797SCyndy Ishida // 35656d797SCyndy Ishida // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 45656d797SCyndy Ishida // See https://llvm.org/LICENSE.txt for license information. 55656d797SCyndy Ishida // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 65656d797SCyndy Ishida // 75656d797SCyndy Ishida //===----------------------------------------------------------------------===// 85656d797SCyndy Ishida // 95656d797SCyndy Ishida // This file defines the command-line driver for llvm-readtapi. 105656d797SCyndy Ishida // 115656d797SCyndy Ishida //===----------------------------------------------------------------------===// 125656d797SCyndy Ishida #include "DiffEngine.h" 13c6f29dbbSCyndy Ishida #include "llvm/BinaryFormat/Magic.h" 14ec64af59SCyndy Ishida #include "llvm/Option/Arg.h" 15ec64af59SCyndy Ishida #include "llvm/Option/ArgList.h" 16ec64af59SCyndy Ishida #include "llvm/Option/Option.h" 175656d797SCyndy Ishida #include "llvm/Support/CommandLine.h" 185656d797SCyndy Ishida #include "llvm/Support/Error.h" 19c6f29dbbSCyndy Ishida #include "llvm/Support/FileSystem.h" 205656d797SCyndy Ishida #include "llvm/Support/InitLLVM.h" 215656d797SCyndy Ishida #include "llvm/Support/MemoryBuffer.h" 22c6f29dbbSCyndy Ishida #include "llvm/Support/Path.h" 235656d797SCyndy Ishida #include "llvm/Support/raw_ostream.h" 24c6f29dbbSCyndy Ishida #include "llvm/TextAPI/DylibReader.h" 25ec64af59SCyndy Ishida #include "llvm/TextAPI/TextAPIError.h" 26ae182dbbSCyndy Ishida #include "llvm/TextAPI/TextAPIReader.h" 27ae182dbbSCyndy Ishida #include "llvm/TextAPI/TextAPIWriter.h" 28c6f29dbbSCyndy Ishida #include "llvm/TextAPI/Utils.h" 295656d797SCyndy Ishida #include <cstdlib> 305656d797SCyndy Ishida 317189219eSCyndy Ishida #if !defined(_MSC_VER) && !defined(__MINGW32__) 327189219eSCyndy Ishida #include <unistd.h> 337189219eSCyndy Ishida #endif 347189219eSCyndy Ishida 355656d797SCyndy Ishida using namespace llvm; 365656d797SCyndy Ishida using namespace MachO; 375656d797SCyndy Ishida using namespace object; 385656d797SCyndy Ishida 395656d797SCyndy Ishida namespace { 40ec64af59SCyndy Ishida using namespace llvm::opt; 41ec64af59SCyndy Ishida enum ID { 42ec64af59SCyndy Ishida OPT_INVALID = 0, // This is not an option ID. 43ec64af59SCyndy Ishida #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), 44ec64af59SCyndy Ishida #include "TapiOpts.inc" 45ec64af59SCyndy Ishida #undef OPTION 465656d797SCyndy Ishida }; 475656d797SCyndy Ishida 48*dd647e3eSChandler Carruth #define OPTTABLE_STR_TABLE_CODE 49ec64af59SCyndy Ishida #include "TapiOpts.inc" 50*dd647e3eSChandler Carruth #undef OPTTABLE_STR_TABLE_CODE 51*dd647e3eSChandler Carruth 52*dd647e3eSChandler Carruth #define OPTTABLE_PREFIXES_TABLE_CODE 53*dd647e3eSChandler Carruth #include "TapiOpts.inc" 54*dd647e3eSChandler Carruth #undef OPTTABLE_PREFIXES_TABLE_CODE 555656d797SCyndy Ishida 56ec64af59SCyndy Ishida static constexpr opt::OptTable::Info InfoTable[] = { 57ec64af59SCyndy Ishida #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), 58ec64af59SCyndy Ishida #include "TapiOpts.inc" 59ec64af59SCyndy Ishida #undef OPTION 60ec64af59SCyndy Ishida }; 61ec64af59SCyndy Ishida 62ec64af59SCyndy Ishida class TAPIOptTable : public opt::GenericOptTable { 63ec64af59SCyndy Ishida public: 64*dd647e3eSChandler Carruth TAPIOptTable() 65*dd647e3eSChandler Carruth : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) { 66ec64af59SCyndy Ishida setGroupedShortOptions(true); 67ec64af59SCyndy Ishida } 68ec64af59SCyndy Ishida }; 69ec64af59SCyndy Ishida 70c6f29dbbSCyndy Ishida struct StubOptions { 71c6f29dbbSCyndy Ishida bool DeleteInput = false; 727189219eSCyndy Ishida bool DeletePrivate = false; 737189219eSCyndy Ishida bool TraceLibs = false; 74c6f29dbbSCyndy Ishida }; 75c6f29dbbSCyndy Ishida 7651340480SCyndy Ishida struct CompareOptions { 7751340480SCyndy Ishida ArchitectureSet ArchsToIgnore; 7851340480SCyndy Ishida }; 7951340480SCyndy Ishida 80ec64af59SCyndy Ishida struct Context { 81ec64af59SCyndy Ishida std::vector<std::string> Inputs; 827189219eSCyndy Ishida StubOptions StubOpt; 8351340480SCyndy Ishida CompareOptions CmpOpt; 84ec64af59SCyndy Ishida std::unique_ptr<llvm::raw_fd_stream> OutStream; 85ae182dbbSCyndy Ishida FileType WriteFT = FileType::TBD_V5; 86ae182dbbSCyndy Ishida bool Compact = false; 87e1f69b86SCyndy Ishida Architecture Arch = AK_unknown; 88ec64af59SCyndy Ishida }; 89ec64af59SCyndy Ishida 9086fa4b2cSCyndy Ishida // Use unique exit code to differentiate failures not directly caused from 9186fa4b2cSCyndy Ishida // TextAPI operations. This is used for wrapping `compare` operations in 9286fa4b2cSCyndy Ishida // automation and scripting. 9386fa4b2cSCyndy Ishida const int NON_TAPI_EXIT_CODE = 2; 9486fa4b2cSCyndy Ishida const std::string TOOLNAME = "llvm-readtapi"; 9586fa4b2cSCyndy Ishida ExitOnError ExitOnErr; 9686fa4b2cSCyndy Ishida } // anonymous namespace 9786fa4b2cSCyndy Ishida 9886fa4b2cSCyndy Ishida // Handle error reporting in cases where `ExitOnError` is not used. 9986fa4b2cSCyndy Ishida static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { 10086fa4b2cSCyndy Ishida errs() << TOOLNAME << ": error: " << Message << "\n"; 10186fa4b2cSCyndy Ishida errs().flush(); 10286fa4b2cSCyndy Ishida exit(ExitCode); 10386fa4b2cSCyndy Ishida } 10486fa4b2cSCyndy Ishida 1057189219eSCyndy Ishida // Handle warnings. 1067189219eSCyndy Ishida static void reportWarning(Twine Message) { 1077189219eSCyndy Ishida errs() << TOOLNAME << ": warning: " << Message << "\n"; 1087189219eSCyndy Ishida } 1097189219eSCyndy Ishida 1107189219eSCyndy Ishida /// Get what the symlink points to. 1117189219eSCyndy Ishida /// This is a no-op on windows as it references POSIX level apis. 1125bcd9105SCyndy Ishida static void read_link(const Twine &Path, SmallVectorImpl<char> &Output) { 1137189219eSCyndy Ishida #if !defined(_MSC_VER) && !defined(__MINGW32__) 1147189219eSCyndy Ishida Output.clear(); 1157189219eSCyndy Ishida if (Path.isTriviallyEmpty()) 1165bcd9105SCyndy Ishida return; 1177189219eSCyndy Ishida 1187189219eSCyndy Ishida SmallString<PATH_MAX> Storage; 1197189219eSCyndy Ishida auto P = Path.toNullTerminatedStringRef(Storage); 1207189219eSCyndy Ishida SmallString<PATH_MAX> Result; 1217189219eSCyndy Ishida ssize_t Len; 1227189219eSCyndy Ishida if ((Len = ::readlink(P.data(), Result.data(), PATH_MAX)) == -1) 1235bcd9105SCyndy Ishida reportError("unable to read symlink: " + Path); 1247189219eSCyndy Ishida Result.resize_for_overwrite(Len); 1257189219eSCyndy Ishida Output.swap(Result); 1267189219eSCyndy Ishida #else 1277189219eSCyndy Ishida reportError("unable to read symlink on windows: " + Path); 1287189219eSCyndy Ishida #endif 1297189219eSCyndy Ishida } 1307189219eSCyndy Ishida 13186fa4b2cSCyndy Ishida static std::unique_ptr<InterfaceFile> 13286fa4b2cSCyndy Ishida getInterfaceFile(const StringRef Filename, bool ResetBanner = true) { 133bc191ac3SCyndy Ishida ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' "); 1345656d797SCyndy Ishida ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 13574dcf0b5SAbhina Sree MemoryBuffer::getFile(Filename, /*IsText=*/true); 1365656d797SCyndy Ishida if (BufferOrErr.getError()) 137ae182dbbSCyndy Ishida ExitOnErr(errorCodeToError(BufferOrErr.getError())); 138c6f29dbbSCyndy Ishida auto Buffer = std::move(*BufferOrErr); 139c6f29dbbSCyndy Ishida 140c6f29dbbSCyndy Ishida std::unique_ptr<InterfaceFile> IF; 141c6f29dbbSCyndy Ishida switch (identify_magic(Buffer->getBuffer())) { 142c6f29dbbSCyndy Ishida case file_magic::macho_dynamically_linked_shared_lib: 143c6f29dbbSCyndy Ishida case file_magic::macho_dynamically_linked_shared_lib_stub: 144b64992a3SCyndy Ishida case file_magic::macho_universal_binary: 145b64992a3SCyndy Ishida IF = ExitOnErr(DylibReader::get(Buffer->getMemBufferRef())); 146c6f29dbbSCyndy Ishida break; 147b64992a3SCyndy Ishida case file_magic::tapi_file: 148b64992a3SCyndy Ishida IF = ExitOnErr(TextAPIReader::get(Buffer->getMemBufferRef())); 149c6f29dbbSCyndy Ishida break; 150c6f29dbbSCyndy Ishida default: 151c6f29dbbSCyndy Ishida reportError(Filename + ": unsupported file type"); 152c6f29dbbSCyndy Ishida } 153c6f29dbbSCyndy Ishida 154e1f69b86SCyndy Ishida if (ResetBanner) 155bc191ac3SCyndy Ishida ExitOnErr.setBanner(TOOLNAME + ": error: "); 156c6f29dbbSCyndy Ishida return IF; 1575656d797SCyndy Ishida } 1585656d797SCyndy Ishida 15986fa4b2cSCyndy Ishida static bool handleCompareAction(const Context &Ctx) { 160ae182dbbSCyndy Ishida if (Ctx.Inputs.size() != 2) 161ae182dbbSCyndy Ishida reportError("compare only supports two input files", 162ae182dbbSCyndy Ishida /*ExitCode=*/NON_TAPI_EXIT_CODE); 163ae182dbbSCyndy Ishida 164bc191ac3SCyndy Ishida // Override default exit code. 165bc191ac3SCyndy Ishida ExitOnErr = ExitOnError(TOOLNAME + ": error: ", 166bc191ac3SCyndy Ishida /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); 167bc191ac3SCyndy Ishida auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); 168bc191ac3SCyndy Ishida auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); 1695656d797SCyndy Ishida 17051340480SCyndy Ishida // Remove all architectures to ignore before running comparison. 17151340480SCyndy Ishida auto removeArchFromIF = [](auto &IF, const ArchitectureSet &ArchSet, 17251340480SCyndy Ishida const Architecture ArchToRemove) { 17351340480SCyndy Ishida if (!ArchSet.has(ArchToRemove)) 17451340480SCyndy Ishida return; 17551340480SCyndy Ishida if (ArchSet.count() == 1) 17651340480SCyndy Ishida return; 17751340480SCyndy Ishida auto OutIF = IF->remove(ArchToRemove); 17851340480SCyndy Ishida if (!OutIF) 17951340480SCyndy Ishida ExitOnErr(OutIF.takeError()); 18051340480SCyndy Ishida IF = std::move(*OutIF); 18151340480SCyndy Ishida }; 18251340480SCyndy Ishida 18351340480SCyndy Ishida if (!Ctx.CmpOpt.ArchsToIgnore.empty()) { 18451340480SCyndy Ishida const ArchitectureSet LeftArchs = LeftIF->getArchitectures(); 18551340480SCyndy Ishida const ArchitectureSet RightArchs = RightIF->getArchitectures(); 18651340480SCyndy Ishida for (const auto Arch : Ctx.CmpOpt.ArchsToIgnore) { 18751340480SCyndy Ishida removeArchFromIF(LeftIF, LeftArchs, Arch); 18851340480SCyndy Ishida removeArchFromIF(RightIF, RightArchs, Arch); 18951340480SCyndy Ishida } 19051340480SCyndy Ishida } 19151340480SCyndy Ishida 192ec64af59SCyndy Ishida raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 193ae182dbbSCyndy Ishida return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); 194ae182dbbSCyndy Ishida } 195ae182dbbSCyndy Ishida 19686fa4b2cSCyndy Ishida static bool handleWriteAction(const Context &Ctx, 197ae182dbbSCyndy Ishida std::unique_ptr<InterfaceFile> Out = nullptr) { 198ae182dbbSCyndy Ishida if (!Out) { 199ae182dbbSCyndy Ishida if (Ctx.Inputs.size() != 1) 200ae182dbbSCyndy Ishida reportError("write only supports one input file"); 201bc191ac3SCyndy Ishida Out = getInterfaceFile(Ctx.Inputs.front()); 202ae182dbbSCyndy Ishida } 203ae182dbbSCyndy Ishida raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 204ae182dbbSCyndy Ishida ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); 205ae182dbbSCyndy Ishida return EXIT_SUCCESS; 206ae182dbbSCyndy Ishida } 207ae182dbbSCyndy Ishida 20886fa4b2cSCyndy Ishida static bool handleMergeAction(const Context &Ctx) { 209ae182dbbSCyndy Ishida if (Ctx.Inputs.size() < 2) 210ae182dbbSCyndy Ishida reportError("merge requires at least two input files"); 211ae182dbbSCyndy Ishida 212ae182dbbSCyndy Ishida std::unique_ptr<InterfaceFile> Out; 213ae182dbbSCyndy Ishida for (StringRef FileName : Ctx.Inputs) { 214bc191ac3SCyndy Ishida auto IF = getInterfaceFile(FileName); 215ae182dbbSCyndy Ishida // On the first iteration copy the input file and skip merge. 216ae182dbbSCyndy Ishida if (!Out) { 217ae182dbbSCyndy Ishida Out = std::move(IF); 218ae182dbbSCyndy Ishida continue; 219ae182dbbSCyndy Ishida } 220b64992a3SCyndy Ishida Out = ExitOnErr(Out->merge(IF.get())); 221ae182dbbSCyndy Ishida } 222ae182dbbSCyndy Ishida return handleWriteAction(Ctx, std::move(Out)); 2235656d797SCyndy Ishida } 2245656d797SCyndy Ishida 2257189219eSCyndy Ishida static void stubifyImpl(std::unique_ptr<InterfaceFile> IF, Context &Ctx) { 2267189219eSCyndy Ishida // TODO: Add inlining and magic merge support. 2277189219eSCyndy Ishida if (Ctx.OutStream == nullptr) { 2287189219eSCyndy Ishida std::error_code EC; 2296eb7273bSCyndy Ishida assert(!IF->getPath().empty() && "Unknown output location"); 2307189219eSCyndy Ishida SmallString<PATH_MAX> OutputLoc = IF->getPath(); 2317189219eSCyndy Ishida replace_extension(OutputLoc, ".tbd"); 2327189219eSCyndy Ishida Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 2337189219eSCyndy Ishida if (EC) 2347189219eSCyndy Ishida reportError("opening file '" + OutputLoc + ": " + EC.message()); 2357189219eSCyndy Ishida } 2367189219eSCyndy Ishida 2377189219eSCyndy Ishida handleWriteAction(Ctx, std::move(IF)); 2387189219eSCyndy Ishida // Clear out output stream after file has been written incase more files are 2397189219eSCyndy Ishida // stubifed. 2407189219eSCyndy Ishida Ctx.OutStream = nullptr; 2417189219eSCyndy Ishida } 2427189219eSCyndy Ishida 2437189219eSCyndy Ishida static void stubifyDirectory(const StringRef InputPath, Context &Ctx) { 2447189219eSCyndy Ishida assert(InputPath.back() != '/' && "Unexpected / at end of input path."); 2457189219eSCyndy Ishida StringMap<std::vector<SymLink>> SymLinks; 2467189219eSCyndy Ishida StringMap<std::unique_ptr<InterfaceFile>> Dylibs; 2477189219eSCyndy Ishida StringMap<std::string> OriginalNames; 2487189219eSCyndy Ishida std::set<std::pair<std::string, bool>> LibsToDelete; 2497189219eSCyndy Ishida 2507189219eSCyndy Ishida std::error_code EC; 2517189219eSCyndy Ishida for (sys::fs::recursive_directory_iterator IT(InputPath, EC), IE; IT != IE; 2527189219eSCyndy Ishida IT.increment(EC)) { 2537189219eSCyndy Ishida if (EC == std::errc::no_such_file_or_directory) { 2547189219eSCyndy Ishida reportWarning(IT->path() + ": " + EC.message()); 2557189219eSCyndy Ishida continue; 2567189219eSCyndy Ishida } 2577189219eSCyndy Ishida if (EC) 2587189219eSCyndy Ishida reportError(IT->path() + ": " + EC.message()); 2597189219eSCyndy Ishida 2602d48489cSCyndy Ishida // Skip header directories (include/Headers/PrivateHeaders). 2617189219eSCyndy Ishida StringRef Path = IT->path(); 2622d48489cSCyndy Ishida if (sys::fs::is_directory(Path)) { 2632d48489cSCyndy Ishida const StringRef Stem = sys::path::stem(Path); 2642d48489cSCyndy Ishida if ((Stem == "include") || (Stem == "Headers") || 2652d48489cSCyndy Ishida (Stem == "PrivateHeaders") || (Stem == "Modules")) { 2667189219eSCyndy Ishida IT.no_push(); 2677189219eSCyndy Ishida continue; 2687189219eSCyndy Ishida } 2692d48489cSCyndy Ishida } 2702d48489cSCyndy Ishida 2712d48489cSCyndy Ishida // Skip module files too. 2722d48489cSCyndy Ishida if (Path.ends_with(".map") || Path.ends_with(".modulemap")) 2732d48489cSCyndy Ishida continue; 2747189219eSCyndy Ishida 2757189219eSCyndy Ishida // Check if the entry is a symlink. We don't follow symlinks but we record 2767189219eSCyndy Ishida // their content. 2777189219eSCyndy Ishida bool IsSymLink; 2787189219eSCyndy Ishida if (auto EC = sys::fs::is_symlink_file(Path, IsSymLink)) 2797189219eSCyndy Ishida reportError(Path + ": " + EC.message()); 2807189219eSCyndy Ishida 2817189219eSCyndy Ishida if (IsSymLink) { 2827189219eSCyndy Ishida IT.no_push(); 2837189219eSCyndy Ishida 2847189219eSCyndy Ishida bool ShouldSkip; 2857189219eSCyndy Ishida auto SymLinkEC = shouldSkipSymLink(Path, ShouldSkip); 2867189219eSCyndy Ishida 2877189219eSCyndy Ishida // If symlink is broken, for some reason, we should continue 2887189219eSCyndy Ishida // trying to repair it before quitting. 2897189219eSCyndy Ishida if (!SymLinkEC && ShouldSkip) 2907189219eSCyndy Ishida continue; 2917189219eSCyndy Ishida 2927189219eSCyndy Ishida if (Ctx.StubOpt.DeletePrivate && 2937189219eSCyndy Ishida isPrivateLibrary(Path.drop_front(InputPath.size()), true)) { 2947189219eSCyndy Ishida LibsToDelete.emplace(Path, false); 2957189219eSCyndy Ishida continue; 2967189219eSCyndy Ishida } 2977189219eSCyndy Ishida 2987189219eSCyndy Ishida SmallString<PATH_MAX> SymPath; 2995bcd9105SCyndy Ishida read_link(Path, SymPath); 3007189219eSCyndy Ishida // Sometimes there are broken symlinks that are absolute paths, which are 3017189219eSCyndy Ishida // invalid during build time, but would be correct during runtime. In the 3027189219eSCyndy Ishida // case of an absolute path we should check first if the path exists with 3037189219eSCyndy Ishida // the known locations as prefix. 3047189219eSCyndy Ishida SmallString<PATH_MAX> LinkSrc = Path; 3057189219eSCyndy Ishida SmallString<PATH_MAX> LinkTarget; 3067189219eSCyndy Ishida if (sys::path::is_absolute(SymPath)) { 3077189219eSCyndy Ishida LinkTarget = InputPath; 3087189219eSCyndy Ishida sys::path::append(LinkTarget, SymPath); 3097189219eSCyndy Ishida 3107189219eSCyndy Ishida // TODO: Investigate supporting a file manager for file system accesses. 3117189219eSCyndy Ishida if (sys::fs::exists(LinkTarget)) { 3127189219eSCyndy Ishida // Convert the absolute path to an relative path. 3137189219eSCyndy Ishida if (auto ec = MachO::make_relative(LinkSrc, LinkTarget, SymPath)) 3147189219eSCyndy Ishida reportError(LinkTarget + ": " + EC.message()); 3157189219eSCyndy Ishida } else if (!sys::fs::exists(SymPath)) { 3167189219eSCyndy Ishida reportWarning("ignoring broken symlink: " + Path); 3177189219eSCyndy Ishida continue; 3187189219eSCyndy Ishida } else { 3197189219eSCyndy Ishida LinkTarget = SymPath; 3207189219eSCyndy Ishida } 3217189219eSCyndy Ishida } else { 3227189219eSCyndy Ishida LinkTarget = LinkSrc; 3237189219eSCyndy Ishida sys::path::remove_filename(LinkTarget); 3247189219eSCyndy Ishida sys::path::append(LinkTarget, SymPath); 3257189219eSCyndy Ishida } 3267189219eSCyndy Ishida 3277189219eSCyndy Ishida // For Apple SDKs, the symlink src is guaranteed to be a canonical path 3287189219eSCyndy Ishida // because we don't follow symlinks when scanning. The symlink target is 3297189219eSCyndy Ishida // constructed from the symlink path and needs to be canonicalized. 3307189219eSCyndy Ishida if (auto ec = sys::fs::real_path(Twine(LinkTarget), LinkTarget)) { 3317189219eSCyndy Ishida reportWarning(LinkTarget + ": " + ec.message()); 3327189219eSCyndy Ishida continue; 3337189219eSCyndy Ishida } 3347189219eSCyndy Ishida 335a99bf0f6SKazu Hirata SymLinks[LinkTarget.c_str()].emplace_back(LinkSrc.str(), 336a99bf0f6SKazu Hirata std::string(SymPath.str())); 3377189219eSCyndy Ishida 3387189219eSCyndy Ishida continue; 3397189219eSCyndy Ishida } 3407189219eSCyndy Ishida 3417189219eSCyndy Ishida bool IsDirectory = false; 3427189219eSCyndy Ishida if (auto EC = sys::fs::is_directory(Path, IsDirectory)) 3437189219eSCyndy Ishida reportError(Path + ": " + EC.message()); 3447189219eSCyndy Ishida if (IsDirectory) 3457189219eSCyndy Ishida continue; 3467189219eSCyndy Ishida 3477189219eSCyndy Ishida if (Ctx.StubOpt.DeletePrivate && 3487189219eSCyndy Ishida isPrivateLibrary(Path.drop_front(InputPath.size()))) { 3497189219eSCyndy Ishida IT.no_push(); 3507189219eSCyndy Ishida LibsToDelete.emplace(Path, false); 3517189219eSCyndy Ishida continue; 3527189219eSCyndy Ishida } 3537189219eSCyndy Ishida auto IF = getInterfaceFile(Path); 3547189219eSCyndy Ishida if (Ctx.StubOpt.TraceLibs) 3557189219eSCyndy Ishida errs() << Path << "\n"; 3567189219eSCyndy Ishida 3577189219eSCyndy Ishida // Normalize path for map lookup by removing the extension. 3587189219eSCyndy Ishida SmallString<PATH_MAX> NormalizedPath(Path); 3597189219eSCyndy Ishida replace_extension(NormalizedPath, ""); 3607189219eSCyndy Ishida 3617189219eSCyndy Ishida if ((IF->getFileType() == FileType::MachO_DynamicLibrary) || 3627189219eSCyndy Ishida (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) { 3637189219eSCyndy Ishida OriginalNames[NormalizedPath.c_str()] = IF->getPath(); 3647189219eSCyndy Ishida 3657189219eSCyndy Ishida // Don't add this MachO dynamic library because we already have a 3667189219eSCyndy Ishida // text-based stub recorded for this path. 3677189219eSCyndy Ishida if (Dylibs.count(NormalizedPath.c_str())) 3687189219eSCyndy Ishida continue; 3697189219eSCyndy Ishida } 3707189219eSCyndy Ishida 3717189219eSCyndy Ishida Dylibs[NormalizedPath.c_str()] = std::move(IF); 3727189219eSCyndy Ishida } 3737189219eSCyndy Ishida 3747189219eSCyndy Ishida for (auto &Lib : Dylibs) { 3757189219eSCyndy Ishida auto &Dylib = Lib.second; 3767189219eSCyndy Ishida // Get the original file name. 3777189219eSCyndy Ishida SmallString<PATH_MAX> NormalizedPath(Dylib->getPath()); 3787189219eSCyndy Ishida stubifyImpl(std::move(Dylib), Ctx); 3797189219eSCyndy Ishida 3807189219eSCyndy Ishida replace_extension(NormalizedPath, ""); 3817189219eSCyndy Ishida auto Found = OriginalNames.find(NormalizedPath.c_str()); 3827189219eSCyndy Ishida if (Found == OriginalNames.end()) 3837189219eSCyndy Ishida continue; 3847189219eSCyndy Ishida 3857189219eSCyndy Ishida if (Ctx.StubOpt.DeleteInput) 3867189219eSCyndy Ishida LibsToDelete.emplace(Found->second, true); 3877189219eSCyndy Ishida 3887189219eSCyndy Ishida // Don't allow for more than 20 levels of symlinks when searching for 3897189219eSCyndy Ishida // libraries to stubify. 3907189219eSCyndy Ishida StringRef LibToCheck = Found->second; 3917189219eSCyndy Ishida for (int i = 0; i < 20; ++i) { 39261a286acSKazu Hirata auto LinkIt = SymLinks.find(LibToCheck); 3937189219eSCyndy Ishida if (LinkIt != SymLinks.end()) { 3947189219eSCyndy Ishida for (auto &SymInfo : LinkIt->second) { 3957189219eSCyndy Ishida SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath); 3967189219eSCyndy Ishida SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent); 3977189219eSCyndy Ishida replace_extension(LinkSrc, "tbd"); 3987189219eSCyndy Ishida replace_extension(LinkTarget, "tbd"); 3997189219eSCyndy Ishida 4007189219eSCyndy Ishida if (auto EC = sys::fs::remove(LinkSrc)) 4017189219eSCyndy Ishida reportError(LinkSrc + " : " + EC.message()); 4027189219eSCyndy Ishida 4037189219eSCyndy Ishida if (auto EC = sys::fs::create_link(LinkTarget, LinkSrc)) 4047189219eSCyndy Ishida reportError(LinkTarget + " : " + EC.message()); 4057189219eSCyndy Ishida 4067189219eSCyndy Ishida if (Ctx.StubOpt.DeleteInput) 4077189219eSCyndy Ishida LibsToDelete.emplace(SymInfo.SrcPath, true); 4087189219eSCyndy Ishida 4097189219eSCyndy Ishida LibToCheck = SymInfo.SrcPath; 4107189219eSCyndy Ishida } 4117189219eSCyndy Ishida } else 4127189219eSCyndy Ishida break; 4137189219eSCyndy Ishida } 4147189219eSCyndy Ishida } 4157189219eSCyndy Ishida 4167189219eSCyndy Ishida // Recursively delete the directories. This will abort when they are not empty 4177189219eSCyndy Ishida // or we reach the root of the SDK. 4187189219eSCyndy Ishida for (const auto &[LibPath, IsInput] : LibsToDelete) { 4197189219eSCyndy Ishida if (!IsInput && SymLinks.count(LibPath)) 4207189219eSCyndy Ishida continue; 4217189219eSCyndy Ishida 4227189219eSCyndy Ishida if (auto EC = sys::fs::remove(LibPath)) 4237189219eSCyndy Ishida reportError(LibPath + " : " + EC.message()); 4247189219eSCyndy Ishida 4257189219eSCyndy Ishida std::error_code EC; 4267189219eSCyndy Ishida auto Dir = sys::path::parent_path(LibPath); 4277189219eSCyndy Ishida do { 4287189219eSCyndy Ishida EC = sys::fs::remove(Dir); 4297189219eSCyndy Ishida Dir = sys::path::parent_path(Dir); 4307189219eSCyndy Ishida if (!Dir.starts_with(InputPath)) 4317189219eSCyndy Ishida break; 4327189219eSCyndy Ishida } while (!EC); 4337189219eSCyndy Ishida } 4347189219eSCyndy Ishida } 4357189219eSCyndy Ishida 436c6f29dbbSCyndy Ishida static bool handleStubifyAction(Context &Ctx) { 437c6f29dbbSCyndy Ishida if (Ctx.Inputs.empty()) 438c6f29dbbSCyndy Ishida reportError("stubify requires at least one input file"); 439c6f29dbbSCyndy Ishida 440c6f29dbbSCyndy Ishida if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr)) 441c6f29dbbSCyndy Ishida reportError("cannot write multiple inputs into single output file"); 442c6f29dbbSCyndy Ishida 4437189219eSCyndy Ishida for (StringRef PathName : Ctx.Inputs) { 4447189219eSCyndy Ishida bool IsDirectory = false; 4457189219eSCyndy Ishida if (auto EC = sys::fs::is_directory(PathName, IsDirectory)) 4467189219eSCyndy Ishida reportError(PathName + ": " + EC.message()); 4477189219eSCyndy Ishida 4487189219eSCyndy Ishida if (IsDirectory) { 4497189219eSCyndy Ishida if (Ctx.OutStream != nullptr) 4507189219eSCyndy Ishida reportError("cannot stubify directory'" + PathName + 4517189219eSCyndy Ishida "' into single output file"); 4527189219eSCyndy Ishida stubifyDirectory(PathName, Ctx); 4537189219eSCyndy Ishida continue; 454c6f29dbbSCyndy Ishida } 4557189219eSCyndy Ishida 4567189219eSCyndy Ishida stubifyImpl(getInterfaceFile(PathName), Ctx); 4577189219eSCyndy Ishida if (Ctx.StubOpt.DeleteInput) 4587189219eSCyndy Ishida if (auto ec = sys::fs::remove(PathName)) 4597189219eSCyndy Ishida reportError("deleting file '" + PathName + ": " + ec.message()); 460c6f29dbbSCyndy Ishida } 461c6f29dbbSCyndy Ishida return EXIT_SUCCESS; 462c6f29dbbSCyndy Ishida } 463c6f29dbbSCyndy Ishida 464e1f69b86SCyndy Ishida using IFOperation = 465e1f69b86SCyndy Ishida std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>( 466e1f69b86SCyndy Ishida const llvm::MachO::InterfaceFile &, Architecture)>; 46786fa4b2cSCyndy Ishida static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, 468e1f69b86SCyndy Ishida IFOperation act) { 469e1f69b86SCyndy Ishida if (Ctx.Inputs.size() != 1) 470e1f69b86SCyndy Ishida reportError(Action + " only supports one input file"); 471e1f69b86SCyndy Ishida if (Ctx.Arch == AK_unknown) 472e1f69b86SCyndy Ishida reportError(Action + " requires -arch <arch>"); 473e1f69b86SCyndy Ishida 474e1f69b86SCyndy Ishida auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); 475e1f69b86SCyndy Ishida auto OutIF = act(*IF, Ctx.Arch); 476e1f69b86SCyndy Ishida if (!OutIF) 477e1f69b86SCyndy Ishida ExitOnErr(OutIF.takeError()); 478e1f69b86SCyndy Ishida 479e1f69b86SCyndy Ishida return handleWriteAction(Ctx, std::move(*OutIF)); 480e1f69b86SCyndy Ishida } 481e1f69b86SCyndy Ishida 482c6f29dbbSCyndy Ishida static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) { 483c6f29dbbSCyndy Ishida Opt.DeleteInput = Args.hasArg(OPT_delete_input); 4847189219eSCyndy Ishida Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries); 4857189219eSCyndy Ishida Opt.TraceLibs = Args.hasArg(OPT_t); 486c6f29dbbSCyndy Ishida } 487c6f29dbbSCyndy Ishida 488ec64af59SCyndy Ishida int main(int Argc, char **Argv) { 489ec64af59SCyndy Ishida InitLLVM X(Argc, Argv); 490ec64af59SCyndy Ishida BumpPtrAllocator A; 491ec64af59SCyndy Ishida StringSaver Saver(A); 492ec64af59SCyndy Ishida TAPIOptTable Tbl; 493ec64af59SCyndy Ishida Context Ctx; 494bc191ac3SCyndy Ishida ExitOnErr.setBanner(TOOLNAME + ": error:"); 495bc191ac3SCyndy Ishida opt::InputArgList Args = Tbl.parseArgs( 496bc191ac3SCyndy Ishida Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); 497ec64af59SCyndy Ishida if (Args.hasArg(OPT_help)) { 498e1f69b86SCyndy Ishida Tbl.printHelp(outs(), 499142e567cSCyndy Ishida "USAGE: llvm-readtapi <command> [-arch <architecture> " 500142e567cSCyndy Ishida "<options>]* <inputs> [-o " 501e1f69b86SCyndy Ishida "<output>]*", 502142e567cSCyndy Ishida "LLVM TAPI file reader and transformer"); 503142e567cSCyndy Ishida return EXIT_SUCCESS; 504142e567cSCyndy Ishida } 505142e567cSCyndy Ishida 506142e567cSCyndy Ishida if (Args.hasArg(OPT_version)) { 507142e567cSCyndy Ishida cl::PrintVersionMessage(); 508ec64af59SCyndy Ishida return EXIT_SUCCESS; 509ec64af59SCyndy Ishida } 510ec64af59SCyndy Ishida 511ec64af59SCyndy Ishida for (opt::Arg *A : Args.filtered(OPT_INPUT)) 512ec64af59SCyndy Ishida Ctx.Inputs.push_back(A->getValue()); 513ec64af59SCyndy Ishida 5143b48a7a0SCyndy Ishida if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { 515ec64af59SCyndy Ishida std::string OutputLoc = std::move(A->getValue()); 516ec64af59SCyndy Ishida std::error_code EC; 517ec64af59SCyndy Ishida Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 518bc191ac3SCyndy Ishida if (EC) 519bc191ac3SCyndy Ishida reportError("error opening the file '" + OutputLoc + EC.message(), 520bc191ac3SCyndy Ishida NON_TAPI_EXIT_CODE); 521ec64af59SCyndy Ishida } 522ec64af59SCyndy Ishida 523ae182dbbSCyndy Ishida Ctx.Compact = Args.hasArg(OPT_compact); 524ae182dbbSCyndy Ishida 525ae182dbbSCyndy Ishida if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { 526ae182dbbSCyndy Ishida StringRef FT = A->getValue(); 527ae182dbbSCyndy Ishida Ctx.WriteFT = TextAPIWriter::parseFileType(FT); 528ae182dbbSCyndy Ishida if (Ctx.WriteFT < FileType::TBD_V3) 529ae182dbbSCyndy Ishida reportError("deprecated filetype '" + FT + "' is not supported to write"); 530ae182dbbSCyndy Ishida if (Ctx.WriteFT == FileType::Invalid) 531ae182dbbSCyndy Ishida reportError("unsupported filetype '" + FT + "'"); 532ae182dbbSCyndy Ishida } 533ae182dbbSCyndy Ishida 53451340480SCyndy Ishida auto SanitizeArch = [&](opt::Arg *A) { 53551340480SCyndy Ishida StringRef ArchStr = A->getValue(); 53651340480SCyndy Ishida auto Arch = getArchitectureFromName(ArchStr); 53751340480SCyndy Ishida if (Arch == AK_unknown) 53851340480SCyndy Ishida reportError("unsupported architecture '" + ArchStr); 53951340480SCyndy Ishida return Arch; 54051340480SCyndy Ishida }; 54151340480SCyndy Ishida 54251340480SCyndy Ishida if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) 54351340480SCyndy Ishida Ctx.Arch = SanitizeArch(A); 54451340480SCyndy Ishida 54551340480SCyndy Ishida for (opt::Arg *A : Args.filtered(OPT_ignore_arch_EQ)) 54651340480SCyndy Ishida Ctx.CmpOpt.ArchsToIgnore.set(SanitizeArch(A)); 54751340480SCyndy Ishida 548ae182dbbSCyndy Ishida // Handle top level and exclusive operation. 549ae182dbbSCyndy Ishida SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); 550ae182dbbSCyndy Ishida 551ae182dbbSCyndy Ishida if (ActionArgs.empty()) 552ae182dbbSCyndy Ishida // If no action specified, write out tapi file in requested format. 553ae182dbbSCyndy Ishida return handleWriteAction(Ctx); 554ae182dbbSCyndy Ishida 555ae182dbbSCyndy Ishida if (ActionArgs.size() > 1) { 556ae182dbbSCyndy Ishida std::string Buf; 557ae182dbbSCyndy Ishida raw_string_ostream OS(Buf); 558ae182dbbSCyndy Ishida OS << "only one of the following actions can be specified:"; 559ae182dbbSCyndy Ishida for (auto *Arg : ActionArgs) 560ae182dbbSCyndy Ishida OS << " " << Arg->getSpelling(); 561ae182dbbSCyndy Ishida reportError(OS.str()); 562ae182dbbSCyndy Ishida } 563ae182dbbSCyndy Ishida 564ae182dbbSCyndy Ishida switch (ActionArgs.front()->getOption().getID()) { 565ae182dbbSCyndy Ishida case OPT_compare: 566ec64af59SCyndy Ishida return handleCompareAction(Ctx); 567ae182dbbSCyndy Ishida case OPT_merge: 568ae182dbbSCyndy Ishida return handleMergeAction(Ctx); 569e1f69b86SCyndy Ishida case OPT_extract: 570e1f69b86SCyndy Ishida return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract); 571e1f69b86SCyndy Ishida case OPT_remove: 572e1f69b86SCyndy Ishida return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove); 573c6f29dbbSCyndy Ishida case OPT_stubify: 574c6f29dbbSCyndy Ishida setStubOptions(Args, Ctx.StubOpt); 575c6f29dbbSCyndy Ishida return handleStubifyAction(Ctx); 576ae182dbbSCyndy Ishida } 577ec64af59SCyndy Ishida 578ec64af59SCyndy Ishida return EXIT_SUCCESS; 5795656d797SCyndy Ishida } 580