xref: /freebsd/contrib/llvm-project/clang/lib/Frontend/DependencyFile.cpp (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
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, const FileEntry &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