xref: /llvm-project/llvm/tools/llvm-readtapi/llvm-readtapi.cpp (revision 86fa4b2c46191fe6be88e38bb46487472f6892c9)
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