diff --git a/llvm/docs/CommandGuide/llvm-libtool-darwin.rst b/llvm/docs/CommandGuide/llvm-libtool-darwin.rst --- a/llvm/docs/CommandGuide/llvm-libtool-darwin.rst +++ b/llvm/docs/CommandGuide/llvm-libtool-darwin.rst @@ -50,6 +50,20 @@ Show help and usage for this command without grouping the options into categories. +.. option:: -l + + Searches for the library libx.a in the library search path. If the string `` + ends with '.o', then the library 'x' is searched for without prepending 'lib' + or appending '.a'. If the library is found, it is added to the list of input + files. Otherwise, an error is raised. + +.. option:: -L + + Adds `` to the list of directories in which to search for libraries. The + directories are searched in the order in which they are specified with + :option:`-L` and before the default search path. The default search path + includes directories `/lib`, `/usr/lib` and `/usr/local/lib`. + .. option:: -o Specify the output file name. Must be specified exactly once. diff --git a/llvm/test/tools/llvm-libtool-darwin/L-and-l.test b/llvm/test/tools/llvm-libtool-darwin/L-and-l.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-libtool-darwin/L-and-l.test @@ -0,0 +1,135 @@ +## This test checks that -l and -L options work correctly. + +# RUN: yaml2obj %S/Inputs/input1.yaml -o %t-input1.o +# RUN: yaml2obj %S/Inputs/input2.yaml -o %t-input2.o + +## Check that the library is recognised when it ends with '.o': +# RUN: llvm-libtool-darwin -static -o %t.lib -l%basename_t.tmp-input1.o -l%basename_t.tmp-input2.o -L%T +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# CHECK-NAMES: [[PREFIX]]-input1.o +# CHECK-NAMES-NEXT: [[PREFIX]]-input2.o + +# 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 the library is recognised when prepended with 'lib' and appended with '.a': +# RUN: rm -rf %t/dirname && mkdir -p %t/dirname +# RUN: llvm-ar cr %t/dirname/libinput2.a %t-input2.o + +# RUN: llvm-libtool-darwin -static -o %t.lib -linput2 -L%t/dirname +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# SINGLE-NAMES: [[PREFIX]]-input2.o + +# SINGLE-SYMBOLS: Archive map +# SINGLE-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# SINGLE-SYMBOLS-EMPTY: + +## -l and -L option specified multiple times: +# RUN: rm -rf %t/otherDirname && mkdir -p %t/otherDirname +# RUN: llvm-ar cr %t/otherDirname/libinput1.a %t-input1.o + +# RUN: llvm-libtool-darwin -static -o %t.lib -linput2 -linput1 -L%t/dirname -L%t/otherDirname +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=OTHER-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=OTHER-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# OTHER-NAMES: [[PREFIX]]-input2.o +# OTHER-NAMES-NEXT: [[PREFIX]]-input1.o + +# OTHER-SYMBOLS: Archive map +# OTHER-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# OTHER-SYMBOLS-NEXT: _symbol1 in [[PREFIX]]-input1.o +# OTHER-SYMBOLS-EMPTY: + +## Check that if multiple directories specified with -L have the same named file +## in them, the file from the first directory is selected. +# RUN: llvm-ar cr %t/otherDirname/libinput2.a %t-input1.o + +# RUN: llvm-libtool-darwin -static -o %t.lib -linput2 -L%t/dirname -L%t/otherDirname +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +## -l option combined with an input file: +# RUN: llvm-libtool-darwin -static -o %t.lib %t-input1.o -linput2 -L%t/dirname +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=CHECK-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +## Specify same -l option twice: +## 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 -l%basename_t.tmp-input2.o -l%basename_t.tmp-input2.o -L%T +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=DOUBLE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=DOUBLE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines + +# DOUBLE-NAMES: [[PREFIX]]-input2.o +# DOUBLE-NAMES-NEXT: [[PREFIX]]-input2.o + +# DOUBLE-SYMBOLS: Archive map +# DOUBLE-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# DOUBLE-SYMBOLS-NEXT: _symbol2 in [[PREFIX]]-input2.o +# DOUBLE-SYMBOLS-EMPTY: + +## Check that an error is thrown when the input library cannot be found: +# RUN: not llvm-libtool-darwin -static -o %t.lib -lfile-will-not-exist.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND -DFILE=file-will-not-exist.o + +# NOT-FOUND: error: cannot locate file '[[FILE]]' + +## Check that an error is thrown when the input library cannot be found +## (for a file prepended with 'lib' and appended with '.a'): +# RUN: not llvm-libtool-darwin -static -o %t.lib -lfile-will-not-exist 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND -DFILE=libfile-will-not-exist.a + +## Check that an error is thrown when the input library cannot be found +## (since 'lib' and '.a' are added): +# RUN: llvm-ar cr %t/dirname/file %t-input1.o +# RUN: not llvm-libtool-darwin -static -o %t.lib -lfile -L%t/dirname 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND -DFILE=libfile.a + +# RUN: llvm-ar cr %t/dirname/libfile.a %t-input1.o +# RUN: not llvm-libtool-darwin -static -o %t.lib -llibfile.a -L%t/dirname 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND -DFILE=liblibfile.a.a + +## Check that an error is thrown when the input library is not valid: +# RUN: touch %t/dirname/not-valid.o +# RUN: not llvm-libtool-darwin -static -o %t.lib -lnot-valid.o -L%t/dirname 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-VALID -DFILE=not-valid.o + +# NOT-VALID: error: '[[FILE]]': The file was not recognized as a valid object file + +## Check that an error is thrown when the input library is not valid: +## (for a file prepended with 'lib' and appended with '.a'): +# RUN: touch %t/dirname/libnot-valid.a +# RUN: not llvm-libtool-darwin -static -o %t.lib -lnot-valid -L%t/dirname 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-VALID -DFILE=libnot-valid.a + +## Check that 'lib' and '.a' are not added to a file ending in '.o': +# RUN: llvm-ar cr %t/dirname/libfoo.o.a %t-input1.o +# RUN: not llvm-libtool-darwin -static -o %t.lib -lfoo.o -L%t/dirname 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND -DFILE=foo.o + +## Check that 'lib' and '.a' are added to a file ending in any other extension +## beside '.o' (e.g. '.ext'): +# RUN: llvm-ar cr %t/dirname/libbar.ext.a %t-input2.o +# RUN: llvm-libtool-darwin -static -o %t.lib -lbar.ext -L%t/dirname +# RUN: llvm-ar t %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-NAMES --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp +# RUN: llvm-nm --print-armap %t.lib | \ +# RUN: FileCheck %s --check-prefix=SINGLE-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines diff --git a/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp b/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp --- a/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp +++ b/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp @@ -66,12 +66,69 @@ cl::desc("Pass in file containing a list of filenames"), cl::value_desc("listfile[,dirname]"), cl::cat(LibtoolCategory)); +static cl::list Libraries( + "l", + cl::desc( + "l searches for the library libx.a in the library search path. If" + " the string 'x' ends with '.o', then the library 'x' is searched for" + " without prepending 'lib' or appending '.a'"), + cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory)); + +static cl::list LibrarySearchDirs( + "L", + cl::desc( + "L adds to the list of directories in which to search for" + " libraries"), + cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory)); + +static const std::array StandardSearchDirs{ + "/lib", + "/usr/lib", + "/usr/local/lib", +}; + struct Config { bool Deterministic = true; // Updated by 'D' and 'U' modifiers. uint32_t ArchCPUType; uint32_t ArchCPUSubtype; }; +static Expected searchForFile(const Twine &FileName) { + + auto FindLib = + [FileName](ArrayRef SearchDirs) -> Optional { + for (StringRef Dir : SearchDirs) { + SmallString<128> Path; + sys::path::append(Path, Dir, FileName); + + if (sys::fs::exists(Path)) + return std::string(Path); + } + return None; + }; + + Optional Found = FindLib(LibrarySearchDirs); + if (!Found.hasValue()) + Found = FindLib(StandardSearchDirs); + if (Found.hasValue()) + return Found.getValue(); + + return createStringError(std::errc::invalid_argument, + "cannot locate file '%s'", FileName.str().c_str()); +} + +static Error processCommandLineLibraries() { + for (StringRef BaseName : Libraries) { + Expected FullPath = searchForFile( + BaseName.endswith(".o") ? BaseName.str() : "lib" + BaseName + ".a"); + if (!FullPath) + return FullPath.takeError(); + InputFiles.push_back(FullPath.get()); + } + + return Error::success(); +} + static Error processFileList() { StringRef FileName, DirName; std::tie(FileName, DirName) = StringRef(FileList).rsplit(","); @@ -393,6 +450,10 @@ else if (NonDeterministicOption) C.Deterministic = false; + if (!Libraries.empty()) + if (Error E = processCommandLineLibraries()) + return std::move(E); + if (!FileList.empty()) if (Error E = processFileList()) return std::move(E);