[lld] [lld][COFF] Add /linkreprofullpathrsp flag (PR #165449)
David Truby via llvm-commits
llvm-commits at lists.llvm.org
Mon Nov 3 07:15:59 PST 2025
https://github.com/DavidTruby updated https://github.com/llvm/llvm-project/pull/165449
>From e49faac40e1d3faf382b5c8d343ee3cebcac97c5 Mon Sep 17 00:00:00 2001
From: David Truby <david at truby.dev>
Date: Tue, 28 Oct 2025 14:25:01 +0000
Subject: [PATCH 1/2] [lld][COFF] Add /linkreprofullpathrsp flag
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.
---
lld/COFF/Driver.cpp | 33 +++++++++++++++++----
lld/COFF/Driver.h | 5 ++--
lld/COFF/Options.td | 3 ++
lld/test/COFF/linkreprofullpathrsp.test | 38 +++++++++++++++++++++++++
4 files changed, 71 insertions(+), 8 deletions(-)
create mode 100644 lld/test/COFF/linkreprofullpathrsp.test
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 0e528de9c3652..e2ad2f1c1e993 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -318,7 +318,8 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
}
}
-void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
+void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy,
+ llvm::raw_ostream *reproFile, bool defaultlib) {
auto future = std::make_shared<std::future<MBErrPair>>(
createFutureForFile(std::string(path)));
std::string pathStr = std::string(path);
@@ -356,8 +357,17 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
Err(ctx) << msg;
else
Err(ctx) << msg << "; did you mean '" << nearest << "'";
- } else
+ } else {
+ // Write full path to library to repro file if /linkreprofullpathrsp
+ // is specified.
+ if (reproFile) {
+ *reproFile << '"';
+ if (defaultlib)
+ *reproFile << "/defaultlib:";
+ *reproFile << pathStr << "\"\n";
+ }
ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy);
+ }
});
}
@@ -514,7 +524,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, false, nullptr);
break;
case OPT_entry:
if (!arg->getValue()[0])
@@ -2204,6 +2214,17 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
config->incremental = false;
}
+ // Handle /linkreprofullpathrsp.
+ std::unique_ptr<llvm::raw_ostream> reproFile;
+ if (auto *arg = args.getLastArg(OPT_linkreprofullpathrsp)) {
+ std::error_code ec;
+ reproFile = std::make_unique<llvm::raw_fd_ostream>(arg->getValue(), ec);
+ if (ec) {
+ Err(ctx) << "cannot open " << arg->getValue() << ": " << ec.message();
+ reproFile.reset();
+ }
+ }
+
if (errCount(ctx))
return;
@@ -2245,11 +2266,11 @@ 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, true, inLib, reproFile.get());
break;
case OPT_INPUT:
if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
- enqueuePath(*path, isWholeArchive(*path), inLib);
+ enqueuePath(*path, isWholeArchive(*path), inLib, reproFile.get());
break;
default:
// Ignore other options.
@@ -2289,7 +2310,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, false, reproFile.get(), true);
run();
if (errorCount())
return;
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 14710d5853bcf..f4b72a317d077 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -88,11 +88,12 @@ class LinkerDriver {
void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym,
StringRef parentName);
- void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); }
+ void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false, nullptr); }
MemoryBufferRef takeBuffer(std::unique_ptr<MemoryBuffer> mb);
- void enqueuePath(StringRef path, bool wholeArchive, bool lazy);
+ void enqueuePath(StringRef path, bool wholeArchive, bool lazy,
+ raw_ostream *reproFile, bool defaultlib = false);
// Returns a list of chunks of selected symbols.
std::vector<Chunk *> getChunks() const;
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index d77478fc9c987..dca8a721dc4fd 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -74,6 +74,9 @@ def libpath : P<"libpath", "Additional library search path">;
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",
diff --git a/lld/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test
new file mode 100644
index 0000000000000..c77f09eae9dcd
--- /dev/null
+++ b/lld/test/COFF/linkreprofullpathrsp.test
@@ -0,0 +1,38 @@
+# REQUIRES: x86
+# Unsupported on Windows due to maximum path length limitations.
+
+# RUN: rm -rf %t.dir
+# RUN: split-file %s %t.dir
+# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj
+# RUN: llvm-mc -filetype=obj -triple=i386-windows %t.dir/drectve.s -o %t.dir/drectve.obj
+# RUN: echo '_main at 0' > %t.order
+# RUN: touch %t.def
+# RUN: touch %t.cg
+
+
+
+Test link.exe-style /linkreprofullpathrsp: flag.
+# RUN: mkdir -p %t.dir/build1
+# RUN: cd %t.dir/build1
+# RUN: lld-link %t.obj %p/Inputs/std32.lib /subsystem:console /defaultlib:%p/Inputs/library.lib \
+# RUN: /entry:main at 0 /linkreprofullpathrsp:%t.rsp /out:%t.exe
+# RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp
+
+# RSP: [[T]].obj
+# RSP-NEXT: "[[P]]/Inputs/std32.lib"
+# RSP-NEXT: "/defaultlib:[[P]]/Inputs/library.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"
>From f34b707062d1129f66da4387bb250a3d0407acf1 Mon Sep 17 00:00:00 2001
From: David Truby <david at truby.dev>
Date: Mon, 3 Nov 2025 15:08:11 +0000
Subject: [PATCH 2/2] Actually find the full path
---
lld/COFF/Driver.cpp | 23 +++++++++++++++++------
lld/test/COFF/linkreprofullpathrsp.test | 14 +++++---------
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index e2ad2f1c1e993..c26955fbbfe5b 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -318,6 +318,20 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
}
}
+static void handleReproFile(COFFLinkerContext &ctx, raw_ostream &reproFile,
+ StringRef path, bool defaultlib) {
+ reproFile << '"';
+ if (defaultlib)
+ reproFile << "/defaultlib:";
+ 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 wholeArchive, bool lazy,
llvm::raw_ostream *reproFile, bool defaultlib) {
auto future = std::make_shared<std::future<MBErrPair>>(
@@ -360,12 +374,9 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy,
} else {
// Write full path to library to repro file if /linkreprofullpathrsp
// is specified.
- if (reproFile) {
- *reproFile << '"';
- if (defaultlib)
- *reproFile << "/defaultlib:";
- *reproFile << pathStr << "\"\n";
- }
+ if (reproFile)
+ handleReproFile(ctx, *reproFile, pathStr, defaultlib);
+
ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy);
}
});
diff --git a/lld/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test
index c77f09eae9dcd..5e09eff273a2b 100644
--- a/lld/test/COFF/linkreprofullpathrsp.test
+++ b/lld/test/COFF/linkreprofullpathrsp.test
@@ -1,26 +1,22 @@
# REQUIRES: x86
# Unsupported on Windows due to maximum path length limitations.
+# UNSUPPORTED: system-windows
# RUN: rm -rf %t.dir
-# RUN: split-file %s %t.dir
-# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj
-# RUN: llvm-mc -filetype=obj -triple=i386-windows %t.dir/drectve.s -o %t.dir/drectve.obj
-# RUN: echo '_main at 0' > %t.order
-# RUN: touch %t.def
-# RUN: touch %t.cg
-
-
Test link.exe-style /linkreprofullpathrsp: flag.
# RUN: mkdir -p %t.dir/build1
# RUN: cd %t.dir/build1
# RUN: lld-link %t.obj %p/Inputs/std32.lib /subsystem:console /defaultlib:%p/Inputs/library.lib \
-# RUN: /entry:main at 0 /linkreprofullpathrsp:%t.rsp /out:%t.exe
+# RUN: /libpath:%p/Inputs /defaultlib:std64.lib ret42.lib /entry:main at 0 /linkreprofullpathrsp:%t.rsp \
+# RUN: /out:%t.exe
# RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp
# RSP: [[T]].obj
# RSP-NEXT: "[[P]]/Inputs/std32.lib"
+# RSP-NEXT: "[[P]]/Inputs/ret42.lib"
# RSP-NEXT: "/defaultlib:[[P]]/Inputs/library.lib"
+# RSP-NEXT: "/defaultlib:[[P]]/Inputs/std64.lib"
#--- drectve.s
.section .drectve, "yn"
More information about the llvm-commits
mailing list