diff --git a/lld/Common/Reproduce.cpp b/lld/Common/Reproduce.cpp --- a/lld/Common/Reproduce.cpp +++ b/lld/Common/Reproduce.cpp @@ -54,7 +54,12 @@ std::string k = std::string(arg.getSpelling()); if (arg.getNumValues() == 0) return k; - std::string v = quote(arg.getValue()); + std::string v; + for (size_t i = 0; i < arg.getNumValues(); ++i) { + if (i > 0) + v.push_back(' '); + v += quote(arg.getValue(i)); + } if (arg.getOption().getRenderStyle() == opt::Option::RenderJoinedStyle) return k + v; return k + " " + v; diff --git a/lld/ELF/DriverUtils.cpp b/lld/ELF/DriverUtils.cpp --- a/lld/ELF/DriverUtils.cpp +++ b/lld/ELF/DriverUtils.cpp @@ -184,7 +184,7 @@ // fail because the archive we are creating doesn't contain empty // directories for the output path (-o doesn't create directories). // Strip directories to prevent the issue. - os << "-o " << quote(sys::path::filename(arg->getValue())) << "\n"; + os << "-o " << quote(path::filename(arg->getValue())) << "\n"; break; case OPT_dynamic_list: case OPT_library_path: diff --git a/lld/MachO/Driver.h b/lld/MachO/Driver.h --- a/lld/MachO/Driver.h +++ b/lld/MachO/Driver.h @@ -35,6 +35,8 @@ #undef OPTION }; +std::string createResponseFile(const llvm::opt::InputArgList &args); + // Check for both libfoo.dylib and libfoo.tbd (in that order). llvm::Optional resolveDylibPath(llvm::StringRef path); diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -37,6 +37,7 @@ #include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" +#include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" #include @@ -548,6 +549,12 @@ } } +static const char *getReproduceOption(opt::InputArgList &args) { + if (auto *arg = args.getLastArg(OPT_reproduce)) + return arg->getValue(); + return getenv("LLD_REPRODUCE"); +} + bool macho::link(llvm::ArrayRef argsArr, bool canExitEarly, raw_ostream &stdoutOS, raw_ostream &stderrOS) { lld::stdoutOS = &stdoutOS; @@ -569,6 +576,20 @@ return true; } + if (const char *path = getReproduceOption(args)) { + // Note that --reproduce is a debug option so you can ignore it + // if you are trying to understand the whole picture of the code. + Expected> errOrWriter = + TarWriter::create(path, path::stem(path)); + if (errOrWriter) { + tar = std::move(*errOrWriter); + tar->append("response.txt", createResponseFile(args)); + tar->append("version.txt", getLLDVersion() + "\n"); + } else { + error("--reproduce: " + toString(errOrWriter.takeError())); + } + } + config = make(); symtab = make(); target = createTargetInfo(args); diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp --- a/lld/MachO/DriverUtils.cpp +++ b/lld/MachO/DriverUtils.cpp @@ -9,8 +9,10 @@ #include "Driver.h" #include "InputFiles.h" +#include "lld/Common/Args.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" +#include "lld/Common/Reproduce.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -96,6 +98,55 @@ lld::outs() << "\n"; } +static std::string rewritePath(StringRef s) { + if (fs::exists(s)) + return relativeToRoot(s); + return std::string(s); +} + +// Reconstructs command line arguments so that so that you can re-run +// the same command with the same inputs. This is for --reproduce. +std::string macho::createResponseFile(const opt::InputArgList &args) { + SmallString<0> data; + raw_svector_ostream os(data); + + // Copy the command line to the output while rewriting paths. + for (auto *arg : args) { + switch (arg->getOption().getID()) { + case OPT_reproduce: + break; + case OPT_INPUT: + os << quote(rewritePath(arg->getValue())) << "\n"; + break; + case OPT_o: + os << "-o " << quote(path::filename(arg->getValue())) << "\n"; + break; + case OPT_filelist: + if (Optional buffer = readFile(arg->getValue())) + for (StringRef path : args::getLines(*buffer)) + os << quote(rewritePath(path)) << "\n"; + break; + case OPT_force_load: + case OPT_rpath: + case OPT_syslibroot: + case OPT_F: + case OPT_L: + case OPT_order_file: + os << arg->getSpelling() << " " << quote(rewritePath(arg->getValue())) + << "\n"; + break; + case OPT_sectcreate: + os << arg->getSpelling() << " " << quote(arg->getValue(0)) << " " + << quote(arg->getValue(1)) << " " + << quote(rewritePath(arg->getValue(2))) << "\n"; + break; + default: + os << toString(*arg) << "\n"; + } + } + return std::string(data.str()); +} + Optional macho::resolveDylibPath(StringRef path) { // TODO: if a tbd and dylib are both present, we should check to make sure // they are consistent. diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -27,6 +27,7 @@ namespace lto { class InputFile; } // namespace lto +class TarWriter; } // namespace llvm namespace lld { @@ -36,6 +37,10 @@ class Symbol; struct Reloc; +// If --reproduce option is given, all input files are written +// to this tar archive. +extern std::unique_ptr tar; + // If .subsections_via_symbols is set, each InputSection will be split along // symbol boundaries. The keys of a SubsectionMap represent the offsets of // each subsection from the start of the original pre-split InputSection. diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -56,12 +56,14 @@ #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" +#include "lld/Common/Reproduce.h" #include "llvm/ADT/iterator.h" #include "llvm/BinaryFormat/MachO.h" #include "llvm/LTO/LTO.h" #include "llvm/Support/Endian.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" +#include "llvm/Support/TarWriter.h" using namespace llvm; using namespace llvm::MachO; @@ -71,6 +73,7 @@ using namespace lld::macho; std::vector macho::inputFiles; +std::unique_ptr macho::tar; // Open a given file path and return it as a memory-mapped file. Optional macho::readFile(StringRef path) { @@ -88,8 +91,11 @@ // If this is a regular non-fat file, return it. const char *buf = mbref.getBufferStart(); auto *hdr = reinterpret_cast(buf); - if (read32be(&hdr->magic) != MachO::FAT_MAGIC) + if (read32be(&hdr->magic) != MachO::FAT_MAGIC) { + if (tar) + tar->append(relativeToRoot(path), mbref.getBuffer()); return mbref; + } // Object files and archive files may be fat files, which contains // multiple real files for different CPU ISAs. Here, we search for a @@ -112,6 +118,8 @@ uint32_t size = read32be(&arch[i].size); if (offset + size > mbref.getBufferSize()) error(path + ": slice extends beyond end of file"); + if (tar) + tar->append(relativeToRoot(path), mbref.getBuffer()); return MemoryBufferRef(StringRef(buf + offset, size), path.copy(bAlloc)); } diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -14,6 +14,10 @@ def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">, HelpText<"Use colors in diagnostics (default: auto)">, MetaVarName<"[auto,always,never]">; +def reproduce: Separate<["--"], "reproduce">; +def reproduce_eq: Joined<["--"], "reproduce=">, + Alias(reproduce)>, + HelpText<"Write tar file containing inputs and command to reproduce link">; // This is a complete Options.td compiled from Apple's ld(1) manpage diff --git a/lld/test/MachO/reproduce.s b/lld/test/MachO/reproduce.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/reproduce.s @@ -0,0 +1,37 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t.dir +# RUN: mkdir -p %t.dir/build1 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build1/foo.o +# RUN: cd %t.dir +# RUN: %lld -platform_version macos 10.10.0 11.0 build1/foo.o -o bar --reproduce repro1.tar +# RUN: tar xOf repro1.tar repro1/%:t.dir/build1/foo.o > build1-foo.o +# RUN: cmp build1/foo.o build1-foo.o + +# RUN: tar xf repro1.tar repro1/response.txt repro1/version.txt +# RUN: FileCheck %s --check-prefix=RSP1 < repro1/response.txt +# RSP1: {{^}}-platform_version macos 10.10.0 11.0{{$}} +# RSP1-NOT: {{^}}repro1{{[/\\]}} +# RSP1-NEXT: {{[/\\]}}foo.o +# RSP1-NEXT: -o bar +# RSP1-NOT: --reproduce + +# RUN: FileCheck %s --check-prefix=VERSION < repro1/version.txt +# VERSION: LLD + +# RUN: mkdir -p %t.dir/build2/a/b/c +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build2/foo.o +# RUN: cd %t.dir/build2/a/b/c +# RUN: echo ./../../../foo.o > %t.dir/build2/filelist +# RUN: env LLD_REPRODUCE=repro2.tar %lld -filelist %t.dir/build2/filelist -o /dev/null +# RUN: tar xOf repro2.tar repro2/%:t.dir/build2/foo.o > build2-foo.o +# RUN: cmp %t.dir/build2/foo.o build2-foo.o + +# RUN: tar xf repro2.tar repro2/response.txt repro2/version.txt +# RUN: FileCheck %s --check-prefix=RSP2 < repro2/response.txt +# RSP2-NOT: {{^}}repro2{{[/\\]}} +# RSP2: {{[/\\]}}foo.o + +.globl _main +_main: + ret