[llvm] [readtapi] Add support for stubify-ing directories (PR #76885)

Cyndy Ishida via llvm-commits llvm-commits at lists.llvm.org
Wed Jan 3 20:09:03 PST 2024


https://github.com/cyndyishida updated https://github.com/llvm/llvm-project/pull/76885

>From 3de2a31487575036acb0f4dd5304d071dd4249f4 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Tue, 2 Jan 2024 10:47:33 -0800
Subject: [PATCH] [readtapi] Add support for stubify-ing directories

When given a directory input `llvm-readtapi` traverses through the directory
to find dylibs or tbd files to operate on. TBD files will be created with
the same base file name as the dylib. Symlinks should be
created if the input is one.

This also introduces options to delete input files which
are defined as library files that existed before `readtapi -stubify` was
invoked. Also the ability to delete private libraries where private
libraries are in a predefined file system locations on darwin based
platforms.
---
 llvm/include/llvm/TextAPI/Utils.h             |  50 ++++
 llvm/lib/TextAPI/Utils.cpp                    | 150 +++++++++++
 .../llvm-readtapi/Inputs/libSystem.1.yaml     |  43 +++
 .../tools/llvm-readtapi/stubify-delete.test   |  18 ++
 .../{stubify.test => stubify-simple.test}     |   0
 .../tools/llvm-readtapi/stubify-symlink.test  |  54 ++++
 llvm/tools/llvm-readtapi/TapiOpts.td          |   3 +
 llvm/tools/llvm-readtapi/llvm-readtapi.cpp    | 251 ++++++++++++++++--
 8 files changed, 551 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readtapi/Inputs/libSystem.1.yaml
 create mode 100644 llvm/test/tools/llvm-readtapi/stubify-delete.test
 rename llvm/test/tools/llvm-readtapi/{stubify.test => stubify-simple.test} (100%)
 create mode 100644 llvm/test/tools/llvm-readtapi/stubify-symlink.test

diff --git a/llvm/include/llvm/TextAPI/Utils.h b/llvm/include/llvm/TextAPI/Utils.h
index bb22ea5e9606b2..3449fe12eb975a 100644
--- a/llvm/include/llvm/TextAPI/Utils.h
+++ b/llvm/include/llvm/TextAPI/Utils.h
@@ -21,14 +21,64 @@
 #define PATH_MAX 1024
 #endif
 
+#define MACCATALYST_PREFIX_PATH "/System/iOSSupport"
+#define DRIVERKIT_PREFIX_PATH "/System/DriverKit"
+
 namespace llvm::MachO {
 
 using PathSeq = std::vector<std::string>;
 
+// Defines simple struct for storing symbolic links.
+struct SymLink {
+  std::string SrcPath;
+  std::string LinkContent;
+
+  SymLink(std::string Path, std::string Link)
+      : SrcPath(std::move(Path)), LinkContent(std::move(Link)) {}
+
+  SymLink(StringRef Path, StringRef Link)
+      : SrcPath(std::string(Path)), LinkContent(std::string(Link)) {}
+};
+
 /// Replace extension considering frameworks.
 ///
 /// \param Path Location of file.
 /// \param Extension File extension to update with.
 void replace_extension(SmallVectorImpl<char> &Path, const Twine &Extension);
+
+/// Determine whether to skip over symlink due to either too many symlink levels
+/// or is cyclic.
+///
+/// \param Path Location to symlink.
+/// \param Result Holds whether to skip over Path.
+std::error_code shouldSkipSymLink(const Twine &Path, bool &Result);
+
+/// Get what the symlink points to.
+///
+/// \param Path The symlink.
+/// \param LinkPath Holds what the symlink points to.
+std::error_code read_link(const Twine &Path, SmallVectorImpl<char> &LinkPath);
+
+/// Turn absolute symlink into relative.
+///
+/// \param From The symlink.
+/// \param To What the symlink points to.
+/// \param RelativePath Path location to update what the symlink points to.
+std::error_code make_relative(StringRef From, StringRef To,
+                              SmallVectorImpl<char> &RelativePath);
+
+/// Replace input with it's realpath.
+/// This is a no-op on windows as it references POSIX level apis.
+///
+/// \param Path Input path to replace.
+std::error_code realpath(SmallVectorImpl<char> &Path);
+
+/// Determine if library is private by parsing file path.
+/// It does not touch the file system.
+///
+/// \param Path File path for library.
+/// \param IsSymLink Whether path points to a symlink.
+bool isPrivateLibrary(StringRef Path, bool IsSymLink = false);
+
 } // namespace llvm::MachO
 #endif // LLVM_TEXTAPI_UTILS_H
diff --git a/llvm/lib/TextAPI/Utils.cpp b/llvm/lib/TextAPI/Utils.cpp
index 6d85083e0b54c1..06ca863a941a3d 100644
--- a/llvm/lib/TextAPI/Utils.cpp
+++ b/llvm/lib/TextAPI/Utils.cpp
@@ -11,6 +11,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/TextAPI/Utils.h"
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+#include <unistd.h>
+#endif
 
 using namespace llvm;
 using namespace llvm::MachO;
@@ -38,3 +41,150 @@ void llvm::MachO::replace_extension(SmallVectorImpl<char> &Path,
   // Append extension.
   Path.append(Ext.begin(), Ext.end());
 }
+
+std::error_code llvm::MachO::shouldSkipSymLink(const Twine &Path,
+                                               bool &Result) {
+  Result = false;
+  SmallString<PATH_MAX> Storage;
+  auto P = Path.toNullTerminatedStringRef(Storage);
+  sys::fs::file_status stat1;
+  auto ec = sys::fs::status(P.data(), stat1);
+  if (ec == std::errc::too_many_symbolic_link_levels) {
+    Result = true;
+    return {};
+  }
+
+  if (ec)
+    return ec;
+
+  StringRef Parent = sys::path::parent_path(P);
+  while (!Parent.empty()) {
+    sys::fs::file_status stat2;
+    if (auto ec = sys::fs::status(Parent, stat2))
+      return ec;
+
+    if (sys::fs::equivalent(stat1, stat2)) {
+      Result = true;
+      return {};
+    }
+
+    Parent = sys::path::parent_path(Parent);
+  }
+  return {};
+}
+
+std::error_code llvm::MachO::read_link(const Twine &Path,
+                                       SmallVectorImpl<char> &LinkPath) {
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+  errno = 0;
+  SmallString<PATH_MAX> Storage;
+  auto P = Path.toNullTerminatedStringRef(Storage);
+  SmallString<PATH_MAX> Result;
+  ssize_t Len;
+  if ((Len = ::readlink(P.data(), Result.data(), PATH_MAX)) == -1)
+    return {errno, std::generic_category()};
+
+  Result.resize_for_overwrite(Len);
+  LinkPath.swap(Result);
+#endif
+  return {};
+}
+
+std::error_code
+llvm::MachO::make_relative(StringRef From, StringRef To,
+                           SmallVectorImpl<char> &RelativePath) {
+  SmallString<PATH_MAX> Src = From;
+  SmallString<PATH_MAX> Dst = To;
+  if (auto ec = sys::fs::make_absolute(Src))
+    return ec;
+
+  if (auto ec = sys::fs::make_absolute(Dst))
+    return ec;
+
+  SmallString<PATH_MAX> Result;
+  Src = sys::path::parent_path(From);
+  auto it1 = sys::path::begin(Src), it2 = sys::path::begin(Dst),
+       ie1 = sys::path::end(Src), ie2 = sys::path::end(Dst);
+  // Ignore the common part.
+  for (; it1 != ie1 && it2 != ie2; ++it1, ++it2) {
+    if (*it1 != *it2)
+      break;
+  }
+
+  for (; it1 != ie1; ++it1)
+    sys::path::append(Result, "../");
+
+  for (; it2 != ie2; ++it2)
+    sys::path::append(Result, *it2);
+
+  if (Result.empty())
+    Result = ".";
+
+  RelativePath.swap(Result);
+
+  return {};
+}
+std::error_code llvm::MachO::realpath(SmallVectorImpl<char> &Path) {
+
+  if (Path.back() != '\0')
+    Path.append({'\0'});
+  SmallString<PATH_MAX> Result;
+
+  errno = 0;
+  const char *Ptr = nullptr;
+  if ((Ptr = ::realpath(Path.data(), Result.data())) == nullptr)
+    return {errno, std::generic_category()};
+
+  assert(Ptr == Result.data() && "Unexpected pointer");
+  Result.resize_for_overwrite(strlen(Result.data()));
+  Path.swap(Result);
+  return {};
+}
+
+bool llvm::MachO::isPrivateLibrary(StringRef Path, bool IsSymLink) {
+  // Remove the iOSSupport and DriverKit prefix to identify public locations.
+  Path.consume_front(MACCATALYST_PREFIX_PATH);
+  Path.consume_front(DRIVERKIT_PREFIX_PATH);
+  // Also /Library/Apple prefix for ROSP.
+  Path.consume_front("/Library/Apple");
+
+  if (Path.starts_with("/usr/local/lib"))
+    return true;
+
+  if (Path.starts_with("/System/Library/PrivateFrameworks"))
+    return true;
+
+  // Everything in /usr/lib/swift (including sub-directories) are considered
+  // public.
+  if (Path.consume_front("/usr/lib/swift/"))
+    return false;
+
+  // Only libraries directly in /usr/lib are public. All other libraries in
+  // sub-directories are private.
+  if (Path.consume_front("/usr/lib/"))
+    return Path.contains('/');
+
+  // "/System/Library/Frameworks/" is a public location.
+  if (Path.starts_with("/System/Library/Frameworks/")) {
+    StringRef Name, Rest;
+    std::tie(Name, Rest) =
+        Path.drop_front(sizeof("/System/Library/Frameworks")).split('.');
+
+    // Allow symlinks to top-level frameworks.
+    if (IsSymLink && Rest == "framework")
+      return false;
+
+    // Only top level framework are public.
+    // /System/Library/Frameworks/Foo.framework/Foo ==> true
+    // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true
+    // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false
+    // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar
+    // ==> false
+    // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo
+    // ==> false
+    return !(Rest.starts_with("framework/") &&
+             (Rest.ends_with(Name) || Rest.ends_with((Name + ".tbd").str()) ||
+              (IsSymLink && Rest.ends_with("Current"))));
+  }
+  return false;
+}
diff --git a/llvm/test/tools/llvm-readtapi/Inputs/libSystem.1.yaml b/llvm/test/tools/llvm-readtapi/Inputs/libSystem.1.yaml
new file mode 100644
index 00000000000000..5da52929e026b4
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/Inputs/libSystem.1.yaml
@@ -0,0 +1,43 @@
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x00000003
+  filetype:        0x00000006
+  ncmds:           4
+  sizeofcmds:      136
+  flags:           0x00100085
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_ID_DYLIB
+    cmdsize:         56
+    dylib:
+      name:           24
+      timestamp:       1
+      current_version: 65536
+      compatibility_version: 65536
+    Content:   /usr/lib/libSystem.1.dylib
+    ZeroPadBytes:    4
+  - cmd:             LC_BUILD_VERSION
+    cmdsize:         32
+    platform:        1
+    minos:           658944
+    sdk:             659200
+    ntools:          1
+    Tools:
+      - tool:            3
+        version:         32899328
+  - cmd:             LC_BUILD_VERSION
+    cmdsize:         32
+    platform:        6
+    minos:           786432
+    sdk:             851968
+    ntools:          1
+    Tools:
+      - tool:            3
+        version:         32899328
+  - cmd:             LC_VERSION_MIN_MACOSX
+    cmdsize:         16
+    version:         658944
+    sdk:             659200
+...
diff --git a/llvm/test/tools/llvm-readtapi/stubify-delete.test b/llvm/test/tools/llvm-readtapi/stubify-delete.test
new file mode 100644
index 00000000000000..666d740560cbfe
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/stubify-delete.test
@@ -0,0 +1,18 @@
+; RUN: rm -rf %t
+# Setup a mix of public and private libraries that resemble apple sdk.
+; RUN: mkdir -p %t/sysroot/usr/local/lib/ %t/sysroot/usr/lib/
+; RUN: mkdir -p %t/sysroot/System/Library/Frameworks/System.framework %t/sysroot/System/Library/PrivateFrameworks/Fat.framework
+; RUN: yaml2obj %S/Inputs/libSystem.1.yaml -o %t/sysroot/System/Library/Frameworks/System.framework/System
+; RUN: yaml2obj %S/Inputs/objc.yaml -o %t/sysroot/usr/lib/libobjc.dylib
+; RUN: cp %t/sysroot/usr/lib/libobjc.dylib %t/sysroot/usr/local/lib/libobjc-unstable.dylib
+; RUN: yaml2obj %S/Inputs/universal.yaml -o %t/sysroot/System/Library/PrivateFrameworks/Fat.framework/Fat
+; RUN: llvm-readtapi -stubify %t/sysroot --delete-input --delete-private-libraries 2>&1 | FileCheck %s --allow-empty  --implicit-check-not warning: --implicit-check-not error:
+# Validate expected files are removed.
+; RUN: not test -f %t/sysroot/System/Library/PrivateFrameworks
+; RUN: not test -f %t/sysroot/usr/local
+; RUN: not test -f %t/sysroot/usr/lib/libobjc.dylib
+; RUN: not test -f %t/sysroot/System/Library/Frameworks/System.framework/System
+; RUN: test -f %t/sysroot/System/Library/Frameworks/System.framework/System.tbd
+; RUN: test -f %t/sysroot/usr/lib/libobjc.tbd
+
+
diff --git a/llvm/test/tools/llvm-readtapi/stubify.test b/llvm/test/tools/llvm-readtapi/stubify-simple.test
similarity index 100%
rename from llvm/test/tools/llvm-readtapi/stubify.test
rename to llvm/test/tools/llvm-readtapi/stubify-simple.test
diff --git a/llvm/test/tools/llvm-readtapi/stubify-symlink.test b/llvm/test/tools/llvm-readtapi/stubify-symlink.test
new file mode 100644
index 00000000000000..707dce297e50ff
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/stubify-symlink.test
@@ -0,0 +1,54 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: mkdir -p %t/sysroot/usr/lib/
+; RUN: mkdir -p %t/sysroot/System/Library/Frameworks/System.framework
+; RUN: yaml2obj %S/Inputs/libSystem.1.yaml -o %t/sysroot/usr/lib/libSystem.1.dylib
+; RUN: cd %t/sysroot/usr/lib
+# Set relative symlink.
+; RUN: ln -s  libSystem.1.dylib libSystem.dylib
+# Set broken but accepted symlink.
+; RUN: ln -s /usr/lib/libSystem.1.dylib %t/sysroot/System/Library/Frameworks/System.framework/System
+# Set absolute symlink.
+; RUN: ln -s  %t/sysroot/usr/lib/libSystem.dylib libfoo.dylib
+; RUN: cd %t
+; RUN: llvm-readtapi -stubify %t/sysroot 2>&1 | FileCheck -allow-empty %s
+; RUN: llvm-readtapi -compare %t/sysroot/usr/lib/libfoo.tbd  %t/expected_system.tbd 2>&1 | FileCheck -allow-empty %s
+; RUN: file %t/sysroot/System/Library/Frameworks/System.framework/System.tbd 2>&1 | FileCheck -allow-empty %s
+
+# Warn on invalid symlink.
+; RUN: ln -s  %t/sysroot/usr/libSystem.dylib %t/sysroot/usr/lib/libbroken.dylib
+; RUN: llvm-readtapi -stubify %t/sysroot 2>&1 | FileCheck %s --check-prefix BROKEN_SYMLINK 
+
+; CHECK-NOT: error: 
+; CHECK-NOT: warning: 
+
+; BROKEN_SYMLINK: ignoring broken symlink: {{.*}}/usr/lib/libbroken.dylib
+
+;--- expected_system.tbd
+{
+  "main_library": {
+    "flags": [
+      {
+        "attributes": [
+          "not_app_extension_safe"
+        ]
+      }
+    ],
+    "install_names": [
+      {
+        "name": "/usr/lib/libSystem.1.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "min_deployment": "10.14",
+        "target": "x86_64-macos"
+      },
+      {
+        "min_deployment": "12",
+        "target": "x86_64-maccatalyst"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
diff --git a/llvm/tools/llvm-readtapi/TapiOpts.td b/llvm/tools/llvm-readtapi/TapiOpts.td
index 34ec5616a42e27..3b8f26579bd4af 100644
--- a/llvm/tools/llvm-readtapi/TapiOpts.td
+++ b/llvm/tools/llvm-readtapi/TapiOpts.td
@@ -32,3 +32,6 @@ defm arch: JS<"arch", "specify the <architecture>", "<architecture>">;
 // Stub options
 //
 def delete_input : FF<"delete-input", "delete and replace input file on success">;
+def delete_private_libraries : FF<"delete-private-libraries", "delete private system dynamic libraries and frameworks">;
+def t: FF<"t", "logs each library loaded, useful for debugging problems with search paths where the wrong library is loaded">;
+
diff --git a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
index 4bd47223ea91f5..dd2118e19767bf 100644
--- a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
+++ b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
@@ -32,10 +32,6 @@ using namespace llvm;
 using namespace MachO;
 using namespace object;
 
-#if !defined(PATH_MAX)
-#define PATH_MAX 1024
-#endif
-
 namespace {
 using namespace llvm::opt;
 enum ID {
@@ -67,13 +63,15 @@ class TAPIOptTable : public opt::GenericOptTable {
 
 struct StubOptions {
   bool DeleteInput = false;
+  bool DeletePrivate = false;
+  bool TraceLibs = false;
 };
 
 struct Context {
   std::vector<std::string> Inputs;
+  StubOptions StubOpt;
   std::unique_ptr<llvm::raw_fd_stream> OutStream;
   FileType WriteFT = FileType::TBD_V5;
-  StubOptions StubOpt;
   bool Compact = false;
   Architecture Arch = AK_unknown;
 };
@@ -93,6 +91,11 @@ static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) {
   exit(ExitCode);
 }
 
+// Handle warnings.
+static void reportWarning(Twine Message) {
+  errs() << TOOLNAME << ": warning: " << Message << "\n";
+}
+
 static std::unique_ptr<InterfaceFile>
 getInterfaceFile(const StringRef Filename, bool ResetBanner = true) {
   ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' ");
@@ -167,6 +170,213 @@ static bool handleMergeAction(const Context &Ctx) {
   return handleWriteAction(Ctx, std::move(Out));
 }
 
+static void stubifyImpl(std::unique_ptr<InterfaceFile> IF, Context &Ctx) {
+  // TODO: Add inlining and magic merge support.
+  if (Ctx.OutStream == nullptr) {
+    std::error_code EC;
+    SmallString<PATH_MAX> OutputLoc = IF->getPath();
+    replace_extension(OutputLoc, ".tbd");
+    Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
+    if (EC)
+      reportError("opening file '" + OutputLoc + ": " + EC.message());
+  }
+
+  handleWriteAction(Ctx, std::move(IF));
+  // Clear out output stream after file has been written incase more files are
+  // stubifed.
+  Ctx.OutStream = nullptr;
+}
+
+static void stubifyDirectory(const StringRef InputPath, Context &Ctx) {
+  assert(InputPath.back() != '/' && "Unexpected / at end of input path.");
+  StringMap<std::vector<SymLink>> SymLinks;
+  StringMap<std::unique_ptr<InterfaceFile>> Dylibs;
+  StringMap<std::string> OriginalNames;
+  std::set<std::pair<std::string, bool>> LibsToDelete;
+
+  std::error_code ec;
+  for (sys::fs::recursive_directory_iterator it(InputPath, ec), ie; it != ie;
+       it.increment(ec)) {
+    if (ec == std::errc::no_such_file_or_directory) {
+      reportWarning(it->path() + ": " + ec.message());
+      continue;
+    }
+    if (ec)
+      reportError(it->path() + ": " + ec.message());
+
+    // Skip header directories (include/Headers/PrivateHeaders) and module
+    // files.
+    StringRef Path = it->path();
+    if (Path.ends_with("/include") || Path.ends_with("/Headers") ||
+        Path.ends_with("/PrivateHeaders") || Path.ends_with("/Modules") ||
+        Path.ends_with(".map") || Path.ends_with(".modulemap")) {
+      it.no_push();
+      continue;
+    }
+
+    // Check if the entry is a symlink. We don't follow symlinks but we record
+    // their content.
+    bool IsSymLink;
+    if (auto ec = sys::fs::is_symlink_file(Path, IsSymLink))
+      reportError(Path + ": " + ec.message());
+
+    if (IsSymLink) {
+      it.no_push();
+
+      bool ShouldSkip;
+      auto SymLinkEC = shouldSkipSymLink(Path, ShouldSkip);
+
+      // If symlink is broken, for some reason, we should continue
+      // trying to repair it before quitting.
+      if (!SymLinkEC && ShouldSkip)
+        continue;
+
+      if (Ctx.StubOpt.DeletePrivate &&
+          isPrivateLibrary(Path.drop_front(InputPath.size()), true)) {
+        LibsToDelete.emplace(Path, false);
+        continue;
+      }
+
+      SmallString<PATH_MAX> SymPath;
+      if (auto ec = MachO::read_link(Path, SymPath))
+        reportError("cannot read '" + Path + "' :" + ec.message());
+
+      // Sometimes there are broken symlinks that are absolute paths, which are
+      // invalid during build time, but would be correct during runtime. In the
+      // case of an absolute path we should check first if the path exists with
+      // the known locations as prefix.
+      SmallString<PATH_MAX> LinkSrc = Path;
+      SmallString<PATH_MAX> LinkTarget;
+      if (sys::path::is_absolute(SymPath)) {
+        LinkTarget = InputPath;
+        sys::path::append(LinkTarget, SymPath);
+
+        // TODO: Investigate supporting a file manager for file system accesses.
+        if (sys::fs::exists(LinkTarget)) {
+          // Convert the absolute path to an relative path.
+          if (auto ec = MachO::make_relative(LinkSrc, LinkTarget, SymPath))
+            reportError(LinkTarget + ": " + ec.message());
+        } else if (!sys::fs::exists(SymPath)) {
+          reportWarning("ignoring broken symlink: " + Path);
+          continue;
+        } else {
+          LinkTarget = SymPath;
+        }
+      } else {
+        LinkTarget = LinkSrc;
+        sys::path::remove_filename(LinkTarget);
+        sys::path::append(LinkTarget, SymPath);
+      }
+
+      // For Apple SDKs, the symlink src is guaranteed to be a canonical path
+      // because we don't follow symlinks when scanning. The symlink target is
+      // constructed from the symlink path and needs to be canonicalized.
+      if (auto ec = realpath(LinkTarget)) {
+        reportWarning(LinkTarget + ": " + ec.message());
+        continue;
+      }
+
+      auto itr = SymLinks.insert({LinkTarget.c_str(), std::vector<SymLink>()});
+      itr.first->second.emplace_back(LinkSrc.str(), std::string(SymPath.str()));
+
+      continue;
+    }
+
+    bool IsDirectory = false;
+    if (auto EC = sys::fs::is_directory(Path, IsDirectory))
+      reportError(Path + ": " + EC.message());
+    if (IsDirectory)
+      continue;
+
+    if (Ctx.StubOpt.DeletePrivate &&
+        isPrivateLibrary(Path.drop_front(InputPath.size()))) {
+      it.no_push();
+      LibsToDelete.emplace(Path, false);
+      continue;
+    }
+    auto IF = getInterfaceFile(Path);
+    if (Ctx.StubOpt.TraceLibs)
+      errs() << Path << "\n";
+
+    // Normalize path for map lookup by removing the extension.
+    SmallString<PATH_MAX> NormalizedPath(Path);
+    replace_extension(NormalizedPath, "");
+
+    if ((IF->getFileType() == FileType::MachO_DynamicLibrary) ||
+        (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) {
+      OriginalNames[NormalizedPath.c_str()] = IF->getPath();
+
+      // Don't add this MachO dynamic library because we already have a
+      // text-based stub recorded for this path.
+      if (Dylibs.count(NormalizedPath.c_str()))
+        continue;
+    }
+
+    Dylibs[NormalizedPath.c_str()] = std::move(IF);
+  }
+
+  for (auto &Lib : Dylibs) {
+    auto &Dylib = Lib.second;
+    // Get the original file name.
+    SmallString<PATH_MAX> NormalizedPath(Dylib->getPath());
+    stubifyImpl(std::move(Dylib), Ctx);
+
+    replace_extension(NormalizedPath, "");
+    auto Found = OriginalNames.find(NormalizedPath.c_str());
+    if (Found == OriginalNames.end())
+      continue;
+
+    if (Ctx.StubOpt.DeleteInput)
+      LibsToDelete.emplace(Found->second, true);
+
+    // Don't allow for more than 20 levels of symlinks when searching for
+    // libraries to stubify.
+    StringRef LibToCheck = Found->second;
+    for (int i = 0; i < 20; ++i) {
+      auto itr = SymLinks.find(LibToCheck.str());
+      if (itr != SymLinks.end()) {
+        for (auto &SymInfo : itr->second) {
+          SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath);
+          SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent);
+          replace_extension(LinkSrc, "tbd");
+          replace_extension(LinkTarget, "tbd");
+
+          if (auto ec = sys::fs::remove(LinkSrc))
+            reportError(LinkSrc + " : " + ec.message());
+
+          if (auto ec = sys::fs::create_link(LinkTarget, LinkSrc))
+            reportError(LinkTarget + " : " + ec.message());
+
+          if (Ctx.StubOpt.DeleteInput)
+            LibsToDelete.emplace(SymInfo.SrcPath, true);
+
+          LibToCheck = SymInfo.SrcPath;
+        }
+      } else
+        break;
+    }
+  }
+
+  // Recursively delete the directories. This will abort when they are not empty
+  // or we reach the root of the SDK.
+  for (const auto &[LibPath, IsInput] : LibsToDelete) {
+    if (!IsInput && SymLinks.count(LibPath))
+      continue;
+
+    if (auto ec = sys::fs::remove(LibPath))
+      reportError(LibPath + " : " + ec.message());
+
+    std::error_code ec;
+    auto Dir = sys::path::parent_path(LibPath);
+    do {
+      ec = sys::fs::remove(Dir);
+      Dir = sys::path::parent_path(Dir);
+      if (!Dir.starts_with(InputPath))
+        break;
+    } while (!ec);
+  }
+}
+
 static bool handleStubifyAction(Context &Ctx) {
   if (Ctx.Inputs.empty())
     reportError("stubify requires at least one input file");
@@ -174,19 +384,23 @@ static bool handleStubifyAction(Context &Ctx) {
   if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr))
     reportError("cannot write multiple inputs into single output file");
 
-  for (StringRef FileName : Ctx.Inputs) {
-    auto IF = getInterfaceFile(FileName);
-    if (Ctx.StubOpt.DeleteInput) {
-      std::error_code EC;
-      SmallString<PATH_MAX> OutputLoc = FileName;
-      MachO::replace_extension(OutputLoc, ".tbd");
-      Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);
-      if (EC)
-        reportError("opening file '" + OutputLoc + ": " + EC.message());
-      if (auto Err = sys::fs::remove(FileName))
-        reportError("deleting file '" + FileName + ": " + EC.message());
+  for (StringRef PathName : Ctx.Inputs) {
+    bool IsDirectory = false;
+    if (auto EC = sys::fs::is_directory(PathName, IsDirectory))
+      reportError(PathName + ": " + EC.message());
+
+    if (IsDirectory) {
+      if (Ctx.OutStream != nullptr)
+        reportError("cannot stubify directory'" + PathName +
+                    "' into single output file");
+      stubifyDirectory(PathName, Ctx);
+      continue;
     }
-    handleWriteAction(Ctx, std::move(IF));
+
+    stubifyImpl(getInterfaceFile(PathName), Ctx);
+    if (Ctx.StubOpt.DeleteInput)
+      if (auto ec = sys::fs::remove(PathName))
+        reportError("deleting file '" + PathName + ": " + ec.message());
   }
   return EXIT_SUCCESS;
 }
@@ -211,6 +425,8 @@ static bool handleSingleFileAction(const Context &Ctx, const StringRef Action,
 
 static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) {
   Opt.DeleteInput = Args.hasArg(OPT_delete_input);
+  Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries);
+  Opt.TraceLibs = Args.hasArg(OPT_t);
 }
 
 int main(int Argc, char **Argv) {
@@ -236,7 +452,6 @@ int main(int Argc, char **Argv) {
     return EXIT_SUCCESS;
   }
 
-  // TODO: Add support for picking up libraries from directory input.
   for (opt::Arg *A : Args.filtered(OPT_INPUT))
     Ctx.Inputs.push_back(A->getValue());
 



More information about the llvm-commits mailing list