Skip to content

Commit

Permalink
[ELF] Add --dependency-file option
Browse files Browse the repository at this point in the history
Clang and GCC have a feature (-MD flag) to create a dependency file
in a format that build systems such as Make or Ninja can read, which
specifies all the additional inputs such .h files.

This change introduces the same functionality to lld bringing it to
feature parity with ld and gold which gained this feature recently.
See https://sourceware.org/bugzilla/show_bug.cgi?id=22843 for more
details and discussion.

The implementation corresponds to -MD -MP compiler flag where the
generated dependency file also includes phony targets which works
around the errors where the dependency is removed. This matches the
format used by ld and gold.

Fixes PR42806

Differential Revision: https://reviews.llvm.org/D82437
  • Loading branch information
petrhosek committed Jul 30, 2020
1 parent 4c16eaf commit b4c7657
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 1 deletion.
5 changes: 4 additions & 1 deletion lld/ELF/Config.h
Expand Up @@ -11,6 +11,7 @@

#include "lld/Common/ErrorHandler.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"
Expand Down Expand Up @@ -90,11 +91,13 @@ struct Configuration {
uint8_t osabi = 0;
uint32_t andFeatures = 0;
llvm::CachePruningPolicy thinLTOCachePolicy;
llvm::SetVector<StringRef> dependencyFiles; // for --dependency-file
llvm::StringMap<uint64_t> 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;
Expand Down
75 changes: 75 additions & 0 deletions lld/ELF/Driver.cpp
Expand Up @@ -918,6 +918,7 @@ static void readConfigs(opt::InputArgList &args) {
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);
Expand Down Expand Up @@ -1564,6 +1565,75 @@ static void handleLibcall(StringRef name) {
sym->fetch();
}

// Handle --dependency-file=<path>. If that option is given, lld creates a
// file at a given path with the following contents:
//
// <output-file>: <input-file> ...
//
// <input-file>:
//
// where <output-file> is a pathname of an output file and <input-file>
// ... 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 <input-file> 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
Expand Down Expand Up @@ -2064,6 +2134,11 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
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];
Expand Down
1 change: 1 addition & 0 deletions lld/ELF/InputFiles.cpp
Expand Up @@ -110,6 +110,7 @@ Optional<MemoryBufferRef> elf::readFile(StringRef path) {
path = saver.save(config->chroot + path);

log(path);
config->dependencyFiles.insert(path);

auto mbOrErr = MemoryBuffer::getFile(path, -1, false);
if (auto ec = mbOrErr.getError()) {
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Options.td
Expand Up @@ -132,6 +132,9 @@ defm demangle: B<"demangle",
"Demangle symbol names (default)",
"Do not demangle symbol names">;

defm dependency_file: EEq<"dependency-file", "Write a dependency file">,
MetaVarName<"<path>">;

def disable_new_dtags: F<"disable-new-dtags">,
HelpText<"Disable new dynamic tags">;

Expand Down
21 changes: 21 additions & 0 deletions 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:

0 comments on commit b4c7657

Please sign in to comment.