using namespace llvm::sys::fs;
if (Config->Verbose)
llvm::outs() << Path << "\n";
- if (!Config->Reproduce.empty())
- copyFile(Path, concat_paths(Config->Reproduce, Path));
Optional<MemoryBufferRef> Buffer = readFile(Path);
if (!Buffer.hasValue())
return false;
}
-static void logCommandline(ArrayRef<const char *> Args) {
- if (std::error_code EC = sys::fs::create_directories(
- Config->Reproduce, /*IgnoreExisting=*/false)) {
- error(EC, Config->Reproduce + ": can't create directory");
- return;
- }
-
- SmallString<128> Path;
- path::append(Path, Config->Reproduce, "invocation.txt");
- std::error_code EC;
- raw_fd_ostream OS(Path, EC, sys::fs::OpenFlags::F_None);
- check(EC);
-
- OS << Args[0];
- for (size_t I = 1, E = Args.size(); I < E; ++I)
- OS << " " << Args[I];
- OS << "\n";
-}
-
void LinkerDriver::main(ArrayRef<const char *> ArgsArr) {
ELFOptTable Parser;
opt::InputArgList Args = Parser.parse(ArgsArr.slice(1));
initLLVM(Args);
if (!Config->Reproduce.empty())
- logCommandline(ArgsArr);
+ saveLinkerInputs(Args);
createFiles(Args);
checkOptions(Args);
outs() << "\n";
}
-// Concatenates S and T so that the resulting path becomes S/T.
-// There are a few exceptions:
-//
-// 1. The result will never escape from S. Therefore, all ".."
-// are removed from T before concatenatig them.
-// 2. Windows drive letters are removed from T before concatenation.
-std::string elf::concat_paths(StringRef S, StringRef T) {
- // Remove leading '/' or a drive letter, and then remove "..".
- SmallString<128> T2(path::relative_path(T));
- path::remove_dots(T2, /*remove_dot_dot=*/true);
-
+// Makes a given pathname an absolute path first, and then remove
+// beginning /. For example, "../foo.o" is converted to "home/john/foo.o",
+// assuming that the current directory is "/home/john/bar".
+static std::string relative_to_root(StringRef Path) {
+ SmallString<128> Abs = Path;
+ if (std::error_code EC = fs::make_absolute(Abs))
+ fatal("make_absolute failed: " + EC.message());
+ path::remove_dots(Abs, /*remove_dot_dot=*/true);
+
+ // This is Windows specific. root_name() returns a drive letter
+ // (e.g. "c:") or a UNC name (//net). We want to keep it as part
+ // of the result.
SmallString<128> Res;
- path::append(Res, S, T2);
+ StringRef Root = path::root_name(Path);
+ if (Path.endswith(":"))
+ Res = Root.drop_back();
+ else if (Path.startswith("//"))
+ Res = Root.substr(2);
+
+ path::append(Res, path::relative_path(Abs));
return Res.str();
}
-void elf::copyFile(StringRef Src, StringRef Dest) {
+// Copies file Src to {Config->Reproduce}/Src.
+// Returns the new path relative to Config->Reproduce.
+static std::string copyFile(StringRef Src) {
+ std::string Relpath = relative_to_root(Src);
+ SmallString<128> Dest;
+ path::append(Dest, Config->Reproduce, Relpath);
+
SmallString<128> Dir(Dest);
path::remove_filename(Dir);
- if (std::error_code EC = sys::fs::create_directories(Dir)) {
+ if (std::error_code EC = sys::fs::create_directories(Dir))
error(EC, Dir + ": can't create directory");
- return;
- }
if (std::error_code EC = sys::fs::copy_file(Src, Dest))
error(EC, "failed to copy file: " + Dest);
+ return Relpath;
+}
+
+// Quote a given string if it contains a space character.
+static std::string quote(StringRef S) {
+ if (S.find(' ') == StringRef::npos)
+ return S;
+ return ("\"" + S + "\"").str();
+}
+
+// Copies all input files to Config->Reproduce directory and
+// create a response file as "response.txt", so that you can re-run
+// the same command with the same inputs just by executing
+// "ld.lld @response.txt". Used by --reproduce. This feature is
+// supposed to be used by users to report an issue to LLD developers.
+void elf::saveLinkerInputs(const llvm::opt::InputArgList &Args) {
+ // Create the output directory.
+ if (std::error_code EC = sys::fs::create_directories(
+ Config->Reproduce, /*IgnoreExisting=*/false)) {
+ error(EC, Config->Reproduce + ": can't create directory");
+ return;
+ }
+
+ // Open "response.txt".
+ SmallString<128> Path;
+ path::append(Path, Config->Reproduce, "response.txt");
+ std::error_code EC;
+ raw_fd_ostream OS(Path, EC, sys::fs::OpenFlags::F_None);
+ check(EC);
+
+ // Dump the command line to response.txt while copying files
+ // and rewriting paths.
+ for (auto *Arg : Args) {
+ switch (Arg->getOption().getID()) {
+ case OPT_reproduce:
+ break;
+ case OPT_script:
+ OS << "--script ";
+ // fallthrough
+ case OPT_INPUT: {
+ StringRef Path = Arg->getValue();
+ if (fs::exists(Path))
+ OS << quote(copyFile(Path)) << "\n";
+ else
+ OS << quote(Path) << "\n";
+ break;
+ }
+ default:
+ OS << Arg->getAsString(Args) << "\n";
+ }
+ }
}
std::string elf::findFromSearchPaths(StringRef Path) {
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.dir/build1/foo.o
# RUN: cd %t.dir
# RUN: ld.lld build1/foo.o -o bar -shared --as-needed --reproduce repro
-# RUN: diff build1/foo.o repro/build1/foo.o
+# RUN: diff build1/foo.o repro/%:t.dir/build1/foo.o
-# RUN: FileCheck %s --check-prefix=INVOCATION < repro/invocation.txt
-# INVOCATION: lld{{[^\s]*}} build1/foo.o -o bar -shared --as-needed --reproduce repro
+# RUN: FileCheck %s --check-prefix=RSP < repro/response.txt
+# RSP: {{.*}}foo.o
+# RSP-NEXT: -o bar
+# RSP-NEXT: -shared
+# RSP-NEXT: --as-needed
# RUN: mkdir -p %t.dir/build2/a/b/c
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.dir/build2/foo.o
# RUN: cd %t.dir/build2/a/b/c
# RUN: ld.lld ./../../../foo.o -o bar -shared --as-needed --reproduce repro
-# RUN: diff %t.dir/build2/foo.o repro/foo.o
+# RUN: diff %t.dir/build2/foo.o repro/%:t.dir/build2/foo.o
+
+# RUN: not ld.lld build1/foo.o --reproduce repro2 'foo bar' -soname=foo
+# RUN: FileCheck %s --check-prefix=RSP2 < repro2/response.txt
+# RSP2: "foo bar"
+# RSP2-NEXT: -soname=foo
# RUN: not ld.lld build1/foo.o -o bar -shared --as-needed --reproduce . 2>&1 \
# RUN: | FileCheck --check-prefix=ERROR %s