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