xref: /llvm-project/llvm/tools/llvm-readtapi/llvm-readtapi.cpp (revision dd647e3e608ed0b2bac7c588d5859b80ef4a5976)
1142e567cSCyndy Ishida //===-- llvm-readtapi.cpp - tapi file reader and transformer -----*- C++-*-===//
25656d797SCyndy Ishida //
35656d797SCyndy Ishida // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45656d797SCyndy Ishida // See https://llvm.org/LICENSE.txt for license information.
55656d797SCyndy Ishida // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65656d797SCyndy Ishida //
75656d797SCyndy Ishida //===----------------------------------------------------------------------===//
85656d797SCyndy Ishida //
95656d797SCyndy Ishida // This file defines the command-line driver for llvm-readtapi.
105656d797SCyndy Ishida //
115656d797SCyndy Ishida //===----------------------------------------------------------------------===//
125656d797SCyndy Ishida #include "DiffEngine.h"
13c6f29dbbSCyndy Ishida #include "llvm/BinaryFormat/Magic.h"
14ec64af59SCyndy Ishida #include "llvm/Option/Arg.h"
15ec64af59SCyndy Ishida #include "llvm/Option/ArgList.h"
16ec64af59SCyndy Ishida #include "llvm/Option/Option.h"
175656d797SCyndy Ishida #include "llvm/Support/CommandLine.h"
185656d797SCyndy Ishida #include "llvm/Support/Error.h"
19c6f29dbbSCyndy Ishida #include "llvm/Support/FileSystem.h"
205656d797SCyndy Ishida #include "llvm/Support/InitLLVM.h"
215656d797SCyndy Ishida #include "llvm/Support/MemoryBuffer.h"
22c6f29dbbSCyndy Ishida #include "llvm/Support/Path.h"
235656d797SCyndy Ishida #include "llvm/Support/raw_ostream.h"
24c6f29dbbSCyndy Ishida #include "llvm/TextAPI/DylibReader.h"
25ec64af59SCyndy Ishida #include "llvm/TextAPI/TextAPIError.h"
26ae182dbbSCyndy Ishida #include "llvm/TextAPI/TextAPIReader.h"
27ae182dbbSCyndy Ishida #include "llvm/TextAPI/TextAPIWriter.h"
28c6f29dbbSCyndy Ishida #include "llvm/TextAPI/Utils.h"
295656d797SCyndy Ishida #include <cstdlib>
305656d797SCyndy Ishida 
317189219eSCyndy Ishida #if !defined(_MSC_VER) && !defined(__MINGW32__)
327189219eSCyndy Ishida #include <unistd.h>
337189219eSCyndy Ishida #endif
347189219eSCyndy Ishida 
355656d797SCyndy Ishida using namespace llvm;
365656d797SCyndy Ishida using namespace MachO;
375656d797SCyndy Ishida using namespace object;
385656d797SCyndy Ishida 
395656d797SCyndy Ishida namespace {
40ec64af59SCyndy Ishida using namespace llvm::opt;
41ec64af59SCyndy Ishida enum ID {
42ec64af59SCyndy Ishida   OPT_INVALID = 0, // This is not an option ID.
43ec64af59SCyndy Ishida #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
44ec64af59SCyndy Ishida #include "TapiOpts.inc"
45ec64af59SCyndy Ishida #undef OPTION
465656d797SCyndy Ishida };
475656d797SCyndy Ishida 
48*dd647e3eSChandler Carruth #define OPTTABLE_STR_TABLE_CODE
49ec64af59SCyndy Ishida #include "TapiOpts.inc"
50*dd647e3eSChandler Carruth #undef OPTTABLE_STR_TABLE_CODE
51*dd647e3eSChandler Carruth 
52*dd647e3eSChandler Carruth #define OPTTABLE_PREFIXES_TABLE_CODE
53*dd647e3eSChandler Carruth #include "TapiOpts.inc"
54*dd647e3eSChandler Carruth #undef OPTTABLE_PREFIXES_TABLE_CODE
555656d797SCyndy Ishida 
56ec64af59SCyndy Ishida static constexpr opt::OptTable::Info InfoTable[] = {
57ec64af59SCyndy Ishida #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
58ec64af59SCyndy Ishida #include "TapiOpts.inc"
59ec64af59SCyndy Ishida #undef OPTION
60ec64af59SCyndy Ishida };
61ec64af59SCyndy Ishida 
62ec64af59SCyndy Ishida class TAPIOptTable : public opt::GenericOptTable {
63ec64af59SCyndy Ishida public:
64*dd647e3eSChandler Carruth   TAPIOptTable()
65*dd647e3eSChandler Carruth       : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {
66ec64af59SCyndy Ishida     setGroupedShortOptions(true);
67ec64af59SCyndy Ishida   }
68ec64af59SCyndy Ishida };
69ec64af59SCyndy Ishida 
70c6f29dbbSCyndy Ishida struct StubOptions {
71c6f29dbbSCyndy Ishida   bool DeleteInput = false;
727189219eSCyndy Ishida   bool DeletePrivate = false;
737189219eSCyndy Ishida   bool TraceLibs = false;
74c6f29dbbSCyndy Ishida };
75c6f29dbbSCyndy Ishida 
7651340480SCyndy Ishida struct CompareOptions {
7751340480SCyndy Ishida   ArchitectureSet ArchsToIgnore;
7851340480SCyndy Ishida };
7951340480SCyndy Ishida 
80ec64af59SCyndy Ishida struct Context {
81ec64af59SCyndy Ishida   std::vector<std::string> Inputs;
827189219eSCyndy Ishida   StubOptions StubOpt;
8351340480SCyndy Ishida   CompareOptions CmpOpt;
84ec64af59SCyndy Ishida   std::unique_ptr<llvm::raw_fd_stream> OutStream;
85ae182dbbSCyndy Ishida   FileType WriteFT = FileType::TBD_V5;
86ae182dbbSCyndy Ishida   bool Compact = false;
87e1f69b86SCyndy Ishida   Architecture Arch = AK_unknown;
88ec64af59SCyndy Ishida };
89ec64af59SCyndy Ishida 
9086fa4b2cSCyndy Ishida // Use unique exit code to differentiate failures not directly caused from
9186fa4b2cSCyndy Ishida // TextAPI operations. This is used for wrapping `compare` operations in
9286fa4b2cSCyndy Ishida // automation and scripting.
9386fa4b2cSCyndy Ishida const int NON_TAPI_EXIT_CODE = 2;
9486fa4b2cSCyndy Ishida const std::string TOOLNAME = "llvm-readtapi";
9586fa4b2cSCyndy Ishida ExitOnError ExitOnErr;
9686fa4b2cSCyndy Ishida } // anonymous namespace
9786fa4b2cSCyndy Ishida 
9886fa4b2cSCyndy Ishida // Handle error reporting in cases where `ExitOnError` is not used.
9986fa4b2cSCyndy Ishida static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) {
10086fa4b2cSCyndy Ishida   errs() << TOOLNAME << ": error: " << Message << "\n";
10186fa4b2cSCyndy Ishida   errs().flush();
10286fa4b2cSCyndy Ishida   exit(ExitCode);
10386fa4b2cSCyndy Ishida }
10486fa4b2cSCyndy Ishida 
1057189219eSCyndy Ishida // Handle warnings.
1067189219eSCyndy Ishida static void reportWarning(Twine Message) {
1077189219eSCyndy Ishida   errs() << TOOLNAME << ": warning: " << Message << "\n";
1087189219eSCyndy Ishida }
1097189219eSCyndy Ishida 
1107189219eSCyndy Ishida /// Get what the symlink points to.
1117189219eSCyndy Ishida /// This is a no-op on windows as it references POSIX level apis.
1125bcd9105SCyndy Ishida static void read_link(const Twine &Path, SmallVectorImpl<char> &Output) {
1137189219eSCyndy Ishida #if !defined(_MSC_VER) && !defined(__MINGW32__)
1147189219eSCyndy Ishida   Output.clear();
1157189219eSCyndy Ishida   if (Path.isTriviallyEmpty())
1165bcd9105SCyndy Ishida     return;
1177189219eSCyndy Ishida 
1187189219eSCyndy Ishida   SmallString<PATH_MAX> Storage;
1197189219eSCyndy Ishida   auto P = Path.toNullTerminatedStringRef(Storage);
1207189219eSCyndy Ishida   SmallString<PATH_MAX> Result;
1217189219eSCyndy Ishida   ssize_t Len;
1227189219eSCyndy Ishida   if ((Len = ::readlink(P.data(), Result.data(), PATH_MAX)) == -1)
1235bcd9105SCyndy Ishida     reportError("unable to read symlink: " + Path);
1247189219eSCyndy Ishida   Result.resize_for_overwrite(Len);
1257189219eSCyndy Ishida   Output.swap(Result);
1267189219eSCyndy Ishida #else
1277189219eSCyndy Ishida   reportError("unable to read symlink on windows: " + Path);
1287189219eSCyndy Ishida #endif
1297189219eSCyndy Ishida }
1307189219eSCyndy Ishida 
13186fa4b2cSCyndy Ishida static std::unique_ptr<InterfaceFile>
13286fa4b2cSCyndy Ishida getInterfaceFile(const StringRef Filename, bool ResetBanner = true) {
133bc191ac3SCyndy Ishida   ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' ");
1345656d797SCyndy Ishida   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
13574dcf0b5SAbhina Sree       MemoryBuffer::getFile(Filename, /*IsText=*/true);
1365656d797SCyndy Ishida   if (BufferOrErr.getError())
137ae182dbbSCyndy Ishida     ExitOnErr(errorCodeToError(BufferOrErr.getError()));
138c6f29dbbSCyndy Ishida   auto Buffer = std::move(*BufferOrErr);
139c6f29dbbSCyndy Ishida 
140c6f29dbbSCyndy Ishida   std::unique_ptr<InterfaceFile> IF;
141c6f29dbbSCyndy Ishida   switch (identify_magic(Buffer->getBuffer())) {
142c6f29dbbSCyndy Ishida   case file_magic::macho_dynamically_linked_shared_lib:
143c6f29dbbSCyndy Ishida   case file_magic::macho_dynamically_linked_shared_lib_stub:
144b64992a3SCyndy Ishida   case file_magic::macho_universal_binary:
145b64992a3SCyndy Ishida     IF = ExitOnErr(DylibReader::get(Buffer->getMemBufferRef()));
146c6f29dbbSCyndy Ishida     break;
147b64992a3SCyndy Ishida   case file_magic::tapi_file:
148b64992a3SCyndy Ishida     IF = ExitOnErr(TextAPIReader::get(Buffer->getMemBufferRef()));
149c6f29dbbSCyndy Ishida     break;
150c6f29dbbSCyndy Ishida   default:
151c6f29dbbSCyndy Ishida     reportError(Filename + ": unsupported file type");
152c6f29dbbSCyndy Ishida   }
153c6f29dbbSCyndy Ishida 
154e1f69b86SCyndy Ishida   if (ResetBanner)
155bc191ac3SCyndy Ishida     ExitOnErr.setBanner(TOOLNAME + ": error: ");
156c6f29dbbSCyndy Ishida   return IF;
1575656d797SCyndy Ishida }
1585656d797SCyndy Ishida 
15986fa4b2cSCyndy Ishida static bool handleCompareAction(const Context &Ctx) {
160ae182dbbSCyndy Ishida   if (Ctx.Inputs.size() != 2)
161ae182dbbSCyndy Ishida     reportError("compare only supports two input files",
162ae182dbbSCyndy Ishida                 /*ExitCode=*/NON_TAPI_EXIT_CODE);
163ae182dbbSCyndy Ishida 
164bc191ac3SCyndy Ishida   // Override default exit code.
165bc191ac3SCyndy Ishida   ExitOnErr = ExitOnError(TOOLNAME + ": error: ",
166bc191ac3SCyndy Ishida                           /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE);
167bc191ac3SCyndy Ishida   auto LeftIF = getInterfaceFile(Ctx.Inputs.front());
168bc191ac3SCyndy Ishida   auto RightIF = getInterfaceFile(Ctx.Inputs.at(1));
1695656d797SCyndy Ishida 
17051340480SCyndy Ishida   // Remove all architectures to ignore before running comparison.
17151340480SCyndy Ishida   auto removeArchFromIF = [](auto &IF, const ArchitectureSet &ArchSet,
17251340480SCyndy Ishida                              const Architecture ArchToRemove) {
17351340480SCyndy Ishida     if (!ArchSet.has(ArchToRemove))
17451340480SCyndy Ishida       return;
17551340480SCyndy Ishida     if (ArchSet.count() == 1)
17651340480SCyndy Ishida       return;
17751340480SCyndy Ishida     auto OutIF = IF->remove(ArchToRemove);
17851340480SCyndy Ishida     if (!OutIF)
17951340480SCyndy Ishida       ExitOnErr(OutIF.takeError());
18051340480SCyndy Ishida     IF = std::move(*OutIF);
18151340480SCyndy Ishida   };
18251340480SCyndy Ishida 
18351340480SCyndy Ishida   if (!Ctx.CmpOpt.ArchsToIgnore.empty()) {
18451340480SCyndy Ishida     const ArchitectureSet LeftArchs = LeftIF->getArchitectures();
18551340480SCyndy Ishida     const ArchitectureSet RightArchs = RightIF->getArchitectures();
18651340480SCyndy Ishida     for (const auto Arch : Ctx.CmpOpt.ArchsToIgnore) {
18751340480SCyndy Ishida       removeArchFromIF(LeftIF, LeftArchs, Arch);
18851340480SCyndy Ishida       removeArchFromIF(RightIF, RightArchs, Arch);
18951340480SCyndy Ishida     }
19051340480SCyndy Ishida   }
19151340480SCyndy Ishida 
192ec64af59SCyndy Ishida   raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
193ae182dbbSCyndy Ishida   return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS);
194ae182dbbSCyndy Ishida }
195ae182dbbSCyndy Ishida 
19686fa4b2cSCyndy Ishida static bool handleWriteAction(const Context &Ctx,
197ae182dbbSCyndy Ishida                               std::unique_ptr<InterfaceFile> Out = nullptr) {
198ae182dbbSCyndy Ishida   if (!Out) {
199ae182dbbSCyndy Ishida     if (Ctx.Inputs.size() != 1)
200ae182dbbSCyndy Ishida       reportError("write only supports one input file");
201bc191ac3SCyndy Ishida     Out = getInterfaceFile(Ctx.Inputs.front());
202ae182dbbSCyndy Ishida   }
203ae182dbbSCyndy Ishida   raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
204ae182dbbSCyndy Ishida   ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact));
205ae182dbbSCyndy Ishida   return EXIT_SUCCESS;
206ae182dbbSCyndy Ishida }
207ae182dbbSCyndy Ishida 
20886fa4b2cSCyndy Ishida static bool handleMergeAction(const Context &Ctx) {
209ae182dbbSCyndy Ishida   if (Ctx.Inputs.size() < 2)
210ae182dbbSCyndy Ishida     reportError("merge requires at least two input files");
211ae182dbbSCyndy Ishida 
212ae182dbbSCyndy Ishida   std::unique_ptr<InterfaceFile> Out;
213ae182dbbSCyndy Ishida   for (StringRef FileName : Ctx.Inputs) {
214bc191ac3SCyndy Ishida     auto IF = getInterfaceFile(FileName);
215ae182dbbSCyndy Ishida     // On the first iteration copy the input file and skip merge.
216ae182dbbSCyndy Ishida     if (!Out) {
217ae182dbbSCyndy Ishida       Out = std::move(IF);
218ae182dbbSCyndy Ishida       continue;
219ae182dbbSCyndy Ishida     }
220b64992a3SCyndy Ishida     Out = ExitOnErr(Out->merge(IF.get()));
221ae182dbbSCyndy Ishida   }
222ae182dbbSCyndy Ishida   return handleWriteAction(Ctx, std::move(Out));
2235656d797SCyndy Ishida }
2245656d797SCyndy Ishida 
2257189219eSCyndy Ishida static void stubifyImpl(std::unique_ptr<InterfaceFile> IF, Context &Ctx) {
2267189219eSCyndy Ishida   // TODO: Add inlining and magic merge support.
2277189219eSCyndy Ishida   if (Ctx.OutStream == nullptr) {
2287189219eSCyndy Ishida     std::error_code EC;
2296eb7273bSCyndy Ishida     assert(!IF->getPath().empty() && "Unknown output location");
2307189219eSCyndy Ishida     SmallString<PATH_MAX> OutputLoc = IF->getPath();
2317189219eSCyndy Ishida     replace_extension(OutputLoc, ".tbd");
2327189219eSCyndy Ishida     Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
2337189219eSCyndy Ishida     if (EC)
2347189219eSCyndy Ishida       reportError("opening file '" + OutputLoc + ": " + EC.message());
2357189219eSCyndy Ishida   }
2367189219eSCyndy Ishida 
2377189219eSCyndy Ishida   handleWriteAction(Ctx, std::move(IF));
2387189219eSCyndy Ishida   // Clear out output stream after file has been written incase more files are
2397189219eSCyndy Ishida   // stubifed.
2407189219eSCyndy Ishida   Ctx.OutStream = nullptr;
2417189219eSCyndy Ishida }
2427189219eSCyndy Ishida 
2437189219eSCyndy Ishida static void stubifyDirectory(const StringRef InputPath, Context &Ctx) {
2447189219eSCyndy Ishida   assert(InputPath.back() != '/' && "Unexpected / at end of input path.");
2457189219eSCyndy Ishida   StringMap<std::vector<SymLink>> SymLinks;
2467189219eSCyndy Ishida   StringMap<std::unique_ptr<InterfaceFile>> Dylibs;
2477189219eSCyndy Ishida   StringMap<std::string> OriginalNames;
2487189219eSCyndy Ishida   std::set<std::pair<std::string, bool>> LibsToDelete;
2497189219eSCyndy Ishida 
2507189219eSCyndy Ishida   std::error_code EC;
2517189219eSCyndy Ishida   for (sys::fs::recursive_directory_iterator IT(InputPath, EC), IE; IT != IE;
2527189219eSCyndy Ishida        IT.increment(EC)) {
2537189219eSCyndy Ishida     if (EC == std::errc::no_such_file_or_directory) {
2547189219eSCyndy Ishida       reportWarning(IT->path() + ": " + EC.message());
2557189219eSCyndy Ishida       continue;
2567189219eSCyndy Ishida     }
2577189219eSCyndy Ishida     if (EC)
2587189219eSCyndy Ishida       reportError(IT->path() + ": " + EC.message());
2597189219eSCyndy Ishida 
2602d48489cSCyndy Ishida     // Skip header directories (include/Headers/PrivateHeaders).
2617189219eSCyndy Ishida     StringRef Path = IT->path();
2622d48489cSCyndy Ishida     if (sys::fs::is_directory(Path)) {
2632d48489cSCyndy Ishida       const StringRef Stem = sys::path::stem(Path);
2642d48489cSCyndy Ishida       if ((Stem == "include") || (Stem == "Headers") ||
2652d48489cSCyndy Ishida           (Stem == "PrivateHeaders") || (Stem == "Modules")) {
2667189219eSCyndy Ishida         IT.no_push();
2677189219eSCyndy Ishida         continue;
2687189219eSCyndy Ishida       }
2692d48489cSCyndy Ishida     }
2702d48489cSCyndy Ishida 
2712d48489cSCyndy Ishida     // Skip module files too.
2722d48489cSCyndy Ishida     if (Path.ends_with(".map") || Path.ends_with(".modulemap"))
2732d48489cSCyndy Ishida       continue;
2747189219eSCyndy Ishida 
2757189219eSCyndy Ishida     // Check if the entry is a symlink. We don't follow symlinks but we record
2767189219eSCyndy Ishida     // their content.
2777189219eSCyndy Ishida     bool IsSymLink;
2787189219eSCyndy Ishida     if (auto EC = sys::fs::is_symlink_file(Path, IsSymLink))
2797189219eSCyndy Ishida       reportError(Path + ": " + EC.message());
2807189219eSCyndy Ishida 
2817189219eSCyndy Ishida     if (IsSymLink) {
2827189219eSCyndy Ishida       IT.no_push();
2837189219eSCyndy Ishida 
2847189219eSCyndy Ishida       bool ShouldSkip;
2857189219eSCyndy Ishida       auto SymLinkEC = shouldSkipSymLink(Path, ShouldSkip);
2867189219eSCyndy Ishida 
2877189219eSCyndy Ishida       // If symlink is broken, for some reason, we should continue
2887189219eSCyndy Ishida       // trying to repair it before quitting.
2897189219eSCyndy Ishida       if (!SymLinkEC && ShouldSkip)
2907189219eSCyndy Ishida         continue;
2917189219eSCyndy Ishida 
2927189219eSCyndy Ishida       if (Ctx.StubOpt.DeletePrivate &&
2937189219eSCyndy Ishida           isPrivateLibrary(Path.drop_front(InputPath.size()), true)) {
2947189219eSCyndy Ishida         LibsToDelete.emplace(Path, false);
2957189219eSCyndy Ishida         continue;
2967189219eSCyndy Ishida       }
2977189219eSCyndy Ishida 
2987189219eSCyndy Ishida       SmallString<PATH_MAX> SymPath;
2995bcd9105SCyndy Ishida       read_link(Path, SymPath);
3007189219eSCyndy Ishida       // Sometimes there are broken symlinks that are absolute paths, which are
3017189219eSCyndy Ishida       // invalid during build time, but would be correct during runtime. In the
3027189219eSCyndy Ishida       // case of an absolute path we should check first if the path exists with
3037189219eSCyndy Ishida       // the known locations as prefix.
3047189219eSCyndy Ishida       SmallString<PATH_MAX> LinkSrc = Path;
3057189219eSCyndy Ishida       SmallString<PATH_MAX> LinkTarget;
3067189219eSCyndy Ishida       if (sys::path::is_absolute(SymPath)) {
3077189219eSCyndy Ishida         LinkTarget = InputPath;
3087189219eSCyndy Ishida         sys::path::append(LinkTarget, SymPath);
3097189219eSCyndy Ishida 
3107189219eSCyndy Ishida         // TODO: Investigate supporting a file manager for file system accesses.
3117189219eSCyndy Ishida         if (sys::fs::exists(LinkTarget)) {
3127189219eSCyndy Ishida           // Convert the absolute path to an relative path.
3137189219eSCyndy Ishida           if (auto ec = MachO::make_relative(LinkSrc, LinkTarget, SymPath))
3147189219eSCyndy Ishida             reportError(LinkTarget + ": " + EC.message());
3157189219eSCyndy Ishida         } else if (!sys::fs::exists(SymPath)) {
3167189219eSCyndy Ishida           reportWarning("ignoring broken symlink: " + Path);
3177189219eSCyndy Ishida           continue;
3187189219eSCyndy Ishida         } else {
3197189219eSCyndy Ishida           LinkTarget = SymPath;
3207189219eSCyndy Ishida         }
3217189219eSCyndy Ishida       } else {
3227189219eSCyndy Ishida         LinkTarget = LinkSrc;
3237189219eSCyndy Ishida         sys::path::remove_filename(LinkTarget);
3247189219eSCyndy Ishida         sys::path::append(LinkTarget, SymPath);
3257189219eSCyndy Ishida       }
3267189219eSCyndy Ishida 
3277189219eSCyndy Ishida       // For Apple SDKs, the symlink src is guaranteed to be a canonical path
3287189219eSCyndy Ishida       // because we don't follow symlinks when scanning. The symlink target is
3297189219eSCyndy Ishida       // constructed from the symlink path and needs to be canonicalized.
3307189219eSCyndy Ishida       if (auto ec = sys::fs::real_path(Twine(LinkTarget), LinkTarget)) {
3317189219eSCyndy Ishida         reportWarning(LinkTarget + ": " + ec.message());
3327189219eSCyndy Ishida         continue;
3337189219eSCyndy Ishida       }
3347189219eSCyndy Ishida 
335a99bf0f6SKazu Hirata       SymLinks[LinkTarget.c_str()].emplace_back(LinkSrc.str(),
336a99bf0f6SKazu Hirata                                                 std::string(SymPath.str()));
3377189219eSCyndy Ishida 
3387189219eSCyndy Ishida       continue;
3397189219eSCyndy Ishida     }
3407189219eSCyndy Ishida 
3417189219eSCyndy Ishida     bool IsDirectory = false;
3427189219eSCyndy Ishida     if (auto EC = sys::fs::is_directory(Path, IsDirectory))
3437189219eSCyndy Ishida       reportError(Path + ": " + EC.message());
3447189219eSCyndy Ishida     if (IsDirectory)
3457189219eSCyndy Ishida       continue;
3467189219eSCyndy Ishida 
3477189219eSCyndy Ishida     if (Ctx.StubOpt.DeletePrivate &&
3487189219eSCyndy Ishida         isPrivateLibrary(Path.drop_front(InputPath.size()))) {
3497189219eSCyndy Ishida       IT.no_push();
3507189219eSCyndy Ishida       LibsToDelete.emplace(Path, false);
3517189219eSCyndy Ishida       continue;
3527189219eSCyndy Ishida     }
3537189219eSCyndy Ishida     auto IF = getInterfaceFile(Path);
3547189219eSCyndy Ishida     if (Ctx.StubOpt.TraceLibs)
3557189219eSCyndy Ishida       errs() << Path << "\n";
3567189219eSCyndy Ishida 
3577189219eSCyndy Ishida     // Normalize path for map lookup by removing the extension.
3587189219eSCyndy Ishida     SmallString<PATH_MAX> NormalizedPath(Path);
3597189219eSCyndy Ishida     replace_extension(NormalizedPath, "");
3607189219eSCyndy Ishida 
3617189219eSCyndy Ishida     if ((IF->getFileType() == FileType::MachO_DynamicLibrary) ||
3627189219eSCyndy Ishida         (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) {
3637189219eSCyndy Ishida       OriginalNames[NormalizedPath.c_str()] = IF->getPath();
3647189219eSCyndy Ishida 
3657189219eSCyndy Ishida       // Don't add this MachO dynamic library because we already have a
3667189219eSCyndy Ishida       // text-based stub recorded for this path.
3677189219eSCyndy Ishida       if (Dylibs.count(NormalizedPath.c_str()))
3687189219eSCyndy Ishida         continue;
3697189219eSCyndy Ishida     }
3707189219eSCyndy Ishida 
3717189219eSCyndy Ishida     Dylibs[NormalizedPath.c_str()] = std::move(IF);
3727189219eSCyndy Ishida   }
3737189219eSCyndy Ishida 
3747189219eSCyndy Ishida   for (auto &Lib : Dylibs) {
3757189219eSCyndy Ishida     auto &Dylib = Lib.second;
3767189219eSCyndy Ishida     // Get the original file name.
3777189219eSCyndy Ishida     SmallString<PATH_MAX> NormalizedPath(Dylib->getPath());
3787189219eSCyndy Ishida     stubifyImpl(std::move(Dylib), Ctx);
3797189219eSCyndy Ishida 
3807189219eSCyndy Ishida     replace_extension(NormalizedPath, "");
3817189219eSCyndy Ishida     auto Found = OriginalNames.find(NormalizedPath.c_str());
3827189219eSCyndy Ishida     if (Found == OriginalNames.end())
3837189219eSCyndy Ishida       continue;
3847189219eSCyndy Ishida 
3857189219eSCyndy Ishida     if (Ctx.StubOpt.DeleteInput)
3867189219eSCyndy Ishida       LibsToDelete.emplace(Found->second, true);
3877189219eSCyndy Ishida 
3887189219eSCyndy Ishida     // Don't allow for more than 20 levels of symlinks when searching for
3897189219eSCyndy Ishida     // libraries to stubify.
3907189219eSCyndy Ishida     StringRef LibToCheck = Found->second;
3917189219eSCyndy Ishida     for (int i = 0; i < 20; ++i) {
39261a286acSKazu Hirata       auto LinkIt = SymLinks.find(LibToCheck);
3937189219eSCyndy Ishida       if (LinkIt != SymLinks.end()) {
3947189219eSCyndy Ishida         for (auto &SymInfo : LinkIt->second) {
3957189219eSCyndy Ishida           SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath);
3967189219eSCyndy Ishida           SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent);
3977189219eSCyndy Ishida           replace_extension(LinkSrc, "tbd");
3987189219eSCyndy Ishida           replace_extension(LinkTarget, "tbd");
3997189219eSCyndy Ishida 
4007189219eSCyndy Ishida           if (auto EC = sys::fs::remove(LinkSrc))
4017189219eSCyndy Ishida             reportError(LinkSrc + " : " + EC.message());
4027189219eSCyndy Ishida 
4037189219eSCyndy Ishida           if (auto EC = sys::fs::create_link(LinkTarget, LinkSrc))
4047189219eSCyndy Ishida             reportError(LinkTarget + " : " + EC.message());
4057189219eSCyndy Ishida 
4067189219eSCyndy Ishida           if (Ctx.StubOpt.DeleteInput)
4077189219eSCyndy Ishida             LibsToDelete.emplace(SymInfo.SrcPath, true);
4087189219eSCyndy Ishida 
4097189219eSCyndy Ishida           LibToCheck = SymInfo.SrcPath;
4107189219eSCyndy Ishida         }
4117189219eSCyndy Ishida       } else
4127189219eSCyndy Ishida         break;
4137189219eSCyndy Ishida     }
4147189219eSCyndy Ishida   }
4157189219eSCyndy Ishida 
4167189219eSCyndy Ishida   // Recursively delete the directories. This will abort when they are not empty
4177189219eSCyndy Ishida   // or we reach the root of the SDK.
4187189219eSCyndy Ishida   for (const auto &[LibPath, IsInput] : LibsToDelete) {
4197189219eSCyndy Ishida     if (!IsInput && SymLinks.count(LibPath))
4207189219eSCyndy Ishida       continue;
4217189219eSCyndy Ishida 
4227189219eSCyndy Ishida     if (auto EC = sys::fs::remove(LibPath))
4237189219eSCyndy Ishida       reportError(LibPath + " : " + EC.message());
4247189219eSCyndy Ishida 
4257189219eSCyndy Ishida     std::error_code EC;
4267189219eSCyndy Ishida     auto Dir = sys::path::parent_path(LibPath);
4277189219eSCyndy Ishida     do {
4287189219eSCyndy Ishida       EC = sys::fs::remove(Dir);
4297189219eSCyndy Ishida       Dir = sys::path::parent_path(Dir);
4307189219eSCyndy Ishida       if (!Dir.starts_with(InputPath))
4317189219eSCyndy Ishida         break;
4327189219eSCyndy Ishida     } while (!EC);
4337189219eSCyndy Ishida   }
4347189219eSCyndy Ishida }
4357189219eSCyndy Ishida 
436c6f29dbbSCyndy Ishida static bool handleStubifyAction(Context &Ctx) {
437c6f29dbbSCyndy Ishida   if (Ctx.Inputs.empty())
438c6f29dbbSCyndy Ishida     reportError("stubify requires at least one input file");
439c6f29dbbSCyndy Ishida 
440c6f29dbbSCyndy Ishida   if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr))
441c6f29dbbSCyndy Ishida     reportError("cannot write multiple inputs into single output file");
442c6f29dbbSCyndy Ishida 
4437189219eSCyndy Ishida   for (StringRef PathName : Ctx.Inputs) {
4447189219eSCyndy Ishida     bool IsDirectory = false;
4457189219eSCyndy Ishida     if (auto EC = sys::fs::is_directory(PathName, IsDirectory))
4467189219eSCyndy Ishida       reportError(PathName + ": " + EC.message());
4477189219eSCyndy Ishida 
4487189219eSCyndy Ishida     if (IsDirectory) {
4497189219eSCyndy Ishida       if (Ctx.OutStream != nullptr)
4507189219eSCyndy Ishida         reportError("cannot stubify directory'" + PathName +
4517189219eSCyndy Ishida                     "' into single output file");
4527189219eSCyndy Ishida       stubifyDirectory(PathName, Ctx);
4537189219eSCyndy Ishida       continue;
454c6f29dbbSCyndy Ishida     }
4557189219eSCyndy Ishida 
4567189219eSCyndy Ishida     stubifyImpl(getInterfaceFile(PathName), Ctx);
4577189219eSCyndy Ishida     if (Ctx.StubOpt.DeleteInput)
4587189219eSCyndy Ishida       if (auto ec = sys::fs::remove(PathName))
4597189219eSCyndy Ishida         reportError("deleting file '" + PathName + ": " + ec.message());
460c6f29dbbSCyndy Ishida   }
461c6f29dbbSCyndy Ishida   return EXIT_SUCCESS;
462c6f29dbbSCyndy Ishida }
463c6f29dbbSCyndy Ishida 
464e1f69b86SCyndy Ishida using IFOperation =
465e1f69b86SCyndy Ishida     std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>(
466e1f69b86SCyndy Ishida         const llvm::MachO::InterfaceFile &, Architecture)>;
46786fa4b2cSCyndy Ishida static bool handleSingleFileAction(const Context &Ctx, const StringRef Action,
468e1f69b86SCyndy Ishida                                    IFOperation act) {
469e1f69b86SCyndy Ishida   if (Ctx.Inputs.size() != 1)
470e1f69b86SCyndy Ishida     reportError(Action + " only supports one input file");
471e1f69b86SCyndy Ishida   if (Ctx.Arch == AK_unknown)
472e1f69b86SCyndy Ishida     reportError(Action + " requires -arch <arch>");
473e1f69b86SCyndy Ishida 
474e1f69b86SCyndy Ishida   auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false);
475e1f69b86SCyndy Ishida   auto OutIF = act(*IF, Ctx.Arch);
476e1f69b86SCyndy Ishida   if (!OutIF)
477e1f69b86SCyndy Ishida     ExitOnErr(OutIF.takeError());
478e1f69b86SCyndy Ishida 
479e1f69b86SCyndy Ishida   return handleWriteAction(Ctx, std::move(*OutIF));
480e1f69b86SCyndy Ishida }
481e1f69b86SCyndy Ishida 
482c6f29dbbSCyndy Ishida static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) {
483c6f29dbbSCyndy Ishida   Opt.DeleteInput = Args.hasArg(OPT_delete_input);
4847189219eSCyndy Ishida   Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries);
4857189219eSCyndy Ishida   Opt.TraceLibs = Args.hasArg(OPT_t);
486c6f29dbbSCyndy Ishida }
487c6f29dbbSCyndy Ishida 
488ec64af59SCyndy Ishida int main(int Argc, char **Argv) {
489ec64af59SCyndy Ishida   InitLLVM X(Argc, Argv);
490ec64af59SCyndy Ishida   BumpPtrAllocator A;
491ec64af59SCyndy Ishida   StringSaver Saver(A);
492ec64af59SCyndy Ishida   TAPIOptTable Tbl;
493ec64af59SCyndy Ishida   Context Ctx;
494bc191ac3SCyndy Ishida   ExitOnErr.setBanner(TOOLNAME + ": error:");
495bc191ac3SCyndy Ishida   opt::InputArgList Args = Tbl.parseArgs(
496bc191ac3SCyndy Ishida       Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); });
497ec64af59SCyndy Ishida   if (Args.hasArg(OPT_help)) {
498e1f69b86SCyndy Ishida     Tbl.printHelp(outs(),
499142e567cSCyndy Ishida                   "USAGE: llvm-readtapi <command> [-arch <architecture> "
500142e567cSCyndy Ishida                   "<options>]* <inputs> [-o "
501e1f69b86SCyndy Ishida                   "<output>]*",
502142e567cSCyndy Ishida                   "LLVM TAPI file reader and transformer");
503142e567cSCyndy Ishida     return EXIT_SUCCESS;
504142e567cSCyndy Ishida   }
505142e567cSCyndy Ishida 
506142e567cSCyndy Ishida   if (Args.hasArg(OPT_version)) {
507142e567cSCyndy Ishida     cl::PrintVersionMessage();
508ec64af59SCyndy Ishida     return EXIT_SUCCESS;
509ec64af59SCyndy Ishida   }
510ec64af59SCyndy Ishida 
511ec64af59SCyndy Ishida   for (opt::Arg *A : Args.filtered(OPT_INPUT))
512ec64af59SCyndy Ishida     Ctx.Inputs.push_back(A->getValue());
513ec64af59SCyndy Ishida 
5143b48a7a0SCyndy Ishida   if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) {
515ec64af59SCyndy Ishida     std::string OutputLoc = std::move(A->getValue());
516ec64af59SCyndy Ishida     std::error_code EC;
517ec64af59SCyndy Ishida     Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
518bc191ac3SCyndy Ishida     if (EC)
519bc191ac3SCyndy Ishida       reportError("error opening the file '" + OutputLoc + EC.message(),
520bc191ac3SCyndy Ishida                   NON_TAPI_EXIT_CODE);
521ec64af59SCyndy Ishida   }
522ec64af59SCyndy Ishida 
523ae182dbbSCyndy Ishida   Ctx.Compact = Args.hasArg(OPT_compact);
524ae182dbbSCyndy Ishida 
525ae182dbbSCyndy Ishida   if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) {
526ae182dbbSCyndy Ishida     StringRef FT = A->getValue();
527ae182dbbSCyndy Ishida     Ctx.WriteFT = TextAPIWriter::parseFileType(FT);
528ae182dbbSCyndy Ishida     if (Ctx.WriteFT < FileType::TBD_V3)
529ae182dbbSCyndy Ishida       reportError("deprecated filetype '" + FT + "' is not supported to write");
530ae182dbbSCyndy Ishida     if (Ctx.WriteFT == FileType::Invalid)
531ae182dbbSCyndy Ishida       reportError("unsupported filetype '" + FT + "'");
532ae182dbbSCyndy Ishida   }
533ae182dbbSCyndy Ishida 
53451340480SCyndy Ishida   auto SanitizeArch = [&](opt::Arg *A) {
53551340480SCyndy Ishida     StringRef ArchStr = A->getValue();
53651340480SCyndy Ishida     auto Arch = getArchitectureFromName(ArchStr);
53751340480SCyndy Ishida     if (Arch == AK_unknown)
53851340480SCyndy Ishida       reportError("unsupported architecture '" + ArchStr);
53951340480SCyndy Ishida     return Arch;
54051340480SCyndy Ishida   };
54151340480SCyndy Ishida 
54251340480SCyndy Ishida   if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ))
54351340480SCyndy Ishida     Ctx.Arch = SanitizeArch(A);
54451340480SCyndy Ishida 
54551340480SCyndy Ishida   for (opt::Arg *A : Args.filtered(OPT_ignore_arch_EQ))
54651340480SCyndy Ishida     Ctx.CmpOpt.ArchsToIgnore.set(SanitizeArch(A));
54751340480SCyndy Ishida 
548ae182dbbSCyndy Ishida   // Handle top level and exclusive operation.
549ae182dbbSCyndy Ishida   SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group));
550ae182dbbSCyndy Ishida 
551ae182dbbSCyndy Ishida   if (ActionArgs.empty())
552ae182dbbSCyndy Ishida     // If no action specified, write out tapi file in requested format.
553ae182dbbSCyndy Ishida     return handleWriteAction(Ctx);
554ae182dbbSCyndy Ishida 
555ae182dbbSCyndy Ishida   if (ActionArgs.size() > 1) {
556ae182dbbSCyndy Ishida     std::string Buf;
557ae182dbbSCyndy Ishida     raw_string_ostream OS(Buf);
558ae182dbbSCyndy Ishida     OS << "only one of the following actions can be specified:";
559ae182dbbSCyndy Ishida     for (auto *Arg : ActionArgs)
560ae182dbbSCyndy Ishida       OS << " " << Arg->getSpelling();
561ae182dbbSCyndy Ishida     reportError(OS.str());
562ae182dbbSCyndy Ishida   }
563ae182dbbSCyndy Ishida 
564ae182dbbSCyndy Ishida   switch (ActionArgs.front()->getOption().getID()) {
565ae182dbbSCyndy Ishida   case OPT_compare:
566ec64af59SCyndy Ishida     return handleCompareAction(Ctx);
567ae182dbbSCyndy Ishida   case OPT_merge:
568ae182dbbSCyndy Ishida     return handleMergeAction(Ctx);
569e1f69b86SCyndy Ishida   case OPT_extract:
570e1f69b86SCyndy Ishida     return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract);
571e1f69b86SCyndy Ishida   case OPT_remove:
572e1f69b86SCyndy Ishida     return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove);
573c6f29dbbSCyndy Ishida   case OPT_stubify:
574c6f29dbbSCyndy Ishida     setStubOptions(Args, Ctx.StubOpt);
575c6f29dbbSCyndy Ishida     return handleStubifyAction(Ctx);
576ae182dbbSCyndy Ishida   }
577ec64af59SCyndy Ishida 
578ec64af59SCyndy Ishida   return EXIT_SUCCESS;
5795656d797SCyndy Ishida }
580