summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Pavlov <sepavloff@gmail.com>2017-12-30 08:15:15 +0000
committerSerge Pavlov <sepavloff@gmail.com>2017-12-30 08:15:15 +0000
commit0c5f92eb6a3bd47ffa05d75681ae10bc53ca92c6 (patch)
tree65e06382e3b09ccff60e891e1c694af4b436b5ee
parent727bd578df48604636398fa39631da5ae11505dc (diff)
Added support for reading configuration files
Configuration file is read as a response file in which file names in the nested constructs `@file` are resolved relative to the directory where the including file resides. Lines in which the first non-whitespace character is '#' are considered as comments and are skipped. Trailing backslashes are used to concatenate lines in the same way as they are used in shell scripts. Differential Revision: https://reviews.llvm.org/D24926 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@321580 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/llvm/Support/CommandLine.h27
-rw-r--r--lib/Support/CommandLine.cpp47
-rw-r--r--unittests/Support/CommandLineTest.cpp133
3 files changed, 207 insertions, 0 deletions
diff --git a/include/llvm/Support/CommandLine.h b/include/llvm/Support/CommandLine.h
index d1901db7c68..f043c112861 100644
--- a/include/llvm/Support/CommandLine.h
+++ b/include/llvm/Support/CommandLine.h
@@ -1862,6 +1862,33 @@ using TokenizerCallback = void (*)(StringRef Source, StringSaver &Saver,
SmallVectorImpl<const char *> &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<const char *> &NewArgv,
+ bool MarkEOLs = false);
+
+/// 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<const char *> &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
diff --git a/lib/Support/CommandLine.cpp b/lib/Support/CommandLine.cpp
index 4caf4a4fdce..d9059ee908c 100644
--- a/lib/Support/CommandLine.cpp
+++ b/lib/Support/CommandLine.cpp
@@ -873,6 +873,44 @@ void cl::TokenizeWindowsCommandLine(StringRef Src, StringSaver &Saver,
NewArgv.push_back(nullptr);
}
+void cl::tokenizeConfigFile(StringRef Source, StringSaver &Saver,
+ SmallVectorImpl<const char *> &NewArgv,
+ bool MarkEOLs) {
+ for (const char *Cur = Source.begin(); Cur != Source.end();) {
+ SmallString<128> Line;
+ // 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 the current line.
+ const char *Start = Cur;
+ for (const char *End = Source.end(); Cur != End; ++Cur) {
+ if (*Cur == '\\') {
+ if (Cur + 1 != End) {
+ ++Cur;
+ if (*Cur == '\n' ||
+ (*Cur == '\r' && (Cur + 1 != End) && Cur[1] == '\n')) {
+ Line.append(Start, Cur - 1);
+ Cur += (*Cur == '\r' ? 2 : 1);
+ Start = Cur;
+ }
+ }
+ } else if (*Cur == '\n')
+ break;
+ }
+ // Tokenize line.
+ Line.append(Start, Cur);
+ cl::TokenizeGNUCommandLine(Line, Saver, NewArgv, MarkEOLs);
+ }
+}
+
// It is called byte order marker but the UTF-8 BOM is actually not affected
// by the host system's endianness.
static bool hasUTF8ByteOrderMark(ArrayRef<char> S) {
@@ -977,6 +1015,15 @@ bool cl::ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer,
return AllExpanded;
}
+bool cl::readConfigFile(StringRef CfgFile, StringSaver &Saver,
+ SmallVectorImpl<const char *> &Argv) {
+ if (!ExpandResponseFile(CfgFile, Saver, cl::tokenizeConfigFile, Argv,
+ /*MarkEOLs*/ false, /*RelativeNames*/ true))
+ return false;
+ return ExpandResponseFiles(Saver, cl::tokenizeConfigFile, 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
diff --git a/unittests/Support/CommandLineTest.cpp b/unittests/Support/CommandLineTest.cpp
index 1fb0213b4d1..36ff4e247e9 100644
--- a/unittests/Support/CommandLineTest.cpp
+++ b/unittests/Support/CommandLineTest.cpp
@@ -207,6 +207,85 @@ TEST(CommandLineTest, TokenizeWindowsCommandLine) {
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<const char *, 0> Actual;
+ BumpPtrAllocator A;
+ StringSaver Saver(A);
+ cl::tokenizeConfigFile("\\\n", Saver, Actual, /*MarkEOLs=*/false);
+ EXPECT_TRUE(Actual.empty());
+}
+
+TEST(CommandLineTest, TokenizeConfigFile9) {
+ SmallVector<const char *, 0> 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] = {
@@ -648,4 +727,58 @@ TEST(CommandLineTest, SetDefautValue) {
EXPECT_TRUE(Opt3 == 3);
}
+TEST(CommandLineTest, ReadConfigFile) {
+ llvm::SmallVector<const char *, 1> 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 << "# Comment\n"
+ "-option_1\n"
+ "@subconfig\n"
+ "-option_3=abcd\n"
+ "-option_4=\\\n"
+ "cdef\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"
+ "\n"
+ " # comment\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(), 4U);
+ EXPECT_STREQ(Argv[0], "-option_1");
+ EXPECT_STREQ(Argv[1], "-option_2");
+ EXPECT_STREQ(Argv[2], "-option_3=abcd");
+ EXPECT_STREQ(Argv[3], "-option_4=cdef");
+
+ llvm::sys::fs::remove(TestCfg2);
+ llvm::sys::fs::remove(TestCfg);
+ llvm::sys::fs::remove(TestDir);
+}
+
} // anonymous namespace