diff --git a/llvm/include/llvm/TextAPI/TextAPIWriter.h b/llvm/include/llvm/TextAPI/TextAPIWriter.h index 89fc984854dba..7fd32c6fe2a9e 100644 --- a/llvm/include/llvm/TextAPI/TextAPIWriter.h +++ b/llvm/include/llvm/TextAPI/TextAPIWriter.h @@ -9,6 +9,7 @@ #ifndef LLVM_TEXTAPI_TEXTAPIWRITER_H #define LLVM_TEXTAPI_TEXTAPIWRITER_H +#include "llvm/ADT/StringSwitch.h" #include "llvm/TextAPI/InterfaceFile.h" namespace llvm { @@ -32,6 +33,19 @@ class TextAPIWriter { static Error writeToStream(raw_ostream &OS, const InterfaceFile &File, const FileType FileKind = FileType::Invalid, bool Compact = false); + + /// Get TAPI FileType from the input string. + /// + /// \param FT String of input to map to FileType. + static FileType parseFileType(const StringRef FT) { + return StringSwitch(FT) + .Case("tbd-v1", FileType::TBD_V1) + .Case("tbd-v2", FileType::TBD_V2) + .Case("tbd-v3", FileType::TBD_V3) + .Case("tbd-v4", FileType::TBD_V4) + .Case("tbd-v5", FileType::TBD_V5) + .Default(FileType::Invalid); + } }; } // end namespace MachO. diff --git a/llvm/test/tools/llvm-readtapi/command-line.test b/llvm/test/tools/llvm-readtapi/command-line.test index 3b17194a7c147..400e0670e5aa0 100644 --- a/llvm/test/tools/llvm-readtapi/command-line.test +++ b/llvm/test/tools/llvm-readtapi/command-line.test @@ -1,7 +1,13 @@ ; RUN: llvm-readtapi --help 2>&1 | FileCheck %s ; RUN: llvm-readtapi -help 2>&1 | FileCheck %s +; RUN: not llvm-readtapi -merge -compare -compact %t/tmp.tbd %t/tmp2.tbd 2>&1 | FileCheck %s --check-prefix MULTI_ACTION +; RUN: not llvm-readtapi -merge -compact %t/tmp.tbd %t/tmp2.tbd --filetype=tbd-v2 2>&1 | FileCheck %s --check-prefix FILE_FORMAT CHECK: OVERVIEW: LLVM TAPI file reader and manipulator CHECK: USAGE: llvm-readtapi [options] CHECK: OPTIONS: CHECK: -help display this help + +MULTI_ACTION: error: only one of the following actions can be specified: -merge -compare +FILE_FORMAT: error: deprecated filetype 'tbd-v2' is not supported to write + diff --git a/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test b/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test index 32a350b28eb1e..09dc768518ebc 100644 --- a/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test +++ b/llvm/test/tools/llvm-readtapi/compare-incorrect-format.test @@ -2,6 +2,6 @@ ; RUN: yaml2obj %S/Inputs/macho.yaml -o %t/macho.dylib ; RUN: not llvm-readtapi --compare %S/Inputs/v4A.tbd %t/macho.dylib 2>&1 | FileCheck %s -; CHECK: error: {{.*}}macho.dylib' unsupported file format +; CHECK: error: {{.*}}macho.dylib' unsupported file type ; CHECK-NOT: error: ; CHECK-NOT: warning: diff --git a/llvm/test/tools/llvm-readtapi/merge-invalid.test b/llvm/test/tools/llvm-readtapi/merge-invalid.test new file mode 100644 index 0000000000000..f66bfbe899815 --- /dev/null +++ b/llvm/test/tools/llvm-readtapi/merge-invalid.test @@ -0,0 +1,52 @@ +; RUN: rm -rf %t +; RUN: split-file %s %t +; RUN: not llvm-readtapi -merge %t/libfoo.tbd %t/libbar.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix DIFF +; RUN: not llvm-readtapi -merge %t/libfoo.tbd 2>&1 | FileCheck %s --allow-empty --check-prefix INPUT + +; DIFF: install names do not match +; INPUT: merge requires at least two input files + +;--- libfoo.tbd +{ + "main_library": { + "allowable_clients": [ + { + "clients": [ + "ClientAll" + ] + } + ], + "install_names": [ + { + "name": "/usr/lib/libfoo.dylib" + } + ], + "target_info": [ + { + "min_deployment": "13.1", + "target": "x86_64-macos" + } + ] + }, + "tapi_tbd_version": 5 +} + +;--- libbar.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +install-name: '/usr/lib/libbar.dylib' +allowable-clients: + - targets: [ arm64-macos ] + clients: [ ClientAll ] +reexported-libraries: + - targets: [ arm64-macos ] + libraries: [ '/usr/lib/liball.dylib' ] +exports: + - targets: [ arm64-macos ] + symbols: [ _sym1 ] + objc-classes: [ _A ] + objc-ivars: [ _A._ivar1 ] + weak-symbols: [ _weak1 ] + thread-local-symbols: [ _tlv1 ] +... diff --git a/llvm/test/tools/llvm-readtapi/merge.test b/llvm/test/tools/llvm-readtapi/merge.test new file mode 100644 index 0000000000000..a0aa7ac55e3cf --- /dev/null +++ b/llvm/test/tools/llvm-readtapi/merge.test @@ -0,0 +1,139 @@ +; RUN: rm -rf %t +; RUN: split-file %s %t +; RUN: llvm-readtapi -merge %t/i386.tbd %t/x86_64.tbd %t/arm64.tbd --filetype tbd-v5 -o %t/out.tbd -compact 2>&1 | FileCheck %s --allow-empty +; RUN: llvm-readtapi -merge %t/i386.tbd %t/x86_64.tbd %t/arm64.tbd --filetype=tbd-v5 --o %t/out.tbd -compact 2>&1 | FileCheck %s --allow-empty +; RUN: llvm-readtapi -compare %t/out.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty + +; CHECK-NOT: error +; CHECK-NOT: warning + +;--- expected.tbd +{ + "main_library": { + "allowable_clients": [{ "clients": ["ClientAll"] }], + "exported_symbols": [ + { + "data": { + "global": ["_sym1"], + "objc_class": ["_A"], + "thread_local": ["_tlv1"], + "weak": ["_weak1"] + } + }, + { + "data": { + "objc_ivar": ["_A._ivar1"] + }, + "targets": [ "x86_64-macos", "arm64-macos" ] + } + ], + "install_names": [ + { "name": "/usr/lib/libfat.dylib" } + ], + "reexported_libraries": [ + { + "names": [ "/usr/lib/liball.dylib" ] + } + ], + "target_info": [ + { "target": "i386-macos" }, + { + "min_deployment": "13.1", + "target": "x86_64-macos" + }, + { + "target": "arm64-macos" + } + ] + }, + "tapi_tbd_version": 5 +} + + +;--- i386.tbd +--- !tapi-tbd-v3 +archs: [ i386 ] +platform: macosx +install-name: /usr/lib/libfat.dylib +exports: + - archs: [ i386 ] + allowable-clients: [ ClientAll ] + re-exports: [ /usr/lib/liball.dylib ] + symbols: [ _sym1 ] + objc-classes: [ _A ] + weak-def-symbols: [ _weak1 ] + thread-local-symbols: [ _tlv1 ] +... + +;--- x86_64.tbd +{ + "main_library": { + "allowable_clients": [ + { + "clients": [ + "ClientAll" + ] + } + ], + "exported_symbols": [ + { + "data": { + "global": [ + "_sym1" + ], + "objc_class": [ + "_A" + ], + "objc_ivar": [ + "_A._ivar1" + ], + "thread_local": [ + "_tlv1" + ], + "weak": [ + "_weak1" + ] + } + } + ], + "install_names": [ + { + "name": "/usr/lib/libfat.dylib" + } + ], + "reexported_libraries": [ + { + "names": [ + "/usr/lib/liball.dylib" + ] + } + ], + "target_info": [ + { + "min_deployment": "13.1", + "target": "x86_64-macos" + } + ] + }, + "tapi_tbd_version": 5 +} + +;--- arm64.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +install-name: '/usr/lib/libfat.dylib' +allowable-clients: + - targets: [ arm64-macos ] + clients: [ ClientAll ] +reexported-libraries: + - targets: [ arm64-macos ] + libraries: [ '/usr/lib/liball.dylib' ] +exports: + - targets: [ arm64-macos ] + symbols: [ _sym1 ] + objc-classes: [ _A ] + objc-ivars: [ _A._ivar1 ] + weak-symbols: [ _weak1 ] + thread-local-symbols: [ _tlv1 ] +... diff --git a/llvm/test/tools/llvm-readtapi/write.test b/llvm/test/tools/llvm-readtapi/write.test new file mode 100644 index 0000000000000..1ec7a40a2e405 --- /dev/null +++ b/llvm/test/tools/llvm-readtapi/write.test @@ -0,0 +1,77 @@ +; RUN: rm -rf %t +; RUN: split-file %s %t +; RUN: llvm-readtapi %t/arm64.tbd 2>&1 | FileCheck %s + +; CHECK-NOT: error +; CHECK-NOT: warning +; CHECK: { +; CHECK-NEXT: "main_library": { +; CHECK-NEXT: "allowable_clients": [ +; CHECK-NEXT: { +; CHECK-NEXT: "clients": [ +; CHECK-NEXT: "ClientAll" +; CHECK-NEXT: ] +; CHECK-NEXT: } +; CHECK-NEXT: ], +; CHECK-NEXT: "exported_symbols": [ +; CHECK-NEXT: { +; CHECK-NEXT: "data": { +; CHECK-NEXT: "global": [ +; CHECK-NEXT: "_sym1" +; CHECK-NEXT: ], +; CHECK-NEXT: "objc_class": [ +; CHECK-NEXT: "_A" +; CHECK-NEXT: ], +; CHECK-NEXT: "objc_ivar": [ +; CHECK-NEXT: "_A._ivar1" +; CHECK-NEXT: ], +; CHECK-NEXT: "thread_local": [ +; CHECK-NEXT: "_tlv1" +; CHECK-NEXT: ], +; CHECK-NEXT: "weak": [ +; CHECK-NEXT: "_weak1" +; CHECK-NEXT: ] +; CHECK-NEXT: } +; CHECK-NEXT: } +; CHECK-NEXT: ], +; CHECK-NEXT: "install_names": [ +; CHECK-NEXT: { +; CHECK-NEXT: "name": "/usr/lib/libfat.dylib" +; CHECK-NEXT: } +; CHECK-NEXT: ], +; CHECK-NEXT: "reexported_libraries": [ +; CHECK-NEXT: { +; CHECK-NEXT: "names": [ +; CHECK-NEXT: "/usr/lib/liball.dylib" +; CHECK-NEXT: ] +; CHECK-NEXT: } +; CHECK-NEXT: ], +; CHECK-NEXT: "target_info": [ +; CHECK-NEXT: { +; CHECK-NEXT: "target": "arm64-macos" +; CHECK-NEXT: } +; CHECK-NEXT: ] +; CHECK-NEXT: }, +; CHECK-NEXT: "tapi_tbd_version": 5 +; CHECK-NEXT: } + + +;--- arm64.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +install-name: '/usr/lib/libfat.dylib' +allowable-clients: + - targets: [ arm64-macos ] + clients: [ ClientAll ] +reexported-libraries: + - targets: [ arm64-macos ] + libraries: [ '/usr/lib/liball.dylib' ] +exports: + - targets: [ arm64-macos ] + symbols: [ _sym1 ] + objc-classes: [ _A ] + objc-ivars: [ _A._ivar1 ] + weak-symbols: [ _weak1 ] + thread-local-symbols: [ _tlv1 ] +... diff --git a/llvm/tools/llvm-readtapi/DiffEngine.cpp b/llvm/tools/llvm-readtapi/DiffEngine.cpp index 5f4b25ca6c0b2..40722d2da180f 100644 --- a/llvm/tools/llvm-readtapi/DiffEngine.cpp +++ b/llvm/tools/llvm-readtapi/DiffEngine.cpp @@ -560,13 +560,11 @@ void DiffEngine::printDifferences(raw_ostream &OS, } bool DiffEngine::compareFiles(raw_ostream &OS) { - const auto *IFLHS = &(FileLHS->getInterfaceFile()); - const auto *IFRHS = &(FileRHS->getInterfaceFile()); - if (*IFLHS == *IFRHS) + if (*FileLHS == *FileRHS) return false; - OS << "< " << std::string(IFLHS->getPath().data()) << "\n> " - << std::string(IFRHS->getPath().data()) << "\n\n"; - std::vector Diffs = findDifferences(IFLHS, IFRHS); + OS << "< " << std::string(FileLHS->getPath().data()) << "\n> " + << std::string(FileRHS->getPath().data()) << "\n\n"; + std::vector Diffs = findDifferences(FileLHS, FileRHS); printDifferences(OS, Diffs, 0); return true; } diff --git a/llvm/tools/llvm-readtapi/DiffEngine.h b/llvm/tools/llvm-readtapi/DiffEngine.h index 27b72573d011e..5f7c29c920814 100644 --- a/llvm/tools/llvm-readtapi/DiffEngine.h +++ b/llvm/tools/llvm-readtapi/DiffEngine.h @@ -141,14 +141,14 @@ class InlineDoc : public AttributeDiff { /// output of the differences found in the files. class DiffEngine { public: - DiffEngine(object::TapiUniversal *InputFileNameLHS, - object::TapiUniversal *InputFileNameRHS) + DiffEngine(MachO::InterfaceFile *InputFileNameLHS, + MachO::InterfaceFile *InputFileNameRHS) : FileLHS(InputFileNameLHS), FileRHS(InputFileNameRHS){}; bool compareFiles(raw_ostream &); private: - object::TapiUniversal *FileLHS; - object::TapiUniversal *FileRHS; + MachO::InterfaceFile *FileLHS; + MachO::InterfaceFile *FileRHS; /// Function that prints the differences found in the files. void printDifferences(raw_ostream &, const std::vector &, int); diff --git a/llvm/tools/llvm-readtapi/TapiOpts.td b/llvm/tools/llvm-readtapi/TapiOpts.td index 932a21f7d0711..1efa86ea3ae48 100644 --- a/llvm/tools/llvm-readtapi/TapiOpts.td +++ b/llvm/tools/llvm-readtapi/TapiOpts.td @@ -8,12 +8,16 @@ multiclass JS { } // -// General Driver options +// Top level operations // -def help : FF<"help", "display this help">; -defm output: JS<"o", "write output to ","">; +def action_group : OptionGroup<"action group">; +def compare : FF<"compare", "compare tapi files for library differences">, Group; +def merge : FF<"merge", "merge the input files that represent the same library">, Group; // -// Compare options +// General Driver options // -def compare : FF<"compare", "compare tapi files for library differences">; +def help : FF<"help", "display this help">; +defm output: JS<"o", "write output to ","">; +def compact: FF<"compact", "write compact tapi output file">; +defm filetype: JS<"filetype", "specify the output file type (tbd-v3, tbd-v4 or tbd-v5)","">; diff --git a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp index 1307916120e98..3e0bcc49d19cc 100644 --- a/llvm/tools/llvm-readtapi/llvm-readtapi.cpp +++ b/llvm/tools/llvm-readtapi/llvm-readtapi.cpp @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// #include "DiffEngine.h" -#include "llvm/Object/TapiUniversal.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -21,6 +20,8 @@ #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TextAPI/TextAPIError.h" +#include "llvm/TextAPI/TextAPIReader.h" +#include "llvm/TextAPI/TextAPIWriter.h" #include using namespace llvm; @@ -56,18 +57,32 @@ class TAPIOptTable : public opt::GenericOptTable { } }; +// Handle error reporting in cases where `ExitOnError` is not used. +void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { + WithColor::error(errs()) << Message << "\n"; + errs().flush(); + exit(ExitCode); +} + struct Context { std::vector Inputs; std::unique_ptr OutStream; + FileType WriteFT = FileType::TBD_V5; + bool Compact = false; }; -Expected> -convertFileToBinary(const StringRef Filename) { +std::unique_ptr getInterfaceFile(const StringRef Filename, + ExitOnError &ExitOnErr) { + ExitOnErr.setBanner("error: '" + Filename.str() + "' "); ErrorOr> BufferOrErr = - MemoryBuffer::getFileOrSTDIN(Filename); + MemoryBuffer::getFile(Filename); if (BufferOrErr.getError()) - return errorCodeToError(BufferOrErr.getError()); - return createBinary(BufferOrErr.get()->getMemBufferRef()); + ExitOnErr(errorCodeToError(BufferOrErr.getError())); + Expected> IF = + TextAPIReader::get((*BufferOrErr)->getMemBufferRef()); + if (!IF) + ExitOnErr(IF.takeError()); + return std::move(*IF); } // Use unique exit code to differentiate failures not directly caused from @@ -76,33 +91,50 @@ convertFileToBinary(const StringRef Filename) { const int NON_TAPI_EXIT_CODE = 2; bool handleCompareAction(const Context &Ctx) { + if (Ctx.Inputs.size() != 2) + reportError("compare only supports two input files", + /*ExitCode=*/NON_TAPI_EXIT_CODE); + ExitOnError ExitOnErr("error: ", /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); - if (Ctx.Inputs.size() != 2) { - ExitOnErr(make_error(TextAPIErrorCode::InvalidInputFormat, - "compare only supports 2 input files")); - } - StringRef InputFileName = Ctx.Inputs.front(); - ExitOnErr.setBanner("error: '" + InputFileName.str() + "' "); - auto BinLHS = ExitOnErr(convertFileToBinary(InputFileName)); - - TapiUniversal *FileLHS = dyn_cast(BinLHS.get()); - if (!FileLHS) { - ExitOnErr(createStringError(std::errc::executable_format_error, - "unsupported file format")); - } + auto LeftIF = getInterfaceFile(Ctx.Inputs.front(), ExitOnErr); + auto RightIF = getInterfaceFile(Ctx.Inputs.at(1), ExitOnErr); - StringRef CompareInputFileName = Ctx.Inputs.at(1); - ExitOnErr.setBanner("error: '" + CompareInputFileName.str() + "' "); - auto BinRHS = ExitOnErr(convertFileToBinary(CompareInputFileName)); + raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); + return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); +} - TapiUniversal *FileRHS = dyn_cast(BinRHS.get()); - if (!FileRHS) { - ExitOnErr(createStringError(std::errc::executable_format_error, - "unsupported file format")); +bool handleWriteAction(const Context &Ctx, + std::unique_ptr Out = nullptr) { + ExitOnError ExitOnErr("error: "); + if (!Out) { + if (Ctx.Inputs.size() != 1) + reportError("write only supports one input file"); + Out = getInterfaceFile(Ctx.Inputs.front(), ExitOnErr); } - raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); - return DiffEngine(FileLHS, FileRHS).compareFiles(OS); + ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); + return EXIT_SUCCESS; +} + +bool handleMergeAction(const Context &Ctx) { + if (Ctx.Inputs.size() < 2) + reportError("merge requires at least two input files"); + + ExitOnError ExitOnErr("error: "); + std::unique_ptr Out; + for (StringRef FileName : Ctx.Inputs) { + auto IF = getInterfaceFile(FileName, ExitOnErr); + // On the first iteration copy the input file and skip merge. + if (!Out) { + Out = std::move(IF); + continue; + } + auto ResultIF = Out->merge(IF.get()); + if (!ResultIF) + ExitOnErr(ResultIF.takeError()); + Out = std::move(ResultIF.get()); + } + return handleWriteAction(Ctx, std::move(Out)); } } // anonymous namespace @@ -138,8 +170,39 @@ int main(int Argc, char **Argv) { } } - if (Args.hasArg(OPT_compare)) + Ctx.Compact = Args.hasArg(OPT_compact); + + if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { + StringRef FT = A->getValue(); + Ctx.WriteFT = TextAPIWriter::parseFileType(FT); + if (Ctx.WriteFT < FileType::TBD_V3) + reportError("deprecated filetype '" + FT + "' is not supported to write"); + if (Ctx.WriteFT == FileType::Invalid) + reportError("unsupported filetype '" + FT + "'"); + } + + // Handle top level and exclusive operation. + SmallVector ActionArgs(Args.filtered(OPT_action_group)); + + if (ActionArgs.empty()) + // If no action specified, write out tapi file in requested format. + return handleWriteAction(Ctx); + + if (ActionArgs.size() > 1) { + std::string Buf; + raw_string_ostream OS(Buf); + OS << "only one of the following actions can be specified:"; + for (auto *Arg : ActionArgs) + OS << " " << Arg->getSpelling(); + reportError(OS.str()); + } + + switch (ActionArgs.front()->getOption().getID()) { + case OPT_compare: return handleCompareAction(Ctx); + case OPT_merge: + return handleMergeAction(Ctx); + } return EXIT_SUCCESS; }