CMake/Source/cmGraphVizWriter.cxx
2016-11-08 09:42:04 -05:00

515 lines
16 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmGraphVizWriter.h"
#include <cmConfigure.h>
#include <iostream>
#include <sstream>
#include <utility>
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmStateSnapshot.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cm_auto_ptr.hxx"
#include "cmake.h"
static const char* getShapeForTarget(const cmGeneratorTarget* target)
{
if (!target) {
return "ellipse";
}
switch (target->GetType()) {
case cmStateEnums::EXECUTABLE:
return "house";
case cmStateEnums::STATIC_LIBRARY:
return "diamond";
case cmStateEnums::SHARED_LIBRARY:
return "polygon";
case cmStateEnums::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(
cmStateEnums::TargetType targetType) const
{
switch (targetType) {
case cmStateEnums::EXECUTABLE:
return this->GenerateForExecutables;
case cmStateEnums::STATIC_LIBRARY:
return this->GenerateForStaticLibs;
case cmStateEnums::SHARED_LIBRARY:
return this->GenerateForSharedLibs;
case cmStateEnums::MODULE_LIBRARY:
return this->GenerateForModuleLibs;
default:
break;
}
return false;
}