From aa00e96a849cf0953db5f3ad519b6cb4447e0ff0 Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Sat, 30 Apr 2016 21:40:04 +0000 Subject: [PATCH] ELF: Make --reproduce to produce a response file. The aim of this patch is to make it easy to re-run the command without updating paths in the command line. Here is a use case. Assume that Alice is having an issue with lld and is reporting the issue to developer Bob. Alice's current directly is /home/alice/work and her command line is "ld.lld -o foo foo.o ../bar.o". She adds "--reproduce repro" to the command line and re-run. Then the following text will be produced as response.txt (notice that the paths are rewritten so that they are relative to /home/alice/work/repro.) -o home/alice/work/foo home/alice/work/foo.o home/alice/bar.o The command also produces the following files by copying inputs. /home/alice/repro/home/alice/work/foo.o /home/alice/repro/home/alice/bar.o Alice zips the directory and send it to Bob. Bob get an archive from Alice and extract it to his home directory as /home/bob/repro. Now his directory have the following files. /home/bob/repro/response.txt /home/bob/repro/home/alice/work/foo.o /home/bob/repro/home/alice/bar.o Bob then re-run the command with these files by the following commands. cd /home/bob/repro ld.lld @response.txt This command will run the linker with the same command line options and the same input files as Alice's, so it is very likely that Bob will see the same issue as Alice saw. Differential Revision: http://reviews.llvm.org/D19737 llvm-svn: 268169 --- lld/ELF/Driver.cpp | 23 +----------- lld/ELF/Driver.h | 4 +-- lld/ELF/DriverUtils.cpp | 94 +++++++++++++++++++++++++++++++++++++++--------- lld/test/ELF/reproduce.s | 16 ++++++--- 4 files changed, 92 insertions(+), 45 deletions(-) diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index 8198fb8..eb3eee5 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -109,8 +109,6 @@ void LinkerDriver::addFile(StringRef Path) { using namespace llvm::sys::fs; if (Config->Verbose) llvm::outs() << Path << "\n"; - if (!Config->Reproduce.empty()) - copyFile(Path, concat_paths(Config->Reproduce, Path)); Optional Buffer = readFile(Path); if (!Buffer.hasValue()) @@ -238,25 +236,6 @@ static bool hasZOption(opt::InputArgList &Args, StringRef Key) { return false; } -static void logCommandline(ArrayRef 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 ArgsArr) { ELFOptTable Parser; opt::InputArgList Args = Parser.parse(ArgsArr.slice(1)); @@ -273,7 +252,7 @@ void LinkerDriver::main(ArrayRef ArgsArr) { initLLVM(Args); if (!Config->Reproduce.empty()) - logCommandline(ArgsArr); + saveLinkerInputs(Args); createFiles(Args); checkOptions(Args); diff --git a/lld/ELF/Driver.h b/lld/ELF/Driver.h index f754147..7265de4 100644 --- a/lld/ELF/Driver.h +++ b/lld/ELF/Driver.h @@ -68,9 +68,7 @@ enum { void printHelp(const char *Argv0); void printVersion(); -std::string concat_paths(StringRef S, StringRef T); -void copyFile(StringRef Src, StringRef Dest); - +void saveLinkerInputs(const llvm::opt::InputArgList &Args); std::string findFromSearchPaths(StringRef Path); std::string searchLibrary(StringRef Path); std::string buildSysrootedPath(llvm::StringRef Dir, llvm::StringRef File); diff --git a/lld/ELF/DriverUtils.cpp b/lld/ELF/DriverUtils.cpp index 3953d5f..57dfea3 100644 --- a/lld/ELF/DriverUtils.cpp +++ b/lld/ELF/DriverUtils.cpp @@ -87,31 +87,93 @@ void elf::printVersion() { 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) { diff --git a/lld/test/ELF/reproduce.s b/lld/test/ELF/reproduce.s index 566678f..e182f06f 100644 --- a/lld/test/ELF/reproduce.s +++ b/lld/test/ELF/reproduce.s @@ -5,16 +5,24 @@ # 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 -- 2.7.4