[llvm] d6f9b97 - Reland "[TextAPI] Implement TBDv5 Writer"

Cyndy Ishida via llvm-commits llvm-commits at lists.llvm.org
Wed Feb 22 12:02:48 PST 2023


Author: Cyndy Ishida
Date: 2023-02-22T12:01:37-08:00
New Revision: d6f9b97bae97b9a476f36e5d6cebe8ac13c1914e

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

LOG: Reland "[TextAPI] Implement TBDv5 Writer"

Create writer for new JSON format.
The new JSON format allows practically all attributes to be defined per
target in a universal library however the internal representation only
allows one for the time being. For now the write will always write those
attributes as default available for all targets (install name,
compatability & current version, swift abi, flags e.g. flatnamepace &
app exenstion safety)

rdar://102076911

Reviewed By: ributzka

Differential Revision: https://reviews.llvm.org/D144339

Added: 
    

Modified: 
    llvm/include/llvm/TextAPI/InterfaceFile.h
    llvm/include/llvm/TextAPI/PackedVersion.h
    llvm/include/llvm/TextAPI/TextAPIWriter.h
    llvm/lib/TextAPI/PackedVersion.cpp
    llvm/lib/TextAPI/TextStub.cpp
    llvm/lib/TextAPI/TextStubCommon.h
    llvm/lib/TextAPI/TextStubV5.cpp
    llvm/unittests/TextAPI/TextStubV5Tests.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/TextAPI/InterfaceFile.h b/llvm/include/llvm/TextAPI/InterfaceFile.h
