summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/gn/BUILD.gn3
-rw-r--r--tools/gn/function_set_defaults.cc3
-rw-r--r--tools/gn/function_template.cc126
-rw-r--r--tools/gn/functions.cc107
-rw-r--r--tools/gn/functions_unittest.cc55
-rw-r--r--tools/gn/gn.gyp3
-rw-r--r--tools/gn/import_manager.cc2
-rw-r--r--tools/gn/scope.cc145
-rw-r--r--tools/gn/scope.h31
-rw-r--r--tools/gn/scope_unittest.cc75
-rw-r--r--tools/gn/template.cc73
-rw-r--r--tools/gn/template.h53
12 files changed, 524 insertions, 152 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 455edf0..8665bbf 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -127,6 +127,8 @@ static_library("gn_lib") {
"target.h",
"target_generator.cc",
"target_generator.h",
+ "template.cc",
+ "template.h",
"token.cc",
"token.h",
"tokenizer.cc",
@@ -167,6 +169,7 @@ test("gn_unittests") {
"filesystem_utils_unittest.cc",
"file_template_unittest.cc",
"function_rebase_path_unittest.cc",
+ "functions_unittest.cc",
"input_conversion_unittest.cc",
"label_unittest.cc",
"loader_unittest.cc",
diff --git a/tools/gn/function_set_defaults.cc b/tools/gn/function_set_defaults.cc
index 94e9ff3..17d1103 100644
--- a/tools/gn/function_set_defaults.cc
+++ b/tools/gn/function_set_defaults.cc
@@ -82,7 +82,8 @@ Value RunSetDefaults(Scope* scope,
// Now copy the values set on the scope we made into the free-floating one
// (with no containing scope) used to hold the target defaults.
Scope* dest = scope->MakeTargetDefaults(target_type);
- block_scope.NonRecursiveMergeTo(dest, function, "<SHOULD NOT FAIL>", err);
+ block_scope.NonRecursiveMergeTo(dest, false, function,
+ "<SHOULD NOT FAIL>", err);
return Value();
}
diff --git a/tools/gn/function_template.cc b/tools/gn/function_template.cc
index f726529..46a2fec 100644
--- a/tools/gn/function_template.cc
+++ b/tools/gn/function_template.cc
@@ -6,6 +6,7 @@
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
+#include "tools/gn/template.h"
#include "tools/gn/value.h"
namespace functions {
@@ -14,71 +15,129 @@ const char kTemplate[] = "template";
const char kTemplate_Help[] =
"template: Define a template rule.\n"
"\n"
- " A template defines a custom rule name that can expand to one or more\n"
- " other rules (typically built-in rules like \"static_library\"). It\n"
+ " A template defines a custom name that acts like a function. It\n"
" provides a way to add to the built-in target types.\n"
"\n"
" The template() function is used to declare a template. To invoke the\n"
" template, just use the name of the template like any other target\n"
" type.\n"
"\n"
+ " Often you will want to declare your template in a special file that\n"
+ " other files will import (see \"gn help import\") so your template\n"
+ " rule can be shared across build files.\n"
+ "\n"
"More details:\n"
"\n"
- " Semantically, the code in the template is stored. When a function\n"
- " with the name is called, the block following the invocation is\n"
- " executed, *then* your template code is executed. So if the invocation\n"
- " sets the |source| variable, for example, that variable will be\n"
- " accessible to you when the template code runs.\n"
+ " When you call template() it creates a closure around all variables\n"
+ " currently in scope with the code in the template block. When the\n"
+ " template is invoked, the closure will be executed.\n"
"\n"
- " The template() function does not generate a closure, so the\n"
- " environment, current directory, etc. will all be the same as from\n"
- " the template is invoked.\n"
+ " When the template is invoked, the code in the caller is executed and\n"
+ " passed to the template code as an implicit \"invoker\" variable. The\n"
+ " template uses this to read state out of the invoking code.\n"
"\n"
- "Hints:\n"
+ " One thing explicitly excluded from the closure is the \"current\n"
+ " directory\" against which relative file names are resolved. The\n"
+ " current directory will be that of the invoking code, since typically\n"
+ " that code specifies the file names. This means all files internal\n"
+ " to the template should use absolute names.\n"
"\n"
- " If your template expands to more than one target, be sure to name\n"
- " the intermediate targets based on the name of the template\n"
- " instantiation so that the names are globally unique. The variable\n"
- " |target_name| will be this name.\n"
+ "Target naming:\n"
"\n"
- " Likewise, you will always want to generate a target in your template\n"
- " with the original |target_name|. Otherwise, invoking your template\n"
- " will not actually generate a node in the dependency graph that other\n"
- " targets can reference.\n"
+ " Your template should almost always define a built-in target with the\n"
+ " name the template invoker specified. For example, if you have an IDL\n"
+ " template and somebody does:\n"
+ " idl(\"foo\") {...\n"
+ " you will normally want this to expand to something defining a\n"
+ " source_set or static_library named \"foo\" (among other things you may\n"
+ " need). This way, when another target specifies a dependency on\n"
+ " \"foo\", the static_library or source_set will be linked.\n"
"\n"
- " Often you will want to declare your template in a special file that\n"
- " other files will import (see \"gn help import\") so your template\n"
- " rule can be shared across build files.\n"
+ " It is also important that any other targets your template expands to\n"
+ " have globally unique names, or you will get collisions.\n"
+ "\n"
+ " Access the invoking name in your template via the implicit\n"
+ " \"target_name\" variable. This should also be the basis of how other\n"
+ " targets that a template expands to to ensure uniquness.\n"
+ "\n"
+ " A typical example would be a template that defines an action to\n"
+ " generate some source files, and a source_set to compile that source.\n"
+ " Your template would name the source_set \"target_name\" because\n"
+ " that's what you want external targets to depend on to link your code.\n"
+ " And you would name the action something like \"${target_name}_action\"\n"
+ " to make it unique. The source set would have a dependency on the\n"
+ " action to make it run.\n"
"\n"
"Example of defining a template:\n"
"\n"
" template(\"my_idl\") {\n"
- " # Maps input files to output files, used in both targets below.\n"
+ " # Be nice and help callers debug problems by checking that the\n"
+ " # variables the template requires are defined. This gives a nice\n"
+ " # message rather than giving the user an error about an\n"
+ " # undefined variable in the file defining the template\n"
+ " #\n"
+ " # You can also use defined() to give default values to variables\n"
+ " # unspecified by the invoker.\n"
+ " assert(defined(invoker.sources),\n"
+ " \"Need sources in $target_name listing the idl files.\")\n"
+ "\n"
+ " # Define a variable containing a source expansion\n"
+ " # (see \"gn help source_expansion\") that maps input files to\n"
+ " # output files. It is used in both targets below.\n"
" filter = [ \"$target_gen_dir/{{source_name_part}}.cc\",\n"
" \"$target_gen_dir/{{source_name_part}}.h\" ]\n"
"\n"
- " # Intermediate target to compile IDL to C source.\n"
+ " # Intermediate target to convert IDL to C source. Note that the name\n"
+ " # is based on the name the invoker of the template specified. This\n"
+ " # way, each time the template is invoked we get a unique\n"
+ " # intermediate action name (since all target names are in the global\n"
+ " # scope).\n"
" action_foreach(\"${target_name}_code_gen\") {\n"
- " # The |sources| will be inherited from the surrounding scope so\n"
- " # we don't need to redefine it.\n"
- " script = \"foo.py\"\n"
+ " # Access the scope defined by the invoker via the implicit\n"
+ " # \"invoker\" variable.\n"
+ " sources = invoker.sources\n"
+ "\n"
+ " # Note that we need an absolute path for our script file name.\n"
+ " # The current directory when executing this code will be that of\n"
+ " # the invoker (this is why we can use the \"sources\" directly\n"
+ " # above without having to rebase all of the paths). But if we need\n"
+ " # to reference a script relative to the template file, we'll need\n"
+ " # to use an absolute path instead.\n"
+ " script = \"//tools/idl/idl_code_generator.py\"\n"
" outputs = filter # Variable from above.\n"
" }\n"
"\n"
- " # Name the static library the same as the template invocation so\n"
+ " # Name the source set the same as the template invocation so\n"
" # instancing this template produces something that other targets\n"
" # can link to in their deps.\n"
- " static_library(target_name) {\n"
+ " source_set(target_name) {\n"
" # Generates the list of sources.\n"
" # See \"gn help process_file_template\"\n"
- " sources = process_file_template(sources, filter)\n"
+ " sources = process_file_template(invoker.sources, filter)\n"
+ "\n"
+ " # This target depends on the files produced by the above code gen\n"
+ " # target.\n"
+ " deps = [ \":${target_name}_code_gen\" ]\n"
" }\n"
" }\n"
"\n"
"Example of invoking the resulting template:\n"
"\n"
+ " # This calls the template code above, defining target_name to be\n"
+ " # \"foo_idl_files\" and \"invoker\" to be the set of stuff defined in\n"
+ " # the curly brackets.\n"
" my_idl(\"foo_idl_files\") {\n"
+ " # Goes into the template as \"invoker.sources\".\n"
" sources = [ \"foo.idl\", \"bar.idl\" ]\n"
+ " }\n"
+ "\n"
+ " # Here is a target that depends on our template.\n"
+ " executable(\"my_exe\") {\n"
+ " # Depend on the name we gave the template call above. Internally,\n"
+ " # this will produce a dependency from executable to the source_set\n"
+ " # inside the template (since it has this name), which will in turn\n"
+ " # depend on the code gen action.\n"
+ " deps = [ \":foo_idl_files\" ]\n"
" }\n";
Value RunTemplate(Scope* scope,
@@ -97,16 +156,17 @@ Value RunTemplate(Scope* scope,
return Value();
std::string template_name = args[0].string_value();
- const FunctionCallNode* existing_template = scope->GetTemplate(template_name);
+ const Template* existing_template = scope->GetTemplate(template_name);
if (existing_template) {
*err = Err(function, "Duplicate template definition.",
"A template with this name was already defined.");
- err->AppendSubErr(Err(existing_template->function(),
+ err->AppendSubErr(Err(existing_template->GetDefinitionRange(),
"Previous definition."));
return Value();
}
- scope->AddTemplate(template_name, function);
+ scope->AddTemplate(template_name,
+ scoped_ptr<Template>(new Template(scope, function)));
return Value();
}
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index 8d146b2..43cdcd36 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -16,46 +16,10 @@
#include "tools/gn/scheduler.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
+#include "tools/gn/template.h"
#include "tools/gn/token.h"
#include "tools/gn/value.h"
-namespace {
-
-// This is called when a template is invoked. When we see a template
-// declaration, that funciton is RunTemplate.
-Value RunTemplateInvocation(Scope* scope,
- const FunctionCallNode* invocation,
- const std::vector<Value>& args,
- BlockNode* block,
- const FunctionCallNode* rule,
- Err* err) {
- if (!EnsureNotProcessingImport(invocation, scope, err))
- return Value();
-
- Scope block_scope(scope);
- if (!FillTargetBlockScope(scope, invocation,
- invocation->function().value().as_string(),
- block, args, &block_scope, err))
- return Value();
-
- // Run the block for the rule invocation.
- block->ExecuteBlockInScope(&block_scope, err);
- if (err->has_error())
- return Value();
-
- // Now run the rule itself with that block as the current scope.
- rule->block()->ExecuteBlockInScope(&block_scope, err);
- if (err->has_error())
- return Value();
-
- block_scope.CheckForUnusedVars(err);
- return Value();
-}
-
-} // namespace
-
-// ----------------------------------------------------------------------------
-
bool EnsureNotProcessingImport(const ParseNode* node,
const Scope* scope,
Err* err) {
@@ -97,7 +61,7 @@ bool FillTargetBlockScope(const Scope* scope,
// the block in.
const Scope* default_scope = scope->GetTargetDefaults(target_type);
if (default_scope) {
- if (!default_scope->NonRecursiveMergeTo(block_scope, function,
+ if (!default_scope->NonRecursiveMergeTo(block_scope, false, function,
"target defaults", err))
return false;
}
@@ -331,16 +295,29 @@ const char kDefined_Help[] =
" Returns true if the given argument is defined. This is most useful in\n"
" templates to assert that the caller set things up properly.\n"
"\n"
+ " You can pass an identifier:\n"
+ " defined(foo)\n"
+ " which will return true or false depending on whether foo is defined in\n"
+ " the current scope.\n"
+ "\n"
+ " You can also check a named scope:\n"
+ " defined(foo.bar)\n"
+ " which returns true if both foo is defined and bar is defined on the\n"
+ " named scope foo. It will throw an error if foo is defined but is not\n"
+ " a scope.\n"
+ "\n"
"Example:\n"
"\n"
" template(\"mytemplate\") {\n"
" # To help users call this template properly...\n"
- " assert(defined(sources), \"Sources must be defined\")\n"
+ " assert(defined(invoker.sources), \"Sources must be defined\")\n"
"\n"
" # If we want to accept an optional \"values\" argument, we don't\n"
" # want to dereference something that may not be defined.\n"
- " if (!defined(outputs)) {\n"
- " outputs = []\n"
+ " if (defined(invoker.values)) {\n"
+ " values = invoker.values\n"
+ " } else {\n"
+ " values = \"some default value\"\n"
" }\n"
" }\n";
@@ -349,17 +326,42 @@ Value RunDefined(Scope* scope,
const ListNode* args_list,
Err* err) {
const std::vector<const ParseNode*>& args_vector = args_list->contents();
- const IdentifierNode* identifier = NULL;
- if (args_vector.size() != 1 ||
- !(identifier = args_vector[0]->AsIdentifier())) {
- *err = Err(function, "Bad argument to defined().",
- "defined() takes one argument which should be an identifier.");
+ if (args_vector.size() != 1) {
+ *err = Err(function, "Wrong number of arguments to defined().",
+ "Expecting exactly one.");
return Value();
}
- if (scope->GetValue(identifier->value().value()))
- return Value(function, true);
- return Value(function, false);
+ const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
+ if (identifier) {
+ // Passed an identifier "defined(foo)".
+ if (scope->GetValue(identifier->value().value()))
+ return Value(function, true);
+ return Value(function, false);
+ }
+
+ const AccessorNode* accessor = args_vector[0]->AsAccessor();
+ if (accessor) {
+ // Passed an accessor "defined(foo.bar)".
+ if (accessor->member()) {
+ // The base of the accessor must be a scope if it's defined.
+ const Value* base = scope->GetValue(accessor->base().value());
+ if (!base)
+ return Value(function, false);
+ if (!base->VerifyTypeIs(Value::SCOPE, err))
+ return Value();
+
+ // Check the member inside the scope to see if its defined.
+ if (base->scope_value()->GetValue(accessor->member()->value().value()))
+ return Value(function, true);
+ return Value(function, false);
+ }
+ }
+
+ // Argument is invalid.
+ *err = Err(function, "Bad thing passed to defined().",
+ "It should be of the form defined(foo) or defined(foo.bar).");
+ return Value();
}
// getenv ----------------------------------------------------------------------
@@ -625,14 +627,13 @@ Value RunFunction(Scope* scope,
function_map.find(name.value());
if (found_function == function_map.end()) {
// No built-in function matching this, check for a template.
- const FunctionCallNode* rule =
+ const Template* templ =
scope->GetTemplate(function->function().value().as_string());
- if (rule) {
+ if (templ) {
Value args = args_list->Execute(scope, err);
if (err->has_error())
return Value();
- return RunTemplateInvocation(scope, function, args.list_value(), block,
- rule, err);
+ return templ->Invoke(scope, function, args.list_value(), block, err);
}
*err = Err(name, "Unknown function.");
diff --git a/tools/gn/functions_unittest.cc b/tools/gn/functions_unittest.cc
new file mode 100644
index 0000000..74ca605
--- /dev/null
+++ b/tools/gn/functions_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/functions.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/test_with_scope.h"
+#include "tools/gn/value.h"
+
+TEST(Functions, Defined) {
+ TestWithScope setup;
+
+ //InputFile input_file(SourceFile("//foo"));
+ FunctionCallNode function_call;
+ Err err;
+
+ // Test an undefined identifier.
+ Token undefined_token(Location(), Token::IDENTIFIER, "undef");
+ ListNode args_list_identifier_undefined;
+ args_list_identifier_undefined.append_item(
+ scoped_ptr<ParseNode>(new IdentifierNode(undefined_token)));
+ Value result = functions::RunDefined(setup.scope(), &function_call,
+ &args_list_identifier_undefined, &err);
+ ASSERT_EQ(Value::BOOLEAN, result.type());
+ EXPECT_FALSE(result.boolean_value());
+
+ // Define a value that's itself a scope value.
+ const char kDef[] = "def"; // Defined variable name.
+ Scope nested(setup.scope());
+ setup.scope()->SetValue(kDef, Value(NULL, &nested), NULL);
+
+ // Test the defined identifier.
+ Token defined_token(Location(), Token::IDENTIFIER, kDef);
+ ListNode args_list_identifier_defined;
+ args_list_identifier_defined.append_item(
+ scoped_ptr<ParseNode>(new IdentifierNode(defined_token)));
+ result = functions::RunDefined(setup.scope(), &function_call,
+ &args_list_identifier_defined, &err);
+ ASSERT_EQ(Value::BOOLEAN, result.type());
+ EXPECT_TRUE(result.boolean_value());
+
+ // Should also work by passing an accessor node so you can do
+ // "defined(def.foo)" to see if foo is defined on the def scope.
+ scoped_ptr<AccessorNode> undef_accessor(new AccessorNode);
+ undef_accessor->set_base(defined_token);
+ undef_accessor->set_member(scoped_ptr<IdentifierNode>(
+ new IdentifierNode(undefined_token)));
+ ListNode args_list_accessor_defined;
+ args_list_accessor_defined.append_item(undef_accessor.PassAs<ParseNode>());
+ result = functions::RunDefined(setup.scope(), &function_call,
+ &args_list_accessor_defined, &err);
+ ASSERT_EQ(Value::BOOLEAN, result.type());
+ EXPECT_FALSE(result.boolean_value());
+}
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index ab4cfe8..1c9c1c6 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -131,6 +131,8 @@
'target.h',
'target_generator.cc',
'target_generator.h',
+ 'template.cc',
+ 'template.h',
'token.cc',
'token.h',
'tokenizer.cc',
@@ -168,6 +170,7 @@
'filesystem_utils_unittest.cc',
'file_template_unittest.cc',
'function_rebase_path_unittest.cc',
+ 'functions_unittest.cc',
'input_conversion_unittest.cc',
'label_unittest.cc',
'loader_unittest.cc',
diff --git a/tools/gn/import_manager.cc b/tools/gn/import_manager.cc
index 0d87942..2e8293e 100644
--- a/tools/gn/import_manager.cc
+++ b/tools/gn/import_manager.cc
@@ -81,6 +81,6 @@ bool ImportManager::DoImport(const SourceFile& file,
}
}
- return imported_scope->NonRecursiveMergeTo(scope, node_for_err,
+ return imported_scope->NonRecursiveMergeTo(scope, false, node_for_err,
"import", err);
}
diff --git a/tools/gn/scope.cc b/tools/gn/scope.cc
index 86f82be..78612f2 100644
--- a/tools/gn/scope.cc
+++ b/tools/gn/scope.cc
@@ -7,6 +7,7 @@
#include "base/logging.h"
#include "base/stl_util.h"
#include "tools/gn/parse_tree.h"
+#include "tools/gn/template.h"
namespace {
@@ -41,6 +42,7 @@ Scope::Scope(const Scope* parent)
Scope::~Scope() {
STLDeleteContainerPairSecondPointers(target_defaults_.begin(),
target_defaults_.end());
+ STLDeleteContainerPairSecondPointers(templates_.begin(), templates_.end());
}
const Value* Scope::GetValue(const base::StringPiece& ident,
@@ -119,14 +121,14 @@ Value* Scope::SetValue(const base::StringPiece& ident,
return &r.value;
}
-bool Scope::AddTemplate(const std::string& name, const FunctionCallNode* decl) {
+bool Scope::AddTemplate(const std::string& name, scoped_ptr<Template> templ) {
if (GetTemplate(name))
return false;
- templates_[name] = decl;
+ templates_[name] = templ.release();
return true;
}
-const FunctionCallNode* Scope::GetTemplate(const std::string& name) const {
+const Template* Scope::GetTemplate(const std::string& name) const {
TemplateMap::const_iterator found = templates_.find(name);
if (found != templates_.end())
return found->second;
@@ -191,24 +193,28 @@ void Scope::GetCurrentScopeValues(KeyValueMap* output) const {
}
bool Scope::NonRecursiveMergeTo(Scope* dest,
+ bool clobber_existing,
const ParseNode* node_for_err,
const char* desc_for_err,
Err* err) const {
// Values.
for (RecordMap::const_iterator i = values_.begin(); i != values_.end(); ++i) {
const Value& new_value = i->second.value;
- const Value* existing_value = dest->GetValue(i->first);
- if (existing_value && new_value != *existing_value) {
- // Value present in both the source and the dest.
- std::string desc_string(desc_for_err);
- *err = Err(node_for_err, "Value collision.",
- "This " + desc_string + " contains \"" + i->first.as_string() + "\"");
- err->AppendSubErr(Err(i->second.value, "defined here.",
- "Which would clobber the one in your current scope"));
- err->AppendSubErr(Err(*existing_value, "defined here.",
- "Executing " + desc_string + " should not conflict with anything "
- "in the current\nscope unless the values are identical."));
- return false;
+ if (!clobber_existing) {
+ const Value* existing_value = dest->GetValue(i->first);
+ if (existing_value && new_value != *existing_value) {
+ // Value present in both the source and the dest.
+ std::string desc_string(desc_for_err);
+ *err = Err(node_for_err, "Value collision.",
+ "This " + desc_string + " contains \"" + i->first.as_string() +
+ "\"");
+ err->AppendSubErr(Err(i->second.value, "defined here.",
+ "Which would clobber the one in your current scope"));
+ err->AppendSubErr(Err(*existing_value, "defined here.",
+ "Executing " + desc_string + " should not conflict with anything "
+ "in the current\nscope unless the values are identical."));
+ return false;
+ }
}
dest->values_[i->first] = i->second;
}
@@ -216,34 +222,42 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
// Target defaults are owning pointers.
for (NamedScopeMap::const_iterator i = target_defaults_.begin();
i != target_defaults_.end(); ++i) {
- if (dest->GetTargetDefaults(i->first)) {
- // TODO(brettw) it would be nice to know the origin of a
- // set_target_defaults so we can give locations for the colliding target
- // defaults.
- std::string desc_string(desc_for_err);
- *err = Err(node_for_err, "Target defaults collision.",
- "This " + desc_string + " contains target defaults for\n"
- "\"" + i->first + "\" which would clobber one for the\n"
- "same target type in your current scope. It's unfortunate that I'm "
- "too stupid\nto tell you the location of where the target defaults "
- "were set. Usually\nthis happens in the BUILDCONFIG.gn file.");
- return false;
+ if (!clobber_existing) {
+ if (dest->GetTargetDefaults(i->first)) {
+ // TODO(brettw) it would be nice to know the origin of a
+ // set_target_defaults so we can give locations for the colliding target
+ // defaults.
+ std::string desc_string(desc_for_err);
+ *err = Err(node_for_err, "Target defaults collision.",
+ "This " + desc_string + " contains target defaults for\n"
+ "\"" + i->first + "\" which would clobber one for the\n"
+ "same target type in your current scope. It's unfortunate that I'm "
+ "too stupid\nto tell you the location of where the target defaults "
+ "were set. Usually\nthis happens in the BUILDCONFIG.gn file.");
+ return false;
+ }
}
- Scope* s = new Scope(settings_);
- i->second->NonRecursiveMergeTo(s, node_for_err, "<SHOULDN'T HAPPEN>", err);
- dest->target_defaults_[i->first] = s;
+ // Be careful to delete any pointer we're about to clobber.
+ Scope** dest_scope = &dest->target_defaults_[i->first];
+ if (*dest_scope)
+ delete *dest_scope;
+ *dest_scope = new Scope(settings_);
+ i->second->NonRecursiveMergeTo(*dest_scope, clobber_existing, node_for_err,
+ "<SHOULDN'T HAPPEN>", err);
}
// Sources assignment filter.
if (sources_assignment_filter_) {
- if (dest->GetSourcesAssignmentFilter()) {
- // Sources assignment filter present in both the source and the dest.
- std::string desc_string(desc_for_err);
- *err = Err(node_for_err, "Assignment filter collision.",
- "The " + desc_string + " contains a sources_assignment_filter which\n"
- "would clobber the one in your current scope.");
- return false;
+ if (!clobber_existing) {
+ if (dest->GetSourcesAssignmentFilter()) {
+ // Sources assignment filter present in both the source and the dest.
+ std::string desc_string(desc_for_err);
+ *err = Err(node_for_err, "Assignment filter collision.",
+ "The " + desc_string + " contains a sources_assignment_filter "
+ "which\nwould clobber the one in your current scope.");
+ return false;
+ }
}
dest->sources_assignment_filter_.reset(
new PatternList(*sources_assignment_filter_));
@@ -252,25 +266,56 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
// Templates.
for (TemplateMap::const_iterator i = templates_.begin();
i != templates_.end(); ++i) {
- const FunctionCallNode* existing_template = dest->GetTemplate(i->first);
- if (existing_template) {
- // Rule present in both the source and the dest.
- std::string desc_string(desc_for_err);
- *err = Err(node_for_err, "Template collision.",
- "This " + desc_string + " contains a template \"" + i->first + "\"");
- err->AppendSubErr(Err(i->second->function(), "defined here.",
- "Which would clobber the one in your current scope"));
- err->AppendSubErr(Err(existing_template->function(), "defined here.",
- "Executing " + desc_string + " should not conflict with anything "
- "in the current\nscope."));
- return false;
+ if (!clobber_existing) {
+ const Template* existing_template = dest->GetTemplate(i->first);
+ if (existing_template) {
+ // Rule present in both the source and the dest.
+ std::string desc_string(desc_for_err);
+ *err = Err(node_for_err, "Template collision.",
+ "This " + desc_string + " contains a template \"" +
+ i->first + "\"");
+ err->AppendSubErr(Err(i->second->GetDefinitionRange(), "defined here.",
+ "Which would clobber the one in your current scope"));
+ err->AppendSubErr(Err(existing_template->GetDefinitionRange(),
+ "defined here.",
+ "Executing " + desc_string + " should not conflict with anything "
+ "in the current\nscope."));
+ return false;
+ }
}
- dest->templates_.insert(*i);
+
+ // Be careful to delete any pointer we're about to clobber.
+ const Template** dest_template = &dest->templates_[i->first];
+ if (*dest_template)
+ delete *dest_template;
+ *dest_template = i->second;
}
return true;
}
+scoped_ptr<Scope> Scope::MakeClosure() const {
+ scoped_ptr<Scope> result;
+ if (const_containing_) {
+ // We reached the top of the mutable scope stack. The result scope just
+ // references the const scope (which will never change).
+ result.reset(new Scope(const_containing_));
+ } else if (mutable_containing_) {
+ // There are more nested mutable scopes. Recursively go up the stack to
+ // get the closure.
+ result = mutable_containing_->MakeClosure();
+ } else {
+ // This is a standalone scope, just copy it.
+ result.reset(new Scope(settings_));
+ }
+
+ // Add in our variables and we're done.
+ Err err;
+ NonRecursiveMergeTo(result.get(), true, NULL, "<SHOULDN'T HAPPEN>", &err);
+ DCHECK(!err.has_error());
+ return result.Pass();
+}
+
Scope* Scope::MakeTargetDefaults(const std::string& target_type) {
if (GetTargetDefaults(target_type))
return NULL;
diff --git a/tools/gn/scope.h b/tools/gn/scope.h
index bd6722d..82e037f 100644
--- a/tools/gn/scope.h
+++ b/tools/gn/scope.h
@@ -21,6 +21,7 @@ class ImportManager;
class ParseNode;
class Settings;
class TargetManager;
+class Template;
// Scope for the script execution.
//
@@ -128,11 +129,11 @@ class Scope {
const ParseNode* set_node);
// Templates associated with this scope. A template can only be set once, so
- // AddTemplate will fail and return NULL if a rule with that name already
+ // AddTemplate will fail and return false if a rule with that name already
// exists. GetTemplate returns NULL if the rule doesn't exist, and it will
// check all containing scoped rescursively.
- bool AddTemplate(const std::string& name, const FunctionCallNode* decl);
- const FunctionCallNode* GetTemplate(const std::string& name) const;
+ bool AddTemplate(const std::string& name, scoped_ptr<Template> templ);
+ const Template* GetTemplate(const std::string& name) const;
// Marks the given identifier as (un)used in the current scope.
void MarkUsed(const base::StringPiece& ident);
@@ -158,10 +159,13 @@ class Scope {
// copied, neither will the reference to the containing scope (this is why
// it's "non-recursive").
//
- // It is an error to merge a variable into a scope that already has something
- // with that name in scope (meaning in that scope or in any of its containing
- // scopes). If this happens, the error will be set and the function will
- // return false.
+ // If clobber_existing is true, any existing values will be overwritten. In
+ // this mode, this function will never fail.
+ //
+ // If clobber_existing is false, it will be an error to merge a variable into
+ // a scope that already has something with that name in scope (meaning in
+ // that scope or in any of its containing scopes). If this happens, the error
+ // will be set and the function will return false.
//
// This is used in different contexts. When generating the error, the given
// parse node will be blamed, and the given desc will be used to describe
@@ -169,10 +173,18 @@ class Scope {
// would be "import" when doing an import, and the error string would say
// something like "The import contains...".
bool NonRecursiveMergeTo(Scope* dest,
+ bool clobber_existing,
const ParseNode* node_for_err,
const char* desc_for_err,
Err* err) const;
+ // Constructs a scope that is a copy of the current one. Nested scopes will
+ // be collapsed until we reach a const containing scope. The resulting
+ // closure will reference the const containing scope as its containing scope
+ // (since we assume the const scope won't change, we don't have to copy its
+ // values).
+ scoped_ptr<Scope> MakeClosure() const;
+
// Makes an empty scope with the given name. Returns NULL if the name is
// already set.
Scope* MakeTargetDefaults(const std::string& target_type);
@@ -267,9 +279,8 @@ class Scope {
// scope's filter.
scoped_ptr<PatternList> sources_assignment_filter_;
- // Non-owning pointers, the function calls are owned by the input file which
- // should be kept around by the input file manager.
- typedef std::map<std::string, const FunctionCallNode*> TemplateMap;
+ // Owning pointers, must be deleted.
+ typedef std::map<std::string, const Template*> TemplateMap;
TemplateMap templates_;
// Opaque pointers. See SetProperty() above.
diff --git a/tools/gn/scope_unittest.cc b/tools/gn/scope_unittest.cc
index 494b90de..5585e50 100644
--- a/tools/gn/scope_unittest.cc
+++ b/tools/gn/scope_unittest.cc
@@ -8,6 +8,21 @@
#include "tools/gn/scope.h"
#include "tools/gn/test_with_scope.h"
+namespace {
+
+bool HasStringValueEqualTo(const Scope* scope,
+ const char* name,
+ const char* expected_value) {
+ const Value* value = scope->GetValue(name);
+ if (!value)
+ return false;
+ if (value->type() != Value::STRING)
+ return false;
+ return value->string_value() == expected_value;
+}
+
+} // namespace
+
TEST(Scope, NonRecursiveMergeTo) {
TestWithScope setup;
@@ -29,11 +44,27 @@ TEST(Scope, NonRecursiveMergeTo) {
new_scope.SetValue("v", new_value, &assignment);
Err err;
- EXPECT_FALSE(new_scope.NonRecursiveMergeTo(
- setup.scope(), &assignment, "error", &err));
+ EXPECT_FALSE(setup.scope()->NonRecursiveMergeTo(
+ &new_scope, false, &assignment, "error", &err));
EXPECT_TRUE(err.has_error());
}
+ // The clobber flag should just overwrite colliding values.
+ {
+ Scope new_scope(setup.settings());
+ Value new_value(&assignment, "goodbye");
+ new_scope.SetValue("v", new_value, &assignment);
+
+ Err err;
+ EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
+ &new_scope, true, &assignment, "error", &err));
+ EXPECT_FALSE(err.has_error());
+
+ const Value* found_value = new_scope.GetValue("v");
+ ASSERT_TRUE(found_value);
+ EXPECT_TRUE(old_value == *found_value);
+ }
+
// Don't flag values that technically collide but have the same value.
{
Scope new_scope(setup.settings());
@@ -41,12 +72,48 @@ TEST(Scope, NonRecursiveMergeTo) {
new_scope.SetValue("v", new_value, &assignment);
Err err;
- EXPECT_TRUE(new_scope.NonRecursiveMergeTo(
- setup.scope(), &assignment, "error", &err));
+ EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
+ &new_scope, false, &assignment, "error", &err));
EXPECT_FALSE(err.has_error());
}
}
+TEST(Scope, MakeClosure) {
+ // Create 3 nested scopes [const root from setup] <- nested1 <- nested2.
+ TestWithScope setup;
+
+ // Make a pretend parse node with proper tracking that we can blame for the
+ // given value.
+ InputFile input_file(SourceFile("//foo"));
+ Token assignment_token(Location(&input_file, 1, 1), Token::STRING,
+ "\"hello\"");
+ LiteralNode assignment;
+ assignment.set_value(assignment_token);
+ setup.scope()->SetValue("on_root", Value(&assignment, "on_root"),
+ &assignment);
+
+ // Root scope should be const from the nested caller's perspective.
+ Scope nested1(static_cast<const Scope*>(setup.scope()));
+ nested1.SetValue("on_one", Value(&assignment, "on_one"), &assignment);
+
+ Scope nested2(&nested1);
+ nested2.SetValue("on_one", Value(&assignment, "on_two"), &assignment);
+ nested2.SetValue("on_two", Value(&assignment, "on_two2"), &assignment);
+
+ // Making a closure from the root scope.
+ scoped_ptr<Scope> result = setup.scope()->MakeClosure();
+ EXPECT_FALSE(result->containing()); // Should have no containing scope.
+ EXPECT_TRUE(result->GetValue("on_root")); // Value should be copied.
+
+ // Making a closure from the second nested scope.
+ result = nested2.MakeClosure();
+ EXPECT_EQ(setup.scope(),
+ result->containing()); // Containing scope should be the root.
+ EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_root", "on_root"));
+ EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_one", "on_two"));
+ EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_two", "on_two2"));
+}
+
TEST(Scope, GetMutableValue) {
TestWithScope setup;
diff --git a/tools/gn/template.cc b/tools/gn/template.cc
new file mode 100644
index 0000000..cae5e0c
--- /dev/null
+++ b/tools/gn/template.cc
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/template.h"
+
+#include "tools/gn/err.h"
+#include "tools/gn/functions.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/scope.h"
+#include "tools/gn/value.h"
+
+Template::Template(const Scope* scope, const FunctionCallNode* def)
+ : closure_(scope->MakeClosure()),
+ definition_(def) {
+}
+
+Template::Template(scoped_ptr<Scope> scope, const FunctionCallNode* def)
+ : closure_(scope.Pass()),
+ definition_(def) {
+}
+
+Template::~Template() {
+}
+
+scoped_ptr<Template> Template::Clone() const {
+ // We can make a new closure from our closure to copy it.
+ return scoped_ptr<Template>(
+ new Template(closure_->MakeClosure(), definition_));
+}
+
+Value Template::Invoke(Scope* scope,
+ const FunctionCallNode* invocation,
+ const std::vector<Value>& args,
+ BlockNode* block,
+ Err* err) const {
+ // Don't allow templates to be executed from imported files. Imports are for
+ // simple values only.
+ if (!EnsureNotProcessingImport(invocation, scope, err))
+ return Value();
+
+ // First run the invocation's block.
+ Scope invocation_scope(scope);
+ if (!FillTargetBlockScope(scope, invocation,
+ invocation->function().value().as_string(),
+ block, args, &invocation_scope, err))
+ return Value();
+ block->ExecuteBlockInScope(&invocation_scope, err);
+ if (err->has_error())
+ return Value();
+
+ // Set up the scope to run the template. This should be dependent on the
+ // closure, but have the "invoker" and "target_name" values injected, and the
+ // current dir matching the invoker.
+ Scope template_scope(closure_.get());
+ template_scope.SetValue("invoker", Value(NULL, &invocation_scope),
+ invocation);
+ template_scope.set_source_dir(scope->GetSourceDir());
+
+ const base::StringPiece target_name("target_name");
+ template_scope.SetValue(target_name,
+ Value(invocation, args[0].string_value()),
+ invocation);
+
+ // Run the template code. Don't check for unused variables since the
+ // template could be executed in many different ways and it could be that
+ // not all executions use all values in the closure.
+ return definition_->block()->ExecuteBlockInScope(&template_scope, err);
+}
+
+LocationRange Template::GetDefinitionRange() const {
+ return definition_->GetRange();
+}
diff --git a/tools/gn/template.h b/tools/gn/template.h
new file mode 100644
index 0000000..0176862
--- /dev/null
+++ b/tools/gn/template.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TEMPLATE_H_
+#define TOOLS_GN_TEMPLATE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+class BlockNode;
+class Err;
+class FunctionCallNode;
+class LocationRange;
+class Scope;
+class Value;
+
+class Template {
+ public:
+ // Makes a new closure based on the given scope.
+ Template(const Scope* scope, const FunctionCallNode* def);
+
+ // Takes ownership of a previously-constructed closure.
+ Template(scoped_ptr<Scope> closure, const FunctionCallNode* def);
+
+ ~Template();
+
+ // Makes a copy of this template.
+ scoped_ptr<Template> Clone() const;
+
+ // Invoke the template. The values correspond to the state of the code
+ // invoking the template.
+ Value Invoke(Scope* scope,
+ const FunctionCallNode* invocation,
+ const std::vector<Value>& args,
+ BlockNode* block,
+ Err* err) const;
+
+ // Returns the location range where this template was defined.
+ LocationRange GetDefinitionRange() const;
+
+ private:
+ Template();
+
+ scoped_ptr<Scope> closure_;
+ const FunctionCallNode* definition_;
+
+ DISALLOW_COPY_AND_ASSIGN(Template);
+};
+
+#endif // TOOLS_GN_TEMPLATE_H_