cmVariableWatchCommand.cxx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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 "cmVariableWatchCommand.h"
  4. #include <limits>
  5. #include <memory>
  6. #include <utility>
  7. #include "cmExecutionStatus.h"
  8. #include "cmListFileCache.h"
  9. #include "cmMakefile.h"
  10. #include "cmMessageType.h"
  11. #include "cmStringAlgorithms.h"
  12. #include "cmSystemTools.h"
  13. #include "cmVariableWatch.h"
  14. #include "cmake.h"
  15. namespace {
  16. struct cmVariableWatchCallbackData
  17. {
  18. bool InCallback;
  19. std::string Command;
  20. };
  21. void cmVariableWatchCommandVariableAccessed(const std::string& variable,
  22. int access_type, void* client_data,
  23. const char* newValue,
  24. const cmMakefile* mf)
  25. {
  26. cmVariableWatchCallbackData* data =
  27. static_cast<cmVariableWatchCallbackData*>(client_data);
  28. if (data->InCallback) {
  29. return;
  30. }
  31. data->InCallback = true;
  32. auto accessString = cmVariableWatch::GetAccessAsString(access_type);
  33. /// Ultra bad!!
  34. cmMakefile* makefile = const_cast<cmMakefile*>(mf);
  35. std::string stack = makefile->GetProperty("LISTFILE_STACK");
  36. if (!data->Command.empty()) {
  37. cmListFileFunction newLFF;
  38. const char* const currentListFile =
  39. mf->GetDefinition("CMAKE_CURRENT_LIST_FILE");
  40. const auto fakeLineNo =
  41. std::numeric_limits<decltype(cmListFileArgument::Line)>::max();
  42. newLFF.Arguments = {
  43. { variable, cmListFileArgument::Quoted, fakeLineNo },
  44. { accessString, cmListFileArgument::Quoted, fakeLineNo },
  45. { newValue ? newValue : "", cmListFileArgument::Quoted, fakeLineNo },
  46. { currentListFile, cmListFileArgument::Quoted, fakeLineNo },
  47. { stack, cmListFileArgument::Quoted, fakeLineNo }
  48. };
  49. newLFF.Name = data->Command;
  50. newLFF.Line = fakeLineNo;
  51. cmExecutionStatus status(*makefile);
  52. if (!makefile->ExecuteCommand(newLFF, status)) {
  53. cmSystemTools::Error(
  54. cmStrCat("Error in cmake code at\nUnknown:0:\nA command failed "
  55. "during the invocation of callback \"",
  56. data->Command, "\"."));
  57. }
  58. } else {
  59. makefile->IssueMessage(
  60. MessageType::LOG,
  61. cmStrCat("Variable \"", variable, "\" was accessed using ", accessString,
  62. " with value \"", (newValue ? newValue : ""), "\"."));
  63. }
  64. data->InCallback = false;
  65. }
  66. void deleteVariableWatchCallbackData(void* client_data)
  67. {
  68. cmVariableWatchCallbackData* data =
  69. static_cast<cmVariableWatchCallbackData*>(client_data);
  70. delete data;
  71. }
  72. /** This command does not really have a final pass but it needs to
  73. stay alive since it owns variable watch callback information. */
  74. class FinalAction
  75. {
  76. public:
  77. /* NOLINTNEXTLINE(performance-unnecessary-value-param) */
  78. FinalAction(cmMakefile* makefile, std::string variable)
  79. : Action{ std::make_shared<Impl>(makefile, std::move(variable)) }
  80. {
  81. }
  82. void operator()(cmMakefile&) const {}
  83. private:
  84. struct Impl
  85. {
  86. Impl(cmMakefile* makefile, std::string variable)
  87. : Makefile{ makefile }
  88. , Variable{ std::move(variable) }
  89. {
  90. }
  91. ~Impl()
  92. {
  93. this->Makefile->GetCMakeInstance()->GetVariableWatch()->RemoveWatch(
  94. this->Variable, cmVariableWatchCommandVariableAccessed);
  95. }
  96. cmMakefile* const Makefile;
  97. std::string const Variable;
  98. };
  99. std::shared_ptr<Impl const> Action;
  100. };
  101. } // anonymous namespace
  102. bool cmVariableWatchCommand(std::vector<std::string> const& args,
  103. cmExecutionStatus& status)
  104. {
  105. if (args.empty()) {
  106. status.SetError("must be called with at least one argument.");
  107. return false;
  108. }
  109. std::string const& variable = args[0];
  110. std::string command;
  111. if (args.size() > 1) {
  112. command = args[1];
  113. }
  114. if (variable == "CMAKE_CURRENT_LIST_FILE") {
  115. status.SetError(cmStrCat("cannot be set on the variable: ", variable));
  116. return false;
  117. }
  118. auto* const data = new cmVariableWatchCallbackData;
  119. data->InCallback = false;
  120. data->Command = std::move(command);
  121. if (!status.GetMakefile().GetCMakeInstance()->GetVariableWatch()->AddWatch(
  122. variable, cmVariableWatchCommandVariableAccessed, data,
  123. deleteVariableWatchCallbackData)) {
  124. deleteVariableWatchCallbackData(data);
  125. return false;
  126. }
  127. status.GetMakefile().AddFinalAction(
  128. FinalAction{ &status.GetMakefile(), variable });
  129. return true;
  130. }