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