Index: include/llvm/Support/CommandLine.h =================================================================== --- include/llvm/Support/CommandLine.h +++ include/llvm/Support/CommandLine.h @@ -1842,6 +1842,20 @@ SmallVectorImpl &NewArgv, bool MarkEOLs); +/// Reads command line options from the given configuration file. +/// +/// \param [in] CfgFileName Path to configuration file. +/// \param [in] Saver Objects that saves allocated strings. +/// \param [out] Argv Array to which the read options are added. +/// \return true if the file was successfully read. +/// +/// It reads content of the specified file, tokenizes it and expands "@file" +/// commands resolving file names in them relative to the directory where +/// CfgFilename resides. +/// +bool readConfigFile(StringRef CfgFileName, StringSaver &Saver, + SmallVectorImpl &Argv); + /// \brief Expand response files on a command line recursively using the given /// StringSaver and tokenization strategy. Argv should contain the command line /// before expansion and will be modified in place. If requested, Argv will Index: lib/Support/CommandLine.cpp =================================================================== --- lib/Support/CommandLine.cpp +++ lib/Support/CommandLine.cpp @@ -708,6 +708,7 @@ SmallVectorImpl &NewArgv, bool MarkEOLs) { SmallString<128> Token; + bool NonWhitespaceSeenInLine = false; for (size_t I = 0, E = Src.size(); I != E; ++I) { // Consume runs of whitespace. if (Token.empty()) { @@ -719,11 +720,27 @@ } if (I == E) break; + // Skip comment line. + if (!NonWhitespaceSeenInLine && Src[I] == '#') { + ++I; + while (I != E && Src[I] != '\n') + ++I; + if (I == E) + break; + continue; + } + NonWhitespaceSeenInLine = true; } // Backslash escapes the next character. if (I + 1 < E && Src[I] == '\\') { ++I; // Skip the escape. + if (Src[I] == '\n') + continue; // Ignore backlash followed by '\n'. + if (Src[I] == '\r' && I + 1 < E && Src[I + 1] == '\n') { + ++I; + continue; // Ignore backlash followed by \r\n. + } Token.push_back(Src[I]); continue; } @@ -748,6 +765,8 @@ if (!Token.empty()) NewArgv.push_back(Saver.save(StringRef(Token)).data()); Token.clear(); + if (Src[I] == '\n') + NonWhitespaceSeenInLine = false; continue; } @@ -809,6 +828,7 @@ // This is a small state machine to consume characters until it reaches the // end of the source string. enum { INIT, UNQUOTED, QUOTED } State = INIT; + bool NonWhitespaceSeenInLine = false; for (size_t I = 0, E = Src.size(); I != E; ++I) { // INIT state indicates that the current input index is at the start of // the string or between tokens. @@ -819,6 +839,18 @@ NewArgv.push_back(nullptr); continue; } + + // Skip comment line. + if (!NonWhitespaceSeenInLine && Src[I] == '#') { + ++I; + while (I != E && Src[I] != '\n') + ++I; + if (I == E) + break; + continue; + } + NonWhitespaceSeenInLine = true; + if (Src[I] == '"') { State = QUOTED; continue; @@ -841,9 +873,12 @@ NewArgv.push_back(Saver.save(StringRef(Token)).data()); Token.clear(); State = INIT; - // Mark the end of lines in response files - if (MarkEOLs && Src[I] == '\n') - NewArgv.push_back(nullptr); + if (Src[I] == '\n') { + NonWhitespaceSeenInLine = false; + // Mark the end of lines in response files + if (MarkEOLs) + NewArgv.push_back(nullptr); + } continue; } if (Src[I] == '"') { @@ -983,6 +1018,15 @@ return AllExpanded; } +bool cl::readConfigFile(StringRef CfgFile, StringSaver &Saver, + SmallVectorImpl &Argv) { + if (!ExpandResponseFile(CfgFile, Saver, cl::TokenizeGNUCommandLine, Argv, + /*MarkEOLs*/ false, /*RelativeNames*/ true)) + return false; + return ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Argv, + /*MarkEOLs*/ false, /*RelativeNames*/ true); +} + /// ParseEnvironmentOptions - An alternative entry point to the /// CommandLine library, which allows you to read the program's name /// from the caller (as PROGNAME) and its command-line arguments from Index: unittests/Support/CommandLineTest.cpp =================================================================== --- unittests/Support/CommandLineTest.cpp +++ unittests/Support/CommandLineTest.cpp @@ -198,6 +198,56 @@ array_lengthof(Output)); } +TEST(CommandLineTest, TokenizeGNUCommandLineBS) { + const char *Input = "\\"; + const char *Output[1] = { "\\" }; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\"; + Output[0] = "abc\\"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "\\abc"; + Output[0] = "abc"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\123"; + Output[0] = "abc123"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\1"; + Output[0] = "abc1"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\\\123"; + Output[0] = "abc\\123"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "\\\nabc"; + Output[0] = "abc"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "\\\r\nabc"; + Output[0] = "abc"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\\n123"; + Output[0] = "abc123"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\\r\n123"; + Output[0] = "abc123"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\\n"; + Output[0] = "abc"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); + + Input = "abc\\\r\n"; + Output[0] = "abc"; + testCommandLineTokenizer(cl::TokenizeGNUCommandLine, Input, Output, 1); +} + TEST(CommandLineTest, TokenizeWindowsCommandLine) { const char Input[] = "a\\b c\\\\d e\\\\\"f g\" h\\\"i j\\\\\\\"k \"lmn\" o pqr " "\"st \\\"u\" \\v"; @@ -207,6 +257,38 @@ array_lengthof(Output)); } +TEST(CommandLineTest, TokenizeCommandLineComment) { + for (auto Tokenizer : { cl::TokenizeGNUCommandLine , + cl::TokenizeWindowsCommandLine }) { + const char *Input = "# abc\n" + "123"; + const char *Output[3] = { "123" }; + testCommandLineTokenizer(Tokenizer, Input, Output, 1); + + Input = " # abc\n" + "123"; + Output[0] = "123"; + testCommandLineTokenizer(Tokenizer, Input, Output, 1); + + Input = "123 # abc"; + Output[0] = "123"; + Output[1] = "#"; + Output[2] = "abc"; + testCommandLineTokenizer(Tokenizer, Input, Output, 3); + + Input = "abc\n" + "#123"; + Output[0] = "abc"; + testCommandLineTokenizer(Tokenizer, Input, Output, 1); + + Input = "abc def\n" + "#123"; + Output[0] = "abc"; + Output[1] = "def"; + testCommandLineTokenizer(Tokenizer, Input, Output, 2); + } +} + TEST(CommandLineTest, AliasesWithArguments) { static const size_t ARGC = 3; const char *const Inputs[][ARGC] = { @@ -613,4 +695,52 @@ llvm::sys::fs::remove(TestDir); } +TEST(CommandLineTest, ReadConfigFile) { + llvm::SmallVector Argv; + + llvm::SmallString<128> TestDir; + std::error_code EC = + llvm::sys::fs::createUniqueDirectory("unittest", TestDir); + EXPECT_TRUE(!EC); + + llvm::SmallString<128> TestCfg; + llvm::sys::path::append(TestCfg, TestDir, "foo"); + std::ofstream ConfigFile(TestCfg.c_str()); + EXPECT_TRUE(ConfigFile.is_open()); + ConfigFile << "-option_1\n" + "@subconfig\n" + "-option_3=abcd\n"; + ConfigFile.close(); + + llvm::SmallString<128> TestCfg2; + llvm::sys::path::append(TestCfg2, TestDir, "subconfig"); + std::ofstream ConfigFile2(TestCfg2.c_str()); + EXPECT_TRUE(ConfigFile2.is_open()); + ConfigFile2 << "-option_2\n"; + ConfigFile2.close(); + + // Make sure the current directory is not the directory where config files + // resides. In this case the code that expands response files will not find + // 'subconfig' unless it resolves nested inclusions relative to the including + // file. + llvm::SmallString<128> CurrDir; + EC = llvm::sys::fs::current_path(CurrDir); + EXPECT_TRUE(!EC); + EXPECT_TRUE(StringRef(CurrDir) != StringRef(TestDir)); + + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + bool Result = llvm::cl::readConfigFile(TestCfg, Saver, Argv); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 3U); + EXPECT_STREQ(Argv[0], "-option_1"); + EXPECT_STREQ(Argv[1], "-option_2"); + EXPECT_STREQ(Argv[2], "-option_3=abcd"); + + llvm::sys::fs::remove(TestCfg2); + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestDir); +} + } // anonymous namespace