diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -82,6 +82,7 @@ llvm-install-name-tool llvm-jitlink llvm-lib + llvm-libtool-darwin llvm-link llvm-lipo llvm-locstats diff --git a/llvm/test/tools/llvm-libtool/Darwin/Inputs/helloMachO.yaml b/llvm/test/tools/llvm-libtool/Darwin/Inputs/helloMachO.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool/Darwin/Inputs/helloMachO.yaml @@ -0,0 +1,56 @@ +# int hello() { +# return 0; +# } +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x00000003 + filetype: 0x00000001 + ncmds: 2 + sizeofcmds: 176 + flags: 0x00002000 + reserved: 0x00000000 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 152 + segname: '' + vmaddr: 0 + vmsize: 8 + fileoff: 312 + filesize: 8 + maxprot: 7 + initprot: 7 + nsects: 1 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x0000000000000000 + size: 8 + offset: 0x00000138 + align: 4 + reloff: 0x00000000 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x00000000 + reserved2: 0x00000000 + reserved3: 0x00000000 + content: 554889E531C05DC3 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 320 + nsyms: 1 + stroff: 336 + strsize: 8 +LinkEditData: + NameList: + - n_strx: 1 + n_type: 0x0F + n_sect: 1 + n_desc: 0 + n_value: 0 + StringTable: + - '' + - _hello +... diff --git a/llvm/test/tools/llvm-libtool/Darwin/Inputs/mainMachO.yaml b/llvm/test/tools/llvm-libtool/Darwin/Inputs/mainMachO.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool/Darwin/Inputs/mainMachO.yaml @@ -0,0 +1,57 @@ +# int main() { +# return 0; +# } +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x00000003 + filetype: 0x00000001 + ncmds: 2 + sizeofcmds: 176 + flags: 0x00002000 + reserved: 0x00000000 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 152 + segname: '' + vmaddr: 0 + vmsize: 15 + fileoff: 312 + filesize: 15 + maxprot: 7 + initprot: 7 + nsects: 1 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x0000000000000000 + size: 15 + offset: 0x00000138 + align: 4 + reloff: 0x00000000 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x00000000 + reserved2: 0x00000000 + reserved3: 0x00000000 + content: 554889E531C0C745FC000000005DC3 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 328 + nsyms: 1 + stroff: 344 + strsize: 8 +LinkEditData: + NameList: + - n_strx: 1 + n_type: 0x0F + n_sect: 1 + n_desc: 0 + n_value: 0 + StringTable: + - '' + - _main + - '' +... diff --git a/llvm/test/tools/llvm-libtool/Darwin/create-static-lib.test b/llvm/test/tools/llvm-libtool/Darwin/create-static-lib.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool/Darwin/create-static-lib.test @@ -0,0 +1,56 @@ +## This test checks that a correct static library is created. + +# RUN: yaml2obj %S/Inputs/helloMachO.yaml -o %t-hello.o +# RUN: yaml2obj %S/Inputs/mainMachO.yaml -o %t-main.o + +# RUN: rm -rf %t.lib +# RUN: llvm-libtool-darwin -static -o %t.lib %t-hello.o %t-main.o + +## Check that binaries are present: +# RUN: llvm-ar tv %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-NAMES --implicit-check-not=.o + +# CHECK-NAMES: hello.o +# CHECK-NAMES: main.o + +## Check that symbols are present: +# RUN: llvm-nm %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-SYMBOLS --implicit-check-not=T + +# CHECK-SYMBOLS: T _hello +# CHECK-SYMBOLS: T _main + +## Check that output archive is in Darwin format: +# RUN: llvm-objdump --macho --archive-headers %t.lib | \ +# RUN: FileCheck %s --check-prefix=FORMAT --implicit-check-not=.o + +# FORMAT: __.SYMDEF +# FORMAT-NEXT: hello.o +# FORMAT-NEXT: main.o + +## Checking that the output file is overwritten: +# RUN: llvm-libtool-darwin -static -o %t.lib %t-main.o %t-hello.o +# RUN: llvm-ar tv %t.lib | \ +# RUN: FileCheck %s --check-prefix=OVERWRITE-NAMES --implicit-check-not=.o +# RUN: llvm-nm %t.lib | \ +# RUN: FileCheck %s --check-prefix=OVERWRITE-SYMBOLS --implicit-check-not=T + +# OVERWRITE-NAMES: main.o +# OVERWRITE-NAMES: hello.o +# OVERWRITE-SYMBOLS: T _main +# OVERWRITE-SYMBOLS: T _hello + +## Duplicate a binary: +# RUN: llvm-libtool-darwin -static -o %t.lib %t-hello.o %t-main.o %t-hello.o +# RUN: llvm-ar tv %t.lib | \ +# RUN: FileCheck %s --check-prefix=DUPLICATE-NAMES --implicit-check-not=.o +# RUN: llvm-nm %t.lib | \ +# RUN: FileCheck %s --check-prefix=DUPLICATE-SYMBOLS --implicit-check-not=T + +# DUPLICATE-NAMES: hello.o +# DUPLICATE-NAMES: main.o +# DUPLICATE-NAMES: hello.o + +# DUPLICATE-SYMBOLS: T _hello +# DUPLICATE-SYMBOLS: T _main +# DUPLICATE-SYMBOLS: T _hello diff --git a/llvm/test/tools/llvm-libtool/Darwin/help-message.test b/llvm/test/tools/llvm-libtool/Darwin/help-message.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool/Darwin/help-message.test @@ -0,0 +1,11 @@ +## This test checks that the help message is displayed correctly. + +# RUN: llvm-libtool-darwin -h | FileCheck --check-prefix=LIBTOOL-USAGE %s --match-full-lines +# RUN: llvm-libtool-darwin --help | FileCheck --check-prefix=LIBTOOL-USAGE %s --match-full-lines +# RUN: not llvm-libtool-darwin 2>&1 | FileCheck --check-prefix=LIBTOOL-USAGE %s --match-full-lines +# 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 + +# LIBTOOL-USAGE: USAGE: llvm-libtool option[s] -o output input[s] + +# UNKNOWN-ARG: unknown argument '{{-+}}abcabc' diff --git a/llvm/test/tools/llvm-libtool/Darwin/invalid-arguments.test b/llvm/test/tools/llvm-libtool/Darwin/invalid-arguments.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool/Darwin/invalid-arguments.test @@ -0,0 +1,100 @@ +## This test checks that an error is thrown in case of invalid argument(s). + +## Missing input file: +# RUN: not llvm-libtool-darwin -o libOut.a 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NO-INPUT + +# NO-INPUT: no input file specified + +## Missing output file: +# RUN: not llvm-libtool-darwin %t 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NO-OUTPUT + +# NO-OUTPUT: no output file specified (specify with -o output) + +## Missing argument to -o: +# RUN: not llvm-libtool-darwin %t -o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING + +# MISSING: missing argument to -o option + +## Missing both -static and -dynamic options: +# RUN: not llvm-libtool-darwin -o libOut.a %t 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-OPERATION + +# MISSING-OPERATION: must specify either -static or -dynamic option + +## Passing both -static and -dynamic options: +# RUN: not llvm-libtool-darwin -static -dynamic -o libOut.a %t 2>&1 | \ +# RUN: FileCheck %s --check-prefix=DOUBLE-OPERATION + +# DOUBLE-OPERATION: cannot specify both -static and -dynamic options + +## Input file not found: +# RUN: not llvm-libtool-darwin -static -o libOut.a %t.missing 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NO-FILE -DFILE=%t.missing + +# NO-FILE: '[[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 libOut.a %t.invalid 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-OBJECT + +# NOT-OBJECT: The file was not recognized as a valid object file + +## Input file is not a MachO object file: +# RUN: yaml2obj %s -o %t +# RUN: not llvm-libtool-darwin -static -o libOut.a %t 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-MACHO + +# NOT-MACHO: format not supported + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000001 + - Name: .data + Type: SHT_PROGBITS + Flags: [ SHF_WRITE, SHF_ALLOC ] + AddressAlign: 0x0000000000000001 + - Name: .bss + Type: SHT_NOBITS + Flags: [ SHF_WRITE, SHF_ALLOC ] + AddressAlign: 0x0000000000000001 + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x0000000000000001 + EntSize: 0x0000000000000001 + Content: 004743433A2028474E552920382E332E3120323031393131323120285265642048617420382E332E312D352900 + - Name: .note.GNU-stack + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 +Symbols: + - Name: foo.c + Type: STT_FILE + Index: SHN_ABS + - Name: .text + Type: STT_SECTION + Section: .text + - Name: .data + Type: STT_SECTION + Section: .data + - Name: .bss + Type: STT_SECTION + Section: .bss + - Name: .note.GNU-stack + Type: STT_SECTION + Section: .note.GNU-stack + - Name: .comment + Type: STT_SECTION + Section: .comment +... diff --git a/llvm/tools/llvm-libtool/CMakeLists.txt b/llvm/tools/llvm-libtool/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Darwin) diff --git a/llvm/tools/llvm-libtool/Darwin/CMakeLists.txt b/llvm/tools/llvm-libtool/Darwin/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool/Darwin/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + Object + Option + Support + ) + +set(LLVM_TARGET_DEFINITIONS LibtoolOpts.td) +tablegen(LLVM LibtoolOpts.inc -gen-opt-parser-defs) +add_public_tablegen_target(LibtoolOptsTableGen) + +add_llvm_tool(llvm-libtool-darwin + llvm-libtool.cpp + DEPENDS + LibtoolOptsTableGen +) diff --git a/llvm/tools/llvm-libtool/Darwin/LLVMBuild.txt b/llvm/tools/llvm-libtool/Darwin/LLVMBuild.txt new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool/Darwin/LLVMBuild.txt @@ -0,0 +1,20 @@ +;===- ./tools/llvm-libtool/Darwin/LVMBuild.txt --------------------------*- Conf -*--===; +; +; 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 +; +;===------------------------------------------------------------------------===; +; +; This is an LLVMBuild description file for the components in this subdirectory. +; +; For more information on the LLVMBuild system, please see: +; +; http://llvm.org/docs/LLVMBuild.html +; +;===------------------------------------------------------------------------===; +[component_0] +type = Tool +name = llvm-libtool-darwin +parent = Tools +required_libraries = Object Option Support diff --git a/llvm/tools/llvm-libtool/Darwin/LibtoolOpts.td b/llvm/tools/llvm-libtool/Darwin/LibtoolOpts.td new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool/Darwin/LibtoolOpts.td @@ -0,0 +1,18 @@ +include "llvm/Option/OptParser.td" + +def help : Flag<["-", "--"], "help">; +def h : Flag<["-"], "h">, Alias; + +def version : Flag<["-", "--"], "version">, + HelpText<"Print the version and exit.">; + +def output : JoinedOrSeparate<["-"], "o">, HelpText<"Write output to ">, + MetaVarName<"">; + +def static : Flag<["-"], "static">, + HelpText<"Produce a statically linked library from the " + "input files">; + +def dynamic : Flag<["-"], "dynamic">, + HelpText<"Produce a shared dynamic library from the " + "input files">; diff --git a/llvm/tools/llvm-libtool/Darwin/llvm-libtool.cpp b/llvm/tools/llvm-libtool/Darwin/llvm-libtool.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool/Darwin/llvm-libtool.cpp @@ -0,0 +1,214 @@ +//===-- llvm-libtool.cpp - a tool for creating libraries --------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// A utility for creating static and dynamic libraries for Darwin. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/Archive.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/MachOUniversal.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/WithColor.h" + +using namespace llvm; +using namespace llvm::object; + +// The name this program was invoked as. +static StringRef ToolName; + +LLVM_ATTRIBUTE_NORETURN static void reportError(Twine Message) { + WithColor::error(errs(), ToolName) << Message << "\n"; + errs().flush(); + exit(EXIT_FAILURE); +} + +LLVM_ATTRIBUTE_NORETURN static void reportError(StringRef File, Error E) { + assert(E); + std::string Buf; + raw_string_ostream OS(Buf); + logAllUnhandledErrors(std::move(E), OS); + OS.flush(); + WithColor::error(errs(), ToolName) << "'" << File << "': " << Buf; + exit(EXIT_FAILURE); +} + +namespace { + +enum LibtoolID { + LIBTOOL_INVALID = 0, // This is not an option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + LIBTOOL_##ID, +#include "LibtoolOpts.inc" +#undef OPTION +}; + +#define PREFIX(NAME, VALUE) const char *const LIBTOOL_##NAME[] = VALUE; +#include "LibtoolOpts.inc" +#undef PREFIX + +static const opt::OptTable::Info LibtoolInfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + {LIBTOOL_##PREFIX, \ + NAME, \ + HELPTEXT, \ + METAVAR, \ + LIBTOOL_##ID, \ + opt::Option::KIND##Class, \ + PARAM, \ + FLAGS, \ + LIBTOOL_##GROUP, \ + LIBTOOL_##ALIAS, \ + ALIASARGS, \ + VALUES}, +#include "LibtoolOpts.inc" +#undef OPTION +}; + +class LibtoolOptTable : public opt::OptTable { +public: + LibtoolOptTable() : OptTable(LibtoolInfoTable) {} +}; + +struct Config { + std::vector InputFiles; + StringRef OutputFile; + + // Boolean options + + // Create static library + bool Static; + + // Create shared dynamic library + bool Dynamic; + + // Use zeroes for timestamps and UIDs/GIDs + // TODO implement option -D to modify Deterministic + bool Deterministic = false; +}; + +} // namespace + +static Config parseLibtoolOptions(ArrayRef ArgsArr) { + Config C; + LibtoolOptTable T; + unsigned MissingArgumentIndex, MissingArgumentCount; + opt::InputArgList InputArgs = + T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); + + if (MissingArgumentCount) + reportError("missing argument to " + + StringRef(InputArgs.getArgString(MissingArgumentIndex)) + + " option"); + + if (InputArgs.size() == 0) { + T.PrintHelp(errs(), "llvm-libtool option[s] -o output input[s]", + ToolName.str().c_str()); + exit(EXIT_FAILURE); + } + + if (InputArgs.hasArg(LIBTOOL_help)) { + T.PrintHelp(outs(), "llvm-libtool option[s] -o output input[s]", + ToolName.str().c_str()); + exit(EXIT_SUCCESS); + } + + if (InputArgs.hasArg(LIBTOOL_version)) { + outs() << ToolName + "\n"; + cl::PrintVersionMessage(); + exit(EXIT_SUCCESS); + } + + for (auto *Arg : InputArgs.filtered(LIBTOOL_UNKNOWN)) + reportError("unknown argument '" + Arg->getAsString(InputArgs) + "'"); + + if (!InputArgs.hasArg(LIBTOOL_output)) + reportError("no output file specified (specify with -o output)"); + C.OutputFile = InputArgs.getLastArgValue(LIBTOOL_output); + + for (auto *Arg : InputArgs.filtered(LIBTOOL_INPUT)) { + C.InputFiles.push_back(Arg->getValue()); + } + if (C.InputFiles.empty()) + reportError("no input file specified"); + + C.Static = InputArgs.hasArg(LIBTOOL_static); + C.Dynamic = InputArgs.hasArg(LIBTOOL_dynamic); + if (C.Static && C.Dynamic) + reportError("cannot specify both -static and -dynamic options"); + if (!C.Static && !C.Dynamic) + reportError("must specify either -static or -dynamic option"); + + return C; +} + +static void verifyDarwinObject(const NewArchiveMember &Member) { + auto MBRef = Member.Buf->getMemBufferRef(); + Expected> ObjOrErr = + object::ObjectFile::createObjectFile(MBRef); + + // throw error if not a valid object file + if (Error E = ObjOrErr.takeError()) + reportError(Member.MemberName, std::move(E)); + + // throw error if not in darwin format + if (!isa(**ObjOrErr)) + reportError("'" + Member.MemberName + "': format not supported"); +} + +static void addMember(std::vector &Members, + StringRef FileName, bool Deterministic) { + Expected NMOrErr = + NewArchiveMember::getFile(FileName, Deterministic); + + if (Error E = NMOrErr.takeError()) + reportError(FileName, std::move(E)); + + // 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 darwin object file + verifyDarwinObject(*NMOrErr); + + Members.push_back(std::move(*NMOrErr)); +} + +static void createStaticLibrary(const Config &Config) { + std::vector NewMembers; + for (const auto &Member : Config.InputFiles) + addMember(NewMembers, Member, Config.Deterministic); + + if (Error E = + writeArchive(Config.OutputFile, NewMembers, + /*symtab=*/true, + /*kind=*/object::Archive::K_DARWIN, Config.Deterministic, + /*thin=*/false)) + reportError(Config.OutputFile, std::move(E)); +} + +int main(int Argc, char **Argv) { + InitLLVM X(Argc, Argv); + ToolName = Argv[0]; + Config C = parseLibtoolOptions(makeArrayRef(Argv + 1, Argc)); + + if (C.Static) + createStaticLibrary(C); + else + reportError("dynamic is currently unsupported"); + return EXIT_SUCCESS; +}