blob: 35b9a1d5b7c14441a408e2807f5ced355f7d156c [file] [log] [blame]
/* 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 "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 = makefile->GetProperty("LISTFILE_STACK");
if (!data->Command.empty()) {
cmListFileFunction newLFF;
const char* const currentListFile =
mf->GetDefinition("CMAKE_CURRENT_LIST_FILE");
const auto fakeLineNo =
std::numeric_limits<decltype(cmListFileArgument::Line)>::max();
newLFF.Arguments = {
{ variable, cmListFileArgument::Quoted, fakeLineNo },
{ accessString, cmListFileArgument::Quoted, fakeLineNo },
{ newValue ? newValue : "", cmListFileArgument::Quoted, fakeLineNo },
{ currentListFile, cmListFileArgument::Quoted, fakeLineNo },
{ stack, cmListFileArgument::Quoted, fakeLineNo }
};
newLFF.Name = data->Command;
newLFF.Line = fakeLineNo;
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;
}