Commit bf5ff790 authored by sdefresne's avatar sdefresne Committed by Commit bot

Support for excluding variable from forwarding via forward_variables_form.

Some templates wants to forward all their parameters to the underlying
target except a select few they use. Previously they had to list all
the variables the underlying target supported which was error prone and
brittle if the underlying target is changed.

With this change they can just use the following:

  forward_variables_from(invoker, "*", ["my_extra_variable"])
  # my_extra_variable is not forwarded, all other variables are.

BUG=580293

Review URL: https://codereview.chromium.org/1632573002

Cr-Commit-Position: refs/heads/master@{#371492}
parent 33d50063
...@@ -1327,7 +1327,8 @@ ...@@ -1327,7 +1327,8 @@
## **forward_variables_from**: Copies variables from a different scope. ## **forward_variables_from**: Copies variables from a different scope.
``` ```
forward_variables_from(from_scope, variable_list_or_star) forward_variables_from(from_scope, variable_list_or_star,
variable_to_not_forward_list = [])
Copies the given variables from the given scope to the local scope Copies the given variables from the given scope to the local scope
if they exist. This is normally used in the context of templates to if they exist. This is normally used in the context of templates to
...@@ -1354,6 +1355,10 @@ ...@@ -1354,6 +1355,10 @@
is never applied by this function. It's assumed than any desired is never applied by this function. It's assumed than any desired
filtering was already done when sources was set on the from_scope. filtering was already done when sources was set on the from_scope.
If variables_to_not_forward_list is non-empty, then it must contains
a list of variable names that will not be forwarded. This is mostly
useful when variable_list_or_star has a value of "*".
``` ```
### **Examples** ### **Examples**
...@@ -1385,6 +1390,18 @@ ...@@ -1385,6 +1390,18 @@
} }
} }
# A template that wraps another. It adds behavior based on one
# variable, and forwards all others to the nested target.
template("my_ios_test_app") {
ios_test_app(target_name) {
forward_variables_from(invoker, "*", ["test_bundle_name"])
if (!defined(extra_substitutions)) {
extra_substitutions = []
}
extra_substitutions += [ "BUNDLE_ID_TEST_NAME=$test_bundle_name" ]
}
}
``` ```
## **get_label_info**: Get an attribute from a target's label. ## **get_label_info**: Get an attribute from a target's label.
......
...@@ -14,6 +14,7 @@ namespace { ...@@ -14,6 +14,7 @@ namespace {
void ForwardAllValues(const FunctionCallNode* function, void ForwardAllValues(const FunctionCallNode* function,
Scope* source, Scope* source,
Scope* dest, Scope* dest,
const std::set<std::string>& exclusion_set,
Err* err) { Err* err) {
Scope::MergeOptions options; Scope::MergeOptions options;
// This function needs to clobber existing for it to be useful. It will be // This function needs to clobber existing for it to be useful. It will be
...@@ -23,6 +24,7 @@ void ForwardAllValues(const FunctionCallNode* function, ...@@ -23,6 +24,7 @@ void ForwardAllValues(const FunctionCallNode* function,
options.clobber_existing = true; options.clobber_existing = true;
options.skip_private_vars = true; options.skip_private_vars = true;
options.mark_dest_used = false; options.mark_dest_used = false;
options.excluded_values = exclusion_set;
source->NonRecursiveMergeTo(dest, options, function, source->NonRecursiveMergeTo(dest, options, function,
"source scope", err); "source scope", err);
source->MarkAllUsed(); source->MarkAllUsed();
...@@ -31,10 +33,13 @@ void ForwardAllValues(const FunctionCallNode* function, ...@@ -31,10 +33,13 @@ void ForwardAllValues(const FunctionCallNode* function,
void ForwardValuesFromList(Scope* source, void ForwardValuesFromList(Scope* source,
Scope* dest, Scope* dest,
const std::vector<Value>& list, const std::vector<Value>& list,
const std::set<std::string>& exclusion_set,
Err* err) { Err* err) {
for (const Value& cur : list) { for (const Value& cur : list) {
if (!cur.VerifyTypeIs(Value::STRING, err)) if (!cur.VerifyTypeIs(Value::STRING, err))
return; return;
if (exclusion_set.find(cur.string_value()) != exclusion_set.end())
continue;
const Value* value = source->GetValue(cur.string_value(), true); const Value* value = source->GetValue(cur.string_value(), true);
if (value) { if (value) {
// Use the storage key for the original value rather than the string in // Use the storage key for the original value rather than the string in
...@@ -66,7 +71,8 @@ const char kForwardVariablesFrom_HelpShort[] = ...@@ -66,7 +71,8 @@ const char kForwardVariablesFrom_HelpShort[] =
const char kForwardVariablesFrom_Help[] = const char kForwardVariablesFrom_Help[] =
"forward_variables_from: Copies variables from a different scope.\n" "forward_variables_from: Copies variables from a different scope.\n"
"\n" "\n"
" forward_variables_from(from_scope, variable_list_or_star)\n" " forward_variables_from(from_scope, variable_list_or_star,\n"
" variable_to_not_forward_list = [])\n"
"\n" "\n"
" Copies the given variables from the given scope to the local scope\n" " Copies the given variables from the given scope to the local scope\n"
" if they exist. This is normally used in the context of templates to\n" " if they exist. This is normally used in the context of templates to\n"
...@@ -94,6 +100,10 @@ const char kForwardVariablesFrom_Help[] = ...@@ -94,6 +100,10 @@ const char kForwardVariablesFrom_Help[] =
" is never applied by this function. It's assumed than any desired\n" " is never applied by this function. It's assumed than any desired\n"
" filtering was already done when sources was set on the from_scope.\n" " filtering was already done when sources was set on the from_scope.\n"
"\n" "\n"
" If variables_to_not_forward_list is non-empty, then it must contains\n"
" a list of variable names that will not be forwarded. This is mostly\n"
" useful when variable_list_or_star has a value of \"*\".\n"
"\n"
"Examples\n" "Examples\n"
"\n" "\n"
" # This is a common action template. It would invoke a script with\n" " # This is a common action template. It would invoke a script with\n"
...@@ -121,6 +131,19 @@ const char kForwardVariablesFrom_Help[] = ...@@ -121,6 +131,19 @@ const char kForwardVariablesFrom_Help[] =
" target(my_wrapper_target_type, target_name) {\n" " target(my_wrapper_target_type, target_name) {\n"
" forward_variables_from(invoker, \"*\")\n" " forward_variables_from(invoker, \"*\")\n"
" }\n" " }\n"
" }\n"
"\n"
" # A template that wraps another. It adds behavior based on one \n"
" # variable, and forwards all others to the nested target.\n"
" template(\"my_ios_test_app\") {\n"
" ios_test_app(target_name) {\n"
" forward_variables_from(invoker, \"*\", [\"test_bundle_name\"])\n"
" if (!defined(extra_substitutions)) {\n"
" extra_substitutions = []\n"
" }\n"
" extra_substitutions += [ \"BUNDLE_ID_TEST_NAME=$test_bundle_name\" "
"]\n"
" }\n"
" }\n"; " }\n";
// This function takes a ListNode rather than a resolved vector of values // This function takes a ListNode rather than a resolved vector of values
...@@ -131,9 +154,9 @@ Value RunForwardVariablesFrom(Scope* scope, ...@@ -131,9 +154,9 @@ Value RunForwardVariablesFrom(Scope* scope,
const ListNode* args_list, const ListNode* args_list,
Err* err) { Err* err) {
const std::vector<const ParseNode*>& args_vector = args_list->contents(); const std::vector<const ParseNode*>& args_vector = args_list->contents();
if (args_vector.size() != 2) { if (args_vector.size() != 2 && args_vector.size() != 3) {
*err = Err(function, "Wrong number of arguments.", *err = Err(function, "Wrong number of arguments.",
"Expecting exactly two."); "Expecting two or three arguments.");
return Value(); return Value();
} }
...@@ -157,18 +180,40 @@ Value RunForwardVariablesFrom(Scope* scope, ...@@ -157,18 +180,40 @@ Value RunForwardVariablesFrom(Scope* scope,
return Value(); return Value();
Scope* source = value->scope_value(); Scope* source = value->scope_value();
// Extract the exclusion list if defined.
std::set<std::string> exclusion_set;
if (args_vector.size() == 3) {
Value exclusion_value = args_vector[2]->Execute(scope, err);
if (err->has_error())
return Value();
if (exclusion_value.type() != Value::LIST) {
*err = Err(exclusion_value, "Not a valid list of variables to exclude.",
"Expecting a list of strings.");
return Value();
}
for (const Value& cur : exclusion_value.list_value()) {
if (!cur.VerifyTypeIs(Value::STRING, err))
return Value();
exclusion_set.insert(cur.string_value());
}
}
// Extract the list. If all_values is not set, the what_value will be a list. // Extract the list. If all_values is not set, the what_value will be a list.
Value what_value = args_vector[1]->Execute(scope, err); Value what_value = args_vector[1]->Execute(scope, err);
if (err->has_error()) if (err->has_error())
return Value(); return Value();
if (what_value.type() == Value::STRING) { if (what_value.type() == Value::STRING) {
if (what_value.string_value() == "*") { if (what_value.string_value() == "*") {
ForwardAllValues(function, source, scope, err); ForwardAllValues(function, source, scope, exclusion_set, err);
return Value(); return Value();
} }
} else { } else {
if (what_value.type() == Value::LIST) { if (what_value.type() == Value::LIST) {
ForwardValuesFromList(source, scope, what_value.list_value(), err); ForwardValuesFromList(source, scope, what_value.list_value(),
exclusion_set, err);
return Value(); return Value();
} }
} }
......
...@@ -32,6 +32,34 @@ TEST(FunctionForwardVariablesFrom, List) { ...@@ -32,6 +32,34 @@ TEST(FunctionForwardVariablesFrom, List) {
setup.print_output().clear(); setup.print_output().clear();
} }
TEST(FunctionForwardVariablesFrom, ListWithExclusion) {
Scheduler scheduler;
TestWithScope setup;
// Defines a template and copy the two x and y, and z values out.
TestParseInput input(
"template(\"a\") {\n"
" forward_variables_from(invoker, [\"x\", \"y\", \"z\"], [\"z\"])\n"
" assert(!defined(z))\n" // "z" should still be undefined.
" print(\"$target_name, $x, $y\")\n"
"}\n"
"a(\"target\") {\n"
" x = 1\n"
" y = 2\n"
" z = 3\n"
" print(\"$z\")\n"
"}\n");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
EXPECT_EQ("3\ntarget, 1, 2\n", setup.print_output());
setup.print_output().clear();
}
TEST(FunctionForwardVariablesFrom, ErrorCases) { TEST(FunctionForwardVariablesFrom, ErrorCases) {
Scheduler scheduler; Scheduler scheduler;
TestWithScope setup; TestWithScope setup;
...@@ -65,19 +93,61 @@ TEST(FunctionForwardVariablesFrom, ErrorCases) { ...@@ -65,19 +93,61 @@ TEST(FunctionForwardVariablesFrom, ErrorCases) {
EXPECT_TRUE(err.has_error()); EXPECT_TRUE(err.has_error());
EXPECT_EQ("Not a valid list of variables to copy.", err.message()); EXPECT_EQ("Not a valid list of variables to copy.", err.message());
// Type check the exclusion list.
TestParseInput invalid_exclusion_list(
"template(\"c\") {\n"
" forward_variables_from(invoker, \"*\", 42)\n"
" print(\"$target_name\")\n"
"}\n"
"c(\"target\") {\n"
"}\n");
ASSERT_FALSE(invalid_exclusion_list.has_error());
err = Err();
invalid_exclusion_list.parsed()->Execute(setup.scope(), &err);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Not a valid list of variables to exclude.", err.message());
// Programmatic values should error. // Programmatic values should error.
TestParseInput prog( TestParseInput prog(
"template(\"c\") {\n" "template(\"d\") {\n"
" forward_variables_from(invoker, [\"root_out_dir\"])\n" " forward_variables_from(invoker, [\"root_out_dir\"])\n"
" print(\"$target_name\")\n" " print(\"$target_name\")\n"
"}\n" "}\n"
"c(\"target\") {\n" "d(\"target\") {\n"
"}\n"); "}\n");
ASSERT_FALSE(prog.has_error()); ASSERT_FALSE(prog.has_error());
err = Err(); err = Err();
prog.parsed()->Execute(setup.scope(), &err); prog.parsed()->Execute(setup.scope(), &err);
EXPECT_TRUE(err.has_error()); EXPECT_TRUE(err.has_error());
EXPECT_EQ("This value can't be forwarded.", err.message()); EXPECT_EQ("This value can't be forwarded.", err.message());
// Not enough arguments.
TestParseInput not_enough_arguments(
"template(\"e\") {\n"
" forward_variables_from(invoker)\n"
" print(\"$target_name\")\n"
"}\n"
"e(\"target\") {\n"
"}\n");
ASSERT_FALSE(not_enough_arguments.has_error());
err = Err();
not_enough_arguments.parsed()->Execute(setup.scope(), &err);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Wrong number of arguments.", err.message());
// Too many arguments.
TestParseInput too_many_arguments(
"template(\"f\") {\n"
" forward_variables_from(invoker, \"*\", [], [])\n"
" print(\"$target_name\")\n"
"}\n"
"f(\"target\") {\n"
"}\n");
ASSERT_FALSE(too_many_arguments.has_error());
err = Err();
too_many_arguments.parsed()->Execute(setup.scope(), &err);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Wrong number of arguments.", err.message());
} }
TEST(FunctionForwardVariablesFrom, Star) { TEST(FunctionForwardVariablesFrom, Star) {
...@@ -106,3 +176,33 @@ TEST(FunctionForwardVariablesFrom, Star) { ...@@ -106,3 +176,33 @@ TEST(FunctionForwardVariablesFrom, Star) {
EXPECT_EQ("target, 1, 2\n", setup.print_output()); EXPECT_EQ("target, 1, 2\n", setup.print_output());
setup.print_output().clear(); setup.print_output().clear();
} }
TEST(FunctionForwardVariablesFrom, StarWithExclusion) {
Scheduler scheduler;
TestWithScope setup;
// Defines a template and copy all values except z value. The "*" behavior
// should clobber existing variables with the same name.
TestParseInput input(
"template(\"a\") {\n"
" x = 1000000\n" // Should be clobbered.
" forward_variables_from(invoker, \"*\", [\"z\"])\n"
" print(\"$target_name, $x, $y\")\n"
"}\n"
"a(\"target\") {\n"
" x = 1\n"
" y = 2\n"
" z = 3\n"
" print(\"$z\")\n"
"}\n");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
EXPECT_EQ("3\ntarget, 1, 2\n", setup.print_output());
setup.print_output().clear();
}
...@@ -25,6 +25,15 @@ bool IsPrivateVar(const base::StringPiece& name) { ...@@ -25,6 +25,15 @@ bool IsPrivateVar(const base::StringPiece& name) {
} // namespace } // namespace
// Defaults to all false, which are the things least likely to cause errors.
Scope::MergeOptions::MergeOptions()
: clobber_existing(false),
skip_private_vars(false),
mark_dest_used(false) {
}
Scope::MergeOptions::~MergeOptions() {
}
Scope::ProgrammaticProvider::~ProgrammaticProvider() { Scope::ProgrammaticProvider::~ProgrammaticProvider() {
scope_->RemoveProvider(this); scope_->RemoveProvider(this);
...@@ -249,17 +258,23 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -249,17 +258,23 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
Err* err) const { Err* err) const {
// Values. // Values.
for (const auto& pair : values_) { for (const auto& pair : values_) {
if (options.skip_private_vars && IsPrivateVar(pair.first)) const base::StringPiece& current_name = pair.first;
if (options.skip_private_vars && IsPrivateVar(current_name))
continue; // Skip this private var. continue; // Skip this private var.
if (!options.excluded_values.empty() &&
options.excluded_values.find(current_name.as_string()) !=
options.excluded_values.end()) {
continue; // Skip this excluded value.
}
const Value& new_value = pair.second.value; const Value& new_value = pair.second.value;
if (!options.clobber_existing) { if (!options.clobber_existing) {
const Value* existing_value = dest->GetValue(pair.first); const Value* existing_value = dest->GetValue(current_name);
if (existing_value && new_value != *existing_value) { if (existing_value && new_value != *existing_value) {
// Value present in both the source and the dest. // Value present in both the source and the dest.
std::string desc_string(desc_for_err); std::string desc_string(desc_for_err);
*err = Err(node_for_err, "Value collision.", *err = Err(node_for_err, "Value collision.",
"This " + desc_string + " contains \"" + pair.first.as_string() + "This " + desc_string + " contains \"" + current_name.as_string() +
"\""); "\"");
err->AppendSubErr(Err(pair.second.value, "defined here.", err->AppendSubErr(Err(pair.second.value, "defined here.",
"Which would clobber the one in your current scope")); "Which would clobber the one in your current scope"));
...@@ -269,23 +284,30 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -269,23 +284,30 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
return false; return false;
} }
} }
dest->values_[pair.first] = pair.second; dest->values_[current_name] = pair.second;
if (options.mark_dest_used) if (options.mark_dest_used)
dest->MarkUsed(pair.first); dest->MarkUsed(current_name);
} }
// Target defaults are owning pointers. // Target defaults are owning pointers.
for (const auto& pair : target_defaults_) { for (const auto& pair : target_defaults_) {
const std::string& current_name = pair.first;
if (!options.excluded_values.empty() &&
options.excluded_values.find(current_name) !=
options.excluded_values.end()) {
continue; // Skip the excluded value.
}
if (!options.clobber_existing) { if (!options.clobber_existing) {
if (dest->GetTargetDefaults(pair.first)) { if (dest->GetTargetDefaults(current_name)) {
// TODO(brettw) it would be nice to know the origin of a // TODO(brettw) it would be nice to know the origin of a
// set_target_defaults so we can give locations for the colliding target // set_target_defaults so we can give locations for the colliding target
// defaults. // defaults.
std::string desc_string(desc_for_err); std::string desc_string(desc_for_err);
*err = Err(node_for_err, "Target defaults collision.", *err = Err(node_for_err, "Target defaults collision.",
"This " + desc_string + " contains target defaults for\n" "This " + desc_string + " contains target defaults for\n"
"\"" + pair.first + "\" which would clobber one for the\n" "\"" + current_name + "\" which would clobber one for the\n"
"same target type in your current scope. It's unfortunate that I'm " "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 " "too stupid\nto tell you the location of where the target defaults "
"were set. Usually\nthis happens in the BUILDCONFIG.gn file."); "were set. Usually\nthis happens in the BUILDCONFIG.gn file.");
...@@ -294,7 +316,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -294,7 +316,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
} }
// Be careful to delete any pointer we're about to clobber. // Be careful to delete any pointer we're about to clobber.
Scope** dest_scope = &dest->target_defaults_[pair.first]; Scope** dest_scope = &dest->target_defaults_[current_name];
if (*dest_scope) if (*dest_scope)
delete *dest_scope; delete *dest_scope;
*dest_scope = new Scope(settings_); *dest_scope = new Scope(settings_);
...@@ -320,11 +342,17 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -320,11 +342,17 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
// Templates. // Templates.
for (const auto& pair : templates_) { for (const auto& pair : templates_) {
if (options.skip_private_vars && IsPrivateVar(pair.first)) const std::string& current_name = pair.first;
if (options.skip_private_vars && IsPrivateVar(current_name))
continue; // Skip this private template. continue; // Skip this private template.
if (!options.excluded_values.empty() &&
options.excluded_values.find(current_name) !=
options.excluded_values.end()) {
continue; // Skip the excluded value.
}
if (!options.clobber_existing) { if (!options.clobber_existing) {
const Template* existing_template = dest->GetTemplate(pair.first); const Template* existing_template = dest->GetTemplate(current_name);
// Since templates are refcounted, we can check if it's the same one by // Since templates are refcounted, we can check if it's the same one by
// comparing pointers. // comparing pointers.
if (existing_template && pair.second.get() != existing_template) { if (existing_template && pair.second.get() != existing_template) {
...@@ -333,7 +361,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -333,7 +361,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
std::string desc_string(desc_for_err); std::string desc_string(desc_for_err);
*err = Err(node_for_err, "Template collision.", *err = Err(node_for_err, "Template collision.",
"This " + desc_string + " contains a template \"" + "This " + desc_string + " contains a template \"" +
pair.first + "\""); current_name + "\"");
err->AppendSubErr(Err(pair.second->GetDefinitionRange(), err->AppendSubErr(Err(pair.second->GetDefinitionRange(),
"defined here.", "defined here.",
"Which would clobber the one in your current scope")); "Which would clobber the one in your current scope"));
...@@ -346,7 +374,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest, ...@@ -346,7 +374,7 @@ bool Scope::NonRecursiveMergeTo(Scope* dest,
} }
// Be careful to delete any pointer we're about to clobber. // Be careful to delete any pointer we're about to clobber.
dest->templates_[pair.first] = pair.second; dest->templates_[current_name] = pair.second;
} }
return true; return true;
......
...@@ -65,12 +65,8 @@ class Scope { ...@@ -65,12 +65,8 @@ class Scope {
// Options for configuring scope merges. // Options for configuring scope merges.
struct MergeOptions { struct MergeOptions {
// Defaults to all false, which are the things least likely to cause errors. MergeOptions();
MergeOptions() ~MergeOptions();
: clobber_existing(false),
skip_private_vars(false),
mark_dest_used(false) {
}
// When set, all existing avlues in the destination scope will be // When set, all existing avlues in the destination scope will be
// overwritten. // overwritten.
...@@ -92,6 +88,9 @@ class Scope { ...@@ -92,6 +88,9 @@ class Scope {
// import, for example, or files that don't need a variable from the .gni // import, for example, or files that don't need a variable from the .gni
// file will throw an error. // file will throw an error.
bool mark_dest_used; bool mark_dest_used;
// When set, those variables are not merged.
std::set<std::string> excluded_values;
}; };
// Creates an empty toplevel scope. // Creates an empty toplevel scope.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment