Index: ELF/Driver.h =================================================================== --- ELF/Driver.h +++ ELF/Driver.h @@ -68,9 +68,7 @@ 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); Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -109,8 +109,6 @@ 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 @@ 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 @@ initLLVM(Args); if (!Config->Reproduce.empty()) - logCommandline(ArgsArr); + saveLinkerInputs(Args); createFiles(Args); checkOptions(Args); Index: ELF/DriverUtils.cpp =================================================================== --- ELF/DriverUtils.cpp +++ ELF/DriverUtils.cpp @@ -87,31 +87,98 @@ 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 / (or \ or a drive letter on Windows). 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. +// Note that this quoting scheme is not perfect because space +// is not the only metacharacter in response files, but it's +// okay because we don't want to have a complicated logic +// for a debugging feature. +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) { Index: test/ELF/reproduce.s =================================================================== --- test/ELF/reproduce.s +++ 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