blob: 6294f79b712a6b985ad3e4ca98cf25ac41f92b04 [file] [log] [blame]
// 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.
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) {
// Passed an accessor "defined(foo.bar)".
if (accessor->member()) {
// The base of the accessor must be a scope if it's defined.
const Value* base = scope->GetValue(accessor->base().value());
if (!base) {
*err = Err(accessor, "Undefined identifier");
return Value();
}
if (!base->VerifyTypeIs(Value::SCOPE, err))
return Value();
// Check the member inside the scope to see if its defined.
if (base->scope_value()->GetValue(accessor->member()->value().value()))
return Value(function, true);
return Value(function, false);
}
}
// Argument is invalid.
*err = Err(function, "Bad thing passed to defined().",
"It should be of the form defined(foo) or defined(foo.bar).");
return Value();
}
// getenv ----------------------------------------------------------------------
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();
}
// 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(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(NotNeeded, false)
INSERT_FUNCTION(Pool, false)
INSERT_FUNCTION(Print, 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