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 @@ -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; @@ -829,12 +833,14 @@ 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"})); + EXPECT_THAT( + Argv, testing::Pointwise( + StringEquality(), + {"test/test", "-flag_1", "-option_1", "-option_2", "-option_21", + "-option_22", "-option_23=abcd", "-option_24=/incdir", + "-option_3=abcd", "-option_31", "-option_32", + "-option_33=abcd", "-option_34=/incdir", "-option_4=efjk", + "-option_5=/", "-option_6=/sub", "-flag_2"})); } TEST(CommandLineTest, RecursiveResponseFiles) { @@ -1045,15 +1051,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 +1079,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) {