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
This commit is contained in:
Rui Ueyama
2016-04-30 21:40:04 +00:00
parent 53aa9f2475
commit aa00e96a84
4 changed files with 91 additions and 44 deletions

View File

@@ -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<MemoryBufferRef> Buffer = readFile(Path);
if (!Buffer.hasValue())
@@ -238,25 +236,6 @@ static bool hasZOption(opt::InputArgList &Args, StringRef Key) {
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));
@@ -273,7 +252,7 @@ void LinkerDriver::main(ArrayRef<const char *> ArgsArr) {
initLLVM(Args);
if (!Config->Reproduce.empty())
logCommandline(ArgsArr);
saveLinkerInputs(Args);
createFiles(Args);
checkOptions(Args);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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