mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-11 16:32:14 -06:00
Trace: include line_end field in json-v1 format
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).
This commit is contained in:
@@ -309,7 +309,13 @@ Options
|
||||
was called.
|
||||
|
||||
``line``
|
||||
The line in ``file`` of the function call.
|
||||
The line in ``file`` where the function call begins.
|
||||
|
||||
``line_end``
|
||||
If the function call spans multiple lines, this field will
|
||||
be set to the line where the function call ends. If the function
|
||||
calls spans a single line, this field will be unset. This field
|
||||
was added in minor version 2 of the ``json-v1`` format.
|
||||
|
||||
``defer``
|
||||
Optional member that is present when the function call was deferred
|
||||
|
||||
7
Help/release/dev/trace-line-end.rst
Normal file
7
Help/release/dev/trace-line-end.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
trace-line-end
|
||||
--------------
|
||||
|
||||
* Add the field ``line_end`` to the json-v1 trace format. This
|
||||
field tells you the line in file ``file`` at which the function
|
||||
call ends. Tools can use this new field, together with ``line``
|
||||
and ``file``, to map traces to lines of CMake source code.
|
||||
@@ -84,7 +84,8 @@ bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
|
||||
for (size_t i = startArg; i < args.size(); ++i) {
|
||||
funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
|
||||
}
|
||||
cmListFileFunction func{ callCommand, context.Line, std::move(funcArgs) };
|
||||
cmListFileFunction func{ callCommand, context.Line, context.Line,
|
||||
std::move(funcArgs) };
|
||||
|
||||
if (defer) {
|
||||
if (defer->Id.empty()) {
|
||||
|
||||
@@ -432,7 +432,7 @@ static int CCONV cmExecuteCommand(void* arg, const char* name, int numArgs,
|
||||
lffArgs.emplace_back(args[i], cmListFileArgument::Quoted, 0);
|
||||
}
|
||||
|
||||
cmListFileFunction lff{ name, 0, std::move(lffArgs) };
|
||||
cmListFileFunction lff{ name, 0, 0, std::move(lffArgs) };
|
||||
cmExecutionStatus status(*mf);
|
||||
return mf->ExecuteCommand(lff, status);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ struct cmListFileParser
|
||||
cmListFileLexer* Lexer;
|
||||
std::string FunctionName;
|
||||
long FunctionLine;
|
||||
long FunctionLineEnd;
|
||||
std::vector<cmListFileArgument> FunctionArguments;
|
||||
enum
|
||||
{
|
||||
@@ -146,7 +147,7 @@ bool cmListFileParser::Parse()
|
||||
if (this->ParseFunction(token->text, token->line)) {
|
||||
this->ListFile->Functions.emplace_back(
|
||||
std::move(this->FunctionName), this->FunctionLine,
|
||||
std::move(this->FunctionArguments));
|
||||
this->FunctionLineEnd, std::move(this->FunctionArguments));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -259,6 +260,7 @@ bool cmListFileParser::ParseFunction(const char* name, long line)
|
||||
}
|
||||
} else if (token->type == cmListFileLexer_Token_ParenRight) {
|
||||
if (parenDepth == 0) {
|
||||
this->FunctionLineEnd = token->line;
|
||||
return true;
|
||||
}
|
||||
parenDepth--;
|
||||
|
||||
@@ -51,9 +51,9 @@ struct cmListFileArgument
|
||||
class cmListFileFunction
|
||||
{
|
||||
public:
|
||||
cmListFileFunction(std::string name, long line,
|
||||
cmListFileFunction(std::string name, long line, long lineEnd,
|
||||
std::vector<cmListFileArgument> args)
|
||||
: Impl{ std::make_shared<Implementation>(std::move(name), line,
|
||||
: Impl{ std::make_shared<Implementation>(std::move(name), line, lineEnd,
|
||||
std::move(args)) }
|
||||
{
|
||||
}
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
}
|
||||
|
||||
long Line() const noexcept { return this->Impl->Line; }
|
||||
long LineEnd() const noexcept { return this->Impl->LineEnd; }
|
||||
|
||||
std::vector<cmListFileArgument> const& Arguments() const noexcept
|
||||
{
|
||||
@@ -78,11 +79,12 @@ public:
|
||||
private:
|
||||
struct Implementation
|
||||
{
|
||||
Implementation(std::string name, long line,
|
||||
Implementation(std::string name, long line, long lineEnd,
|
||||
std::vector<cmListFileArgument> args)
|
||||
: OriginalName{ std::move(name) }
|
||||
, LowerCaseName{ cmSystemTools::LowerCase(this->OriginalName) }
|
||||
, Line{ line }
|
||||
, LineEnd{ lineEnd }
|
||||
, Arguments{ std::move(args) }
|
||||
{
|
||||
}
|
||||
@@ -90,6 +92,7 @@ private:
|
||||
std::string OriginalName;
|
||||
std::string LowerCaseName;
|
||||
long Line = 0;
|
||||
long LineEnd = 0;
|
||||
std::vector<cmListFileArgument> Arguments;
|
||||
};
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ bool cmMacroHelperCommand::operator()(
|
||||
newLFFArgs.push_back(std::move(arg));
|
||||
}
|
||||
cmListFileFunction newLFF{ func.OriginalName(), func.Line(),
|
||||
std::move(newLFFArgs) };
|
||||
func.LineEnd(), std::move(newLFFArgs) };
|
||||
cmExecutionStatus status(makefile);
|
||||
if (!makefile.ExecuteCommand(newLFF, status) || status.GetNestedError()) {
|
||||
// The error message should have already included the call stack
|
||||
|
||||
@@ -291,6 +291,9 @@ void cmMakefile::PrintCommandTrace(
|
||||
builder["indentation"] = "";
|
||||
val["file"] = full_path;
|
||||
val["line"] = static_cast<Json::Value::Int64>(lff.Line());
|
||||
if (lff.Line() != lff.LineEnd()) {
|
||||
val["line_end"] = static_cast<Json::Value::Int64>(lff.LineEnd());
|
||||
}
|
||||
if (deferId) {
|
||||
val["defer"] = *deferId;
|
||||
}
|
||||
@@ -1664,6 +1667,7 @@ void cmMakefile::Configure()
|
||||
"the first line.",
|
||||
this->Backtrace);
|
||||
cmListFileFunction project{ "project",
|
||||
0,
|
||||
0,
|
||||
{ { "Project", cmListFileArgument::Unquoted,
|
||||
0 },
|
||||
|
||||
@@ -58,7 +58,7 @@ void cmVariableWatchCommandVariableAccessed(const std::string& variable,
|
||||
{ stack, cmListFileArgument::Quoted, fakeLineNo }
|
||||
};
|
||||
|
||||
cmListFileFunction newLFF{ data->Command, fakeLineNo,
|
||||
cmListFileFunction newLFF{ data->Command, fakeLineNo, fakeLineNo,
|
||||
std::move(newLFFArgs) };
|
||||
cmExecutionStatus status(*makefile);
|
||||
if (!makefile->ExecuteCommand(newLFF, status)) {
|
||||
|
||||
@@ -30,6 +30,8 @@ required_traces = [
|
||||
{
|
||||
'args': ['STATUS', 'JSON-V1 str', 'spaces'],
|
||||
'cmd': 'message',
|
||||
'line': 1,
|
||||
'line_end': 5
|
||||
},
|
||||
{
|
||||
'args': ['ASDF', 'fff', 'sss', ' SPACES !!! '],
|
||||
@@ -57,6 +59,24 @@ required_traces = [
|
||||
}
|
||||
]
|
||||
|
||||
def assert_fields_look_good(line):
|
||||
expected_fields = {'args', 'cmd', 'file', 'frame', 'global_frame','line', 'time'}
|
||||
if "line_end" in line:
|
||||
assert isinstance(line['line_end'], int)
|
||||
assert line['line'] != line['line_end']
|
||||
expected_fields.add("line_end")
|
||||
|
||||
assert set(line.keys()) == expected_fields
|
||||
|
||||
assert isinstance(line['args'], list)
|
||||
assert isinstance(line['cmd'], unicode)
|
||||
assert isinstance(line['file'], unicode)
|
||||
assert isinstance(line['frame'], int)
|
||||
assert isinstance(line['global_frame'], int)
|
||||
assert isinstance(line['line'], int)
|
||||
assert isinstance(line['time'], float)
|
||||
|
||||
|
||||
with open(trace_file, 'r') as fp:
|
||||
# Check for version (must be the first document)
|
||||
vers = json.loads(fp.readline())
|
||||
@@ -67,18 +87,15 @@ with open(trace_file, 'r') as fp:
|
||||
|
||||
for i in fp.readlines():
|
||||
line = json.loads(i)
|
||||
assert sorted(line.keys()) == ['args', 'cmd', 'file', 'frame', 'global_frame','line', 'time']
|
||||
assert isinstance(line['args'], list)
|
||||
assert isinstance(line['cmd'], unicode)
|
||||
assert isinstance(line['file'], unicode)
|
||||
assert isinstance(line['frame'], int)
|
||||
assert isinstance(line['global_frame'], int)
|
||||
assert isinstance(line['line'], int)
|
||||
assert isinstance(line['time'], float)
|
||||
|
||||
assert_fields_look_good(line)
|
||||
for j in required_traces:
|
||||
# Compare the subset of required keys with line
|
||||
if {k: line[k] for k in j} == j:
|
||||
subset = {
|
||||
k: line[k]
|
||||
for k in j
|
||||
if k in line
|
||||
}
|
||||
if subset == j:
|
||||
required_traces.remove(j)
|
||||
|
||||
assert not required_traces
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
message(STATUS "JSON-V1 str" "spaces")
|
||||
message(
|
||||
STATUS
|
||||
"JSON-V1 str"
|
||||
"spaces"
|
||||
)
|
||||
set(ASDF fff sss " SPACES !!! ")
|
||||
set(FOO 42)
|
||||
set(BAR " space in string!")
|
||||
|
||||
Reference in New Issue
Block a user