[lld][COFF] Add /linkreprofullpathrsp flag (#174971)

This patch adds the /linkreprofullpathrsp flag with the same behaviour
as link.exe. This flag emits a file containing the full paths to each
object passed to the link line.

This is used in particular when linking Arm64X binaries, as you need the
full path to all the Arm64 objects that were used in a standard Arm64
build.

See:

https://learn.microsoft.com/en-us/cpp/build/reference/link-repro-full-path-rsp
for the Microsoft documentation of the flag.

Relands #165449
This commit is contained in:
David Truby 2026-01-08 14:35:47 +01:00 committed by GitHub
parent 476ad9f03c
commit 0f3ba5a208
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 105 additions and 13 deletions

View File

@ -340,7 +340,26 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
}
}
void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
void LinkerDriver::handleReproFile(StringRef path, InputOpt inputOpt) {
if (!reproFile)
return;
*reproFile << '"';
if (inputOpt == InputOpt::DefaultLib)
*reproFile << "/defaultlib:";
else if (inputOpt == InputOpt::WholeArchive)
*reproFile << "/wholearchive:";
SmallString<128> absPath = path;
std::error_code ec = sys::fs::make_absolute(absPath);
if (ec)
Err(ctx) << "cannot find absolute path for reproFile for " << absPath
<< ": " << ec.message();
sys::path::remove_dots(absPath, true);
*reproFile << absPath << "\"\n";
}
void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
auto future = std::make_shared<std::future<MBErrPair>>(
createFutureForFile(std::string(path)));
std::string pathStr = std::string(path);
@ -378,8 +397,11 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
Err(ctx) << msg;
else
Err(ctx) << msg << "; did you mean '" << nearest << "'";
} else
ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy);
} else {
handleReproFile(pathStr, inputOpt);
ctx.driver.addBuffer(std::move(mb), inputOpt == InputOpt::WholeArchive,
lazy);
}
});
}
@ -428,10 +450,9 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
StringRef parentName) {
auto reportBufferError = [=](Error &&e) {
StringRef childName =
CHECK(c.getName(),
"could not get child name for archive " + parentName +
" while loading symbol " + toCOFFString(ctx, sym));
StringRef childName = CHECK(
c.getName(), "could not get child name for archive " + parentName +
" while loading symbol " + toCOFFString(ctx, sym));
Fatal(ctx) << "could not get the buffer for the member defining symbol "
<< &sym << ": " << parentName << "(" << childName
<< "): " << std::move(e);
@ -537,7 +558,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
break;
case OPT_defaultlib:
if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
enqueuePath(*path, false, false);
enqueuePath(*path, false, InputOpt::DefaultLib);
break;
case OPT_entry:
if (!arg->getValue()[0])
@ -1638,6 +1659,15 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
}
}
}
// Handle /linkreprofullpathrsp
if (auto *arg = args.getLastArg(OPT_linkreprofullpathrsp)) {
std::error_code ec;
reproFile = std::make_unique<raw_fd_ostream>(arg->getValue(), ec);
if (ec) {
Err(ctx) << "cannot open " << arg->getValue() << ": " << ec.message();
reproFile.reset();
}
}
if (!args.hasArg(OPT_INPUT, OPT_wholearchive_file)) {
if (args.hasArg(OPT_deffile))
@ -2280,11 +2310,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
break;
case OPT_wholearchive_file:
if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
enqueuePath(*path, true, inLib);
enqueuePath(*path, inLib, InputOpt::WholeArchive);
break;
case OPT_INPUT:
if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
enqueuePath(*path, isWholeArchive(*path), inLib);
enqueuePath(*path, inLib,
isWholeArchive(*path) ? InputOpt::WholeArchive
: InputOpt::None);
break;
default:
// Ignore other options.
@ -2324,7 +2356,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
// addWinSysRootLibSearchPaths(), which is why they are in a separate loop.
for (auto *arg : args.filtered(OPT_defaultlib))
if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
enqueuePath(*path, false, false);
enqueuePath(*path, false, InputOpt::DefaultLib);
run();
if (errorCount())
return;
@ -2890,6 +2922,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (config->showTiming)
ctx.rootTimer.print();
// Clean up /linkreprofullpathrsp file
reproFile.reset();
if (config->timeTraceEnabled) {
// Manually stop the topmost "COFF link" scope, since we're shutting down.
timeTraceProfilerEnd();

View File

@ -88,11 +88,13 @@ public:
void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym,
StringRef parentName);
void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); }
enum class InputOpt { None, DefaultLib, WholeArchive };
void enqueuePDB(StringRef Path) { enqueuePath(Path, false); }
MemoryBufferRef takeBuffer(std::unique_ptr<MemoryBuffer> mb);
void enqueuePath(StringRef path, bool wholeArchive, bool lazy);
void enqueuePath(StringRef path, bool lazy,
InputOpt inputOpt = InputOpt::None);
// Returns a list of chunks of selected symbols.
std::vector<Chunk *> getChunks() const;
@ -138,6 +140,10 @@ private:
//
std::string getImportName(bool asLib);
// Write fullly resolved path to repro file if /linkreprofullpathrsp
// is specified.
void handleReproFile(StringRef path, InputOpt inputOpt);
void createImportLibrary(bool asLib);
// Used by the resolver to parse .drectve section contents.
@ -193,6 +199,9 @@ private:
int sdkMajor = 0;
llvm::SmallString<128> windowsSdkLibPath;
// For linkreprofullpathrsp
std::unique_ptr<llvm::raw_fd_ostream> reproFile;
// Functions below this line are defined in DriverUtils.cpp.
void printHelp(const char *argv0);

