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()) {}
}
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();
+}
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