mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-13 00:56:08 +08:00

After !6954 got merged, it has become easier for tools to get full stack-traces for runtime traces of a CMake program. The trace information already included in the JSON objects (line number, source file path) allows tools that display these stack traces to print the CMake source code associated to them. However, CMake commands may spawn multiple lines, and the JSON information associated to a trace only contains the line in which the command started, but not the one in which it ended. If tools want to print stack traces along the relevant source code, and they want to print the whole command associated to the stack frame, they will have to implement their own CMake language parser to know where the command ends. In order to simplify the life of those who want to write tooling for CMake, this commit adds a `line_end` field to the json-v1 trace format. If a given command spans multiple lines, the `line_end` field will contain the line of the last line spanned by the command (that of the closing parenthesis associated to the command).
156 lines
4.5 KiB
C++
156 lines
4.5 KiB
C++
/* 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 "cmValue.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()) {
|
|
cmValue 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, 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;
|
|
}
|