[llvm] [readtapi] Add Extract & Remove architecture functionality (PR #72657)

Cyndy Ishida via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 17 10:17:50 PST 2023


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

>From dcf4a716b7163674e65ac622da047422b13dcf9f Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Tue, 14 Nov 2023 15:00:30 -0800
Subject: [PATCH 1/2] [readtapi] Add Merge functionality

Merge allows a user to merge different files (tbds or dylibs) to emit
out a single tbd with all input contents. This does require that all
inputs represent the same library.

* Add a helper function for boilerplate fatal error handling.
* Add a simple & implicit writeAction
---
 llvm/include/llvm/TextAPI/TextAPIWriter.h     |  14 ++
 .../tools/llvm-readtapi/command-line.test     |   6 +
 .../compare-incorrect-format.test             |   2 +-
 .../tools/llvm-readtapi/merge-invalid.test    |  52 +++++++
 llvm/test/tools/llvm-readtapi/merge.test      | 139 ++++++++++++++++++
 llvm/test/tools/llvm-readtapi/write.test      |  77 ++++++++++
 llvm/tools/llvm-readtapi/DiffEngine.cpp       |  10 +-
 llvm/tools/llvm-readtapi/DiffEngine.h         |   8 +-
 llvm/tools/llvm-readtapi/TapiOpts.td          |  14 +-
 llvm/tools/llvm-readtapi/llvm-readtapi.cpp    | 121 +++++++++++----
 10 files changed, 398 insertions(+), 45 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readtapi/merge-invalid.test
 create mode 100644 llvm/test/tools/llvm-readtapi/merge.test
 create mode 100644 llvm/test/tools/llvm-readtapi/write.test

diff --git a/llvm/include/llvm/TextAPI/TextAPIWriter.h b/llvm/include/llvm/TextAPI/TextAPIWriter.h
index 89fc984854dbae0..7fd32c6fe2a9eff 100644
--- a/llvm/include/llvm/TextAPI/TextAPIWriter.h
+++ b/llvm/include/llvm/TextAPI/TextAPIWriter.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_TEXTAPI_TEXTAPIWRITER_H
 #define LLVM_TEXTAPI_TEXTAPIWRITER_H
 
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/TextAPI/InterfaceFile.h"
 
 namespace llvm {
@@ -32,6 +33,19 @@ class TextAPIWriter {
   static Error writeToStream(raw_ostream &OS, const InterfaceFile &File,
                              const FileType FileKind = FileType::Invalid,
                              bool Compact = false);
+
+  /// Get TAPI FileType from the input string.
+  ///
+  /// \param FT String of input to map to FileType.
+  static FileType parseFileType(const StringRef FT) {
+    return StringSwitch<FileType>(FT)
+        .Case("tbd-v1", FileType::TBD_V1)
+        .Case("tbd-v2", FileType::TBD_V2)
+        .Case("tbd-v3", FileType::TBD_V3)
+        .Case("tbd-v4", FileType::TBD_V4)
+        .Case("tbd-v5", FileType::TBD_V5)
+        .Default(FileType::Invalid);
+  }
 };
 
 } // end namespace MachO.
diff --git a/llvm/test/tools/llvm-readtapi/command-line.test b/llvm/test/tools/llvm-readtapi/command-line.test
index 3b17194a7c14792..400e0670e5aa067 100644
--- a/llvm/test/tools/llvm-readtapi/command-line.test
+++ b/llvm/test/tools/llvm-readtapi/command-line.test
@@ -1,7 +1,13 @@
 ; RUN: llvm-readtapi --help 2>&1 | FileCheck %s 
 ; RUN: llvm-readtapi -help 2>&1 | FileCheck %s 
+; RUN: not llvm-readtapi -merge -compare -compact %t/tmp.tbd %t/tmp2.tbd 2>&1 | FileCheck %s --check-prefix MULTI_ACTION
+; RUN: not llvm-readtapi -merge -compact %t/tmp.tbd %t/tmp2.tbd --filetype=tbd-v2 2>&1 | FileCheck %s --check-prefix FILE_FORMAT 
 
 CHECK: OVERVIEW: LLVM TAPI file reader and manipulator
 CHECK: USAGE: llvm-readtapi [options] <inputs>
 CHECK: OPTIONS:
 CHECK:   -help    display this help
+
+MULTI_ACTION: error: only one of the following actions can be specified: -merge -compare
+FILE_FORMAT: error: deprecated filetype 'tbd-v2' is not supported to write
+
diff --git a/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test b/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test
index 32a350b28eb1e40..09dc768518ebc79 100644
--- a/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test
+++ b/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test
@@ -2,6 +2,6 @@
 ; RUN: yaml2obj %S/Inputs/macho.yaml -o %t/macho.dylib
 ; RUN: not llvm-readtapi --compare %S/Inputs/v4A.tbd %t/macho.dylib 2>&1 | FileCheck %s
 
-; CHECK: error: {{.*}}macho.dylib' unsupported file format
+; CHECK: error: {{.*}}macho.dylib' unsupported file type 
 ; CHECK-NOT: error:
 ; CHECK-NOT: warning:
diff --git a/llvm/test/tools/llvm-readtapi/merge-invalid.test b/llvm/test/tools/llvm-readtapi/merge-invalid.test
new file mode 100644
index 000000000000000..f66bfbe8998158e
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/merge-invalid.test
@@ -0,0 +1,52 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: not llvm-readtapi -merge %t/libfoo.tbd %t/libbar.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix DIFF
+; RUN: not llvm-readtapi -merge %t/libfoo.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix INPUT
+
+; DIFF: install names do not match
+; INPUT: merge requires at least two input files
+
+;--- libfoo.tbd
+{
+  "main_library": {
+    "allowable_clients": [
+      {
+        "clients": [
+          "ClientAll"
+        ]
+      }
+    ],
+    "install_names": [
+      {
+        "name": "/usr/lib/libfoo.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "min_deployment": "13.1",
+        "target": "x86_64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+;--- libbar.tbd
+--- !tapi-tbd
+tbd-version:     4
+targets:         [ arm64-macos ]
+install-name:    '/usr/lib/libbar.dylib'
+allowable-clients:
+  - targets:         [ arm64-macos ]
+    clients:         [ ClientAll ]
+reexported-libraries:
+  - targets:         [ arm64-macos ]
+    libraries:       [ '/usr/lib/liball.dylib' ]
+exports:
+  - targets:         [ arm64-macos ]
+    symbols:         [ _sym1 ]
+    objc-classes:    [ _A ]
+    objc-ivars:      [ _A._ivar1 ]
+    weak-symbols:    [ _weak1 ]
+    thread-local-symbols: [ _tlv1 ]
+...
diff --git a/llvm/test/tools/llvm-readtapi/merge.test b/llvm/test/tools/llvm-readtapi/merge.test
new file mode 100644
index 000000000000000..a0aa7ac55e3cff2
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/merge.test
@@ -0,0 +1,139 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: llvm-readtapi -merge %t/i386.tbd %t/x86_64.tbd %t/arm64.tbd --filetype tbd-v5 -o %t/out.tbd -compact 2>&1 | FileCheck %s --allow-empty 
+; RUN: llvm-readtapi -merge %t/i386.tbd %t/x86_64.tbd %t/arm64.tbd --filetype=tbd-v5 --o %t/out.tbd -compact 2>&1 | FileCheck %s --allow-empty 
+; RUN: llvm-readtapi -compare %t/out.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty 
+
+; CHECK-NOT: error 
+; CHECK-NOT: warning
+
+;--- expected.tbd
+{
+    "main_library": {
+        "allowable_clients": [{ "clients": ["ClientAll"] }],
+        "exported_symbols": [
+            {
+                "data": {
+                    "global": ["_sym1"],
+                    "objc_class": ["_A"],
+                    "thread_local": ["_tlv1"],
+                    "weak": ["_weak1"]
+                }
+            },
+            {
+                "data": {
+                    "objc_ivar": ["_A._ivar1"]
+                },
+                "targets": [ "x86_64-macos", "arm64-macos" ]
+            }
+        ],
+        "install_names": [
+            { "name": "/usr/lib/libfat.dylib" }
+        ],
+        "reexported_libraries": [
+            {
+                "names": [ "/usr/lib/liball.dylib" ]
+            }
+        ],
+        "target_info": [
+            { "target": "i386-macos" },
+            {
+                "min_deployment": "13.1",
+                "target": "x86_64-macos"
+            },
+            {
+                "target": "arm64-macos"
+            }
+        ]
+    },
+    "tapi_tbd_version": 5
+}
+
+
+;--- i386.tbd
+--- !tapi-tbd-v3
+archs:           [ i386 ]
+platform:        macosx
+install-name:    /usr/lib/libfat.dylib
+exports:         
+  - archs:           [ i386 ]
+    allowable-clients: [ ClientAll ]
+    re-exports:      [ /usr/lib/liball.dylib ]
+    symbols:         [ _sym1 ]
+    objc-classes:    [ _A ]
+    weak-def-symbols: [ _weak1 ]
+    thread-local-symbols: [ _tlv1 ]
+...
+
+;--- x86_64.tbd
+{
+  "main_library": {
+    "allowable_clients": [
+      {
+        "clients": [
+          "ClientAll"
+        ]
+      }
+    ],
+    "exported_symbols": [
+      {
+        "data": {
+          "global": [
+            "_sym1"
+          ],
+          "objc_class": [
+            "_A"
+          ],
+          "objc_ivar": [
+            "_A._ivar1"
+          ],
+          "thread_local": [
+            "_tlv1"
+          ],
+          "weak": [
+            "_weak1"
+          ]
+        }
+      }
+    ],
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/liball.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "min_deployment": "13.1",
+        "target": "x86_64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+;--- arm64.tbd
+--- !tapi-tbd
+tbd-version:     4
+targets:         [ arm64-macos ]
+install-name:    '/usr/lib/libfat.dylib'
+allowable-clients:
+  - targets:         [ arm64-macos ]
+    clients:         [ ClientAll ]
+reexported-libraries:
+  - targets:         [ arm64-macos ]
+    libraries:       [ '/usr/lib/liball.dylib' ]
+exports:
+  - targets:         [ arm64-macos ]
+    symbols:         [ _sym1 ]
+    objc-classes:    [ _A ]
+    objc-ivars:      [ _A._ivar1 ]
+    weak-symbols:    [ _weak1 ]
+    thread-local-symbols: [ _tlv1 ]
+...
diff --git a/llvm/test/tools/llvm-readtapi/write.test b/llvm/test/tools/llvm-readtapi/write.test
new file mode 100644
index 000000000000000..1ec7a40a2e40586
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/write.test
@@ -0,0 +1,77 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: llvm-readtapi %t/arm64.tbd 2>&1 | FileCheck %s
+
+; CHECK-NOT: error 
+; CHECK-NOT: warning
+; CHECK: {
+; CHECK-NEXT:   "main_library": {
+; CHECK-NEXT:     "allowable_clients": [
+; CHECK-NEXT:       {
+; CHECK-NEXT:         "clients": [
+; CHECK-NEXT:           "ClientAll"
+; CHECK-NEXT:         ]
+; CHECK-NEXT:       }
+; CHECK-NEXT:     ],
+; CHECK-NEXT:     "exported_symbols": [
+; CHECK-NEXT:       {
+; CHECK-NEXT:         "data": {
+; CHECK-NEXT:           "global": [
+; CHECK-NEXT:             "_sym1"
+; CHECK-NEXT:           ],
+; CHECK-NEXT:           "objc_class": [
+; CHECK-NEXT:             "_A"
+; CHECK-NEXT:           ],
+; CHECK-NEXT:           "objc_ivar": [
+; CHECK-NEXT:             "_A._ivar1"
+; CHECK-NEXT:           ],
+; CHECK-NEXT:           "thread_local": [
+; CHECK-NEXT:             "_tlv1"
+; CHECK-NEXT:           ],
+; CHECK-NEXT:           "weak": [
+; CHECK-NEXT:             "_weak1"
+; CHECK-NEXT:           ]
+; CHECK-NEXT:         }
+; CHECK-NEXT:       }
+; CHECK-NEXT:     ],
+; CHECK-NEXT:     "install_names": [
+; CHECK-NEXT:       {
+; CHECK-NEXT:         "name": "/usr/lib/libfat.dylib"
+; CHECK-NEXT:       }
+; CHECK-NEXT:     ],
+; CHECK-NEXT:     "reexported_libraries": [
+; CHECK-NEXT:       {
+; CHECK-NEXT:         "names": [
+; CHECK-NEXT:           "/usr/lib/liball.dylib"
+; CHECK-NEXT:         ]
+; CHECK-NEXT:       }
+; CHECK-NEXT:     ],
+; CHECK-NEXT:     "target_info": [
+; CHECK-NEXT:       {
+; CHECK-NEXT:         "target": "arm64-macos"
+; CHECK-NEXT:       }
+; CHECK-NEXT:     ]
+; CHECK-NEXT:   },
+; CHECK-NEXT:   "tapi_tbd_version": 5
+; CHECK-NEXT: }
+
+
+;--- arm64.tbd
+--- !tapi-tbd
+tbd-version:     4
+targets:         [ arm64-macos ]
+install-name:    '/usr/lib/libfat.dylib'
+allowable-clients:
+  - targets:         [ arm64-macos ]
+    clients:         [ ClientAll ]
+reexported-libraries:
+  - targets:         [ arm64-macos ]
+    libraries:       [ '/usr/lib/liball.dylib' ]
+exports:
+  - targets:         [ arm64-macos ]
+    symbols:         [ _sym1 ]
+    objc-classes:    [ _A ]
+    objc-ivars:      [ _A._ivar1 ]
+    weak-symbols:    [ _weak1 ]
+    thread-local-symbols: [ _tlv1 ]
+...
diff --git a/llvm/tools/llvm-readtapi/DiffEngine.cpp b/llvm/tools/llvm-readtapi/DiffEngine.cpp
index 5f4b25ca6c0b2de..40722d2da180fd1 100644
--- a/llvm/tools/llvm-readtapi/DiffEngine.cpp
+++ b/llvm/tools/llvm-readtapi/DiffEngine.cpp
@@ -560,13 +560,11 @@ void DiffEngine::printDifferences(raw_ostream &OS,
 }
 
 bool DiffEngine::compareFiles(raw_ostream &OS) {
-  const auto *IFLHS = &(FileLHS->getInterfaceFile());
-  const auto *IFRHS = &(FileRHS->getInterfaceFile());
-  if (*IFLHS == *IFRHS)
+  if (*FileLHS == *FileRHS)
     return false;
-  OS << "< " << std::string(IFLHS->getPath().data()) << "\n> "
-     << std::string(IFRHS->getPath().data()) << "\n\n";
-  std::vector<DiffOutput> Diffs = findDifferences(IFLHS, IFRHS);
+  OS << "< " << std::string(FileLHS->getPath().data()) << "\n> "
+     << std::string(FileRHS->getPath().data()) << "\n\n";
+  std::vector<DiffOutput> Diffs = findDifferences(FileLHS, FileRHS);
   printDifferences(OS, Diffs, 0);
   return true;
 }
diff --git a/llvm/tools/llvm-readtapi/DiffEngine.h b/llvm/tools/llvm-readtapi/DiffEngine.h
index 27b72573d011e62..5f7c29c920814f7 100644
--- a/llvm/tools/llvm-readtapi/DiffEngine.h
+++ b/llvm/tools/llvm-readtapi/DiffEngine.h
@@ -141,14 +141,14 @@ class InlineDoc : public AttributeDiff {
 /// output of the differences found in the files.
 class DiffEngine {
 public:
-  DiffEngine(object::TapiUniversal *InputFileNameLHS,
-             object::TapiUniversal *InputFileNameRHS)
+  DiffEngine(MachO::InterfaceFile *InputFileNameLHS,
+             MachO::InterfaceFile *InputFileNameRHS)
       : FileLHS(InputFileNameLHS), FileRHS(InputFileNameRHS){};
   bool compareFiles(raw_ostream &);
 
 private:
-  object::TapiUniversal *FileLHS;
-  object::TapiUniversal *FileRHS;
+  MachO::InterfaceFile *FileLHS;
+  MachO::InterfaceFile *FileRHS;
 
   /// Function that prints the differences found in the files.
   void printDifferences(raw_ostream &, const std::vector<DiffOutput> &, int);
diff --git a/llvm/tools/llvm-readtapi/TapiOpts.td b/llvm/tools/llvm-readtapi/TapiOpts.td
index 932a21f7d071199..1efa86ea3ae48d6 100644
--- a/llvm/tools/llvm-readtapi/TapiOpts.td
+++ b/llvm/tools/llvm-readtapi/TapiOpts.td
@@ -8,12 +8,16 @@ multiclass JS<string name, string help, string var = ""> {
 }
 
 //
-// General Driver options 
+// Top level operations 
 //
-def help : FF<"help", "display this help">;
-defm output: JS<"o", "write output to <file>","<file>">;
+def action_group : OptionGroup<"action group">;
+def compare : FF<"compare", "compare tapi files for library differences">, Group<action_group>;
+def merge : FF<"merge", "merge the input files that represent the same library">, Group<action_group>;
 
 //
-// Compare options
+// General Driver options 
 //
-def compare : FF<"compare", "compare tapi files for library differences">;
+def help : FF<"help", "display this help">;
+defm output: JS<"o", "write output to <file>","<file>">;
+def compact: FF<"compact", "write compact tapi output file">;
+defm filetype: JS<"filetype", "specify the output file type (tbd-v3, tbd-v4 or tbd-v5)","<value>">;
diff --git a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
index 1307916120e98e6..3e0bcc49d19cc4c 100644
--- a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
+++ b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
@@ -10,7 +10,6 @@
 //
 //===----------------------------------------------------------------------===//
 #include "DiffEngine.h"
-#include "llvm/Object/TapiUniversal.h"
 #include "llvm/Option/Arg.h"
 #include "llvm/Option/ArgList.h"
 #include "llvm/Option/Option.h"
@@ -21,6 +20,8 @@
 #include "llvm/Support/WithColor.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TextAPI/TextAPIError.h"
+#include "llvm/TextAPI/TextAPIReader.h"
+#include "llvm/TextAPI/TextAPIWriter.h"
 #include <cstdlib>
 
 using namespace llvm;
@@ -56,18 +57,32 @@ class TAPIOptTable : public opt::GenericOptTable {
   }
 };
 
+// Handle error reporting in cases where `ExitOnError` is not used.
+void reportError(Twine Message, int ExitCode = EXIT_FAILURE) {
+  WithColor::error(errs()) << Message << "\n";
+  errs().flush();
+  exit(ExitCode);
+}
+
 struct Context {
   std::vector<std::string> Inputs;
   std::unique_ptr<llvm::raw_fd_stream> OutStream;
+  FileType WriteFT = FileType::TBD_V5;
+  bool Compact = false;
 };
 
-Expected<std::unique_ptr<Binary>>
-convertFileToBinary(const StringRef Filename) {
+std::unique_ptr<InterfaceFile> getInterfaceFile(const StringRef Filename,
+                                                ExitOnError &ExitOnErr) {
+  ExitOnErr.setBanner("error: '" + Filename.str() + "' ");
   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
-      MemoryBuffer::getFileOrSTDIN(Filename);
+      MemoryBuffer::getFile(Filename);
   if (BufferOrErr.getError())
-    return errorCodeToError(BufferOrErr.getError());
-  return createBinary(BufferOrErr.get()->getMemBufferRef());
+    ExitOnErr(errorCodeToError(BufferOrErr.getError()));
+  Expected<std::unique_ptr<InterfaceFile>> IF =
+      TextAPIReader::get((*BufferOrErr)->getMemBufferRef());
+  if (!IF)
+    ExitOnErr(IF.takeError());
+  return std::move(*IF);
 }
 
 // Use unique exit code to differentiate failures not directly caused from
@@ -76,33 +91,50 @@ convertFileToBinary(const StringRef Filename) {
 const int NON_TAPI_EXIT_CODE = 2;
 
 bool handleCompareAction(const Context &Ctx) {
+  if (Ctx.Inputs.size() != 2)
+    reportError("compare only supports two input files",
+                /*ExitCode=*/NON_TAPI_EXIT_CODE);
+
   ExitOnError ExitOnErr("error: ", /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE);
-  if (Ctx.Inputs.size() != 2) {
-    ExitOnErr(make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
-                                       "compare only supports 2 input files"));
-  }
-  StringRef InputFileName = Ctx.Inputs.front();
-  ExitOnErr.setBanner("error: '" + InputFileName.str() + "' ");
-  auto BinLHS = ExitOnErr(convertFileToBinary(InputFileName));
-
-  TapiUniversal *FileLHS = dyn_cast<TapiUniversal>(BinLHS.get());
-  if (!FileLHS) {
-    ExitOnErr(createStringError(std::errc::executable_format_error,
-                                "unsupported file format"));
-  }
+  auto LeftIF = getInterfaceFile(Ctx.Inputs.front(), ExitOnErr);
+  auto RightIF = getInterfaceFile(Ctx.Inputs.at(1), ExitOnErr);
 
-  StringRef CompareInputFileName = Ctx.Inputs.at(1);
-  ExitOnErr.setBanner("error: '" + CompareInputFileName.str() + "' ");
-  auto BinRHS = ExitOnErr(convertFileToBinary(CompareInputFileName));
+  raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
+  return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS);
+}
 
-  TapiUniversal *FileRHS = dyn_cast<TapiUniversal>(BinRHS.get());
-  if (!FileRHS) {
-    ExitOnErr(createStringError(std::errc::executable_format_error,
-                                "unsupported file format"));
+bool handleWriteAction(const Context &Ctx,
+                       std::unique_ptr<InterfaceFile> Out = nullptr) {
+  ExitOnError ExitOnErr("error: ");
+  if (!Out) {
+    if (Ctx.Inputs.size() != 1)
+      reportError("write only supports one input file");
+    Out = getInterfaceFile(Ctx.Inputs.front(), ExitOnErr);
   }
-
   raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();
-  return DiffEngine(FileLHS, FileRHS).compareFiles(OS);
+  ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact));
+  return EXIT_SUCCESS;
+}
+
+bool handleMergeAction(const Context &Ctx) {
+  if (Ctx.Inputs.size() < 2)
+    reportError("merge requires at least two input files");
+
+  ExitOnError ExitOnErr("error: ");
+  std::unique_ptr<InterfaceFile> Out;
+  for (StringRef FileName : Ctx.Inputs) {
+    auto IF = getInterfaceFile(FileName, ExitOnErr);
+    // On the first iteration copy the input file and skip merge.
+    if (!Out) {
+      Out = std::move(IF);
+      continue;
+    }
+    auto ResultIF = Out->merge(IF.get());
+    if (!ResultIF)
+      ExitOnErr(ResultIF.takeError());
+    Out = std::move(ResultIF.get());
+  }
+  return handleWriteAction(Ctx, std::move(Out));
 }
 
 } // anonymous namespace
@@ -138,8 +170,39 @@ int main(int Argc, char **Argv) {
     }
   }
 
-  if (Args.hasArg(OPT_compare))
+  Ctx.Compact = Args.hasArg(OPT_compact);
+
+  if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) {
+    StringRef FT = A->getValue();
+    Ctx.WriteFT = TextAPIWriter::parseFileType(FT);
+    if (Ctx.WriteFT < FileType::TBD_V3)
+      reportError("deprecated filetype '" + FT + "' is not supported to write");
+    if (Ctx.WriteFT == FileType::Invalid)
+      reportError("unsupported filetype '" + FT + "'");
+  }
+
+  // Handle top level and exclusive operation.
+  SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group));
+
+  if (ActionArgs.empty())
+    // If no action specified, write out tapi file in requested format.
+    return handleWriteAction(Ctx);
+
+  if (ActionArgs.size() > 1) {
+    std::string Buf;
+    raw_string_ostream OS(Buf);
+    OS << "only one of the following actions can be specified:";
+    for (auto *Arg : ActionArgs)
+      OS << " " << Arg->getSpelling();
+    reportError(OS.str());
+  }
+
+  switch (ActionArgs.front()->getOption().getID()) {
+  case OPT_compare:
     return handleCompareAction(Ctx);
+  case OPT_merge:
+    return handleMergeAction(Ctx);
+  }
 
   return EXIT_SUCCESS;
 }

>From f59f1b5ec41198c6219a625455cc4b251a95369d Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Thu, 16 Nov 2023 12:58:09 -0800
Subject: [PATCH 2/2] [readtapi] Add Extract & Remove architecture
 functionality

This adds functionality to tbd files similiar to what `lipo -extract`
and `lipo -remove` does for binaries.
---
 .../tools/llvm-readtapi/command-line.test     |   2 +-
 .../tools/llvm-readtapi/extract-invalid.test  |  53 ++++
 llvm/test/tools/llvm-readtapi/extract.test    | 201 +++++++++++++
 .../tools/llvm-readtapi/remove-invalid.test   |  57 ++++
 llvm/test/tools/llvm-readtapi/remove.test     | 274 ++++++++++++++++++
 llvm/tools/llvm-readtapi/TapiOpts.td          |   3 +
 llvm/tools/llvm-readtapi/llvm-readtapi.cpp    |  36 ++-
 7 files changed, 623 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readtapi/extract-invalid.test
 create mode 100644 llvm/test/tools/llvm-readtapi/extract.test
 create mode 100644 llvm/test/tools/llvm-readtapi/remove-invalid.test
 create mode 100644 llvm/test/tools/llvm-readtapi/remove.test

diff --git a/llvm/test/tools/llvm-readtapi/command-line.test b/llvm/test/tools/llvm-readtapi/command-line.test
index 400e0670e5aa067..67e485ddd7ef501 100644
--- a/llvm/test/tools/llvm-readtapi/command-line.test
+++ b/llvm/test/tools/llvm-readtapi/command-line.test
@@ -4,7 +4,7 @@
 ; RUN: not llvm-readtapi -merge -compact %t/tmp.tbd %t/tmp2.tbd --filetype=tbd-v2 2>&1 | FileCheck %s --check-prefix FILE_FORMAT 
 
 CHECK: OVERVIEW: LLVM TAPI file reader and manipulator
-CHECK: USAGE: llvm-readtapi [options] <inputs>
+CHECK: llvm-readtapi [options]* [-arch <arch>]* <inputs> [-o <output>]*
 CHECK: OPTIONS:
 CHECK:   -help    display this help
 
diff --git a/llvm/test/tools/llvm-readtapi/extract-invalid.test b/llvm/test/tools/llvm-readtapi/extract-invalid.test
new file mode 100644
index 000000000000000..026b782eb00c122
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/extract-invalid.test
@@ -0,0 +1,53 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: not llvm-readtapi -extract %t/libfoo.tbd %t/libbar.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix EXTRA
+; RUN: not llvm-readtapi -extract %t/libfoo.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix MISSING
+; RUN: not llvm-readtapi -arch x86_64 -extract %t/libfoo.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix MISMATCH 
+
+; EXTRA: error: extract only supports one input file
+; MISSING:  extract requires -arch <arch>
+; MISMATCH: error: {{.*}}libfoo.tbd' file doesn't have architecture 'x86_64'
+
+;--- libfoo.tbd
+--- !tapi-tbd
+tbd-version:     4
+targets:         [ arm64-ios ]
+flags:           [ not_app_extension_safe ]
+install-name:    '/usr/lib/libfoo.dylib'
+exports:
+  - targets:         [ arm64-ios ]
+    symbols:         [ _bar ]
+...
+
+;--- libbar.tbd
+{
+  "main_library": {
+    "exported_symbols": [
+      {
+        "data": {
+          "global": [
+            "_bar"
+          ]
+        }
+      }
+    ],
+    "flags": [
+      {
+        "attributes": [
+          "not_app_extension_safe"
+        ]
+      }
+    ],
+    "install_names": [
+      {
+        "name": "/usr/lib/libbar.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "target": "arm64-ios"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
diff --git a/llvm/test/tools/llvm-readtapi/extract.test b/llvm/test/tools/llvm-readtapi/extract.test
new file mode 100644
index 000000000000000..175efcda2d7cba1
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/extract.test
@@ -0,0 +1,201 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t 
+; RUN: llvm-readtapi -arch x86_64 -extract %t/libfat.tbd -compact -o %t/libslim.tbd 2>&1 | FileCheck --allow-empty %s
+; RUN: llvm-readtapi --compare %t/libslim.tbd %t/libslim_expected.tbd 2>&1 | FileCheck --allow-empty %s
+
+; RUN: llvm-readtapi -arch armv7s --extract %t/libfat2.tbd 2>&1 | FileCheck %s --check-prefix OUTPUT 
+
+; CHECK-NOT: error
+; CHECK-NOT: warning
+
+; OUTPUT: {
+; OUTPUT-NEXT:  "main_library": {
+; OUTPUT-NEXT:    "install_names": [
+; OUTPUT-NEXT:      {
+; OUTPUT-NEXT:        "name": "/usr/lib/libfat.dylib"
+; OUTPUT-NEXT:      }
+; OUTPUT-NEXT:    ],
+; OUTPUT-NEXT:    "target_info": [
+; OUTPUT-NEXT:      {
+; OUTPUT-NEXT:        "target": "armv7s-ios"
+; OUTPUT-NEXT:      }
+; OUTPUT-NEXT:    ]
+; OUTPUT-NEXT:  },
+; OUTPUT-NEXT:  "tapi_tbd_version": 5
+; OUTPUT-NEXT: }
+
+//--- libfat.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          },
+          "targets": [
+            "x86_64-macos"
+          ]
+        },
+        {
+          "data": {
+            "global": [
+              "_sym2"
+            ]
+          },
+          "targets": [
+            "x86_64h-macos"
+          ]
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        },
+        {
+          "target": "x86_64h-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      },
+      {
+        "target": "x86_64h-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libfat2.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          }
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "arm64-ios"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ],
+        "targets": [
+          "arm64-ios"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "armv7s-ios"
+      },
+      {
+        "target": "arm64-ios"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libslim_expected.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          }
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
diff --git a/llvm/test/tools/llvm-readtapi/remove-invalid.test b/llvm/test/tools/llvm-readtapi/remove-invalid.test
new file mode 100644
index 000000000000000..e9de48c9745bfb6
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/remove-invalid.test
@@ -0,0 +1,57 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t 
+; RUN: not llvm-readtapi --remove -arch arm64 %t/libslim.tbd 2>&1 | FileCheck %s
+
+CHECK: {{.*}}libslim.tbd' cannot remove last architecture slice 'arm64'
+
+//--- libslim.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          }
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "arm64-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "arm64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
diff --git a/llvm/test/tools/llvm-readtapi/remove.test b/llvm/test/tools/llvm-readtapi/remove.test
new file mode 100644
index 000000000000000..673634762f3823f
--- /dev/null
+++ b/llvm/test/tools/llvm-readtapi/remove.test
@@ -0,0 +1,274 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t 
+; RUN: llvm-readtapi --remove -arch x86_64h %t/libfat.tbd -o %t/libslim.tbd 2>&1 | FileCheck --allow-empty %s
+; RUN: llvm-readtapi --compare %t/libslim.tbd %t/libslim_expected.tbd
+
+; RUN: llvm-readtapi --remove -arch x86_64h %t/libfat2.tbd -o %t/libslim2.tbd 2>&1 | FileCheck --allow-empty %s
+; RUN: llvm-readtapi --compare %t/libslim2.tbd %t/libslim_expected.tbd
+
+; RUN: llvm-readtapi --remove -arch x86_64 %t/libfat3.tbd -o %t/libslim3.tbd 2>&1 | FileCheck --allow-empty %s
+; RUN: llvm-readtapi --compare %t/libslim3.tbd %t/libslim3_expected.tbd
+
+; CHECK-NOT: error
+; CHECK-NOT: warning
+
+//--- libfat.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          },
+          "targets": [
+            "x86_64-macos"
+          ]
+        },
+        {
+          "data": {
+            "global": [
+              "_sym2"
+            ]
+          },
+          "targets": [
+            "x86_64h-macos"
+          ]
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        },
+        {
+          "target": "x86_64h-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      },
+      {
+        "target": "x86_64h-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libfat2.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          },
+          "targets": [
+            "x86_64-macos"
+          ]
+        },
+        {
+          "data": {
+            "global": [
+              "_sym2"
+            ]
+          },
+          "targets": [
+            "x86_64h-macos"
+          ]
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        },
+        {
+          "target": "x86_64h-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libfat3.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          }
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ],
+        "targets": [
+          "x86_64-macos"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      },
+      {
+        "target": "x86_64h-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libslim_expected.tbd
+{
+  "libraries": [
+    {
+      "exported_symbols": [
+        {
+          "data": {
+            "global": [
+              "_sym1"
+            ]
+          }
+        }
+      ],
+      "install_names": [
+        {
+          "name": "/usr/lib/internal/libfat.dylib"
+        }
+      ],
+      "parent_umbrellas": [
+        {
+          "umbrella": "fat"
+        }
+      ],
+      "target_info": [
+        {
+          "target": "x86_64-macos"
+        }
+      ]
+    }
+  ],
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "reexported_libraries": [
+      {
+        "names": [
+          "/usr/lib/internal/libfat.dylib"
+        ]
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- libslim3_expected.tbd
+{
+  "main_library": {
+    "install_names": [
+      {
+        "name": "/usr/lib/libfat.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "target": "x86_64h-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
diff --git a/llvm/tools/llvm-readtapi/TapiOpts.td b/llvm/tools/llvm-readtapi/TapiOpts.td
index 1efa86ea3ae48d6..8fda035142a2ea0 100644
--- a/llvm/tools/llvm-readtapi/TapiOpts.td
+++ b/llvm/tools/llvm-readtapi/TapiOpts.td
@@ -13,6 +13,8 @@ multiclass JS<string name, string help, string var = ""> {
 def action_group : OptionGroup<"action group">;
 def compare : FF<"compare", "compare tapi files for library differences">, Group<action_group>;
 def merge : FF<"merge", "merge the input files that represent the same library">, Group<action_group>;
+def extract: FF<"extract", "extract architecture from input file">, Group<action_group>;
+def remove: FF<"remove", "remove architecture from input file">, Group<action_group>;
 
 //
 // General Driver options 
@@ -21,3 +23,4 @@ def help : FF<"help", "display this help">;
 defm output: JS<"o", "write output to <file>","<file>">;
 def compact: FF<"compact", "write compact tapi output file">;
 defm filetype: JS<"filetype", "specify the output file type (tbd-v3, tbd-v4 or tbd-v5)","<value>">;
+defm arch: JS<"arch", "specify the architecture", "<architecture>">;
diff --git a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
index 3e0bcc49d19cc4c..2527d1f143b4b43 100644
--- a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
+++ b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp
@@ -69,6 +69,7 @@ struct Context {
   std::unique_ptr<llvm::raw_fd_stream> OutStream;
   FileType WriteFT = FileType::TBD_V5;
   bool Compact = false;
+  Architecture Arch = AK_unknown;
 };
 
 std::unique_ptr<InterfaceFile> getInterfaceFile(const StringRef Filename,
@@ -137,6 +138,25 @@ bool handleMergeAction(const Context &Ctx) {
   return handleWriteAction(Ctx, std::move(Out));
 }
 
+using IFOperation =
+    std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>(
+        const llvm::MachO::InterfaceFile &, Architecture)>;
+bool handleSingleFileAction(const Context &Ctx, const StringRef Action,
+                            IFOperation act) {
+  if (Ctx.Inputs.size() != 1)
+    reportError(Action + " only supports one input file");
+  if (Ctx.Arch == AK_unknown)
+    reportError(Action + " requires -arch <arch>");
+
+  ExitOnError ExitOnErr("error: ");
+  auto IF = getInterfaceFile(Ctx.Inputs.front(), ExitOnErr);
+  auto OutIF = act(*IF, Ctx.Arch);
+  if (!OutIF)
+    ExitOnErr(OutIF.takeError());
+
+  return handleWriteAction(Ctx, std::move(*OutIF));
+}
+
 } // anonymous namespace
 
 int main(int Argc, char **Argv) {
@@ -151,8 +171,10 @@ int main(int Argc, char **Argv) {
         exit(1);
       });
   if (Args.hasArg(OPT_help)) {
-    Tbl.printHelp(outs(), "llvm-readtapi [options] <inputs>",
-                  "LLVM TAPI file reader and manipulator");
+    Tbl.printHelp(
+        outs(),
+        "llvm-readtapi [options]* [-arch <arch>]* <inputs> [-o <output>]*",
+        "LLVM TAPI file reader and manipulator");
     return EXIT_SUCCESS;
   }
 
@@ -181,6 +203,12 @@ int main(int Argc, char **Argv) {
       reportError("unsupported filetype '" + FT + "'");
   }
 
+  if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) {
+    StringRef Arch = A->getValue();
+    Ctx.Arch = getArchitectureFromName(Arch);
+    if (Ctx.Arch == AK_unknown)
+      reportError("unsupported architecture '" + Arch);
+  }
   // Handle top level and exclusive operation.
   SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group));
 
@@ -202,6 +230,10 @@ int main(int Argc, char **Argv) {
     return handleCompareAction(Ctx);
   case OPT_merge:
     return handleMergeAction(Ctx);
+  case OPT_extract:
+    return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract);
+  case OPT_remove:
+    return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove);
   }
 
   return EXIT_SUCCESS;



More information about the llvm-commits mailing list