Skip to content

Commit b4c7657

Browse files
committed
[ELF] Add --dependency-file option
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
1 parent 4c16eaf commit b4c7657

File tree

5 files changed

+104
-1
lines changed

5 files changed

+104
-1
lines changed

lld/ELF/Config.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "lld/Common/ErrorHandler.h"
1313
#include "llvm/ADT/MapVector.h"
14+
#include "llvm/ADT/SetVector.h"
1415
#include "llvm/ADT/StringRef.h"
1516
#include "llvm/ADT/StringSet.h"
1617
#include "llvm/BinaryFormat/ELF.h"
@@ -90,11 +91,13 @@ struct Configuration {
9091
uint8_t osabi = 0;
9192
uint32_t andFeatures = 0;
9293
llvm::CachePruningPolicy thinLTOCachePolicy;
94+
llvm::SetVector<StringRef> dependencyFiles; // for --dependency-file
9395
llvm::StringMap<uint64_t> sectionStartMap;
9496
llvm::StringRef bfdname;
9597
llvm::StringRef chroot;
96-
llvm::StringRef dynamicLinker;
98+
llvm::StringRef dependencyFile;
9799
llvm::StringRef dwoDir;
100+
llvm::StringRef dynamicLinker;
98101
llvm::StringRef entry;
99102
llvm::StringRef emulation;
100103
llvm::StringRef fini;

lld/ELF/Driver.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,7 @@ static void readConfigs(opt::InputArgList &args) {
918918
config->optimizeBBJumps =
919919
args.hasFlag(OPT_optimize_bb_jumps, OPT_no_optimize_bb_jumps, false);
920920
config->demangle = args.hasFlag(OPT_demangle, OPT_no_demangle, true);
921+
config->dependencyFile = args.getLastArgValue(OPT_dependency_file);
921922
config->dependentLibraries = args.hasFlag(OPT_dependent_libraries, OPT_no_dependent_libraries, true);
922923
config->disableVerify = args.hasArg(OPT_disable_verify);
923924
config->discard = getDiscard(args);
@@ -1564,6 +1565,75 @@ static void handleLibcall(StringRef name) {
15641565
sym->fetch();
15651566
}
15661567

1568+
// Handle --dependency-file=<path>. If that option is given, lld creates a
1569+
// file at a given path with the following contents:
1570+
//
1571+
// <output-file>: <input-file> ...
1572+
//
1573+
// <input-file>:
1574+
//
1575+
// where <output-file> is a pathname of an output file and <input-file>
1576+
// ... is a list of pathnames of all input files. `make` command can read a
1577+
// file in the above format and interpret it as a dependency info. We write
1578+
// phony targets for every <input-file> to avoid an error when that file is
1579+
// removed.
1580+
//
1581+
// This option is useful if you want to make your final executable to depend
1582+
// on all input files including system libraries. Here is why.
1583+
//
1584+
// When you write a Makefile, you usually write it so that the final
1585+
// executable depends on all user-generated object files. Normally, you
1586+
// don't make your executable to depend on system libraries (such as libc)
1587+
// because you don't know the exact paths of libraries, even though system
1588+
// libraries that are linked to your executable statically are technically a
1589+
// part of your program. By using --dependency-file option, you can make
1590+
// lld to dump dependency info so that you can maintain exact dependencies
1591+
// easily.
1592+
static void writeDependencyFile() {
1593+
std::error_code ec;
1594+
raw_fd_ostream os(config->dependencyFile, ec, sys::fs::F_None);
1595+
if (ec) {
1596+
error("cannot open " + config->dependencyFile + ": " + ec.message());
1597+
return;
1598+
}
1599+
1600+
// We use the same escape rules as Clang/GCC which are accepted by Make/Ninja:
1601+
// * A space is escaped by a backslash which itself must be escaped.
1602+
// * A hash sign is escaped by a single backslash.
1603+
// * $ is escapes as $$.
1604+
auto printFilename = [](raw_fd_ostream &os, StringRef filename) {
1605+
llvm::SmallString<256> nativePath;
1606+
llvm::sys::path::native(filename.str(), nativePath);
1607+
llvm::sys::path::remove_dots(nativePath, /*remove_dot_dot=*/true);
1608+
for (unsigned i = 0, e = nativePath.size(); i != e; ++i) {
1609+
if (nativePath[i] == '#') {
1610+
os << '\\';
1611+
} else if (nativePath[i] == ' ') {
1612+
os << '\\';
1613+
unsigned j = i;
1614+
while (j > 0 && nativePath[--j] == '\\')
1615+
os << '\\';
1616+
} else if (nativePath[i] == '$') {
1617+
os << '$';
1618+
}
1619+
os << nativePath[i];
1620+
}
1621+
};
1622+
1623+
os << config->outputFile << ":";
1624+
for (StringRef path : config->dependencyFiles) {
1625+
os << " \\\n ";
1626+
printFilename(os, path);
1627+
}
1628+
os << "\n";
1629+
1630+
for (StringRef path : config->dependencyFiles) {
1631+
os << "\n";
1632+
printFilename(os, path);
1633+
os << ":\n";
1634+
}
1635+
}
1636+
15671637
// Replaces common symbols with defined symbols reside in .bss sections.
15681638
// This function is called after all symbol names are resolved. As a
15691639
// result, the passes after the symbol resolution won't see any
@@ -2064,6 +2134,11 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
20642134
return false;
20652135
});
20662136

2137+
// Since we now have a complete set of input files, we can create
2138+
// a .d file to record build dependencies.
2139+
if (!config->dependencyFile.empty())
2140+
writeDependencyFile();
2141+
20672142
// Now that the number of partitions is fixed, save a pointer to the main
20682143
// partition.
20692144
mainPart = &partitions[0];

lld/ELF/InputFiles.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Optional<MemoryBufferRef> elf::readFile(StringRef path) {
110110
path = saver.save(config->chroot + path);
111111

112112
log(path);
113+
config->dependencyFiles.insert(path);
113114

114115
auto mbOrErr = MemoryBuffer::getFile(path, -1, false);
115116
if (auto ec = mbOrErr.getError()) {

lld/ELF/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ defm demangle: B<"demangle",
132132
"Demangle symbol names (default)",
133133
"Do not demangle symbol names">;
134134

135+
defm dependency_file: EEq<"dependency-file", "Write a dependency file">,
136+
MetaVarName<"<path>">;
137+
135138
def disable_new_dtags: F<"disable-new-dtags">,
136139
HelpText<"Disable new dynamic tags">;
137140

lld/test/ELF/dependency-file.s

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# REQUIRES: x86
2+
# RUN: mkdir -p %t
3+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t/foo.o
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o "%t/bar baz.o"
5+
# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o "%t/#quux$.o"
6+
# RUN: ld.lld -o %t/foo.exe %t/foo.o %t/"bar baz.o" "%t/#quux$.o" --dependency-file=%t/foo.d
7+
# RUN: FileCheck --match-full-lines -DFILE=%t %s < %t/foo.d
8+
9+
# CHECK: [[FILE]]{{/|\\\\}}foo.exe: \
10+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}foo.o \
11+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}bar\ baz.o \
12+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}\#quux$$.o
13+
# CHECK-EMPTY:
14+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}foo.o:
15+
# CHECK-EMPTY:
16+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}bar\ baz.o:
17+
# CHECK-EMPTY:
18+
# CHECK-NEXT: [[FILE]]{{/|\\\\}}\#quux$$.o:
19+
20+
.global _start
21+
_start:

0 commit comments

Comments
 (0)