
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).
198 lines
6.4 KiB
C++
198 lines
6.4 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmMacroCommand.h"
|
|
|
|
#include <cstdio>
|
|
#include <utility>
|
|
|
|
#include <cm/memory>
|
|
#include <cm/string_view>
|
|
#include <cmext/algorithm>
|
|
#include <cmext/string_view>
|
|
|
|
#include "cmExecutionStatus.h"
|
|
#include "cmFunctionBlocker.h"
|
|
#include "cmListFileCache.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmPolicies.h"
|
|
#include "cmRange.h"
|
|
#include "cmState.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
|
|
namespace {
|
|
|
|
// define the class for macro commands
|
|
class cmMacroHelperCommand
|
|
{
|
|
public:
|
|
/**
|
|
* This is called when the command is first encountered in
|
|
* the CMakeLists.txt file.
|
|
*/
|
|
bool operator()(std::vector<cmListFileArgument> const& args,
|
|
cmExecutionStatus& inStatus) const;
|
|
|
|
std::vector<std::string> Args;
|
|
std::vector<cmListFileFunction> Functions;
|
|
cmPolicies::PolicyMap Policies;
|
|
std::string FilePath;
|
|
};
|
|
|
|
bool cmMacroHelperCommand::operator()(
|
|
std::vector<cmListFileArgument> const& args,
|
|
cmExecutionStatus& inStatus) const
|
|
{
|
|
cmMakefile& makefile = inStatus.GetMakefile();
|
|
|
|
// Expand the argument list to the macro.
|
|
std::vector<std::string> expandedArgs;
|
|
makefile.ExpandArguments(args, expandedArgs);
|
|
|
|
// make sure the number of arguments passed is at least the number
|
|
// required by the signature
|
|
if (expandedArgs.size() < this->Args.size() - 1) {
|
|
std::string errorMsg =
|
|
cmStrCat("Macro invoked with incorrect arguments for macro named: ",
|
|
this->Args[0]);
|
|
inStatus.SetError(errorMsg);
|
|
return false;
|
|
}
|
|
|
|
cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath,
|
|
this->Policies);
|
|
|
|
// set the value of argc
|
|
std::string argcDef = std::to_string(expandedArgs.size());
|
|
|
|
auto eit = expandedArgs.begin() + (this->Args.size() - 1);
|
|
std::string expandedArgn = cmJoin(cmMakeRange(eit, expandedArgs.end()), ";");
|
|
std::string expandedArgv = cmJoin(expandedArgs, ";");
|
|
std::vector<std::string> variables;
|
|
variables.reserve(this->Args.size() - 1);
|
|
for (unsigned int j = 1; j < this->Args.size(); ++j) {
|
|
variables.push_back("${" + this->Args[j] + "}");
|
|
}
|
|
std::vector<std::string> argVs;
|
|
argVs.reserve(expandedArgs.size());
|
|
char argvName[60];
|
|
for (unsigned int j = 0; j < expandedArgs.size(); ++j) {
|
|
snprintf(argvName, sizeof(argvName), "${ARGV%u}", j);
|
|
argVs.emplace_back(argvName);
|
|
}
|
|
// Invoke all the functions that were collected in the block.
|
|
// for each function
|
|
for (cmListFileFunction const& func : this->Functions) {
|
|
// Replace the formal arguments and then invoke the command.
|
|
std::vector<cmListFileArgument> newLFFArgs;
|
|
newLFFArgs.reserve(func.Arguments().size());
|
|
|
|
// for each argument of the current function
|
|
for (cmListFileArgument const& k : func.Arguments()) {
|
|
cmListFileArgument arg;
|
|
arg.Value = k.Value;
|
|
if (k.Delim != cmListFileArgument::Bracket) {
|
|
// replace formal arguments
|
|
for (unsigned int j = 0; j < variables.size(); ++j) {
|
|
cmSystemTools::ReplaceString(arg.Value, variables[j],
|
|
expandedArgs[j]);
|
|
}
|
|
// replace argc
|
|
cmSystemTools::ReplaceString(arg.Value, "${ARGC}", argcDef);
|
|
|
|
cmSystemTools::ReplaceString(arg.Value, "${ARGN}", expandedArgn);
|
|
cmSystemTools::ReplaceString(arg.Value, "${ARGV}", expandedArgv);
|
|
|
|
// if the current argument of the current function has ${ARGV in it
|
|
// then try replacing ARGV values
|
|
if (arg.Value.find("${ARGV") != std::string::npos) {
|
|
for (unsigned int t = 0; t < expandedArgs.size(); ++t) {
|
|
cmSystemTools::ReplaceString(arg.Value, argVs[t], expandedArgs[t]);
|
|
}
|
|
}
|
|
}
|
|
arg.Delim = k.Delim;
|
|
arg.Line = k.Line;
|
|
newLFFArgs.push_back(std::move(arg));
|
|
}
|
|
cmListFileFunction newLFF{ func.OriginalName(), func.Line(),
|
|
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
|
|
// so we do not need to report an error here.
|
|
macroScope.Quiet();
|
|
inStatus.SetNestedError();
|
|
return false;
|
|
}
|
|
if (status.GetReturnInvoked()) {
|
|
inStatus.SetReturnInvoked();
|
|
return true;
|
|
}
|
|
if (status.GetBreakInvoked()) {
|
|
inStatus.SetBreakInvoked();
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class cmMacroFunctionBlocker : public cmFunctionBlocker
|
|
{
|
|
public:
|
|
cm::string_view StartCommandName() const override { return "macro"_s; }
|
|
cm::string_view EndCommandName() const override { return "endmacro"_s; }
|
|
|
|
bool ArgumentsMatch(cmListFileFunction const&,
|
|
cmMakefile& mf) const override;
|
|
|
|
bool Replay(std::vector<cmListFileFunction> functions,
|
|
cmExecutionStatus& status) override;
|
|
|
|
std::vector<std::string> Args;
|
|
};
|
|
|
|
bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
|
|
cmMakefile& mf) const
|
|
{
|
|
std::vector<std::string> expandedArguments;
|
|
mf.ExpandArguments(lff.Arguments(), expandedArguments);
|
|
return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
|
|
}
|
|
|
|
bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
|
|
cmExecutionStatus& status)
|
|
{
|
|
cmMakefile& mf = status.GetMakefile();
|
|
mf.AppendProperty("MACROS", this->Args[0]);
|
|
// create a new command and add it to cmake
|
|
cmMacroHelperCommand f;
|
|
f.Args = this->Args;
|
|
f.Functions = std::move(functions);
|
|
f.FilePath = this->GetStartingContext().FilePath;
|
|
mf.RecordPolicies(f.Policies);
|
|
return mf.GetState()->AddScriptedCommand(
|
|
this->Args[0],
|
|
BT<cmState::Command>(std::move(f),
|
|
mf.GetBacktrace().Push(this->GetStartingContext())),
|
|
mf);
|
|
}
|
|
}
|
|
|
|
bool cmMacroCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (args.empty()) {
|
|
status.SetError("called with incorrect number of arguments");
|
|
return false;
|
|
}
|
|
|
|
// create a function blocker
|
|
{
|
|
auto fb = cm::make_unique<cmMacroFunctionBlocker>();
|
|
cm::append(fb->Args, args);
|
|
status.GetMakefile().AddFunctionBlocker(std::move(fb));
|
|
}
|
|
return true;
|
|
}
|