Index: docs/CommandLine.rst =================================================================== --- docs/CommandLine.rst +++ docs/CommandLine.rst @@ -1262,6 +1262,130 @@ enabled by an optional fourth argument to `cl::ParseEnvironmentOptions`_ and `cl::ParseCommandLineOptions`_. +.. _configuration files: + +Configuration files +^^^^^^^^^^^^^^^^^^^ + +Set of options may be groupped into **configurations**, which makes it easier to +specify necessary options. For instance, a build tool may be run to produce a +program for debugging or for product delivery. Both modes may require to specify +many options that set include paths, set of libraries, compiler options and so +on. These options can be groupped info *debug* and *release* configurations, and +be specified just by choosing one of them. + +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" + +Particular configuration file may be specified in several ways: + + - Using command line option `--config`, followed by configuration file name, + - By setting up environment variable, specific to the application. + - As a default configuration. + +There are separate functions that search for configuration file name specified by +each way. + +The function **cl::findConfigFileFromArgs** is used to find configuration +specified in command line. It searches command line for the option `--config`, +which must be followed by is either a full path to configuration file, or just a +configuration file name, for instance: + +:: + + --config /home/user/cfgs/testing.txt + --config debug + --config debug.cfg + +If the argument contains a directory separator, it is considered as a file name, +options are read from that file. Otherwise the argument is appended extension +`cfg` if it is not specified yet, and the obtained file name is searched for +in the directories specified in the application source code. For instance, in +the invocation: + +:: + + appl --config debug + +file `debug.cfg` is searched for in the directories `~/.llvm` and `/etc/llvm`, +if they were specified in call to **cl::findConfigFileFromArgs**. + +The function **cl::findConfigFileFromEnv** may be used to find config file +specified by environment variable. If neither `--config` nor environment variable +are specified, the application may looks for a default configuration file using +function **cl::findDefaultCfgFile**. It searches for the given file in the +specified set of well-known directories. If the file was not found there, it is +looked for in the directory where application executable resides. For example, +if a tool `appl` was run by the command: + +:: + + /export/tools/appl + +the cl::findConfigFileFromEnv looks for the files: + +:: + + ~/.llvm/appl.cfg + /etc/llvm/appl.cfg + /export/tools/appl.cfg + +if the directories `~/.llvm` and `/etc/llvm` were specified in call to +**cl::findDefaultCfgFile**. + +To use configuration files, application calls appropriate library functions as +in the example: + +.. code-block:: c++ + + // This array must contain argument specified in application invocation, these + // are same as specified by argument of `main`. + SmallVector argv; + + // Directories that are searched for configuration files. + static const char * const SearchDirs[] = { "~/.llvm", "/etc/llvm" }; + + // Variable that is assigned the path to the found file. + llvm::SmallString<128> ConfigFile; + + // Look for the configuration file specigied in command line. + auto SRes = llvm::cl::findConfigFileFromArgs(ConfigFile, argv, SearchDirs); + if (llvm::cl::checkConfigFileSearchResult(SRes, ConfigFile, SearchDirs, argv[0])) + return 1; + + // If command line does not contain option '--config', look for envirinment + // variable, "APPLCFG" in our case. + if (SRes == llvm::cl::SearchResult::NotSpecified) { + SRes = llvm::cl::findConfigFileFromEnv(ConfigFile, "APPLCFG"); + if (llvm::cl::checkConfigFileSearchResult(SRes, ConfigFile, SearchDirs, argv[0])) + return 1; + } + + // Finally try to find default config file. + SRes = llvm::cl::findDefaultCfgFile(ConfigFile, SearchDirs, ProgName, + "appl.cfg"); + if (llvm::cl::checkConfigFileSearchResult(SRes, ConfigFile, SearchDirs, argv[0])) + return 1; + + // If file is found, read it. + if (SRes == llvm::cl::CfgFileSearch::Successful) + llvm::cl::readConfigFile(ConfigFile, Saver, argv); + + Top-Level Classes and Functions ------------------------------- Index: include/llvm/Support/CommandLine.h =================================================================== --- include/llvm/Support/CommandLine.h +++ include/llvm/Support/CommandLine.h @@ -1777,6 +1777,105 @@ SmallVectorImpl &NewArgv, bool MarkEOLs); +/// \brief 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 [in] MarkEOLs Added for compatibility with TokenizerCallback. +/// \param [out] NewArgv All parsed strings are appended to NewArgv. +/// +/// It works like TokenizeGNUCommandLine with ability to skip comment lines. +/// +void TokenizeConfigFile(StringRef Source, StringSaver &Saver, + SmallVectorImpl &NewArgv, + bool MarkEOLs = false); + +/// Enumerates possible results of configuration file search. +/// +enum class SearchResult { + Successful, ///< File is found. + NotSpecified, ///< File is not specified, no diagnostic required. + NoArgument, ///< Option '--config' is not followed by argument. + Multiple, ///< Several options '--config' are found. + NotFoundCfg, ///< Configuration specified by --config was not found. + NotFoundOpt, ///< File specified by '--config' does not exist. + NotFoundEnv ///< File specified by environmental variable does not exist. +}; + +/// \brief Tries to find configuration file specified by command line arguments. +/// +/// \param CfgFileName [out] Configuration file name, if it was specified by +/// command line. +/// \param Argv [in, out] Command line option supplied to the executable. +/// \param Dirs [in] Directories used to search configuration file. +/// \return Error code of the search. +/// +/// If option '--config' is specified, its argument is assigned to CfgName. Both +/// the option and the argument are removed from Argv. +/// +SearchResult findConfigFileFromArgs(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs); + +/// \brief Tries to find configuration file specified by the given environment +/// variable. +/// +/// \param CfgFileName [out] Configuration file name specified by the variable. +/// \param VarName [in] Environment variable that may contain full path to +/// configuration file. +/// \return Error code of the search. +/// +SearchResult findConfigFileFromEnv(SmallVectorImpl &CfgFileName, + StringRef VarName); + +/// \brief Tries to find default configuration file. +/// +/// \param CfgFileName [out] Configuration file name, if the file was found. +/// \param Dirs [in] Directories used to search configuration file. +/// \param ProgramFullPath [in] Path to the tool executable. +/// \param FileName [in] Name of the configuration file. +/// \return Error code of the search. +/// +SearchResult findDefaultCfgFile(SmallVectorImpl &CfgFileName, + ArrayRef Dirs, + StringRef ProgramFullPath, + StringRef FileName); + +/// \brief Report error occurred in config file search. +/// +/// \param Res [in] Code of search result. +/// \param CfgFileName [in] If set it is configuration name or full path to +/// configuration file. +/// \param Dirs [in] Directories used to search configuration file. +/// \param ProgramFullPath [in] Path to the tool executable. +/// \param ToolName [in] Name of tool that reads options. +/// \return true if the specified code represents an error. +/// +bool checkConfigFileSearchResult(SearchResult Res, + StringRef CfgFileName, + ArrayRef Dirs, + StringRef ProgramFullPath); + +/// \brief Tries to read command line options from configuration file. +/// +/// \param Saver [in] Objects that saves allocated strings. +/// \param Argv [out] Command line into which options are read. +/// \param ToolName [in] Name of tool that reads options. +/// +/// Configuration file provides a facility for a tool to set up some command +/// line options before the options actually specified in command line. Options +/// specified in configuration file are automatically processed every time the +/// tool executable is run. +/// +/// The default configuration file is a file named .cfg (for instance +/// 'clang.cfg') placed in the same directory as the tool executable. If +/// environment variable CFG (for instance CLANGCFG) is set, its value +/// is considered as full path to configuration file, default file is not used +/// in this case. +/// +void readConfigFile(SmallVectorImpl &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 @@ -30,6 +30,7 @@ #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" @@ -704,6 +705,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; } @@ -941,6 +948,167 @@ return AllExpanded; } +static bool findFileInDirectories(SmallVectorImpl &FilePath, + std::string FileName, + ArrayRef Directories) { + for (const char *Dir : Directories) { + assert(Dir); + FilePath.clear(); + if (Dir[0] == '~') { + assert(llvm::sys::path::is_separator(Dir[1])); + if (llvm::sys::path::home_directory(FilePath)) { + llvm::sys::path::append(FilePath, Dir + 2, FileName); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + continue; + } + llvm::sys::path::append(FilePath, Dir, FileName); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + + return false; +} + +cl::SearchResult cl::findConfigFileFromArgs(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs) { + assert(!Argv.empty()); + CfgFileName.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 SearchResult::NotSpecified; + + // '--config' must be followed by config name. + if (CfgOption + 1 == Argv.end()) + return SearchResult::NoArgument; + // Only one '--config' is allowed. + if (std::find_if(CfgOption + 1, Argv.end(), + [](const char *X) { + return strcmp(X, "--config") == 0; + }) != Argv.end()) + return SearchResult::Multiple; + std::string CfgFile = *(CfgOption + 1); + + // Remove '--config' and its parameter from command line options. + Argv.erase(CfgOption, CfgOption + 2); + + // If argument contains directory separator, treat it as a path to + // configuration file. Otherwise it is a configuration name and must be + // resolved to file path. + if (!llvm::sys::path::has_parent_path(CfgFile)) { + if (!StringRef(CfgFile).endswith(".cfg")) + CfgFile += ".cfg"; + + // Look configuration file in well-known directories, if they are + // specified. + if (findFileInDirectories(CfgFileName, CfgFile, Dirs)) + return SearchResult::Successful; + + // Keep configuration name for error report. + CfgFileName.clear(); + CfgFileName.append(CfgFile.begin(), CfgFile.end()); + return SearchResult::NotFoundCfg; + } + + // Configuration was specified by path. + CfgFileName.append(CfgFile.begin(), CfgFile.end()); + + assert(!CfgFileName.empty()); + if (!llvm::sys::fs::is_regular_file(CfgFileName)) + return SearchResult::NotFoundOpt; + return SearchResult::Successful; +} + + +cl::SearchResult cl::findConfigFileFromEnv(SmallVectorImpl &CfgFileName, + StringRef VarName) { + CfgFileName.clear(); + if (auto CfgPath = sys::Process::GetEnv(VarName)) { + CfgFileName.append(CfgPath->begin(), CfgPath->end()); + if (!CfgFileName.empty()) { + if (!llvm::sys::fs::is_regular_file(CfgFileName)) + return SearchResult::NotFoundEnv; + return SearchResult::Successful; + } + // Treat empty variable as if it was is not set. + } + return SearchResult::NotSpecified; +} + + +cl::SearchResult cl::findDefaultCfgFile(SmallVectorImpl &CfgFileName, + ArrayRef Dirs, + StringRef ProgramFullPath, + StringRef FileName) { + CfgFileName.clear(); + if (findFileInDirectories(CfgFileName, FileName, Dirs)) + return SearchResult::Successful; + + // If not found, try searching the directory where executable resides. + CfgFileName.clear(); + llvm::sys::path::append(CfgFileName, + llvm::sys::path::parent_path(ProgramFullPath), + FileName); + if (llvm::sys::fs::is_regular_file(CfgFileName)) + return SearchResult::Successful; + return SearchResult::NotSpecified; +} + + +bool cl::checkConfigFileSearchResult(SearchResult Res, + StringRef CfgFile, + ArrayRef Dirs, + StringRef ProgramFullPath) { + switch (Res) { + case SearchResult::Successful: + case SearchResult::NotSpecified: + return false; + case SearchResult::NoArgument: + errs() << ProgramFullPath << + ": CommandLine Error: Option '--config' must be followed by " + "configuration name or full path to configuration file\n"; + break; + case SearchResult::Multiple: + errs() << ProgramFullPath << + ": CommandLine Error: More than one option '--config' is specified\n"; + break; + case SearchResult::NotFoundCfg: + errs() << ProgramFullPath << + ": CommandLine Error: Configuration '" << CfgFile << "' specified by " + "option '--config' cannot be found in directories:\n"; + for (const char *Dir : Dirs) + errs() << " " << Dir << "\n"; + break; + case SearchResult::NotFoundOpt: + errs() << ProgramFullPath << + ": CommandLine Error: Configuration file '" << CfgFile << "' specified " + "by option '--config' cannot be found\n"; + break; + case SearchResult::NotFoundEnv: + errs() << ProgramFullPath << + ": CommandLine Error: Configuration file '" << CfgFile << "' specified " + "by environment variable cannot be found\n"; + break; + } + return true; +} + +void cl::readConfigFile(SmallVectorImpl &CfgFile, StringSaver &Saver, + SmallVectorImpl &Argv) { + CfgFile.push_back(0); + SmallVector NewArgs; + ExpandResponseFile(CfgFile.data(), Saver, TokenizeConfigFile, NewArgs); + ExpandResponseFiles(Saver, TokenizeConfigFile, NewArgs, false); + Argv.insert(Argv.begin() + 1, NewArgs.begin(), NewArgs.end()); +} + /// 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 @@ -971,6 +1139,39 @@ ParseCommandLineOptions(newArgc, &newArgv[0], 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, const char *Overview, bool IgnoreErrors) { return GlobalParser->ParseCommandLineOptions(argc, argv, Overview, Index: unittests/Support/CommandLineTest.cpp =================================================================== --- unittests/Support/CommandLineTest.cpp +++ unittests/Support/CommandLineTest.cpp @@ -7,11 +7,15 @@ // //===----------------------------------------------------------------------===// +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Config/config.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.h" #include "gtest/gtest.h" +#include #include #include @@ -201,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] = { @@ -476,4 +559,216 @@ EXPECT_FALSE(cl::ParseCommandLineOptions(3, args2, nullptr, true)); } +TEST(CommandLineTest, ConfigFileErrors) { + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv = { "test/test" }; + ArrayRef Dirs; + cl::SearchResult Result; + + // Config in not specified. + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::NotSpecified); + + // Missed argument of --config + Argv = { "--config" }; + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::NoArgument); + + // Multiple options --config + Argv = { "--config", "a", "--config", "b" }; + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::Multiple); + + // Inexistent file specified by full path. + Argv = { "--config", "/abcd.cfg" }; + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::NotFoundOpt); + EXPECT_TRUE(Argv.empty()); + EXPECT_TRUE(CfgFileName.equals("/abcd.cfg")); + + // Inexistent configuration. + Argv = { "--config", "abcd.cfg" }; + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::NotFoundCfg); + EXPECT_TRUE(Argv.empty()); + EXPECT_TRUE(CfgFileName.equals("abcd.cfg")); + + // Inexistent configuration, configuration without extension. + Argv = { "--config", "abcd" }; + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + EXPECT_TRUE(Result == cl::SearchResult::NotFoundCfg); + EXPECT_TRUE(Argv.empty()); + EXPECT_TRUE(CfgFileName.equals("abcd.cfg")); +} + +TEST(CommandLineTest, ReadConfigFile) { + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv; + const char *ProgramFullPath = "/test/test"; + ArrayRef Dirs; + cl::SearchResult Result; + + 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()); + Result = cl::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + + EXPECT_TRUE(Result == cl::SearchResult::Successful); + EXPECT_TRUE(CfgFileName.equals(TestCfg)); + EXPECT_EQ(Argv.size(), 2); + EXPECT_STREQ(Argv[0], ProgramFullPath); + EXPECT_STREQ(Argv[1], "-flag_1"); + + llvm::cl::readConfigFile(CfgFileName, Saver, Argv); + + 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"); + + // 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::findConfigFileFromArgs(CfgFileName, Argv, Dirs); + + EXPECT_TRUE(Result == cl::SearchResult::Successful); + 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"); + + llvm::cl::readConfigFile(CfgFileName, Saver, Argv); + + 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"); + + llvm::sys::fs::remove(TestCfg); +} + +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; + cl::SearchResult Result = cl::findDefaultCfgFile(CfgFileName, + ArrayRef(), Argv[0], "test.cfg"); + + EXPECT_TRUE(Result == cl::SearchResult::Successful); + EXPECT_STREQ(CfgFileName.c_str(), TestCfg.c_str()); + + llvm::cl::readConfigFile(CfgFileName, Saver, Argv); + + 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"); + + 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" }; + ArrayRef Dirs = { TestCfgDir.c_str() }; + llvm::SmallString<128> CfgFileName; + cl::SearchResult Result = cl::findDefaultCfgFile(CfgFileName, Dirs, Argv[0], + "test.cfg"); + + EXPECT_TRUE(Result == cl::SearchResult::Successful); + EXPECT_STREQ(CfgFileName.c_str(), TestCfg.c_str()); + + llvm::cl::readConfigFile(CfgFileName, Saver, Argv); + + 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"); + + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestCfgDir); + llvm::sys::fs::remove(TestTool); + llvm::sys::fs::remove(TestDir); +} + } // anonymous namespace