| // Copyright (c) 2013 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 "gn/functions.h" |
| |
| #include <stddef.h> |
| #include <cctype> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/environment.h" |
| #include "base/strings/string_util.h" |
| #include "gn/build_settings.h" |
| #include "gn/config.h" |
| #include "gn/config_values_generator.h" |
| #include "gn/err.h" |
| #include "gn/input_file.h" |
| #include "gn/parse_node_value_adapter.h" |
| #include "gn/parse_tree.h" |
| #include "gn/pool.h" |
| #include "gn/scheduler.h" |
| #include "gn/scope.h" |
| #include "gn/settings.h" |
| #include "gn/template.h" |
| #include "gn/token.h" |
| #include "gn/value.h" |
| #include "gn/value_extractors.h" |
| #include "gn/variables.h" |
| |
| namespace { |
| |
| // Some functions take a {} following them, and some don't. For the ones that |
| // don't, this is used to verify that the given block node is null and will |
| // set the error accordingly if it's not. Returns true if the block is null. |
| bool VerifyNoBlockForFunctionCall(const FunctionCallNode* function, |
| const BlockNode* block, |
| Err* err) { |
| if (!block) |
| return true; |
| |
| *err = |
| Err(block, "Unexpected '{'.", |
| "This function call doesn't take a {} block following it, and you\n" |
| "can't have a {} block that's not connected to something like an if\n" |
| "statement or a target declaration."); |
| err->AppendRange(function->function().range()); |
| return false; |
| } |
| |
| // This key is set as a scope property on the scope of a declare_args() block, |
| // in order to prevent reading a variable defined earlier in the same call |
| // (see `gn help declare_args` for more). |
| const void* kInDeclareArgsKey = nullptr; |
| |
| } // namespace |
| |
| bool EnsureNotReadingFromSameDeclareArgs(const ParseNode* node, |
| const Scope* cur_scope, |
| const Scope* val_scope, |
| Err* err) { |
| // If the value didn't come from a scope at all, we're safe. |
| if (!val_scope) |
| return true; |
| |
| const Scope* val_args_scope = nullptr; |
| val_scope->GetProperty(&kInDeclareArgsKey, &val_args_scope); |
| |
| const Scope* cur_args_scope = nullptr; |
| cur_scope->GetProperty(&kInDeclareArgsKey, &cur_args_scope); |
| if (!val_args_scope || !cur_args_scope || (val_args_scope != cur_args_scope)) |
| return true; |
| |
| *err = |
| Err(node, |
| "Reading a variable defined in the same declare_args() call.\n" |
| "\n" |
| "If you need to set the value of one arg based on another, put\n" |
| "them in two separate declare_args() calls, one after the other.\n"); |
| return false; |
| } |
| |
| bool EnsureNotProcessingImport(const ParseNode* node, |
| const Scope* scope, |
| Err* err) { |
| if (scope->IsProcessingImport()) { |
| *err = |
| Err(node, "Not valid from an import.", |
| "Imports are for defining defaults, variables, and rules. The\n" |
| "appropriate place for this kind of thing is really in a normal\n" |
| "BUILD file."); |
| return false; |
| } |
| return true; |
| } |
| |
| bool EnsureNotProcessingBuildConfig(const ParseNode* node, |
| const Scope* scope, |
| Err* err) { |
| if (scope->IsProcessingBuildConfig()) { |
| *err = Err(node, "Not valid from the build config.", |
| "You can't do this kind of thing from the build config script, " |
| "silly!\nPut it in a regular BUILD file."); |
| return false; |
| } |
| return true; |
| } |
| |
| bool FillTargetBlockScope(const Scope* scope, |
| const FunctionCallNode* function, |
| const std::string& target_type, |
| const BlockNode* block, |
| const std::vector<Value>& args, |
| Scope* block_scope, |
| Err* err) { |
| if (!block) { |
| FillNeedsBlockError(function, err); |
| return false; |
| } |
| |
| // Copy the target defaults, if any, into the scope we're going to execute |
| // the block in. |
| const Scope* default_scope = scope->GetTargetDefaults(target_type); |
| if (default_scope) { |
| Scope::MergeOptions merge_options; |
| merge_options.skip_private_vars = true; |
| if (!default_scope->NonRecursiveMergeTo(block_scope, merge_options, |
| function, "target defaults", err)) |
| return false; |
| } |
| |
| // The name is the single argument to the target function. |
| if (!EnsureSingleStringArg(function, args, err)) |
| return false; |
| |
| // Set the target name variable to the current target, and mark it used |
| // because we don't want to issue an error if the script ignores it. |
| const std::string_view target_name(variables::kTargetName); |
| block_scope->SetValue(target_name, Value(function, args[0].string_value()), |
| function); |
| block_scope->MarkUsed(target_name); |
| return true; |
| } |
| |
| void FillNeedsBlockError(const FunctionCallNode* function, Err* err) { |
| *err = Err(function->function(), "This function call requires a block.", |
| "The block's \"{\" must be on the same line as the function " |
| "call's \")\"."); |
| } |
| |
| bool EnsureSingleStringArg(const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| if (args.size() != 1) { |
| *err = Err(function->function(), "Incorrect arguments.", |
| "This function requires a single string argument."); |
| return false; |
| } |
| return args[0].VerifyTypeIs(Value::STRING, err); |
| } |
| |
| const Label& ToolchainLabelForScope(const Scope* scope) { |
| return scope->settings()->toolchain_label(); |
| } |
| |
| Label MakeLabelForScope(const Scope* scope, |
| const FunctionCallNode* function, |
| const std::string& name) { |
| const Label& toolchain_label = ToolchainLabelForScope(scope); |
| return Label(scope->GetSourceDir(), name, toolchain_label.dir(), |
| toolchain_label.name()); |
| } |
| |
| // static |
| const int NonNestableBlock::kKey = 0; |
| |
| NonNestableBlock::NonNestableBlock(Scope* scope, |
| const FunctionCallNode* function, |
| const char* type_description) |
| : scope_(scope), |
| function_(function), |
| type_description_(type_description), |
| key_added_(false) {} |
| |
| NonNestableBlock::~NonNestableBlock() { |
| if (key_added_) |
| scope_->SetProperty(&kKey, nullptr); |
| } |
| |
| bool NonNestableBlock::Enter(Err* err) { |
| void* scope_value = scope_->GetProperty(&kKey, nullptr); |
| if (scope_value) { |
| // Existing block. |
| const NonNestableBlock* existing = |
| reinterpret_cast<const NonNestableBlock*>(scope_value); |
| *err = Err(function_, "Can't nest these things.", |
| std::string("You are trying to nest a ") + type_description_ + |
| " inside a " + existing->type_description_ + "."); |
| err->AppendSubErr(Err(existing->function_, "The enclosing block.")); |
| return false; |
| } |
| |
| scope_->SetProperty(&kKey, this); |
| key_added_ = true; |
| return true; |
| } |
| |
| namespace functions { |
| |
| // assert ---------------------------------------------------------------------- |
| |
| const char kAssert[] = "assert"; |
| const char kAssert_HelpShort[] = |
| "assert: Assert an expression is true at generation time."; |
| const char kAssert_Help[] = |
| R"(assert: Assert an expression is true at generation time. |
| |
| assert(<condition> [, <error string>]) |
| |
| If the condition is false, the build will fail with an error. If the |
| optional second argument is provided, that string will be printed |
| with the error message. |
| |
| Examples |
| |
| assert(is_win) |
| assert(defined(sources), "Sources must be defined"); |
| )"; |
| |
| Value RunAssert(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| // Check usage: Assert takes 1 or 2 arguments. |
| if (args.size() != 1 && args.size() != 2) { |
| *err = Err(function->function(), "Wrong number of arguments.", |
| "assert() takes one or two arguments, " |
| "were you expecting something else?"); |
| return Value(); |
| } |
| |
| // Check usage: The first argument must be a boolean. |
| if (args[0].type() != Value::BOOLEAN) { |
| *err = Err(function->function(), "Assertion value not a bool."); |
| return Value(); |
| } |
| bool assertion_passed = args[0].boolean_value(); |
| |
| // Check usage: The second argument, if present, must be a string. |
| if (args.size() == 2 && args[1].type() != Value::STRING) { |
| *err = Err(function->function(), "Assertion message is not a string."); |
| return Value(); |
| } |
| |
| // Assertion passed: there is nothing to do, so return an empty value. |
| if (assertion_passed) { |
| return Value(); |
| } |
| |
| // Assertion failed; try to make a useful message and report it. |
| if (args.size() == 2) { |
| *err = |
| Err(function->function(), "Assertion failed.", args[1].string_value()); |
| } else { |
| *err = Err(function->function(), "Assertion failed."); |
| } |
| if (args[0].origin()) { |
| // If you do "assert(foo)" we'd ideally like to show you where foo was |
| // set, and in this case the origin of the args will tell us that. |
| // However, if you do "assert(foo && bar)" the source of the value will |
| // be the assert like, which isn't so helpful. |
| // |
| // So we try to see if the args are from the same line or not. This will |
| // break if you do "assert(\nfoo && bar)" and we may show the second line |
| // as the source, oh well. The way around this is to check to see if the |
| // origin node is inside our function call block. |
| Location origin_location = args[0].origin()->GetRange().begin(); |
| if (origin_location.file() != function->function().location().file() || |
| origin_location.line_number() != |
| function->function().location().line_number()) { |
| err->AppendSubErr( |
| Err(args[0].origin()->GetRange(), "", "This is where it was set.")); |
| } |
| } |
| return Value(); |
| } |
| |
| // config ---------------------------------------------------------------------- |
| |
| const char kConfig[] = "config"; |
| const char kConfig_HelpShort[] = "config: Defines a configuration object."; |
| const char kConfig_Help[] = |
| R"(config: Defines a configuration object. |
| |
| Configuration objects can be applied to targets and specify sets of compiler |
| flags, includes, defines, etc. They provide a way to conveniently group sets |
| of this configuration information. |
| |
| A config is referenced by its label just like a target. |
| |
| The values in a config are additive only. If you want to remove a flag you |
| need to remove the corresponding config that sets it. The final set of flags, |
| defines, etc. for a target is generated in this order: |
| |
| 1. The values specified directly on the target (rather than using a config). |
| 2. The configs specified in the target's "configs" list, in order. |
| 3. Public_configs from a breadth-first traversal of the dependency tree in |
| the order that the targets appear in "deps". |
| 4. All dependent configs from a breadth-first traversal of the dependency |
| tree in the order that the targets appear in "deps". |
| |
| More background |
| |
| Configs solve a problem where the build system needs to have a higher-level |
| understanding of various compiler settings. For example, some compiler flags |
| have to appear in a certain order relative to each other, some settings like |
| defines and flags logically go together, and the build system needs to |
| de-duplicate flags even though raw command-line parameters can't always be |
| operated on in that way. |
| |
| The config gives a name to a group of settings that can then be reasoned |
| about by GN. GN can know that configs with the same label are the same thing |
| so can be de-duplicated. It allows related settings to be grouped so they |
| are added or removed as a unit. And it allows targets to refer to settings |
| with conceptual names ("no_rtti", "enable_exceptions", etc.) rather than |
| having to hard-coding every compiler's flags each time they are referred to. |
| |
| Variables valid in a config definition |
| |
| )" |
| |
| CONFIG_VALUES_VARS_HELP |
| |
| R"( Nested configs: configs |
| General: visibility |
| |
| Variables on a target used to apply configs |
| |
| all_dependent_configs, configs, public_configs |
| |
| Example |
| |
| config("myconfig") { |
| include_dirs = [ "include/common" ] |
| defines = [ "ENABLE_DOOM_MELON" ] |
| } |
| |
| executable("mything") { |
| configs = [ ":myconfig" ] |
| } |
| )"; |
| |
| Value RunConfig(const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Scope* scope, |
| Err* err) { |
| NonNestableBlock non_nestable(scope, function, "config"); |
| if (!non_nestable.Enter(err)) |
| return Value(); |
| |
| if (!EnsureSingleStringArg(function, args, err) || |
| !EnsureNotProcessingImport(function, scope, err)) |
| return Value(); |
| |
| Label label(MakeLabelForScope(scope, function, args[0].string_value())); |
| |
| if (g_scheduler->verbose_logging()) |
| g_scheduler->Log("Defining config", label.GetUserVisibleName(true)); |
| |
| // Create the new config. |
| std::unique_ptr<Config> config = std::make_unique<Config>( |
| scope->settings(), label, scope->build_dependency_files()); |
| config->set_defined_from(function); |
| if (!Visibility::FillItemVisibility(config.get(), scope, err)) |
| return Value(); |
| |
| // Fill the flags and such. |
| const SourceDir& input_dir = scope->GetSourceDir(); |
| ConfigValuesGenerator gen(&config->own_values(), scope, input_dir, err); |
| gen.Run(); |
| if (err->has_error()) |
| return Value(); |
| |
| // Read sub-configs. |
| const Value* configs_value = scope->GetValue(variables::kConfigs, true); |
| if (configs_value) { |
| ExtractListOfUniqueLabels(scope->settings()->build_settings(), |
| *configs_value, scope->GetSourceDir(), |
| ToolchainLabelForScope(scope), &config->configs(), |
| err); |
| } |
| if (err->has_error()) |
| return Value(); |
| |
| // Save the generated item. |
| Scope::ItemVector* collector = scope->GetItemCollector(); |
| if (!collector) { |
| *err = Err(function, "Can't define a config in this context."); |
| return Value(); |
| } |
| collector->push_back(std::move(config)); |
| |
| return Value(); |
| } |
| |
| // declare_args ---------------------------------------------------------------- |
| |
| const char kDeclareArgs[] = "declare_args"; |
| const char kDeclareArgs_HelpShort[] = "declare_args: Declare build arguments."; |
| const char kDeclareArgs_Help[] = |
| R"(declare_args: Declare build arguments. |
| |
| Introduces the given arguments into the current scope. If they are not |
| specified on the command line or in a toolchain's arguments, the default |
| values given in the declare_args block will be used. However, these defaults |
| will not override command-line values. |
| |
| See also "gn help buildargs" for an overview. |
| |
| The precise behavior of declare args is: |
| |
| 1. The declare_args() block executes. Any variable defined in the enclosing |
| scope is available for reading, but any variable defined earlier in |
| the current scope is not (since the overrides haven't been applied yet). |
| |
| 2. At the end of executing the block, any variables set within that scope |
| are saved, with the values specified in the block used as the "default value" |
| for that argument. Once saved, these variables are available for override |
| via args.gn. |
| |
| 3. User-defined overrides are applied. Anything set in "gn args" now |
| overrides any default values. The resulting set of variables is promoted |
| to be readable from the following code in the file. |
| |
| This has some ramifications that may not be obvious: |
| |
| - You should not perform difficult work inside a declare_args block since |
| this only sets a default value that may be discarded. In particular, |
| don't use the result of exec_script() to set the default value. If you |
| want to have a script-defined default, set some default "undefined" value |
| like [], "", or -1, and after the declare_args block, call exec_script if |
| the value is unset by the user. |
| |
| - Because you cannot read the value of a variable defined in the same |
| block, if you need to make the default value of one arg depend |
| on the possibly-overridden value of another, write two separate |
| declare_args() blocks: |
| |
| declare_args() { |
| enable_foo = true |
| } |
| declare_args() { |
| # Bar defaults to same user-overridden state as foo. |
| enable_bar = enable_foo |
| } |
| |
| Example |
| |
| declare_args() { |
| enable_teleporter = true |
| enable_doom_melon = false |
| } |
| |
| If you want to override the (default disabled) Doom Melon: |
| gn --args="enable_doom_melon=true enable_teleporter=true" |
| This also sets the teleporter, but it's already defaulted to on so it will |
| have no effect. |
| )"; |
| |
| Value RunDeclareArgs(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| BlockNode* block, |
| Err* err) { |
| NonNestableBlock non_nestable(scope, function, "declare_args"); |
| if (!non_nestable.Enter(err)) |
| return Value(); |
| |
| Scope block_scope(scope); |
| block_scope.SetProperty(&kInDeclareArgsKey, &block_scope); |
| block->Execute(&block_scope, err); |
| if (err->has_error()) |
| return Value(); |
| |
| // Pass the values from our scope into the Args object for adding to the |
| // scope with the proper values (taking into account the defaults given in |
| // the block_scope, and arguments passed into the build). |
| Scope::KeyValueMap values; |
| block_scope.GetCurrentScopeValues(&values); |
| scope->settings()->build_settings()->build_args().DeclareArgs(values, scope, |
| err); |
| return Value(); |
| } |
| |
| // defined --------------------------------------------------------------------- |
| |
| const char kDefined[] = "defined"; |
| const char kDefined_HelpShort[] = |
| "defined: Returns whether an identifier is defined."; |
| const char kDefined_Help[] = |
| R"(defined: Returns whether an identifier is defined. |
| |
| Returns true if the given argument is defined. This is most useful in |
| templates to assert that the caller set things up properly. |
| |
| You can pass an identifier: |
| defined(foo) |
| which will return true or false depending on whether foo is defined in the |
| current scope. |
| |
| You can also check a named scope: |
| defined(foo.bar) |
| which will return true or false depending on whether bar is defined in the |
| named scope foo. It will throw an error if foo is not defined or is not a |
| scope. |
| |
| You can also check a named scope using a subscript string expression: |
| defined(foo[bar + "_name"]) |
| which will return true or false depending on whether the subscript |
| expression expands to the name of a member of the scope foo. It will |
| throw an error if foo is not defined or is not a scope, or if the |
| expression does not expand to a string, or if it is an empty string. |
| |
| Example |
| |
| template("mytemplate") { |
| # To help users call this template properly... |
| assert(defined(invoker.sources), "Sources must be defined") |
| |
| # If we want to accept an optional "values" argument, we don't |
| # want to dereference something that may not be defined. |
| if (defined(invoker.values)) { |
| values = invoker.values |
| } else { |
| values = "some default value" |
| } |
| } |
| )"; |
| |
| Value RunDefined(Scope* scope, |
| const FunctionCallNode* function, |
| const ListNode* args_list, |
| Err* err) { |
| const auto& args_vector = args_list->contents(); |
| if (args_vector.size() != 1) { |
| *err = Err(function, "Wrong number of arguments to defined().", |
| "Expecting exactly one."); |
| return Value(); |
| } |
| |
| 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) { |
| // The base of the accessor must be a scope if it's defined. |
| const Value* base = scope->GetValue(accessor->base().value()); |
| if (!base) { |
| *err = Err(accessor, "Undefined identifier"); |
| return Value(); |
| } |
| if (!base->VerifyTypeIs(Value::SCOPE, err)) |
| return Value(); |
| |
| std::string scope_member; |
| |
| if (accessor->member()) { |
| // Passed an accessor "defined(foo.bar)". |
| scope_member = accessor->member()->value().value(); |
| } else if (accessor->subscript()) { |
| // Passed an accessor "defined(foo["bar"])". |
| Value subscript_value = accessor->subscript()->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| if (!subscript_value.VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| scope_member = subscript_value.string_value(); |
| } |
| if (!scope_member.empty()) { |
| // Check the member inside the scope to see if its defined. |
| bool result = base->scope_value()->GetValue(scope_member) != nullptr; |
| return Value(function, result); |
| } |
| } |
| |
| // Argument is invalid. |
| *err = Err(function, "Bad thing passed to defined().", |
| "It should be of the form defined(foo), defined(foo.bar) or " |
| "defined(foo[<string-expression>])."); |
| return Value(); |
| } |
| |
| // getenv ---------------------------------------------------------------------- |
| |
| const char kGetEnv[] = "getenv"; |
| const char kGetEnv_HelpShort[] = "getenv: Get an environment variable."; |
| const char kGetEnv_Help[] = |
| R"(getenv: Get an environment variable. |
| |
| value = getenv(env_var_name) |
| |
| Returns the value of the given environment variable. If the value is not |
| found, it will try to look up the variable with the "opposite" case (based on |
| the case of the first letter of the variable), but is otherwise |
| case-sensitive. |
| |
| If the environment variable is not found, the empty string will be returned. |
| Note: it might be nice to extend this if we had the concept of "none" in the |
| language to indicate lookup failure. |
| |
| Example |
| |
| home_dir = getenv("HOME") |
| )"; |
| |
| Value RunGetEnv(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| if (!EnsureSingleStringArg(function, args, err)) |
| return Value(); |
| |
| std::unique_ptr<base::Environment> env(base::Environment::Create()); |
| |
| std::string result; |
| if (!env->GetVar(args[0].string_value().c_str(), &result)) |
| return Value(function, ""); // Not found, return empty string. |
| return Value(function, result); |
| } |
| |
| // import ---------------------------------------------------------------------- |
| |
| const char kImport[] = "import"; |
| const char kImport_HelpShort[] = |
| "import: Import a file into the current scope."; |
| const char kImport_Help[] = |
| R"(import: Import a file into the current scope. |
| |
| The import command loads the rules and variables resulting from executing the |
| given file into the current scope. |
| |
| By convention, imported files are named with a .gni extension. |
| |
| An import is different than a C++ "include". The imported file is executed in |
| a standalone environment from the caller of the import command. The results |
| of this execution are cached for other files that import the same .gni file. |
| |
| Note that you can not import a BUILD.gn file that's otherwise used in the |
| build. Files must either be imported or implicitly loaded as a result of deps |
| rules, but not both. |
| |
| The imported file's scope will be merged with the scope at the point import |
| was called. If there is a conflict (both the current scope and the imported |
| file define some variable or rule with the same name but different value), a |
| runtime error will be thrown. Therefore, it's good practice to minimize the |
| stuff that an imported file defines. |
| |
| Variables and templates beginning with an underscore '_' are considered |
| private and will not be imported. Imported files can use such variables for |
| internal computation without affecting other files. |
| |
| Examples |
| |
| import("//build/rules/idl_compilation_rule.gni") |
| |
| # Looks in the current directory. |
| import("my_vars.gni") |
| )"; |
| |
| Value RunImport(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| if (!EnsureSingleStringArg(function, args, err)) |
| return Value(); |
| |
| const SourceDir& input_dir = scope->GetSourceDir(); |
| SourceFile import_file = input_dir.ResolveRelativeFile( |
| args[0], err, scope->settings()->build_settings()->root_path_utf8()); |
| scope->AddBuildDependencyFile(import_file); |
| if (!err->has_error()) { |
| scope->settings()->import_manager().DoImport(import_file, function, scope, |
| err); |
| } |
| return Value(); |
| } |
| |
| // not_needed ----------------------------------------------------------------- |
| |
| const char kNotNeeded[] = "not_needed"; |
| const char kNotNeeded_HelpShort[] = |
| "not_needed: Mark variables from scope as not needed."; |
| const char kNotNeeded_Help[] = |
| R"(not_needed: Mark variables from scope as not needed. |
| |
| not_needed(variable_list_or_star, variable_to_ignore_list = []) |
| not_needed(from_scope, variable_list_or_star, |
| variable_to_ignore_list = []) |
| |
| Mark the variables in the current or given scope as not needed, which means |
| you will not get an error about unused variables for these. The |
| variable_to_ignore_list allows excluding variables from "all matches" if |
| variable_list_or_star is "*". |
| |
| Example |
| |
| not_needed("*", [ "config" ]) |
| not_needed([ "data_deps", "deps" ]) |
| not_needed(invoker, "*", [ "config" ]) |
| not_needed(invoker, [ "data_deps", "deps" ]) |
| )"; |
| |
| Value RunNotNeeded(Scope* scope, |
| const FunctionCallNode* function, |
| const ListNode* args_list, |
| Err* err) { |
| const auto& args_vector = args_list->contents(); |
| if (args_vector.size() < 1 || args_vector.size() > 3) { |
| *err = Err(function, "Wrong number of arguments.", |
| "Expecting one, two or three arguments."); |
| return Value(); |
| } |
| auto args_cur = args_vector.begin(); |
| |
| Value* value = nullptr; // Value to use, may point to result_value. |
| Value result_value; // Storage for the "evaluate" case. |
| Value scope_value; // Storage for an evaluated scope. |
| const IdentifierNode* identifier = (*args_cur)->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_cur)->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| value = &result_value; |
| } |
| args_cur++; |
| |
| // Extract the source scope if different from current one. |
| Scope* source = scope; |
| if (value->type() == Value::SCOPE) { |
| if (args_cur == args_vector.end()) { |
| *err = Err( |
| function, "Wrong number of arguments.", |
| "The first argument is a scope, expecting two or three arguments."); |
| return Value(); |
| } |
| // Copy the scope value if it will be overridden. |
| if (value == &result_value) { |
| scope_value = Value(nullptr, value->scope_value()->MakeClosure()); |
| source = scope_value.scope_value(); |
| } else { |
| source = value->scope_value(); |
| } |
| result_value = (*args_cur)->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| value = &result_value; |
| args_cur++; |
| } else if (args_vector.size() > 2) { |
| *err = Err( |
| function, "Wrong number of arguments.", |
| "The first argument is not a scope, expecting one or two arguments."); |
| return Value(); |
| } |
| |
| // Extract the exclusion list if defined. |
| Value exclusion_value; |
| std::set<std::string> exclusion_set; |
| if (args_cur != args_vector.end()) { |
| exclusion_value = (*args_cur)->Execute(source, 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()); |
| } |
| } |
| |
| if (value->type() == Value::STRING) { |
| if (value->string_value() == "*") { |
| source->MarkAllUsed(exclusion_set); |
| return Value(); |
| } |
| } else if (value->type() == Value::LIST) { |
| if (exclusion_value.type() != Value::NONE) { |
| *err = Err(exclusion_value, "Not supported with a variable list.", |
| "Exclusion list can only be used with the string \"*\"."); |
| return Value(); |
| } |
| for (const Value& cur : value->list_value()) { |
| if (!cur.VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| // We don't need the return value, we invoke scope::GetValue only to mark |
| // the value as used. Note that we cannot use Scope::MarkUsed because we |
| // want to also search in the parent scope. |
| (void)source->GetValue(cur.string_value(), true); |
| } |
| return Value(); |
| } |
| |
| // Not the right type of argument. |
| *err = Err(*value, "Not a valid list of variables.", |
| "Expecting either the string \"*\" or a list of strings."); |
| return Value(); |
| } |
| |
| // pool ------------------------------------------------------------------------ |
| |
| const char kPool[] = "pool"; |
| const char kPool_HelpShort[] = "pool: Defines a pool object."; |
| const char kPool_Help[] = |
| R"*(pool: Defines a pool object. |
| |
| Pool objects can be applied to a tool to limit the parallelism of the |
| build. This object has a single property "depth" corresponding to |
| the number of tasks that may run simultaneously. |
| |
| As the file containing the pool definition may be executed in the |
| context of more than one toolchain it is recommended to specify an |
| explicit toolchain when defining and referencing a pool. |
| |
| A pool named "console" defined in the root build file represents Ninja's |
| console pool. Targets using this pool will have access to the console's |
| stdin and stdout, and output will not be buffered. This special pool must |
| have a depth of 1. Pools not defined in the root must not be named "console". |
| The console pool can only be defined for the default toolchain. |
| Refer to the Ninja documentation on the console pool for more info. |
| |
| A pool is referenced by its label just like a target. |
| |
| Variables |
| |
| depth* |
| * = required |
| |
| Example |
| |
| if (current_toolchain == default_toolchain) { |
| pool("link_pool") { |
| depth = 1 |
| } |
| } |
| |
| toolchain("toolchain") { |
| tool("link") { |
| command = "..." |
| pool = ":link_pool($default_toolchain)" |
| } |
| } |
| )*"; |
| |
| const char kDepth[] = "depth"; |
| |
| Value RunPool(const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Scope* scope, |
| Err* err) { |
| NonNestableBlock non_nestable(scope, function, "pool"); |
| if (!non_nestable.Enter(err)) |
| return Value(); |
| |
| if (!EnsureSingleStringArg(function, args, err) || |
| !EnsureNotProcessingImport(function, scope, err)) |
| return Value(); |
| |
| Label label(MakeLabelForScope(scope, function, args[0].string_value())); |
| |
| if (g_scheduler->verbose_logging()) |
| g_scheduler->Log("Defining pool", label.GetUserVisibleName(true)); |
| |
| // Get the pool depth. It is an error to define a pool without a depth, |
| // so check first for the presence of the value. |
| const Value* depth = scope->GetValue(kDepth, true); |
| if (!depth) { |
| *err = Err(function, "Can't define a pool without depth."); |
| return Value(); |
| } |
| |
| if (!depth->VerifyTypeIs(Value::INTEGER, err)) |
| return Value(); |
| |
| if (depth->int_value() < 0) { |
| *err = Err(*depth, "depth must be positive or 0."); |
| return Value(); |
| } |
| |
| // Create the new pool. |
| std::unique_ptr<Pool> pool = std::make_unique<Pool>( |
| scope->settings(), label, scope->build_dependency_files()); |
| |
| if (label.name() == "console") { |
| const Settings* settings = scope->settings(); |
| if (!settings->is_default()) { |
| *err = Err( |
| function, |
| "\"console\" pool must be defined only in the default toolchain."); |
| return Value(); |
| } |
| if (label.dir() != settings->build_settings()->root_target_label().dir()) { |
| *err = Err(function, "\"console\" pool must be defined in the root //."); |
| return Value(); |
| } |
| if (depth->int_value() != 1) { |
| *err = Err(*depth, "\"console\" pool must have depth 1."); |
| return Value(); |
| } |
| } |
| pool->set_depth(depth->int_value()); |
| |
| // Save the generated item. |
| Scope::ItemVector* collector = scope->GetItemCollector(); |
| if (!collector) { |
| *err = Err(function, "Can't define a pool in this context."); |
| return Value(); |
| } |
| collector->push_back(std::move(pool)); |
| |
| return Value(); |
| } |
| |
| // print ----------------------------------------------------------------------- |
| |
| const char kPrint[] = "print"; |
| const char kPrint_HelpShort[] = "print: Prints to the console."; |
| const char kPrint_Help[] = |
| R"(print: Prints to the console. |
| |
| Prints all arguments to the console separated by spaces. A newline is |
| automatically appended to the end. |
| |
| This function is intended for debugging. Note that build files are run in |
| parallel so you may get interleaved prints. A buildfile may also be executed |
| more than once in parallel in the context of different toolchains so the |
| prints from one file may be duplicated or |
| interleaved with itself. |
| |
| Examples |
| |
| print("Hello world") |
| |
| print(sources, deps) |
| )"; |
| |
| Value RunPrint(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| std::string output; |
| for (size_t i = 0; i < args.size(); i++) { |
| if (i != 0) |
| output.push_back(' '); |
| output.append(args[i].ToString(false)); |
| } |
| output.push_back('\n'); |
| |
| const BuildSettings::PrintCallback& cb = |
| scope->settings()->build_settings()->print_callback(); |
| if (cb) { |
| cb(output); |
| } else { |
| printf("%s", output.c_str()); |
| fflush(stdout); |
| } |
| |
| return Value(); |
| } |
| |
| // print_stack_trace ----------------------------------------------------------- |
| |
| const char kPrintStackTrace[] = "print_stack_trace"; |
| const char kPrintStackTrace_HelpShort[] = |
| "print_stack_trace: Prints a stack trace."; |
| const char kPrintStackTrace_Help[] = |
| R"(print_stack_trace: Prints a stack trace. |
| |
| Prints the current file location, and all template invocations that led up to |
| this location, to the console. |
| |
| Examples |
| |
| template("foo"){ |
| print_stack_trace() |
| } |
| template("bar"){ |
| foo(target_name + ".foo") { |
| baz = invoker.baz |
| } |
| } |
| bar("lala") { |
| baz = 42 |
| } |
| |
| will print out the following: |
| |
| print_stack_trace() initiated at //build.gn:2 |
| bar("lala") //BUILD.gn:9 |
| foo("lala.foo") //BUILD.gn:5 |
| print_stack_trace() //BUILD.gn:2 |
| |
| )"; |
| |
| Value RunPrintStackTrace(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| std::string location_str = function->GetRange().begin().Describe(false); |
| std::string toolchain = |
| scope->settings()->toolchain_label().GetUserVisibleName(false); |
| std::string output = "print_stack_trace() initiated at: " + location_str + |
| " using: " + toolchain; |
| output.push_back('\n'); |
| |
| for (const auto& entry : scope->GetTemplateInvocationEntries()) { |
| output.append(" " + entry.Describe() + "\n"); |
| } |
| output.append(" print_stack_trace() " + location_str + "\n"); |
| |
| const BuildSettings::PrintCallback& cb = |
| scope->settings()->build_settings()->print_callback(); |
| if (cb) { |
| cb(output); |
| } else { |
| printf("%s", output.c_str()); |
| fflush(stdout); |
| } |
| |
| return Value(); |
| } |
| |
| // split_list ------------------------------------------------------------------ |
| |
| const char kSplitList[] = "split_list"; |
| const char kSplitList_HelpShort[] = |
| "split_list: Splits a list into N different sub-lists."; |
| const char kSplitList_Help[] = |
| R"(split_list: Splits a list into N different sub-lists. |
| |
| result = split_list(input, n) |
| |
| Given a list and a number N, splits the list into N sub-lists of |
| approximately equal size. The return value is a list of the sub-lists. The |
| result will always be a list of size N. If N is greater than the number of |
| elements in the input, it will be padded with empty lists. |
| |
| The expected use is to divide source files into smaller uniform chunks. |
| |
| Example |
| |
| The code: |
| mylist = [1, 2, 3, 4, 5, 6] |
| print(split_list(mylist, 3)) |
| |
| Will print: |
| [[1, 2], [3, 4], [5, 6] |
| )"; |
| Value RunSplitList(Scope* scope, |
| const FunctionCallNode* function, |
| const ListNode* args_list, |
| Err* err) { |
| const auto& args_vector = args_list->contents(); |
| if (args_vector.size() != 2) { |
| *err = Err(function, "Wrong number of arguments to split_list().", |
| "Expecting exactly two."); |
| return Value(); |
| } |
| |
| ParseNodeValueAdapter list_adapter; |
| if (!list_adapter.InitForType(scope, args_vector[0].get(), Value::LIST, err)) |
| return Value(); |
| const std::vector<Value>& input = list_adapter.get().list_value(); |
| |
| ParseNodeValueAdapter count_adapter; |
| if (!count_adapter.InitForType(scope, args_vector[1].get(), Value::INTEGER, |
| err)) |
| return Value(); |
| int64_t count = count_adapter.get().int_value(); |
| if (count <= 0) { |
| *err = Err(function, "Requested result size is not positive."); |
| return Value(); |
| } |
| |
| Value result(function, Value::LIST); |
| result.list_value().resize(count); |
| |
| // Every result list gets at least this many items in it. |
| int64_t min_items_per_list = static_cast<int64_t>(input.size()) / count; |
| |
| // This many result lists get an extra item which is the remainder from above. |
| int64_t extra_items = static_cast<int64_t>(input.size()) % count; |
| |
| // Allocate all lists that have a remainder assigned to them (max items). |
| int64_t max_items_per_list = min_items_per_list + 1; |
| auto last_item_end = input.begin(); |
| for (int64_t i = 0; i < extra_items; i++) { |
| result.list_value()[i] = Value(function, Value::LIST); |
| |
| auto begin_add = last_item_end; |
| last_item_end += max_items_per_list; |
| result.list_value()[i].list_value().assign(begin_add, last_item_end); |
| } |
| |
| // Allocate all smaller items that don't have a remainder. |
| for (int64_t i = extra_items; i < count; i++) { |
| result.list_value()[i] = Value(function, Value::LIST); |
| |
| auto begin_add = last_item_end; |
| last_item_end += min_items_per_list; |
| result.list_value()[i].list_value().assign(begin_add, last_item_end); |
| } |
| |
| return result; |
| } |
| |
| // string_join ----------------------------------------------------------------- |
| |
| const char kStringJoin[] = "string_join"; |
| const char kStringJoin_HelpShort[] = |
| "string_join: Concatenates a list of strings with a separator."; |
| const char kStringJoin_Help[] = |
| R"(string_join: Concatenates a list of strings with a separator. |
| |
| result = string_join(separator, strings) |
| |
| Concatenate a list of strings with intervening occurrences of separator. |
| |
| Examples |
| |
| string_join("", ["a", "b", "c"]) --> "abc" |
| string_join("|", ["a", "b", "c"]) --> "a|b|c" |
| string_join(", ", ["a", "b", "c"]) --> "a, b, c" |
| string_join("s", ["", ""]) --> "s" |
| )"; |
| |
| Value RunStringJoin(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| // Check usage: Number of arguments. |
| if (args.size() != 2) { |
| *err = Err(function, "Wrong number of arguments to string_join().", |
| "Expecting exactly two. usage: string_join(separator, strings)"); |
| return Value(); |
| } |
| |
| // Check usage: separator is a string. |
| if (!args[0].VerifyTypeIs(Value::STRING, err)) { |
| *err = Err(function, |
| "separator in string_join(separator, strings) is not " |
| "a string", |
| "Expecting separator argument to be a string."); |
| return Value(); |
| } |
| const std::string separator = args[0].string_value(); |
| |
| // Check usage: strings is a list. |
| if (!args[1].VerifyTypeIs(Value::LIST, err)) { |
| *err = Err(function, |
| "strings in string_join(separator, strings) " |
| "is not a list", |
| "Expecting strings argument to be a list."); |
| return Value(); |
| } |
| const std::vector<Value> strings = args[1].list_value(); |
| |
| // Arguments looks good; do the join. |
| std::stringstream stream; |
| for (size_t i = 0; i < strings.size(); ++i) { |
| if (!strings[i].VerifyTypeIs(Value::STRING, err)) { |
| return Value(); |
| } |
| if (i != 0) { |
| stream << separator; |
| } |
| stream << strings[i].string_value(); |
| } |
| return Value(function, stream.str()); |
| } |
| |
| // string_replace -------------------------------------------------------------- |
| |
| const char kStringReplace[] = "string_replace"; |
| const char kStringReplace_HelpShort[] = |
| "string_replace: Replaces substring in the given string."; |
| const char kStringReplace_Help[] = |
| R"(string_replace: Replaces substring in the given string. |
| |
| result = string_replace(str, old, new[, max]) |
| |
| Returns a copy of the string str in which the occurrences of old have been |
| replaced with new, optionally restricting the number of replacements. The |
| replacement is performed sequentially, so if new contains old, it won't be |
| replaced. |
| |
| Example |
| |
| The code: |
| mystr = "Hello, world!" |
| print(string_replace(mystr, "world", "GN")) |
| |
| Will print: |
| Hello, GN! |
| )"; |
| |
| Value RunStringReplace(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| if (args.size() < 3 || args.size() > 4) { |
| *err = Err(function, "Wrong number of arguments to string_replace()."); |
| return Value(); |
| } |
| |
| if (!args[0].VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| const std::string str = args[0].string_value(); |
| |
| if (!args[1].VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| const std::string& old = args[1].string_value(); |
| |
| if (!args[2].VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| const std::string& new_ = args[2].string_value(); |
| |
| int64_t max = INT64_MAX; |
| if (args.size() > 3) { |
| if (!args[3].VerifyTypeIs(Value::INTEGER, err)) |
| return Value(); |
| max = args[3].int_value(); |
| if (max <= 0) { |
| *err = Err(function, "Requested number of replacements is not positive."); |
| return Value(); |
| } |
| } |
| |
| int64_t n = 0; |
| std::string val(str); |
| size_t start_pos = 0; |
| while ((start_pos = val.find(old, start_pos)) != std::string::npos) { |
| val.replace(start_pos, old.length(), new_); |
| start_pos += new_.length(); |
| if (++n >= max) |
| break; |
| } |
| return Value(function, std::move(val)); |
| } |
| |
| // string_split ---------------------------------------------------------------- |
| |
| const char kStringSplit[] = "string_split"; |
| const char kStringSplit_HelpShort[] = |
| "string_split: Split string into a list of strings."; |
| const char kStringSplit_Help[] = |
| R"(string_split: Split string into a list of strings. |
| |
| result = string_split(str[, sep]) |
| |
| Split string into all substrings separated by separator and returns a list |
| of the substrings between those separators. |
| |
| If the separator argument is omitted, the split is by any whitespace, and |
| any leading/trailing whitespace is ignored; similar to Python's str.split(). |
| |
| Examples without a separator (split on whitespace): |
| |
| string_split("") --> [] |
| string_split("a") --> ["a"] |
| string_split(" aa bb") --> ["aa", "bb"] |
| |
| Examples with a separator (split on separators): |
| |
| string_split("", "|") --> [""] |
| string_split(" a b ", " ") --> ["", "", "a", "b", "", ""] |
| string_split("aa+-bb+-c", "+-") --> ["aa", "bb", "c"] |
| )"; |
| |
| Value RunStringSplit(Scope* scope, |
| const FunctionCallNode* function, |
| const std::vector<Value>& args, |
| Err* err) { |
| // Check usage: argument count. |
| if (args.size() != 1 && args.size() != 2) { |
| *err = Err(function, "Wrong number of arguments to string_split().", |
| "Usage: string_split(str[, sep])"); |
| return Value(); |
| } |
| |
| // Check usage: str is a string. |
| if (!args[0].VerifyTypeIs(Value::STRING, err)) { |
| return Value(); |
| } |
| const std::string str = args[0].string_value(); |
| |
| // Check usage: separator is a non-empty string. |
| std::string separator; |
| if (args.size() == 2) { |
| if (!args[1].VerifyTypeIs(Value::STRING, err)) { |
| return Value(); |
| } |
| separator = args[1].string_value(); |
| if (separator.empty()) { |
| *err = Err(function, |
| "Separator argument to string_split() " |
| "cannot be empty string", |
| "Usage: string_split(str[, sep])"); |
| return Value(); |
| } |
| } |
| |
| // Split the string into a std::vector. |
| std::vector<std::string> strings; |
| if (!separator.empty()) { |
| // Case: Explicit separator argument. |
| // Note: split_string("", "x") --> [""] like Python. |
| size_t pos = 0; |
| size_t next_pos = 0; |
| while ((next_pos = str.find(separator, pos)) != std::string::npos) { |
| strings.push_back(str.substr(pos, next_pos - pos)); |
| pos = next_pos + separator.length(); |
| } |
| strings.push_back(str.substr(pos, std::string::npos)); |
| } else { |
| // Case: Split on any whitespace and strip ends. |
| // Note: split_string("") --> [] like Python. |
| std::string::const_iterator pos = str.cbegin(); |
| while (pos != str.end()) { |
| // Advance past spaces. After this, pos is pointing to non-whitespace. |
| pos = find_if(pos, str.end(), [](char x) { return !std::isspace(x); }); |
| if (pos == str.end()) { |
| // Tail is all whitespace, so we're done. |
| break; |
| } |
| // Advance past non-whitespace to get next chunk. |
| std::string::const_iterator next_whitespace_position = |
| find_if(pos, str.end(), [](char x) { return std::isspace(x); }); |
| strings.push_back(std::string(pos, next_whitespace_position)); |
| pos = next_whitespace_position; |
| } |
| } |
| |
| // Convert vector of std::strings to list of GN strings. |
| Value result(function, Value::LIST); |
| result.list_value().resize(strings.size()); |
| for (size_t i = 0; i < strings.size(); ++i) { |
| result.list_value()[i] = Value(function, strings[i]); |
| } |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| FunctionInfo::FunctionInfo() |
| : self_evaluating_args_runner(nullptr), |
| generic_block_runner(nullptr), |
| executed_block_runner(nullptr), |
| no_block_runner(nullptr), |
| help_short(nullptr), |
| help(nullptr), |
| is_target(false) {} |
| |
| FunctionInfo::FunctionInfo(SelfEvaluatingArgsFunction seaf, |
| const char* in_help_short, |
| const char* in_help, |
| bool in_is_target) |
| : self_evaluating_args_runner(seaf), |
| generic_block_runner(nullptr), |
| executed_block_runner(nullptr), |
| no_block_runner(nullptr), |
| help_short(in_help_short), |
| help(in_help), |
| is_target(in_is_target) {} |
| |
| FunctionInfo::FunctionInfo(GenericBlockFunction gbf, |
| const char* in_help_short, |
| const char* in_help, |
| bool in_is_target) |
| : self_evaluating_args_runner(nullptr), |
| generic_block_runner(gbf), |
| executed_block_runner(nullptr), |
| no_block_runner(nullptr), |
| help_short(in_help_short), |
| help(in_help), |
| is_target(in_is_target) {} |
| |
| FunctionInfo::FunctionInfo(ExecutedBlockFunction ebf, |
| const char* in_help_short, |
| const char* in_help, |
| bool in_is_target) |
| : self_evaluating_args_runner(nullptr), |
| generic_block_runner(nullptr), |
| executed_block_runner(ebf), |
| no_block_runner(nullptr), |
| help_short(in_help_short), |
| help(in_help), |
| is_target(in_is_target) {} |
| |
| FunctionInfo::FunctionInfo(NoBlockFunction nbf, |
| const char* in_help_short, |
| const char* in_help, |
| bool in_is_target) |
| : self_evaluating_args_runner(nullptr), |
| generic_block_runner(nullptr), |
| executed_block_runner(nullptr), |
| no_block_runner(nbf), |
| help_short(in_help_short), |
| help(in_help), |
| is_target(in_is_target) {} |
| |
| // Setup the function map via a static initializer. We use this because it |
| // avoids race conditions without having to do some global setup function or |
| // locking-heavy singleton checks at runtime. In practice, we always need this |
| // before we can do anything interesting, so it's OK to wait for the |
| // initializer. |
| struct FunctionInfoInitializer { |
| FunctionInfoMap map; |
| |
| FunctionInfoInitializer() { |
| #define INSERT_FUNCTION(command, is_target) \ |
| map[k##command] = FunctionInfo(&Run##command, k##command##_HelpShort, \ |
| k##command##_Help, is_target); |
| |
| INSERT_FUNCTION(Action, true) |
| INSERT_FUNCTION(ActionForEach, true) |
| INSERT_FUNCTION(BundleData, true) |
| INSERT_FUNCTION(CreateBundle, true) |
| INSERT_FUNCTION(Copy, true) |
| INSERT_FUNCTION(Executable, true) |
| INSERT_FUNCTION(Group, true) |
| INSERT_FUNCTION(LoadableModule, true) |
| INSERT_FUNCTION(SharedLibrary, true) |
| INSERT_FUNCTION(SourceSet, true) |
| INSERT_FUNCTION(StaticLibrary, true) |
| INSERT_FUNCTION(Target, true) |
| INSERT_FUNCTION(GeneratedFile, true) |
| INSERT_FUNCTION(RustLibrary, true) |
| INSERT_FUNCTION(RustProcMacro, true) |
| |
| INSERT_FUNCTION(Assert, false) |
| INSERT_FUNCTION(Config, false) |
| INSERT_FUNCTION(DeclareArgs, false) |
| INSERT_FUNCTION(Defined, false) |
| INSERT_FUNCTION(ExecScript, false) |
| INSERT_FUNCTION(FilterExclude, false) |
| INSERT_FUNCTION(FilterInclude, false) |
| INSERT_FUNCTION(FilterLabelsInclude, false) |
| INSERT_FUNCTION(FilterLabelsExclude, false) |
| INSERT_FUNCTION(ForEach, false) |
| INSERT_FUNCTION(ForwardVariablesFrom, false) |
| INSERT_FUNCTION(GetEnv, false) |
| INSERT_FUNCTION(GetLabelInfo, false) |
| INSERT_FUNCTION(GetPathInfo, false) |
| INSERT_FUNCTION(GetTargetOutputs, false) |
| INSERT_FUNCTION(Import, false) |
| INSERT_FUNCTION(LabelMatches, false) |
| INSERT_FUNCTION(NotNeeded, false) |
| INSERT_FUNCTION(Pool, false) |
| INSERT_FUNCTION(Print, false) |
| INSERT_FUNCTION(PrintStackTrace, false) |
| INSERT_FUNCTION(ProcessFileTemplate, false) |
| INSERT_FUNCTION(ReadFile, false) |
| INSERT_FUNCTION(RebasePath, false) |
| INSERT_FUNCTION(SetDefaults, false) |
| INSERT_FUNCTION(SetDefaultToolchain, false) |
| INSERT_FUNCTION(SplitList, false) |
| INSERT_FUNCTION(StringJoin, false) |
| INSERT_FUNCTION(StringReplace, false) |
| INSERT_FUNCTION(StringSplit, false) |
| INSERT_FUNCTION(Template, false) |
| INSERT_FUNCTION(Tool, false) |
| INSERT_FUNCTION(Toolchain, false) |
| INSERT_FUNCTION(WriteFile, false) |
| |
| #undef INSERT_FUNCTION |
| } |
| }; |
| const FunctionInfoInitializer function_info; |
| |
| const FunctionInfoMap& GetFunctions() { |
| return function_info.map; |
| } |
| |
| Value RunFunction(Scope* scope, |
| const FunctionCallNode* function, |
| const ListNode* args_list, |
| BlockNode* block, |
| Err* err) { |
| const Token& name = function->function(); |
| |
| std::string template_name(function->function().value()); |
| const Template* templ = scope->GetTemplate(template_name); |
| if (templ) { |
| Value args = args_list->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| return templ->Invoke(scope, function, template_name, args.list_value(), |
| block, err); |
| } |
| |
| // No template matching this, check for a built-in function. |
| const FunctionInfoMap& function_map = GetFunctions(); |
| FunctionInfoMap::const_iterator found_function = |
| function_map.find(name.value()); |
| if (found_function == function_map.end()) { |
| *err = Err(name, "Unknown function."); |
| return Value(); |
| } |
| |
| if (found_function->second.self_evaluating_args_runner) { |
| // Self evaluating args functions are special weird built-ins like foreach. |
| // Rather than force them all to check that they have a block or no block |
| // and risk bugs for new additions, check a whitelist here. |
| if (found_function->second.self_evaluating_args_runner != &RunForEach) { |
| if (!VerifyNoBlockForFunctionCall(function, block, err)) |
| return Value(); |
| } |
| return found_function->second.self_evaluating_args_runner(scope, function, |
| args_list, err); |
| } |
| |
| // All other function types take a pre-executed set of args. |
| Value args = args_list->Execute(scope, err); |
| if (err->has_error()) |
| return Value(); |
| |
| if (found_function->second.generic_block_runner) { |
| if (!block) { |
| FillNeedsBlockError(function, err); |
| return Value(); |
| } |
| return found_function->second.generic_block_runner( |
| scope, function, args.list_value(), block, err); |
| } |
| |
| if (found_function->second.executed_block_runner) { |
| if (!block) { |
| FillNeedsBlockError(function, err); |
| return Value(); |
| } |
| |
| Scope block_scope(scope); |
| block->Execute(&block_scope, err); |
| if (err->has_error()) |
| return Value(); |
| |
| Value result = found_function->second.executed_block_runner( |
| function, args.list_value(), &block_scope, err); |
| if (err->has_error()) |
| return Value(); |
| |
| if (!block_scope.CheckForUnusedVars(err)) |
| return Value(); |
| return result; |
| } |
| |
| // Otherwise it's a no-block function. |
| if (!VerifyNoBlockForFunctionCall(function, block, err)) |
| return Value(); |
| return found_function->second.no_block_runner(scope, function, |
| args.list_value(), err); |
| } |
| |
| } // namespace functions |