1 //===-- llvm-readtapi.cpp - tapi file reader and manipulator -----*- C++-*-===// 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 // This file defines the command-line driver for llvm-readtapi. 10 // 11 //===----------------------------------------------------------------------===// 12 #include "DiffEngine.h" 13 #include "llvm/Option/Arg.h" 14 #include "llvm/Option/ArgList.h" 15 #include "llvm/Option/Option.h" 16 #include "llvm/Support/CommandLine.h" 17 #include "llvm/Support/Error.h" 18 #include "llvm/Support/InitLLVM.h" 19 #include "llvm/Support/MemoryBuffer.h" 20 #include "llvm/Support/raw_ostream.h" 21 #include "llvm/TextAPI/TextAPIError.h" 22 #include "llvm/TextAPI/TextAPIReader.h" 23 #include "llvm/TextAPI/TextAPIWriter.h" 24 #include <cstdlib> 25 26 using namespace llvm; 27 using namespace MachO; 28 using namespace object; 29 30 namespace { 31 using namespace llvm::opt; 32 enum ID { 33 OPT_INVALID = 0, // This is not an option ID. 34 #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), 35 #include "TapiOpts.inc" 36 #undef OPTION 37 }; 38 39 #define PREFIX(NAME, VALUE) \ 40 static constexpr StringLiteral NAME##_init[] = VALUE; \ 41 static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \ 42 std::size(NAME##_init) - 1); 43 #include "TapiOpts.inc" 44 #undef PREFIX 45 46 static constexpr opt::OptTable::Info InfoTable[] = { 47 #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), 48 #include "TapiOpts.inc" 49 #undef OPTION 50 }; 51 52 class TAPIOptTable : public opt::GenericOptTable { 53 public: 54 TAPIOptTable() : opt::GenericOptTable(InfoTable) { 55 setGroupedShortOptions(true); 56 } 57 }; 58 59 struct Context { 60 std::vector<std::string> Inputs; 61 std::unique_ptr<llvm::raw_fd_stream> OutStream; 62 FileType WriteFT = FileType::TBD_V5; 63 bool Compact = false; 64 Architecture Arch = AK_unknown; 65 }; 66 67 // Use unique exit code to differentiate failures not directly caused from 68 // TextAPI operations. This is used for wrapping `compare` operations in 69 // automation and scripting. 70 const int NON_TAPI_EXIT_CODE = 2; 71 const std::string TOOLNAME = "llvm-readtapi"; 72 ExitOnError ExitOnErr; 73 } // anonymous namespace 74 75 // Handle error reporting in cases where `ExitOnError` is not used. 76 static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { 77 errs() << TOOLNAME << ": error: " << Message << "\n"; 78 errs().flush(); 79 exit(ExitCode); 80 } 81 82 static std::unique_ptr<InterfaceFile> 83 getInterfaceFile(const StringRef Filename, bool ResetBanner = true) { 84 ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' "); 85 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 86 MemoryBuffer::getFile(Filename); 87 if (BufferOrErr.getError()) 88 ExitOnErr(errorCodeToError(BufferOrErr.getError())); 89 Expected<std::unique_ptr<InterfaceFile>> IF = 90 TextAPIReader::get((*BufferOrErr)->getMemBufferRef()); 91 if (!IF) 92 ExitOnErr(IF.takeError()); 93 if (ResetBanner) 94 ExitOnErr.setBanner(TOOLNAME + ": error: "); 95 return std::move(*IF); 96 } 97 98 static bool handleCompareAction(const Context &Ctx) { 99 if (Ctx.Inputs.size() != 2) 100 reportError("compare only supports two input files", 101 /*ExitCode=*/NON_TAPI_EXIT_CODE); 102 103 // Override default exit code. 104 ExitOnErr = ExitOnError(TOOLNAME + ": error: ", 105 /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); 106 auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); 107 auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); 108 109 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 110 return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); 111 } 112 113 static bool handleWriteAction(const Context &Ctx, 114 std::unique_ptr<InterfaceFile> Out = nullptr) { 115 if (!Out) { 116 if (Ctx.Inputs.size() != 1) 117 reportError("write only supports one input file"); 118 Out = getInterfaceFile(Ctx.Inputs.front()); 119 } 120 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 121 ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); 122 return EXIT_SUCCESS; 123 } 124 125 static bool handleMergeAction(const Context &Ctx) { 126 if (Ctx.Inputs.size() < 2) 127 reportError("merge requires at least two input files"); 128 129 std::unique_ptr<InterfaceFile> Out; 130 for (StringRef FileName : Ctx.Inputs) { 131 auto IF = getInterfaceFile(FileName); 132 // On the first iteration copy the input file and skip merge. 133 if (!Out) { 134 Out = std::move(IF); 135 continue; 136 } 137 auto ResultIF = Out->merge(IF.get()); 138 if (!ResultIF) 139 ExitOnErr(ResultIF.takeError()); 140 Out = std::move(ResultIF.get()); 141 } 142 return handleWriteAction(Ctx, std::move(Out)); 143 } 144 145 using IFOperation = 146 std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>( 147 const llvm::MachO::InterfaceFile &, Architecture)>; 148 static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, 149 IFOperation act) { 150 if (Ctx.Inputs.size() != 1) 151 reportError(Action + " only supports one input file"); 152 if (Ctx.Arch == AK_unknown) 153 reportError(Action + " requires -arch <arch>"); 154 155 auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); 156 auto OutIF = act(*IF, Ctx.Arch); 157 if (!OutIF) 158 ExitOnErr(OutIF.takeError()); 159 160 return handleWriteAction(Ctx, std::move(*OutIF)); 161 } 162 163 int main(int Argc, char **Argv) { 164 InitLLVM X(Argc, Argv); 165 BumpPtrAllocator A; 166 StringSaver Saver(A); 167 TAPIOptTable Tbl; 168 Context Ctx; 169 ExitOnErr.setBanner(TOOLNAME + ": error:"); 170 opt::InputArgList Args = Tbl.parseArgs( 171 Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); 172 if (Args.hasArg(OPT_help)) { 173 Tbl.printHelp(outs(), 174 "USAGE: llvm-readtapi [options] [-arch <arch>]* <inputs> [-o " 175 "<output>]*", 176 "LLVM TAPI file reader and manipulator"); 177 return EXIT_SUCCESS; 178 } 179 180 for (opt::Arg *A : Args.filtered(OPT_INPUT)) 181 Ctx.Inputs.push_back(A->getValue()); 182 183 if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { 184 std::string OutputLoc = std::move(A->getValue()); 185 std::error_code EC; 186 Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 187 if (EC) 188 reportError("error opening the file '" + OutputLoc + EC.message(), 189 NON_TAPI_EXIT_CODE); 190 } 191 192 Ctx.Compact = Args.hasArg(OPT_compact); 193 194 if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { 195 StringRef FT = A->getValue(); 196 Ctx.WriteFT = TextAPIWriter::parseFileType(FT); 197 if (Ctx.WriteFT < FileType::TBD_V3) 198 reportError("deprecated filetype '" + FT + "' is not supported to write"); 199 if (Ctx.WriteFT == FileType::Invalid) 200 reportError("unsupported filetype '" + FT + "'"); 201 } 202 203 if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) { 204 StringRef Arch = A->getValue(); 205 Ctx.Arch = getArchitectureFromName(Arch); 206 if (Ctx.Arch == AK_unknown) 207 reportError("unsupported architecture '" + Arch); 208 } 209 // Handle top level and exclusive operation. 210 SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); 211 212 if (ActionArgs.empty()) 213 // If no action specified, write out tapi file in requested format. 214 return handleWriteAction(Ctx); 215 216 if (ActionArgs.size() > 1) { 217 std::string Buf; 218 raw_string_ostream OS(Buf); 219 OS << "only one of the following actions can be specified:"; 220 for (auto *Arg : ActionArgs) 221 OS << " " << Arg->getSpelling(); 222 reportError(OS.str()); 223 } 224 225 switch (ActionArgs.front()->getOption().getID()) { 226 case OPT_compare: 227 return handleCompareAction(Ctx); 228 case OPT_merge: 229 return handleMergeAction(Ctx); 230 case OPT_extract: 231 return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract); 232 case OPT_remove: 233 return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove); 234 } 235 236 return EXIT_SUCCESS; 237 } 238