600 lines
17 KiB
C++
600 lines
17 KiB
C++
/*============================================================================
|
|
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 "cmMakefile.h"
|
|
#include "cmLocalGenerator.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmGeneratedFileStream.h"
|
|
|
|
|
|
|
|
static const char* getShapeForTarget(const cmTarget* target)
|
|
{
|
|
if (!target)
|
|
{
|
|
return "ellipse";
|
|
}
|
|
|
|
switch ( target->GetType() )
|
|
{
|
|
case cmTarget::EXECUTABLE:
|
|
return "house";
|
|
case cmTarget::STATIC_LIBRARY:
|
|
return "diamond";
|
|
case cmTarget::SHARED_LIBRARY:
|
|
return "polygon";
|
|
case cmTarget::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")
|
|
,GenerateForExecutables(true)
|
|
,GenerateForStaticLibs(true)
|
|
,GenerateForSharedLibs(true)
|
|
,GenerateForModuleLibs(true)
|
|
,GenerateForExternals(true)
|
|
,GeneratePerTarget(true)
|
|
,GenerateDependers(true)
|
|
,LocalGenerators(localGenerators)
|
|
,HaveTargetsAndLibs(false)
|
|
{
|
|
}
|
|
|
|
|
|
void cmGraphVizWriter::ReadSettings(const char* settingsFileName,
|
|
const char* fallbackSettingsFileName)
|
|
{
|
|
cmake cm;
|
|
cmGlobalGenerator ggi;
|
|
ggi.SetCMakeInstance(&cm);
|
|
cmsys::auto_ptr<cmLocalGenerator> lg(ggi.CreateLocalGenerator());
|
|
cmMakefile *mf = lg->GetMakefile();
|
|
|
|
const char* inFileName = settingsFileName;
|
|
|
|
if ( !cmSystemTools::FileExists(inFileName) )
|
|
{
|
|
inFileName = fallbackSettingsFileName;
|
|
if ( !cmSystemTools::FileExists(inFileName) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !mf->ReadListFile(0, 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.size() > 0)
|
|
{
|
|
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 == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->CollectTargetsAndLibs();
|
|
|
|
for(std::map<std::string, const cmTarget*>::const_iterator ptrIt =
|
|
this->TargetPtrs.begin();
|
|
ptrIt != this->TargetPtrs.end();
|
|
++ptrIt)
|
|
{
|
|
if (ptrIt->second == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (this->GenerateForTargetType(ptrIt->second->GetType()) == false)
|
|
{
|
|
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 == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->CollectTargetsAndLibs();
|
|
|
|
for(std::map<std::string, const cmTarget*>::const_iterator ptrIt =
|
|
this->TargetPtrs.begin();
|
|
ptrIt != this->TargetPtrs.end();
|
|
++ptrIt)
|
|
{
|
|
if (ptrIt->second == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (this->GenerateForTargetType(ptrIt->second->GetType()) == false)
|
|
{
|
|
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 cmTarget*>::const_iterator ptrIt =
|
|
this->TargetPtrs.begin();
|
|
ptrIt != this->TargetPtrs.end();
|
|
++ptrIt)
|
|
{
|
|
if (ptrIt->second == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (this->GenerateForTargetType(ptrIt->second->GetType()) == false)
|
|
{
|
|
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 cmTarget* >::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 == NULL) // it's an external library
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
std::string myNodeName = this->TargetNamesNodes.find(targetName)->second;
|
|
|
|
const cmTarget::LinkLibraryVectorType* ll =
|
|
&(targetPtrIt->second->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 cmTarget* >::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 == NULL) // 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 cmTarget*>::const_iterator dependerIt =
|
|
this->TargetPtrs.begin();
|
|
dependerIt != this->TargetPtrs.end();
|
|
++dependerIt)
|
|
{
|
|
if (dependerIt->second == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (this->GenerateForTargetType(dependerIt->second->GetType()) == false)
|
|
{
|
|
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->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 cmTarget* 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 == false)
|
|
{
|
|
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 )
|
|
{
|
|
const cmTargets* targets = &((*lit)->GetMakefile()->GetTargets());
|
|
for ( cmTargets::const_iterator tit = targets->begin();
|
|
tit != targets->end();
|
|
++ tit )
|
|
{
|
|
const char* realTargetName = tit->first.c_str();
|
|
if(this->IgnoreThisTarget(realTargetName))
|
|
{
|
|
// Skip ignored targets
|
|
continue;
|
|
}
|
|
//std::cout << "Found target: " << tit->first.c_str() << std::endl;
|
|
cmOStringStream ostr;
|
|
ostr << this->GraphNodePrefix << cnt++;
|
|
this->TargetNamesNodes[realTargetName] = ostr.str();
|
|
this->TargetPtrs[realTargetName] = &tit->second;
|
|
}
|
|
}
|
|
|
|
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 )
|
|
{
|
|
const cmTargets* targets = &((*lit)->GetMakefile()->GetTargets());
|
|
for ( cmTargets::const_iterator tit = targets->begin();
|
|
tit != targets->end();
|
|
++ tit )
|
|
{
|
|
const char* realTargetName = tit->first.c_str();
|
|
if (this->IgnoreThisTarget(realTargetName))
|
|
{
|
|
// Skip ignored targets
|
|
continue;
|
|
}
|
|
const cmTarget::LinkLibraryVectorType* ll =
|
|
&(tit->second.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 cmTarget*>::const_iterator tarIt =
|
|
this->TargetPtrs.find(libName);
|
|
if ( tarIt == this->TargetPtrs.end() )
|
|
{
|
|
cmOStringStream ostr;
|
|
ostr << this->GraphNodePrefix << cnt++;
|
|
this->TargetNamesNodes[libName] = ostr.str();
|
|
this->TargetPtrs[libName] = NULL;
|
|
// str << " \"" << ostr.c_str() << "\" [ 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(cmTarget::TargetType targetType)
|
|
const
|
|
{
|
|
switch (targetType)
|
|
{
|
|
case cmTarget::EXECUTABLE:
|
|
return this->GenerateForExecutables;
|
|
case cmTarget::STATIC_LIBRARY:
|
|
return this->GenerateForStaticLibs;
|
|
case cmTarget::SHARED_LIBRARY:
|
|
return this->GenerateForSharedLibs;
|
|
case cmTarget::MODULE_LIBRARY:
|
|
return this->GenerateForModuleLibs;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|