diff options
-rw-r--r-- | tools/gn/BUILD.gn | 3 | ||||
-rw-r--r-- | tools/gn/function_set_defaults.cc | 3 | ||||
-rw-r--r-- | tools/gn/function_template.cc | 126 | ||||
-rw-r--r-- | tools/gn/functions.cc | 107 | ||||
-rw-r--r-- | tools/gn/functions_unittest.cc | 55 | ||||
-rw-r--r-- | tools/gn/gn.gyp | 3 | ||||
-rw-r--r-- | tools/gn/import_manager.cc | 2 | ||||
-rw-r--r-- | tools/gn/scope.cc | 145 | ||||
-rw-r--r-- | tools/gn/scope.h | 31 | ||||
-rw-r--r-- | tools/gn/scope_unittest.cc | 75 | ||||
-rw-r--r-- | tools/gn/template.cc | 73 | ||||
-rw-r--r-- | tools/gn/template.h | 53 |
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_ |