diff --git a/llvm/include/llvm/TextAPI/InterfaceFile.h b/llvm/include/llvm/TextAPI/InterfaceFile.h --- a/llvm/include/llvm/TextAPI/InterfaceFile.h +++ b/llvm/include/llvm/TextAPI/InterfaceFile.h @@ -396,7 +396,16 @@ const_filtered_symbol_range exports() const { std::function fn = [](const Symbol *Symbol) { - return !Symbol->isUndefined(); + return !Symbol->isUndefined() && !Symbol->isReexported(); + }; + return make_filter_range( + make_range({Symbols.begin()}, {Symbols.end()}), + fn); + } + + const_filtered_symbol_range reexports() const { + std::function fn = [](const Symbol *Symbol) { + return Symbol->isReexported(); }; return make_filter_range( make_range({Symbols.begin()}, {Symbols.end()}), diff --git a/llvm/include/llvm/TextAPI/PackedVersion.h b/llvm/include/llvm/TextAPI/PackedVersion.h --- a/llvm/include/llvm/TextAPI/PackedVersion.h +++ b/llvm/include/llvm/TextAPI/PackedVersion.h @@ -53,6 +53,8 @@ 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 --- a/llvm/include/llvm/TextAPI/TextAPIWriter.h +++ b/llvm/include/llvm/TextAPI/TextAPIWriter.h @@ -22,7 +22,8 @@ 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 --- a/llvm/lib/TextAPI/PackedVersion.cpp +++ b/llvm/lib/TextAPI/PackedVersion.cpp @@ -100,6 +100,13 @@ 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 --- a/llvm/lib/TextAPI/TextStub.cpp +++ b/llvm/lib/TextAPI/TextStub.cpp @@ -1159,10 +1159,17 @@ 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 Files; diff --git a/llvm/lib/TextAPI/TextStubCommon.h b/llvm/lib/TextAPI/TextStubCommon.h --- a/llvm/lib/TextAPI/TextStubCommon.h +++ b/llvm/lib/TextAPI/TextStubCommon.h @@ -45,6 +45,9 @@ Expected> 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 --- a/llvm/lib/TextAPI/TextStubV5.cpp +++ b/llvm/lib/TextAPI/TextStubV5.cpp @@ -161,6 +161,10 @@ return {"invalid ", Keys[Key], " section"}; } +static llvm::SmallString<128> getSerializeErrorMsg(TBDKey Key) { + return {"missing ", Keys[Key], " information"}; +} + class JSONStubError : public llvm::ErrorInfo { public: JSONStubError(Twine ErrMsg) : Message(ErrMsg.str()) {} @@ -716,3 +720,294 @@ } return std::move(IF); } + +namespace { + +template +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 +std::vector serializeTargets(const AggregateT Targets, + const TargetList &ActiveTargets) { + std::vector 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 +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>; + +template +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 >> +Array serializeField(TBDKey Key, const AggregateT &Values, + const TargetList &ActiveTargets, bool IsArray = true) { + std::map> Entries; + for (const auto &[Target, Val] : Values) + Entries[Val].insert(Target); + + if (!IsArray) { + std::map, 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 &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 Weaks; + std::vector Globals; + std::vector TLV; + std::vector ObjCClasses; + std::vector IVars; + std::vector 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, SymbolFields> Entries; + for (const auto *Sym : Symbols) { + std::set 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 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(getSerializeErrorMsg(TBDKey::TargetInfo)); + + Array Name = serializeScalar(TBDKey::Name, File->getInstallName()); + if (!insertNonEmptyValues(Library, TBDKey::InstallName, std::move(Name))) + return make_error(getSerializeErrorMsg(TBDKey::InstallName)); + + // Handle optional keys. + Array Flags = serializeFlags(File); + insertNonEmptyValues(Library, TBDKey::Flags, std::move(Flags)); + + Array CurrentV = serializeScalar( + TBDKey::Version, File->getCurrentVersion(), PackedVersion(1, 0, 0)); + insertNonEmptyValues(Library, TBDKey::CurrentVersion, std::move(CurrentV)); + + Array CompatV = serializeScalar( + TBDKey::Version, File->getCompatibilityVersion(), PackedVersion(1, 0, 0)); + insertNonEmptyValues(Library, TBDKey::CompatibilityVersion, + std::move(CompatV)); + + Array SwiftABI = serializeScalar( + 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 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 --- a/llvm/unittests/TextAPI/TextStubV5Tests.cpp +++ b/llvm/unittests/TextAPI/TextStubV5Tests.cpp @@ -520,4 +520,524 @@ 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 Input = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Input.tbd")); + EXPECT_TRUE(!!Input); + TBDFile InputFile = std::move(Input.get()); + + Expected 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(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(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 Input = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Input.tbd")); + EXPECT_TRUE(!!Input); + TBDFile InputFile = std::move(Input.get()); + + Expected 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 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 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 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 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 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 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