| // 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/err.h" |
| #include "tools/gn/functions.h" |
| #include "tools/gn/parse_tree.h" |
| #include "tools/gn/scope.h" |
| |
| namespace functions { |
| |
| namespace { |
| |
| void ForwardAllValues(const FunctionCallNode* function, |
| Scope* source, |
| Scope* dest, |
| const std::set<std::string>& exclusion_set, |
| Err* err) { |
| Scope::MergeOptions options; |
| // This function needs to clobber existing for it to be useful. It will be |
| // called in a template to forward all values, but there will be some |
| // default stuff like configs set up in both scopes, so it would always |
| // fail if it didn't clobber. |
| options.clobber_existing = true; |
| options.skip_private_vars = true; |
| options.mark_dest_used = false; |
| options.excluded_values = exclusion_set; |
| source->NonRecursiveMergeTo(dest, options, function, "source scope", err); |
| source->MarkAllUsed(); |
| } |
| |
| void ForwardValuesFromList(Scope* source, |
| Scope* dest, |
| const std::vector<Value>& list, |
| const std::set<std::string>& exclusion_set, |
| Err* err) { |
| for (const Value& cur : list) { |
| if (!cur.VerifyTypeIs(Value::STRING, err)) |
| return; |
| if (exclusion_set.find(cur.string_value()) != exclusion_set.end()) |
| continue; |
| const Value* value = source->GetValue(cur.string_value(), true); |
| if (value) { |
| // Use the storage key for the original value rather than the string in |
| // "cur" because "cur" is a temporary that will be deleted, and Scopes |
| // expect a persistent std::string_view (it won't copy). Not doing this |
| // will lead the scope's key to point to invalid memory after this |
| // returns. |
| std::string_view storage_key = source->GetStorageKey(cur.string_value()); |
| if (storage_key.empty()) { |
| // Programmatic value, don't allow copying. |
| *err = |
| Err(cur, "This value can't be forwarded.", |
| "The variable \"" + cur.string_value() + "\" is a built-in."); |
| return; |
| } |
| |
| // Don't allow clobbering existing values. |
| const Value* existing_value = dest->GetValue(storage_key); |
| if (existing_value) { |
| *err = Err( |
| cur, "Clobbering existing value.", |
| "The current scope already defines a value \"" + |
| cur.string_value() + |
| "\".\nforward_variables_from() won't clobber " |
| "existing values. If you want to\nmerge lists, you'll need to " |
| "do this explicitly."); |
| err->AppendSubErr(Err(*existing_value, "value being clobbered.")); |
| return; |
| } |
| |
| // Keep the origin information from the original value. The normal |
| // usage is for this to be used in a template, and if there's an error, |
| // the user expects to see the line where they set the variable |
| // blamed, rather than a template call to forward_variables_from(). |
| dest->SetValue(storage_key, *value, value->origin()); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| const char kForwardVariablesFrom[] = "forward_variables_from"; |
| const char kForwardVariablesFrom_HelpShort[] = |
| "forward_variables_from: Copies variables from a different scope."; |
| const char kForwardVariablesFrom_Help[] = |
| R"(forward_variables_from: Copies variables from a different scope. |
| |
| 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 if they |
| exist. This is normally used in the context of templates to use the values of |
| variables defined in the template invocation to a template-defined target. |
| |
| The variables in the given variable_list will be copied if they exist in the |
| given scope or any enclosing scope. If they do not exist, nothing will happen |
| and they be left undefined in the current scope. |
| |
| As a special case, if the variable_list is a string with the value of "*", |
| all variables from the given scope will be copied. "*" only copies variables |
| set directly on the from_scope, not enclosing ones. Otherwise it would |
| duplicate all global variables. |
| |
| When an explicit list of variables is supplied, if the variable exists in the |
| current (destination) scope already, an error will be thrown. If "*" is |
| specified, variables in the current scope will be clobbered (the latter is |
| important because most targets have an implicit configs list, which means it |
| wouldn't work at all if it didn't clobber). |
| |
| The sources assignment filter (see "gn help set_sources_assignment_filter") |
| is never applied by this function. It's assumed than any desired 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 |
| |
| # forward_variables_from(invoker, ["foo"]) |
| # is equivalent to: |
| assert(!defined(foo)) |
| if (defined(invoker.foo)) { |
| foo = invoker.foo |
| } |
| |
| # This is a common action template. It would invoke a script with some given |
| # parameters, and wants to use the various types of deps and the visibility |
| # from the invoker if it's defined. It also injects an additional dependency |
| # to all targets. |
| template("my_test") { |
| action(target_name) { |
| forward_variables_from(invoker, [ "data_deps", "deps", |
| "public_deps", "visibility"]) |
| # Add our test code to the dependencies. |
| # "deps" may or may not be defined at this point. |
| if (defined(deps)) { |
| deps += [ "//tools/doom_melon" ] |
| } else { |
| deps = [ "//tools/doom_melon" ] |
| } |
| } |
| } |
| |
| # This is a template around a target whose type depends on a global variable. |
| # It forwards all values from the invoker. |
| template("my_wrapper") { |
| target(my_wrapper_target_type, target_name) { |
| forward_variables_from(invoker, "*") |
| } |
| } |
| |
| # 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" ] |
| } |
| } |
| )"; |
| |
| // This function takes a ListNode rather than a resolved vector of values |
| // both avoid copying the potentially-large source scope, and so the variables |
| // in the source scope can be marked as used. |
| Value RunForwardVariablesFrom(Scope* scope, |
| const FunctionCallNode* function, |
| const ListNode* args_list, |
| Err* err) { |
| const auto& args_vector = args_list->contents(); |
| if (args_vector.size() != 2 && args_vector.size() != 3) { |
| *err = Err(function, "Wrong number of arguments.", |
| "Expecting two or three arguments."); |
| return Value(); |
| } |
| |
| Value* value = nullptr; // Value to use, may point to result_value. |
| Value result_value; // Storage for the "evaluate" case. |
| const IdentifierNode* identifier = args_vector[0]->AsIdentifier(); |
| if (identifier) { |
| // Optimize the common case where the input scope is an identifier. This |
| // prevents a copy of a potentially large Scope object. |
| value = scope->GetMutableValue(identifier->value().value(), |
| Scope::SEARCH_NESTED, true); |
| if (!value) { |
| *err = Err(identifier, "Undefined identifier."); |
| return Value(); |
| } |
| } else { |
| // Non-optimized case, just evaluate the argument. |
| result_value = args_vector[0]->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| value = &result_value; |
| } |
| |
| // Extract the source scope. |
| if (!value->VerifyTypeIs(Value::SCOPE, err)) |
| return 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. |
| Value what_value = args_vector[1]->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| if (what_value.type() == Value::STRING) { |
| if (what_value.string_value() == "*") { |
| ForwardAllValues(function, source, scope, exclusion_set, err); |
| return Value(); |
| } |
| } else { |
| if (what_value.type() == Value::LIST) { |
| ForwardValuesFromList(source, scope, what_value.list_value(), |
| exclusion_set, err); |
| return Value(); |
| } |
| } |
| |
| // Not the right type of argument. |
| *err = Err(what_value, "Not a valid list of variables to copy.", |
| "Expecting either the string \"*\" or a list of strings."); |
| return Value(); |
| } |
| |
| } // namespace functions |