//===--- DependencyGraph.cpp - Generate dependency file -------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This code generates a header dependency graph in DOT format, for use // with, e.g., GraphViz. // //===----------------------------------------------------------------------===// #include "clang/Frontend/Utils.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SetVector.h" #include "llvm/Support/GraphWriter.h" #include "llvm/Support/raw_ostream.h" using namespace clang; namespace DOT = llvm::DOT; namespace { class DependencyGraphCallback : public PPCallbacks { const Preprocessor *PP; std::string OutputFile; std::string SysRoot; llvm::SetVector AllFiles; typedef llvm::DenseMap > DependencyMap; DependencyMap Dependencies; private: raw_ostream &writeNodeReference(raw_ostream &OS, const FileEntry *Node); void OutputGraphFile(); public: DependencyGraphCallback(const Preprocessor *_PP, StringRef OutputFile, StringRef SysRoot) : PP(_PP), OutputFile(OutputFile.str()), SysRoot(SysRoot.str()) { } void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported, SrcMgr::CharacteristicKind FileType) override; void EndOfMainFile() override { OutputGraphFile(); } }; } void clang::AttachDependencyGraphGen(Preprocessor &PP, StringRef OutputFile, StringRef SysRoot) { PP.addPPCallbacks(std::make_unique(&PP, OutputFile, SysRoot)); } void DependencyGraphCallback::InclusionDirective( SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported, SrcMgr::CharacteristicKind FileType) { if (!File) return; SourceManager &SM = PP->getSourceManager(); const FileEntry *FromFile = SM.getFileEntryForID(SM.getFileID(SM.getExpansionLoc(HashLoc))); if (!FromFile) return; Dependencies[FromFile].push_back(File); AllFiles.insert(File); AllFiles.insert(FromFile); } raw_ostream & DependencyGraphCallback::writeNodeReference(raw_ostream &OS, const FileEntry *Node) { OS << "header_" << Node->getUID(); return OS; } void DependencyGraphCallback::OutputGraphFile() { std::error_code EC; llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_Text); if (EC) { PP->getDiagnostics().Report(diag::err_fe_error_opening) << OutputFile << EC.message(); return; } OS << "digraph \"dependencies\" {\n"; // Write the nodes for (unsigned I = 0, N = AllFiles.size(); I != N; ++I) { // Write the node itself. OS.indent(2); writeNodeReference(OS, AllFiles[I]); OS << " [ shape=\"box\", label=\""; StringRef FileName = AllFiles[I]->getName(); if (FileName.startswith(SysRoot)) FileName = FileName.substr(SysRoot.size()); OS << DOT::EscapeString(FileName) << "\"];\n"; } // Write the edges for (DependencyMap::iterator F = Dependencies.begin(), FEnd = Dependencies.end(); F != FEnd; ++F) { for (unsigned I = 0, N = F->second.size(); I != N; ++I) { OS.indent(2); writeNodeReference(OS, F->first); OS << " -> "; writeNodeReference(OS, F->second[I]); OS << ";\n"; } } OS << "}\n"; }