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

via llvm-commits llvm-commits at lists.llvm.org
Sat Feb 3 09:33:47 PST 2024


Author: Cyndy Ishida
Date: 2024-02-03T09:33:42-08:00
New Revision: 7189219ec9fc768f159917052b4b5998d077c39f

URL: https://github.com/llvm/llvm-project/commit/7189219ec9fc768f159917052b4b5998d077c39f
DIFF: https://github.com/llvm/llvm-project/commit/7189219ec9fc768f159917052b4b5998d077c39f.diff

LOG: [readtapi] Add support for stubify-ing directories (#76885)

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.

Added: 
    llvm/test/tools/llvm-readtapi/Inputs/libSystem.1.yaml
    llvm/test/tools/llvm-readtapi/stubify-delete.test
    llvm/test/tools/llvm-readtapi/stubify-simple.test
    llvm/test/tools/llvm-readtapi/stubify-symlink.test

Modified: 
    llvm/include/llvm/TextAPI/Utils.h
    llvm/lib/TextAPI/Utils.cpp
    llvm/tools/llvm-readtapi/TapiOpts.td
    llvm/tools/llvm-readtapi/llvm-readtapi.cpp

Removed: 
    llvm/test/tools/llvm-readtapi/stubify.test


################################################################################
diff  --git a/llvm/include/llvm/TextAPI/Utils.h b/llvm/include/llvm/TextAPI/Utils.h
index bb22ea5e9606b..31d3c45f9e296 100644
--- a/llvm/include/llvm/TextAPI/Utils.h
+++ b/llvm/include/llvm/TextAPI/Utils.h
@@ -21,14 +21,52 @@
 #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);
+
+/// 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);
+
+/// 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 6d85083e0b54c..b5c3999a86ea9 100644
--- a/llvm/lib/TextAPI/Utils.cpp
+++ b/llvm/lib/TextAPI/Utils.cpp
@@ -38,3 +38,117 @@ 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::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 {};
+}
+
+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 0000000000000..5da52929e026b
--- /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 0000000000000..666d740560cbf
--- /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 0000000000000..8f81349ee22b8
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/stubify-symlink.test
@@ -0,0 +1,57 @@
+# Tapi's awareness of symlinks relies on POSIX level apis not available on windows.
+; UNSUPPORTED: system-windows
+
+; 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 34ec5616a42e2..3b8f26579bd4a 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 4bd47223ea91f..ba348e4cf3867 100644
--- a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
+++ b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
@@ -28,14 +28,14 @@
 #include "llvm/TextAPI/Utils.h"
 #include <cstdlib>
 
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+#include <unistd.h>
+#endif
+
 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 +67,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 +95,34 @@ static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) {
   exit(ExitCode);
 }
 
+// Handle warnings.
+static void reportWarning(Twine Message) {
+  errs() << TOOLNAME << ": warning: " << Message << "\n";
+}
+
+/// Get what the symlink points to.
+/// This is a no-op on windows as it references POSIX level apis.
+static std::error_code read_link(const Twine &Path,
+                                 SmallVectorImpl<char> &Output) {
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+  Output.clear();
+  if (Path.isTriviallyEmpty())
+    return std::error_code();
+
+  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 std::error_code(errno, std::generic_category());
+  Result.resize_for_overwrite(Len);
+  Output.swap(Result);
+  return std::error_code();
+#else
+  reportError("unable to read symlink on windows: " + Path);
+#endif
+}
+
 static std::unique_ptr<InterfaceFile>
 getInterfaceFile(const StringRef Filename, bool ResetBanner = true) {
   ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' ");
@@ -167,6 +197,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 = 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 = sys::fs::real_path(Twine(LinkTarget), 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 LinkIt = SymLinks.find(LibToCheck.str());
+      if (LinkIt != SymLinks.end()) {
+        for (auto &SymInfo : LinkIt->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 +411,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 +452,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 +479,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