cmGraphVizWriter.cxx 18 KB

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