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 @@ -26,10 +26,6 @@ Build a static library only for the specified `` and ignore all other architectures in the files. -.. option:: -color - - Use colors in output. - .. option:: -D Use zero for timestamps and UIDs/GIDs. This is set by default. @@ -45,11 +41,6 @@ Show help and usage for this command. -.. option:: -help-list - - 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 `` diff --git a/llvm/test/tools/llvm-libtool-darwin/L-and-l.test b/llvm/test/tools/llvm-libtool-darwin/L-and-l.test --- a/llvm/test/tools/llvm-libtool-darwin/L-and-l.test +++ b/llvm/test/tools/llvm-libtool-darwin/L-and-l.test @@ -44,6 +44,17 @@ # RUN: llvm-nm --print-armap %t.lib | \ # RUN: FileCheck %s --check-prefix=OTHER-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines +## Check it is possible to pass arguments to -l and -L separated from the option +## and the options 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 -l input2 -l input1 -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 diff --git a/llvm/test/tools/llvm-libtool-darwin/help-message.test b/llvm/test/tools/llvm-libtool-darwin/help-message.test --- a/llvm/test/tools/llvm-libtool-darwin/help-message.test +++ b/llvm/test/tools/llvm-libtool-darwin/help-message.test @@ -1,13 +1,11 @@ ## This test checks that the help message is displayed correctly. # RUN: llvm-libtool-darwin -h | \ -# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE,CATEG %s --match-full-lines --implicit-check-not="General options:" +# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE %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: FileCheck --check-prefixes=LIBTOOL-USAGE %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 --implicit-check-not="safepoint-ir-verifier-print-only" +# RUN: FileCheck --check-prefixes=LIBTOOL-USAGE %s --match-full-lines --implicit-check-not="General options:" # 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 @@ -16,12 +14,4 @@ # LIBTOOL-USAGE: USAGE: llvm-libtool-darwin{{(\.exe)?}} [options] # LIBTOOL-USAGE: OPTIONS: -# CATEG: Color Options: -# LIST-NOT: Color Options: -# CATEG: Generic Options: -# 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' +# UNKNOWN-ARG: unknown argument '{{-+}}abcabc' diff --git a/llvm/test/tools/llvm-libtool-darwin/invalid-input-output-args.test b/llvm/test/tools/llvm-libtool-darwin/invalid-input-output-args.test --- a/llvm/test/tools/llvm-libtool-darwin/invalid-input-output-args.test +++ b/llvm/test/tools/llvm-libtool-darwin/invalid-input-output-args.test @@ -10,13 +10,13 @@ # 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 +# NO-OUTPUT: -o option: must be specified ## Missing argument to -o: # 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! +# MISSING: -o: missing argument ## Input file not found: # RUN: not llvm-libtool-darwin -static -o %t.lib %t.missing 2>&1 | \ diff --git a/llvm/test/tools/llvm-libtool-darwin/missing-library-type.test b/llvm/test/tools/llvm-libtool-darwin/missing-library-type.test --- a/llvm/test/tools/llvm-libtool-darwin/missing-library-type.test +++ b/llvm/test/tools/llvm-libtool-darwin/missing-library-type.test @@ -2,4 +2,4 @@ # 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 +# MISSING-OPERATION: -static option: must be specified diff --git a/llvm/test/tools/llvm-libtool-darwin/universal-object-flattening.test b/llvm/test/tools/llvm-libtool-darwin/universal-object-flattening.test --- a/llvm/test/tools/llvm-libtool-darwin/universal-object-flattening.test +++ b/llvm/test/tools/llvm-libtool-darwin/universal-object-flattening.test @@ -104,7 +104,7 @@ # RUN: not llvm-libtool-darwin -static -o %t.lib %t-universal.o -arch_only 2>&1 | \ # RUN: FileCheck %s --check-prefix=REQUIRE-ARCH -# REQUIRE-ARCH: for the --arch_only option: requires a value! +# REQUIRE-ARCH: -arch_only: missing argument ## x86_64-arm64-universal.yaml --- !fat-mach-o diff --git a/llvm/test/tools/llvm-libtool-darwin/version.test b/llvm/test/tools/llvm-libtool-darwin/version.test --- a/llvm/test/tools/llvm-libtool-darwin/version.test +++ b/llvm/test/tools/llvm-libtool-darwin/version.test @@ -15,7 +15,7 @@ ## Regular errors should occur when an operation is specified. # RUN: not llvm-libtool-darwin -V -static 2>&1 | FileCheck --check-prefix=ERROR %s -# ERROR: for the -o option: must be specified +# ERROR: -o option: must be specified ## A valid command line should print the version and perform the operation. # RUN: yaml2obj %S/Inputs/input1.yaml -o %t-input1.o diff --git a/llvm/tools/llvm-libtool-darwin/CMakeLists.txt b/llvm/tools/llvm-libtool-darwin/CMakeLists.txt --- a/llvm/tools/llvm-libtool-darwin/CMakeLists.txt +++ b/llvm/tools/llvm-libtool-darwin/CMakeLists.txt @@ -2,14 +2,22 @@ BinaryFormat Core Object + Option Support TargetParser TextAPI ${LLVM_TARGETS_TO_BUILD} ) +set(LLVM_TARGET_DEFINITIONS Opts.td) +tablegen(LLVM Opts.inc -gen-opt-parser-defs) +add_public_tablegen_target(LibtoolDarwinOptsTableGen) + add_llvm_tool(llvm-libtool-darwin llvm-libtool-darwin.cpp + + DEPENDS + LibtoolDarwinOptsTableGen ) if(LLVM_INSTALL_CCTOOLS_SYMLINKS) diff --git a/llvm/tools/llvm-libtool-darwin/Opts.td b/llvm/tools/llvm-libtool-darwin/Opts.td new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-libtool-darwin/Opts.td @@ -0,0 +1,25 @@ +include "llvm/Option/OptParser.td" + +class F : Flag<["-", "--"], letter>, HelpText; +class FF : Flag<["-", "--"], name>, HelpText; +class S : Separate<["-", "--"], letter>, HelpText; +class SS : Separate<["-", "--"], name>, HelpText; +class JS : JoinedOrSeparate<["-", "--"], letter>, HelpText; + +def help : FF<"help", "Display this help">; +def : F<"h", "">, Alias; +def deterministicOption : F<"D","Use zero for timestamps and UIDs/GIDs (Default)">; +def nonDeterministicOption : F<"U", "Use actual timestamps and UIDs/GIDs">; +def version : FF<"version", "Display the version of this program">; +def : F<"V", "Print the version number and exit">, Alias; +def noWarningForNoSymbols : FF<"no_warning_for_no_symbols", "Do not warn about files that have no symbols">; +def warningsAsErrors : FF<"warnings_as_errors", "Treat warnings as errors">; +def static : Flag<["-", "--"], "static">, HelpText<"Produce a statically linked library from the input files">; +def outputFile : S<"o", "Specify output filename">, MetaVarName<"">; +def fileList : SS<"filelist", "Pass in file containing a list of filenames">, MetaVarName<"">; +def archType : SS<"arch_only", "Specify architecture type for output library">, MetaVarName<"">; +def libraries : JS<"l", "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'">, MetaVarName<"">; +def librarySearchDirs : JS<"L", "L adds to the list of directories in which to search for libraries">, MetaVarName<"">; +def ignoredSyslibRoot : SS<"syslibroot", "">, Flags<[HelpHidden]>; +def dependencyInfoPath : SS<"dependency_info", "Write an Xcode dependency info file describing the dependencies of the created library">, MetaVarName<"">; 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 @@ -19,12 +19,15 @@ #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/MachOUniversalWriter.h" #include "llvm/Object/ObjectFile.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/WithColor.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TextAPI/Architecture.h" #include @@ -32,83 +35,64 @@ using namespace llvm; using namespace llvm::object; +using namespace llvm::opt; + +// Command-line option boilerplate. +namespace { +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; -class NewArchiveMemberList; -typedef std::map MembersPerArchitectureMap; - -cl::OptionCategory LibtoolCategory("llvm-libtool-darwin Options"); +#define PREFIX(NAME, VALUE) \ + static constexpr StringLiteral NAME##_init[] = VALUE; \ + static constexpr ArrayRef NAME(NAME##_init, \ + std::size(NAME##_init) - 1); +#include "Opts.inc" +#undef PREFIX + +static constexpr opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Opts.inc" +#undef OPTION +}; -static cl::opt OutputFile("o", cl::desc("Specify output filename"), - cl::value_desc("filename"), - cl::cat(LibtoolCategory)); +class LibtoolDarwinOptTable : public opt::GenericOptTable { +public: + LibtoolDarwinOptTable() : GenericOptTable(InfoTable) {} +}; +} // end anonymous namespace -static cl::list InputFiles(cl::Positional, - cl::desc(""), - cl::cat(LibtoolCategory)); +class NewArchiveMemberList; +typedef std::map MembersPerArchitectureMap; -static cl::opt - ArchType("arch_only", - cl::desc("Specify architecture type for output library"), - cl::value_desc("arch_type"), cl::cat(LibtoolCategory)); +static std::string OutputFile; +static std::vector InputFiles; +static std::optional ArchType; enum class Operation { None, 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::init(Operation::None), cl::cat(LibtoolCategory)); - -static cl::opt DeterministicOption( - "D", cl::desc("Use zero for timestamps and UIDs/GIDs (Default)"), - cl::init(false), cl::cat(LibtoolCategory)); - -static cl::opt - NonDeterministicOption("U", cl::desc("Use actual timestamps and UIDs/GIDs"), - cl::init(false), cl::cat(LibtoolCategory)); - -static cl::opt - FileList("filelist", - 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::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::Prefix, cl::cat(LibtoolCategory)); - -static cl::opt DependencyInfoPath( - "dependency_info", - cl::desc("Write an Xcode dependency info file describing the dependencies " - "of the created library"), - cl::cat(LibtoolCategory)); - -static cl::opt - VersionOption("V", cl::desc("Print the version number and exit"), - cl::cat(LibtoolCategory)); - -static cl::opt NoWarningForNoSymbols( - "no_warning_for_no_symbols", - cl::desc("Do not warn about files that have no symbols"), - cl::cat(LibtoolCategory), cl::init(false)); - -static cl::opt WarningsAsErrors("warnings_as_errors", - cl::desc("Treat warnings as errors"), - cl::cat(LibtoolCategory), - cl::init(false)); - -static cl::opt IgnoredSyslibRoot("syslibroot", cl::Hidden); +static Operation LibraryOperation = Operation::None; + +static bool DeterministicOption; +static bool NonDeterministicOption; +static std::string FileList; +static std::vector Libraries; +static std::vector LibrarySearchDirs; +static std::string DependencyInfoPath; +static bool VersionOption; +static bool NoWarningForNoSymbols; +static bool WarningsAsErrors; +static std::string IgnoredSyslibRoot; static const std::array StandardSearchDirs{ "/lib", @@ -288,14 +272,15 @@ if (Error E = AddMember(*this, FileName)()) return std::move(E); - if (!ArchType.empty()) { + std::string Arch = ArchType.value_or(""); + if (!Arch.empty()) { uint64_t ArchCPUID = getCPUID(C.ArchCPUType, C.ArchCPUSubtype); if (Data.MembersPerArchitecture.find(ArchCPUID) == Data.MembersPerArchitecture.end()) return createStringError(std::errc::invalid_argument, "no library created (no object files in input " "files matching -arch_only %s)", - ArchType.c_str()); + Arch.c_str()); } return std::move(Data); } @@ -383,7 +368,7 @@ // If -arch_only is specified then skip this file if it doesn't match // the architecture specified. - if (!ArchType.empty() && !acceptFileArch(FileCPUType, FileCPUSubtype)) { + if (ArchType && !acceptFileArch(FileCPUType, FileCPUSubtype)) { return Error::success(); } @@ -426,7 +411,7 @@ // If -arch_only is specified then skip this file if it doesn't match // the architecture specified. - if (!ArchType.empty() && + if (ArchType && !acceptFileArch(*FileCPUTypeOrErr, *FileCPUSubTypeOrErr)) { return Error::success(); } @@ -649,16 +634,58 @@ return writeUniversalBinary(*Slices, OutputFile); } +static void parseRawArgs(int Argc, char **Argv) { + LibtoolDarwinOptTable Tbl; + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver{A}; + opt::InputArgList Args = + Tbl.parseArgs(Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { + llvm::errs() << Msg << '\n'; + std::exit(1); + }); + + if (Args.hasArg(OPT_help)) { + Tbl.printHelp(llvm::outs(), "llvm-libtool-darwin [options] ", + "llvm-libtool-darwin"); + std::exit(0); + } + + InputFiles = Args.getAllArgValues(OPT_INPUT); + Libraries = Args.getAllArgValues(OPT_libraries); + LibrarySearchDirs = Args.getAllArgValues(OPT_librarySearchDirs); + + if (const opt::Arg *A = Args.getLastArg(OPT_outputFile)) + OutputFile = A->getValue(); + + if (const opt::Arg *A = Args.getLastArg(OPT_archType)) + ArchType = std::make_optional(A->getValue()); + + if (const opt::Arg *A = Args.getLastArg(OPT_fileList)) + FileList = A->getValue(); + + if (const opt::Arg *A = Args.getLastArg(OPT_dependencyInfoPath)) + DependencyInfoPath = A->getValue(); + + if (const opt::Arg *A = Args.getLastArg(OPT_ignoredSyslibRoot)) + IgnoredSyslibRoot = A->getValue(); + + LibraryOperation = + Args.hasArg(OPT_static) ? Operation::Static : Operation::None; + DeterministicOption = Args.hasArg(OPT_deterministicOption); + NonDeterministicOption = Args.hasArg(OPT_nonDeterministicOption); + VersionOption = Args.hasArg(OPT_version); + NoWarningForNoSymbols = Args.hasArg(OPT_noWarningForNoSymbols); + WarningsAsErrors = Args.hasArg(OPT_warningsAsErrors); +} + static Expected parseCommandLine(int Argc, char **Argv) { Config C; - cl::ParseCommandLineOptions(Argc, Argv, "llvm-libtool-darwin\n"); + parseRawArgs(Argc, Argv); if (LibraryOperation == Operation::None) { if (!VersionOption) { - std::string Error; - raw_string_ostream Stream(Error); - LibraryOperation.error("must be specified", "", Stream); - return createStringError(std::errc::invalid_argument, Error.c_str()); + return createStringError(std::errc::invalid_argument, + "-static option: must be specified"); } return C; } @@ -669,10 +696,8 @@ : std::make_unique(DependencyInfoPath); if (OutputFile.empty()) { - std::string Error; - raw_string_ostream Stream(Error); - OutputFile.error("must be specified", "o", Stream); - return createStringError(std::errc::invalid_argument, Error.c_str()); + return createStringError(std::errc::invalid_argument, + "-o option: must be specified"); } if (DeterministicOption && NonDeterministicOption) @@ -693,13 +718,13 @@ return createStringError(std::errc::invalid_argument, "no input files specified"); - if (ArchType.getNumOccurrences()) { - if (Error E = validateArchitectureName(ArchType)) + if (ArchType) { + if (Error E = validateArchitectureName(ArchType.value())) return std::move(E); std::tie(C.ArchCPUType, C.ArchCPUSubtype) = MachO::getCPUTypeFromArchitecture( - MachO::getArchitectureFromName(ArchType)); + MachO::getArchitectureFromName(ArchType.value())); } GlobalDependencyInfo->write("llvm-libtool-darwin " LLVM_VERSION_STRING, @@ -710,7 +735,6 @@ int main(int Argc, char **Argv) { InitLLVM X(Argc, Argv); - cl::HideUnrelatedOptions({&LibtoolCategory, &getColorCategory()}); Expected ConfigOrErr = parseCommandLine(Argc, Argv); if (!ConfigOrErr) { WithColor::defaultErrorHandler(ConfigOrErr.takeError()); diff --git a/llvm/utils/gn/secondary/llvm/tools/llvm-libtool-darwin/BUILD.gn b/llvm/utils/gn/secondary/llvm/tools/llvm-libtool-darwin/BUILD.gn --- a/llvm/utils/gn/secondary/llvm/tools/llvm-libtool-darwin/BUILD.gn +++ b/llvm/utils/gn/secondary/llvm/tools/llvm-libtool-darwin/BUILD.gn @@ -1,5 +1,11 @@ import("//llvm/tools/cctools_symlinks.gni") import("//llvm/utils/gn/build/symlink_or_copy.gni") +import("//llvm/utils/TableGen/tablegen.gni") + +tablegen("Opts") { + visibility = [ ":llvm-libtool-darwin" ] + args = [ "-gen-opt-parser-defs" ] +} if (llvm_install_cctools_symlinks) { symlink_or_copy("libtool") { @@ -20,7 +26,9 @@ executable("llvm-libtool-darwin") { deps = [ + ":Opts", "//llvm/lib/Object", + "//llvm/lib/Option", "//llvm/lib/Support", "//llvm/lib/Target:TargetsToBuild", "//llvm/lib/TargetParser", diff --git a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel --- a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel @@ -3679,6 +3679,18 @@ ], ) +gentbl( + name = "LibtoolDarwinOptionsTableGen", + strip_include_prefix = "tools/llvm-libtool-darwin", + tbl_outs = [( + "-gen-opt-parser-defs", + "tools/llvm-libtool-darwin/Opts.inc", + )], + tblgen = ":llvm-tblgen", + td_file = "tools/llvm-libtool-darwin/Opts.td", + td_srcs = ["include/llvm/Option/OptParser.td"], +) + cc_binary( name = "llvm-libtool-darwin", srcs = glob([ @@ -3691,8 +3703,10 @@ ":AllTargetsAsmParsers", ":AllTargetsCodeGens", ":BinaryFormat", + ":LibtoolDarwinOptionsTableGen", ":Core", ":Object", + ":Option", ":Support", ":TextAPI", ],