xref: /freebsd/contrib/llvm-project/clang/lib/Frontend/DependencyFile.cpp (revision a2fda816eb054d5873be223ef2461741dfcc253c)
1  //===--- DependencyFile.cpp - Generate dependency file --------------------===//
2  //
3  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4  // See https://llvm.org/LICENSE.txt for license information.
5  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6  //
7  //===----------------------------------------------------------------------===//
8  //
9  // This code generates dependency files.
10  //
11  //===----------------------------------------------------------------------===//
12  
13  #include "clang/Frontend/Utils.h"
14  #include "clang/Basic/FileManager.h"
15  #include "clang/Basic/SourceManager.h"
16  #include "clang/Frontend/DependencyOutputOptions.h"
17  #include "clang/Frontend/FrontendDiagnostic.h"
18  #include "clang/Lex/DirectoryLookup.h"
19  #include "clang/Lex/ModuleMap.h"
20  #include "clang/Lex/PPCallbacks.h"
21  #include "clang/Lex/Preprocessor.h"
22  #include "clang/Serialization/ASTReader.h"
23  #include "llvm/ADT/StringSet.h"
24  #include "llvm/Support/FileSystem.h"
25  #include "llvm/Support/Path.h"
26  #include "llvm/Support/raw_ostream.h"
27  #include <optional>
28  
29  using namespace clang;
30  
31  namespace {
32  struct DepCollectorPPCallbacks : public PPCallbacks {
33    DependencyCollector &DepCollector;
34    Preprocessor &PP;
35    DepCollectorPPCallbacks(DependencyCollector &L, Preprocessor &PP)
36        : DepCollector(L), PP(PP) {}
37  
38    void LexedFileChanged(FileID FID, LexedFileChangeReason Reason,
39                          SrcMgr::CharacteristicKind FileType, FileID PrevFID,
40                          SourceLocation Loc) override {
41      if (Reason != PPCallbacks::LexedFileChangeReason::EnterFile)
42        return;
43  
44      // Dependency generation really does want to go all the way to the
45      // file entry for a source location to find out what is depended on.
46      // We do not want #line markers to affect dependency generation!
47      if (std::optional<StringRef> Filename =
48              PP.getSourceManager().getNonBuiltinFilenameForID(FID))
49        DepCollector.maybeAddDependency(
50            llvm::sys::path::remove_leading_dotslash(*Filename),
51            /*FromModule*/ false, isSystem(FileType), /*IsModuleFile*/ false,
52            /*IsMissing*/ false);
53    }
54  
55    void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
56                     SrcMgr::CharacteristicKind FileType) override {
57      StringRef Filename =
58          llvm::sys::path::remove_leading_dotslash(SkippedFile.getName());
59      DepCollector.maybeAddDependency(Filename, /*FromModule=*/false,
60                                      /*IsSystem=*/isSystem(FileType),
61                                      /*IsModuleFile=*/false,
62                                      /*IsMissing=*/false);
63    }
64  
65    void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
66                            StringRef FileName, bool IsAngled,
67                            CharSourceRange FilenameRange,
68                            OptionalFileEntryRef File, StringRef SearchPath,
69                            StringRef RelativePath, const Module *Imported,
70                            SrcMgr::CharacteristicKind FileType) override {
71      if (!File)
72        DepCollector.maybeAddDependency(FileName, /*FromModule*/ false,
73                                        /*IsSystem*/ false,
74                                        /*IsModuleFile*/ false,
75                                        /*IsMissing*/ true);
76      // Files that actually exist are handled by FileChanged.
77    }
78  
79    void HasInclude(SourceLocation Loc, StringRef SpelledFilename, bool IsAngled,
80                    OptionalFileEntryRef File,
81                    SrcMgr::CharacteristicKind FileType) override {
82      if (!File)
83        return;
84      StringRef Filename =
85          llvm::sys::path::remove_leading_dotslash(File->getName());
86      DepCollector.maybeAddDependency(Filename, /*FromModule=*/false,
87                                      /*IsSystem=*/isSystem(FileType),
88                                      /*IsModuleFile=*/false,
89                                      /*IsMissing=*/false);
90    }
91  
92    void EndOfMainFile() override {
93      DepCollector.finishedMainFile(PP.getDiagnostics());
94    }
95  };
96  
97  struct DepCollectorMMCallbacks : public ModuleMapCallbacks {
98    DependencyCollector &DepCollector;
99    DepCollectorMMCallbacks(DependencyCollector &DC) : DepCollector(DC) {}
100  
101    void moduleMapFileRead(SourceLocation Loc, FileEntryRef Entry,
102                           bool IsSystem) override {
103      StringRef Filename = Entry.getName();
104      DepCollector.maybeAddDependency(Filename, /*FromModule*/ false,
105                                      /*IsSystem*/ IsSystem,
106                                      /*IsModuleFile*/ false,
107                                      /*IsMissing*/ false);
108    }
109  };
110  
111  struct DepCollectorASTListener : public ASTReaderListener {
112    DependencyCollector &DepCollector;
113    FileManager &FileMgr;
114    DepCollectorASTListener(DependencyCollector &L, FileManager &FileMgr)
115        : DepCollector(L), FileMgr(FileMgr) {}
116    bool needsInputFileVisitation() override { return true; }
117    bool needsSystemInputFileVisitation() override {
118      return DepCollector.needSystemDependencies();
119    }
120    void visitModuleFile(StringRef Filename,
121                         serialization::ModuleKind Kind) override {
122      DepCollector.maybeAddDependency(Filename, /*FromModule*/ true,
123                                      /*IsSystem*/ false, /*IsModuleFile*/ true,
124                                      /*IsMissing*/ false);
125    }
126    bool visitInputFile(StringRef Filename, bool IsSystem,
127                        bool IsOverridden, bool IsExplicitModule) override {
128      if (IsOverridden || IsExplicitModule)
129        return true;
130  
131      // Run this through the FileManager in order to respect 'use-external-name'
132      // in case we have a VFS overlay.
133      if (auto FE = FileMgr.getOptionalFileRef(Filename))
134        Filename = FE->getName();
135  
136      DepCollector.maybeAddDependency(Filename, /*FromModule*/ true, IsSystem,
137                                      /*IsModuleFile*/ false,
138                                      /*IsMissing*/ false);
139      return true;
140    }
141  };
142  } // end anonymous namespace
143  
144  void DependencyCollector::maybeAddDependency(StringRef Filename,
145                                               bool FromModule, bool IsSystem,
146                                               bool IsModuleFile,
147                                               bool IsMissing) {
148    if (sawDependency(Filename, FromModule, IsSystem, IsModuleFile, IsMissing))
149      addDependency(Filename);
150  }
151  
152  bool DependencyCollector::addDependency(StringRef Filename) {
153    StringRef SearchPath;
154  #ifdef _WIN32
155    // Make the search insensitive to case and separators.
156    llvm::SmallString<256> TmpPath = Filename;
157    llvm::sys::path::native(TmpPath);
158    std::transform(TmpPath.begin(), TmpPath.end(), TmpPath.begin(), ::tolower);
159    SearchPath = TmpPath.str();
160  #else
161    SearchPath = Filename;
162  #endif
163  
164    if (Seen.insert(SearchPath).second) {
165      Dependencies.push_back(std::string(Filename));
166      return true;
167    }
168    return false;
169  }
170  
171  static bool isSpecialFilename(StringRef Filename) {
172    return Filename == "<built-in>";
173  }
174  
175  bool DependencyCollector::sawDependency(StringRef Filename, bool FromModule,
176                                          bool IsSystem, bool IsModuleFile,
177                                          bool IsMissing) {
178    return !isSpecialFilename(Filename) &&
179           (needSystemDependencies() || !IsSystem);
180  }
181  
182  DependencyCollector::~DependencyCollector() { }
183  void DependencyCollector::attachToPreprocessor(Preprocessor &PP) {
184    PP.addPPCallbacks(std::make_unique<DepCollectorPPCallbacks>(*this, PP));
185    PP.getHeaderSearchInfo().getModuleMap().addModuleMapCallbacks(
186        std::make_unique<DepCollectorMMCallbacks>(*this));
187  }
188  void DependencyCollector::attachToASTReader(ASTReader &R) {
189    R.addListener(
190        std::make_unique<DepCollectorASTListener>(*this, R.getFileManager()));
191  }
192  
193  DependencyFileGenerator::DependencyFileGenerator(
194      const DependencyOutputOptions &Opts)
195      : OutputFile(Opts.OutputFile), Targets(Opts.Targets),
196        IncludeSystemHeaders(Opts.IncludeSystemHeaders),
197        PhonyTarget(Opts.UsePhonyTargets),
198        AddMissingHeaderDeps(Opts.AddMissingHeaderDeps), SeenMissingHeader(false),
199        IncludeModuleFiles(Opts.IncludeModuleFiles),
200        OutputFormat(Opts.OutputFormat), InputFileIndex(0) {
201    for (const auto &ExtraDep : Opts.ExtraDeps) {
202      if (addDependency(ExtraDep.first))
203        ++InputFileIndex;
204    }
205  }
206  
207  void DependencyFileGenerator::attachToPreprocessor(Preprocessor &PP) {
208    // Disable the "file not found" diagnostic if the -MG option was given.
209    if (AddMissingHeaderDeps)
210      PP.SetSuppressIncludeNotFoundError(true);
211  
212    DependencyCollector::attachToPreprocessor(PP);
213  }
214  
215  bool DependencyFileGenerator::sawDependency(StringRef Filename, bool FromModule,
216                                              bool IsSystem, bool IsModuleFile,
217                                              bool IsMissing) {
218    if (IsMissing) {
219      // Handle the case of missing file from an inclusion directive.
220      if (AddMissingHeaderDeps)
221        return true;
222      SeenMissingHeader = true;
223      return false;
224    }
225    if (IsModuleFile && !IncludeModuleFiles)
226      return false;
227  
228    if (isSpecialFilename(Filename))
229      return false;
230  
231    if (IncludeSystemHeaders)
232      return true;
233  
234    return !IsSystem;
235  }
236  
237  void DependencyFileGenerator::finishedMainFile(DiagnosticsEngine &Diags) {
238    outputDependencyFile(Diags);
239  }
240  
241  /// Print the filename, with escaping or quoting that accommodates the three
242  /// most likely tools that use dependency files: GNU Make, BSD Make, and
243  /// NMake/Jom.
244  ///
245  /// BSD Make is the simplest case: It does no escaping at all.  This means
246  /// characters that are normally delimiters, i.e. space and # (the comment
247  /// character) simply aren't supported in filenames.
248  ///
249  /// GNU Make does allow space and # in filenames, but to avoid being treated
250  /// as a delimiter or comment, these must be escaped with a backslash. Because
251  /// backslash is itself the escape character, if a backslash appears in a
252  /// filename, it should be escaped as well.  (As a special case, $ is escaped
253  /// as $$, which is the normal Make way to handle the $ character.)
254  /// For compatibility with BSD Make and historical practice, if GNU Make
255  /// un-escapes characters in a filename but doesn't find a match, it will
256  /// retry with the unmodified original string.
257  ///
258  /// GCC tries to accommodate both Make formats by escaping any space or #
259  /// characters in the original filename, but not escaping backslashes.  The
260  /// apparent intent is so that filenames with backslashes will be handled
261  /// correctly by BSD Make, and by GNU Make in its fallback mode of using the
262  /// unmodified original string; filenames with # or space characters aren't
263  /// supported by BSD Make at all, but will be handled correctly by GNU Make
264  /// due to the escaping.
265  ///
266  /// A corner case that GCC gets only partly right is when the original filename
267  /// has a backslash immediately followed by space or #.  GNU Make would expect
268  /// this backslash to be escaped; however GCC escapes the original backslash
269  /// only when followed by space, not #.  It will therefore take a dependency
270  /// from a directive such as
271  ///     #include "a\ b\#c.h"
272  /// and emit it as
273  ///     a\\\ b\\#c.h
274  /// which GNU Make will interpret as
275  ///     a\ b\
276  /// followed by a comment. Failing to find this file, it will fall back to the
277  /// original string, which probably doesn't exist either; in any case it won't
278  /// find
279  ///     a\ b\#c.h
280  /// which is the actual filename specified by the include directive.
281  ///
282  /// Clang does what GCC does, rather than what GNU Make expects.
283  ///
284  /// NMake/Jom has a different set of scary characters, but wraps filespecs in
285  /// double-quotes to avoid misinterpreting them; see
286  /// https://msdn.microsoft.com/en-us/library/dd9y37ha.aspx for NMake info,
287  /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
288  /// for Windows file-naming info.
289  static void PrintFilename(raw_ostream &OS, StringRef Filename,
290                            DependencyOutputFormat OutputFormat) {
291    // Convert filename to platform native path
292    llvm::SmallString<256> NativePath;
293    llvm::sys::path::native(Filename.str(), NativePath);
294  
295    if (OutputFormat == DependencyOutputFormat::NMake) {
296      // Add quotes if needed. These are the characters listed as "special" to
297      // NMake, that are legal in a Windows filespec, and that could cause
298      // misinterpretation of the dependency string.
299      if (NativePath.find_first_of(" #${}^!") != StringRef::npos)
300        OS << '\"' << NativePath << '\"';
301      else
302        OS << NativePath;
303      return;
304    }
305    assert(OutputFormat == DependencyOutputFormat::Make);
306    for (unsigned i = 0, e = NativePath.size(); i != e; ++i) {
307      if (NativePath[i] == '#') // Handle '#' the broken gcc way.
308        OS << '\\';
309      else if (NativePath[i] == ' ') { // Handle space correctly.
310        OS << '\\';
311        unsigned j = i;
312        while (j > 0 && NativePath[--j] == '\\')
313          OS << '\\';
314      } else if (NativePath[i] == '$') // $ is escaped by $$.
315        OS << '$';
316      OS << NativePath[i];
317    }
318  }
319  
320  void DependencyFileGenerator::outputDependencyFile(DiagnosticsEngine &Diags) {
321    if (SeenMissingHeader) {
322      llvm::sys::fs::remove(OutputFile);
323      return;
324    }
325  
326    std::error_code EC;
327    llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
328    if (EC) {
329      Diags.Report(diag::err_fe_error_opening) << OutputFile << EC.message();
330      return;
331    }
332  
333    outputDependencyFile(OS);
334  }
335  
336  void DependencyFileGenerator::outputDependencyFile(llvm::raw_ostream &OS) {
337    // Write out the dependency targets, trying to avoid overly long
338    // lines when possible. We try our best to emit exactly the same
339    // dependency file as GCC>=10, assuming the included files are the
340    // same.
341    const unsigned MaxColumns = 75;
342    unsigned Columns = 0;
343  
344    for (StringRef Target : Targets) {
345      unsigned N = Target.size();
346      if (Columns == 0) {
347        Columns += N;
348      } else if (Columns + N + 2 > MaxColumns) {
349        Columns = N + 2;
350        OS << " \\\n  ";
351      } else {
352        Columns += N + 1;
353        OS << ' ';
354      }
355      // Targets already quoted as needed.
356      OS << Target;
357    }
358  
359    OS << ':';
360    Columns += 1;
361  
362    // Now add each dependency in the order it was seen, but avoiding
363    // duplicates.
364    ArrayRef<std::string> Files = getDependencies();
365    for (StringRef File : Files) {
366      if (File == "<stdin>")
367        continue;
368      // Start a new line if this would exceed the column limit. Make
369      // sure to leave space for a trailing " \" in case we need to
370      // break the line on the next iteration.
371      unsigned N = File.size();
372      if (Columns + (N + 1) + 2 > MaxColumns) {
373        OS << " \\\n ";
374        Columns = 2;
375      }
376      OS << ' ';
377      PrintFilename(OS, File, OutputFormat);
378      Columns += N + 1;
379    }
380    OS << '\n';
381  
382    // Create phony targets if requested.
383    if (PhonyTarget && !Files.empty()) {
384      unsigned Index = 0;
385      for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {
386        if (Index++ == InputFileIndex)
387          continue;
388        PrintFilename(OS, *I, OutputFormat);
389        OS << ":\n";
390      }
391    }
392  }
393