diff options
Diffstat (limited to 'Source/cmGraphVizWriter.cxx')
-rw-r--r-- | Source/cmGraphVizWriter.cxx | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/Source/cmGraphVizWriter.cxx b/Source/cmGraphVizWriter.cxx new file mode 100644 index 0000000..adb9936 --- /dev/null +++ b/Source/cmGraphVizWriter.cxx @@ -0,0 +1,512 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "cmGraphVizWriter.h" + +#include "cmGeneratedFileStream.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" + +static const char* getShapeForTarget(const cmGeneratorTarget* target) +{ + if (!target) { + return "ellipse"; + } + + switch (target->GetType()) { + case cmState::EXECUTABLE: + return "house"; + case cmState::STATIC_LIBRARY: + return "diamond"; + case cmState::SHARED_LIBRARY: + return "polygon"; + case cmState::MODULE_LIBRARY: + return "octagon"; + default: + break; + } + + return "box"; +} + +cmGraphVizWriter::cmGraphVizWriter( + const std::vector<cmLocalGenerator*>& localGenerators) + : GraphType("digraph") + , GraphName("GG") + , GraphHeader("node [\n fontsize = \"12\"\n];") + , GraphNodePrefix("node") + , LocalGenerators(localGenerators) + , GenerateForExecutables(true) + , GenerateForStaticLibs(true) + , GenerateForSharedLibs(true) + , GenerateForModuleLibs(true) + , GenerateForExternals(true) + , GeneratePerTarget(true) + , GenerateDependers(true) + , HaveTargetsAndLibs(false) +{ +} + +void cmGraphVizWriter::ReadSettings(const char* settingsFileName, + const char* fallbackSettingsFileName) +{ + cmake cm; + cm.SetHomeDirectory(""); + cm.SetHomeOutputDirectory(""); + cm.GetCurrentSnapshot().SetDefaultDefinitions(); + cmGlobalGenerator ggi(&cm); + CM_AUTO_PTR<cmMakefile> mf(new cmMakefile(&ggi, cm.GetCurrentSnapshot())); + CM_AUTO_PTR<cmLocalGenerator> lg(ggi.CreateLocalGenerator(mf.get())); + + const char* inFileName = settingsFileName; + + if (!cmSystemTools::FileExists(inFileName)) { + inFileName = fallbackSettingsFileName; + if (!cmSystemTools::FileExists(inFileName)) { + return; + } + } + + if (!mf->ReadListFile(inFileName)) { + cmSystemTools::Error("Problem opening GraphViz options file: ", + inFileName); + return; + } + + std::cout << "Reading GraphViz options file: " << inFileName << std::endl; + +#define __set_if_set(var, cmakeDefinition) \ + { \ + const char* value = mf->GetDefinition(cmakeDefinition); \ + if (value) { \ + var = value; \ + } \ + } + + __set_if_set(this->GraphType, "GRAPHVIZ_GRAPH_TYPE"); + __set_if_set(this->GraphName, "GRAPHVIZ_GRAPH_NAME"); + __set_if_set(this->GraphHeader, "GRAPHVIZ_GRAPH_HEADER"); + __set_if_set(this->GraphNodePrefix, "GRAPHVIZ_NODE_PREFIX"); + +#define __set_bool_if_set(var, cmakeDefinition) \ + { \ + const char* value = mf->GetDefinition(cmakeDefinition); \ + if (value) { \ + var = mf->IsOn(cmakeDefinition); \ + } \ + } + + __set_bool_if_set(this->GenerateForExecutables, "GRAPHVIZ_EXECUTABLES"); + __set_bool_if_set(this->GenerateForStaticLibs, "GRAPHVIZ_STATIC_LIBS"); + __set_bool_if_set(this->GenerateForSharedLibs, "GRAPHVIZ_SHARED_LIBS"); + __set_bool_if_set(this->GenerateForModuleLibs, "GRAPHVIZ_MODULE_LIBS"); + __set_bool_if_set(this->GenerateForExternals, "GRAPHVIZ_EXTERNAL_LIBS"); + __set_bool_if_set(this->GeneratePerTarget, "GRAPHVIZ_GENERATE_PER_TARGET"); + __set_bool_if_set(this->GenerateDependers, "GRAPHVIZ_GENERATE_DEPENDERS"); + + std::string ignoreTargetsRegexes; + __set_if_set(ignoreTargetsRegexes, "GRAPHVIZ_IGNORE_TARGETS"); + + this->TargetsToIgnoreRegex.clear(); + if (!ignoreTargetsRegexes.empty()) { + std::vector<std::string> ignoreTargetsRegExVector; + cmSystemTools::ExpandListArgument(ignoreTargetsRegexes, + ignoreTargetsRegExVector); + for (std::vector<std::string>::const_iterator itvIt = + ignoreTargetsRegExVector.begin(); + itvIt != ignoreTargetsRegExVector.end(); ++itvIt) { + std::string currentRegexString(*itvIt); + cmsys::RegularExpression currentRegex; + if (!currentRegex.compile(currentRegexString.c_str())) { + std::cerr << "Could not compile bad regex \"" << currentRegexString + << "\"" << std::endl; + } + this->TargetsToIgnoreRegex.push_back(currentRegex); + } + } +} + +// Iterate over all targets and write for each one a graph which shows +// which other targets depend on it. +void cmGraphVizWriter::WriteTargetDependersFiles(const char* fileName) +{ + if (!this->GenerateDependers) { + return; + } + + this->CollectTargetsAndLibs(); + + for (std::map<std::string, const cmGeneratorTarget*>::const_iterator ptrIt = + this->TargetPtrs.begin(); + ptrIt != this->TargetPtrs.end(); ++ptrIt) { + if (ptrIt->second == CM_NULLPTR) { + continue; + } + + if (!this->GenerateForTargetType(ptrIt->second->GetType())) { + continue; + } + + std::string currentFilename = fileName; + currentFilename += "."; + currentFilename += ptrIt->first; + currentFilename += ".dependers"; + + cmGeneratedFileStream str(currentFilename.c_str()); + if (!str) { + return; + } + + std::set<std::string> insertedConnections; + std::set<std::string> insertedNodes; + + std::cout << "Writing " << currentFilename << "..." << std::endl; + this->WriteHeader(str); + + this->WriteDependerConnections(ptrIt->first, insertedNodes, + insertedConnections, str); + + this->WriteFooter(str); + } +} + +// Iterate over all targets and write for each one a graph which shows +// on which targets it depends. +void cmGraphVizWriter::WritePerTargetFiles(const char* fileName) +{ + if (!this->GeneratePerTarget) { + return; + } + + this->CollectTargetsAndLibs(); + + for (std::map<std::string, const cmGeneratorTarget*>::const_iterator ptrIt = + this->TargetPtrs.begin(); + ptrIt != this->TargetPtrs.end(); ++ptrIt) { + if (ptrIt->second == CM_NULLPTR) { + continue; + } + + if (!this->GenerateForTargetType(ptrIt->second->GetType())) { + continue; + } + + std::set<std::string> insertedConnections; + std::set<std::string> insertedNodes; + + std::string currentFilename = fileName; + currentFilename += "."; + currentFilename += ptrIt->first; + cmGeneratedFileStream str(currentFilename.c_str()); + if (!str) { + return; + } + + std::cout << "Writing " << currentFilename << "..." << std::endl; + this->WriteHeader(str); + + this->WriteConnections(ptrIt->first, insertedNodes, insertedConnections, + str); + this->WriteFooter(str); + } +} + +void cmGraphVizWriter::WriteGlobalFile(const char* fileName) +{ + this->CollectTargetsAndLibs(); + + cmGeneratedFileStream str(fileName); + if (!str) { + return; + } + this->WriteHeader(str); + + std::cout << "Writing " << fileName << "..." << std::endl; + + std::set<std::string> insertedConnections; + std::set<std::string> insertedNodes; + + for (std::map<std::string, const cmGeneratorTarget*>::const_iterator ptrIt = + this->TargetPtrs.begin(); + ptrIt != this->TargetPtrs.end(); ++ptrIt) { + if (ptrIt->second == CM_NULLPTR) { + continue; + } + + if (!this->GenerateForTargetType(ptrIt->second->GetType())) { + continue; + } + + this->WriteConnections(ptrIt->first, insertedNodes, insertedConnections, + str); + } + this->WriteFooter(str); +} + +void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& str) const +{ + str << this->GraphType << " \"" << this->GraphName << "\" {" << std::endl; + str << this->GraphHeader << std::endl; +} + +void cmGraphVizWriter::WriteFooter(cmGeneratedFileStream& str) const +{ + str << "}" << std::endl; +} + +void cmGraphVizWriter::WriteConnections( + const std::string& targetName, std::set<std::string>& insertedNodes, + std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const +{ + std::map<std::string, const cmGeneratorTarget*>::const_iterator targetPtrIt = + this->TargetPtrs.find(targetName); + + if (targetPtrIt == this->TargetPtrs.end()) // not found at all + { + return; + } + + this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str); + + if (targetPtrIt->second == CM_NULLPTR) // it's an external library + { + return; + } + + std::string myNodeName = this->TargetNamesNodes.find(targetName)->second; + + const cmTarget::LinkLibraryVectorType* ll = + &(targetPtrIt->second->Target->GetOriginalLinkLibraries()); + + for (cmTarget::LinkLibraryVectorType::const_iterator llit = ll->begin(); + llit != ll->end(); ++llit) { + const char* libName = llit->first.c_str(); + std::map<std::string, std::string>::const_iterator libNameIt = + this->TargetNamesNodes.find(libName); + + // can happen e.g. if GRAPHVIZ_TARGET_IGNORE_REGEX is used + if (libNameIt == this->TargetNamesNodes.end()) { + continue; + } + + std::string connectionName = myNodeName; + connectionName += "-"; + connectionName += libNameIt->second; + if (insertedConnections.find(connectionName) == + insertedConnections.end()) { + insertedConnections.insert(connectionName); + this->WriteNode(libName, this->TargetPtrs.find(libName)->second, + insertedNodes, str); + + str << " \"" << myNodeName << "\" -> \"" << libNameIt->second << "\""; + str << " // " << targetName << " -> " << libName << std::endl; + this->WriteConnections(libName, insertedNodes, insertedConnections, str); + } + } +} + +void cmGraphVizWriter::WriteDependerConnections( + const std::string& targetName, std::set<std::string>& insertedNodes, + std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const +{ + std::map<std::string, const cmGeneratorTarget*>::const_iterator targetPtrIt = + this->TargetPtrs.find(targetName); + + if (targetPtrIt == this->TargetPtrs.end()) // not found at all + { + return; + } + + this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str); + + if (targetPtrIt->second == CM_NULLPTR) // it's an external library + { + return; + } + + std::string myNodeName = this->TargetNamesNodes.find(targetName)->second; + + // now search who links against me + for (std::map<std::string, const cmGeneratorTarget*>::const_iterator + dependerIt = this->TargetPtrs.begin(); + dependerIt != this->TargetPtrs.end(); ++dependerIt) { + if (dependerIt->second == CM_NULLPTR) { + continue; + } + + if (!this->GenerateForTargetType(dependerIt->second->GetType())) { + continue; + } + + // Now we have a target, check whether it links against targetName. + // If so, draw a connection, and then continue with dependers on that one. + const cmTarget::LinkLibraryVectorType* ll = + &(dependerIt->second->Target->GetOriginalLinkLibraries()); + + for (cmTarget::LinkLibraryVectorType::const_iterator llit = ll->begin(); + llit != ll->end(); ++llit) { + std::string libName = llit->first; + if (libName == targetName) { + // So this target links against targetName. + std::map<std::string, std::string>::const_iterator dependerNodeNameIt = + this->TargetNamesNodes.find(dependerIt->first); + + if (dependerNodeNameIt != this->TargetNamesNodes.end()) { + std::string connectionName = dependerNodeNameIt->second; + connectionName += "-"; + connectionName += myNodeName; + + if (insertedConnections.find(connectionName) == + insertedConnections.end()) { + insertedConnections.insert(connectionName); + this->WriteNode(dependerIt->first, dependerIt->second, + insertedNodes, str); + + str << " \"" << dependerNodeNameIt->second << "\" -> \"" + << myNodeName << "\""; + str << " // " << targetName << " -> " << dependerIt->first + << std::endl; + this->WriteDependerConnections(dependerIt->first, insertedNodes, + insertedConnections, str); + } + } + break; + } + } + } +} + +void cmGraphVizWriter::WriteNode(const std::string& targetName, + const cmGeneratorTarget* target, + std::set<std::string>& insertedNodes, + cmGeneratedFileStream& str) const +{ + if (insertedNodes.find(targetName) == insertedNodes.end()) { + insertedNodes.insert(targetName); + std::map<std::string, std::string>::const_iterator nameIt = + this->TargetNamesNodes.find(targetName); + + str << " \"" << nameIt->second << "\" [ label=\"" << targetName + << "\" shape=\"" << getShapeForTarget(target) << "\"];" << std::endl; + } +} + +void cmGraphVizWriter::CollectTargetsAndLibs() +{ + if (!this->HaveTargetsAndLibs) { + this->HaveTargetsAndLibs = true; + int cnt = this->CollectAllTargets(); + if (this->GenerateForExternals) { + this->CollectAllExternalLibs(cnt); + } + } +} + +int cmGraphVizWriter::CollectAllTargets() +{ + int cnt = 0; + // First pass get the list of all cmake targets + for (std::vector<cmLocalGenerator*>::const_iterator lit = + this->LocalGenerators.begin(); + lit != this->LocalGenerators.end(); ++lit) { + std::vector<cmGeneratorTarget*> targets = (*lit)->GetGeneratorTargets(); + for (std::vector<cmGeneratorTarget*>::const_iterator it = targets.begin(); + it != targets.end(); ++it) { + const char* realTargetName = (*it)->GetName().c_str(); + if (this->IgnoreThisTarget(realTargetName)) { + // Skip ignored targets + continue; + } + // std::cout << "Found target: " << tit->first << std::endl; + std::ostringstream ostr; + ostr << this->GraphNodePrefix << cnt++; + this->TargetNamesNodes[realTargetName] = ostr.str(); + this->TargetPtrs[realTargetName] = *it; + } + } + + return cnt; +} + +int cmGraphVizWriter::CollectAllExternalLibs(int cnt) +{ + // Ok, now find all the stuff we link to that is not in cmake + for (std::vector<cmLocalGenerator*>::const_iterator lit = + this->LocalGenerators.begin(); + lit != this->LocalGenerators.end(); ++lit) { + std::vector<cmGeneratorTarget*> targets = (*lit)->GetGeneratorTargets(); + for (std::vector<cmGeneratorTarget*>::const_iterator it = targets.begin(); + it != targets.end(); ++it) { + const char* realTargetName = (*it)->GetName().c_str(); + if (this->IgnoreThisTarget(realTargetName)) { + // Skip ignored targets + continue; + } + const cmTarget::LinkLibraryVectorType* ll = + &((*it)->Target->GetOriginalLinkLibraries()); + for (cmTarget::LinkLibraryVectorType::const_iterator llit = ll->begin(); + llit != ll->end(); ++llit) { + const char* libName = llit->first.c_str(); + if (this->IgnoreThisTarget(libName)) { + // Skip ignored targets + continue; + } + + std::map<std::string, const cmGeneratorTarget*>::const_iterator tarIt = + this->TargetPtrs.find(libName); + if (tarIt == this->TargetPtrs.end()) { + std::ostringstream ostr; + ostr << this->GraphNodePrefix << cnt++; + this->TargetNamesNodes[libName] = ostr.str(); + this->TargetPtrs[libName] = CM_NULLPTR; + // str << " \"" << ostr << "\" [ label=\"" << libName + // << "\" shape=\"ellipse\"];" << std::endl; + } + } + } + } + return cnt; +} + +bool cmGraphVizWriter::IgnoreThisTarget(const std::string& name) +{ + for (std::vector<cmsys::RegularExpression>::iterator itvIt = + this->TargetsToIgnoreRegex.begin(); + itvIt != this->TargetsToIgnoreRegex.end(); ++itvIt) { + cmsys::RegularExpression& regEx = *itvIt; + if (regEx.is_valid()) { + if (regEx.find(name)) { + return true; + } + } + } + + return false; +} + +bool cmGraphVizWriter::GenerateForTargetType( + cmState::TargetType targetType) const +{ + switch (targetType) { + case cmState::EXECUTABLE: + return this->GenerateForExecutables; + case cmState::STATIC_LIBRARY: + return this->GenerateForStaticLibs; + case cmState::SHARED_LIBRARY: + return this->GenerateForSharedLibs; + case cmState::MODULE_LIBRARY: + return this->GenerateForModuleLibs; + default: + break; + } + return false; +} |