1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/err.h"
6 #include "gn/functions.h"
7 #include "gn/parse_tree.h"
8 #include "gn/scope.h"
9
10 namespace functions {
11
12 namespace {
13
ForwardAllValues(const FunctionCallNode * function,Scope * source,Scope * dest,const std::set<std::string> & exclusion_set,Err * err)14 void ForwardAllValues(const FunctionCallNode* function,
15 Scope* source,
16 Scope* dest,
17 const std::set<std::string>& exclusion_set,
18 Err* err) {
19 Scope::MergeOptions options;
20 // This function needs to clobber existing for it to be useful. It will be
21 // called in a template to forward all values, but there will be some
22 // default stuff like configs set up in both scopes, so it would always
23 // fail if it didn't clobber.
24 options.clobber_existing = true;
25 options.skip_private_vars = true;
26 options.mark_dest_used = false;
27 options.excluded_values = exclusion_set;
28 source->NonRecursiveMergeTo(dest, options, function, "source scope", err);
29 source->MarkAllUsed();
30 }
31
ForwardValuesFromList(Scope * source,Scope * dest,const std::vector<Value> & list,const std::set<std::string> & exclusion_set,Err * err)32 void ForwardValuesFromList(Scope* source,
33 Scope* dest,
34 const std::vector<Value>& list,
35 const std::set<std::string>& exclusion_set,
36 Err* err) {
37 for (const Value& cur : list) {
38 if (!cur.VerifyTypeIs(Value::STRING, err))
39 return;
40 if (exclusion_set.find(cur.string_value()) != exclusion_set.end())
41 continue;
42 const Value* value = source->GetValue(cur.string_value(), true);
43 if (value) {
44 // Use the storage key for the original value rather than the string in
45 // "cur" because "cur" is a temporary that will be deleted, and Scopes
46 // expect a persistent std::string_view (it won't copy). Not doing this
47 // will lead the scope's key to point to invalid memory after this
48 // returns.
49 std::string_view storage_key = source->GetStorageKey(cur.string_value());
50 if (storage_key.empty()) {
51 // Programmatic value, don't allow copying.
52 *err =
53 Err(cur, "This value can't be forwarded.",
54 "The variable \"" + cur.string_value() + "\" is a built-in.");
55 return;
56 }
57
58 // Don't allow clobbering existing values.
59 const Value* existing_value = dest->GetValue(storage_key);
60 if (existing_value) {
61 *err = Err(
62 cur, "Clobbering existing value.",
63 "The current scope already defines a value \"" +
64 cur.string_value() +
65 "\".\nforward_variables_from() won't clobber "
66 "existing values. If you want to\nmerge lists, you'll need to "
67 "do this explicitly.");
68 err->AppendSubErr(Err(*existing_value, "value being clobbered."));
69 return;
70 }
71
72 // Keep the origin information from the original value. The normal
73 // usage is for this to be used in a template, and if there's an error,
74 // the user expects to see the line where they set the variable
75 // blamed, rather than a template call to forward_variables_from().
76 dest->SetValue(storage_key, *value, value->origin());
77 }
78 }
79 }
80
81 } // namespace
82
83 const char kForwardVariablesFrom[] = "forward_variables_from";
84 const char kForwardVariablesFrom_HelpShort[] =
85 "forward_variables_from: Copies variables from a different scope.";
86 const char kForwardVariablesFrom_Help[] =
87 R"(forward_variables_from: Copies variables from a different scope.
88
89 forward_variables_from(from_scope, variable_list_or_star,
90 variable_to_not_forward_list = [])
91
92 Copies the given variables from the given scope to the local scope if they
93 exist. This is normally used in the context of templates to use the values of
94 variables defined in the template invocation to a template-defined target.
95
96 The variables in the given variable_list will be copied if they exist in the
97 given scope or any enclosing scope. If they do not exist, nothing will happen
98 and they be left undefined in the current scope.
99
100 As a special case, if the variable_list is a string with the value of "*",
101 all variables from the given scope will be copied. "*" only copies variables
102 set directly on the from_scope, not enclosing ones. Otherwise it would
103 duplicate all global variables.
104
105 When an explicit list of variables is supplied, if the variable exists in the
106 current (destination) scope already, an error will be thrown. If "*" is
107 specified, variables in the current scope will be clobbered (the latter is
108 important because most targets have an implicit configs list, which means it
109 wouldn't work at all if it didn't clobber).
110
111 If variables_to_not_forward_list is non-empty, then it must contains a list
112 of variable names that will not be forwarded. This is mostly useful when
113 variable_list_or_star has a value of "*".
114
115 Examples
116
117 # forward_variables_from(invoker, ["foo"])
118 # is equivalent to:
119 assert(!defined(foo))
120 if (defined(invoker.foo)) {
121 foo = invoker.foo
122 }
123
124 # This is a common action template. It would invoke a script with some given
125 # parameters, and wants to use the various types of deps and the visibility
126 # from the invoker if it's defined. It also injects an additional dependency
127 # to all targets.
128 template("my_test") {
129 action(target_name) {
130 forward_variables_from(invoker, [ "data_deps", "deps",
131 "public_deps", "visibility"])
132 # Add our test code to the dependencies.
133 # "deps" may or may not be defined at this point.
134 if (defined(deps)) {
135 deps += [ "//tools/doom_melon" ]
136 } else {
137 deps = [ "//tools/doom_melon" ]
138 }
139 }
140 }
141
142 # This is a template around a target whose type depends on a global variable.
143 # It forwards all values from the invoker.
144 template("my_wrapper") {
145 target(my_wrapper_target_type, target_name) {
146 forward_variables_from(invoker, "*")
147 }
148 }
149
150 # A template that wraps another. It adds behavior based on one
151 # variable, and forwards all others to the nested target.
152 template("my_ios_test_app") {
153 ios_test_app(target_name) {
154 forward_variables_from(invoker, "*", ["test_bundle_name"])
155 if (!defined(extra_substitutions)) {
156 extra_substitutions = []
157 }
158 extra_substitutions += [ "BUNDLE_ID_TEST_NAME=$test_bundle_name" ]
159 }
160 }
161 )";
162
163 // This function takes a ListNode rather than a resolved vector of values
164 // both avoid copying the potentially-large source scope, and so the variables
165 // in the source scope can be marked as used.
RunForwardVariablesFrom(Scope * scope,const FunctionCallNode * function,const ListNode * args_list,Err * err)166 Value RunForwardVariablesFrom(Scope* scope,
167 const FunctionCallNode* function,
168 const ListNode* args_list,
169 Err* err) {
170 const auto& args_vector = args_list->contents();
171 if (args_vector.size() != 2 && args_vector.size() != 3) {
172 *err = Err(function, "Wrong number of arguments.",
173 "Expecting two or three arguments.");
174 return Value();
175 }
176
177 Value* value = nullptr; // Value to use, may point to result_value.
178 Value result_value; // Storage for the "evaluate" case.
179 const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
180 if (identifier) {
181 // Optimize the common case where the input scope is an identifier. This
182 // prevents a copy of a potentially large Scope object.
183 value = scope->GetMutableValue(identifier->value().value(),
184 Scope::SEARCH_NESTED, true);
185 if (!value) {
186 *err = Err(identifier, "Undefined identifier.");
187 return Value();
188 }
189 } else {
190 // Non-optimized case, just evaluate the argument.
191 result_value = args_vector[0]->Execute(scope, err);
192 if (err->has_error())
193 return Value();
194 value = &result_value;
195 }
196
197 // Extract the source scope.
198 if (!value->VerifyTypeIs(Value::SCOPE, err))
199 return Value();
200 Scope* source = value->scope_value();
201
202 // Extract the exclusion list if defined.
203 std::set<std::string> exclusion_set;
204 if (args_vector.size() == 3) {
205 Value exclusion_value = args_vector[2]->Execute(scope, err);
206 if (err->has_error())
207 return Value();
208
209 if (exclusion_value.type() != Value::LIST) {
210 *err = Err(exclusion_value, "Not a valid list of variables to exclude.",
211 "Expecting a list of strings.");
212 return Value();
213 }
214
215 for (const Value& cur : exclusion_value.list_value()) {
216 if (!cur.VerifyTypeIs(Value::STRING, err))
217 return Value();
218
219 exclusion_set.insert(cur.string_value());
220 }
221 }
222
223 // Extract the list. If all_values is not set, the what_value will be a list.
224 Value what_value = args_vector[1]->Execute(scope, err);
225 if (err->has_error())
226 return Value();
227 if (what_value.type() == Value::STRING) {
228 if (what_value.string_value() == "*") {
229 ForwardAllValues(function, source, scope, exclusion_set, err);
230 return Value();
231 }
232 } else {
233 if (what_value.type() == Value::LIST) {
234 ForwardValuesFromList(source, scope, what_value.list_value(),
235 exclusion_set, err);
236 return Value();
237 }
238 }
239
240 // Not the right type of argument.
241 *err = Err(what_value, "Not a valid list of variables to copy.",
242 "Expecting either the string \"*\" or a list of strings.");
243 return Value();
244 }
245
246 } // namespace functions
247