1 //===-- llvm-readtapi.cpp - tapi file reader and transformer -----*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines the command-line driver for llvm-readtapi.
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"
31 #if !defined(_MSC_VER) && !defined(__MINGW32__)
36 using namespace MachO
;
37 using namespace object
;
40 using namespace llvm::opt
;
42 OPT_INVALID
= 0, // This is not an option ID.
43 #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
44 #include "TapiOpts.inc"
48 #define OPTTABLE_STR_TABLE_CODE
49 #include "TapiOpts.inc"
50 #undef OPTTABLE_STR_TABLE_CODE
52 #define OPTTABLE_PREFIXES_TABLE_CODE
53 #include "TapiOpts.inc"
54 #undef OPTTABLE_PREFIXES_TABLE_CODE
56 static constexpr opt::OptTable::Info InfoTable
[] = {
57 #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
58 #include "TapiOpts.inc"
62 class TAPIOptTable
: public opt::GenericOptTable
{
65 : opt::GenericOptTable(OptionStrTable
, OptionPrefixesTable
, InfoTable
) {
66 setGroupedShortOptions(true);
71 bool DeleteInput
= false;
72 bool DeletePrivate
= false;
73 bool TraceLibs
= false;
76 struct CompareOptions
{
77 ArchitectureSet ArchsToIgnore
;
81 std::vector
<std::string
> Inputs
;
83 CompareOptions CmpOpt
;
84 std::unique_ptr
<llvm::raw_fd_stream
> OutStream
;
85 FileType WriteFT
= FileType::TBD_V5
;
87 Architecture Arch
= AK_unknown
;
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
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";
106 static void reportWarning(Twine Message
) {
107 errs() << TOOLNAME
<< ": warning: " << Message
<< "\n";
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__)
115 if (Path
.isTriviallyEmpty())
118 SmallString
<PATH_MAX
> Storage
;
119 auto P
= Path
.toNullTerminatedStringRef(Storage
);
120 SmallString
<PATH_MAX
> Result
;
122 if ((Len
= ::readlink(P
.data(), Result
.data(), PATH_MAX
)) == -1)
123 reportError("unable to read symlink: " + Path
);
124 Result
.resize_for_overwrite(Len
);
127 reportError("unable to read symlink on windows: " + Path
);
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(BufferOrErr
.getError()));
138 auto Buffer
= std::move(*BufferOrErr
);
140 std::unique_ptr
<InterfaceFile
> IF
;
141 switch (identify_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
->getMemBufferRef()));
147 case file_magic::tapi_file
:
148 IF
= ExitOnErr(TextAPIReader::get(Buffer
->getMemBufferRef()));
151 reportError(Filename
+ ": unsupported file type");
155 ExitOnErr
.setBanner(TOOLNAME
+ ": error: ");
159 static bool handleCompareAction(const Context
&Ctx
) {
160 if (Ctx
.Inputs
.size() != 2)
161 reportError("compare only supports two input files",
162 /*ExitCode=*/NON_TAPI_EXIT_CODE
);
164 // Override default exit code.
165 ExitOnErr
= ExitOnError(TOOLNAME
+ ": error: ",
166 /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE
);
167 auto LeftIF
= getInterfaceFile(Ctx
.Inputs
.front());
168 auto RightIF
= getInterfaceFile(Ctx
.Inputs
.at(1));
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(ArchToRemove
))
175 if (ArchSet
.count() == 1)
177 auto OutIF
= IF
->remove(ArchToRemove
);
179 ExitOnErr(OutIF
.takeError());
180 IF
= std::move(*OutIF
);
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
);
192 raw_ostream
&OS
= Ctx
.OutStream
? *Ctx
.OutStream
: outs();
193 return DiffEngine(LeftIF
.get(), RightIF
.get()).compareFiles(OS
);
196 static bool handleWriteAction(const Context
&Ctx
,
197 std::unique_ptr
<InterfaceFile
> Out
= nullptr) {
199 if (Ctx
.Inputs
.size() != 1)
200 reportError("write only supports one input file");
201 Out
= getInterfaceFile(Ctx
.Inputs
.front());
203 raw_ostream
&OS
= Ctx
.OutStream
? *Ctx
.OutStream
: outs();
204 ExitOnErr(TextAPIWriter::writeToStream(OS
, *Out
, Ctx
.WriteFT
, Ctx
.Compact
));
208 static bool handleMergeAction(const Context
&Ctx
) {
209 if (Ctx
.Inputs
.size() < 2)
210 reportError("merge requires at least two input files");
212 std::unique_ptr
<InterfaceFile
> Out
;
213 for (StringRef FileName
: Ctx
.Inputs
) {
214 auto IF
= getInterfaceFile(FileName
);
215 // On the first iteration copy the input file and skip merge.
220 Out
= ExitOnErr(Out
->merge(IF
.get()));
222 return handleWriteAction(Ctx
, std::move(Out
));
225 static void stubifyImpl(std::unique_ptr
<InterfaceFile
> IF
, Context
&Ctx
) {
226 // TODO: Add inlining and magic merge support.
227 if (Ctx
.OutStream
== nullptr) {
229 assert(!IF
->getPath().empty() && "Unknown output location");
230 SmallString
<PATH_MAX
> OutputLoc
= IF
->getPath();
231 replace_extension(OutputLoc
, ".tbd");
232 Ctx
.OutStream
= std::make_unique
<llvm::raw_fd_stream
>(OutputLoc
, EC
);
234 reportError("opening file '" + OutputLoc
+ ": " + EC
.message());
237 handleWriteAction(Ctx
, std::move(IF
));
238 // Clear out output stream after file has been written incase more files are
240 Ctx
.OutStream
= nullptr;
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
;
251 for (sys::fs::recursive_directory_iterator
IT(InputPath
, EC
), IE
; IT
!= IE
;
253 if (EC
== std::errc::no_such_file_or_directory
) {
254 reportWarning(IT
->path() + ": " + EC
.message());
258 reportError(IT
->path() + ": " + EC
.message());
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
);
264 if ((Stem
== "include") || (Stem
== "Headers") ||
265 (Stem
== "PrivateHeaders") || (Stem
== "Modules")) {
271 // Skip module files too.
272 if (Path
.ends_with(".map") || Path
.ends_with(".modulemap"))
275 // Check if the entry is a symlink. We don't follow symlinks but we record
278 if (auto EC
= sys::fs::is_symlink_file(Path
, IsSymLink
))
279 reportError(Path
+ ": " + EC
.message());
285 auto SymLinkEC
= shouldSkipSymLink(Path
, ShouldSkip
);
287 // If symlink is broken, for some reason, we should continue
288 // trying to repair it before quitting.
289 if (!SymLinkEC
&& ShouldSkip
)
292 if (Ctx
.StubOpt
.DeletePrivate
&&
293 isPrivateLibrary(Path
.drop_front(InputPath
.size()), true)) {
294 LibsToDelete
.emplace(Path
, false);
298 SmallString
<PATH_MAX
> SymPath
;
299 read_link(Path
, 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(SymPath
)) {
307 LinkTarget
= InputPath
;
308 sys::path::append(LinkTarget
, SymPath
);
310 // TODO: Investigate supporting a file manager for file system accesses.
311 if (sys::fs::exists(LinkTarget
)) {
312 // Convert the absolute path to an relative path.
313 if (auto ec
= MachO::make_relative(LinkSrc
, LinkTarget
, SymPath
))
314 reportError(LinkTarget
+ ": " + EC
.message());
315 } else if (!sys::fs::exists(SymPath
)) {
316 reportWarning("ignoring broken symlink: " + Path
);
319 LinkTarget
= SymPath
;
322 LinkTarget
= LinkSrc
;
323 sys::path::remove_filename(LinkTarget
);
324 sys::path::append(LinkTarget
, SymPath
);
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(Twine(LinkTarget
), LinkTarget
)) {
331 reportWarning(LinkTarget
+ ": " + ec
.message());
335 SymLinks
[LinkTarget
.c_str()].emplace_back(LinkSrc
.str(),
336 std::string(SymPath
.str()));
341 bool IsDirectory
= false;
342 if (auto EC
= sys::fs::is_directory(Path
, IsDirectory
))
343 reportError(Path
+ ": " + EC
.message());
347 if (Ctx
.StubOpt
.DeletePrivate
&&
348 isPrivateLibrary(Path
.drop_front(InputPath
.size()))) {
350 LibsToDelete
.emplace(Path
, false);
353 auto IF
= getInterfaceFile(Path
);
354 if (Ctx
.StubOpt
.TraceLibs
)
355 errs() << Path
<< "\n";
357 // Normalize path for map lookup by removing the extension.
358 SmallString
<PATH_MAX
> NormalizedPath(Path
);
359 replace_extension(NormalizedPath
, "");
361 if ((IF
->getFileType() == FileType::MachO_DynamicLibrary
) ||
362 (IF
->getFileType() == FileType::MachO_DynamicLibrary_Stub
)) {
363 OriginalNames
[NormalizedPath
.c_str()] = IF
->getPath();
365 // Don't add this MachO dynamic library because we already have a
366 // text-based stub recorded for this path.
367 if (Dylibs
.count(NormalizedPath
.c_str()))
371 Dylibs
[NormalizedPath
.c_str()] = std::move(IF
);
374 for (auto &Lib
: Dylibs
) {
375 auto &Dylib
= Lib
.second
;
376 // Get the original file name.
377 SmallString
<PATH_MAX
> NormalizedPath(Dylib
->getPath());
378 stubifyImpl(std::move(Dylib
), Ctx
);
380 replace_extension(NormalizedPath
, "");
381 auto Found
= OriginalNames
.find(NormalizedPath
.c_str());
382 if (Found
== OriginalNames
.end())
385 if (Ctx
.StubOpt
.DeleteInput
)
386 LibsToDelete
.emplace(Found
->second
, true);
388 // Don't allow for more than 20 levels of symlinks when searching for
389 // libraries to stubify.
390 StringRef LibToCheck
= Found
->second
;
391 for (int i
= 0; i
< 20; ++i
) {
392 auto LinkIt
= SymLinks
.find(LibToCheck
);
393 if (LinkIt
!= SymLinks
.end()) {
394 for (auto &SymInfo
: LinkIt
->second
) {
395 SmallString
<PATH_MAX
> LinkSrc(SymInfo
.SrcPath
);
396 SmallString
<PATH_MAX
> LinkTarget(SymInfo
.LinkContent
);
397 replace_extension(LinkSrc
, "tbd");
398 replace_extension(LinkTarget
, "tbd");
400 if (auto EC
= sys::fs::remove(LinkSrc
))
401 reportError(LinkSrc
+ " : " + EC
.message());
403 if (auto EC
= sys::fs::create_link(LinkTarget
, LinkSrc
))
404 reportError(LinkTarget
+ " : " + EC
.message());
406 if (Ctx
.StubOpt
.DeleteInput
)
407 LibsToDelete
.emplace(SymInfo
.SrcPath
, true);
409 LibToCheck
= SymInfo
.SrcPath
;
416 // Recursively delete the directories. This will abort when they are not empty
417 // or we reach the root of the SDK.
418 for (const auto &[LibPath
, IsInput
] : LibsToDelete
) {
419 if (!IsInput
&& SymLinks
.count(LibPath
))
422 if (auto EC
= sys::fs::remove(LibPath
))
423 reportError(LibPath
+ " : " + EC
.message());
426 auto Dir
= sys::path::parent_path(LibPath
);
428 EC
= sys::fs::remove(Dir
);
429 Dir
= sys::path::parent_path(Dir
);
430 if (!Dir
.starts_with(InputPath
))
436 static bool handleStubifyAction(Context
&Ctx
) {
437 if (Ctx
.Inputs
.empty())
438 reportError("stubify requires at least one input file");
440 if ((Ctx
.Inputs
.size() > 1) && (Ctx
.OutStream
!= nullptr))
441 reportError("cannot write multiple inputs into single output file");
443 for (StringRef PathName
: Ctx
.Inputs
) {
444 bool IsDirectory
= false;
445 if (auto EC
= sys::fs::is_directory(PathName
, IsDirectory
))
446 reportError(PathName
+ ": " + EC
.message());
449 if (Ctx
.OutStream
!= nullptr)
450 reportError("cannot stubify directory'" + PathName
+
451 "' into single output file");
452 stubifyDirectory(PathName
, Ctx
);
456 stubifyImpl(getInterfaceFile(PathName
), Ctx
);
457 if (Ctx
.StubOpt
.DeleteInput
)
458 if (auto ec
= sys::fs::remove(PathName
))
459 reportError("deleting file '" + PathName
+ ": " + ec
.message());
465 std::function
<llvm::Expected
<std::unique_ptr
<InterfaceFile
>>(
466 const llvm::MachO::InterfaceFile
&, Architecture
)>;
467 static bool handleSingleFileAction(const Context
&Ctx
, const StringRef Action
,
469 if (Ctx
.Inputs
.size() != 1)
470 reportError(Action
+ " only supports one input file");
471 if (Ctx
.Arch
== AK_unknown
)
472 reportError(Action
+ " requires -arch <arch>");
474 auto IF
= getInterfaceFile(Ctx
.Inputs
.front(), /*ResetBanner=*/false);
475 auto OutIF
= act(*IF
, Ctx
.Arch
);
477 ExitOnErr(OutIF
.takeError());
479 return handleWriteAction(Ctx
, std::move(*OutIF
));
482 static void setStubOptions(opt::InputArgList
&Args
, StubOptions
&Opt
) {
483 Opt
.DeleteInput
= Args
.hasArg(OPT_delete_input
);
484 Opt
.DeletePrivate
= Args
.hasArg(OPT_delete_private_libraries
);
485 Opt
.TraceLibs
= Args
.hasArg(OPT_t
);
488 int main(int Argc
, char **Argv
) {
489 InitLLVM
X(Argc
, Argv
);
491 StringSaver
Saver(A
);
494 ExitOnErr
.setBanner(TOOLNAME
+ ": error:");
495 opt::InputArgList Args
= Tbl
.parseArgs(
496 Argc
, Argv
, OPT_UNKNOWN
, Saver
, [&](StringRef Msg
) { reportError(Msg
); });
497 if (Args
.hasArg(OPT_help
)) {
498 Tbl
.printHelp(outs(),
499 "USAGE: llvm-readtapi <command> [-arch <architecture> "
500 "<options>]* <inputs> [-o "
502 "LLVM TAPI file reader and transformer");
506 if (Args
.hasArg(OPT_version
)) {
507 cl::PrintVersionMessage();
511 for (opt::Arg
*A
: Args
.filtered(OPT_INPUT
))
512 Ctx
.Inputs
.push_back(A
->getValue());
514 if (opt::Arg
*A
= Args
.getLastArg(OPT_output_EQ
)) {
515 std::string OutputLoc
= std::move(A
->getValue());
517 Ctx
.OutStream
= std::make_unique
<llvm::raw_fd_stream
>(OutputLoc
, EC
);
519 reportError("error opening the file '" + OutputLoc
+ EC
.message(),
523 Ctx
.Compact
= Args
.hasArg(OPT_compact
);
525 if (opt::Arg
*A
= Args
.getLastArg(OPT_filetype_EQ
)) {
526 StringRef FT
= A
->getValue();
527 Ctx
.WriteFT
= TextAPIWriter::parseFileType(FT
);
528 if (Ctx
.WriteFT
< FileType::TBD_V3
)
529 reportError("deprecated filetype '" + FT
+ "' is not supported to write");
530 if (Ctx
.WriteFT
== FileType::Invalid
)
531 reportError("unsupported filetype '" + FT
+ "'");
534 auto SanitizeArch
= [&](opt::Arg
*A
) {
535 StringRef ArchStr
= A
->getValue();
536 auto Arch
= getArchitectureFromName(ArchStr
);
537 if (Arch
== AK_unknown
)
538 reportError("unsupported architecture '" + ArchStr
);
542 if (opt::Arg
*A
= Args
.getLastArg(OPT_arch_EQ
))
543 Ctx
.Arch
= SanitizeArch(A
);
545 for (opt::Arg
*A
: Args
.filtered(OPT_ignore_arch_EQ
))
546 Ctx
.CmpOpt
.ArchsToIgnore
.set(SanitizeArch(A
));
548 // Handle top level and exclusive operation.
549 SmallVector
<opt::Arg
*, 1> ActionArgs(Args
.filtered(OPT_action_group
));
551 if (ActionArgs
.empty())
552 // If no action specified, write out tapi file in requested format.
553 return handleWriteAction(Ctx
);
555 if (ActionArgs
.size() > 1) {
557 raw_string_ostream
OS(Buf
);
558 OS
<< "only one of the following actions can be specified:";
559 for (auto *Arg
: ActionArgs
)
560 OS
<< " " << Arg
->getSpelling();
561 reportError(OS
.str());
564 switch (ActionArgs
.front()->getOption().getID()) {
566 return handleCompareAction(Ctx
);
568 return handleMergeAction(Ctx
);
570 return handleSingleFileAction(Ctx
, "extract", &InterfaceFile::extract
);
572 return handleSingleFileAction(Ctx
, "remove", &InterfaceFile::remove
);
574 setStubOptions(Args
, Ctx
.StubOpt
);
575 return handleStubifyAction(Ctx
);