From 6e98d8a8974eb38258c8de87d3cd82da22d09225 Mon Sep 17 00:00:00 2001 From: Sameer Arora Date: Wed, 1 Jul 2020 15:02:22 -0700 Subject: [PATCH] [llvm-libtool-darwin] Add support for -static option Add support for creating static libraries when the input includes only Mach-O binaries (and not libraries/archives themselves). Reviewed by alexshap, Ktwu, smeenai, jhenderson, MaskRay, mtrent Differential Revision: https://reviews.llvm.org/D83002 --- docs/CommandGuide/llvm-libtool-darwin.rst | 4 + test/tools/llvm-libtool-darwin/basic.test | 10 --- .../create-static-lib.test | 66 ++++++++++++++++ .../llvm-libtool-darwin/help-message.test | 12 ++- .../invalid-input-output-args.test | 35 ++++++++- .../missing-library-type.test | 5 ++ tools/llvm-libtool-darwin/CMakeLists.txt | 1 + tools/llvm-libtool-darwin/LLVMBuild.txt | 2 +- .../llvm-libtool-darwin.cpp | 75 +++++++++++++++++++ 9 files changed, 191 insertions(+), 19 deletions(-) delete mode 100644 test/tools/llvm-libtool-darwin/basic.test create mode 100644 test/tools/llvm-libtool-darwin/create-static-lib.test create mode 100644 test/tools/llvm-libtool-darwin/missing-library-type.test diff --git a/docs/CommandGuide/llvm-libtool-darwin.rst b/docs/CommandGuide/llvm-libtool-darwin.rst index 0baacfd88e8..6bae95c3a66 100644 --- a/docs/CommandGuide/llvm-libtool-darwin.rst +++ b/docs/CommandGuide/llvm-libtool-darwin.rst @@ -42,6 +42,10 @@ OPTIONS Specify the output file name. Must be specified exactly once. +.. option:: -static + + Produces a static library from the input files. + EXIT STATUS ----------- diff --git a/test/tools/llvm-libtool-darwin/basic.test b/test/tools/llvm-libtool-darwin/basic.test deleted file mode 100644 index 5cb6fea899b..00000000000 --- a/test/tools/llvm-libtool-darwin/basic.test +++ /dev/null @@ -1,10 +0,0 @@ -## This test checks that main exits normally (error code 0) for correct input/output args. - -# RUN: yaml2obj %S/Inputs/input1.yaml -o %t-input1.o -# RUN: yaml2obj %S/Inputs/input2.yaml -o %t-input2.o - -## Pass single input: -# RUN: llvm-libtool-darwin -o %t.lib %t-input1.o - -## Pass multiple inputs: -# RUN: llvm-libtool-darwin -o %t.lib %t-input1.o %t-input2.o diff --git a/test/tools/llvm-libtool-darwin/create-static-lib.test b/test/tools/llvm-libtool-darwin/create-static-lib.test new file mode 100644 index 00000000000..6c96782b57d --- /dev/null +++ b/test/tools/llvm-libtool-darwin/create-static-lib.test @@ -0,0 +1,66 @@ +## This test checks that a correct static library is created. + +# RUN: yaml2obj %S/Inputs/input1.yaml -o %t-input1.o +# RUN: yaml2obj %S/Inputs/input2.yaml -o %t-input2.o + +# RUN: rm -rf %t.lib +# RUN: llvm-libtool-darwin -static -o %t.lib %t-input1.o %t-input2.o + +## Check that binaries are present: +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp + +# CHECK-NAMES: [[PREFIX]]-input1.o +# CHECK-NAMES-NEXT: [[PREFIX]]-input2.o + +## Check that symbols are present: +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# CHECK-SYMBOLS: Archive map +# CHECK-SYMBOLS-NEXT: _symbol1 in [[PREFIX]]-input1.o +# CHECK-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# CHECK-SYMBOLS-EMPTY: + +## Check that output archive is in Darwin format: +# RUN: llvm-objdump --macho --archive-headers %t.lib | \ +# RUN: FileCheck %s --check-prefix=FORMAT -DPREFIX=%basename_t.tmp -DARCHIVE=%t + +# FORMAT: Archive : [[ARCHIVE]] +# FORMAT-NEXT: __.SYMDEF +# FORMAT-NEXT: [[PREFIX]]-input1.o +# FORMAT-NEXT: [[PREFIX]]-input2.o +# FORMAT-NOT: {{.}} + +## Check that the output file is overwritten: +# RUN: llvm-libtool-darwin -static -o %t.lib %t-input2.o +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=OVERWRITE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=OVERWRITE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# OVERWRITE-NAMES: [[PREFIX]]-input2.o + +# OVERWRITE-SYMBOLS: Archive map +# OVERWRITE-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# OVERWRITE-SYMBOLS-EMPTY: + +## Duplicate a binary: +## cctools' libtool raises a warning in this case. +## The warning is not yet implemented for llvm-libtool-darwin. +# RUN: llvm-libtool-darwin -static -o %t.lib %t-input1.o %t-input2.o %t-input1.o 2>&1 | \ +# RUN: FileCheck %s --allow-empty --implicit-check-not={{.}} +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=DUPLICATE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=DUPLICATE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# DUPLICATE-NAMES: [[PREFIX]]-input1.o +# DUPLICATE-NAMES-NEXT: [[PREFIX]]-input2.o +# DUPLICATE-NAMES-NEXT: [[PREFIX]]-input1.o + +# DUPLICATE-SYMBOLS: Archive map +# DUPLICATE-SYMBOLS-NEXT: _symbol1 in [[PREFIX]]-input1.o +# DUPLICATE-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# DUPLICATE-SYMBOLS-NEXT: _symbol1 in [[PREFIX]]-input1.o +# DUPLICATE-SYMBOLS-EMPTY: diff --git a/test/tools/llvm-libtool-darwin/help-message.test b/test/tools/llvm-libtool-darwin/help-message.test index 4face3887ed..d8b8c79ba8e 100644 --- a/test/tools/llvm-libtool-darwin/help-message.test +++ b/test/tools/llvm-libtool-darwin/help-message.test @@ -1,10 +1,13 @@ ## This test checks that the help message is displayed correctly. -# RUN: llvm-libtool-darwin -h | FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines -# RUN: llvm-libtool-darwin -help | FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines -# RUN: llvm-libtool-darwin --help | FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines +# RUN: llvm-libtool-darwin -h | \ +# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines --implicit-check-not="General options:" +# RUN: llvm-libtool-darwin -help | \ +# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines --implicit-check-not="General options:" +# RUN: llvm-libtool-darwin --help | \ +# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines --implicit-check-not="General options:" # RUN: llvm-libtool-darwin --help-list | \ -# RUN: FileCheck -check-prefixes=LIBTOOL-USAGE,LIST %s --match-full-lines +# RUN: FileCheck -check-prefixes=LIBTOOL-USAGE,LIST %s --match-full-lines --implicit-check-not="safepoint-ir-verifier-print-only" # RUN: not llvm-libtool-darwin -abcabc 2>&1 | FileCheck --check-prefix=UNKNOWN-ARG %s # RUN: not llvm-libtool-darwin --abcabc 2>&1 | FileCheck --check-prefix=UNKNOWN-ARG %s @@ -19,5 +22,6 @@ # LIST-NOT: Generic Options: # CATEG: llvm-libtool-darwin Options: # LIST-NOT: llvm-libtool-darwin Options: +# LIST-NOT: General options: # UNKNOWN-ARG: Unknown command line argument '{{-+}}abcabc' diff --git a/test/tools/llvm-libtool-darwin/invalid-input-output-args.test b/test/tools/llvm-libtool-darwin/invalid-input-output-args.test index 5b4d2d988ae..66ee65b2624 100644 --- a/test/tools/llvm-libtool-darwin/invalid-input-output-args.test +++ b/test/tools/llvm-libtool-darwin/invalid-input-output-args.test @@ -1,25 +1,52 @@ ## This test checks that an error is thrown in case of invalid input/output args. ## Missing input file: -# RUN: not llvm-libtool-darwin -o %t.lib 2>&1 | \ +# RUN: not llvm-libtool-darwin -static -o %t.lib 2>&1 | \ # RUN: FileCheck %s --check-prefix=NO-INPUT # NO-INPUT: Must specify at least 1 positional argument ## Missing output file: -# RUN: not llvm-libtool-darwin %t.input 2>&1 | \ +# RUN: not llvm-libtool-darwin -static %t.input 2>&1 | \ # RUN: FileCheck %s --check-prefix=NO-OUTPUT # NO-OUTPUT: for the -o option: must be specified at least once! ## Missing argument to -o: -# RUN: not llvm-libtool-darwin %t.input -o 2>&1 | \ +# RUN: not llvm-libtool-darwin -static %t.input -o 2>&1 | \ # RUN: FileCheck %s --check-prefix=MISSING # MISSING: for the -o option: requires a value! ## Passing in two output files: -# RUN: not llvm-libtool-darwin %t.input -o %t.lib1 -o %t.lib2 2>&1 | \ +# RUN: not llvm-libtool-darwin -static %t.input -o %t.lib1 -o %t.lib2 2>&1 | \ # RUN: FileCheck %s --check-prefix=DOUBLE-OUTPUT # DOUBLE-OUTPUT: for the -o option: must occur exactly one time! + +## Input file not found: +# RUN: not llvm-libtool-darwin -static -o %t.lib %t.missing 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NO-FILE -DFILE=%t.missing + +# NO-FILE: error: '[[FILE]]': {{[nN]}}o such file or directory + +## Input file is not an object file: +# RUN: touch %t.invalid +# RUN: not llvm-libtool-darwin -static -o %t.lib %t.invalid 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-OBJECT -DFILE=%basename_t.tmp.invalid + +# NOT-OBJECT: error: '[[FILE]]': The file was not recognized as a valid object file + +## Input file is not a Mach-O object file: +# RUN: yaml2obj %s -o %t.elf +# RUN: not llvm-libtool-darwin -static -o %t.lib %t.elf 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-MACHO -DFILE=%basename_t.tmp.elf + +# NOT-MACHO: error: '[[FILE]]': format not supported + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 diff --git a/test/tools/llvm-libtool-darwin/missing-library-type.test b/test/tools/llvm-libtool-darwin/missing-library-type.test new file mode 100644 index 00000000000..45956e787ae --- /dev/null +++ b/test/tools/llvm-libtool-darwin/missing-library-type.test @@ -0,0 +1,5 @@ +## Missing library type option: +# RUN: not llvm-libtool-darwin -o %t.lib %t.input 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-OPERATION + +# MISSING-OPERATION: Library Type: option: must be specified at least once! diff --git a/tools/llvm-libtool-darwin/CMakeLists.txt b/tools/llvm-libtool-darwin/CMakeLists.txt index b2ea63a8da4..75059741277 100644 --- a/tools/llvm-libtool-darwin/CMakeLists.txt +++ b/tools/llvm-libtool-darwin/CMakeLists.txt @@ -1,4 +1,5 @@ set(LLVM_LINK_COMPONENTS + Object Support ) diff --git a/tools/llvm-libtool-darwin/LLVMBuild.txt b/tools/llvm-libtool-darwin/LLVMBuild.txt index 3858d2faa77..5f7d555e604 100644 --- a/tools/llvm-libtool-darwin/LLVMBuild.txt +++ b/tools/llvm-libtool-darwin/LLVMBuild.txt @@ -17,4 +17,4 @@ type = Tool name = llvm-libtool-darwin parent = Tools -required_libraries = Support +required_libraries = Object Support diff --git a/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp b/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp index bc2f9f765f4..ac61ffe956d 100644 --- a/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp +++ b/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp @@ -10,11 +10,15 @@ // //===----------------------------------------------------------------------===// +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/ObjectFile.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/WithColor.h" using namespace llvm; +using namespace llvm::object; cl::OptionCategory LibtoolCategory("llvm-libtool-darwin Options"); @@ -27,8 +31,79 @@ static cl::list InputFiles(cl::Positional, cl::OneOrMore, cl::cat(LibtoolCategory)); +enum class Operation { Static }; + +static cl::opt LibraryOperation( + cl::desc("Library Type: "), + cl::values( + clEnumValN(Operation::Static, "static", + "Produce a statically linked library from the input files")), + cl::Required, cl::cat(LibtoolCategory)); + +static Error verifyMachOObject(const NewArchiveMember &Member) { + auto MBRef = Member.Buf->getMemBufferRef(); + Expected> ObjOrErr = + object::ObjectFile::createObjectFile(MBRef); + + // Throw error if not a valid object file. + if (!ObjOrErr) + return createFileError(Member.MemberName, ObjOrErr.takeError()); + + // Throw error if not in Mach-O format. + if (!isa(**ObjOrErr)) + return createStringError(std::errc::invalid_argument, + "'%s': format not supported", + Member.MemberName.data()); + + return Error::success(); +} + +static Error addMember(std::vector &Members, + StringRef FileName) { + Expected NMOrErr = + NewArchiveMember::getFile(FileName, /*Deterministic=*/true); + + if (!NMOrErr) + return createFileError(FileName, NMOrErr.takeError()); + + // For regular archives, use the basename of the object path for the member + // name. + NMOrErr->MemberName = sys::path::filename(NMOrErr->MemberName); + + // Verify that Member is a Mach-O object file. + if (Error E = verifyMachOObject(*NMOrErr)) + return E; + + Members.push_back(std::move(*NMOrErr)); + return Error::success(); +} + +static Error createStaticLibrary() { + std::vector NewMembers; + for (StringRef Member : InputFiles) + if (Error E = addMember(NewMembers, Member)) + return E; + + if (Error E = writeArchive(OutputFile, NewMembers, + /*WriteSymtab=*/true, + /*Kind=*/object::Archive::K_DARWIN, + /*Deterministic=*/true, + /*Thin=*/false)) + return E; + return Error::success(); +} + int main(int Argc, char **Argv) { InitLLVM X(Argc, Argv); cl::HideUnrelatedOptions({&LibtoolCategory, &ColorCategory}); cl::ParseCommandLineOptions(Argc, Argv, "llvm-libtool-darwin\n"); + + switch (LibraryOperation) { + case Operation::Static: + if (Error E = createStaticLibrary()) { + WithColor::defaultErrorHandler(std::move(E)); + exit(EXIT_FAILURE); + } + break; + } }