cmGraphVizWriter.cxx 18 KB

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