diff --git a/llvm/include/llvm/TextAPI/ArchitectureSet.h b/llvm/include/llvm/TextAPI/ArchitectureSet.h --- a/llvm/include/llvm/TextAPI/ArchitectureSet.h +++ b/llvm/include/llvm/TextAPI/ArchitectureSet.h @@ -40,13 +40,18 @@ ArchitectureSet(Architecture Arch) : ArchitectureSet() { set(Arch); } ArchitectureSet(const std::vector &Archs); + static ArchitectureSet All() { return ArchitectureSet(EndIndexVal); } + void set(Architecture Arch) { if (Arch == AK_unknown) return; ArchSet |= 1U << static_cast(Arch); } - void clear(Architecture Arch) { ArchSet &= ~(1U << static_cast(Arch)); } + ArchitectureSet clear(Architecture Arch) { + ArchSet &= ~(1U << static_cast(Arch)); + return ArchSet; + } bool has(Architecture Arch) const { return ArchSet & (1U << static_cast(Arch)); 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 @@ -372,6 +372,37 @@ return SymbolsSet->undefineds(); }; + /// Extract architecture slice from Interface. + /// + /// \param Arch architecture to extract from. + /// \return New InterfaceFile with extracted architecture slice. + llvm::Expected> + extract(Architecture Arch) const; + + /// Remove architecture slice from Interface. + /// + /// \param Arch architecture to remove. + /// \return New Interface File with removed architecture slice. + llvm::Expected> + remove(Architecture Arch) const; + + /// Merge Interfaces for the same library. The following library attributes + /// must match. + /// * Install name, Current & Compatibility version, + /// * Two-level namespace enablement, and App extension enablement. + /// + /// \param O The Interface to merge. + /// \return New Interface File that was merged. + llvm::Expected> + merge(const InterfaceFile *O) const; + + /// Inline reexported library into Interface. + /// + /// \param Library Interface of reexported library. + /// \param Overwrite Whether to overwrite preexisting inlined library. + void inlineLibrary(std::shared_ptr Library, + bool Overwrite = false); + /// The equality is determined by attributes that impact linking /// compatibilities. Path, & FileKind are irrelevant since these by /// itself should not impact linking. diff --git a/llvm/include/llvm/TextAPI/Symbol.h b/llvm/include/llvm/TextAPI/Symbol.h --- a/llvm/include/llvm/TextAPI/Symbol.h +++ b/llvm/include/llvm/TextAPI/Symbol.h @@ -121,6 +121,10 @@ return (Flags & SymbolFlags::Text) == SymbolFlags::Text; } + bool hasArchitecture(Architecture Arch) const { + return mapToArchitectureSet(Targets).contains(Arch); + } + using const_target_iterator = TargetList::const_iterator; using const_target_range = llvm::iterator_range; const_target_range targets() const { return {Targets}; } diff --git a/llvm/include/llvm/TextAPI/TextAPIError.h b/llvm/include/llvm/TextAPI/TextAPIError.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/TextAPI/TextAPIError.h @@ -0,0 +1,38 @@ +//===- llvm/TextAPI/TextAPIError.h - TAPI Error -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Define TAPI specific error codes. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TEXTAPI_TEXTAPIERROR_H +#define LLVM_TEXTAPI_TEXTAPIERROR_H + +#include "llvm/Support/Error.h" + +namespace llvm::MachO { +enum class TextAPIErrorCode { + NoSuchArchitecture, + EmptyResults, + GenericFrontendError, +}; + +class TextAPIError : public llvm::ErrorInfo { +public: + static char ID; + TextAPIErrorCode EC; + + TextAPIError(TextAPIErrorCode EC) : EC(EC) {} + + void log(raw_ostream &OS) const override; + std::error_code convertToErrorCode() const override; +}; + +} // namespace llvm::MachO +#endif // LLVM_TEXTAPI_TEXTAPIERROR_H diff --git a/llvm/lib/TextAPI/CMakeLists.txt b/llvm/lib/TextAPI/CMakeLists.txt --- a/llvm/lib/TextAPI/CMakeLists.txt +++ b/llvm/lib/TextAPI/CMakeLists.txt @@ -8,6 +8,7 @@ Symbol.cpp SymbolSet.cpp Target.cpp + TextAPIError.cpp TextStub.cpp TextStubCommon.cpp diff --git a/llvm/lib/TextAPI/InterfaceFile.cpp b/llvm/lib/TextAPI/InterfaceFile.cpp --- a/llvm/lib/TextAPI/InterfaceFile.cpp +++ b/llvm/lib/TextAPI/InterfaceFile.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "llvm/TextAPI/InterfaceFile.h" +#include "llvm/TextAPI/TextAPIError.h" #include #include @@ -81,6 +82,267 @@ Documents.insert(Pos, Document); } +void InterfaceFile::inlineLibrary(std::shared_ptr Library, + bool Overwrite) { + auto AddFwk = [&](std::shared_ptr &&Reexport) { + auto It = lower_bound( + Documents, Reexport->getInstallName(), + [](std::shared_ptr &Lhs, const StringRef Rhs) { + return Lhs->getInstallName() < Rhs; + }); + + if (Overwrite && It != Documents.end() && + Reexport->getInstallName() == (*It)->getInstallName()) { + std::replace(Documents.begin(), Documents.end(), *It, + std::move(Reexport)); + return; + } + + if ((It != Documents.end()) && + !(Reexport->getInstallName() < (*It)->getInstallName())) + return; + + Documents.emplace(It, std::move(Reexport)); + }; + for (auto Doc : Library->documents()) + AddFwk(std::move(Doc)); + + Library->Documents.clear(); + AddFwk(std::move(Library)); +} + +Expected> +InterfaceFile::merge(const InterfaceFile *O) const { + // Verify files can be merged. + if (getInstallName() != O->getInstallName()) { + return make_error("install names do not match", + inconvertibleErrorCode()); + } + + if (getCurrentVersion() != O->getCurrentVersion()) { + return make_error("current versions do not match", + inconvertibleErrorCode()); + } + + if (getCompatibilityVersion() != O->getCompatibilityVersion()) { + return make_error("compatibility versions do not match", + inconvertibleErrorCode()); + } + + if ((getSwiftABIVersion() != 0) && (O->getSwiftABIVersion() != 0) && + (getSwiftABIVersion() != O->getSwiftABIVersion())) { + return make_error("swift ABI versions do not match", + inconvertibleErrorCode()); + } + + if (isTwoLevelNamespace() != O->isTwoLevelNamespace()) { + return make_error("two level namespace flags do not match", + inconvertibleErrorCode()); + } + + if (isApplicationExtensionSafe() != O->isApplicationExtensionSafe()) { + return make_error( + "application extension safe flags do not match", + inconvertibleErrorCode()); + } + + std::unique_ptr IF(new InterfaceFile()); + IF->setFileType(std::max(getFileType(), O->getFileType())); + IF->setPath(getPath()); + IF->setInstallName(getInstallName()); + IF->setCurrentVersion(getCurrentVersion()); + IF->setCompatibilityVersion(getCompatibilityVersion()); + + if (getSwiftABIVersion() == 0) + IF->setSwiftABIVersion(O->getSwiftABIVersion()); + else + IF->setSwiftABIVersion(getSwiftABIVersion()); + + IF->setTwoLevelNamespace(isTwoLevelNamespace()); + IF->setApplicationExtensionSafe(isApplicationExtensionSafe()); + + for (const auto &It : umbrellas()) { + if (!It.second.empty()) + IF->addParentUmbrella(It.first, It.second); + } + for (const auto &It : O->umbrellas()) { + if (!It.second.empty()) + IF->addParentUmbrella(It.first, It.second); + } + IF->addTargets(targets()); + IF->addTargets(O->targets()); + + for (const auto &Lib : allowableClients()) + for (const auto &Target : Lib.targets()) + IF->addAllowableClient(Lib.getInstallName(), Target); + + for (const auto &Lib : O->allowableClients()) + for (const auto &Target : Lib.targets()) + IF->addAllowableClient(Lib.getInstallName(), Target); + + for (const auto &Lib : reexportedLibraries()) + for (const auto &Target : Lib.targets()) + IF->addReexportedLibrary(Lib.getInstallName(), Target); + + for (const auto &Lib : O->reexportedLibraries()) + for (const auto &Target : Lib.targets()) + IF->addReexportedLibrary(Lib.getInstallName(), Target); + + for (const auto &[Target, Path] : rpaths()) + IF->addRPath(Target, Path); + for (const auto &[Target, Path] : O->rpaths()) + IF->addRPath(Target, Path); + + for (const auto *Sym : symbols()) { + IF->addSymbol(Sym->getKind(), Sym->getName(), Sym->targets(), + Sym->getFlags()); + } + + for (const auto *Sym : O->symbols()) { + IF->addSymbol(Sym->getKind(), Sym->getName(), Sym->targets(), + Sym->getFlags()); + } + + return std::move(IF); +} + +Expected> +InterfaceFile::remove(Architecture Arch) const { + if (getArchitectures() == Arch) + return make_error("cannot remove last architecture slice '" + + getArchitectureName(Arch) + "'", + inconvertibleErrorCode()); + + if (!getArchitectures().has(Arch)) { + bool Found = false; + for (auto &Doc : Documents) { + if (Doc->getArchitectures().has(Arch)) { + Found = true; + break; + } + } + + if (!Found) + return make_error(TextAPIErrorCode::NoSuchArchitecture); + } + + std::unique_ptr IF(new InterfaceFile()); + IF->setFileType(getFileType()); + IF->setPath(getPath()); + IF->addTargets(targets(ArchitectureSet::All().clear(Arch))); + IF->setInstallName(getInstallName()); + IF->setCurrentVersion(getCurrentVersion()); + IF->setCompatibilityVersion(getCompatibilityVersion()); + IF->setSwiftABIVersion(getSwiftABIVersion()); + IF->setTwoLevelNamespace(isTwoLevelNamespace()); + IF->setApplicationExtensionSafe(isApplicationExtensionSafe()); + for (const auto &It : umbrellas()) + if (It.first.Arch != Arch) + IF->addParentUmbrella(It.first, It.second); + + for (const auto &Lib : allowableClients()) { + for (const auto &Target : Lib.targets()) + if (Target.Arch != Arch) + IF->addAllowableClient(Lib.getInstallName(), Target); + } + + for (const auto &Lib : reexportedLibraries()) { + for (const auto &Target : Lib.targets()) + if (Target.Arch != Arch) + IF->addReexportedLibrary(Lib.getInstallName(), Target); + } + + for (const auto *Sym : symbols()) { + auto Archs = Sym->getArchitectures(); + Archs.clear(Arch); + if (Archs.empty()) + continue; + + IF->addSymbol(Sym->getKind(), Sym->getName(), Sym->targets(Archs), + Sym->getFlags()); + } + + for (auto &Doc : Documents) { + // Skip the inlined document if the to be removed architecture is the + // only one left. + if (Doc->getArchitectures() == Arch) + continue; + + // If the document doesn't contain the arch, then no work is to be done + // and it can be copied over. + if (!Doc->getArchitectures().has(Arch)) { + auto NewDoc = Doc; + IF->addDocument(std::move(NewDoc)); + continue; + } + + auto Result = Doc->remove(Arch); + if (!Result) + return Result; + + IF->addDocument(std::move(Result.get())); + } + + return std::move(IF); +} + +Expected> +InterfaceFile::extract(Architecture Arch) const { + if (!getArchitectures().has(Arch)) { + return make_error("file doesn't have architecture '" + + getArchitectureName(Arch) + "'", + inconvertibleErrorCode()); + } + + std::unique_ptr IF(new InterfaceFile()); + IF->setFileType(getFileType()); + IF->setPath(getPath()); + IF->addTargets(targets(Arch)); + IF->setInstallName(getInstallName()); + IF->setCurrentVersion(getCurrentVersion()); + IF->setCompatibilityVersion(getCompatibilityVersion()); + IF->setSwiftABIVersion(getSwiftABIVersion()); + IF->setTwoLevelNamespace(isTwoLevelNamespace()); + IF->setApplicationExtensionSafe(isApplicationExtensionSafe()); + for (const auto &It : umbrellas()) + if (It.first.Arch == Arch) + IF->addParentUmbrella(It.first, It.second); + + for (const auto &It : rpaths()) + if (It.first.Arch == Arch) + IF->addRPath(It.first, It.second); + + for (const auto &Lib : allowableClients()) + for (const auto &Target : Lib.targets()) + if (Target.Arch == Arch) + IF->addAllowableClient(Lib.getInstallName(), Target); + + for (const auto &Lib : reexportedLibraries()) + for (const auto &Target : Lib.targets()) + if (Target.Arch == Arch) + IF->addReexportedLibrary(Lib.getInstallName(), Target); + + for (const auto *Sym : symbols()) { + if (Sym->hasArchitecture(Arch)) + IF->addSymbol(Sym->getKind(), Sym->getName(), Sym->targets(Arch), + Sym->getFlags()); + } + + for (auto &Doc : Documents) { + // Skip documents that don't have the requested architecture. + if (!Doc->getArchitectures().has(Arch)) + continue; + + auto Result = Doc->extract(Arch); + if (!Result) + return Result; + + IF->addDocument(std::move(Result.get())); + } + + return std::move(IF); +} + static bool isYAMLTextStub(const FileType &Kind) { return (Kind >= FileType::TBD_V1) && (Kind < FileType::TBD_V5); } diff --git a/llvm/lib/TextAPI/TextAPIError.cpp b/llvm/lib/TextAPI/TextAPIError.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/TextAPI/TextAPIError.cpp @@ -0,0 +1,33 @@ +//===- TextAPIError.cpp - Tapi Error ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Implements TAPI Error. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/TextAPI/TextAPIError.h" + +using namespace llvm; +using namespace llvm::MachO; + +char TextAPIError::ID = 0; + +void TextAPIError::log(raw_ostream &OS) const { + switch (EC) { + case TextAPIErrorCode::NoSuchArchitecture: + OS << "no such architecture\n"; + return; + default: + llvm_unreachable("unhandled TextAPIErrorCode"); + } +} + +std::error_code TextAPIError::convertToErrorCode() const { + llvm_unreachable("convertToErrorCode is not supported."); +} 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 @@ -1104,4 +1104,1145 @@ EXPECT_EQ("invalid exported_symbols section\n", ErrorMessage); } +TEST(TBDv5, MergeIF) { + static const char TBDv5FileA[] = 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" + } + ], + "flags": [ + { + "targets": [ + "x86_64-macos" + ], + "attributes": [ + "flat_namespace" + ] + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "compatibility_versions": [ + { "version": "1.1" } + ], + "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": [ + "ClassA", + "ClassB", + "ClassData" + ], + "objc_eh_type": [ + "ClassA", + "ClassB" + ], + "objc_ivar": [ + "ClassA.ivar1", + "ClassA.ivar2", + "ClassC.ivar1" + ] + }, + "text": { + "global": [ + "_funcFoo" + ] + } + } + ], + "reexported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_globalRe" + ], + "objc_class": [ + "ClassRexport" + ] + }, + "text": { + "global": [ + "_funcA" + ] + } + } + ], + "undefined_symbols": [ + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalBind" + ], + "weak": [ + "referenced_sym" + ] + } + } + ] +}, +"libraries": [] +})"; + + static const char TBDv5FileB[] = 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" + } + ], + "flags": [ + { + "targets": [ + "x86_64-macos" + ], + "attributes": [ + "flat_namespace" + ] + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "compatibility_versions": [ + { "version": "1.1" } + ], + "exported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_globalZ" + ], + "objc_class": [ + "ClassZ" + ], + "weak": [], + "thread_local": [] + }, + "text": { + "global": [ + "_funcZ" + ], + "weak": [], + "thread_local": [] + } + }, + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalVarZ" + ], + "objc_class": [ + "ClassZ", + "ClassF" + ], + "objc_eh_type": [ + "ClassZ", + "ClassF" + ], + "objc_ivar": [ + "ClassZ.ivar1", + "ClassZ.ivar2", + "ClassF.ivar1" + ] + }, + "text": { + "global": [ + "_funcFooZ" + ] + } + } + ] +}, +"libraries": [] +})"; + + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv5FileA, "Test.tbd")); + EXPECT_TRUE(!!ResultA); + TBDFile FileA = std::move(ResultA.get()); + + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv5FileB, "Test.tbd")); + EXPECT_TRUE(!!ResultB); + TBDFile FileB = std::move(ResultB.get()); + + Expected MergedResult = FileA->merge(FileB.get()); + EXPECT_TRUE(!!MergedResult); + TBDFile MergedFile = std::move(MergedResult.get()); + + EXPECT_EQ(FileType::TBD_V5, MergedFile->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), + MergedFile->getInstallName()); + TargetList AllTargets = { + Target(AK_x86_64, PLATFORM_MACOS, VersionTuple(10, 14)), + Target(AK_arm64, PLATFORM_MACOS, VersionTuple(11, 0, 0)), + Target(AK_arm64, PLATFORM_MACCATALYST, VersionTuple(14, 0)), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), MergedFile->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), MergedFile->getArchitectures()); + EXPECT_EQ(PackedVersion(1, 2, 0), MergedFile->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 1, 0), MergedFile->getCompatibilityVersion()); + EXPECT_TRUE(MergedFile->isApplicationExtensionSafe()); + EXPECT_FALSE(MergedFile->isTwoLevelNamespace()); + EXPECT_EQ(0U, MergedFile->documents().size()); + InterfaceFileRef ClientA("ClientA", AllTargets); + InterfaceFileRef ClientB("ClientB", AllTargets); + EXPECT_EQ(2U, MergedFile->allowableClients().size()); + EXPECT_EQ(ClientA, MergedFile->allowableClients().at(0)); + EXPECT_EQ(ClientB, MergedFile->allowableClients().at(1)); + + InterfaceFileRef ReexportA("/u/l/l/libbar.dylib", AllTargets); + InterfaceFileRef ReexportB("/u/l/l/libfoo.dylib", AllTargets); + EXPECT_EQ(2U, MergedFile->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, MergedFile->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, MergedFile->reexportedLibraries().at(1)); + + TargetToAttr RPaths = { + {Target(AK_x86_64, PLATFORM_MACOS), "@executable_path/.../Frameworks"}, + }; + EXPECT_EQ(RPaths, MergedFile->rpaths()); + + TargetToAttr Umbrellas = {{Target(AK_x86_64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACCATALYST), "System"}}; + EXPECT_EQ(Umbrellas, MergedFile->umbrellas()); + + ExportedSymbolSeq Exports, Reexports, Undefineds; + for (const auto *Sym : MergedFile->symbols()) { + TargetList SymTargets{Sym->targets().begin(), Sym->targets().end()}; + ExportedSymbol Temp = + ExportedSymbol{Sym->getKind(), + std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), + Sym->isData(), + SymTargets}; + if (Sym->isUndefined()) + Undefineds.emplace_back(std::move(Temp)); + else + Sym->isReexported() ? Reexports.emplace_back(std::move(Temp)) + : Exports.emplace_back(std::move(Temp)); + } + llvm::sort(Exports); + llvm::sort(Reexports); + llvm::sort(Undefineds); + + TargetList MacOSTargets = {Target(AK_x86_64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACOS)}; + + std::vector ExpectedExportedSymbols = { + {SymbolKind::GlobalSymbol, "_func", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, + "_funcFoo", + false, + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, + "_funcFooZ", + false, + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, "_funcZ", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_global", false, false, true, MacOSTargets}, + {SymbolKind::GlobalSymbol, + "_globalVar", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, + "_globalVarZ", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, "_globalZ", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, + "ClassA", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClass, + "ClassB", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClass, + "ClassData", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClass, + "ClassF", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClass, + "ClassZ", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassA", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassB", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassF", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassZ", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassA.ivar1", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassA.ivar2", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassC.ivar1", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassF.ivar1", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassZ.ivar1", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassZ.ivar2", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + }; + + std::vector ExpectedReexportedSymbols = { + {SymbolKind::GlobalSymbol, "_funcA", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_globalRe", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassRexport", false, false, true, + MacOSTargets}, + }; + + std::vector ExpectedUndefinedSymbols = { + {SymbolKind::GlobalSymbol, + "_globalBind", + false, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, + "referenced_sym", + true, + false, + true, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + }; + + EXPECT_EQ(ExpectedExportedSymbols.size(), Exports.size()); + EXPECT_EQ(ExpectedReexportedSymbols.size(), Reexports.size()); + EXPECT_EQ(ExpectedUndefinedSymbols.size(), Undefineds.size()); + EXPECT_TRUE(std::equal(Exports.begin(), Exports.end(), + std::begin(ExpectedExportedSymbols))); + EXPECT_TRUE(std::equal(Reexports.begin(), Reexports.end(), + std::begin(ExpectedReexportedSymbols))); + EXPECT_TRUE(std::equal(Undefineds.begin(), Undefineds.end(), + std::begin(ExpectedUndefinedSymbols))); +} + +TEST(TBDv5, ExtractIF) { + 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" + } + ], + "flags": [ + { + "targets": [ + "x86_64-macos" + ], + "attributes": [ + "flat_namespace" + ] + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "compatibility_versions": [ + { "version": "1.1" } + ], + "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": [ + "ClassA", + "ClassB", + "ClassData" + ], + "objc_eh_type": [ + "ClassA", + "ClassB" + ], + "objc_ivar": [ + "ClassA.ivar1", + "ClassA.ivar2", + "ClassC.ivar1" + ] + }, + "text": { + "global": [ + "_funcFoo" + ] + } + } + ], + "reexported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_globalRe" + ], + "objc_class": [ + "ClassRexport" + ] + }, + "text": { + "global": [ + "_funcA" + ] + } + } + ], + "undefined_symbols": [ + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalBind" + ], + "weak": [ + "referenced_sym" + ] + } + } + ] +}, +"libraries": [] +})"; + + Expected Result = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd")); + EXPECT_TRUE(!!Result); + TBDFile File = std::move(Result.get()); + + Expected ExtractedResult = File->extract(AK_arm64); + EXPECT_TRUE(!!ExtractedResult); + TBDFile ExtractedFile = std::move(ExtractedResult.get()); + + EXPECT_EQ(FileType::TBD_V5, ExtractedFile->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), + ExtractedFile->getInstallName()); + + TargetList AllTargets = { + Target(AK_arm64, PLATFORM_MACOS, VersionTuple(11, 0, 0)), + Target(AK_arm64, PLATFORM_MACCATALYST, VersionTuple(14, 0)), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), ExtractedFile->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), + ExtractedFile->getArchitectures()); + + EXPECT_EQ(PackedVersion(1, 2, 0), ExtractedFile->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 1, 0), ExtractedFile->getCompatibilityVersion()); + EXPECT_TRUE(ExtractedFile->isApplicationExtensionSafe()); + EXPECT_FALSE(ExtractedFile->isTwoLevelNamespace()); + EXPECT_EQ(0U, ExtractedFile->documents().size()); + + InterfaceFileRef ClientA("ClientA", AllTargets); + InterfaceFileRef ClientB("ClientB", AllTargets); + EXPECT_EQ(2U, ExtractedFile->allowableClients().size()); + EXPECT_EQ(ClientA, ExtractedFile->allowableClients().at(0)); + EXPECT_EQ(ClientB, ExtractedFile->allowableClients().at(1)); + + InterfaceFileRef ReexportA("/u/l/l/libbar.dylib", AllTargets); + InterfaceFileRef ReexportB("/u/l/l/libfoo.dylib", AllTargets); + EXPECT_EQ(2U, ExtractedFile->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, ExtractedFile->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, ExtractedFile->reexportedLibraries().at(1)); + + EXPECT_EQ(0u, ExtractedFile->rpaths().size()); + + TargetToAttr Umbrellas = {{Target(AK_arm64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACCATALYST), "System"}}; + EXPECT_EQ(Umbrellas, ExtractedFile->umbrellas()); + + ExportedSymbolSeq Exports, Reexports, Undefineds; + for (const auto *Sym : ExtractedFile->symbols()) { + TargetList SymTargets{Sym->targets().begin(), Sym->targets().end()}; + ExportedSymbol Temp = + ExportedSymbol{Sym->getKind(), + std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), + Sym->isData(), + SymTargets}; + if (Sym->isUndefined()) + Undefineds.emplace_back(std::move(Temp)); + else + Sym->isReexported() ? Reexports.emplace_back(std::move(Temp)) + : Exports.emplace_back(std::move(Temp)); + } + llvm::sort(Exports); + llvm::sort(Reexports); + llvm::sort(Undefineds); + + TargetList MacOSTargets = {Target(AK_arm64, PLATFORM_MACOS)}; + + std::vector ExpectedExportedSymbols = { + {SymbolKind::GlobalSymbol, "_func", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_global", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassA", false, false, true, MacOSTargets}, + }; + std::vector ExpectedReexportedSymbols = { + {SymbolKind::GlobalSymbol, "_funcA", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_globalRe", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassRexport", false, false, true, + MacOSTargets}, + }; + + EXPECT_EQ(ExpectedExportedSymbols.size(), Exports.size()); + EXPECT_EQ(ExpectedReexportedSymbols.size(), Reexports.size()); + EXPECT_EQ(0U, Undefineds.size()); + EXPECT_TRUE(std::equal(Exports.begin(), Exports.end(), + std::begin(ExpectedExportedSymbols))); + EXPECT_TRUE(std::equal(Reexports.begin(), Reexports.end(), + std::begin(ExpectedReexportedSymbols))); +} + +TEST(TBDv5, RemoveIF) { + 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" + } + ], + "flags": [ + { + "targets": [ + "x86_64-macos" + ], + "attributes": [ + "flat_namespace" + ] + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "compatibility_versions": [ + { "version": "1.1" } + ], + "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": [ + "ClassA", + "ClassB", + "ClassData" + ], + "objc_eh_type": [ + "ClassA", + "ClassB" + ], + "objc_ivar": [ + "ClassA.ivar1", + "ClassA.ivar2", + "ClassC.ivar1" + ] + }, + "text": { + "global": [ + "_funcFoo" + ] + } + } + ], + "reexported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_globalRe" + ], + "objc_class": [ + "ClassRexport" + ] + }, + "text": { + "global": [ + "_funcA" + ] + } + } + ], + "undefined_symbols": [ + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalBind" + ], + "weak": [ + "referenced_sym" + ] + } + } + ] +}, +"libraries": [] +})"; + + Expected Result = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd")); + EXPECT_TRUE(!!Result); + TBDFile File = std::move(Result.get()); + + Expected RemovedResult = File->remove(AK_x86_64); + EXPECT_TRUE(!!RemovedResult); + TBDFile RemovedFile = std::move(RemovedResult.get()); + + EXPECT_EQ(FileType::TBD_V5, RemovedFile->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), + RemovedFile->getInstallName()); + + TargetList AllTargets = { + Target(AK_arm64, PLATFORM_MACOS, VersionTuple(11, 0, 0)), + Target(AK_arm64, PLATFORM_MACCATALYST, VersionTuple(14, 0)), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), RemovedFile->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), RemovedFile->getArchitectures()); + + EXPECT_EQ(PackedVersion(1, 2, 0), RemovedFile->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 1, 0), RemovedFile->getCompatibilityVersion()); + EXPECT_TRUE(RemovedFile->isApplicationExtensionSafe()); + EXPECT_FALSE(RemovedFile->isTwoLevelNamespace()); + EXPECT_EQ(0U, RemovedFile->documents().size()); + + InterfaceFileRef ClientA("ClientA", AllTargets); + InterfaceFileRef ClientB("ClientB", AllTargets); + EXPECT_EQ(2U, RemovedFile->allowableClients().size()); + EXPECT_EQ(ClientA, RemovedFile->allowableClients().at(0)); + EXPECT_EQ(ClientB, RemovedFile->allowableClients().at(1)); + + InterfaceFileRef ReexportA("/u/l/l/libbar.dylib", AllTargets); + InterfaceFileRef ReexportB("/u/l/l/libfoo.dylib", AllTargets); + EXPECT_EQ(2U, RemovedFile->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, RemovedFile->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, RemovedFile->reexportedLibraries().at(1)); + + EXPECT_EQ(0u, RemovedFile->rpaths().size()); + + TargetToAttr Umbrellas = {{Target(AK_arm64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACCATALYST), "System"}}; + EXPECT_EQ(Umbrellas, RemovedFile->umbrellas()); + + ExportedSymbolSeq Exports, Reexports, Undefineds; + for (const auto *Sym : RemovedFile->symbols()) { + TargetList SymTargets{Sym->targets().begin(), Sym->targets().end()}; + ExportedSymbol Temp = + ExportedSymbol{Sym->getKind(), + std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), + Sym->isData(), + SymTargets}; + if (Sym->isUndefined()) + Undefineds.emplace_back(std::move(Temp)); + else + Sym->isReexported() ? Reexports.emplace_back(std::move(Temp)) + : Exports.emplace_back(std::move(Temp)); + } + llvm::sort(Exports); + llvm::sort(Reexports); + llvm::sort(Undefineds); + + TargetList MacOSTargets = {Target(AK_arm64, PLATFORM_MACOS)}; + + std::vector ExpectedExportedSymbols = { + {SymbolKind::GlobalSymbol, "_func", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_global", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassA", false, false, true, MacOSTargets}, + }; + std::vector ExpectedReexportedSymbols = { + {SymbolKind::GlobalSymbol, "_funcA", false, false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_globalRe", false, false, true, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassRexport", false, false, true, + MacOSTargets}, + }; + + EXPECT_EQ(ExpectedExportedSymbols.size(), Exports.size()); + EXPECT_EQ(ExpectedReexportedSymbols.size(), Reexports.size()); + EXPECT_EQ(0U, Undefineds.size()); + EXPECT_TRUE(std::equal(Exports.begin(), Exports.end(), + std::begin(ExpectedExportedSymbols))); + EXPECT_TRUE(std::equal(Reexports.begin(), Reexports.end(), + std::begin(ExpectedReexportedSymbols))); +} + +TEST(TBDv5, InlineIF) { + static const char UmbrellaFile[] = R"({ +"tapi_tbd_version": 5, +"main_library": { + "target_info": [ + { + "target": "x86_64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-macos", + "min_deployment": "10.14" + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "reexported_libraries": [ + { + "names": [ + "/u/l/l/libfoo.dylib", + "/u/l/l/libbar.dylib" + ] + } + ] +}})"; + + static const char ReexportFile[] = R"({ +"tapi_tbd_version": 5, +"main_library": { + "target_info": [ + { + "target": "x86_64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-macos", + "min_deployment": "10.14" + } + ], + "install_names": [ + { + "name" : "/u/l/l/libfoo.dylib" + } + ], + "current_versions": [ + { + "version": "1" + } + ], + "rpaths": [ + { + "targets": [ + "x86_64-macos" + ], + "paths": [ + "@executable_path/.../Frameworks" + ] + } + ], + "exported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_global" + ], + "objc_class": [ + "ClassA" + ], + "weak": [], + "thread_local": [] + } + } + ]}})"; + + Expected UmbrellaResult = + TextAPIReader::get(MemoryBufferRef(UmbrellaFile, "Test.tbd")); + EXPECT_TRUE(!!UmbrellaResult); + TBDFile Umbrella = std::move(UmbrellaResult.get()); + + Expected ReexportResult = + TextAPIReader::get(MemoryBufferRef(ReexportFile, "Test.tbd")); + EXPECT_TRUE(!!ReexportResult); + TBDReexportFile Reexport = std::move(ReexportResult.get()); + Umbrella->inlineLibrary(Reexport); + + EXPECT_EQ(FileType::TBD_V5, Umbrella->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), + Umbrella->getInstallName()); + + TargetList AllTargets = { + Target(AK_x86_64, PLATFORM_MACOS, VersionTuple(10, 14)), + Target(AK_arm64, PLATFORM_MACOS, VersionTuple(11, 0, 0)), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), Umbrella->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), Umbrella->getArchitectures()); + + EXPECT_EQ(PackedVersion(1, 2, 0), Umbrella->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 0, 0), Umbrella->getCompatibilityVersion()); + InterfaceFileRef ReexportA("/u/l/l/libbar.dylib", AllTargets); + InterfaceFileRef ReexportB("/u/l/l/libfoo.dylib", AllTargets); + EXPECT_EQ(2U, Umbrella->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, Umbrella->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, Umbrella->reexportedLibraries().at(1)); + EXPECT_EQ(1U, Umbrella->documents().size()); + + TBDReexportFile Document = Umbrella->documents().front(); + EXPECT_EQ(std::string("/u/l/l/libfoo.dylib"), Document->getInstallName()); + EXPECT_EQ(0U, Document->getSwiftABIVersion()); + EXPECT_TRUE(Document->isTwoLevelNamespace()); + EXPECT_TRUE(Document->isApplicationExtensionSafe()); + EXPECT_EQ(PackedVersion(1, 0, 0), Document->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 0, 0), Document->getCompatibilityVersion()); + + ExportedSymbolSeq Exports; + for (const auto *Sym : Document->symbols()) { + TargetList SymTargets{Sym->targets().begin(), Sym->targets().end()}; + Exports.emplace_back( + ExportedSymbol{Sym->getKind(), std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), Sym->isData(), SymTargets}); + } + llvm::sort(Exports); + + ExportedSymbolSeq ExpectedExports = { + {SymbolKind::GlobalSymbol, "_global", false, false, true, AllTargets}, + {SymbolKind::ObjectiveCClass, "ClassA", false, false, true, AllTargets}, + }; + EXPECT_EQ(ExpectedExports.size(), Exports.size()); + EXPECT_TRUE( + std::equal(Exports.begin(), Exports.end(), std::begin(ExpectedExports))); +} } // end namespace TBDv5