blob: b46b8e89ae3cfae17bf2d74684d9ed466002f9cd [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.starlarkbuildapi;
import com.google.devtools.build.docgen.annot.DocCategory;
import com.google.devtools.build.lib.collect.nestedset.Depset;
import net.starlark.java.annot.Param;
import net.starlark.java.annot.ParamType;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.NoneType;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.StarlarkCallable;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.eval.StarlarkValue;
/** Command line args module. */
@StarlarkBuiltin(
name = "Args",
category = DocCategory.BUILTIN,
doc =
"An object that encapsulates, in a memory-efficient way, the data needed to build part or "
+ "all of a command line."
+ "" //
+ "<p>It often happens that an action requires a large command line containing values"
+ " accumulated from transitive dependencies. For example, a linker command line might"
+ " list every object file needed by all of the libraries being linked. It is best"
+ " practice to store such transitive data in <a"
+ " href='../builtins/depset.html'><code>depset</code></a>s, so that they can be shared"
+ " by multiple targets. However, if the rule author had to convert these depsets into"
+ " lists of strings in order to construct an action command line, it would defeat this"
+ " memory-sharing optimization." //
+ "<p>For this reason, the action-constructing functions accept <code>Args</code> "
+ "objects in addition to strings. Each <code>Args</code> object represents a "
+ "concatenation of strings and depsets, with optional transformations for "
+ "manipulating the data. <code>Args</code> objects do not process the depsets they "
+ "encapsulate until the execution phase, when it comes time to calculate the command "
+ "line. This helps defer any expensive copying until after the analysis phase is "
+ "complete. See the <a href='https://bazel.build/rules/performance'>Optimizing "
+ "Performance</a> page for more information."
+ "" //
+ "<p><code>Args</code> are constructed by calling <a"
+ " href='../builtins/actions.html#args'><code>ctx.actions.args()</code></a>. They can"
+ " be passed as the <code>arguments</code> parameter of <a"
+ " href='../builtins/actions.html#run'><code>ctx.actions.run()</code></a> or <a"
+ " href='../builtins/actions.html#run_shell'><code>ctx.actions.run_shell()</code></a>."
+ " Each mutation of an <code>Args</code> object appends values to the eventual command"
+ " line." //
+ "<p>The <code>map_each</code> feature allows you to customize how items are "
+ "transformed into strings. If you do not provide a <code>map_each</code> function, "
+ "the standard conversion is as follows: "
+ "<ul>" //
+ "<li>Values that are already strings are left as-is." //
+ "<li><a href='../builtins/File.html'><code>File</code></a> objects are turned into"
+ " their <code>File.path</code> values." //
+ "<li><a href='../builtins/Label.html'><code>Label</code></a> objects are turned into"
+ " a string representation that resolves back to the same object when resolved in the"
+ " context of the main repository. If possible, the string representation uses the"
+ " apparent name of a repository in favor of the repository's canonical name, which"
+ " makes this representation suited for use in BUILD files. While the exact form of"
+ " the representation is not guaranteed, typical examples are"
+ " <code>//foo:bar</code>, <code>@repo//foo:bar</code> and"
+ " <code>@@canonical_name~//foo:bar.bzl</code>."
+ "<li>All other types are turned into strings in an <i>unspecified</i> manner. For "
+ " this reason, you should avoid passing values that are not of string or "
+ " <code>File</code> type to <code>add()</code>, and if you pass them to "
+ " <code>add_all()</code> or <code>add_joined()</code> then you should provide a "
+ " <code>map_each</code> function."
+ "</ul>" //
+ ""
+ "<p>When using string formatting (<code>format</code>, <code>format_each</code>, and "
+ "<code>format_joined</code> params of the <code>add*()</code> methods), the format "
+ "template is interpreted in the same way as <code>%</code>-substitution on strings, "
+ "except that the template must have exactly one substitution placeholder and it must "
+ "be <code>%s</code>. Literal percents may be escaped as <code>%%</code>. Formatting "
+ "is applied after the value is converted to a string as per the above."
+ "" //
+ "<p>Each of the <code>add*()</code> methods have an alternate form that accepts an "
+ "extra positional parameter, an \"arg name\" string to insert before the rest of the "
+ "arguments. For <code>add_all</code> and <code>add_joined</code> the extra string "
+ "will not be added if the sequence turns out to be empty. "
+ "For instance, the same usage can add either <code>--foo val1 val2 val3 --bar"
+ "</code> or just <code>--bar</code> to the command line, depending on whether the "
+ "given sequence contains <code>val1..val3</code> or is empty."
+ "" //
+ "<p>If the size of the command line can grow longer than the maximum size allowed by "
+ "the system, the arguments can be spilled over into parameter files. See "
+ "<a href='#use_param_file'><code>use_param_file()</code></a> and "
+ "<a href='#set_param_file_format'><code>set_param_file_format()</code></a>."
+ "" //
+ "<p>Example: Suppose we wanted to generate the command line: "
+ "<pre>\n"
+ "--foo foo1.txt foo2.txt ... fooN.txt --bar bar1.txt,bar2.txt,...,barM.txt --baz\n"
+ "</pre>"
+ "We could use the following <code>Args</code> object: "
+ "<pre class=language-python>\n"
+ "# foo_deps and bar_deps are depsets containing\n"
+ "# File objects for the foo and bar .txt files.\n"
+ "args = ctx.actions.args()\n"
+ "args.add_all(\"--foo\", foo_deps)\n"
+ "args.add_joined(\"--bar\", bar_deps, join_with=\",\")\n"
+ "args.add(\"--baz\")\n"
+ "ctx.actions.run(\n"
+ " ...\n"
+ " arguments = [args],\n"
+ " ...\n"
+ ")\n"
+ "</pre>")
public interface CommandLineArgsApi extends StarlarkValue {
@StarlarkMethod(
name = "add",
doc = "Appends an argument to this command line.",
parameters = {
@Param(
name = "arg_name_or_value",
doc =
"If two positional parameters are passed this is interpreted as the arg name. "
+ "The arg name is added before the value without any processing. "
+ "If only one positional parameter is passed, it is interpreted as "
+ "<code>value</code> (see below)."),
@Param(
name = "value",
defaultValue = "unbound",
doc =
"The object to append. It will be converted to a string using the standard "
+ "conversion mentioned above. Since there is no <code>map_each</code> "
+ "parameter for this function, <code>value</code> should be either a "
+ "string or a <code>File</code>. A list, tuple, depset, or directory "
+ "<code>File</code> must be passed to <a href='#add_all'><code>add_all()"
+ "</code> or <a href='#add_joined'><code>add_joined()</code></a> instead of "
+ "this method."),
@Param(
name = "format",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"A format string pattern, to be applied to the stringified version of <code>value"
+ "</code>.")
},
useStarlarkThread = true)
CommandLineArgsApi addArgument(
Object argNameOrValue, Object value, Object format, StarlarkThread thread)
throws EvalException;
@StarlarkMethod(
name = "add_all",
doc =
"Appends multiple arguments to this command line. The items are processed lazily during "
+ "the execution phase."
+ ""
+ "<p>Most of the processing occurs over a list of arguments to be appended, as per "
+ "the following steps:"
+ "<ol>"
+ "<li>Each directory <code>File</code> item is replaced by all <code>File</code>s "
+ "recursively contained in that directory."
+ "</li>"
+ "<li>If <code>map_each</code> is given, it is applied to each item, and the "
+ " resulting lists of strings are concatenated to form the initial argument "
+ " list. Otherwise, the initial argument list is the result of applying the "
+ " standard conversion to each item."
+ "<li>Each argument in the list is formatted with <code>format_each</code>, if "
+ " present."
+ "<li>If <code>uniquify</code> is true, duplicate arguments are removed. The first "
+ " occurrence is the one that remains."
+ "<li>If a <code>before_each</code> string is given, it is inserted as a new "
+ " argument before each existing argument in the list. This effectively doubles "
+ " the number of arguments to be appended by this point."
+ "<li>Except in the case that the list is empty and <code>omit_if_empty</code> is "
+ " true (the default), the arg name and <code>terminate_with</code> are "
+ " inserted as the first and last arguments, respectively, if they are given."
+ "</ol>"
+ "Note that empty strings are valid arguments that are subject to all these "
+ "processing steps.",
parameters = {
@Param(
name = "arg_name_or_values",
doc =
"If two positional parameters are passed this is interpreted as the arg name. "
+ "The arg name is added before the <code>values</code> as a separate argument "
+ "without any processing. This arg name will not be added if "
+ "<code>omit_if_empty</code> is true (the default) and no other items are "
+ "appended (as happens if <code>values</code> is empty or all of its items "
+ "are filtered). "
+ "If only one positional parameter is passed, it is interpreted as "
+ "<code>values</code> (see below)."),
@Param(
name = "values",
allowedTypes = {
@ParamType(type = Sequence.class),
@ParamType(type = Depset.class),
},
defaultValue = "unbound",
doc = "The list, tuple, or depset whose items will be appended."),
@Param(
name = "map_each",
allowedTypes = {
@ParamType(type = StarlarkCallable.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"A function that converts each item to zero or more strings, which may be further"
+ " processed before appending. If this param is not provided, the standard"
+ " conversion is used.<p>The function is passed either one or two positional"
+ " arguments: the item to convert, followed by an optional <a"
+ " href='../builtins/DirectoryExpander.html'><code>DirectoryExpander</code>"
+ "</a>. The second argument will be passed only if the supplied function is"
+ " user-defined (not built-in) and declares more than one parameter.<p>The"
+ " return value's type depends on how many arguments are to be produced for"
+ " the item:<ul><li>In the common case when each item turns into one string,"
+ " the function should return that string.<li>If the item is to be"
+ " filtered out entirely, the function should return "
+ " <code>None</code>.<li>If the item turns into multiple strings, the function"
+ " returns a list of those strings.</ul>Returning a single string or"
+ " <code>None</code> has the same effect as returning a list of length 1 or"
+ " length 0 respectively. However, it is more efficient and readable to avoid"
+ " creating a list where it is not needed.<p>Ordinarily, items that are"
+ " directories are automatically expanded to their contents when"
+ " <code>expand_directories=True</code> is set. However, this will not expand"
+ " directories contained inside other values -- for instance, when the items"
+ " are structs that have directories as fields. In this situation, the"
+ " <code>DirectoryExpander</code> argument can be applied to manually obtain"
+ " the files of a given directory.<p>To avoid unintended retention of large"
+ " analysis-phase data structures into the execution phase, the"
+ " <code>map_each</code> function must be declared by a top-level"
+ " <code>def</code> statement; it may not be a nested function closure by"
+ " default.<p><i>Warning:</i> <a"
+ " href='../globals/all.html#print'><code>print()</code></a> statements that"
+ " are executed during the call to <code>map_each</code> will not produce any"
+ " visible output."),
@Param(
name = "format_each",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"An optional format string pattern, applied to each string returned by the "
+ "<code>map_each</code> function. "
+ "The format string must have exactly one '%s' placeholder."),
@Param(
name = "before_each",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"An optional argument to append before each argument derived from "
+ "<code>values</code> is appended."),
@Param(
name = "omit_if_empty",
named = true,
positional = false,
defaultValue = "True",
doc =
"If true, if there are no arguments derived from <code>values</code> to be "
+ "appended, then all further processing is suppressed and the command line "
+ "will be unchanged. If false, the arg name and <code>terminate_with</code>, "
+ "if provided, will still be appended regardless of whether or not there are "
+ "other arguments."),
@Param(
name = "uniquify",
named = true,
positional = false,
defaultValue = "False",
doc =
"If true, duplicate arguments that are derived from <code>values</code> will be "
+ "omitted. Only the first occurrence of each argument will remain. Usually "
+ "this feature is not needed because depsets already omit duplicates, "
+ "but it can be useful if <code>map_each</code> emits the same string for "
+ "multiple items."),
@Param(
name = "expand_directories",
named = true,
positional = false,
defaultValue = "True",
doc =
"If true, any directories in <code>values</code> will be expanded to a flat list "
+ "of files. This happens before <code>map_each</code> is applied."),
@Param(
name = "terminate_with",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"An optional argument to append after all other arguments. This argument will not "
+ "be added if <code>omit_if_empty</code> is true (the default) and no other "
+ "items are appended (as happens if <code>values</code> is empty or all of "
+ "its items are filtered)."),
@Param(
name = "allow_closure",
named = true,
positional = false,
defaultValue = "False",
doc =
"If true, allows the use of closures in function parameters like "
+ "<code>map_each</code>. Usually this isn't necessary and it risks retaining "
+ "large analysis-phase data structures into the execution phase."),
},
useStarlarkThread = true)
CommandLineArgsApi addAll(
Object argNameOrValue,
Object values,
Object mapEach,
Object formatEach,
Object beforeEach,
Boolean omitIfEmpty,
Boolean uniquify,
Boolean expandDirectories,
Object terminateWith,
Boolean allowClosure,
StarlarkThread thread)
throws EvalException;
@StarlarkMethod(
name = "add_joined",
doc =
"Appends an argument to this command line by concatenating together multiple values "
+ "using a separator. The items are processed lazily during the execution phase."
+ ""
+ "<p>Processing is similar to <a href='#add_all'><code>add_all()</code></a>, but "
+ "the list of arguments derived from <code>values</code> is combined into a single "
+ "argument as if by <code>join_with.join(...)</code>, and then formatted using the "
+ "given <code>format_joined</code> string template. Unlike <code>add_all()</code>, "
+ "there is no <code>before_each</code> or <code>terminate_with</code> parameter "
+ "since these are not generally useful when the items are combined into a single "
+ "argument."
+ ""
+ "<p>If after filtering there are no strings to join into an argument, and if "
+ "<code>omit_if_empty</code> is true (the default), no processing is done. "
+ "Otherwise if there are no strings to join but <code>omit_if_empty</code> is "
+ "false, the joined string will be an empty string.",
parameters = {
@Param(
name = "arg_name_or_values",
doc =
"If two positional parameters are passed this is interpreted as the arg name. "
+ "The arg name is added before <code>values</code> without any processing. "
+ "This arg will not be added if <code>omit_if_empty</code> is true "
+ "(the default) and there are no strings derived from <code>values</code> "
+ "to join together (which can happen if <code>values</code> is empty "
+ "or all of its items are filtered). "
+ "If only one positional parameter is passed, it is interpreted as "
+ "<code>values</code> (see below)."),
@Param(
name = "values",
allowedTypes = {
@ParamType(type = Sequence.class),
@ParamType(type = Depset.class),
},
defaultValue = "unbound",
doc = "The list, tuple, or depset whose items will be joined."),
@Param(
name = "join_with",
named = true,
positional = false,
doc =
"A delimiter string used to join together the strings obtained from applying "
+ "<code>map_each</code> and <code>format_each</code>, in the same manner as "
+ "<a href='../core/string.html#join'><code>string.join()</code></a>."),
@Param(
name = "map_each",
allowedTypes = {
@ParamType(type = StarlarkCallable.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc = "Same as for <a href='#add_all.map_each'><code>add_all</code></a>."),
@Param(
name = "format_each",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc = "Same as for <a href='#add_all.format_each'><code>add_all</code></a>."),
@Param(
name = "format_joined",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
named = true,
positional = false,
defaultValue = "None",
doc =
"An optional format string pattern applied to the joined string. "
+ "The format string must have exactly one '%s' placeholder."),
@Param(
name = "omit_if_empty",
named = true,
positional = false,
defaultValue = "True",
doc =
"If true, if there are no strings to join together (either because <code>values"
+ "</code> is empty or all its items are filtered), then all further "
+ "processing is suppressed and the command line will be unchanged. "
+ "If false, then even if there are no strings to join together, two "
+ "arguments will be appended: the arg name followed by an empty "
+ "string (which is the logical join of zero strings)."),
@Param(
name = "uniquify",
named = true,
positional = false,
defaultValue = "False",
doc = "Same as for <a href='#add_all.uniquify'><code>add_all</code></a>."),
@Param(
name = "expand_directories",
named = true,
positional = false,
defaultValue = "True",
doc = "Same as for <a href='#add_all.expand_directories'><code>add_all</code></a>."),
@Param(
name = "allow_closure",
named = true,
positional = false,
defaultValue = "False",
doc = "Same as for <a href='#add_all.allow_closure'><code>add_all</code></a>."),
},
useStarlarkThread = true)
CommandLineArgsApi addJoined(
Object argNameOrValue,
Object values,
String joinWith,
Object mapEach,
Object formatEach,
Object formatJoined,
Boolean omitIfEmpty,
Boolean uniquify,
Boolean expandDirectories,
Boolean allowClosure,
StarlarkThread thread)
throws EvalException;
@StarlarkMethod(
name = "use_param_file",
doc =
"Spills the args to a params file, replacing them with a pointer to the param file. "
+ "Use when your args may be too large for the system's command length limits."
+ "<p>Bazel may choose to elide writing the params file to the output tree during "
+ "execution for efficiency. "
+ "If you are debugging actions and want to inspect the param file, "
+ "pass <code>--materialize_param_files</code> to your build.",
parameters = {
@Param(
name = "param_file_arg",
named = true,
doc =
"A format string with a single \"%s\". "
+ "If the args are spilled to a params file then they are replaced "
+ "with an argument consisting of this string formatted with "
+ "the path of the params file."
+ "<p>For example, if the args are spilled to a params file \"params.txt\", "
+ "then specifying \"--file=%s\" would cause the action command line to "
+ "contain \"--file=params.txt\"."),
@Param(
name = "use_always",
named = true,
positional = false,
defaultValue = "False",
doc =
"Whether to always spill the args to a params file. If false, "
+ "bazel will decide whether the arguments need to be spilled "
+ "based on your system and arg length.")
})
CommandLineArgsApi useParamsFile(String paramFileArg, Boolean useAlways) throws EvalException;
@StarlarkMethod(
name = "set_param_file_format",
doc = "Sets the format of the param file, if one is used",
parameters = {
@Param(
name = "format",
named = true,
doc =
"Must be one of:<ul>"
+ "<li>\"multiline\": Each item (argument name or value) is"
+ " written verbatim to the param file with a newline character following"
+ " it.</li>"
+ "<li>\"shell\": Same as \"multiline\", but the items are shell-quoted</li>"
+ "<li>\"flag_per_line\": Same as \"multiline\", but (1) only flags (beginning"
+ " with '--') are written to the param file, and (2) the values of the flags,"
+ " if any, are written on the same line with a '=' separator. This is the"
+ " format expected by the Abseil flags library.</li>"
+ "</ul><p>The format defaults to \"shell\" if not called.")
})
CommandLineArgsApi setParamFileFormat(String format) throws EvalException;
}