diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -917,6 +917,18 @@ `~/.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 typical use-case may be a portable, potentially +not-installed cross-compilation SDK directory: + +:: + + --target=sample + -isystem <@>/include + -L <@>/lib + -T <@>/ldscripts/link.ld + Language and Target-Independent Features ======================================== 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,6 +1078,38 @@ return (S.size() >= 3 && S[0] == '\xef' && S[1] == '\xbb' && S[2] == '\xbf'); } +// Substitute <@> with the response file base path. +static void ExpandResponseDirTokens(StringRef BasePath, StringSaver &Saver, + const char *&Arg) { + assert(sys::path::is_absolute(BasePath)); + constexpr StringLiteral Token("<@>"); + 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)) { + // Although it typically does not make sense to use <@> more than once per + // arg, support it 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 <@> 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, @@ -1116,8 +1148,14 @@ // file, replace the included response file names with their full paths // obtained by required resolution. for (auto &Arg : NewArgv) { + if (!Arg) + continue; + + // Substitute <@> with the response file base path. + ExpandResponseDirTokens(BasePath, Saver, Arg); + // Skip non-rsp file arguments. - if (!Arg || Arg[0] != '@') + if (Arg[0] != '@') continue; StringRef FileName(Arg + 1); @@ -1129,7 +1167,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(); } 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 @@ -784,9 +784,9 @@ TEST(CommandLineTest, ResponseFiles) { vfs::InMemoryFileSystem FS; #ifdef _WIN32 - const char *TestRoot = "C:\\"; + StringRef TestRoot = "C:\\"; #else - const char *TestRoot = "/"; + StringRef TestRoot = "/"; #endif FS.setCurrentWorkingDirectory(TestRoot); @@ -796,8 +796,10 @@ llvm::MemoryBuffer::getMemBuffer("-option_1 -option_2\n" "@incdir/resp2\n" "-option_3=abcd\n" - "@incdir/resp3\n" - "-option_4=efjk\n")); + "@<@>/incdir/resp3\n" + "-option_4=efjk\n" + "-option_5=<@>\n" + "-option_6=<@>/sub\n")); // Directory for included file. llvm::StringRef IncDir = "incdir"; @@ -807,14 +809,16 @@ llvm::sys::path::append(IncludedFileName2, IncDir, "resp2"); FS.addFile(IncludedFileName2, 0, MemoryBuffer::getMemBuffer("-option_21 -option_22\n" - "-option_23=abcd\n")); + "-option_23=abcd\n" + "-option_24=<@>\n")); // Create second included response file of second level. llvm::SmallString<128> IncludedFileName3; llvm::sys::path::append(IncludedFileName3, IncDir, "resp3"); FS.addFile(IncludedFileName3, 0, MemoryBuffer::getMemBuffer("-option_31 -option_32\n" - "-option_33=abcd\n")); + "-option_33=abcd\n" + "-option_34=<@>\n")); // Prepare 'file' with reference to response file. SmallString<128> IncRef; @@ -828,13 +832,21 @@ llvm::StringSaver Saver(A); ASSERT_TRUE(llvm::cl::ExpandResponseFiles( Saver, llvm::cl::TokenizeGNUCommandLine, Argv, false, true, - /*CurrentDir=*/StringRef(TestRoot), FS)); - EXPECT_THAT(Argv, testing::Pointwise( - StringEquality(), - {"test/test", "-flag_1", "-option_1", "-option_2", - "-option_21", "-option_22", "-option_23=abcd", - "-option_3=abcd", "-option_31", "-option_32", - "-option_33=abcd", "-option_4=efjk", "-flag_2"})); + /*CurrentDir=*/TestRoot, FS)); + + std::string ExpectedOption24 = ("-option_24=" + TestRoot + "incdir").str(); + std::string ExpectedOption34 = ("-option_34=" + TestRoot + "incdir").str(); + std::string ExpectedOption5 = ("-option_5=" + TestRoot).str(); + std::string ExpectedOption6 = ("-option_6=" + TestRoot + "sub").str(); + + EXPECT_THAT( + Argv, testing::Pointwise( + StringEquality(), + {"test/test", "-flag_1", "-option_1", "-option_2", "-option_21", + "-option_22", "-option_23=abcd", ExpectedOption24.c_str(), + "-option_3=abcd", "-option_31", "-option_32", + "-option_33=abcd", ExpectedOption34.c_str(), "-option_4=efjk", + ExpectedOption5.c_str(), ExpectedOption6.c_str(), "-flag_2"})); } TEST(CommandLineTest, RecursiveResponseFiles) { @@ -1045,15 +1057,17 @@ TempFile ConfigFile(TestCfg, "", "# Comment\n" "-option_1\n" + "-option_2=<@>/dir1\n" "@subconfig\n" - "-option_3=abcd\n" - "-option_4=\\\n" + "-option_5=abcd\n" + "-option_6=\\\n" "cdef\n"); llvm::SmallString<128> TestCfg2; llvm::sys::path::append(TestCfg2, TestDir.path(), "subconfig"); TempFile ConfigFile2(TestCfg2, "", - "-option_2\n" + "-option_3\n" + "-option_4=<@>/dir2\n" "\n" " # comment\n"); @@ -1071,11 +1085,15 @@ bool Result = llvm::cl::readConfigFile(ConfigFile.path(), Saver, Argv); EXPECT_TRUE(Result); - EXPECT_EQ(Argv.size(), 4U); + EXPECT_EQ(Argv.size(), 6U); 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=abcd"); + EXPECT_STREQ(Argv[5], "-option_6=cdef"); } TEST(CommandLineTest, PositionalEatArgsError) {