//===-- llvm-readtapi.cpp - tapi file reader and transformer -----*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines the command-line driver for llvm-readtapi. // //===----------------------------------------------------------------------===// #include "DiffEngine.h" #include "llvm/BinaryFormat/Magic.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TextAPI/DylibReader.h" #include "llvm/TextAPI/TextAPIError.h" #include "llvm/TextAPI/TextAPIReader.h" #include "llvm/TextAPI/TextAPIWriter.h" #include "llvm/TextAPI/Utils.h" #include using namespace llvm; using namespace MachO; using namespace object; #if !defined(PATH_MAX) #define PATH_MAX 1024 #endif namespace { using namespace llvm::opt; enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), #include "TapiOpts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) \ static constexpr StringLiteral NAME##_init[] = VALUE; \ static constexpr ArrayRef NAME(NAME##_init, \ std::size(NAME##_init) - 1); #include "TapiOpts.inc" #undef PREFIX static constexpr opt::OptTable::Info InfoTable[] = { #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), #include "TapiOpts.inc" #undef OPTION }; class TAPIOptTable : public opt::GenericOptTable { public: TAPIOptTable() : opt::GenericOptTable(InfoTable) { setGroupedShortOptions(true); } }; struct StubOptions { bool DeleteInput = false; }; struct Context { std::vector Inputs; std::unique_ptr OutStream; FileType WriteFT = FileType::TBD_V5; StubOptions StubOpt; bool Compact = false; Architecture Arch = AK_unknown; }; // Use unique exit code to differentiate failures not directly caused from // TextAPI operations. This is used for wrapping `compare` operations in // automation and scripting. const int NON_TAPI_EXIT_CODE = 2; const std::string TOOLNAME = "llvm-readtapi"; ExitOnError ExitOnErr; } // anonymous namespace // Handle error reporting in cases where `ExitOnError` is not used. static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { errs() << TOOLNAME << ": error: " << Message << "\n"; errs().flush(); exit(ExitCode); } static std::unique_ptr getInterfaceFile(const StringRef Filename, bool ResetBanner = true) { ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' "); ErrorOr> BufferOrErr = MemoryBuffer::getFile(Filename); if (BufferOrErr.getError()) ExitOnErr(errorCodeToError(BufferOrErr.getError())); auto Buffer = std::move(*BufferOrErr); std::unique_ptr IF; switch (identify_magic(Buffer->getBuffer())) { case file_magic::macho_dynamically_linked_shared_lib: LLVM_FALLTHROUGH; case file_magic::macho_dynamically_linked_shared_lib_stub: LLVM_FALLTHROUGH; case file_magic::macho_universal_binary: IF = ExitOnErr(DylibReader::get(Buffer->getMemBufferRef())); break; case file_magic::tapi_file: IF = ExitOnErr(TextAPIReader::get(Buffer->getMemBufferRef())); break; default: reportError(Filename + ": unsupported file type"); } if (ResetBanner) ExitOnErr.setBanner(TOOLNAME + ": error: "); return IF; } static bool handleCompareAction(const Context &Ctx) { if (Ctx.Inputs.size() != 2) reportError("compare only supports two input files", /*ExitCode=*/NON_TAPI_EXIT_CODE); // Override default exit code. ExitOnErr = ExitOnError(TOOLNAME + ": error: ", /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); } static bool handleWriteAction(const Context &Ctx, std::unique_ptr Out = nullptr) { if (!Out) { if (Ctx.Inputs.size() != 1) reportError("write only supports one input file"); Out = getInterfaceFile(Ctx.Inputs.front()); } raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); return EXIT_SUCCESS; } static bool handleMergeAction(const Context &Ctx) { if (Ctx.Inputs.size() < 2) reportError("merge requires at least two input files"); std::unique_ptr Out; for (StringRef FileName : Ctx.Inputs) { auto IF = getInterfaceFile(FileName); // On the first iteration copy the input file and skip merge. if (!Out) { Out = std::move(IF); continue; } Out = ExitOnErr(Out->merge(IF.get())); } return handleWriteAction(Ctx, std::move(Out)); } static bool handleStubifyAction(Context &Ctx) { if (Ctx.Inputs.empty()) reportError("stubify requires at least one input file"); if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr)) reportError("cannot write multiple inputs into single output file"); for (StringRef FileName : Ctx.Inputs) { auto IF = getInterfaceFile(FileName); if (Ctx.StubOpt.DeleteInput) { std::error_code EC; SmallString OutputLoc = FileName; MachO::replace_extension(OutputLoc, ".tbd"); Ctx.OutStream = std::make_unique(OutputLoc, EC); if (EC) reportError("opening file '" + OutputLoc + ": " + EC.message()); if (auto Err = sys::fs::remove(FileName)) reportError("deleting file '" + FileName + ": " + EC.message()); } handleWriteAction(Ctx, std::move(IF)); } return EXIT_SUCCESS; } using IFOperation = std::function>( const llvm::MachO::InterfaceFile &, Architecture)>; static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, IFOperation act) { if (Ctx.Inputs.size() != 1) reportError(Action + " only supports one input file"); if (Ctx.Arch == AK_unknown) reportError(Action + " requires -arch "); auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); auto OutIF = act(*IF, Ctx.Arch); if (!OutIF) ExitOnErr(OutIF.takeError()); return handleWriteAction(Ctx, std::move(*OutIF)); } static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) { Opt.DeleteInput = Args.hasArg(OPT_delete_input); } int main(int Argc, char **Argv) { InitLLVM X(Argc, Argv); BumpPtrAllocator A; StringSaver Saver(A); TAPIOptTable Tbl; Context Ctx; ExitOnErr.setBanner(TOOLNAME + ": error:"); opt::InputArgList Args = Tbl.parseArgs( Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); if (Args.hasArg(OPT_help)) { Tbl.printHelp(outs(), "USAGE: llvm-readtapi [-arch " "]* [-o " "]*", "LLVM TAPI file reader and transformer"); return EXIT_SUCCESS; } if (Args.hasArg(OPT_version)) { cl::PrintVersionMessage(); return EXIT_SUCCESS; } // TODO: Add support for picking up libraries from directory input. for (opt::Arg *A : Args.filtered(OPT_INPUT)) Ctx.Inputs.push_back(A->getValue()); if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { std::string OutputLoc = std::move(A->getValue()); std::error_code EC; Ctx.OutStream = std::make_unique(OutputLoc, EC); if (EC) reportError("error opening the file '" + OutputLoc + EC.message(), NON_TAPI_EXIT_CODE); } Ctx.Compact = Args.hasArg(OPT_compact); if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { StringRef FT = A->getValue(); Ctx.WriteFT = TextAPIWriter::parseFileType(FT); if (Ctx.WriteFT < FileType::TBD_V3) reportError("deprecated filetype '" + FT + "' is not supported to write"); if (Ctx.WriteFT == FileType::Invalid) reportError("unsupported filetype '" + FT + "'"); } if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) { StringRef Arch = A->getValue(); Ctx.Arch = getArchitectureFromName(Arch); if (Ctx.Arch == AK_unknown) reportError("unsupported architecture '" + Arch); } // Handle top level and exclusive operation. SmallVector ActionArgs(Args.filtered(OPT_action_group)); if (ActionArgs.empty()) // If no action specified, write out tapi file in requested format. return handleWriteAction(Ctx); if (ActionArgs.size() > 1) { std::string Buf; raw_string_ostream OS(Buf); OS << "only one of the following actions can be specified:"; for (auto *Arg : ActionArgs) OS << " " << Arg->getSpelling(); reportError(OS.str()); } switch (ActionArgs.front()->getOption().getID()) { case OPT_compare: return handleCompareAction(Ctx); case OPT_merge: return handleMergeAction(Ctx); case OPT_extract: return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract); case OPT_remove: return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove); case OPT_stubify: setStubOptions(Args, Ctx.StubOpt); return handleStubifyAction(Ctx); } return EXIT_SUCCESS; }