View File

@ -75,6 +75,9 @@ def link : F<"link">, HelpText<"Ignored for compatibility">;
def linkrepro : Joined<["/", "-", "/?", "-?"], "linkrepro:">,
MetaVarName<"directory">,
HelpText<"Write repro.tar containing inputs and command to reproduce link">;
def linkreprofullpathrsp : Joined<["/", "-", "/?", "-?"], "linkreprofullpathrsp:">,
MetaVarName<"directory">,
HelpText<"Write .rsp file containing inputs used to link with full paths">;
def lldignoreenv : F<"lldignoreenv">,
HelpText<"Ignore environment variables like %LIB%">;
def lldltocache : P<"lldltocache",

View File

@ -40,6 +40,8 @@ COFF Improvements
* ``/fat-lto-objects`` addded to support FatLTO. Without ``/fat-lto-objects`` or with ``/fat-lto-objects:no``, LLD will link LLVM FatLTO objects using the relocatable object file.
(`#165529 <https://github.com/llvm/llvm-project/pull/165529>`_)
* ``/linkreprofullpathrsp`` prints the full path to each object passed to the link line to a file.
(`#174971 <https://github.com/llvm/llvm-project/pull/165449>`_)
MinGW Improvements
------------------

View File

@ -0,0 +1,43 @@
# REQUIRES: x86
# RUN: rm -rf %t.dir %t.obj
# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj
# RUN: yaml2obj %p/Inputs/empty.yaml -o %t.archive.obj
# RUN: rm -f %t.archive.lib
# RUN: llvm-ar rcs %t.archive.lib %t.archive.obj
# RUN: llvm-pdbutil yaml2pdb %S/Inputs/pdb-type-server-simple-ts.yaml -pdb %t.pdb
Test link.exe-style /linkreprofullpathrsp: flag.
# RUN: mkdir -p %t.dir/build1
# RUN: cd %t.dir/build1
# RUN: lld-link /subsystem:console %t.obj %p/Inputs/std32.lib /defaultlib:%p/Inputs/library.lib \
# RUN: /libpath:%p/Inputs /defaultlib:std64.lib ret42.lib /entry:main@0 /linkreprofullpathrsp:%t.rsp \
# RUN: %t.pdb /wholearchive:%t.archive.lib /out:%t.exe /timestamp:0
# # RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp
# RUN: lld-link /subsystem:console @%t.rsp /out:%t2.exe /entry:main@0 /timestamp:0
# RUN: diff %t.exe %t2.exe
# RSP: "[[T]].obj"
# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}std32.lib"
# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}ret42.lib"
# RSP-NEXT: "[[T]].pdb"
# RSP-NEXT: "/wholearchive:[[T]].archive.lib"
# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}library.lib"
# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}std64.lib"
#--- drectve.s
.section .drectve, "yn"
.ascii "/defaultlib:std32"
#--- archive.s
.text
.intel_syntax noprefix
.globl exportfn3
.p2align 4
exportfn3:
ret
.section .drectve,"yni"
.ascii " /EXPORT:exportfn3"