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