diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -63,6 +63,9 @@ - Maximum _ExtInt size was decreased from 16,777,215 bits to 8,388,608 bits. Motivation for this was discussed in PR51829. +- Configuration file syntax extended with ```` token. This expands to + the base path of the current config file. See :ref:`configuration-files` for + details. New Compiler Flags ------------------ diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -843,6 +843,8 @@ option tells Clang to put double-quotes around the entire filename, which is the convention used by NMake and Jom. +.. _configuration-files: + Configuration files ------------------- @@ -917,6 +919,22 @@ `~/.llvm/target.cfg` contains the directive `@os/linux.opts`, the file `linux.opts` is searched for in the directory `~/.llvm/os`. +To generate paths relative to the configuration file, the `` token may +be used. This will expand to the absolute path of the directory containing the +configuration file. + +A potential `` use-case may be search paths in a portable (i.e. not +installed) SDK directory for embedded development. The user may only need to +specify a root configuration file to establish every aspect of the SDK with the +compiler: + +:: + + --target=foo + -isystem /include + -L /lib + -T /ldscripts/link.ld + Language and Target-Independent Features ======================================== diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h --- a/llvm/include/llvm/Support/CommandLine.h +++ b/llvm/include/llvm/Support/CommandLine.h @@ -2103,12 +2103,15 @@ /// \param [in] FS File system used for all file access when running the tool. /// \param [in] CurrentDir Path used to resolve relative rsp files. If set to /// None, process' cwd is used instead. +/// \param [in] ExpandBasePathToken If not empty, token which expands to the +/// base path of the current response file. /// \return true if all @files were expanded successfully or there were none. bool ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer, SmallVectorImpl &Argv, bool MarkEOLs, bool RelativeNames, llvm::Optional CurrentDir, - llvm::vfs::FileSystem &FS); + llvm::vfs::FileSystem &FS, + StringRef ExpandBasePathToken = {}); /// An overload of ExpandResponseFiles() that uses /// llvm::vfs::getRealFileSystem(). @@ -2116,7 +2119,8 @@ StringSaver &Saver, TokenizerCallback Tokenizer, SmallVectorImpl &Argv, bool MarkEOLs = false, bool RelativeNames = false, - llvm::Optional CurrentDir = llvm::None); + llvm::Optional CurrentDir = llvm::None, + StringRef ExpandBasePathToken = {}); /// A convenience helper which concatenates the options specified by the /// environment variable EnvVar and command line options, then expands response diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp --- a/llvm/lib/Support/CommandLine.cpp +++ b/llvm/lib/Support/CommandLine.cpp @@ -1078,11 +1078,44 @@ return (S.size() >= 3 && S[0] == '\xef' && S[1] == '\xbb' && S[2] == '\xbf'); } +// Substitute token with the file's base path. +static void ExpandBasePaths(StringRef BasePath, StringRef Token, + StringSaver &Saver, const char *&Arg) { + assert(sys::path::is_absolute(BasePath)); + const StringRef ArgString(Arg); + + SmallString<128> ResponseFile; + StringRef::size_type StartPos = 0; + for (StringRef::size_type TokenPos = ArgString.find(Token); + TokenPos != StringRef::npos; + TokenPos = ArgString.find(Token, StartPos)) { + // Token may appear more than once per arg (e.g. comma-separated linker + // args). Support by using path-append on any subsequent appearances. + const StringRef LHS = ArgString.substr(StartPos, TokenPos - StartPos); + if (ResponseFile.empty()) + ResponseFile = LHS; + else + llvm::sys::path::append(ResponseFile, LHS); + ResponseFile.append(BasePath); + StartPos = TokenPos + Token.size(); + } + + if (!ResponseFile.empty()) { + // Path-append the remaining arg substring if at least one token appeared. + const StringRef Remaining = ArgString.substr(StartPos); + if (!Remaining.empty()) + llvm::sys::path::append(ResponseFile, Remaining); + Arg = Saver.save(ResponseFile.str()).data(); + } +} + // FName must be an absolute path. -static llvm::Error ExpandResponseFile( - StringRef FName, StringSaver &Saver, TokenizerCallback Tokenizer, - SmallVectorImpl &NewArgv, bool MarkEOLs, bool RelativeNames, - llvm::vfs::FileSystem &FS) { +static llvm::Error ExpandResponseFile(StringRef FName, StringSaver &Saver, + TokenizerCallback Tokenizer, + SmallVectorImpl &NewArgv, + bool MarkEOLs, bool RelativeNames, + llvm::vfs::FileSystem &FS, + StringRef ExpandBasePathToken) { assert(sys::path::is_absolute(FName)); llvm::ErrorOr> MemBufOrErr = FS.getBufferForFile(FName); @@ -1116,8 +1149,15 @@ // file, replace the included response file names with their full paths // obtained by required resolution. for (auto &Arg : NewArgv) { + if (!Arg) + continue; + + // Substitute token with the file's base path. + if (!ExpandBasePathToken.empty()) + ExpandBasePaths(BasePath, ExpandBasePathToken, Saver, Arg); + // Skip non-rsp file arguments. - if (!Arg || Arg[0] != '@') + if (Arg[0] != '@') continue; StringRef FileName(Arg + 1); @@ -1129,7 +1169,7 @@ ResponseFile.push_back('@'); ResponseFile.append(BasePath); llvm::sys::path::append(ResponseFile, FileName); - Arg = Saver.save(ResponseFile.c_str()).data(); + Arg = Saver.save(ResponseFile.str()).data(); } return Error::success(); } @@ -1140,7 +1180,8 @@ SmallVectorImpl &Argv, bool MarkEOLs, bool RelativeNames, llvm::Optional CurrentDir, - llvm::vfs::FileSystem &FS) { + llvm::vfs::FileSystem &FS, + StringRef ExpandBasePathToken) { bool AllExpanded = true; struct ResponseFileRecord { std::string File; @@ -1218,7 +1259,7 @@ SmallVector ExpandedArgv; if (llvm::Error Err = ExpandResponseFile(FName, Saver, Tokenizer, ExpandedArgv, MarkEOLs, - RelativeNames, FS)) { + RelativeNames, FS, ExpandBasePathToken)) { // We couldn't read this file, so we leave it in the argument stream and // move on. // TODO: The error should be propagated up the stack. @@ -1251,10 +1292,11 @@ bool cl::ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer, SmallVectorImpl &Argv, bool MarkEOLs, bool RelativeNames, - llvm::Optional CurrentDir) { + llvm::Optional CurrentDir, + StringRef ExpandBasePathToken) { return ExpandResponseFiles(Saver, std::move(Tokenizer), Argv, MarkEOLs, RelativeNames, std::move(CurrentDir), - *vfs::getRealFileSystem()); + *vfs::getRealFileSystem(), ExpandBasePathToken); } bool cl::expandResponseFiles(int Argc, const char *const *Argv, @@ -1281,16 +1323,18 @@ llvm::sys::path::append(AbsPath, CfgFile); CfgFile = AbsPath.str(); } - if (llvm::Error Err = - ExpandResponseFile(CfgFile, Saver, cl::tokenizeConfigFile, Argv, - /*MarkEOLs=*/false, /*RelativeNames=*/true, - *llvm::vfs::getRealFileSystem())) { + constexpr StringLiteral ExpandBasePathToken(""); + if (llvm::Error Err = ExpandResponseFile( + CfgFile, Saver, cl::tokenizeConfigFile, Argv, + /*MarkEOLs=*/false, /*RelativeNames=*/true, + *llvm::vfs::getRealFileSystem(), ExpandBasePathToken)) { // TODO: The error should be propagated up the stack. llvm::consumeError(std::move(Err)); return false; } return ExpandResponseFiles(Saver, cl::tokenizeConfigFile, Argv, - /*MarkEOLs=*/false, /*RelativeNames=*/true); + /*MarkEOLs=*/false, /*RelativeNames=*/true, + llvm::None, ExpandBasePathToken); } static void initCommonOptions(); diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp --- a/llvm/unittests/Support/CommandLineTest.cpp +++ b/llvm/unittests/Support/CommandLineTest.cpp @@ -1038,25 +1038,34 @@ llvm::SmallVector Argv; TempDir TestDir("unittest", /*Unique*/ true); + TempDir TestSubDir(TestDir.path("subdir"), /*Unique*/ false); - llvm::SmallString<128> TestCfg; - llvm::sys::path::append(TestCfg, TestDir.path(), "foo"); - + llvm::SmallString<128> TestCfg = TestDir.path("foo"); TempFile ConfigFile(TestCfg, "", "# Comment\n" "-option_1\n" + "-option_2=/dir1\n" "@subconfig\n" - "-option_3=abcd\n" - "-option_4=\\\n" + "-option_7=abcd\n" + "-option_8=\\\n" "cdef\n"); - llvm::SmallString<128> TestCfg2; - llvm::sys::path::append(TestCfg2, TestDir.path(), "subconfig"); + llvm::SmallString<128> TestCfg2 = TestDir.path("subconfig"); TempFile ConfigFile2(TestCfg2, "", - "-option_2\n" + "-option_3\n" + "-option_4=/dir2\n" + "@subdir/subfoo\n" "\n" " # comment\n"); + llvm::SmallString<128> TestCfg3 = TestSubDir.path("subfoo"); + TempFile ConfigFile3(TestCfg3, "", + "-option_5=/dir3\n" + "@/subfoo2\n"); + + llvm::SmallString<128> TestCfg4 = TestSubDir.path("subfoo2"); + TempFile ConfigFile4(TestCfg4, "", "-option_6=qwerty\n"); + // 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 @@ -1071,11 +1080,18 @@ bool Result = llvm::cl::readConfigFile(ConfigFile.path(), Saver, Argv); EXPECT_TRUE(Result); - EXPECT_EQ(Argv.size(), 4U); + EXPECT_EQ(Argv.size(), 8U); 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"); + EXPECT_STREQ(Argv[1], + ("-option_2=" + TestDir.path() + "/dir1").str().c_str()); + EXPECT_STREQ(Argv[2], "-option_3"); + EXPECT_STREQ(Argv[3], + ("-option_4=" + TestDir.path() + "/dir2").str().c_str()); + EXPECT_STREQ(Argv[4], + ("-option_5=" + TestSubDir.path() + "/dir3").str().c_str()); + EXPECT_STREQ(Argv[5], "-option_6=qwerty"); + EXPECT_STREQ(Argv[6], "-option_7=abcd"); + EXPECT_STREQ(Argv[7], "-option_8=cdef"); } TEST(CommandLineTest, PositionalEatArgsError) {