diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -10,7 +10,9 @@ #define LLD_ELF_CONFIG_H #include "lld/Common/ErrorHandler.h" +#include "llvm/ADT/CachedHashString.h" #include "llvm/ADT/MapVector.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/BinaryFormat/ELF.h" @@ -90,11 +92,13 @@ uint8_t osabi = 0; uint32_t andFeatures = 0; llvm::CachePruningPolicy thinLTOCachePolicy; + llvm::SetVector dependencyFiles; // for --dependency-file llvm::StringMap sectionStartMap; llvm::StringRef bfdname; llvm::StringRef chroot; - llvm::StringRef dynamicLinker; + llvm::StringRef dependencyFile; llvm::StringRef dwoDir; + llvm::StringRef dynamicLinker; llvm::StringRef entry; llvm::StringRef emulation; llvm::StringRef fini; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -918,6 +918,7 @@ config->optimizeBBJumps = args.hasFlag(OPT_optimize_bb_jumps, OPT_no_optimize_bb_jumps, false); config->demangle = args.hasFlag(OPT_demangle, OPT_no_demangle, true); + config->dependencyFile = args.getLastArgValue(OPT_dependency_file); config->dependentLibraries = args.hasFlag(OPT_dependent_libraries, OPT_no_dependent_libraries, true); config->disableVerify = args.hasArg(OPT_disable_verify); config->discard = getDiscard(args); @@ -1564,6 +1565,75 @@ sym->fetch(); } +// Handle --dependency-file=. If that option is given, lld creates a +// file at a given path with the following contents: +// +// : ... +// +// : +// +// where is a pathname of an output file and +// ... is a list of pathnames of all input files. `make` command can read a +// file in the above format and interpret it as a dependency info. We write +// phony targets for every to avoid an error when that file is +// removed. +// +// This option is useful if you want to make your final executable to depend +// on all input files including system libraries. Here is why. +// +// When you write a Makefile, you usually write it so that the final +// executable depends on all user-generated object files. Normally, you +// don't make your executable to depend on system libraries (such as libc) +// because you don't know the exact paths of libraries, even though system +// libraries that are linked to your executable statically are technically a +// part of your program. By using --dependency-file option, you can make +// lld to dump dependency info so that you can maintain exact dependencies +// easily. +static void writeDependencyFile() { + std::error_code ec; + raw_fd_ostream os(config->dependencyFile, ec, sys::fs::F_None); + if (ec) { + error("cannot open " + config->dependencyFile + ": " + ec.message()); + return; + } + + // We use the same escape rules as Clang/GCC which are accepted by Make/Ninja: + // * A space is escaped by a backslash which itself must be escaped. + // * A hash sign is escaped by a single backslash. + // * $ is escapes as $$. + auto printFilename = [](raw_fd_ostream &os, StringRef filename) { + llvm::SmallString<256> nativePath; + llvm::sys::path::native(filename.str(), nativePath); + llvm::sys::path::remove_dots(nativePath, /*remove_dot_dot=*/true); + for (unsigned i = 0, e = nativePath.size(); i != e; ++i) { + if (nativePath[i] == '#') { + os << '\\'; + } else if (nativePath[i] == ' ') { + os << '\\'; + unsigned j = i; + while (j > 0 && nativePath[--j] == '\\') + os << '\\'; + } else if (nativePath[i] == '$') { + os << '$'; + } + os << nativePath[i]; + } + }; + + os << config->outputFile << ":"; + for (StringRef path : config->dependencyFiles) { + os << " \\\n "; + printFilename(os, path); + } + os << "\n"; + + for (StringRef path : config->dependencyFiles) { + os << "\n"; + printFilename(os, path); + os << ":\n"; + } +} + // Replaces common symbols with defined symbols reside in .bss sections. // This function is called after all symbol names are resolved. As a // result, the passes after the symbol resolution won't see any @@ -2065,6 +2135,11 @@ return false; }); + // Since we now have a complete set of input files, we can create + // a .d file to record build dependencies. + if (!config->dependencyFile.empty()) + writeDependencyFile(); + // Now that the number of partitions is fixed, save a pointer to the main // partition. mainPart = &partitions[0]; diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -110,6 +110,7 @@ path = saver.save(config->chroot + path); log(path); + config->dependencyFiles.insert(llvm::CachedHashString(path)); auto mbOrErr = MemoryBuffer::getFile(path, -1, false); if (auto ec = mbOrErr.getError()) { diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -132,6 +132,9 @@ "Demangle symbol names (default)", "Do not demangle symbol names">; +defm dependency_file: EEq<"dependency-file", "Write a dependency file">, + MetaVarName<"">; + def disable_new_dtags: F<"disable-new-dtags">, HelpText<"Disable new dynamic tags">; diff --git a/lld/test/ELF/dependency-file.s b/lld/test/ELF/dependency-file.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/dependency-file.s @@ -0,0 +1,21 @@ +# REQUIRES: x86 +# RUN: mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o "%t/bar baz.o" +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o "%t/#quux$.o" +# RUN: ld.lld -o %t/foo.exe %t/foo.o %t/"bar baz.o" "%t/#quux$.o" --dependency-file=%t/foo.d +# RUN: FileCheck --match-full-lines -DFILE=%t %s < %t/foo.d + +# CHECK: [[FILE]]{{/|(\\)+}}foo.exe: \ +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}foo.o \ +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}bar\ baz.o \ +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}\#quux$$.o +# CHECK-EMPTY: +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}foo.o: +# CHECK-EMPTY: +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}bar\ baz.o: +# CHECK-EMPTY: +# CHECK-NEXT: [[FILE]]{{/|(\\)+}}\#quux$$.o: + +.global _start +_start: