Skip to content

Commit fe6aada

Browse files
committed
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 (cherry picked from commit d6f9b97)
1 parent b3f80ad commit fe6aada

File tree

8 files changed

+848
-3
lines changed

8 files changed

+848
-3
lines changed

llvm/include/llvm/TextAPI/InterfaceFile.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,16 @@ class InterfaceFile {
396396

397397
const_filtered_symbol_range exports() const {
398398
std::function<bool(const Symbol *)> fn = [](const Symbol *Symbol) {
399-
return !Symbol->isUndefined();
399+
return !Symbol->isUndefined() && !Symbol->isReexported();
400+
};
401+
return make_filter_range(
402+
make_range<const_symbol_iterator>({Symbols.begin()}, {Symbols.end()}),
403+
fn);
404+
}
405+
406+
const_filtered_symbol_range reexports() const {
407+
std::function<bool(const Symbol *)> fn = [](const Symbol *Symbol) {
408+
return Symbol->isReexported();
400409
};
401410
return make_filter_range(
402411
make_range<const_symbol_iterator>({Symbols.begin()}, {Symbols.end()}),

llvm/include/llvm/TextAPI/PackedVersion.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define LLVM_TEXTAPI_PACKEDVERSION_H
1515

1616
#include <cstdint>
17+
#include <string>
1718
#include <utility>
1819

1920
namespace llvm {
@@ -53,6 +54,8 @@ class PackedVersion {
5354

5455
uint32_t rawValue() const { return Version; }
5556

57+
operator std::string() const;
58+
5659
void print(raw_ostream &OS) const;
5760
};
5861

llvm/include/llvm/TextAPI/TextAPIWriter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class TextAPIWriter {
2222
public:
2323
TextAPIWriter() = delete;
2424

25-
static Error writeToStream(raw_ostream &os, const InterfaceFile &);
25+
static Error writeToStream(raw_ostream &OS, const InterfaceFile &File,
26+
bool Compact = false);
2627
};
2728

2829
} // end namespace MachO.

llvm/lib/TextAPI/PackedVersion.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ std::pair<bool, bool> PackedVersion::parse64(StringRef Str) {
100100
return std::make_pair(true, Truncated);
101101
}
102102

103+
PackedVersion::operator std::string() const {
104+
SmallString<32> Str;
105+
raw_svector_ostream OS(Str);
106+
print(OS);
107+
return std::string(Str);
108+
}
109+
103110
void PackedVersion::print(raw_ostream &OS) const {
104111
OS << format("%d", getMajor());
105112
if (getMinor() || getSubminor())

llvm/lib/TextAPI/TextStub.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1159,10 +1159,17 @@ TextAPIReader::get(MemoryBufferRef InputBuffer) {
11591159
return std::move(File);
11601160
}
11611161

1162-
Error TextAPIWriter::writeToStream(raw_ostream &OS, const InterfaceFile &File) {
1162+
Error TextAPIWriter::writeToStream(raw_ostream &OS, const InterfaceFile &File,
1163+
bool Compact) {
11631164
TextAPIContext Ctx;
11641165
Ctx.Path = std::string(File.getPath());
11651166
Ctx.FileKind = File.getFileType();
1167+
1168+
// Write out in JSON format.
1169+
if (Ctx.FileKind >= FileType::TBD_V5) {
1170+
return serializeInterfaceFileToJSON(OS, File, Compact);
1171+
}
1172+
11661173
llvm::yaml::Output YAMLOut(OS, &Ctx, /*WrapColumn=*/80);
11671174

11681175
std::vector<const InterfaceFile *> Files;

llvm/lib/TextAPI/TextStubCommon.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class PackedVersion;
4545

4646
Expected<std::unique_ptr<InterfaceFile>>
4747
getInterfaceFileFromJSON(StringRef JSON);
48+
49+
Error serializeInterfaceFileToJSON(raw_ostream &OS, const InterfaceFile &File,
50+
bool Compact);
4851
} // namespace MachO
4952

5053
namespace yaml {

llvm/lib/TextAPI/TextStubV5.cpp

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ static llvm::SmallString<128> getParseErrorMsg(TBDKey Key) {
161161
return {"invalid ", Keys[Key], " section"};
162162
}
163163

164+
static llvm::SmallString<128> getSerializeErrorMsg(TBDKey Key) {
165+
return {"missing ", Keys[Key], " information"};
166+
}
167+
164168
class JSONStubError : public llvm::ErrorInfo<llvm::json::ParseError> {
165169
public:
166170
JSONStubError(Twine ErrMsg) : Message(ErrMsg.str()) {}
@@ -716,3 +720,294 @@ MachO::getInterfaceFileFromJSON(StringRef JSON) {
716720
}
717721
return std::move(IF);
718722
}
723+
724+
namespace {
725+
726+
template <typename ContainerT = Array>
727+
bool insertNonEmptyValues(Object &Obj, TBDKey Key, ContainerT &&Contents) {
728+
if (Contents.empty())
729+
return false;
730+
Obj[Keys[Key]] = std::move(Contents);
731+
return true;
732+
}
733+
734+
std::string getFormattedStr(const MachO::Target &Targ) {
735+
std::string PlatformStr = Targ.Platform == PLATFORM_MACCATALYST
736+
? "maccatalyst"
737+
: getOSAndEnvironmentName(Targ.Platform);
738+
return (getArchitectureName(Targ.Arch) + "-" + PlatformStr).str();
739+
}
740+
741+
template <typename AggregateT>
742+
std::vector<std::string> serializeTargets(const AggregateT Targets,
743+
const TargetList &ActiveTargets) {
744+
std::vector<std::string> TargetsStr;
745+
if (Targets.size() == ActiveTargets.size())
746+
return TargetsStr;
747+
748+
llvm::for_each(Targets, [&TargetsStr](const MachO::Target &Target) {
749+
TargetsStr.emplace_back(getFormattedStr(Target));
750+
});
751+
return TargetsStr;
752+
}
753+
754+
Array serializeTargetInfo(const TargetList &ActiveTargets) {
755+
Array Targets;
756+
for (const auto Targ : ActiveTargets) {
757+
Object TargetInfo;
758+
TargetInfo[Keys[TBDKey::Deployment]] = Targ.MinDeployment.getAsString();
759+
TargetInfo[Keys[TBDKey::Target]] = getFormattedStr(Targ);
760+
Targets.emplace_back(std::move(TargetInfo));
761+
}
762+
return Targets;
763+
}
764+
765+
template <typename ValueT, typename EntryT = ValueT>
766+
Array serializeScalar(TBDKey Key, ValueT Value, ValueT Default = ValueT()) {
767+
if (Value == Default)
768+
return {};
769+
Array Container;
770+
Object ScalarObj({Object::KV({Keys[Key], EntryT(Value)})});
771+
772+
Container.emplace_back(std::move(ScalarObj));
773+
return Container;
774+
}
775+
776+
using TargetsToValuesMap =
777+
std::map<std::vector<std::string>, std::vector<std::string>>;
778+
779+
template <typename AggregateT = TargetsToValuesMap>
780+
Array serializeAttrToTargets(AggregateT &Entries, TBDKey Key) {
781+
Array Container;
782+
for (const auto &[Targets, Values] : Entries) {
783+
Object Obj;
784+
insertNonEmptyValues(Obj, TBDKey::Targets, std::move(Targets));
785+
Obj[Keys[Key]] = Values;
786+
Container.emplace_back(std::move(Obj));
787+
}
788+
return Container;
789+
}
790+
791+
template <typename ValueT = std::string,
792+
typename AggregateT = std::vector<std::pair<MachO::Target, ValueT>>>
793+
Array serializeField(TBDKey Key, const AggregateT &Values,
794+
const TargetList &ActiveTargets, bool IsArray = true) {
795+
std::map<ValueT, std::set<MachO::Target>> Entries;
796+
for (const auto &[Target, Val] : Values)
797+
Entries[Val].insert(Target);
798+
799+
if (!IsArray) {
800+
std::map<std::vector<std::string>, std::string> FinalEntries;
801+
for (const auto &[Val, Targets] : Entries)
802+
FinalEntries[serializeTargets(Targets, ActiveTargets)] = Val;
803+
return serializeAttrToTargets(FinalEntries, Key);
804+
}
805+
806+
TargetsToValuesMap FinalEntries;
807+
for (const auto &[Val, Targets] : Entries)
808+
FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back(Val);
809+
return serializeAttrToTargets(FinalEntries, Key);
810+
}
811+
812+
Array serializeField(TBDKey Key, const std::vector<InterfaceFileRef> &Values,
813+
const TargetList &ActiveTargets) {
814+
TargetsToValuesMap FinalEntries;
815+
for (const auto &Ref : Values) {
816+
TargetList Targets{Ref.targets().begin(), Ref.targets().end()};
817+
FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back(
818+
Ref.getInstallName());
819+
}
820+
return serializeAttrToTargets(FinalEntries, Key);
821+
}
822+
823+
struct SymbolFields {
824+
struct SymbolTypes {
825+
std::vector<StringRef> Weaks;
826+
std::vector<StringRef> Globals;
827+
std::vector<StringRef> TLV;
828+
std::vector<StringRef> ObjCClasses;
829+
std::vector<StringRef> IVars;
830+
std::vector<StringRef> EHTypes;
831+
832+
bool empty() const {
833+
return Weaks.empty() && Globals.empty() && TLV.empty() &&
834+
ObjCClasses.empty() && IVars.empty() && EHTypes.empty();
835+
}
836+
};
837+
SymbolTypes Data;
838+
SymbolTypes Text;
839+
};
840+
841+
Array serializeSymbols(InterfaceFile::const_filtered_symbol_range Symbols,
842+
const TargetList &ActiveTargets) {
843+
auto AssignForSymbolType = [](SymbolFields::SymbolTypes &Assignment,
844+
const Symbol *Sym) {
845+
switch (Sym->getKind()) {
846+
case SymbolKind::ObjectiveCClass:
847+
Assignment.ObjCClasses.emplace_back(Sym->getName());
848+
return;
849+
case SymbolKind::ObjectiveCClassEHType:
850+
Assignment.EHTypes.emplace_back(Sym->getName());
851+
return;
852+
case SymbolKind::ObjectiveCInstanceVariable:
853+
Assignment.IVars.emplace_back(Sym->getName());
854+
return;
855+
case SymbolKind::GlobalSymbol: {
856+
if (Sym->isWeakReferenced() || Sym->isWeakDefined())
857+
Assignment.Weaks.emplace_back(Sym->getName());
858+
else if (Sym->isThreadLocalValue())
859+
Assignment.TLV.emplace_back(Sym->getName());
860+
else
861+
Assignment.Globals.emplace_back(Sym->getName());
862+
return;
863+
}
864+
}
865+
};
866+
867+
std::map<std::vector<std::string>, SymbolFields> Entries;
868+
for (const auto *Sym : Symbols) {
869+
std::set<MachO::Target> Targets{Sym->targets().begin(),
870+
Sym->targets().end()};
871+
auto JSONTargets = serializeTargets(Targets, ActiveTargets);
872+
if (Sym->isData())
873+
AssignForSymbolType(Entries[std::move(JSONTargets)].Data, Sym);
874+
else if (Sym->isText())
875+
AssignForSymbolType(Entries[std::move(JSONTargets)].Text, Sym);
876+
else
877+
llvm_unreachable("unexpected symbol type");
878+
}
879+
880+
auto InsertSymbolsToJSON = [](Object &SymSection, TBDKey SegmentKey,
881+
SymbolFields::SymbolTypes &SymField) {
882+
if (SymField.empty())
883+
return;
884+
Object Segment;
885+
insertNonEmptyValues(Segment, TBDKey::Globals, std::move(SymField.Globals));
886+
insertNonEmptyValues(Segment, TBDKey::ThreadLocal, std::move(SymField.TLV));
887+
insertNonEmptyValues(Segment, TBDKey::Weak, std::move(SymField.Weaks));
888+
insertNonEmptyValues(Segment, TBDKey::ObjCClass,
889+
std::move(SymField.ObjCClasses));
890+
insertNonEmptyValues(Segment, TBDKey::ObjCEHType,
891+
std::move(SymField.EHTypes));
892+
insertNonEmptyValues(Segment, TBDKey::ObjCIvar, std::move(SymField.IVars));
893+
insertNonEmptyValues(SymSection, SegmentKey, std::move(Segment));
894+
};
895+
896+
Array SymbolSection;
897+
for (auto &[Targets, Fields] : Entries) {
898+
Object AllSyms;
899+
insertNonEmptyValues(AllSyms, TBDKey::Targets, std::move(Targets));
900+
InsertSymbolsToJSON(AllSyms, TBDKey::Data, Fields.Data);
901+
InsertSymbolsToJSON(AllSyms, TBDKey::Text, Fields.Text);
902+
SymbolSection.emplace_back(std::move(AllSyms));
903+
}
904+
905+
return SymbolSection;
906+
}
907+
908+
Array serializeFlags(const InterfaceFile *File) {
909+
// TODO: Give all Targets the same flags for now.
910+
Array Flags;
911+
if (!File->isTwoLevelNamespace())
912+
Flags.emplace_back("flat_namespace");
913+
if (!File->isApplicationExtensionSafe())
914+
Flags.emplace_back("not_app_extension_safe");
915+
return serializeScalar(TBDKey::Attributes, std::move(Flags));
916+
}
917+
918+
Expected<Object> serializeIF(const InterfaceFile *File) {
919+
Object Library;
920+
921+
// Handle required keys.
922+
TargetList ActiveTargets{File->targets().begin(), File->targets().end()};
923+
if (!insertNonEmptyValues(Library, TBDKey::TargetInfo,
924+
serializeTargetInfo(ActiveTargets)))
925+
return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::TargetInfo));
926+
927+
Array Name = serializeScalar<StringRef>(TBDKey::Name, File->getInstallName());
928+
if (!insertNonEmptyValues(Library, TBDKey::InstallName, std::move(Name)))
929+
return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::InstallName));
930+
931+
// Handle optional keys.
932+
Array Flags = serializeFlags(File);
933+
insertNonEmptyValues(Library, TBDKey::Flags, std::move(Flags));
934+
935+
Array CurrentV = serializeScalar<PackedVersion, std::string>(
936+
TBDKey::Version, File->getCurrentVersion(), PackedVersion(1, 0, 0));
937+
insertNonEmptyValues(Library, TBDKey::CurrentVersion, std::move(CurrentV));
938+
939+
Array CompatV = serializeScalar<PackedVersion, std::string>(
940+
TBDKey::Version, File->getCompatibilityVersion(), PackedVersion(1, 0, 0));
941+
insertNonEmptyValues(Library, TBDKey::CompatibilityVersion,
942+
std::move(CompatV));
943+
944+
Array SwiftABI = serializeScalar<uint8_t, int64_t>(
945+
TBDKey::ABI, File->getSwiftABIVersion(), 0u);
946+
insertNonEmptyValues(Library, TBDKey::SwiftABI, std::move(SwiftABI));
947+
948+
Array RPaths = serializeField(TBDKey::Paths, File->rpaths(), ActiveTargets);
949+
insertNonEmptyValues(Library, TBDKey::RPath, std::move(RPaths));
950+
951+
Array Umbrellas = serializeField(TBDKey::Umbrella, File->umbrellas(),
952+
ActiveTargets, /*IsArray=*/false);
953+
insertNonEmptyValues(Library, TBDKey::ParentUmbrella, std::move(Umbrellas));
954+
955+
Array Clients =
956+
serializeField(TBDKey::Clients, File->allowableClients(), ActiveTargets);
957+
insertNonEmptyValues(Library, TBDKey::AllowableClients, std::move(Clients));
958+
959+
Array ReexportLibs =
960+
serializeField(TBDKey::Names, File->reexportedLibraries(), ActiveTargets);
961+
insertNonEmptyValues(Library, TBDKey::ReexportLibs, std::move(ReexportLibs));
962+
963+
// Handle symbols.
964+
Array Exports = serializeSymbols(File->exports(), ActiveTargets);
965+
insertNonEmptyValues(Library, TBDKey::Exports, std::move(Exports));
966+
967+
Array Reexports = serializeSymbols(File->reexports(), ActiveTargets);
968+
insertNonEmptyValues(Library, TBDKey::Reexports, std::move(Reexports));
969+
970+
if (!File->isTwoLevelNamespace()) {
971+
Array Undefineds = serializeSymbols(File->undefineds(), ActiveTargets);
972+
insertNonEmptyValues(Library, TBDKey::Undefineds, std::move(Undefineds));
973+
}
974+
975+
return std::move(Library);
976+
}
977+
978+
Expected<Object> getJSON(const InterfaceFile *File) {
979+
assert(File->getFileType() == FileType::TBD_V5 &&
980+
"unexpected json file format version");
981+
Object Root;
982+
983+
auto MainLibOrErr = serializeIF(File);
984+
if (!MainLibOrErr)
985+
return MainLibOrErr;
986+
Root[Keys[TBDKey::MainLibrary]] = std::move(*MainLibOrErr);
987+
Array Documents;
988+
for (const auto &Doc : File->documents()) {
989+
auto LibOrErr = serializeIF(Doc.get());
990+
if (!LibOrErr)
991+
return LibOrErr;
992+
Documents.emplace_back(std::move(*LibOrErr));
993+
}
994+
995+
Root[Keys[TBDKey::TBDVersion]] = 5;
996+
insertNonEmptyValues(Root, TBDKey::Documents, std::move(Documents));
997+
return std::move(Root);
998+
}
999+
1000+
} // namespace
1001+
1002+
Error MachO::serializeInterfaceFileToJSON(raw_ostream &OS,
1003+
const InterfaceFile &File,
1004+
bool Compact) {
1005+
auto TextFile = getJSON(&File);
1006+
if (!TextFile)
1007+
return TextFile.takeError();
1008+
if (Compact)
1009+
OS << formatv("{0}", Value(std::move(*TextFile))) << "\n";
1010+
else
1011+
OS << formatv("{0:2}", Value(std::move(*TextFile))) << "\n";
1012+
return Error::success();
1013+
}

0 commit comments

Comments
 (0)