Merge branch 'release-3.29'
[kiteware-cmake.git] / Source / cmVariableWatchCommand.cxx
blob24394d96989921284f7316a3918520cc0d6acd16
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"
5 #include <limits>
6 #include <memory>
7 #include <utility>
9 #include "cmExecutionStatus.h"
10 #include "cmListFileCache.h"
11 #include "cmMakefile.h"
12 #include "cmMessageType.h"
13 #include "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
15 #include "cmValue.h"
16 #include "cmVariableWatch.h"
17 #include "cmake.h"
19 class cmLocalGenerator;
21 namespace {
22 struct cmVariableWatchCallbackData
24 bool InCallback;
25 std::string Command;
28 void cmVariableWatchCommandVariableAccessed(const std::string& variable,
29 int access_type, void* client_data,
30 const char* newValue,
31 const cmMakefile* mf)
33 cmVariableWatchCallbackData* data =
34 static_cast<cmVariableWatchCallbackData*>(client_data);
36 if (data->InCallback) {
37 return;
39 data->InCallback = true;
41 auto accessString = cmVariableWatch::GetAccessAsString(access_type);
43 /// Ultra bad!!
44 cmMakefile* makefile = const_cast<cmMakefile*>(mf);
46 std::string stack = *mf->GetProperty("LISTFILE_STACK");
47 if (!data->Command.empty()) {
48 cmValue const currentListFile =
49 mf->GetDefinition("CMAKE_CURRENT_LIST_FILE");
50 const auto fakeLineNo =
51 std::numeric_limits<decltype(cmListFileArgument::Line)>::max();
53 std::vector<cmListFileArgument> newLFFArgs{
54 { variable, cmListFileArgument::Quoted, fakeLineNo },
55 { accessString, cmListFileArgument::Quoted, fakeLineNo },
56 { newValue ? newValue : "", cmListFileArgument::Quoted, fakeLineNo },
57 { *currentListFile, cmListFileArgument::Quoted, fakeLineNo },
58 { stack, cmListFileArgument::Quoted, fakeLineNo }
61 cmListFileFunction newLFF{ data->Command, fakeLineNo, fakeLineNo,
62 std::move(newLFFArgs) };
63 cmExecutionStatus status(*makefile);
64 if (!makefile->ExecuteCommand(newLFF, status)) {
65 cmSystemTools::Error(
66 cmStrCat("Error in cmake code at\nUnknown:0:\nA command failed "
67 "during the invocation of callback \"",
68 data->Command, "\"."));
70 } else {
71 makefile->IssueMessage(
72 MessageType::LOG,
73 cmStrCat("Variable \"", variable, "\" was accessed using ", accessString,
74 " with value \"", (newValue ? newValue : ""), "\"."));
77 data->InCallback = false;
80 void deleteVariableWatchCallbackData(void* client_data)
82 cmVariableWatchCallbackData* data =
83 static_cast<cmVariableWatchCallbackData*>(client_data);
84 delete data;
87 /** This command does not really have a final pass but it needs to
88 stay alive since it owns variable watch callback information. */
89 class FinalAction
91 public:
92 /* NOLINTNEXTLINE(performance-unnecessary-value-param) */
93 FinalAction(cmMakefile* makefile, std::string variable)
94 : Action{ std::make_shared<Impl>(makefile, std::move(variable)) }
98 void operator()(cmLocalGenerator&, const cmListFileBacktrace&) const {}
100 private:
101 struct Impl
103 Impl(cmMakefile* makefile, std::string variable)
104 : Makefile{ makefile }
105 , Variable{ std::move(variable) }
109 ~Impl()
111 this->Makefile->GetCMakeInstance()->GetVariableWatch()->RemoveWatch(
112 this->Variable, cmVariableWatchCommandVariableAccessed);
115 cmMakefile* const Makefile;
116 std::string const Variable;
119 std::shared_ptr<Impl const> Action;
121 } // anonymous namespace
123 bool cmVariableWatchCommand(std::vector<std::string> const& args,
124 cmExecutionStatus& status)
126 if (args.empty()) {
127 status.SetError("must be called with at least one argument.");
128 return false;
130 std::string const& variable = args[0];
131 std::string command;
132 if (args.size() > 1) {
133 command = args[1];
135 if (variable == "CMAKE_CURRENT_LIST_FILE") {
136 status.SetError(cmStrCat("cannot be set on the variable: ", variable));
137 return false;
140 auto* const data = new cmVariableWatchCallbackData;
142 data->InCallback = false;
143 data->Command = std::move(command);
145 if (!status.GetMakefile().GetCMakeInstance()->GetVariableWatch()->AddWatch(
146 variable, cmVariableWatchCommandVariableAccessed, data,
147 deleteVariableWatchCallbackData)) {
148 deleteVariableWatchCallbackData(data);
149 return false;
152 status.GetMakefile().AddGeneratorAction(
153 FinalAction{ &status.GetMakefile(), variable });
154 return true;