| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file Copyright.txt or https://cmake.org/licensing for details. */ | |
| #include "cmVariableWatchCommand.h" | |
| #include <limits> | |
| #include <memory> | |
| #include <utility> | |
| #include "cmExecutionStatus.h" | |
| #include "cmListFileCache.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmProperty.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmVariableWatch.h" | |
| #include "cmake.h" | |
| class cmLocalGenerator; | |
| namespace { | |
| struct cmVariableWatchCallbackData | |
| { | |
| bool InCallback; | |
| std::string Command; | |
| }; | |
| void cmVariableWatchCommandVariableAccessed(const std::string& variable, | |
| int access_type, void* client_data, | |
| const char* newValue, | |
| const cmMakefile* mf) | |
| { | |
| cmVariableWatchCallbackData* data = | |
| static_cast<cmVariableWatchCallbackData*>(client_data); | |
| if (data->InCallback) { | |
| return; | |
| } | |
| data->InCallback = true; | |
| auto accessString = cmVariableWatch::GetAccessAsString(access_type); | |
| /// Ultra bad!! | |
| cmMakefile* makefile = const_cast<cmMakefile*>(mf); | |
| std::string stack = *mf->GetProperty("LISTFILE_STACK"); | |
| if (!data->Command.empty()) { | |
| cmProp const currentListFile = | |
| mf->GetDefinition("CMAKE_CURRENT_LIST_FILE"); | |
| const auto fakeLineNo = | |
| std::numeric_limits<decltype(cmListFileArgument::Line)>::max(); | |
| std::vector<cmListFileArgument> newLFFArgs{ | |
| { variable, cmListFileArgument::Quoted, fakeLineNo }, | |
| { accessString, cmListFileArgument::Quoted, fakeLineNo }, | |
| { newValue ? newValue : "", cmListFileArgument::Quoted, fakeLineNo }, | |
| { *currentListFile, cmListFileArgument::Quoted, fakeLineNo }, | |
| { stack, cmListFileArgument::Quoted, fakeLineNo } | |
| }; | |
| cmListFileFunction newLFF{ data->Command, fakeLineNo, | |
| std::move(newLFFArgs) }; | |
| cmExecutionStatus status(*makefile); | |
| if (!makefile->ExecuteCommand(newLFF, status)) { | |
| cmSystemTools::Error( | |
| cmStrCat("Error in cmake code at\nUnknown:0:\nA command failed " | |
| "during the invocation of callback \"", | |
| data->Command, "\".")); | |
| } | |
| } else { | |
| makefile->IssueMessage( | |
| MessageType::LOG, | |
| cmStrCat("Variable \"", variable, "\" was accessed using ", accessString, | |
| " with value \"", (newValue ? newValue : ""), "\".")); | |
| } | |
| data->InCallback = false; | |
| } | |
| void deleteVariableWatchCallbackData(void* client_data) | |
| { | |
| cmVariableWatchCallbackData* data = | |
| static_cast<cmVariableWatchCallbackData*>(client_data); | |
| delete data; | |
| } | |
| /** This command does not really have a final pass but it needs to | |
| stay alive since it owns variable watch callback information. */ | |
| class FinalAction | |
| { | |
| public: | |
| /* NOLINTNEXTLINE(performance-unnecessary-value-param) */ | |
| FinalAction(cmMakefile* makefile, std::string variable) | |
| : Action{ std::make_shared<Impl>(makefile, std::move(variable)) } | |
| { | |
| } | |
| void operator()(cmLocalGenerator&, const cmListFileBacktrace&) const {} | |
| private: | |
| struct Impl | |
| { | |
| Impl(cmMakefile* makefile, std::string variable) | |
| : Makefile{ makefile } | |
| , Variable{ std::move(variable) } | |
| { | |
| } | |
| ~Impl() | |
| { | |
| this->Makefile->GetCMakeInstance()->GetVariableWatch()->RemoveWatch( | |
| this->Variable, cmVariableWatchCommandVariableAccessed); | |
| } | |
| cmMakefile* const Makefile; | |
| std::string const Variable; | |
| }; | |
| std::shared_ptr<Impl const> Action; | |
| }; | |
| } // anonymous namespace | |
| bool cmVariableWatchCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.empty()) { | |
| status.SetError("must be called with at least one argument."); | |
| return false; | |
| } | |
| std::string const& variable = args[0]; | |
| std::string command; | |
| if (args.size() > 1) { | |
| command = args[1]; | |
| } | |
| if (variable == "CMAKE_CURRENT_LIST_FILE") { | |
| status.SetError(cmStrCat("cannot be set on the variable: ", variable)); | |
| return false; | |
| } | |
| auto* const data = new cmVariableWatchCallbackData; | |
| data->InCallback = false; | |
| data->Command = std::move(command); | |
| if (!status.GetMakefile().GetCMakeInstance()->GetVariableWatch()->AddWatch( | |
| variable, cmVariableWatchCommandVariableAccessed, data, | |
| deleteVariableWatchCallbackData)) { | |
| deleteVariableWatchCallbackData(data); | |
| return false; | |
| } | |
| status.GetMakefile().AddGeneratorAction( | |
| FinalAction{ &status.GetMakefile(), variable }); | |
| return true; | |
| } |