Index: ELF/Driver.h =================================================================== --- ELF/Driver.h +++ ELF/Driver.h @@ -68,8 +68,8 @@ void printHelp(const char *Argv0); void printVersion(); -std::string concat_paths(StringRef S, StringRef T); -void copyFile(StringRef Src, StringRef Dest); +void saveFile(StringRef Src); +void logCommandline(ArrayRef Args); std::string findFromSearchPaths(StringRef Path); std::string searchLibrary(StringRef Path); Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -24,6 +24,12 @@ #include "llvm/Support/raw_ostream.h" #include +#ifdef _WIN32 +# include +#else +# include +#endif + using namespace llvm; using namespace llvm::ELF; using namespace llvm::object; @@ -110,7 +116,7 @@ if (Config->Verbose) llvm::outs() << Path << "\n"; if (!Config->Reproduce.empty()) - copyFile(Path, concat_paths(Config->Reproduce, Path)); + saveFile(Path); Optional Buffer = readFile(Path); if (!Buffer.hasValue()) @@ -238,25 +244,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)); @@ -268,6 +255,9 @@ printVersion(); return; } + if (auto *Arg = Args.getLastArg(OPT_directory)) + if (chdir(Arg->getValue())) + error("chdir failed"); readConfigs(Args); initLLVM(Args); Index: ELF/DriverUtils.cpp =================================================================== --- ELF/DriverUtils.cpp +++ ELF/DriverUtils.cpp @@ -93,17 +93,19 @@ // 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); - - SmallString<128> Res; - path::append(Res, S, T2); - return Res.str(); +static std::string concat(StringRef S, StringRef T) { + SmallString<128> Abs = T; + if (std::error_code EC = fs::make_absolute(Abs)) + error(EC, "make_absolute failed"); + path::remove_dots(Abs, /*remove_dot_dot=*/true); + SmallString<128> Dest(S); + path::append(Dest, path::relative_path(Abs)); + return Dest.str(); } -void elf::copyFile(StringRef Src, StringRef Dest) { +// Copies file Src to {Config->Reproduce}/Src. +void elf::saveFile(StringRef Src) { + std::string Dest = concat(Config->Reproduce, Src); SmallString<128> Dir(Dest); path::remove_filename(Dir); if (std::error_code EC = sys::fs::create_directories(Dir)) { @@ -114,6 +116,62 @@ error(EC, "failed to copy file: " + Dest); } +static bool isSafeChar(char C) { + return ('0' <= C && C <= '9') || + ('a' <= C && C <= 'z') || + ('A' <= C && C <= 'Z') || + strchr("/-_.,", C); +} + +// Quote a given string so that it is safe for Unix shell. +// It replaces ' with '\''. +static std::string quote(StringRef S) { + if (std::all_of(S.begin(), S.end(), isSafeChar)) + return S; + std::string Ret = "'"; + for (char C : S) { + if (C == '\'') + Ret += "'\\''"; + else + Ret += C; + } + return Ret + "'"; +} + +// Saves command line arguments to a file. Use by --reproduce. +void elf::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; + } + + // Open the output file. + 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); + + // Get the currrent working directory. + SmallString<128> Cwd; + if (std::error_code EC = fs::current_path(Cwd)) + fatal("sys::fs::current_path failed: " + EC.message()); + + // Write the command line. + OS << "/ld.lld "; + for (size_t I = 1, E = Args.size(); I < E; ++I) { + if (StringRef(Args[I]).startswith("--reproduce=")) + continue; + if (StringRef(Args[I]) == "--reproduce") { + ++I; + continue; + } + OS << quote(Args[I]) << " "; + } + OS << "--directory " << quote(Cwd) << "\n"; +} + std::string elf::findFromSearchPaths(StringRef Path) { for (StringRef Dir : Config->SearchPaths) { std::string FullPath = buildSysrootedPath(Dir, Path); Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -17,6 +17,9 @@ def build_id_eq : Joined<["--", "-"], "build-id=">; +def directory: JoinedOrSeparate<["--", "-"], "directory">, + HelpText<"change to DIR before doing anything else">; + def L : JoinedOrSeparate<["-"], "L">, MetaVarName<"">, HelpText<"Directory to search for libraries">; Index: test/ELF/reproduce.s =================================================================== --- test/ELF/reproduce.s +++ test/ELF/reproduce.s @@ -5,16 +5,20 @@ # 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 +# INVOCATION: build1/foo.o -o bar -shared --as-needed --directory # 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' 'ab\cd' "foo'bar" +# RUN: FileCheck %s --check-prefix=INVOCATION2 < repro2/invocation.txt +# INVOCATION2: 'foo bar' 'ab\cd' 'foo'\''bar' --directory # RUN: not ld.lld build1/foo.o -o bar -shared --as-needed --reproduce . 2>&1 \ # RUN: | FileCheck --check-prefix=ERROR %s