cmVariableWatchCommand.cxx 4.4 KB

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