/* 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 #include #include "cmExecutionStatus.h" #include "cmListFileCache.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmSystemTools.h" #include "cmVariableWatch.h" #include "cmake.h" struct cmVariableWatchCallbackData { bool InCallback; std::string Command; }; static void cmVariableWatchCommandVariableAccessed(const std::string& variable, int access_type, void* client_data, const char* newValue, const cmMakefile* mf) { cmVariableWatchCallbackData* data = static_cast(client_data); if (data->InCallback) { return; } data->InCallback = true; cmListFileFunction newLFF; cmListFileArgument arg; bool processed = false; const char* accessString = cmVariableWatch::GetAccessAsString(access_type); const char* currentListFile = mf->GetDefinition("CMAKE_CURRENT_LIST_FILE"); /// Ultra bad!! cmMakefile* makefile = const_cast(mf); std::string stack = makefile->GetProperty("LISTFILE_STACK"); if (!data->Command.empty()) { newLFF.Arguments.clear(); newLFF.Arguments.emplace_back(variable, cmListFileArgument::Quoted, 9999); newLFF.Arguments.emplace_back(accessString, cmListFileArgument::Quoted, 9999); newLFF.Arguments.emplace_back(newValue ? newValue : "", cmListFileArgument::Quoted, 9999); newLFF.Arguments.emplace_back(currentListFile, cmListFileArgument::Quoted, 9999); newLFF.Arguments.emplace_back(stack, cmListFileArgument::Quoted, 9999); newLFF.Name = data->Command; newLFF.Line = 9999; cmExecutionStatus status(*makefile); if (!makefile->ExecuteCommand(newLFF, status)) { std::ostringstream error; error << "Error in cmake code at\nUnknown:0:\n" << "A command failed during the invocation of callback \"" << data->Command << "\"."; cmSystemTools::Error(error.str()); data->InCallback = false; return; } processed = true; } if (!processed) { std::ostringstream msg; msg << "Variable \"" << variable << "\" was accessed using " << accessString << " with value \"" << (newValue ? newValue : "") << "\"."; makefile->IssueMessage(MessageType::LOG, msg.str()); } data->InCallback = false; } static void deleteVariableWatchCallbackData(void* client_data) { cmVariableWatchCallbackData* data = static_cast(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: FinalAction(cmMakefile* makefile, std::string variable) : Action(std::make_shared(makefile, std::move(variable))) { } void operator()(cmMakefile&) 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* Makefile; std::string Variable; }; std::shared_ptr Action; }; bool cmVariableWatchCommand::InitialPass(std::vector const& args, cmExecutionStatus&) { if (args.empty()) { this->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") { std::ostringstream ostr; ostr << "cannot be set on the variable: " << variable; this->SetError(ostr.str()); return false; } cmVariableWatchCallbackData* data = new cmVariableWatchCallbackData; data->InCallback = false; data->Command = command; if (!this->Makefile->GetCMakeInstance()->GetVariableWatch()->AddWatch( variable, cmVariableWatchCommandVariableAccessed, data, deleteVariableWatchCallbackData)) { deleteVariableWatchCallbackData(data); return false; } this->Makefile->AddFinalAction(FinalAction(this->Makefile, variable)); return true; }