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 | #if !defined(_MSC_VER) && !defined(__MINGW32__) |
32 | #include <unistd.h> |
33 | #endif |
34 | |
35 | using namespace llvm; |
36 | using namespace MachO; |
37 | using namespace object; |
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 | bool DeletePrivate = false; |
71 | bool TraceLibs = false; |
72 | }; |
73 | |
74 | struct Context { |
75 | std::vector<std::string> Inputs; |
76 | StubOptions StubOpt; |
77 | std::unique_ptr<llvm::raw_fd_stream> OutStream; |
78 | FileType WriteFT = FileType::TBD_V5; |
79 | bool Compact = false; |
80 | Architecture Arch = AK_unknown; |
81 | }; |
82 | |
83 | // Use unique exit code to differentiate failures not directly caused from |
84 | // TextAPI operations. This is used for wrapping `compare` operations in |
85 | // automation and scripting. |
86 | const int NON_TAPI_EXIT_CODE = 2; |
87 | const std::string TOOLNAME = "llvm-readtapi" ; |
88 | ExitOnError ExitOnErr; |
89 | } // anonymous namespace |
90 | |
91 | // Handle error reporting in cases where `ExitOnError` is not used. |
92 | static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { |
93 | errs() << TOOLNAME << ": error: " << Message << "\n" ; |
94 | errs().flush(); |
95 | exit(status: ExitCode); |
96 | } |
97 | |
98 | // Handle warnings. |
99 | static void reportWarning(Twine Message) { |
100 | errs() << TOOLNAME << ": warning: " << Message << "\n" ; |
101 | } |
102 | |
103 | /// Get what the symlink points to. |
104 | /// This is a no-op on windows as it references POSIX level apis. |
105 | static void read_link(const Twine &Path, SmallVectorImpl<char> &Output) { |
106 | #if !defined(_MSC_VER) && !defined(__MINGW32__) |
107 | Output.clear(); |
108 | if (Path.isTriviallyEmpty()) |
109 | return; |
110 | |
111 | SmallString<PATH_MAX> Storage; |
112 | auto P = Path.toNullTerminatedStringRef(Out&: Storage); |
113 | SmallString<PATH_MAX> Result; |
114 | ssize_t Len; |
115 | if ((Len = ::readlink(path: P.data(), buf: Result.data(), PATH_MAX)) == -1) |
116 | reportError(Message: "unable to read symlink: " + Path); |
117 | Result.resize_for_overwrite(N: Len); |
118 | Output.swap(RHS&: Result); |
119 | #else |
120 | reportError("unable to read symlink on windows: " + Path); |
121 | #endif |
122 | } |
123 | |
124 | static std::unique_ptr<InterfaceFile> |
125 | getInterfaceFile(const StringRef Filename, bool ResetBanner = true) { |
126 | ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' " ); |
127 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = |
128 | MemoryBuffer::getFile(Filename); |
129 | if (BufferOrErr.getError()) |
130 | ExitOnErr(errorCodeToError(EC: BufferOrErr.getError())); |
131 | auto Buffer = std::move(*BufferOrErr); |
132 | |
133 | std::unique_ptr<InterfaceFile> IF; |
134 | switch (identify_magic(magic: Buffer->getBuffer())) { |
135 | case file_magic::macho_dynamically_linked_shared_lib: |
136 | LLVM_FALLTHROUGH; |
137 | case file_magic::macho_dynamically_linked_shared_lib_stub: |
138 | LLVM_FALLTHROUGH; |
139 | case file_magic::macho_universal_binary: |
140 | IF = ExitOnErr(DylibReader::get(Buffer: Buffer->getMemBufferRef())); |
141 | break; |
142 | case file_magic::tapi_file: |
143 | IF = ExitOnErr(TextAPIReader::get(InputBuffer: Buffer->getMemBufferRef())); |
144 | break; |
145 | default: |
146 | reportError(Message: Filename + ": unsupported file type" ); |
147 | } |
148 | |
149 | if (ResetBanner) |
150 | ExitOnErr.setBanner(TOOLNAME + ": error: " ); |
151 | return IF; |
152 | } |
153 | |
154 | static bool handleCompareAction(const Context &Ctx) { |
155 | if (Ctx.Inputs.size() != 2) |
156 | reportError(Message: "compare only supports two input files" , |
157 | /*ExitCode=*/NON_TAPI_EXIT_CODE); |
158 | |
159 | // Override default exit code. |
160 | ExitOnErr = ExitOnError(TOOLNAME + ": error: " , |
161 | /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); |
162 | auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); |
163 | auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); |
164 | |
165 | raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); |
166 | return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); |
167 | } |
168 | |
169 | static bool handleWriteAction(const Context &Ctx, |
170 | std::unique_ptr<InterfaceFile> Out = nullptr) { |
171 | if (!Out) { |
172 | if (Ctx.Inputs.size() != 1) |
173 | reportError(Message: "write only supports one input file" ); |
174 | Out = getInterfaceFile(Ctx.Inputs.front()); |
175 | } |
176 | raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); |
177 | ExitOnErr(TextAPIWriter::writeToStream(OS, File: *Out, FileKind: Ctx.WriteFT, Compact: Ctx.Compact)); |
178 | return EXIT_SUCCESS; |
179 | } |
180 | |
181 | static bool handleMergeAction(const Context &Ctx) { |
182 | if (Ctx.Inputs.size() < 2) |
183 | reportError(Message: "merge requires at least two input files" ); |
184 | |
185 | std::unique_ptr<InterfaceFile> Out; |
186 | for (StringRef FileName : Ctx.Inputs) { |
187 | auto IF = getInterfaceFile(FileName); |
188 | // On the first iteration copy the input file and skip merge. |
189 | if (!Out) { |
190 | Out = std::move(IF); |
191 | continue; |
192 | } |
193 | Out = ExitOnErr(Out->merge(IF.get())); |
194 | } |
195 | return handleWriteAction(Ctx, Out: std::move(Out)); |
196 | } |
197 | |
198 | static void stubifyImpl(std::unique_ptr<InterfaceFile> IF, Context &Ctx) { |
199 | // TODO: Add inlining and magic merge support. |
200 | if (Ctx.OutStream == nullptr) { |
201 | std::error_code EC; |
202 | assert(!IF->getPath().empty() && "Unknown output location" ); |
203 | SmallString<PATH_MAX> OutputLoc = IF->getPath(); |
204 | replace_extension(Path&: OutputLoc, Extension: ".tbd" ); |
205 | Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(args&: OutputLoc, args&: EC); |
206 | if (EC) |
207 | reportError(Message: "opening file '" + OutputLoc + ": " + EC.message()); |
208 | } |
209 | |
210 | handleWriteAction(Ctx, Out: std::move(IF)); |
211 | // Clear out output stream after file has been written incase more files are |
212 | // stubifed. |
213 | Ctx.OutStream = nullptr; |
214 | } |
215 | |
216 | static void stubifyDirectory(const StringRef InputPath, Context &Ctx) { |
217 | assert(InputPath.back() != '/' && "Unexpected / at end of input path." ); |
218 | StringMap<std::vector<SymLink>> SymLinks; |
219 | StringMap<std::unique_ptr<InterfaceFile>> Dylibs; |
220 | StringMap<std::string> OriginalNames; |
221 | std::set<std::pair<std::string, bool>> LibsToDelete; |
222 | |
223 | std::error_code EC; |
224 | for (sys::fs::recursive_directory_iterator IT(InputPath, EC), IE; IT != IE; |
225 | IT.increment(ec&: EC)) { |
226 | if (EC == std::errc::no_such_file_or_directory) { |
227 | reportWarning(Message: IT->path() + ": " + EC.message()); |
228 | continue; |
229 | } |
230 | if (EC) |
231 | reportError(Message: IT->path() + ": " + EC.message()); |
232 | |
233 | // Skip header directories (include/Headers/PrivateHeaders) and module |
234 | // files. |
235 | StringRef Path = IT->path(); |
236 | if (Path.ends_with(Suffix: "/include" ) || Path.ends_with(Suffix: "/Headers" ) || |
237 | Path.ends_with(Suffix: "/PrivateHeaders" ) || Path.ends_with(Suffix: "/Modules" ) || |
238 | Path.ends_with(Suffix: ".map" ) || Path.ends_with(Suffix: ".modulemap" )) { |
239 | IT.no_push(); |
240 | continue; |
241 | } |
242 | |
243 | // Check if the entry is a symlink. We don't follow symlinks but we record |
244 | // their content. |
245 | bool IsSymLink; |
246 | if (auto EC = sys::fs::is_symlink_file(path: Path, result&: IsSymLink)) |
247 | reportError(Message: Path + ": " + EC.message()); |
248 | |
249 | if (IsSymLink) { |
250 | IT.no_push(); |
251 | |
252 | bool ShouldSkip; |
253 | auto SymLinkEC = shouldSkipSymLink(Path, Result&: ShouldSkip); |
254 | |
255 | // If symlink is broken, for some reason, we should continue |
256 | // trying to repair it before quitting. |
257 | if (!SymLinkEC && ShouldSkip) |
258 | continue; |
259 | |
260 | if (Ctx.StubOpt.DeletePrivate && |
261 | isPrivateLibrary(Path: Path.drop_front(N: InputPath.size()), IsSymLink: true)) { |
262 | LibsToDelete.emplace(args&: Path, args: false); |
263 | continue; |
264 | } |
265 | |
266 | SmallString<PATH_MAX> SymPath; |
267 | read_link(Path, Output&: SymPath); |
268 | // Sometimes there are broken symlinks that are absolute paths, which are |
269 | // invalid during build time, but would be correct during runtime. In the |
270 | // case of an absolute path we should check first if the path exists with |
271 | // the known locations as prefix. |
272 | SmallString<PATH_MAX> LinkSrc = Path; |
273 | SmallString<PATH_MAX> LinkTarget; |
274 | if (sys::path::is_absolute(path: SymPath)) { |
275 | LinkTarget = InputPath; |
276 | sys::path::append(path&: LinkTarget, a: SymPath); |
277 | |
278 | // TODO: Investigate supporting a file manager for file system accesses. |
279 | if (sys::fs::exists(Path: LinkTarget)) { |
280 | // Convert the absolute path to an relative path. |
281 | if (auto ec = MachO::make_relative(From: LinkSrc, To: LinkTarget, RelativePath&: SymPath)) |
282 | reportError(Message: LinkTarget + ": " + EC.message()); |
283 | } else if (!sys::fs::exists(Path: SymPath)) { |
284 | reportWarning(Message: "ignoring broken symlink: " + Path); |
285 | continue; |
286 | } else { |
287 | LinkTarget = SymPath; |
288 | } |
289 | } else { |
290 | LinkTarget = LinkSrc; |
291 | sys::path::remove_filename(path&: LinkTarget); |
292 | sys::path::append(path&: LinkTarget, a: SymPath); |
293 | } |
294 | |
295 | // For Apple SDKs, the symlink src is guaranteed to be a canonical path |
296 | // because we don't follow symlinks when scanning. The symlink target is |
297 | // constructed from the symlink path and needs to be canonicalized. |
298 | if (auto ec = sys::fs::real_path(path: Twine(LinkTarget), output&: LinkTarget)) { |
299 | reportWarning(Message: LinkTarget + ": " + ec.message()); |
300 | continue; |
301 | } |
302 | |
303 | auto itr = SymLinks.insert(KV: {LinkTarget.c_str(), std::vector<SymLink>()}); |
304 | itr.first->second.emplace_back(args: LinkSrc.str(), args: std::string(SymPath.str())); |
305 | |
306 | continue; |
307 | } |
308 | |
309 | bool IsDirectory = false; |
310 | if (auto EC = sys::fs::is_directory(path: Path, result&: IsDirectory)) |
311 | reportError(Message: Path + ": " + EC.message()); |
312 | if (IsDirectory) |
313 | continue; |
314 | |
315 | if (Ctx.StubOpt.DeletePrivate && |
316 | isPrivateLibrary(Path: Path.drop_front(N: InputPath.size()))) { |
317 | IT.no_push(); |
318 | LibsToDelete.emplace(args&: Path, args: false); |
319 | continue; |
320 | } |
321 | auto IF = getInterfaceFile(Filename: Path); |
322 | if (Ctx.StubOpt.TraceLibs) |
323 | errs() << Path << "\n" ; |
324 | |
325 | // Normalize path for map lookup by removing the extension. |
326 | SmallString<PATH_MAX> NormalizedPath(Path); |
327 | replace_extension(Path&: NormalizedPath, Extension: "" ); |
328 | |
329 | if ((IF->getFileType() == FileType::MachO_DynamicLibrary) || |
330 | (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) { |
331 | OriginalNames[NormalizedPath.c_str()] = IF->getPath(); |
332 | |
333 | // Don't add this MachO dynamic library because we already have a |
334 | // text-based stub recorded for this path. |
335 | if (Dylibs.count(Key: NormalizedPath.c_str())) |
336 | continue; |
337 | } |
338 | |
339 | Dylibs[NormalizedPath.c_str()] = std::move(IF); |
340 | } |
341 | |
342 | for (auto &Lib : Dylibs) { |
343 | auto &Dylib = Lib.second; |
344 | // Get the original file name. |
345 | SmallString<PATH_MAX> NormalizedPath(Dylib->getPath()); |
346 | stubifyImpl(IF: std::move(Dylib), Ctx); |
347 | |
348 | replace_extension(Path&: NormalizedPath, Extension: "" ); |
349 | auto Found = OriginalNames.find(Key: NormalizedPath.c_str()); |
350 | if (Found == OriginalNames.end()) |
351 | continue; |
352 | |
353 | if (Ctx.StubOpt.DeleteInput) |
354 | LibsToDelete.emplace(args&: Found->second, args: true); |
355 | |
356 | // Don't allow for more than 20 levels of symlinks when searching for |
357 | // libraries to stubify. |
358 | StringRef LibToCheck = Found->second; |
359 | for (int i = 0; i < 20; ++i) { |
360 | auto LinkIt = SymLinks.find(Key: LibToCheck.str()); |
361 | if (LinkIt != SymLinks.end()) { |
362 | for (auto &SymInfo : LinkIt->second) { |
363 | SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath); |
364 | SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent); |
365 | replace_extension(Path&: LinkSrc, Extension: "tbd" ); |
366 | replace_extension(Path&: LinkTarget, Extension: "tbd" ); |
367 | |
368 | if (auto EC = sys::fs::remove(path: LinkSrc)) |
369 | reportError(Message: LinkSrc + " : " + EC.message()); |
370 | |
371 | if (auto EC = sys::fs::create_link(to: LinkTarget, from: LinkSrc)) |
372 | reportError(Message: LinkTarget + " : " + EC.message()); |
373 | |
374 | if (Ctx.StubOpt.DeleteInput) |
375 | LibsToDelete.emplace(args&: SymInfo.SrcPath, args: true); |
376 | |
377 | LibToCheck = SymInfo.SrcPath; |
378 | } |
379 | } else |
380 | break; |
381 | } |
382 | } |
383 | |
384 | // Recursively delete the directories. This will abort when they are not empty |
385 | // or we reach the root of the SDK. |
386 | for (const auto &[LibPath, IsInput] : LibsToDelete) { |
387 | if (!IsInput && SymLinks.count(Key: LibPath)) |
388 | continue; |
389 | |
390 | if (auto EC = sys::fs::remove(path: LibPath)) |
391 | reportError(Message: LibPath + " : " + EC.message()); |
392 | |
393 | std::error_code EC; |
394 | auto Dir = sys::path::parent_path(path: LibPath); |
395 | do { |
396 | EC = sys::fs::remove(path: Dir); |
397 | Dir = sys::path::parent_path(path: Dir); |
398 | if (!Dir.starts_with(Prefix: InputPath)) |
399 | break; |
400 | } while (!EC); |
401 | } |
402 | } |
403 | |
404 | static bool handleStubifyAction(Context &Ctx) { |
405 | if (Ctx.Inputs.empty()) |
406 | reportError(Message: "stubify requires at least one input file" ); |
407 | |
408 | if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr)) |
409 | reportError(Message: "cannot write multiple inputs into single output file" ); |
410 | |
411 | for (StringRef PathName : Ctx.Inputs) { |
412 | bool IsDirectory = false; |
413 | if (auto EC = sys::fs::is_directory(PathName, IsDirectory)) |
414 | reportError(PathName + ": " + EC.message()); |
415 | |
416 | if (IsDirectory) { |
417 | if (Ctx.OutStream != nullptr) |
418 | reportError("cannot stubify directory'" + PathName + |
419 | "' into single output file" ); |
420 | stubifyDirectory(PathName, Ctx); |
421 | continue; |
422 | } |
423 | |
424 | stubifyImpl(getInterfaceFile(PathName), Ctx); |
425 | if (Ctx.StubOpt.DeleteInput) |
426 | if (auto ec = sys::fs::remove(PathName)) |
427 | reportError("deleting file '" + PathName + ": " + ec.message()); |
428 | } |
429 | return EXIT_SUCCESS; |
430 | } |
431 | |
432 | using IFOperation = |
433 | std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>( |
434 | const llvm::MachO::InterfaceFile &, Architecture)>; |
435 | static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, |
436 | IFOperation act) { |
437 | if (Ctx.Inputs.size() != 1) |
438 | reportError(Message: Action + " only supports one input file" ); |
439 | if (Ctx.Arch == AK_unknown) |
440 | reportError(Message: Action + " requires -arch <arch>" ); |
441 | |
442 | auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); |
443 | auto OutIF = act(*IF, Ctx.Arch); |
444 | if (!OutIF) |
445 | ExitOnErr(OutIF.takeError()); |
446 | |
447 | return handleWriteAction(Ctx, std::move(*OutIF)); |
448 | } |
449 | |
450 | static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) { |
451 | Opt.DeleteInput = Args.hasArg(OPT_delete_input); |
452 | Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries); |
453 | Opt.TraceLibs = Args.hasArg(OPT_t); |
454 | } |
455 | |
456 | int main(int Argc, char **Argv) { |
457 | InitLLVM X(Argc, Argv); |
458 | BumpPtrAllocator A; |
459 | StringSaver Saver(A); |
460 | TAPIOptTable Tbl; |
461 | Context Ctx; |
462 | ExitOnErr.setBanner(TOOLNAME + ": error:" ); |
463 | opt::InputArgList Args = Tbl.parseArgs( |
464 | Argc, Argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) { reportError(Message: Msg); }); |
465 | if (Args.hasArg(OPT_help)) { |
466 | Tbl.printHelp(OS&: outs(), |
467 | Usage: "USAGE: llvm-readtapi <command> [-arch <architecture> " |
468 | "<options>]* <inputs> [-o " |
469 | "<output>]*" , |
470 | Title: "LLVM TAPI file reader and transformer" ); |
471 | return EXIT_SUCCESS; |
472 | } |
473 | |
474 | if (Args.hasArg(OPT_version)) { |
475 | cl::PrintVersionMessage(); |
476 | return EXIT_SUCCESS; |
477 | } |
478 | |
479 | for (opt::Arg *A : Args.filtered(OPT_INPUT)) |
480 | Ctx.Inputs.push_back(A->getValue()); |
481 | |
482 | if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { |
483 | std::string OutputLoc = std::move(A->getValue()); |
484 | std::error_code EC; |
485 | Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(args&: OutputLoc, args&: EC); |
486 | if (EC) |
487 | reportError(Message: "error opening the file '" + OutputLoc + EC.message(), |
488 | ExitCode: NON_TAPI_EXIT_CODE); |
489 | } |
490 | |
491 | Ctx.Compact = Args.hasArg(OPT_compact); |
492 | |
493 | if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { |
494 | StringRef FT = A->getValue(); |
495 | Ctx.WriteFT = TextAPIWriter::parseFileType(FT); |
496 | if (Ctx.WriteFT < FileType::TBD_V3) |
497 | reportError(Message: "deprecated filetype '" + FT + "' is not supported to write" ); |
498 | if (Ctx.WriteFT == FileType::Invalid) |
499 | reportError(Message: "unsupported filetype '" + FT + "'" ); |
500 | } |
501 | |
502 | if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) { |
503 | StringRef Arch = A->getValue(); |
504 | Ctx.Arch = getArchitectureFromName(Name: Arch); |
505 | if (Ctx.Arch == AK_unknown) |
506 | reportError(Message: "unsupported architecture '" + Arch); |
507 | } |
508 | // Handle top level and exclusive operation. |
509 | SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); |
510 | |
511 | if (ActionArgs.empty()) |
512 | // If no action specified, write out tapi file in requested format. |
513 | return handleWriteAction(Ctx); |
514 | |
515 | if (ActionArgs.size() > 1) { |
516 | std::string Buf; |
517 | raw_string_ostream OS(Buf); |
518 | OS << "only one of the following actions can be specified:" ; |
519 | for (auto *Arg : ActionArgs) |
520 | OS << " " << Arg->getSpelling(); |
521 | reportError(Message: OS.str()); |
522 | } |
523 | |
524 | switch (ActionArgs.front()->getOption().getID()) { |
525 | case OPT_compare: |
526 | return handleCompareAction(Ctx); |
527 | case OPT_merge: |
528 | return handleMergeAction(Ctx); |
529 | case OPT_extract: |
530 | return handleSingleFileAction(Ctx, Action: "extract" , act: &InterfaceFile::extract); |
531 | case OPT_remove: |
532 | return handleSingleFileAction(Ctx, Action: "remove" , act: &InterfaceFile::remove); |
533 | case OPT_stubify: |
534 | setStubOptions(Args, Opt&: Ctx.StubOpt); |
535 | return handleStubifyAction(Ctx); |
536 | } |
537 | |
538 | return EXIT_SUCCESS; |
539 | } |
540 | |