Index: llvm/test/tools/llvm-ar/response-utf8.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-ar/response-utf8.test @@ -0,0 +1,11 @@ +## Check that response files can cope with non-ascii characters. + +# RUN: echo "contents" > %t-£.txt + +# RUN: rm -f %t-£.a +# RUN: echo "r %t-£.a %t-£.txt" > %t-replace.txt +# RUN: llvm-ar @%t-replace.txt + +# RUN: echo "p %t-£.a %t-£.txt" > %t-print.txt +# RUN: llvm-ar @%t-print.txt | FileCheck %s +# CHECK: contents Index: llvm/test/tools/llvm-ar/response.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-ar/response.test @@ -0,0 +1,37 @@ +## llvm-ar should be able to consume response files. + +# RUN: echo contents > %t.txt +# RUN: echo rc %t1.a %t.txt > %t.response1.txt +# RUN: llvm-ar @%t.response1.txt +# RUN: llvm-ar p %t1.a | FileCheck %s --check-prefix=CONTENTS + +## Quotes and Spaces. +# RUN: echo contents > "%t space.txt" +## Python is used here to ensure the quotes are written to the response file +# RUN: %python -c "import os; open(r'%t.response2.txt', 'wb').write(r'%t2.a \"%t space.txt\"'+ '\n')" +# RUN: llvm-ar rc @%t.response2.txt +# RUN: llvm-ar p %t2.a | FileCheck %s --check-prefix=CONTENTS + +## Arguments after the response file. +# RUN: echo rc %t3.a > %t.response3.txt +# RUN: llvm-ar @%t.response3.txt %t.txt +# RUN: llvm-ar p %t3.a | FileCheck %s --check-prefix=CONTENTS + +# CONTENTS: contents + +## rsp-quoting +# RUN: not llvm-ar --rsp-quoting=foobar @%t.response1.txt 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERROR +# ERROR: Invalid response file quoting style foobar + +# RUN: echo "rc %t.a blah\foo" > %t-rsp.txt +# RUN: not llvm-ar --rsp-quoting=windows @%t-rsp.txt 2>&1 | \ +# RUN: FileCheck %s --check-prefix=WIN +# WIN: error: blah\foo: {{[Nn]}}o such file or directory + +# RUN: echo "rc %t.a blah\foo" > %t-rsp.txt +# RUN: not llvm-ar -rsp-quoting posix @%t-rsp.txt 2>&1 | \ +# RUN: FileCheck %s --check-prefix=POSIX +# POSIX: error: blahfoo: {{[Nn]}}o such file or directory + +# RUN: llvm-ar -rsp-quoting=default @%t.response1.txt Index: llvm/tools/llvm-ar/llvm-ar.cpp =================================================================== --- llvm/tools/llvm-ar/llvm-ar.cpp +++ llvm/tools/llvm-ar/llvm-ar.cpp @@ -80,6 +80,10 @@ =bsd - bsd --plugin= - ignored for compatibility -h --help - display this help and exit + --rsp-quoting - quoting style for response files + =default - default + =posix - posix + =windows - windows --version - print the version and exit @ - read options from @@ -175,7 +179,7 @@ static Format FormatType = Default; -static std::string Options; +static std::string KeyLetters; // This enumeration delineates the kinds of operations on an archive // that are permitted. @@ -278,7 +282,7 @@ // modifier/operation pairs have not been violated. static ArchiveOperation parseCommandLine() { if (MRI) { - if (!PositionalArgs.empty() || !Options.empty()) + if (!PositionalArgs.empty() || !KeyLetters.empty()) badUsage("cannot mix -M and other options"); runMRIScript(); } @@ -296,8 +300,8 @@ bool MaybeJustCreateSymTab = false; - for (unsigned i = 0; i < Options.size(); ++i) { - switch (Options[i]) { + for (unsigned i = 0; i < KeyLetters.size(); ++i) { + switch (KeyLetters[i]) { case 'd': ++NumOperations; Operation = Delete; @@ -392,7 +396,7 @@ printHelpMessage(); exit(0); default: - badUsage(std::string("unknown option ") + Options[i]); + badUsage(std::string("unknown option ") + KeyLetters[i]); } } @@ -1093,61 +1097,112 @@ return false; } +using ArgVector = SmallVector; + +static StringRef removeHyphens(StringRef Arg) { + if (Arg.startswith("--")) + return Arg.substr(2); + + if (Arg.startswith("-")) + return Arg.substr(1); + + return Arg; +} + +static const char *matchFlagWithArg(StringRef Expected, + ArgVector::iterator &ArgIt, + ArgVector Args) { + StringRef Arg = removeHyphens(*ArgIt); + size_t len = Expected.size(); + if (Arg == Expected) { + if (++ArgIt == Args.end()) + fail(std::string(Expected) + " requires an argument"); + + return *ArgIt; + } + if (Arg.startswith(Expected) && Arg.size() > len && Arg[len] == '=') + return Arg.data() + len + 1; + + return nullptr; +} + +static cl::TokenizerCallback getRspQuoting(ArgVector Argv) { + cl::TokenizerCallback Default = + Triple(sys::getProcessTriple()).getOS() == Triple::Win32 + ? cl::TokenizeWindowsCommandLine + : cl::TokenizeGNUCommandLine; + + cl::TokenizerCallback Ret = Default; + + for (ArgVector::iterator ArgIt = Argv.begin(); ArgIt != Argv.end(); ++ArgIt) { + if (const char *Match = + matchFlagWithArg("rsp-quoting", ArgIt, Argv)) { + StringRef MatchRef = Match; + if (MatchRef == "posix") + Ret = cl::TokenizeGNUCommandLine; + else if (MatchRef == "windows") + Ret = cl::TokenizeWindowsCommandLine; + else if (MatchRef == "default") + Ret = Default; + else + fail(std::string("Invalid response file quoting style ") + Match); + } + } + + return Ret; +} + static int ar_main(int argc, char **argv) { - SmallVector Argv(argv, argv + argc); + ArgVector Argv(argv + 1, argv + argc); StringSaver Saver(Alloc); - cl::ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Argv); - for (size_t i = 1; i < Argv.size(); ++i) { - StringRef Arg = Argv[i]; - const char *match = nullptr; - auto MatchFlagWithArg = [&](const char *expected) { - size_t len = strlen(expected); - if (Arg == expected) { - if (++i >= Argv.size()) - fail(std::string(expected) + " requires an argument"); - match = Argv[i]; - return true; - } - if (Arg.startswith(expected) && Arg.size() > len && Arg[len] == '=') { - match = Arg.data() + len + 1; - return true; - } - return false; - }; - if (handleGenericOption(Argv[i])) + + cl::ExpandResponseFiles(Saver, getRspQuoting(Argv), Argv); + + for (ArgVector::iterator ArgIt = Argv.begin(); ArgIt != Argv.end(); ++ArgIt) { + const char *Match = nullptr; + + if (handleGenericOption(*ArgIt)) return 0; - if (Arg == "--") { - for (; i < Argv.size(); ++i) - PositionalArgs.push_back(Argv[i]); + if (strcmp(*ArgIt, "--") == 0) { + ++ArgIt; + for (; ArgIt != Argv.end(); ++ArgIt) + PositionalArgs.push_back(*ArgIt); break; } - if (Arg[0] == '-') { - if (Arg.startswith("--")) - Arg = Argv[i] + 2; + + if (*ArgIt[0] != '-') { + if (KeyLetters.empty()) + KeyLetters += *ArgIt; else - Arg = Argv[i] + 1; - if (Arg == "M") { - MRI = true; - } else if (MatchFlagWithArg("format")) { - FormatType = StringSwitch(match) - .Case("default", Default) - .Case("gnu", GNU) - .Case("darwin", DARWIN) - .Case("bsd", BSD) - .Default(Unknown); - if (FormatType == Unknown) - fail(std::string("Invalid format ") + match); - } else if (MatchFlagWithArg("plugin")) { - // Ignored. - } else { - Options += Argv[i] + 1; - } - } else if (Options.empty()) { - Options += Argv[i]; - } else { - PositionalArgs.push_back(Argv[i]); + PositionalArgs.push_back(*ArgIt); + continue; + } + + if (strcmp(*ArgIt, "-M") == 0) { + MRI = true; + continue; + } + + Match = matchFlagWithArg("format", ArgIt, Argv); + if (Match) { + FormatType = StringSwitch(Match) + .Case("default", Default) + .Case("gnu", GNU) + .Case("darwin", DARWIN) + .Case("bsd", BSD) + .Default(Unknown); + if (FormatType == Unknown) + fail(std::string("Invalid format ") + Match); + continue; } + + if (matchFlagWithArg("plugin", ArgIt, Argv) || + matchFlagWithArg("rsp-quoting", ArgIt, Argv)) + continue; + + KeyLetters += *ArgIt + 1; } + ArchiveOperation Operation = parseCommandLine(); return performOperation(Operation, nullptr); }