Index: include/llvm/Support/CommandLine.h =================================================================== --- include/llvm/Support/CommandLine.h +++ include/llvm/Support/CommandLine.h @@ -1828,6 +1828,29 @@ SmallVectorImpl &NewArgv, bool MarkEOLs); +/// Tokenizes content of configuration file. +/// +/// \param [in] Source The string representing content of config file. +/// \param [in] Saver Delegates back to the caller for saving parsed strings. +/// \param [out] NewArgv All parsed strings are appended to NewArgv. +/// \param [in] MarkEOLs Added for compatibility with TokenizerCallback. +/// +/// It works like TokenizeGNUCommandLine with ability to skip comment lines. +/// +void tokenizeConfigFile(StringRef Source, StringSaver &Saver, + SmallVectorImpl &NewArgv, + bool MarkEOLs = false); + +/// Reads command line options from the given configuration file. +/// +/// \param CfgFileName [in] Path to configuration file. +/// \param Saver [in] Objects that saves allocated strings. +/// \param Argv [out] Array to which the read options are added. +/// \return true if the file was successfully read. +/// +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 @@ -723,6 +723,12 @@ // 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; } @@ -982,6 +988,14 @@ return AllExpanded; } +bool cl::readConfigFile(StringRef CfgFile, StringSaver &Saver, + SmallVectorImpl &Argv) { + if (!ExpandResponseFile(CfgFile, Saver, tokenizeConfigFile, Argv, false, + true)) + return false; + return ExpandResponseFiles(Saver, tokenizeConfigFile, Argv, false); +} + /// 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 @@ -1012,6 +1026,39 @@ ParseCommandLineOptions(newArgc, &newArgv[0], StringRef(Overview)); } +void cl::tokenizeConfigFile(StringRef Source, StringSaver &Saver, + SmallVectorImpl &NewArgv, + bool MarkEOLs) { + for (const char *Cur = Source.begin(); Cur != Source.end();) { + // Check for comment line. + if (isWhitespace(*Cur)) { + while (Cur != Source.end() && isWhitespace(*Cur)) + ++Cur; + continue; + } + if (*Cur == '#') { + while (Cur != Source.end() && *Cur != '\n') + ++Cur; + continue; + } + // Find end of current line. + const char *Start = Cur; + for (const char *End = Source.end(); Cur != End; ++Cur) { + if (*Cur == '\\') { + if (Cur + 1 != End) { + ++Cur; + if (Cur != End && *Cur == '\r' && (Cur + 1 != End) && Cur[1] == '\n') + ++Cur; + } + } else if (*Cur == '\n') + break; + } + // Tokenize line. + StringRef Line(Start, Cur - Start); + cl::TokenizeGNUCommandLine(Line, Saver, NewArgv, MarkEOLs); + } +} + bool cl::ParseCommandLineOptions(int argc, const char *const *argv, StringRef Overview, raw_ostream *Errs) { return GlobalParser->ParseCommandLineOptions(argc, argv, Overview, Index: unittests/Support/CommandLineTest.cpp =================================================================== --- unittests/Support/CommandLineTest.cpp +++ unittests/Support/CommandLineTest.cpp @@ -205,6 +205,85 @@ array_lengthof(Output)); } +TEST(CommandLineTest, TokenizeConfigFile1) { + const char *Input = "\\"; + const char *const Output[] = { "\\" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile2) { + const char *Input = "\\abc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile3) { + const char *Input = "abc\\"; + const char *const Output[] = { "abc\\" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile4) { + const char *Input = "abc\\\n123"; + const char *const Output[] = { "abc123" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile5) { + const char *Input = "abc\\\r\n123"; + const char *const Output[] = { "abc123" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile6) { + const char *Input = "abc\\\n"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile7) { + const char *Input = "abc\\\r\n"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile8) { + SmallVector Actual; + BumpPtrAllocator A; + StringSaver Saver(A); + cl::tokenizeConfigFile("\\\n", Saver, Actual, /*MarkEOLs=*/false); + EXPECT_TRUE(Actual.empty()); +} + +TEST(CommandLineTest, TokenizeConfigFile9) { + SmallVector Actual; + BumpPtrAllocator A; + StringSaver Saver(A); + cl::tokenizeConfigFile("\\\r\n", Saver, Actual, /*MarkEOLs=*/false); + EXPECT_TRUE(Actual.empty()); +} + +TEST(CommandLineTest, TokenizeConfigFile10) { + const char *Input = "\\\nabc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile11) { + const char *Input = "\\\r\nabc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + TEST(CommandLineTest, AliasesWithArguments) { static const size_t ARGC = 3; const char *const Inputs[][ARGC] = { @@ -604,4 +683,52 @@ llvm::sys::fs::remove(TestDir); } +TEST(CommandLineTest, ReadConfigFile) { + llvm::SmallVector Argv; + + llvm::SmallString<128> TestCfg; + std::error_code EC = + llvm::sys::fs::createTemporaryFile("unittest", "cfg", TestCfg); + EXPECT_TRUE(!EC); + std::string Directory = llvm::sys::path::parent_path(TestCfg); + std::string FileName = llvm::sys::path::filename(TestCfg); + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + bool Result; + + // Comments are ignored in config files. + std::ofstream Cfg(TestCfg.c_str()); + Cfg << "# Comment\n"; + Cfg << "-option_1 -option_2\n"; + Cfg << "-option_3=abcd\n"; + Cfg.close(); + + Result = llvm::cl::readConfigFile(TestCfg, Saver, Argv); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 3); + EXPECT_STREQ(Argv[0], "-option_1"); + EXPECT_STREQ(Argv[1], "-option_2"); + EXPECT_STREQ(Argv[2], "-option_3=abcd"); + + // Reading from file with lines concatenated by trailing \. + Cfg.open(TestCfg.c_str(), std::ofstream::trunc); + EXPECT_TRUE(Cfg.is_open()); + + Cfg << "\n\n# Comment\n"; + Cfg << "-option_\\\n"; + Cfg << "1 -option_3=abcd\n"; + Cfg.close(); + + Argv.clear(); + Result = llvm::cl::readConfigFile(TestCfg, Saver, Argv); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 2); + EXPECT_STREQ(Argv[0], "-option_1"); + EXPECT_STREQ(Argv[1], "-option_3=abcd"); + + llvm::sys::fs::remove(TestCfg); +} + } // anonymous namespace