xref: /llvm-project/llvm/tools/llvm-readtapi/llvm-readtapi.cpp (revision c6f29dbb596ff0fd6d8b6445eabbf01f6d063c8c)
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     auto IFOrErr = DylibReader::get(Buffer->getMemBufferRef());
113     if (!IFOrErr)
114       ExitOnErr(IFOrErr.takeError());
115     IF = std::move(*IFOrErr);
116     break;
117   }
118   case file_magic::tapi_file: {
119     auto IFOrErr = TextAPIReader::get(Buffer->getMemBufferRef());
120     if (!IFOrErr)
121       ExitOnErr(IFOrErr.takeError());
122     IF = std::move(*IFOrErr);
123     break;
124   }
125   default:
126     reportError(Filename + ": unsupported file type");
127   }
128 
129   if (ResetBanner)
130     ExitOnErr.setBanner(TOOLNAME + ": error: ");
131   return IF;
132 }
133 
134 static bool handleCompareAction(const Context &Ctx) {
135   if (Ctx.Inputs.size() != 2)
136     reportError("compare only supports two input files",
137                 /*ExitCode=*/NON_TAPI_EXIT_CODE);
138 
139   // Override default exit code.
140   ExitOnErr = ExitOnError(TOOLNAME + ": error: ",
141                           /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE);
142   auto LeftIF = getInterfaceFile(Ctx.Inputs.front());
143   auto RightIF = getInterfaceFile(Ctx.Inputs.at(1));
144 
145   raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
146   return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS);
147 }
148 
149 static bool handleWriteAction(const Context &Ctx,
150                               std::unique_ptr<InterfaceFile> Out = nullptr) {
151   if (!Out) {
152     if (Ctx.Inputs.size() != 1)
153       reportError("write only supports one input file");
154     Out = getInterfaceFile(Ctx.Inputs.front());
155   }
156   raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
157   ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact));
158   return EXIT_SUCCESS;
159 }
160 
161 static bool handleMergeAction(const Context &Ctx) {
162   if (Ctx.Inputs.size() < 2)
163     reportError("merge requires at least two input files");
164 
165   std::unique_ptr<InterfaceFile> Out;
166   for (StringRef FileName : Ctx.Inputs) {
167     auto IF = getInterfaceFile(FileName);
168     // On the first iteration copy the input file and skip merge.
169     if (!Out) {
170       Out = std::move(IF);
171       continue;
172     }
173     auto ResultIF = Out->merge(IF.get());
174     if (!ResultIF)
175       ExitOnErr(ResultIF.takeError());
176     Out = std::move(ResultIF.get());
177   }
178   return handleWriteAction(Ctx, std::move(Out));
179 }
180 
181 static bool handleStubifyAction(Context &Ctx) {
182   if (Ctx.Inputs.empty())
183     reportError("stubify requires at least one input file");
184 
185   if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr))
186     reportError("cannot write multiple inputs into single output file");
187 
188   for (StringRef FileName : Ctx.Inputs) {
189     auto IF = getInterfaceFile(FileName);
190     if (Ctx.StubOpt.DeleteInput) {
191       std::error_code EC;
192       SmallString<PATH_MAX> OutputLoc = FileName;
193       MachO::replace_extension(OutputLoc, ".tbd");
194       Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
195       if (EC)
196         reportError("opening file '" + OutputLoc + ": " + EC.message());
197       if (auto Err = sys::fs::remove(FileName))
198         reportError("deleting file '" + FileName + ": " + EC.message());
199     }
200     handleWriteAction(Ctx, std::move(IF));
201   }
202   return EXIT_SUCCESS;
203 }
204 
205 using IFOperation =
206     std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>(
207         const llvm::MachO::InterfaceFile &, Architecture)>;
208 static bool handleSingleFileAction(const Context &Ctx, const StringRef Action,
209                                    IFOperation act) {
210   if (Ctx.Inputs.size() != 1)
211     reportError(Action + " only supports one input file");
212   if (Ctx.Arch == AK_unknown)
213     reportError(Action + " requires -arch <arch>");
214 
215   auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false);
216   auto OutIF = act(*IF, Ctx.Arch);
217   if (!OutIF)
218     ExitOnErr(OutIF.takeError());
219 
220   return handleWriteAction(Ctx, std::move(*OutIF));
221 }
222 
223 static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) {
224   Opt.DeleteInput = Args.hasArg(OPT_delete_input);
225 }
226 
227 int main(int Argc, char **Argv) {
228   InitLLVM X(Argc, Argv);
229   BumpPtrAllocator A;
230   StringSaver Saver(A);
231   TAPIOptTable Tbl;
232   Context Ctx;
233   ExitOnErr.setBanner(TOOLNAME + ": error:");
234   opt::InputArgList Args = Tbl.parseArgs(
235       Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); });
236   if (Args.hasArg(OPT_help)) {
237     Tbl.printHelp(outs(),
238                   "USAGE: llvm-readtapi <command> [-arch <architecture> "
239                   "<options>]* <inputs> [-o "
240                   "<output>]*",
241                   "LLVM TAPI file reader and transformer");
242     return EXIT_SUCCESS;
243   }
244 
245   if (Args.hasArg(OPT_version)) {
246     cl::PrintVersionMessage();
247     return EXIT_SUCCESS;
248   }
249 
250   // TODO: Add support for picking up libraries from directory input.
251   for (opt::Arg *A : Args.filtered(OPT_INPUT))
252     Ctx.Inputs.push_back(A->getValue());
253 
254   if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) {
255     std::string OutputLoc = std::move(A->getValue());
256     std::error_code EC;
257     Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
258     if (EC)
259       reportError("error opening the file '" + OutputLoc + EC.message(),
260                   NON_TAPI_EXIT_CODE);
261   }
262 
263   Ctx.Compact = Args.hasArg(OPT_compact);
264 
265   if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) {
266     StringRef FT = A->getValue();
267     Ctx.WriteFT = TextAPIWriter::parseFileType(FT);
268     if (Ctx.WriteFT < FileType::TBD_V3)
269       reportError("deprecated filetype '" + FT + "' is not supported to write");
270     if (Ctx.WriteFT == FileType::Invalid)
271       reportError("unsupported filetype '" + FT + "'");
272   }
273 
274   if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) {
275     StringRef Arch = A->getValue();
276     Ctx.Arch = getArchitectureFromName(Arch);
277     if (Ctx.Arch == AK_unknown)
278       reportError("unsupported architecture '" + Arch);
279   }
280   // Handle top level and exclusive operation.
281   SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group));
282 
283   if (ActionArgs.empty())
284     // If no action specified, write out tapi file in requested format.
285     return handleWriteAction(Ctx);
286 
287   if (ActionArgs.size() > 1) {
288     std::string Buf;
289     raw_string_ostream OS(Buf);
290     OS << "only one of the following actions can be specified:";
291     for (auto *Arg : ActionArgs)
292       OS << " " << Arg->getSpelling();
293     reportError(OS.str());
294   }
295 
296   switch (ActionArgs.front()->getOption().getID()) {
297   case OPT_compare:
298     return handleCompareAction(Ctx);
299   case OPT_merge:
300     return handleMergeAction(Ctx);
301   case OPT_extract:
302     return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract);
303   case OPT_remove:
304     return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove);
305   case OPT_stubify:
306     setStubOptions(Args, Ctx.StubOpt);
307     return handleStubifyAction(Ctx);
308   }
309 
310   return EXIT_SUCCESS;
311 }
312