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