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