[templates] Add print_stack_trace() function
Add a 'print_stack_trace()' function which will print out
the stack of template invocation calls that led up to the
function call.
Change-Id: I1c19032196ea28b0ba9776b3be1e14cbab7c5bdb
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13820
Reviewed-by: David Turner <digit@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Aaron Wood <aaronwood@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 2aba497..6ffec1d 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -53,6 +53,7 @@
* [not_needed: Mark variables from scope as not needed.](#func_not_needed)
* [pool: Defines a pool object.](#func_pool)
* [print: Prints to the console.](#func_print)
+ * [print_stack_trace: Prints a stack trace.](#func_print_stack_trace)
* [process_file_template: Do template expansion over a list of files.](#func_process_file_template)
* [read_file: Read a file into a variable.](#func_read_file)
* [rebase_path: Rebase a file or directory to another location.](#func_rebase_path)
@@ -2970,6 +2971,35 @@
print(sources, deps)
```
+### <a name="func_print_stack_trace"></a>**print_stack_trace**: Prints a stack trace.
+
+```
+ Prints the current file location, and all template invocations that led up to
+ this location, to the console.
+```
+
+#### **Examples**
+
+```
+ template("foo"){
+ print_stack_trace()
+ }
+ template("bar"){
+ foo(target_name + ".foo") {
+ baz = invoker.baz
+ }
+ }
+ bar("lala") {
+ baz = 42
+ }
+
+ will print out the following:
+
+ print_stack_trace() initiated at //build.gn:2
+ bar("lala") //BUILD.gn:9
+ foo("lala.foo") //BUILD.gn:5
+ print_stack_trace() //BUILD.gn:2
+```
### <a name="func_process_file_template"></a>**process_file_template**: Do template expansion over a list of files.
```
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index 6294f79..2b33190 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -968,6 +968,65 @@
return Value();
}
+// print_stack_trace -----------------------------------------------------------
+
+const char kPrintStackTrace[] = "print_stack_trace";
+const char kPrintStackTrace_HelpShort[] =
+ "print_stack_trace: Prints a stack trace.";
+const char kPrintStackTrace_Help[] =
+ R"(print_stack_trace: Prints a stack trace.
+
+ Prints the current file location, and all template invocations that led up to
+ this location, to the console.
+
+Examples
+
+ template("foo"){
+ print_stack_trace()
+ }
+ template("bar"){
+ foo(target_name + ".foo") {
+ baz = invoker.baz
+ }
+ }
+ bar("lala") {
+ baz = 42
+ }
+
+ will print out the following:
+
+ print_stack_trace() initiated at //build.gn:2
+ bar("lala") //BUILD.gn:9
+ foo("lala.foo") //BUILD.gn:5
+ print_stack_trace() //BUILD.gn:2
+
+)";
+
+Value RunPrintStackTrace(Scope* scope,
+ const FunctionCallNode* function,
+ const std::vector<Value>& args,
+ Err* err) {
+ std::string location_str = function->GetRange().begin().Describe(false);
+ std::string output = "print_stack_trace() initiated at: " + location_str;
+ output.push_back('\n');
+
+ for (const auto& entry : scope->GetTemplateInvocationEntries()) {
+ output.append(" " + entry.Describe() + "\n");
+ }
+ output.append(" print_stack_trace() " + location_str + "\n");
+
+ const BuildSettings::PrintCallback& cb =
+ scope->settings()->build_settings()->print_callback();
+ if (cb) {
+ cb(output);
+ } else {
+ printf("%s", output.c_str());
+ fflush(stdout);
+ }
+
+ return Value();
+}
+
// split_list ------------------------------------------------------------------
const char kSplitList[] = "split_list";
@@ -1393,6 +1452,7 @@
INSERT_FUNCTION(NotNeeded, false)
INSERT_FUNCTION(Pool, false)
INSERT_FUNCTION(Print, false)
+ INSERT_FUNCTION(PrintStackTrace, false)
INSERT_FUNCTION(ProcessFileTemplate, false)
INSERT_FUNCTION(ReadFile, false)
INSERT_FUNCTION(RebasePath, false)
diff --git a/src/gn/functions_unittest.cc b/src/gn/functions_unittest.cc
index 40ac5d9..783a22a 100644
--- a/src/gn/functions_unittest.cc
+++ b/src/gn/functions_unittest.cc
@@ -457,3 +457,147 @@
ASSERT_FALSE(err.has_error())
<< err.message() << err.location().Describe(true);
}
+
+TEST(Template, PrintStackTraceWithOneTemplate) {
+ TestWithScope setup;
+ TestParseInput input(
+ "template(\"foo\") {\n"
+ " print(target_name)\n"
+ " print(invoker.foo_value)\n"
+ " print_stack_trace()\n"
+ "}\n"
+ "foo(\"lala\") {\n"
+ " foo_value = 42\n"
+ "}");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "lala\n"
+ "42\n"
+ "print_stack_trace() initiated at: //test:4\n"
+ " foo(\"lala\") //test:6\n"
+ " print_stack_trace() //test:4\n",
+ setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNoTemplates) {
+ TestWithScope setup;
+ TestParseInput input("print_stack_trace()\n");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "print_stack_trace() initiated at: //test:1\n"
+ " print_stack_trace() //test:1\n",
+ setup.print_output());
+}
+
+
+TEST(Template, PrintStackTraceWithNestedTemplates) {
+ TestWithScope setup;
+ TestParseInput input(
+ "template(\"foo\") {\n"
+ " print(target_name)\n"
+ " print(invoker.foo_value)\n"
+ " print_stack_trace()\n"
+ "}\n"
+ "template(\"baz\") {\n"
+ " foo(\"${target_name}.foo\") {\n"
+ " foo_value = invoker.bar\n"
+ " }\n"
+ "}\n"
+ "baz(\"lala\") {\n"
+ " bar = 42\n"
+ "}");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "lala.foo\n"
+ "42\n"
+ "print_stack_trace() initiated at: //test:4\n"
+ " baz(\"lala\") //test:11\n"
+ " foo(\"lala.foo\") //test:7\n"
+ " print_stack_trace() //test:4\n",
+ setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNonTemplateScopes) {
+ TestWithScope setup;
+ TestParseInput input(
+ "template(\"foo\") {\n"
+ " print(target_name)\n"
+ " if (defined(invoker.foo_value)) {\n"
+ " print(invoker.foo_value)\n"
+ " print_stack_trace()\n"
+ " }\n"
+ "}\n"
+ "template(\"baz\") {\n"
+ " foo(\"${target_name}.foo\") {\n"
+ " foo_value = invoker.bar\n"
+ " }\n"
+ "}\n"
+ "baz(\"lala\") {\n"
+ " bar = 42\n"
+ "}");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "lala.foo\n"
+ "42\n"
+ "print_stack_trace() initiated at: //test:5\n"
+ " baz(\"lala\") //test:13\n"
+ " foo(\"lala.foo\") //test:9\n"
+ " print_stack_trace() //test:5\n",
+ setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNonTemplateScopesBetweenTemplateInvocations) {
+ TestWithScope setup;
+ TestParseInput input(
+ "template(\"foo\") {\n"
+ " print(target_name)\n"
+ " if (defined(invoker.foo_value)) {\n"
+ " print(invoker.foo_value)\n"
+ " print_stack_trace()\n"
+ " }\n"
+ "}\n"
+ "template(\"baz\") {\n"
+ " if (invoker.bar == 42) {\n"
+ " foo(\"${target_name}.foo\") {\n"
+ " foo_value = invoker.bar\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "baz(\"lala\") {\n"
+ " bar = 42\n"
+ "}");
+ ASSERT_FALSE(input.has_error());
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "lala.foo\n"
+ "42\n"
+ "print_stack_trace() initiated at: //test:5\n"
+ " baz(\"lala\") //test:15\n"
+ " foo(\"lala.foo\") //test:10\n"
+ " print_stack_trace() //test:5\n",
+ setup.print_output());
+}
diff --git a/src/gn/scope.cc b/src/gn/scope.cc
index 05b0995..e51282a 100644
--- a/src/gn/scope.cc
+++ b/src/gn/scope.cc
@@ -39,6 +39,13 @@
scope_->RemoveProvider(this);
}
+std::string Scope::TemplateInvocationEntry::Describe() const {
+ std::string ret = template_name;
+ ret += "(\"" + target_name + "\") ";
+ ret += location.Describe(false);
+ return ret;
+}
+
Scope::Scope(const Settings* settings)
: const_containing_(nullptr),
mutable_containing_(nullptr),
@@ -557,6 +564,42 @@
programmatic_providers_.erase(p);
}
+void Scope::SetTemplateInvocationEntry(std::string template_name,
+ std::string target_name,
+ Location location) {
+ template_invocation_entry_ = std::make_unique<TemplateInvocationEntry>(
+ TemplateInvocationEntry{std::move(template_name), std::move(target_name),
+ std::move(location)});
+}
+
+void Scope::AppendTemplateInvocationEntries(
+ std::vector<TemplateInvocationEntry>* out) const {
+
+ // Bare scopes and if/for_each() scopes need to walk up their containing
+ // scopes to find previous template invocations. A scope like this within
+ // a template invocation will have an "invoker" value, but that invoker will
+ // not have an entry, and so both are checked to ensure that the full stack of
+ // invocations is captured.
+ if (containing())
+ containing()->AppendTemplateInvocationEntries(out);
+
+ // Template scopes need to walk up invoker to find previous template
+ // invocations
+ const Value* invoker = GetValue("invoker");
+ if (invoker && invoker->type() == Value::SCOPE)
+ invoker->scope_value()->AppendTemplateInvocationEntries(out);
+
+ if (template_invocation_entry_)
+ out->push_back(*template_invocation_entry_);
+}
+
+std::vector<Scope::TemplateInvocationEntry>
+Scope::GetTemplateInvocationEntries() const {
+ std::vector<Scope::TemplateInvocationEntry> result;
+ AppendTemplateInvocationEntries(&result);
+ return result;
+}
+
// static
bool Scope::RecordMapValuesEqual(const RecordMap& a, const RecordMap& b) {
if (a.size() != b.size())
diff --git a/src/gn/scope.h b/src/gn/scope.h
index 5412a41..e82c56f 100644
--- a/src/gn/scope.h
+++ b/src/gn/scope.h
@@ -8,6 +8,7 @@
#include <map>
#include <memory>
#include <set>
+#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
@@ -15,6 +16,7 @@
#include "base/memory/ref_counted.h"
#include "gn/err.h"
+#include "gn/location.h"
#include "gn/pattern.h"
#include "gn/source_dir.h"
#include "gn/source_file.h"
@@ -94,6 +96,21 @@
std::set<std::string> excluded_values;
};
+ // Details about a Scope's creation as a template invocation
+ struct TemplateInvocationEntry {
+ // Produce a printable string that describes the template invocation:
+ //
+ // 'template_name("target_name") //some/BUILD.gn:<line>'
+ //
+ // The target name is only the string that's passed to the template as the
+ // name, it's not a complete GN label.
+ std::string Describe() const;
+
+ std::string template_name;
+ std::string target_name;
+ Location location;
+ };
+
// Creates an empty toplevel scope.
explicit Scope(const Settings* settings);
@@ -319,6 +336,15 @@
void SetProperty(const void* key, void* value);
void* GetProperty(const void* key, const Scope** found_on_scope) const;
+ // Track template invocations for printing or debugging.
+ void SetTemplateInvocationEntry(std::string template_name,
+ std::string target_name,
+ Location location);
+
+ // Return a vector containing the current stack of template invocations that
+ // lead up to this scope.
+ std::vector<TemplateInvocationEntry> GetTemplateInvocationEntries() const;
+
private:
friend class ProgrammaticProvider;
@@ -339,6 +365,10 @@
// of the values may be different).
static bool RecordMapValuesEqual(const RecordMap& a, const RecordMap& b);
+ // Walk up the containing scopes and any "invoker" Value scopes to gather any
+ // previous template invocations.
+ void AppendTemplateInvocationEntries(std::vector<TemplateInvocationEntry>* out) const;
+
// Scopes can have no containing scope (both null), a mutable containing
// scope, or a const containing scope. The reason is that when we're doing
// a new target, we want to refer to the base_config scope which will be read
@@ -356,6 +386,9 @@
RecordMap values_;
+ // If this is a template scope, track the template invocation.
+ std::unique_ptr<TemplateInvocationEntry> template_invocation_entry_;
+
// Note that this can't use string pieces since the names are constructed from
// Values which might be deallocated before this goes out of scope.
using NamedScopeMap = std::unordered_map<std::string, std::unique_ptr<Scope>>;
diff --git a/src/gn/template.cc b/src/gn/template.cc
index f0eca28..deafa33 100644
--- a/src/gn/template.cc
+++ b/src/gn/template.cc
@@ -64,6 +64,10 @@
Scope template_scope(closure_.get());
template_scope.set_source_dir(scope->GetSourceDir());
+ // Track the invocation of the template on the template's scope
+ template_scope.SetTemplateInvocationEntry(
+ template_name, args[0].string_value(), invocation->GetRange().begin());
+
// Propagate build dependency files from invoker scope (template scope already
// propagated via parent scope).
template_scope.AddBuildDependencyFiles(