index c32917da69f8a..2ee22ee4c8f2d 100644
--- a/llvm/include/llvm/TextAPI/InterfaceFile.h
+++ b/llvm/include/llvm/TextAPI/InterfaceFile.h
@@ -396,7 +396,16 @@ class InterfaceFile {
 
   const_filtered_symbol_range exports() const {
     std::function<bool(const Symbol *)> fn = [](const Symbol *Symbol) {
-      return !Symbol->isUndefined();
+      return !Symbol->isUndefined() && !Symbol->isReexported();
+    };
+    return make_filter_range(
+        make_range<const_symbol_iterator>({Symbols.begin()}, {Symbols.end()}),
+        fn);
+  }
+
+  const_filtered_symbol_range reexports() const {
+    std::function<bool(const Symbol *)> fn = [](const Symbol *Symbol) {
+      return Symbol->isReexported();
     };
     return make_filter_range(
         make_range<const_symbol_iterator>({Symbols.begin()}, {Symbols.end()}),

diff  --git a/llvm/include/llvm/TextAPI/PackedVersion.h b/llvm/include/llvm/TextAPI/PackedVersion.h
index 24bec2ebe8fcd..eafa508967352 100644
--- a/llvm/include/llvm/TextAPI/PackedVersion.h
+++ b/llvm/include/llvm/TextAPI/PackedVersion.h
@@ -14,6 +14,7 @@
 #define LLVM_TEXTAPI_PACKEDVERSION_H
 
 #include <cstdint>
+#include <string>
 #include <utility>
 
 namespace llvm {
@@ -53,6 +54,8 @@ class PackedVersion {
 
   uint32_t rawValue() const { return Version; }
 
+  operator std::string() const;
+
   void print(raw_ostream &OS) const;
 };
 

diff  --git a/llvm/include/llvm/TextAPI/TextAPIWriter.h b/llvm/include/llvm/TextAPI/TextAPIWriter.h
index f9857a806f60a..9bdaaf58d09f3 100644
--- a/llvm/include/llvm/TextAPI/TextAPIWriter.h
+++ b/llvm/include/llvm/TextAPI/TextAPIWriter.h
@@ -22,7 +22,8 @@ class TextAPIWriter {
 public:
   TextAPIWriter() = delete;
 
-  static Error writeToStream(raw_ostream &os, const InterfaceFile &);
+  static Error writeToStream(raw_ostream &OS, const InterfaceFile &File,
+                             bool Compact = false);
 };
 
 } // end namespace MachO.

diff  --git a/llvm/lib/TextAPI/PackedVersion.cpp b/llvm/lib/TextAPI/PackedVersion.cpp
index 67fb30aeb127b..22960c33e9ee8 100644
--- a/llvm/lib/TextAPI/PackedVersion.cpp
+++ b/llvm/lib/TextAPI/PackedVersion.cpp
@@ -100,6 +100,13 @@ std::pair<bool, bool> PackedVersion::parse64(StringRef Str) {
   return std::make_pair(true, Truncated);
 }
 
+PackedVersion::operator std::string() const {
+  SmallString<32> Str;
+  raw_svector_ostream OS(Str);
+  print(OS);
+  return std::string(Str);
+}
+
 void PackedVersion::print(raw_ostream &OS) const {
   OS << format("%d", getMajor());
   if (getMinor() || getSubminor())

diff  --git a/llvm/lib/TextAPI/TextStub.cpp b/llvm/lib/TextAPI/TextStub.cpp
index 73cb61472bff3..c51edc17a5bee 100644
--- a/llvm/lib/TextAPI/TextStub.cpp
+++ b/llvm/lib/TextAPI/TextStub.cpp
@@ -1159,10 +1159,17 @@ TextAPIReader::get(MemoryBufferRef InputBuffer) {
   return std::move(File);
 }
 
-Error TextAPIWriter::writeToStream(raw_ostream &OS, const InterfaceFile &File) {
+Error TextAPIWriter::writeToStream(raw_ostream &OS, const InterfaceFile &File,
+                                   bool Compact) {
   TextAPIContext Ctx;
   Ctx.Path = std::string(File.getPath());
   Ctx.FileKind = File.getFileType();
+
+  // Write out in JSON format.
+  if (Ctx.FileKind >= FileType::TBD_V5) {
+    return serializeInterfaceFileToJSON(OS, File, Compact);
+  }
+
   llvm::yaml::Output YAMLOut(OS, &Ctx, /*WrapColumn=*/80);
 
   std::vector<const InterfaceFile *> Files;

diff  --git a/llvm/lib/TextAPI/TextStubCommon.h b/llvm/lib/TextAPI/TextStubCommon.h
index 51b423137f4e9..9558bd9e2d352 100644
--- a/llvm/lib/TextAPI/TextStubCommon.h
+++ b/llvm/lib/TextAPI/TextStubCommon.h
@@ -45,6 +45,9 @@ class PackedVersion;
 
 Expected<std::unique_ptr<InterfaceFile>>
 getInterfaceFileFromJSON(StringRef JSON);
+
+Error serializeInterfaceFileToJSON(raw_ostream &OS, const InterfaceFile &File,
+                                   bool Compact);
 } // namespace MachO
 
 namespace yaml {

diff  --git a/llvm/lib/TextAPI/TextStubV5.cpp b/llvm/lib/TextAPI/TextStubV5.cpp
index f0ef10dc0a5ca..43519c06d0888 100644
--- a/llvm/lib/TextAPI/TextStubV5.cpp
+++ b/llvm/lib/TextAPI/TextStubV5.cpp
@@ -161,6 +161,10 @@ static llvm::SmallString<128> getParseErrorMsg(TBDKey Key) {
   return {"invalid ", Keys[Key], " section"};
 }
 
+static llvm::SmallString<128> getSerializeErrorMsg(TBDKey Key) {
+  return {"missing ", Keys[Key], " information"};
+}
+
 class JSONStubError : public llvm::ErrorInfo<llvm::json::ParseError> {
 public:
   JSONStubError(Twine ErrMsg) : Message(ErrMsg.str()) {}
@@ -716,3 +720,294 @@ MachO::getInterfaceFileFromJSON(StringRef JSON) {
   }
   return std::move(IF);
 }
+
+namespace {
+
+template <typename ContainerT = Array>
+bool insertNonEmptyValues(Object &Obj, TBDKey Key, ContainerT &&Contents) {
+  if (Contents.empty())
+    return false;
+  Obj[Keys[Key]] = std::move(Contents);
+  return true;
+}
+
+std::string getFormattedStr(const MachO::Target &Targ) {
+  std::string PlatformStr = Targ.Platform == PLATFORM_MACCATALYST
+                                ? "maccatalyst"
+                                : getOSAndEnvironmentName(Targ.Platform);
+  return (getArchitectureName(Targ.Arch) + "-" + PlatformStr).str();
+}
+
+template <typename AggregateT>
+std::vector<std::string> serializeTargets(const AggregateT Targets,
+                                          const TargetList &ActiveTargets) {
+  std::vector<std::string> TargetsStr;
+  if (Targets.size() == ActiveTargets.size())
+    return TargetsStr;
+
+  llvm::for_each(Targets, [&TargetsStr](const MachO::Target &Target) {
+    TargetsStr.emplace_back(getFormattedStr(Target));
+  });
+  return TargetsStr;
+}
+
+Array serializeTargetInfo(const TargetList &ActiveTargets) {
+  Array Targets;
+  for (const auto Targ : ActiveTargets) {
+    Object TargetInfo;
+    TargetInfo[Keys[TBDKey::Deployment]] = Targ.MinDeployment.getAsString();
+    TargetInfo[Keys[TBDKey::Target]] = getFormattedStr(Targ);
+    Targets.emplace_back(std::move(TargetInfo));
+  }
+  return Targets;
+}
+
+template <typename ValueT, typename EntryT = ValueT>
+Array serializeScalar(TBDKey Key, ValueT Value, ValueT Default = ValueT()) {
+  if (Value == Default)
+    return {};
+  Array Container;
+  Object ScalarObj({Object::KV({Keys[Key], EntryT(Value)})});
+
+  Container.emplace_back(std::move(ScalarObj));
+  return Container;
+}
+
+using TargetsToValuesMap =
+    std::map<std::vector<std::string>, std::vector<std::string>>;
+
+template <typename AggregateT = TargetsToValuesMap>
+Array serializeAttrToTargets(AggregateT &Entries, TBDKey Key) {
+  Array Container;
+  for (const auto &[Targets, Values] : Entries) {
+    Object Obj;
+    insertNonEmptyValues(Obj, TBDKey::Targets, std::move(Targets));
+    Obj[Keys[Key]] = Values;
+    Container.emplace_back(std::move(Obj));
+  }
+  return Container;
+}
+
+template <typename ValueT = std::string,
+          typename AggregateT = std::vector<std::pair<MachO::Target, ValueT>>>
+Array serializeField(TBDKey Key, const AggregateT &Values,
+                     const TargetList &ActiveTargets, bool IsArray = true) {
+  std::map<ValueT, std::set<MachO::Target>> Entries;
+  for (const auto &[Target, Val] : Values)
+    Entries[Val].insert(Target);
+
+  if (!IsArray) {
+    std::map<std::vector<std::string>, std::string> FinalEntries;
+    for (const auto &[Val, Targets] : Entries)
+      FinalEntries[serializeTargets(Targets, ActiveTargets)] = Val;
+    return serializeAttrToTargets(FinalEntries, Key);
+  }
+
+  TargetsToValuesMap FinalEntries;
+  for (const auto &[Val, Targets] : Entries)
+    FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back(Val);
+  return serializeAttrToTargets(FinalEntries, Key);
+}
+
+Array serializeField(TBDKey Key, const std::vector<InterfaceFileRef> &Values,
+                     const TargetList &ActiveTargets) {
+  TargetsToValuesMap FinalEntries;
+  for (const auto &Ref : Values) {
+    TargetList Targets{Ref.targets().begin(), Ref.targets().end()};
+    FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back(
+        Ref.getInstallName());
+  }
+  return serializeAttrToTargets(FinalEntries, Key);
+}
+
+struct SymbolFields {
+  struct SymbolTypes {
+    std::vector<StringRef> Weaks;
+    std::vector<StringRef> Globals;
+    std::vector<StringRef> TLV;
+    std::vector<StringRef> ObjCClasses;
+    std::vector<StringRef> IVars;
+    std::vector<StringRef> EHTypes;
+
+    bool empty() const {
+      return Weaks.empty() && Globals.empty() && TLV.empty() &&
+             ObjCClasses.empty() && IVars.empty() && EHTypes.empty();
+    }
+  };
+  SymbolTypes Data;
+  SymbolTypes Text;
+};
+
+Array serializeSymbols(InterfaceFile::const_filtered_symbol_range Symbols,
+                       const TargetList &ActiveTargets) {
+  auto AssignForSymbolType = [](SymbolFields::SymbolTypes &Assignment,
+                                const Symbol *Sym) {
+    switch (Sym->getKind()) {
+    case SymbolKind::ObjectiveCClass:
+      Assignment.ObjCClasses.emplace_back(Sym->getName());
+      return;
+    case SymbolKind::ObjectiveCClassEHType:
+      Assignment.EHTypes.emplace_back(Sym->getName());
+      return;
+    case SymbolKind::ObjectiveCInstanceVariable:
+      Assignment.IVars.emplace_back(Sym->getName());
+      return;
+    case SymbolKind::GlobalSymbol: {
+      if (Sym->isWeakReferenced() || Sym->isWeakDefined())
+        Assignment.Weaks.emplace_back(Sym->getName());
+      else if (Sym->isThreadLocalValue())
+        Assignment.TLV.emplace_back(Sym->getName());
+      else
+        Assignment.Globals.emplace_back(Sym->getName());
+      return;
+    }
+    }
+  };
+
+  std::map<std::vector<std::string>, SymbolFields> Entries;
+  for (const auto *Sym : Symbols) {
+    std::set<MachO::Target> Targets{Sym->targets().begin(),
+                                    Sym->targets().end()};
+    auto JSONTargets = serializeTargets(Targets, ActiveTargets);
+    if (Sym->isData())
+      AssignForSymbolType(Entries[std::move(JSONTargets)].Data, Sym);
+    else if (Sym->isText())
+      AssignForSymbolType(Entries[std::move(JSONTargets)].Text, Sym);
+    else
+      llvm_unreachable("unexpected symbol type");
+  }
+
+  auto InsertSymbolsToJSON = [](Object &SymSection, TBDKey SegmentKey,
+                                SymbolFields::SymbolTypes &SymField) {
+    if (SymField.empty())
+      return;
+    Object Segment;
+    insertNonEmptyValues(Segment, TBDKey::Globals, std::move(SymField.Globals));
+    insertNonEmptyValues(Segment, TBDKey::ThreadLocal, std::move(SymField.TLV));
+    insertNonEmptyValues(Segment, TBDKey::Weak, std::move(SymField.Weaks));
+    insertNonEmptyValues(Segment, TBDKey::ObjCClass,
+                         std::move(SymField.ObjCClasses));
+    insertNonEmptyValues(Segment, TBDKey::ObjCEHType,
+                         std::move(SymField.EHTypes));
+    insertNonEmptyValues(Segment, TBDKey::ObjCIvar, std::move(SymField.IVars));
+    insertNonEmptyValues(SymSection, SegmentKey, std::move(Segment));
+  };
+
+  Array SymbolSection;
+  for (auto &[Targets, Fields] : Entries) {
+    Object AllSyms;
+    insertNonEmptyValues(AllSyms, TBDKey::Targets, std::move(Targets));
+    InsertSymbolsToJSON(AllSyms, TBDKey::Data, Fields.Data);
+    InsertSymbolsToJSON(AllSyms, TBDKey::Text, Fields.Text);
+    SymbolSection.emplace_back(std::move(AllSyms));
+  }
+
+  return SymbolSection;
+}
+
+Array serializeFlags(const InterfaceFile *File) {
+  // TODO: Give all Targets the same flags for now.
+  Array Flags;
+  if (!File->isTwoLevelNamespace())
+    Flags.emplace_back("flat_namespace");
+  if (!File->isApplicationExtensionSafe())
+    Flags.emplace_back("not_app_extension_safe");
+  return serializeScalar(TBDKey::Attributes, std::move(Flags));
+}
+
+Expected<Object> serializeIF(const InterfaceFile *File) {
+  Object Library;
+
+  // Handle required keys.
+  TargetList ActiveTargets{File->targets().begin(), File->targets().end()};
+  if (!insertNonEmptyValues(Library, TBDKey::TargetInfo,
+                            serializeTargetInfo(ActiveTargets)))
+    return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::TargetInfo));
+
+  Array Name = serializeScalar<StringRef>(TBDKey::Name, File->getInstallName());
+  if (!insertNonEmptyValues(Library, TBDKey::InstallName, std::move(Name)))
+    return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::InstallName));
+
+  // Handle optional keys.
+  Array Flags = serializeFlags(File);
+  insertNonEmptyValues(Library, TBDKey::Flags, std::move(Flags));
+
+  Array CurrentV = serializeScalar<PackedVersion, std::string>(
+      TBDKey::Version, File->getCurrentVersion(), PackedVersion(1, 0, 0));
+  insertNonEmptyValues(Library, TBDKey::CurrentVersion, std::move(CurrentV));
+
+  Array CompatV = serializeScalar<PackedVersion, std::string>(
+      TBDKey::Version, File->getCompatibilityVersion(), PackedVersion(1, 0, 0));
+  insertNonEmptyValues(Library, TBDKey::CompatibilityVersion,
+                       std::move(CompatV));
+
+  Array SwiftABI = serializeScalar<uint8_t, int64_t>(
+      TBDKey::ABI, File->getSwiftABIVersion(), 0u);
+  insertNonEmptyValues(Library, TBDKey::SwiftABI, std::move(SwiftABI));
+
+  Array RPaths = serializeField(TBDKey::Paths, File->rpaths(), ActiveTargets);
+  insertNonEmptyValues(Library, TBDKey::RPath, std::move(RPaths));
+
+  Array Umbrellas = serializeField(TBDKey::Umbrella, File->umbrellas(),
+                                   ActiveTargets, /*IsArray=*/false);
+  insertNonEmptyValues(Library, TBDKey::ParentUmbrella, std::move(Umbrellas));
+
+  Array Clients =
+      serializeField(TBDKey::Clients, File->allowableClients(), ActiveTargets);
+  insertNonEmptyValues(Library, TBDKey::AllowableClients, std::move(Clients));
+
+  Array ReexportLibs =
+      serializeField(TBDKey::Names, File->reexportedLibraries(), ActiveTargets);
+  insertNonEmptyValues(Library, TBDKey::ReexportLibs, std::move(ReexportLibs));
+
+  // Handle symbols.
+  Array Exports = serializeSymbols(File->exports(), ActiveTargets);
+  insertNonEmptyValues(Library, TBDKey::Exports, std::move(Exports));
+
+  Array Reexports = serializeSymbols(File->reexports(), ActiveTargets);
+  insertNonEmptyValues(Library, TBDKey::Reexports, std::move(Reexports));
+
+  if (!File->isTwoLevelNamespace()) {
+    Array Undefineds = serializeSymbols(File->undefineds(), ActiveTargets);
+    insertNonEmptyValues(Library, TBDKey::Undefineds, std::move(Undefineds));
+  }
+
+  return std::move(Library);
+}
+
+Expected<Object> getJSON(const InterfaceFile *File) {
+  assert(File->getFileType() == FileType::TBD_V5 &&
+         "unexpected json file format version");
+  Object Root;
+
+  auto MainLibOrErr = serializeIF(File);
+  if (!MainLibOrErr)
+    return MainLibOrErr;
+  Root[Keys[TBDKey::MainLibrary]] = std::move(*MainLibOrErr);
+  Array Documents;
+  for (const auto &Doc : File->documents()) {
+    auto LibOrErr = serializeIF(Doc.get());
+    if (!LibOrErr)
+      return LibOrErr;
+    Documents.emplace_back(std::move(*LibOrErr));
+  }
+
+  Root[Keys[TBDKey::TBDVersion]] = 5;
+  insertNonEmptyValues(Root, TBDKey::Documents, std::move(Documents));
+  return std::move(Root);
+}
+
+} // namespace
+
+Error MachO::serializeInterfaceFileToJSON(raw_ostream &OS,
+                                          const InterfaceFile &File,
+                                          bool Compact) {
+  auto TextFile = getJSON(&File);
+  if (!TextFile)
+    return TextFile.takeError();
+  if (Compact)
+    OS << formatv("{0}", Value(std::move(*TextFile))) << "\n";
+  else
+    OS << formatv("{0:2}", Value(std::move(*TextFile))) << "\n";
+  return Error::success();
+}

diff  --git a/llvm/unittests/TextAPI/TextStubV5Tests.cpp b/llvm/unittests/TextAPI/TextStubV5Tests.cpp
index c9668bbc23130..2536a5a9cccb3 100644
--- a/llvm/unittests/TextAPI/TextStubV5Tests.cpp
+++ b/llvm/unittests/TextAPI/TextStubV5Tests.cpp
@@ -520,4 +520,524 @@ TEST(TBDv5, ReadMultipleDocuments) {
       std::equal(Exports.begin(), Exports.end(), std::begin(ExpectedExports)));
 }
 
+TEST(TBDv5, WriteFile) {
+  static const char TBDv5File[] = R"({
+"tapi_tbd_version": 5,
+"main_library": {
+  "target_info": [
+    {
+      "target": "x86_64-macos",
+      "min_deployment": "10.14"
+    },
+    {
+      "target": "arm64-macos",
+      "min_deployment": "10.14"
+    },
+    {
+      "target": "arm64-maccatalyst",
+      "min_deployment": "12.1"
+    }
+  ],
+  "install_names": [
+    {
+        "name": "@rpath/S/L/F/Foo.framework/Foo"
+    }
+  ],
+  "current_versions": [
+    {
+        "version": "1.2"
+    }
+  ],
+  "compatibility_versions": [
+    { "version": "1.1" }
+  ],
+  "flags": [
+    {
+      "attributes": [
+            "flat_namespace"
+        ]
+    }
+  ],
+  "rpaths": [
+    {
+      "targets": [
+          "x86_64-macos"
+      ],
+      "paths": [
+          "@executable_path/.../Frameworks"
+      ]
+    }
+  ],
+  "parent_umbrellas": [
+    {
+      "umbrella": "System"
+    }
+  ],
+  "allowable_clients": [
+    {
+        "clients": [
+            "ClientA",
+            "ClientB"
+        ]
+    }
+  ],
+  "reexported_libraries": [
+    {
+        "names": [
+            "/u/l/l/libfoo.dylib",
+            "/u/l/l/libbar.dylib"
+        ]
+    }
+  ],
+  "exported_symbols": [
+    {
+        "targets": [
+            "x86_64-macos",
+            "arm64-macos"
+        ],
+        "data": {
+            "global": [
+                "_global"
+            ],
+            "objc_class": [
+                "ClassA"
+            ],
+            "weak": [],
+            "thread_local": []
+        },
+        "text": {
+            "global": [
+                "_func"
+            ],
+            "weak": [],
+            "thread_local": []
+        }
+    },
+    {
+      "targets": [
+          "x86_64-macos"
+      ],
+      "data": {
+          "global": [
+              "_globalVar"
+          ],
+          "objc_class": [
+              "ClassData"
+          ],
+          "objc_eh_type": [
+              "ClassA",
+              "ClassB"
+          ],
+          "objc_ivar": [
+              "ClassA.ivar1",
+              "ClassA.ivar2",
+              "ClassC.ivar1"
+          ]
+      },
+      "text": {
+          "global": [
+              "_funcFoo"
+          ]
+      }
+    }
+  ],
+  "reexported_symbols": [
+    {
+        "data": {
+            "global": [
+                "_globalRe"
+            ],
+            "objc_class": [
+                "ClassRexport"
+            ]
+        },
+        "text": {
+            "global": [
+                "_funcA"
+            ]
+        }
+    }
+  ],
+  "undefined_symbols": [
+    {
+        "targets": [
+            "x86_64-macos"
+        ],
+        "data": {
+            "global": [
+                "_globalBind"
+            ],
+            "weak": [
+                "referenced_sym"
+            ]
+        }
+    }
+  ]
+}})";
+
+  InterfaceFile File;
+  File.setFileType(FileType::TBD_V5);
+
+  TargetList AllTargets = {
+      Target(AK_x86_64, PLATFORM_MACOS, VersionTuple(10, 14)),
+      Target(AK_arm64, PLATFORM_MACOS, VersionTuple(10, 14)),
+      Target(AK_arm64, PLATFORM_MACCATALYST, VersionTuple(12, 1)),
+  };
+  File.addTargets(AllTargets);
+  File.setInstallName("@rpath/S/L/F/Foo.framework/Foo");
+  File.setCurrentVersion(PackedVersion(1, 2, 0));
+  File.setCompatibilityVersion(PackedVersion(1, 1, 0));
+  File.addRPath(AllTargets[0], "@executable_path/.../Frameworks");
+
+  for (const auto &Targ : AllTargets) {
+    File.addParentUmbrella(Targ, "System");
+    File.addAllowableClient("ClientA", Targ);
+    File.addAllowableClient("ClientB", Targ);
+    File.addReexportedLibrary("/u/l/l/libfoo.dylib", Targ);
+    File.addReexportedLibrary("/u/l/l/libbar.dylib", Targ);
+  }
+
+  SymbolFlags Flags = SymbolFlags::None;
+  // Exports.
+  File.addSymbol(SymbolKind::GlobalSymbol, "_global",
+                 {AllTargets[0], AllTargets[1]}, Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::GlobalSymbol, "_func",
+                 {AllTargets[0], AllTargets[1]}, Flags | SymbolFlags::Text);
+  File.addSymbol(SymbolKind::ObjectiveCClass, "ClassA",
+                 {AllTargets[0], AllTargets[1]}, Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::GlobalSymbol, "_funcFoo", {AllTargets[0]},
+                 Flags | SymbolFlags::Text);
+  File.addSymbol(SymbolKind::GlobalSymbol, "_globalVar", {AllTargets[0]},
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCClass, "ClassData", {AllTargets[0]},
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCClassEHType, "ClassA", {AllTargets[0]},
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCClassEHType, "ClassB", {AllTargets[0]},
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCInstanceVariable, "ClassA.ivar1",
+                 {AllTargets[0]}, Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCInstanceVariable, "ClassA.ivar2",
+                 {AllTargets[0]}, Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::ObjectiveCInstanceVariable, "ClassC.ivar1",
+                 {AllTargets[0]}, Flags | SymbolFlags::Data);
+
+  // Reexports.
+  Flags = SymbolFlags::Rexported;
+  File.addSymbol(SymbolKind::GlobalSymbol, "_globalRe", AllTargets,
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::GlobalSymbol, "_funcA", AllTargets,
+                 Flags | SymbolFlags::Text);
+  File.addSymbol(SymbolKind::ObjectiveCClass, "ClassRexport", AllTargets,
+                 Flags | SymbolFlags::Data);
+
+  // Undefineds.
+  Flags = SymbolFlags::Undefined;
+  File.addSymbol(SymbolKind::GlobalSymbol, "_globalBind", {AllTargets[0]},
+                 Flags | SymbolFlags::Data);
+  File.addSymbol(SymbolKind::GlobalSymbol, "referenced_sym", {AllTargets[0]},
+                 Flags | SymbolFlags::Data | SymbolFlags::WeakReferenced);
+
+  File.setTwoLevelNamespace(false);
+  File.setApplicationExtensionSafe(true);
+
+  // Write out file then process it back into IF and compare equality
+  // against TBDv5File.
+  SmallString<4096> Buffer;
+  raw_svector_ostream OS(Buffer);
+  Error Result = TextAPIWriter::writeToStream(OS, File);
+  EXPECT_FALSE(Result);
+
+  Expected<TBDFile> Input =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Input.tbd"));
+  EXPECT_TRUE(!!Input);
+  TBDFile InputFile = std::move(Input.get());
+
+  Expected<TBDFile> Output =
+      TextAPIReader::get(MemoryBufferRef(Buffer, "Output.tbd"));
+  EXPECT_TRUE(!!Output);
+  TBDFile OutputFile = std::move(Output.get());
+  EXPECT_EQ(*InputFile, *OutputFile);
+}
+
+TEST(TBDv5, WriteMultipleDocuments) {
+  static const char TBDv5File[] = R"({ 
+"tapi_tbd_version": 5,
+"main_library": {
+  "target_info": [
+    {
+      "target": "armv7-ios",
+      "min_deployment": "11.0" 
+    }
+  ],
+  "install_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ],
+  "reexported_libraries": [
+    { "names": ["/u/l/l/libfoo.dylib"] 
+    }
+  ]
+},
+"libraries": [
+  {
+    "target_info": [
+      {
+        "target": "armv7-ios",
+        "min_deployment": "11.0" 
+      },
+      {
+        "target": "armv7s-ios",
+        "min_deployment": "11.0" 
+      }
+    ],
+    "install_names":[
+      { "name":"/u/l/l/libfoo.dylib" }
+    ],
+    "current_versions": [
+      {
+          "version": "2.1.1"
+      }
+    ],
+    "rpaths": [
+      {
+        "targets": [
+            "armv7-ios"
+        ],
+        "paths": [
+            "@executable_path/.../Frameworks"
+        ]
+      }],
+    "reexported_libraries": [ { "names": ["@rpath/libfoo.dylib"] } ],
+    "flags":[ 
+      { "attributes": ["not_app_extension_safe"] }
+    ], 
+    "exported_symbols": [
+      {
+        "text": {
+          "global": [ "_funcFoo" ]
+        }
+      }
+    ]
+  },
+  {
+    "target_info": [
+      {
+        "target": "armv7-ios",
+        "min_deployment": "11.0" 
+      }
+    ],
+    "install_names":[
+      { "name":"@rpath/libfoo.dylib" }
+    ],
+    "exported_symbols": [
+      {
+        "data": {
+          "global": [ "_varFooBaz" ]
+        }
+      }
+    ]
+  }
+]})";
+
+  InterfaceFile File;
+  File.setFileType(FileType::TBD_V5);
+
+  TargetList AllTargets = {
+      Target(AK_armv7, PLATFORM_IOS, VersionTuple(11, 0)),
+      Target(AK_armv7s, PLATFORM_IOS, VersionTuple(11, 0)),
+  };
+  File.setInstallName("/S/L/F/Foo.framework/Foo");
+  File.addTarget(AllTargets[0]);
+  File.setCurrentVersion(PackedVersion(1, 0, 0));
+  File.setCompatibilityVersion(PackedVersion(1, 0, 0));
+  File.addReexportedLibrary("/u/l/l/libfoo.dylib", AllTargets[0]);
+  File.setTwoLevelNamespace();
+  File.setApplicationExtensionSafe(true);
+
+  InterfaceFile NestedFile;
+  NestedFile.setFileType(FileType::TBD_V5);
+  NestedFile.setInstallName("/u/l/l/libfoo.dylib");
+  NestedFile.addTargets(AllTargets);
+  NestedFile.setCompatibilityVersion(PackedVersion(1, 0, 0));
+  NestedFile.setTwoLevelNamespace();
+  NestedFile.setApplicationExtensionSafe(false);
+  NestedFile.setCurrentVersion(PackedVersion(2, 1, 1));
+  NestedFile.addRPath(AllTargets[0], "@executable_path/.../Frameworks");
+  for (const auto &Targ : AllTargets)
+    NestedFile.addReexportedLibrary("@rpath/libfoo.dylib", Targ);
+  NestedFile.addSymbol(SymbolKind::GlobalSymbol, "_funcFoo", AllTargets,
+                       SymbolFlags::Text);
+  File.addDocument(std::make_shared<InterfaceFile>(std::move(NestedFile)));
+
+  InterfaceFile NestedFileB;
+  NestedFileB.setFileType(FileType::TBD_V5);
+  NestedFileB.setInstallName("@rpath/libfoo.dylib");
+  NestedFileB.addTarget(AllTargets[0]);
+  NestedFileB.setCompatibilityVersion(PackedVersion(1, 0, 0));
+  NestedFileB.setCurrentVersion(PackedVersion(1, 0, 0));
+  NestedFileB.setTwoLevelNamespace();
+  NestedFileB.setApplicationExtensionSafe(true);
+  NestedFileB.addSymbol(SymbolKind::GlobalSymbol, "_varFooBaz", AllTargets,
+                        SymbolFlags::Data);
+  File.addDocument(std::make_shared<InterfaceFile>(std::move(NestedFileB)));
+
+  // Write out file then process it back into IF and compare equality
+  // against TBDv5File.
+  SmallString<4096> Buffer;
+  raw_svector_ostream OS(Buffer);
+  Error Result = TextAPIWriter::writeToStream(OS, File, /*Compact=*/true);
+  EXPECT_FALSE(Result);
+
+  Expected<TBDFile> Input =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Input.tbd"));
+  EXPECT_TRUE(!!Input);
+  TBDFile InputFile = std::move(Input.get());
+
+  Expected<TBDFile> Output =
+      TextAPIReader::get(MemoryBufferRef(Buffer, "Output.tbd"));
+  EXPECT_TRUE(!!Output);
+  TBDFile OutputFile = std::move(Output.get());
+  EXPECT_EQ(*InputFile, *OutputFile);
+}
+
+TEST(TBDv5, Target_Simulator) {
+  static const char TBDv5File[] = R"({ 
+"tapi_tbd_version": 5,
+"main_library": {
+  "target_info": [
+    {
+      "target": "arm64-ios-simulator",
+      "min_deployment": "11.0"
+    },
+    {
+      "target": "x86_64-ios-simulator",
+      "min_deployment": "11.3" 
+    }
+  ],
+  "install_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ]
+}})";
+
+  Expected<TBDFile> Result =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd"));
+  EXPECT_TRUE(!!Result);
+  TBDFile File = std::move(Result.get());
+  EXPECT_EQ(FileType::TBD_V5, File->getFileType());
+  TargetList ExpectedTargets = {
+      Target(AK_x86_64, PLATFORM_IOSSIMULATOR, VersionTuple(11, 3)),
+      Target(AK_arm64, PLATFORM_IOSSIMULATOR, VersionTuple(11, 0)),
+  };
+  TargetList Targets{File->targets().begin(), File->targets().end()};
+  llvm::sort(Targets);
+  EXPECT_EQ(Targets, ExpectedTargets);
+
+  SmallString<4096> Buffer;
+  raw_svector_ostream OS(Buffer);
+  Error WriteResult = TextAPIWriter::writeToStream(OS, *File);
+  EXPECT_TRUE(!WriteResult);
+
+  Expected<TBDFile> Output =
+      TextAPIReader::get(MemoryBufferRef(Buffer, "Output.tbd"));
+  EXPECT_TRUE(!!Output);
+  TBDFile WriteResultFile = std::move(Output.get());
+  EXPECT_EQ(*File, *WriteResultFile);
+}
+
+TEST(TBDv5, MisspelledKey) {
+  static const char TBDv5File[] = R"({ 
+"tapi_tbd_version": 5,
+"main_library": {
+  "target_info": [
+    {
+      "target": "arm64-ios-simulator",
+      "min_deployment": "11.0"
+    }
+  ],
+  "intall_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ]
+}})";
+
+  Expected<TBDFile> Result =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd"));
+  EXPECT_FALSE(!!Result);
+  std::string ErrorMessage = toString(Result.takeError());
+  EXPECT_EQ("invalid install_names section\n", ErrorMessage);
+}
+
+TEST(TBDv5, InvalidVersion) {
+  static const char TBDv5File[] = R"({ 
+"tapi_tbd_version": 11,
+"main_library": {
+  "target_info": [
+    {
+      "target": "arm64-ios-simulator",
+      "min_deployment": "11.0"
+    }
+  ],
+  "install_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ]
+}})";
+
+  Expected<TBDFile> Result =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd"));
+  EXPECT_FALSE(!!Result);
+  std::string ErrorMessage = toString(Result.takeError());
+  EXPECT_EQ("invalid tapi_tbd_version section\n", ErrorMessage);
+}
+
+TEST(TBDv5, MissingRequiredKey) {
+  static const char TBDv5File[] = R"({ 
+"main_library": {
+  "target_info": [
+    {
+      "target": "arm64-ios-simulator",
+      "min_deployment": "11.0"
+    }
+  ],
+  "install_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ]
+}})";
+
+  Expected<TBDFile> Result =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd"));
+  EXPECT_FALSE(!!Result);
+  std::string ErrorMessage = toString(Result.takeError());
+  EXPECT_EQ("invalid tapi_tbd_version section\n", ErrorMessage);
+}
+
+TEST(TBDv5, InvalidSymbols) {
+  static const char TBDv5File[] = R"({ 
+"tapi_tbd_version": 5,
+"main_library": {
+  "target_info": [
+    {
+      "target": "arm64-driverkit",
+      "min_deployment": "11.0"
+    }
+  ],
+  "install_names":[
+    { "name":"/S/L/F/Foo.framework/Foo" }
+  ],
+  "exported_symbols": [
+    {
+      "daa": {
+        "global": {
+            "weak": []
+          }
+      }
+    }
+  ]
+}})";
+
+  Expected<TBDFile> Result =
+      TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd"));
+  EXPECT_FALSE(!!Result);
+  std::string ErrorMessage = toString(Result.takeError());
+  EXPECT_EQ("invalid exported_symbols section\n", ErrorMessage);
+}
+
 } // end namespace TBDv5


        


More information about the llvm-commits mailing list