/src/llvm-project/clang/lib/Frontend/DependencyFile.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | : DepCollector(L), PP(PP) {} |
37 | | |
38 | | void LexedFileChanged(FileID FID, LexedFileChangeReason Reason, |
39 | | SrcMgr::CharacteristicKind FileType, FileID PrevFID, |
40 | 0 | SourceLocation Loc) override { |
41 | 0 | if (Reason != PPCallbacks::LexedFileChangeReason::EnterFile) |
42 | 0 | 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 | 0 | if (std::optional<StringRef> Filename = |
48 | 0 | PP.getSourceManager().getNonBuiltinFilenameForID(FID)) |
49 | 0 | DepCollector.maybeAddDependency( |
50 | 0 | llvm::sys::path::remove_leading_dotslash(*Filename), |
51 | 0 | /*FromModule*/ false, isSystem(FileType), /*IsModuleFile*/ false, |
52 | 0 | /*IsMissing*/ false); |
53 | 0 | } |
54 | | |
55 | | void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, |
56 | 0 | SrcMgr::CharacteristicKind FileType) override { |
57 | 0 | StringRef Filename = |
58 | 0 | llvm::sys::path::remove_leading_dotslash(SkippedFile.getName()); |
59 | 0 | DepCollector.maybeAddDependency(Filename, /*FromModule=*/false, |
60 | 0 | /*IsSystem=*/isSystem(FileType), |
61 | 0 | /*IsModuleFile=*/false, |
62 | 0 | /*IsMissing=*/false); |
63 | 0 | } |
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 | 0 | SrcMgr::CharacteristicKind FileType) override { |
71 | 0 | if (!File) |
72 | 0 | DepCollector.maybeAddDependency(FileName, /*FromModule*/ false, |
73 | 0 | /*IsSystem*/ false, |
74 | 0 | /*IsModuleFile*/ false, |
75 | 0 | /*IsMissing*/ true); |
76 | | // Files that actually exist are handled by FileChanged. |
77 | 0 | } |
78 | | |
79 | | void HasInclude(SourceLocation Loc, StringRef SpelledFilename, bool IsAngled, |
80 | | OptionalFileEntryRef File, |
81 | 0 | SrcMgr::CharacteristicKind FileType) override { |
82 | 0 | if (!File) |
83 | 0 | return; |
84 | 0 | StringRef Filename = |
85 | 0 | llvm::sys::path::remove_leading_dotslash(File->getName()); |
86 | 0 | DepCollector.maybeAddDependency(Filename, /*FromModule=*/false, |
87 | 0 | /*IsSystem=*/isSystem(FileType), |
88 | 0 | /*IsModuleFile=*/false, |
89 | 0 | /*IsMissing=*/false); |
90 | 0 | } |
91 | | |
92 | 0 | void EndOfMainFile() override { |
93 | 0 | DepCollector.finishedMainFile(PP.getDiagnostics()); |
94 | 0 | } |
95 | | }; |
96 | | |
97 | | struct DepCollectorMMCallbacks : public ModuleMapCallbacks { |
98 | | DependencyCollector &DepCollector; |
99 | 0 | DepCollectorMMCallbacks(DependencyCollector &DC) : DepCollector(DC) {} |
100 | | |
101 | | void moduleMapFileRead(SourceLocation Loc, FileEntryRef Entry, |
102 | 0 | bool IsSystem) override { |
103 | 0 | StringRef Filename = Entry.getName(); |
104 | 0 | DepCollector.maybeAddDependency(Filename, /*FromModule*/ false, |
105 | 0 | /*IsSystem*/ IsSystem, |
106 | 0 | /*IsModuleFile*/ false, |
107 | 0 | /*IsMissing*/ false); |
108 | 0 | } |
109 | | }; |
110 | | |
111 | | struct DepCollectorASTListener : public ASTReaderListener { |
112 | | DependencyCollector &DepCollector; |
113 | | FileManager &FileMgr; |
114 | | DepCollectorASTListener(DependencyCollector &L, FileManager &FileMgr) |
115 | 0 | : DepCollector(L), FileMgr(FileMgr) {} |
116 | 0 | bool needsInputFileVisitation() override { return true; } |
117 | 0 | bool needsSystemInputFileVisitation() override { |
118 | 0 | return DepCollector.needSystemDependencies(); |
119 | 0 | } |
120 | | void visitModuleFile(StringRef Filename, |
121 | 0 | serialization::ModuleKind Kind) override { |
122 | 0 | DepCollector.maybeAddDependency(Filename, /*FromModule*/ true, |
123 | 0 | /*IsSystem*/ false, /*IsModuleFile*/ true, |
124 | 0 | /*IsMissing*/ false); |
125 | 0 | } |
126 | | bool visitInputFile(StringRef Filename, bool IsSystem, |
127 | 0 | bool IsOverridden, bool IsExplicitModule) override { |
128 | 0 | if (IsOverridden || IsExplicitModule) |
129 | 0 | 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 | 0 | if (auto FE = FileMgr.getOptionalFileRef(Filename)) |
134 | 0 | Filename = FE->getName(); |
135 | |
|
136 | 0 | DepCollector.maybeAddDependency(Filename, /*FromModule*/ true, IsSystem, |
137 | 0 | /*IsModuleFile*/ false, |
138 | 0 | /*IsMissing*/ false); |
139 | 0 | return true; |
140 | 0 | } |
141 | | }; |
142 | | } // end anonymous namespace |
143 | | |
144 | | void DependencyCollector::maybeAddDependency(StringRef Filename, |
145 | | bool FromModule, bool IsSystem, |
146 | | bool IsModuleFile, |
147 | 0 | bool IsMissing) { |
148 | 0 | if (sawDependency(Filename, FromModule, IsSystem, IsModuleFile, IsMissing)) |
149 | 0 | addDependency(Filename); |
150 | 0 | } |
151 | | |
152 | 0 | bool DependencyCollector::addDependency(StringRef Filename) { |
153 | 0 | 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 | 0 | SearchPath = Filename; |
162 | 0 | #endif |
163 | |
|
164 | 0 | if (Seen.insert(SearchPath).second) { |
165 | 0 | Dependencies.push_back(std::string(Filename)); |
166 | 0 | return true; |
167 | 0 | } |
168 | 0 | return false; |
169 | 0 | } |
170 | | |
171 | 0 | static bool isSpecialFilename(StringRef Filename) { |
172 | 0 | return Filename == "<built-in>"; |
173 | 0 | } |
174 | | |
175 | | bool DependencyCollector::sawDependency(StringRef Filename, bool FromModule, |
176 | | bool IsSystem, bool IsModuleFile, |
177 | 0 | bool IsMissing) { |
178 | 0 | return !isSpecialFilename(Filename) && |
179 | 0 | (needSystemDependencies() || !IsSystem); |
180 | 0 | } |
181 | | |
182 | 0 | DependencyCollector::~DependencyCollector() { } |
183 | 0 | void DependencyCollector::attachToPreprocessor(Preprocessor &PP) { |
184 | 0 | PP.addPPCallbacks(std::make_unique<DepCollectorPPCallbacks>(*this, PP)); |
185 | 0 | PP.getHeaderSearchInfo().getModuleMap().addModuleMapCallbacks( |
186 | 0 | std::make_unique<DepCollectorMMCallbacks>(*this)); |
187 | 0 | } |
188 | 0 | void DependencyCollector::attachToASTReader(ASTReader &R) { |
189 | 0 | R.addListener( |
190 | 0 | std::make_unique<DepCollectorASTListener>(*this, R.getFileManager())); |
191 | 0 | } |
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 | 0 | OutputFormat(Opts.OutputFormat), InputFileIndex(0) { |
201 | 0 | for (const auto &ExtraDep : Opts.ExtraDeps) { |
202 | 0 | if (addDependency(ExtraDep.first)) |
203 | 0 | ++InputFileIndex; |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 0 | void DependencyFileGenerator::attachToPreprocessor(Preprocessor &PP) { |
208 | | // Disable the "file not found" diagnostic if the -MG option was given. |
209 | 0 | if (AddMissingHeaderDeps) |
210 | 0 | PP.SetSuppressIncludeNotFoundError(true); |
211 | |
|
212 | 0 | DependencyCollector::attachToPreprocessor(PP); |
213 | 0 | } |
214 | | |
215 | | bool DependencyFileGenerator::sawDependency(StringRef Filename, bool FromModule, |
216 | | bool IsSystem, bool IsModuleFile, |
217 | 0 | bool IsMissing) { |
218 | 0 | if (IsMissing) { |
219 | | // Handle the case of missing file from an inclusion directive. |
220 | 0 | if (AddMissingHeaderDeps) |
221 | 0 | return true; |
222 | 0 | SeenMissingHeader = true; |
223 | 0 | return false; |
224 | 0 | } |
225 | 0 | if (IsModuleFile && !IncludeModuleFiles) |
226 | 0 | return false; |
227 | | |
228 | 0 | if (isSpecialFilename(Filename)) |
229 | 0 | return false; |
230 | | |
231 | 0 | if (IncludeSystemHeaders) |
232 | 0 | return true; |
233 | | |
234 | 0 | return !IsSystem; |
235 | 0 | } |
236 | | |
237 | 0 | void DependencyFileGenerator::finishedMainFile(DiagnosticsEngine &Diags) { |
238 | 0 | outputDependencyFile(Diags); |
239 | 0 | } |
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 | 0 | DependencyOutputFormat OutputFormat) { |
291 | | // Convert filename to platform native path |
292 | 0 | llvm::SmallString<256> NativePath; |
293 | 0 | llvm::sys::path::native(Filename.str(), NativePath); |
294 | |
|
295 | 0 | 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 | 0 | if (NativePath.find_first_of(" #${}^!") != StringRef::npos) |
300 | 0 | OS << '\"' << NativePath << '\"'; |
301 | 0 | else |
302 | 0 | OS << NativePath; |
303 | 0 | return; |
304 | 0 | } |
305 | 0 | assert(OutputFormat == DependencyOutputFormat::Make); |
306 | 0 | for (unsigned i = 0, e = NativePath.size(); i != e; ++i) { |
307 | 0 | if (NativePath[i] == '#') // Handle '#' the broken gcc way. |
308 | 0 | OS << '\\'; |
309 | 0 | else if (NativePath[i] == ' ') { // Handle space correctly. |
310 | 0 | OS << '\\'; |
311 | 0 | unsigned j = i; |
312 | 0 | while (j > 0 && NativePath[--j] == '\\') |
313 | 0 | OS << '\\'; |
314 | 0 | } else if (NativePath[i] == '$') // $ is escaped by $$. |
315 | 0 | OS << '$'; |
316 | 0 | OS << NativePath[i]; |
317 | 0 | } |
318 | 0 | } |
319 | | |
320 | 0 | void DependencyFileGenerator::outputDependencyFile(DiagnosticsEngine &Diags) { |
321 | 0 | if (SeenMissingHeader) { |
322 | 0 | llvm::sys::fs::remove(OutputFile); |
323 | 0 | return; |
324 | 0 | } |
325 | | |
326 | 0 | std::error_code EC; |
327 | 0 | llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
328 | 0 | if (EC) { |
329 | 0 | Diags.Report(diag::err_fe_error_opening) << OutputFile << EC.message(); |
330 | 0 | return; |
331 | 0 | } |
332 | | |
333 | 0 | outputDependencyFile(OS); |
334 | 0 | } |
335 | | |
336 | 0 | 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 | 0 | const unsigned MaxColumns = 75; |
342 | 0 | unsigned Columns = 0; |
343 | |
|
344 | 0 | for (StringRef Target : Targets) { |
345 | 0 | unsigned N = Target.size(); |
346 | 0 | if (Columns == 0) { |
347 | 0 | Columns += N; |
348 | 0 | } else if (Columns + N + 2 > MaxColumns) { |
349 | 0 | Columns = N + 2; |
350 | 0 | OS << " \\\n "; |
351 | 0 | } else { |
352 | 0 | Columns += N + 1; |
353 | 0 | OS << ' '; |
354 | 0 | } |
355 | | // Targets already quoted as needed. |
356 | 0 | OS << Target; |
357 | 0 | } |
358 | |
|
359 | 0 | OS << ':'; |
360 | 0 | Columns += 1; |
361 | | |
362 | | // Now add each dependency in the order it was seen, but avoiding |
363 | | // duplicates. |
364 | 0 | ArrayRef<std::string> Files = getDependencies(); |
365 | 0 | for (StringRef File : Files) { |
366 | 0 | if (File == "<stdin>") |
367 | 0 | 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 | 0 | unsigned N = File.size(); |
372 | 0 | if (Columns + (N + 1) + 2 > MaxColumns) { |
373 | 0 | OS << " \\\n "; |
374 | 0 | Columns = 2; |
375 | 0 | } |
376 | 0 | OS << ' '; |
377 | 0 | PrintFilename(OS, File, OutputFormat); |
378 | 0 | Columns += N + 1; |
379 | 0 | } |
380 | 0 | OS << '\n'; |
381 | | |
382 | | // Create phony targets if requested. |
383 | 0 | if (PhonyTarget && !Files.empty()) { |
384 | 0 | unsigned Index = 0; |
385 | 0 | for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { |
386 | 0 | if (Index++ == InputFileIndex) |
387 | 0 | continue; |
388 | 0 | PrintFilename(OS, *I, OutputFormat); |
389 | 0 | OS << ":\n"; |
390 | 0 | } |
391 | 0 | } |
392 | 0 | } |