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 '\'. 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 `*CFG`, for instance `FOOCFG`, if + application name is `foo`. + - As a default configuration. + +Only one way may be used. If option `--config` is given, environment variable +is not checked and the default configuration will not be applied even if the +requested configuration is not found. Similarly, if variable `FOOCFG` exists, +default configuration is never applied. + +Command line option `--config` expects an argument which 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::findConfigFile**. + +If the option `--config` is absent, and environment variable `FOOCFG` is set, +content of `FOOCFG` is considered as a path to configuration file. If `FOOCFG` +is empty, configuration file is not used, no diagnostic is produced. For +instance, in the command: + +:: + + APPLCFG="/etc/llvm/testing.txt" appl + +Options are read from the file `/etc/llvm/testing.txt`. + +If neither `--config` nor `FOOCFG` are specified, the application looks for a +default configuration file. It is a file named `foo.cfg`, it is searched for in +the same directories that are used to search for configuration files specified +explicitly by `--config` or environment variable. If this search also fails, +the file `foo.cfg` is looked for in the directory where application executable +resides. For example, the command: + +:: + + /export/tools/appl + +looks for files: + +:: + + ~/.llvm/appl.cfg + /etc/llvm/appl.cfg + /export/tools/appl.cfg + +If configuration was explicitly specified (by `--config` or environment variable) +and corresponding file was not found, error message is printed and the application +exits. + +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. + // "appl" is a name of our application. + auto SRes = llvm::cl::findConfigFile(ConfigFile, argv, SearchDirs, "appl"); + + // Process errors if occurred. + llvm::cl::reportConfigFileSearchError(SRes, ConfigFile, SearchDirs, argv[0]); + + // 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,91 @@ 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 CfgFileSearch { + Successful, ///< File is found. + Ignored, ///< File not found, no diagnostic required. + NoArgument, ///< Option '--config' is not followed by argument. + 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 for current invocation. +/// +/// \param CfgFileName [out] Configuration file name, if the search was +/// successful. +/// \param Argv [in, out] Command line option supplied to the executable. +/// \param Dirs [in] Directories used to search configuration file. +/// \param ToolName [in] Name of tool that reads options. +/// \return Error code of the search. +/// +/// Configuration file may be specified in several ways: +/// - via option --config, +/// - by setting environmental variable CFG (for instance CLANGCFG), +/// - as default configuration. +/// +/// If option '--config' is specified, its argument is assigned to CfgName, +/// unless it is full path. Both the option and the argument is removed +/// from Argv. +/// If explicitly specified configuration (via --config or env variable) cannot +/// be found, error message is emitted and program exits with non-zero exit +/// code. +/// Argument Argv is expected to store full program name in Argv[0]. +/// +CfgFileSearch findConfigFile(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs, + StringRef ToolName); + +/// \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. +/// +void reportConfigFileSearchError(CfgFileSearch 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,149 @@ 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; +} + +CfgFileSearch cl::findConfigFile(SmallVectorImpl &CfgFileName, + SmallVectorImpl &Argv, + ArrayRef Dirs, + StringRef ToolName) { + assert(!Argv.empty()); + StringRef ProgramFullPath = Argv[0]; + CfgFileName.clear(); + std::string EnvVariable = ToolName.upper() + "CFG"; + bool SpecifiedByEnvVar = false; + + // 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()) { + if (CfgOption + 1 == Argv.end()) + return CfgFileSearch::NoArgument; + + 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 full name of + // 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 directories: + // - ~/.llvm + // - /etc/llvm + if (findFileInDirectories(CfgFileName, CfgFile, Dirs)) + return CfgFileSearch::Successful; + CfgFileName.clear(); + CfgFileName.append(CfgFile.begin(), CfgFile.end()); + return CfgFileSearch::NotFoundCfg; + } + + CfgFileName.append(CfgFile.begin(), CfgFile.end()); + } else if (auto CfgPath = sys::Process::GetEnv(EnvVariable)) { + CfgFileName.append(CfgPath->begin(), CfgPath->end()); + // Do not issue a message if the environmental variable is set to empty + // string, consider this as a way to turn default configuration off. + if (CfgFileName.empty()) + return CfgFileSearch::Ignored; + SpecifiedByEnvVar = true; + } else { + // Search for default configuration file. + std::string FileName = (ToolName + ".cfg").str(); + + // First try searching the same directories as for named configurations. + if (findFileInDirectories(CfgFileName, FileName, Dirs)) + return CfgFileSearch::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 CfgFileSearch::Successful; + return CfgFileSearch::Ignored; + } + + // Configuration was specified by full path. + assert(!CfgFileName.empty()); + if (!llvm::sys::fs::is_regular_file(CfgFileName)) + return SpecifiedByEnvVar ? CfgFileSearch::NotFoundEnv + : CfgFileSearch::NotFoundOpt; + + return CfgFileSearch::Successful; +} + + +void cl::reportConfigFileSearchError(CfgFileSearch Res, + StringRef CfgFile, + ArrayRef Dirs, + StringRef ProgramFullPath) { + switch (Res) { + case CfgFileSearch::Successful: + case CfgFileSearch::Ignored: + return; + case CfgFileSearch::NoArgument: + errs() << ProgramFullPath << + ": CommandLine Error: Option '--config' must be followed by " + "configuration name or full path to configuration file\n"; + break; + case CfgFileSearch::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 CfgFileSearch::NotFoundOpt: + errs() << ProgramFullPath << + ": CommandLine Error: Configuration file '" << CfgFile << "' specified " + "by option '--config' cannot be found\n"; + break; + case CfgFileSearch::NotFoundEnv: + errs() << ProgramFullPath << + ": CommandLine Error: Configuration file '" << CfgFile << "' specified " + "by environment variable cannot be found\n"; + break; + } + exit(1); +} + +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 +1121,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,210 @@ EXPECT_FALSE(cl::ParseCommandLineOptions(3, args2, nullptr, true)); } +TEST(CommandLineTest, ConfigFileErrors) { + llvm::SmallString<128> CfgFileName; + llvm::SmallVector Argv = { "test/test" }; + ArrayRef Dirs; + cl::CfgFileSearch Result; + + // Config in not specified, default is absent. + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + EXPECT_TRUE(Result == cl::CfgFileSearch::Ignored); + + // Missed argument of --config + Argv = { "--config" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + EXPECT_TRUE(Result == cl::CfgFileSearch::NoArgument); + + // Inexistent file specified by full path. + Argv = { "--config", "/abcd.cfg" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + EXPECT_TRUE(Result == cl::CfgFileSearch::NotFoundOpt); + EXPECT_TRUE(Argv.empty()); + EXPECT_TRUE(CfgFileName.equals("/abcd.cfg")); + + // Inexistent configuration. + Argv = { "--config", "abcd.cfg" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + EXPECT_TRUE(Result == cl::CfgFileSearch::NotFoundCfg); + EXPECT_TRUE(Argv.empty()); + EXPECT_TRUE(CfgFileName.equals("abcd.cfg")); + + // Inexistent configuration< configuration without extension. + Argv = { "--config", "abcd" }; + Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + EXPECT_TRUE(Result == cl::CfgFileSearch::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::CfgFileSearch 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::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + + EXPECT_TRUE(Result == cl::CfgFileSearch::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::findConfigFile(CfgFileName, Argv, Dirs, "Test"); + + EXPECT_TRUE(Result == cl::CfgFileSearch::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::CfgFileSearch Result = cl::findConfigFile(CfgFileName, Argv, + ArrayRef(), "test"); + + EXPECT_TRUE(Result == cl::CfgFileSearch::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::CfgFileSearch Result = cl::findConfigFile(CfgFileName, Argv, Dirs, "test"); + + EXPECT_TRUE(Result == cl::CfgFileSearch::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