[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(