cmGraphVizWriter.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmGraphVizWriter.h"
  4. #include <iostream>
  5. #include <sstream>
  6. #include <utility>
  7. #include "cmGeneratedFileStream.h"
  8. #include "cmGeneratorTarget.h"
  9. #include "cmGlobalGenerator.h"
  10. #include "cmLocalGenerator.h"
  11. #include "cmMakefile.h"
  12. #include "cmStateSnapshot.h"
  13. #include "cmSystemTools.h"
  14. #include "cmTarget.h"
  15. #include "cm_auto_ptr.hxx"
  16. #include "cmake.h"
  17. static const char* getShapeForTarget(const cmGeneratorTarget* target)
  18. {
  19. if (!target) {
  20. return "ellipse";
  21. }
  22. switch (target->GetType()) {
  23. case cmStateEnums::EXECUTABLE:
  24. return "house";
  25. case cmStateEnums::STATIC_LIBRARY:
  26. return "diamond";
  27. case cmStateEnums::SHARED_LIBRARY:
  28. return "polygon";
  29. case cmStateEnums::MODULE_LIBRARY:
  30. return "octagon";
  31. default:
  32. break;
  33. }
  34. return "box";
  35. }
  36. cmGraphVizWriter::cmGraphVizWriter(
  37. const std::vector<cmLocalGenerator*>& localGenerators)
  38. : GraphType("digraph")
  39. , GraphName("GG")
  40. , GraphHeader("node [\n fontsize = \"12\"\n];")
  41. , GraphNodePrefix("node")
  42. , LocalGenerators(localGenerators)
  43. , GenerateForExecutables(true)
  44. , GenerateForStaticLibs(true)
  45. , GenerateForSharedLibs(true)
  46. , GenerateForModuleLibs(true)
  47. , GenerateForExternals(true)
  48. , GeneratePerTarget(true)
  49. , GenerateDependers(true)
  50. , HaveTargetsAndLibs(false)
  51. {
  52. }
  53. void cmGraphVizWriter::ReadSettings(const char* settingsFileName,
  54. const char* fallbackSettingsFileName)
  55. {
  56. cmake cm(cmake::RoleScript);
  57. cm.SetHomeDirectory("");
  58. cm.SetHomeOutputDirectory("");
  59. cm.GetCurrentSnapshot().SetDefaultDefinitions();
  60. cmGlobalGenerator ggi(&cm);
  61. CM_AUTO_PTR<cmMakefile> mf(new cmMakefile(&ggi, cm.GetCurrentSnapshot()));
  62. CM_AUTO_PTR<cmLocalGenerator> lg(ggi.CreateLocalGenerator(mf.get()));
  63. const char* inFileName = settingsFileName;
  64. if (!cmSystemTools::FileExists(inFileName)) {
  65. inFileName = fallbackSettingsFileName;
  66. if (!cmSystemTools::FileExists(inFileName)) {
  67. return;
  68. }
  69. }
  70. if (!mf->ReadListFile(inFileName)) {
  71. cmSystemTools::Error("Problem opening GraphViz options file: ",
  72. inFileName);
  73. return;
  74. }
  75. std::cout << "Reading GraphViz options file: " << inFileName << std::endl;
  76. #define __set_if_set(var, cmakeDefinition) \
  77. { \
  78. const char* value = mf->GetDefinition(cmakeDefinition); \
  79. if (value) { \
  80. (var) = value; \
  81. } \
  82. }
  83. __set_if_set(this->GraphType, "GRAPHVIZ_GRAPH_TYPE");
  84. __set_if_set(this->GraphName, "GRAPHVIZ_GRAPH_NAME");
  85. __set_if_set(this->GraphHeader, "GRAPHVIZ_GRAPH_HEADER");
  86. __set_if_set(this->GraphNodePrefix, "GRAPHVIZ_NODE_PREFIX");
  87. #define __set_bool_if_set(var, cmakeDefinition) \
  88. { \
  89. const char* value = mf->GetDefinition(cmakeDefinition); \
  90. if (value) { \
  91. (var) = mf->IsOn(cmakeDefinition); \
  92. } \
  93. }
  94. __set_bool_if_set(this->GenerateForExecutables, "GRAPHVIZ_EXECUTABLES");
  95. __set_bool_if_set(this->GenerateForStaticLibs, "GRAPHVIZ_STATIC_LIBS");
  96. __set_bool_if_set(this->GenerateForSharedLibs, "GRAPHVIZ_SHARED_LIBS");
  97. __set_bool_if_set(this->GenerateForModuleLibs, "GRAPHVIZ_MODULE_LIBS");
  98. __set_bool_if_set(this->GenerateForExternals, "GRAPHVIZ_EXTERNAL_LIBS");
  99. __set_bool_if_set(this->GeneratePerTarget, "GRAPHVIZ_GENERATE_PER_TARGET");
  100. __set_bool_if_set(this->GenerateDependers, "GRAPHVIZ_GENERATE_DEPENDERS");
  101. std::string ignoreTargetsRegexes;
  102. __set_if_set(ignoreTargetsRegexes, "GRAPHVIZ_IGNORE_TARGETS");
  103. this->TargetsToIgnoreRegex.clear();
  104. if (!ignoreTargetsRegexes.empty()) {
  105. std::vector<std::string> ignoreTargetsRegExVector;
  106. cmSystemTools::ExpandListArgument(ignoreTargetsRegexes,
  107. ignoreTargetsRegExVector);
  108. for (std::string const& currentRegexString : ignoreTargetsRegExVector) {
  109. cmsys::RegularExpression currentRegex;
  110. if (!currentRegex.compile(currentRegexString.c_str())) {
  111. std::cerr << "Could not compile bad regex \"" << currentRegexString
  112. << "\"" << std::endl;
  113. }
  114. this->TargetsToIgnoreRegex.push_back(currentRegex);
  115. }
  116. }
  117. }
  118. // Iterate over all targets and write for each one a graph which shows
  119. // which other targets depend on it.
  120. void cmGraphVizWriter::WriteTargetDependersFiles(const char* fileName)
  121. {
  122. if (!this->GenerateDependers) {
  123. return;
  124. }
  125. this->CollectTargetsAndLibs();
  126. for (auto const& ptr : this->TargetPtrs) {
  127. if (ptr.second == nullptr) {
  128. continue;
  129. }
  130. if (!this->GenerateForTargetType(ptr.second->GetType())) {
  131. continue;
  132. }
  133. std::string currentFilename = fileName;
  134. currentFilename += ".";
  135. currentFilename += ptr.first;
  136. currentFilename += ".dependers";
  137. cmGeneratedFileStream str(currentFilename.c_str());
  138. if (!str) {
  139. return;
  140. }
  141. std::set<std::string> insertedConnections;
  142. std::set<std::string> insertedNodes;
  143. std::cout << "Writing " << currentFilename << "..." << std::endl;
  144. this->WriteHeader(str);
  145. this->WriteDependerConnections(ptr.first, insertedNodes,
  146. insertedConnections, str);
  147. this->WriteFooter(str);
  148. }
  149. }
  150. // Iterate over all targets and write for each one a graph which shows
  151. // on which targets it depends.
  152. void cmGraphVizWriter::WritePerTargetFiles(const char* fileName)
  153. {
  154. if (!this->GeneratePerTarget) {
  155. return;
  156. }
  157. this->CollectTargetsAndLibs();
  158. for (auto const& ptr : this->TargetPtrs) {
  159. if (ptr.second == nullptr) {
  160. continue;
  161. }
  162. if (!this->GenerateForTargetType(ptr.second->GetType())) {
  163. continue;
  164. }
  165. std::set<std::string> insertedConnections;
  166. std::set<std::string> insertedNodes;
  167. std::string currentFilename = fileName;
  168. currentFilename += ".";
  169. currentFilename += ptr.first;
  170. cmGeneratedFileStream str(currentFilename.c_str());
  171. if (!str) {
  172. return;
  173. }
  174. std::cout << "Writing " << currentFilename << "..." << std::endl;
  175. this->WriteHeader(str);
  176. this->WriteConnections(ptr.first, insertedNodes, insertedConnections, str);
  177. this->WriteFooter(str);
  178. }
  179. }
  180. void cmGraphVizWriter::WriteGlobalFile(const char* fileName)
  181. {
  182. this->CollectTargetsAndLibs();
  183. cmGeneratedFileStream str(fileName);
  184. if (!str) {
  185. return;
  186. }
  187. this->WriteHeader(str);
  188. std::cout << "Writing " << fileName << "..." << std::endl;
  189. std::set<std::string> insertedConnections;
  190. std::set<std::string> insertedNodes;
  191. for (auto const& ptr : this->TargetPtrs) {
  192. if (ptr.second == nullptr) {
  193. continue;
  194. }
  195. if (!this->GenerateForTargetType(ptr.second->GetType())) {
  196. continue;
  197. }
  198. this->WriteConnections(ptr.first, insertedNodes, insertedConnections, str);
  199. }
  200. this->WriteFooter(str);
  201. }
  202. void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& str) const
  203. {
  204. str << this->GraphType << " \"" << this->GraphName << "\" {" << std::endl;
  205. str << this->GraphHeader << std::endl;
  206. }
  207. void cmGraphVizWriter::WriteFooter(cmGeneratedFileStream& str) const
  208. {
  209. str << "}" << std::endl;
  210. }
  211. void cmGraphVizWriter::WriteConnections(
  212. const std::string& targetName, std::set<std::string>& insertedNodes,
  213. std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const
  214. {
  215. std::map<std::string, const cmGeneratorTarget*>::const_iterator targetPtrIt =
  216. this->TargetPtrs.find(targetName);
  217. if (targetPtrIt == this->TargetPtrs.end()) // not found at all
  218. {
  219. return;
  220. }
  221. this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str);
  222. if (targetPtrIt->second == nullptr) // it's an external library
  223. {
  224. return;
  225. }
  226. std::string myNodeName = this->TargetNamesNodes.find(targetName)->second;
  227. const cmTarget::LinkLibraryVectorType* ll =
  228. &(targetPtrIt->second->Target->GetOriginalLinkLibraries());
  229. for (auto const& llit : *ll) {
  230. const char* libName = llit.first.c_str();
  231. std::map<std::string, std::string>::const_iterator libNameIt =
  232. this->TargetNamesNodes.find(libName);
  233. // can happen e.g. if GRAPHVIZ_TARGET_IGNORE_REGEX is used
  234. if (libNameIt == this->TargetNamesNodes.end()) {
  235. continue;
  236. }
  237. std::string connectionName = myNodeName;
  238. connectionName += "-";
  239. connectionName += libNameIt->second;
  240. if (insertedConnections.find(connectionName) ==
  241. insertedConnections.end()) {
  242. insertedConnections.insert(connectionName);
  243. this->WriteNode(libName, this->TargetPtrs.find(libName)->second,
  244. insertedNodes, str);
  245. str << " \"" << myNodeName << "\" -> \"" << libNameIt->second << "\"";
  246. str << " // " << targetName << " -> " << libName << std::endl;
  247. this->WriteConnections(libName, insertedNodes, insertedConnections, str);
  248. }
  249. }
  250. }
  251. void cmGraphVizWriter::WriteDependerConnections(
  252. const std::string& targetName, std::set<std::string>& insertedNodes,
  253. std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const
  254. {
  255. std::map<std::string, const cmGeneratorTarget*>::const_iterator targetPtrIt =
  256. this->TargetPtrs.find(targetName);
  257. if (targetPtrIt == this->TargetPtrs.end()) // not found at all
  258. {
  259. return;
  260. }
  261. this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str);
  262. if (targetPtrIt->second == nullptr) // it's an external library
  263. {
  264. return;
  265. }
  266. std::string myNodeName = this->TargetNamesNodes.find(targetName)->second;
  267. // now search who links against me
  268. for (auto const& tptr : this->TargetPtrs) {
  269. if (tptr.second == nullptr) {
  270. continue;
  271. }
  272. if (!this->GenerateForTargetType(tptr.second->GetType())) {
  273. continue;
  274. }
  275. // Now we have a target, check whether it links against targetName.
  276. // If so, draw a connection, and then continue with dependers on that one.
  277. const cmTarget::LinkLibraryVectorType* ll =
  278. &(tptr.second->Target->GetOriginalLinkLibraries());
  279. for (auto const& llit : *ll) {
  280. std::string libName = llit.first;
  281. if (libName == targetName) {
  282. // So this target links against targetName.
  283. std::map<std::string, std::string>::const_iterator dependerNodeNameIt =
  284. this->TargetNamesNodes.find(tptr.first);
  285. if (dependerNodeNameIt != this->TargetNamesNodes.end()) {
  286. std::string connectionName = dependerNodeNameIt->second;
  287. connectionName += "-";
  288. connectionName += myNodeName;
  289. if (insertedConnections.find(connectionName) ==
  290. insertedConnections.end()) {
  291. insertedConnections.insert(connectionName);
  292. this->WriteNode(tptr.first, tptr.second, insertedNodes, str);
  293. str << " \"" << dependerNodeNameIt->second << "\" -> \""
  294. << myNodeName << "\"";
  295. str << " // " << targetName << " -> " << tptr.first << std::endl;
  296. this->WriteDependerConnections(tptr.first, insertedNodes,
  297. insertedConnections, str);
  298. }
  299. }
  300. break;
  301. }
  302. }
  303. }
  304. }
  305. void cmGraphVizWriter::WriteNode(const std::string& targetName,
  306. const cmGeneratorTarget* target,
  307. std::set<std::string>& insertedNodes,
  308. cmGeneratedFileStream& str) const
  309. {
  310. if (insertedNodes.find(targetName) == insertedNodes.end()) {
  311. insertedNodes.insert(targetName);
  312. std::map<std::string, std::string>::const_iterator nameIt =
  313. this->TargetNamesNodes.find(targetName);
  314. str << " \"" << nameIt->second << "\" [ label=\"" << targetName
  315. << "\" shape=\"" << getShapeForTarget(target) << "\"];" << std::endl;
  316. }
  317. }
  318. void cmGraphVizWriter::CollectTargetsAndLibs()
  319. {
  320. if (!this->HaveTargetsAndLibs) {
  321. this->HaveTargetsAndLibs = true;
  322. int cnt = this->CollectAllTargets();
  323. if (this->GenerateForExternals) {
  324. this->CollectAllExternalLibs(cnt);
  325. }
  326. }
  327. }
  328. int cmGraphVizWriter::CollectAllTargets()
  329. {
  330. int cnt = 0;
  331. // First pass get the list of all cmake targets
  332. for (cmLocalGenerator* lg : this->LocalGenerators) {
  333. const std::vector<cmGeneratorTarget*>& targets = lg->GetGeneratorTargets();
  334. for (cmGeneratorTarget* target : targets) {
  335. const char* realTargetName = target->GetName().c_str();
  336. if (this->IgnoreThisTarget(realTargetName)) {
  337. // Skip ignored targets
  338. continue;
  339. }
  340. // std::cout << "Found target: " << tit->first << std::endl;
  341. std::ostringstream ostr;
  342. ostr << this->GraphNodePrefix << cnt++;
  343. this->TargetNamesNodes[realTargetName] = ostr.str();
  344. this->TargetPtrs[realTargetName] = target;
  345. }
  346. }
  347. return cnt;
  348. }
  349. int cmGraphVizWriter::CollectAllExternalLibs(int cnt)
  350. {
  351. // Ok, now find all the stuff we link to that is not in cmake
  352. for (cmLocalGenerator* lg : this->LocalGenerators) {
  353. const std::vector<cmGeneratorTarget*>& targets = lg->GetGeneratorTargets();
  354. for (cmGeneratorTarget* target : targets) {
  355. const char* realTargetName = target->GetName().c_str();
  356. if (this->IgnoreThisTarget(realTargetName)) {
  357. // Skip ignored targets
  358. continue;
  359. }
  360. const cmTarget::LinkLibraryVectorType* ll =
  361. &(target->Target->GetOriginalLinkLibraries());
  362. for (auto const& llit : *ll) {
  363. const char* libName = llit.first.c_str();
  364. if (this->IgnoreThisTarget(libName)) {
  365. // Skip ignored targets
  366. continue;
  367. }
  368. std::map<std::string, const cmGeneratorTarget*>::const_iterator tarIt =
  369. this->TargetPtrs.find(libName);
  370. if (tarIt == this->TargetPtrs.end()) {
  371. std::ostringstream ostr;
  372. ostr << this->GraphNodePrefix << cnt++;
  373. this->TargetNamesNodes[libName] = ostr.str();
  374. this->TargetPtrs[libName] = nullptr;
  375. // str << " \"" << ostr << "\" [ label=\"" << libName
  376. // << "\" shape=\"ellipse\"];" << std::endl;
  377. }
  378. }
  379. }
  380. }
  381. return cnt;
  382. }
  383. bool cmGraphVizWriter::IgnoreThisTarget(const std::string& name)
  384. {
  385. for (cmsys::RegularExpression& regEx : this->TargetsToIgnoreRegex) {
  386. if (regEx.is_valid()) {
  387. if (regEx.find(name)) {
  388. return true;
  389. }
  390. }
  391. }
  392. return false;
  393. }
  394. bool cmGraphVizWriter::GenerateForTargetType(
  395. cmStateEnums::TargetType targetType) const
  396. {
  397. switch (targetType) {
  398. case cmStateEnums::EXECUTABLE:
  399. return this->GenerateForExecutables;
  400. case cmStateEnums::STATIC_LIBRARY:
  401. return this->GenerateForStaticLibs;
  402. case cmStateEnums::SHARED_LIBRARY:
  403. return this->GenerateForSharedLibs;
  404. case cmStateEnums::MODULE_LIBRARY:
  405. return this->GenerateForModuleLibs;
  406. default:
  407. break;
  408. }
  409. return false;
  410. }