Index: docs/CommandLine.rst =================================================================== --- docs/CommandLine.rst +++ docs/CommandLine.rst @@ -1255,6 +1255,140 @@ enabled by an optional fourth argument to `cl::ParseEnvironmentOptions`_ and `cl::ParseCommandLineOptions`_. +.. _configuration files: + +Configuration files +^^^^^^^^^^^^^^^^^^^ + +Set of options may be grouped into **configurations**, which makes it easier to +specify necessary options. For instance, a build tool can produce an executable +either for debugging or product delivery. Both modes may require to specify many +options that define include paths, set of libraries, compiler options and so on. +These options can be grouped info *debug* and *release* configurations, and be +applied just by choosing the configuration name. + +A configuration is a collection of options stored in a file. Options may be +specified on one or several lines. Long options may be split between several +lines by trailing backslash. Lines composed of whitespace characters only are +ignored as well as lines in which the first non-blank character is `#`. Example +of configuration file: + +:: + + # Comment + -option_1 -option2 + + # Next option + -option_3 + # Option split between lines + -option_4="long long\ + argument" + +When a configuration is selected, all options contained in it are inserted into +the set of command line arguments as if they were specified at the beginning +of command line. + +Configuration file may be specified in several ways: + + - using command line option `--config`, + - as a default configuration. + +The function **cl::findConfigFile** is used to find configuration specified in +command line. It searches command line for option `--config`, which must be +followed by a configuration file name, for instance: + +:: + + --config /home/user/cfgs/testing.txt + --config debug.cfg + +If the argument contains a directory separator, it is considered as a file path, +options are read from that file. Otherwise the argument is treated as a file +name, which is searched for in the directories provided in call to +**cl::findConfigFile**. For instance, if the function is called: + +.. code-block:: c++ + + const char *Dirs[] = { "~/.llvm", "/etc/llvm" }; + llvm::SmallString<128> ConfigFile; + std::string ErrText; + bool Res = llvm::cl::findConfigFile(ConfigFile, argv, Dirs, false, ErrText); + +then in the invocation: + +:: + + appl --config debug.cfg + +file `debug.cfg` is searched for in the directories `~/.llvm` and `/etc/llvm`. If +the forth argument of the function call is changed to true: + +.. code-block:: c++ + + bool Res = llvm::cl::findConfigFile(ConfigFile, argv, Dirs, true, ErrText); + +`debug.cfg` is searched for in the directory there the executable file `appl` +resides, if it was not found in the directories specified by `Dirs`. + +Default configuration file may be found using the function **cl::searchForFile**. +The code: + +.. code-block:: c++ + + llvm::SmallString<128> ConfigFile; + const char *Dirs[] = { "~/.llvm", "/etc/llvm" }; + bool Res = llvm::cl::searchForFile(ConfigFile, Dirs, argv[0], "appl.cfg"); + +in the case of invocation: + +:: + + /export/tools/appl + +sequentially searches for the files: + +:: + + ~/.llvm/appl.cfg + /etc/llvm/appl.cfg + /export/tools/appl.cfg + +To prevent from search in binary directory, use empty string as the third argument: + +.. code-block:: c++ + + auto Res = llvm::cl::searchForFile(ConfigFile, Dirs, "", "appl.cfg"); + +The following code demonstrates how an application could process config files: + +.. code-block:: c++ + + // This array must contain argument specified in application invocation, these + // are same as specified by argument `argv` of `main`. + SmallVector argv; + + // Directories that are searched for configuration files. + static const char * const Dirs[] = { "~/.llvm", "/etc/llvm" }; + + // Variable that is assigned the path to the found file. + llvm::SmallString<128> ConfigFile; + + // Will contain error text. + std::string ErrText; + + // Look for the configuration file specigied in command line. + bool HasConfig = llvm::cl::findConfigFile(ConfigFile, argv, Dirs, true, ErrText); + if (!HasConfig && !ErrText.empty()) + std::cerr << ErrText; + return 1; + } + + // If config file is found, read it. + unsigned NumOpts = 0; + if (HasConfig) + llvm::cl::readConfigFile(ConfigFile, Saver, argv, NumOpts); + + Top-Level Classes and Functions ------------------------------- Index: include/llvm/Support/CommandLine.h =================================================================== --- include/llvm/Support/CommandLine.h +++ include/llvm/Support/CommandLine.h @@ -1822,6 +1822,78 @@ 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); + +/// Looks for the specified file in well-known directories. +/// +/// \param FilePath [out] File path, if the file was found. +/// \param Dirs [in] Directories used for the search. +/// \param ProgramFullPath [in] Path to the current executable or empty string. +/// \param FileName [in] Name of the file to search for. +/// \return True if file was found. +/// +/// Looks for file specified by FileName sequentially in directories specified +/// by Dirs. If not found and ProgramFullPath is not empty, searches directory +/// where the current executable resides. +/// +bool searchForFile(SmallVectorImpl &FilePath, + ArrayRef Dirs, StringRef ProgramFullPath, + StringRef FileName); + +/// Tries to find configuration file name specified by command line arguments. +/// +/// \param CfgFileName [out] Configuration file name, if it was specified by +/// command line. +/// \param Argv [in, out] Command line options supplied to the executable. +/// \param Dirs [in] Directories used to search configuration file. +/// \param SearchInBinDir [in] If True, also searches directory were executable +/// resides. +/// \param ErrorText [out] If error occurred it is assigned text of the error, +/// otherwise is assigned an empty string. +/// \return true if configuration file was not specified or the command line +/// contains errors. +/// +/// Configuration file is specified in command line by the option '--config' +/// followed by a file name. If the option is found, both the option and the +/// argument are removed from Argv. If the argument contains a directory +/// separator, it is treated as a file path, otherwise it is a file name and is +/// searched for in the directories: +/// - specified by Dir, +/// - where executable resides, if SearchInBinDir is True. +/// If the file is found, its full path is assigned to CfgFileName. +/// +bool findConfigFile(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs, + bool SearchInBinDir, + std::string &ErrorText); + +/// Reads command line options the from the given configuration file. +/// +/// \param CfgFileName [in] Path to configuration file. +/// \param Saver [in] Objects that saves allocated strings. +/// \param Argv [out] Command line into which options are read. +/// \param Num [out] Variable that is assigned the number of options read from +/// the config file. +/// \return true if the file was successfully read. +/// +/// Inserts options read from configuration file into Argv starting from index +/// 1 (Argv[0] must contain executable path). +/// +bool readConfigFile(SmallVectorImpl &CfgFileName, StringSaver &Saver, + SmallVectorImpl &Argv, unsigned &Num); + /// \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 @@ -1838,6 +1910,7 @@ /// \param [in] RelativeNames true if names of nested response files must be /// resolved relative to including file. /// \return true if all @files were expanded successfully or there were none. +/// bool ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer, SmallVectorImpl &Argv, bool MarkEOLs = false, bool RelativeNames = false); 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,140 @@ return AllExpanded; } +/// Search the given directories for the specified file. +/// +/// \param FilePath [out] Full file path, if it was found. +/// \param FileName [in] The file name to search for. +/// \param Directories [in] List of directories to search. +/// \return True if the file was found. +/// +static bool searchDirectoriesForFile(SmallVectorImpl &FilePath, + std::string FileName, + ArrayRef Directories) { + for (const char *Dir : Directories) { + assert(Dir); + FilePath.clear(); + llvm::sys::path::append(FilePath, Dir, FileName); + llvm::sys::path::native(FilePath); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + + return false; +} + +bool cl::searchForFile(SmallVectorImpl &FilePath, + ArrayRef Dirs, StringRef ProgramFullPath, + StringRef FileName) { + FilePath.clear(); + if (searchDirectoriesForFile(FilePath, FileName, Dirs)) + return true; + + // If not found, try searching the directory where executable resides. + FilePath.clear(); + if (!ProgramFullPath.empty()) { + llvm::sys::path::append(FilePath, + llvm::sys::path::parent_path(ProgramFullPath), + FileName); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + + return false; +} + +bool cl::findConfigFile(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs, bool SearchInBinDir, + std::string &ErrorText) { + CfgFileName.clear(); + ErrorText.clear(); + + // If command line contains option '--config', try to load the configuration + // specified by it. + auto CfgOption = std::find_if(Argv.begin(), Argv.end(), [](const char *X) { + return strcmp(X, "--config") == 0; + }); + if (CfgOption == Argv.end()) + return false; + + // '--config' must be followed by config name. + if (CfgOption + 1 == Argv.end()) { + ErrorText = "Option '--config' must be followed by configuration file name"; + return false; + } + // Only one '--config' is allowed. + if (std::find_if(CfgOption + 1, Argv.end(), + [](const char *X) { + return strcmp(X, "--config") == 0; + }) != Argv.end()) { + ErrorText = "More than one options '--config' are specified"; + return false; + } + + std::string CfgFile = *(CfgOption + 1); + + // Remove '--config' and its parameter from the command line options. + Argv.erase(CfgOption, CfgOption + 2); + + // If argument contains directory separator, treat it as a path to + // configuration file. + if (llvm::sys::path::has_parent_path(CfgFile)) { + if (!llvm::sys::fs::is_regular_file(CfgFile)) { + ErrorText = "Configuration file '" + CfgFile + + "' specified by option '--config' cannot be found"; + return false; + } + if (llvm::sys::path::is_relative(CfgFile)) + llvm::sys::fs::current_path(CfgFileName); + llvm::sys::path::append(CfgFileName, CfgFile); + return true; + } + + // Look configuration file in well-known directories, if they are specified. + bool MakeSearch = (!Dirs.empty() || SearchInBinDir); + if (MakeSearch && searchForFile(CfgFileName, Dirs, + SearchInBinDir ? Argv[0] : StringRef(), CfgFile)) + return true; + + ErrorText = "Configuration file '" + CfgFile + + "' specified by option '--config' cannot be found"; + if (MakeSearch) { + ErrorText.append(" in directories:\n"); + if (Dirs.empty()) + for (const char *Dir : Dirs) { + ErrorText.append(1, '\n'); + SmallString<128> DirStr; + llvm::sys::path::native(Dir, DirStr); + ErrorText.append(" "); + ErrorText.append(DirStr.c_str()); + } + if (SearchInBinDir) { + ErrorText.append(1, '\n'); + SmallString<128> BinDir(Argv[0]); + llvm::sys::path::remove_filename(BinDir); + ErrorText.append(" "); + ErrorText.append(BinDir.c_str()); + } + } + + return false; +} + +bool cl::readConfigFile(SmallVectorImpl &CfgFile, StringSaver &Saver, + SmallVectorImpl &Argv, unsigned &Num) { + CfgFile.push_back(0); + SmallVector NewArgs; + if (!ExpandResponseFile(CfgFile.data(), Saver, tokenizeConfigFile, NewArgs, + false, true)) + return false; + if (!ExpandResponseFiles(Saver, tokenizeConfigFile, NewArgs, false)) + return false; + Num = NewArgs.size(); + Argv.insert(Argv.begin() + 1, NewArgs.begin(), NewArgs.end()); + return 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 @@ -1012,6 +1152,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, bool IgnoreErrors) { 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] = { @@ -570,4 +649,307 @@ llvm::sys::fs::remove(TestDir); } +TEST(CommandLineTest, ConfigFileErrors) { + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv = { "test/test" }; + ArrayRef Dirs; + bool Result; + std::string ErrText; + + // Config in not specified. + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText.empty()); + + // Missed argument of --config + Argv = { "--config" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText == + "Option '--config' must be followed by configuration file name"); + + // Multiple options --config + Argv = { "--config", "a", "--config", "b" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText == "More than one options '--config' are specified"); + + // Inexistent file specified by full path. + Argv = { "--config", "/inexistent_file.cfg" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText == "Configuration file '/inexistent_file.cfg' specified " + "by option '--config' cannot be found"); + EXPECT_TRUE(Argv.empty()); + + // Inexistent file. + Argv = { "--config", "inexistent_file.cfg" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText == "Configuration file 'inexistent_file.cfg' specified " + "by option '--config' cannot be found"); + EXPECT_TRUE(Argv.empty()); +} + +TEST(CommandLineTest, ReadConfigFile) { + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv; + const char *ProgramFullPath = "/test/test"; + ArrayRef Dirs; + + 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); + + std::ofstream Cfg(TestCfg.c_str()); + Cfg << "# Comment\n"; + Cfg << "-option_1 -option_2\n"; + Cfg << "-option_3=abcd\n"; + Cfg.close(); + + Argv = { ProgramFullPath, "-flag_1", "--config", FileName.c_str() }; + Dirs = ArrayRef(Directory.c_str()); + bool Result; + std::string ErrText; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + + EXPECT_TRUE(Result); + EXPECT_TRUE(ErrText.empty()); + EXPECT_TRUE(CfgFileName.equals(TestCfg)); + EXPECT_EQ(Argv.size(), 2); + EXPECT_STREQ(Argv[0], ProgramFullPath); + EXPECT_STREQ(Argv[1], "-flag_1"); + + unsigned NumOpts = 0; + Result = llvm::cl::readConfigFile(CfgFileName, Saver, Argv, NumOpts); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 5); + EXPECT_STREQ(Argv[0], ProgramFullPath); + EXPECT_STREQ(Argv[1], "-option_1"); + EXPECT_STREQ(Argv[2], "-option_2"); + EXPECT_STREQ(Argv[3], "-option_3=abcd"); + EXPECT_STREQ(Argv[4], "-flag_1"); + EXPECT_EQ(NumOpts, 3); + + // 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 = { ProgramFullPath, "-flag_1", "--config", FileName.c_str(), + "-flag_2" }; + Dirs = ArrayRef(Directory.c_str()); + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + + EXPECT_TRUE(Result); + EXPECT_TRUE(ErrText.empty()); + EXPECT_TRUE(CfgFileName.equals(TestCfg)); + EXPECT_EQ(Argv.size(), 3); + EXPECT_STREQ(Argv[0], ProgramFullPath); + EXPECT_STREQ(Argv[1], "-flag_1"); + EXPECT_STREQ(Argv[2], "-flag_2"); + + NumOpts = 0; + Result = llvm::cl::readConfigFile(CfgFileName, Saver, Argv, NumOpts); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 5); + EXPECT_STREQ(Argv[0], ProgramFullPath); + EXPECT_STREQ(Argv[1], "-option_1"); + EXPECT_STREQ(Argv[2], "-option_3=abcd"); + EXPECT_STREQ(Argv[3], "-flag_1"); + EXPECT_STREQ(Argv[4], "-flag_2"); + EXPECT_EQ(NumOpts, 2); + + llvm::sys::fs::remove(TestCfg); +} + +TEST(CommandLineTest, ReadConfigFileFromBinDir) { + // Create test directory + llvm::SmallString<128> TestDir; + std::error_code EC = + llvm::sys::fs::createUniqueDirectory("unittest", TestDir); + EXPECT_TRUE(!EC); + + // Pretend executable is in the test directory. + llvm::SmallString<128> ProgramFullPath; + llvm::sys::path::append(ProgramFullPath, TestDir, "testtool"); + + const char * ConfigFileName = "test.cfg"; + + // Create config file in the binary directory. + llvm::SmallString<128> BinCfg; + llvm::sys::path::append(BinCfg, TestDir, ConfigFileName); + std::ofstream Cfg(BinCfg.c_str()); + EXPECT_TRUE(Cfg.is_open()); + Cfg << "-option_1 -option_2"; + Cfg.close(); + + // Create directory for config files. + llvm::SmallString<128> TestCfgDir; + llvm::sys::path::append(TestCfgDir, TestDir, "stddir"); + EC = llvm::sys::fs::create_directory(TestCfgDir); + EXPECT_TRUE(!EC); + + // Create config file there. + llvm::SmallString<128> TestCfg; + llvm::sys::path::append(TestCfg, TestCfgDir, ConfigFileName); + std::ofstream Cfg2(TestCfg.c_str()); + EXPECT_TRUE(Cfg2.is_open()); + Cfg2 << "-option_3=abcd"; + Cfg2.close(); + + // Working variables shared between test cases. + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv; + ArrayRef Dirs; + bool Result; + std::string ErrText; + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + + // Case 1: search directory has higher priority than binary. + Argv = { ProgramFullPath.c_str(), "--config", ConfigFileName }; + Dirs = ArrayRef(TestCfgDir.c_str()); + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, true, ErrText); + EXPECT_TRUE(Result); + EXPECT_TRUE(ErrText.empty()); + EXPECT_TRUE(strcmp(CfgFileName.c_str(), TestCfg.c_str()) == 0); + + // Case 2: without search directories config file in binary directory must + // be found. + Argv = { ProgramFullPath.c_str(), "--config", ConfigFileName }; + Dirs = ArrayRef(); + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, true, ErrText); + EXPECT_TRUE(Result); + EXPECT_TRUE(ErrText.empty()); + EXPECT_TRUE(strcmp(CfgFileName.c_str(), BinCfg.c_str()) == 0); + + // Case 3: Config file is not found if search in binary directory is + // suppressed. + Argv = { ProgramFullPath.c_str(), "--config", ConfigFileName }; + Dirs = ArrayRef(); + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, false, ErrText); + EXPECT_FALSE(Result); + EXPECT_TRUE(ErrText == std::string("Configuration file '") + ConfigFileName + + "' specified by option '--config' cannot be found"); + + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestCfgDir); + llvm::sys::fs::remove(BinCfg); + llvm::sys::fs::remove(TestDir); +} + +TEST(CommandLineTest, ReadDefaultConfigFile) { + llvm::SmallString<128> TestDir; + std::error_code EC = + llvm::sys::fs::createUniqueDirectory("unittest", TestDir); + EXPECT_TRUE(!EC); + + llvm::SmallString<128> TestTool; + llvm::sys::path::append(TestTool, TestDir, "testtool"); + std::ofstream Tool(TestTool.c_str()); + EXPECT_TRUE(Tool.is_open()); + Tool << std::endl; + Tool.close(); + + llvm::SmallString<128> TestCfg; + llvm::sys::path::append(TestCfg, TestDir, "test.cfg"); + std::ofstream Cfg(TestCfg.c_str()); + EXPECT_TRUE(Cfg.is_open()); + Cfg << "-option_1 -option_2\n" + "-option_3=abcd\n"; + Cfg.close(); + + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + + llvm::SmallVector Argv = { TestTool.c_str(), "-flag_1" }; + llvm::SmallString<128> CfgFileName; + bool Result = cl::searchForFile(CfgFileName, ArrayRef(), + Argv[0], "test.cfg"); + + EXPECT_TRUE(Result); + EXPECT_STREQ(CfgFileName.c_str(), TestCfg.c_str()); + + unsigned NumOpts = 0; + Result = llvm::cl::readConfigFile(CfgFileName, Saver, Argv, NumOpts); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 5); + EXPECT_STREQ(Argv[0], TestTool.c_str()); + EXPECT_STREQ(Argv[1], "-option_1"); + EXPECT_STREQ(Argv[2], "-option_2"); + EXPECT_STREQ(Argv[3], "-option_3=abcd"); + EXPECT_STREQ(Argv[4], "-flag_1"); + EXPECT_EQ(NumOpts, 3); + + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestTool); + llvm::sys::fs::remove(TestDir); +} + +TEST(CommandLineTest, ReadDefaultConfigFileFromStdDir) { + llvm::SmallString<128> TestDir; + std::error_code EC = + llvm::sys::fs::createUniqueDirectory("unittest", TestDir); + EXPECT_TRUE(!EC); + + llvm::SmallString<128> TestTool; + llvm::sys::path::append(TestTool, TestDir, "testtool"); + std::ofstream Tool(TestTool.c_str()); + EXPECT_TRUE(Tool.is_open()); + Tool << std::endl; + Tool.close(); + + llvm::SmallString<128> TestCfgDir; + llvm::sys::path::append(TestCfgDir, TestDir, "stddir"); + EC = llvm::sys::fs::create_directory(TestCfgDir); + EXPECT_TRUE(!EC); + + llvm::SmallString<128> TestCfg; + llvm::sys::path::append(TestCfg, TestCfgDir, "test.cfg"); + std::ofstream Cfg(TestCfg.c_str()); + EXPECT_TRUE(Cfg.is_open()); + Cfg << "-option_3=abcd\n"; + Cfg.close(); + + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + + llvm::SmallVector Argv = { TestTool.c_str(), "-flag_1" }; + const char *TestCfgDirStr = TestCfgDir.c_str(); + ArrayRef Dirs(TestCfgDirStr); + llvm::SmallString<128> CfgFileName; + bool Result = cl::searchForFile(CfgFileName, Dirs, Argv[0], "test.cfg"); + + EXPECT_TRUE(Result); + EXPECT_STREQ(CfgFileName.c_str(), TestCfg.c_str()); + + unsigned NumOpts = 0; + Result = llvm::cl::readConfigFile(CfgFileName, Saver, Argv, NumOpts); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 3); + EXPECT_STREQ(Argv[0], TestTool.c_str()); + EXPECT_STREQ(Argv[1], "-option_3=abcd"); + EXPECT_STREQ(Argv[2], "-flag_1"); + EXPECT_EQ(NumOpts, 1); + + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestCfgDir); + llvm::sys::fs::remove(TestTool); + llvm::sys::fs::remove(TestDir); +} + } // anonymous namespace