cmVariableWatchCommand.cxx 4.4 KB

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