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 // Use unique exit code to differentiate failures not directly caused from 60 // TextAPI operations. This is used for wrapping `compare` operations in 61 // automation and scripting. 62 const int NON_TAPI_EXIT_CODE = 2; 63 const std::string TOOLNAME = "llvm-readtapi"; 64 ExitOnError ExitOnErr; 65 66 // Handle error reporting in cases where `ExitOnError` is not used. 67 void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { 68 errs() << TOOLNAME << ": error: " << Message << "\n"; 69 errs().flush(); 70 exit(ExitCode); 71 } 72 73 struct Context { 74 std::vector<std::string> Inputs; 75 std::unique_ptr<llvm::raw_fd_stream> OutStream; 76 FileType WriteFT = FileType::TBD_V5; 77 bool Compact = false; 78 }; 79 80 std::unique_ptr<InterfaceFile> getInterfaceFile(const StringRef Filename) { 81 ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' "); 82 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 83 MemoryBuffer::getFile(Filename); 84 if (BufferOrErr.getError()) 85 ExitOnErr(errorCodeToError(BufferOrErr.getError())); 86 Expected<std::unique_ptr<InterfaceFile>> IF = 87 TextAPIReader::get((*BufferOrErr)->getMemBufferRef()); 88 if (!IF) 89 ExitOnErr(IF.takeError()); 90 // Set Banner back. 91 ExitOnErr.setBanner(TOOLNAME + ": error: "); 92 return std::move(*IF); 93 } 94 95 bool handleCompareAction(const Context &Ctx) { 96 if (Ctx.Inputs.size() != 2) 97 reportError("compare only supports two input files", 98 /*ExitCode=*/NON_TAPI_EXIT_CODE); 99 100 // Override default exit code. 101 ExitOnErr = ExitOnError(TOOLNAME + ": error: ", 102 /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); 103 auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); 104 auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); 105 106 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 107 return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); 108 } 109 110 bool handleWriteAction(const Context &Ctx, 111 std::unique_ptr<InterfaceFile> Out = nullptr) { 112 if (!Out) { 113 if (Ctx.Inputs.size() != 1) 114 reportError("write only supports one input file"); 115 Out = getInterfaceFile(Ctx.Inputs.front()); 116 } 117 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 118 ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); 119 return EXIT_SUCCESS; 120 } 121 122 bool handleMergeAction(const Context &Ctx) { 123 if (Ctx.Inputs.size() < 2) 124 reportError("merge requires at least two input files"); 125 126 std::unique_ptr<InterfaceFile> Out; 127 for (StringRef FileName : Ctx.Inputs) { 128 auto IF = getInterfaceFile(FileName); 129 // On the first iteration copy the input file and skip merge. 130 if (!Out) { 131 Out = std::move(IF); 132 continue; 133 } 134 auto ResultIF = Out->merge(IF.get()); 135 if (!ResultIF) 136 ExitOnErr(ResultIF.takeError()); 137 Out = std::move(ResultIF.get()); 138 } 139 return handleWriteAction(Ctx, std::move(Out)); 140 } 141 142 } // anonymous namespace 143 144 int main(int Argc, char **Argv) { 145 InitLLVM X(Argc, Argv); 146 BumpPtrAllocator A; 147 StringSaver Saver(A); 148 TAPIOptTable Tbl; 149 Context Ctx; 150 ExitOnErr.setBanner(TOOLNAME + ": error:"); 151 opt::InputArgList Args = Tbl.parseArgs( 152 Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); 153 if (Args.hasArg(OPT_help)) { 154 Tbl.printHelp(outs(), "llvm-readtapi [options] <inputs>", 155 "LLVM TAPI file reader and manipulator"); 156 return EXIT_SUCCESS; 157 } 158 159 for (opt::Arg *A : Args.filtered(OPT_INPUT)) 160 Ctx.Inputs.push_back(A->getValue()); 161 162 if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { 163 std::string OutputLoc = std::move(A->getValue()); 164 std::error_code EC; 165 Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 166 if (EC) 167 reportError("error opening the file '" + OutputLoc + EC.message(), 168 NON_TAPI_EXIT_CODE); 169 } 170 171 Ctx.Compact = Args.hasArg(OPT_compact); 172 173 if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { 174 StringRef FT = A->getValue(); 175 Ctx.WriteFT = TextAPIWriter::parseFileType(FT); 176 if (Ctx.WriteFT < FileType::TBD_V3) 177 reportError("deprecated filetype '" + FT + "' is not supported to write"); 178 if (Ctx.WriteFT == FileType::Invalid) 179 reportError("unsupported filetype '" + FT + "'"); 180 } 181 182 // Handle top level and exclusive operation. 183 SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); 184 185 if (ActionArgs.empty()) 186 // If no action specified, write out tapi file in requested format. 187 return handleWriteAction(Ctx); 188 189 if (ActionArgs.size() > 1) { 190 std::string Buf; 191 raw_string_ostream OS(Buf); 192 OS << "only one of the following actions can be specified:"; 193 for (auto *Arg : ActionArgs) 194 OS << " " << Arg->getSpelling(); 195 reportError(OS.str()); 196 } 197 198 switch (ActionArgs.front()->getOption().getID()) { 199 case OPT_compare: 200 return handleCompareAction(Ctx); 201 case OPT_merge: 202 return handleMergeAction(Ctx); 203 } 204 205 return EXIT_SUCCESS; 206 } 207