// Copyright 2017 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.analysis.skylark;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineItemSimpleFormatter;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.actions.RunfilesSupplier;
import com.google.devtools.build.lib.actions.extra.SpawnInfo;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.PseudoAction;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.ShToolchain;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
import com.google.devtools.build.lib.analysis.skylark.SkylarkCustomCommandLine.ScalarArg;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.ParamType;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.FunctionSignature.Shape;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.Runtime.NoneType;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkMutable;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
/** Provides a Skylark interface for all action creation needs. */
@SkylarkModule(
name = "actions",
category = SkylarkModuleCategory.BUILTIN,
doc =
"Module providing functions to create actions. "
+ "Access this module using ctx.actions
."
)
public class SkylarkActionFactory implements SkylarkValue {
private final SkylarkRuleContext context;
private final SkylarkSemantics skylarkSemantics;
private RuleContext ruleContext;
/** Counter for actions.run_shell helper scripts. Every script must have a unique name. */
private int runShellOutputCounter = 0;
public SkylarkActionFactory(
SkylarkRuleContext context,
SkylarkSemantics skylarkSemantics,
RuleContext ruleContext) {
this.context = context;
this.skylarkSemantics = skylarkSemantics;
this.ruleContext = ruleContext;
}
ArtifactRoot newFileRoot() throws EvalException {
return context.isForAspect()
? ruleContext.getConfiguration().getBinDirectory(ruleContext.getRule().getRepository())
: ruleContext.getBinOrGenfilesDirectory();
}
@SkylarkCallable(
name = "declare_file",
doc =
"Declares that the rule or aspect creates a file with the given filename. "
+ "If sibling
is not specified, the file name is relative to the package"
+ "directory, otherwise the file is in the same directory as sibling
."
+ "Files cannot be created outside of the current package."
+ "
Remember that in addition to declaring a file, you must separately create an "
+ "action that emits the file. Creating that action will require passing the returned "
+ "File
object to the action's construction function."
+ "
Note that predeclared output files do not "
+ "need to be (and cannot be) declared using this function. You can obtain their "
+ " {@code builder} should have either executable or a command set.
*/
private void registerSpawnAction(
SkylarkList outputs,
Object inputs,
Object toolsUnchecked,
Object mnemonicUnchecked,
Object progressMessage,
Boolean useDefaultShellEnv,
Object envUnchecked,
Object executionRequirementsUnchecked,
Object inputManifestsUnchecked,
Location location,
SpawnAction.Builder builder)
throws EvalException {
Iterable 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 For this reason, the action-constructing functions accept The When using string formatting ( Each of the 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 "
+ " Example: Suppose we wanted to generate the command line: "
+ " Deprecation note: The Deprecated behavior: Deprecated behavior: If Most of the processing occurs over a list of arguments to be appended, as per "
+ "the following steps:"
+ " The function takes in the item as a positional parameter and must have no "
+ "other parameters. The return value's type depends on how many arguments "
+ "are to be produced for the item:"
+ " Warning: Processing is similar to If after filtering there are no strings to join into an argument, and if "
+ "File
objects from ctx.outputs
"
+ " instead. "
+ "See example of use",
parameters = {
@Param(
name = "filename",
type = String.class,
doc =
"If no 'sibling' provided, path of the new file, relative "
+ "to the current package. Otherwise a base name for a file "
+ "('sibling' determines a directory)."
),
@Param(
name = "sibling",
doc =
"A file that lives in the same directory as the newly created file. "
+ "The file must be in the current package.",
type = Artifact.class,
noneable = true,
positional = false,
named = true,
defaultValue = "None"
)
}
)
public Artifact declareFile(String filename, Object sibling) throws EvalException {
context.checkMutable("actions.declare_file");
if (Runtime.NONE.equals(sibling)) {
return ruleContext.getPackageRelativeArtifact(filename, newFileRoot());
} else {
PathFragment original = ((Artifact) sibling).getRootRelativePath();
PathFragment fragment = original.replaceName(filename);
return ruleContext.getDerivedArtifact(fragment, newFileRoot());
}
}
@SkylarkCallable(
name = "declare_directory",
doc =
"Declares that rule or aspect create a directory with the given name, in the "
+ "current package. You must create an action that generates the directory.",
parameters = {
@Param(
name = "filename",
type = String.class,
doc =
"If no 'sibling' provided, path of the new directory, relative "
+ "to the current package. Otherwise a base name for a file "
+ "('sibling' defines a directory)."
),
@Param(
name = "sibling",
doc = "A file that lives in the same directory as the newly declared directory.",
type = Artifact.class,
noneable = true,
positional = false,
named = true,
defaultValue = "None"
)
}
)
public Artifact declareDirectory(String filename, Object sibling) throws EvalException {
context.checkMutable("actions.declare_directory");
if (Runtime.NONE.equals(sibling)) {
return ruleContext.getPackageRelativeTreeArtifact(
PathFragment.create(filename), newFileRoot());
} else {
PathFragment original = ((Artifact) sibling).getRootRelativePath();
PathFragment fragment = original.replaceName(filename);
return ruleContext.getTreeArtifact(fragment, newFileRoot());
}
}
@SkylarkCallable(
name = "do_nothing",
doc =
"Creates an empty action that neither executes a command nor produces any "
+ "output, but that is useful for inserting 'extra actions'.",
parameters = {
@Param(
name = "mnemonic",
type = String.class,
named = true,
positional = false,
doc = "A one-word description of the action, for example, CppCompile or GoLink."
),
@Param(
name = "inputs",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
generic1 = Artifact.class,
named = true,
positional = false,
defaultValue = "[]",
doc = "List of the input files of the action."
),
}
)
public void doNothing(String mnemonic, Object inputs) throws EvalException {
context.checkMutable("actions.do_nothing");
NestedSetexpand_template
.",
parameters = {
@Param(name = "output", type = Artifact.class, doc = "The output file.", named = true),
@Param(
name = "content",
type = Object.class,
allowedTypes = {@ParamType(type = String.class), @ParamType(type = Args.class)},
doc =
"the contents of the file. "
+ "May be a either a string or an "
+ "actions.args()
object.",
named = true
),
@Param(
name = "is_executable",
type = Boolean.class,
defaultValue = "False",
doc = "Whether the output file should be executable.",
named = true
)
}
)
public void write(Artifact output, Object content, Boolean isExecutable) throws EvalException {
context.checkMutable("actions.write");
final Action action;
if (content instanceof String) {
action = FileWriteAction.create(ruleContext, output, (String) content, isExecutable);
} else if (content instanceof Args) {
Args args = (Args) content;
action =
new ParameterFileWriteAction(
ruleContext.getActionOwner(),
output,
args.build(),
args.parameterFileType,
StandardCharsets.UTF_8);
} else {
throw new AssertionError("Unexpected type: " + content.getClass().getSimpleName());
}
ruleContext.registerAction(action);
}
@SkylarkCallable(
name = "run",
doc =
"Creates an action that runs an executable. "
+ "See example of use",
parameters = {
@Param(
name = "outputs",
type = SkylarkList.class,
generic1 = Artifact.class,
named = true,
positional = false,
doc = "List of the output files of the action."
),
@Param(
name = "inputs",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
generic1 = Artifact.class,
defaultValue = "[]",
named = true,
positional = false,
doc = "List or depset of the input files of the action."
),
@Param(
name = "executable",
type = Object.class,
allowedTypes = {
@ParamType(type = Artifact.class),
@ParamType(type = String.class),
},
named = true,
positional = false,
doc = "The executable file to be called by the action."
),
@Param(
name = "tools",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
generic1 = Artifact.class,
defaultValue = "unbound",
named = true,
positional = false,
doc =
"List or depset of any tools needed by the action. Tools are inputs with additional "
+ "runfiles that are automatically made available to the action."
),
@Param(
name = "arguments",
type = Object.class,
allowedTypes = {
@ParamType(type = SkylarkList.class),
},
defaultValue = "[]",
named = true,
positional = false,
doc =
"Command line arguments of the action. "
+ "Must be a list of strings or "
+ "actions.args()
objects."
),
@Param(
name = "mnemonic",
type = String.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc = "A one-word description of the action, for example, CppCompile or GoLink."
),
@Param(
name = "progress_message",
type = String.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"Progress message to show to the user during the build, "
+ "for example, \"Compiling foo.cc to create foo.o\"."
),
@Param(
name = "use_default_shell_env",
type = Boolean.class,
defaultValue = "False",
named = true,
positional = false,
doc = "Whether the action should use the built in shell environment or not."
),
@Param(
name = "env",
type = SkylarkDict.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc = "Sets the dictionary of environment variables."
),
@Param(
name = "execution_requirements",
type = SkylarkDict.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"Information for scheduling the action. See "
+ "tags "
+ "for useful keys."
),
@Param(
// TODO(bazel-team): The name here isn't accurate anymore.
// This is technically experimental, so folks shouldn't be too attached,
// but consider renaming to be more accurate/opaque.
name = "input_manifests",
type = SkylarkList.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"(Experimental) sets the input runfiles metadata; "
+ "they are typically generated by resolve_command."
)
},
useLocation = true
)
public void run(
SkylarkList outputs,
Object inputs,
Object executableUnchecked,
Object toolsUnchecked,
Object arguments,
Object mnemonicUnchecked,
Object progressMessage,
Boolean useDefaultShellEnv,
Object envUnchecked,
Object executionRequirementsUnchecked,
Object inputManifestsUnchecked,
Location location)
throws EvalException {
context.checkMutable("actions.run");
SpawnAction.Builder builder = new SpawnAction.Builder();
SkylarkList argumentsList = ((SkylarkList) arguments);
buildCommandLine(builder, argumentsList);
if (executableUnchecked instanceof Artifact) {
Artifact executable = (Artifact) executableUnchecked;
builder.addInput(executable);
FilesToRunProvider provider = context.getExecutableRunfiles(executable);
if (provider == null) {
builder.setExecutable(executable);
} else {
builder.setExecutable(provider);
}
} else if (executableUnchecked instanceof String) {
builder.setExecutable(PathFragment.create((String) executableUnchecked));
} else {
throw new EvalException(
null,
"expected file or string for "
+ "executable but got "
+ EvalUtils.getDataTypeName(executableUnchecked)
+ " instead");
}
registerSpawnAction(
outputs,
inputs,
toolsUnchecked,
mnemonicUnchecked,
progressMessage,
useDefaultShellEnv,
envUnchecked,
executionRequirementsUnchecked,
inputManifestsUnchecked,
location,
builder);
}
/**
* Registers actions in the context of this {@link SkylarkActionFactory}.
*
* Use {@link #getActionConstructionContext()} to obtain the context required to
* create those actions.
*/
public void registerAction(ActionAnalysisMetadata... actions) {
ruleContext.registerAction(actions);
}
/**
* Returns information needed to construct actions that can be
* registered with {@link #registerAction(ActionAnalysisMetadata...)}.
*/
public ActionConstructionContext getActionConstructionContext() {
return ruleContext;
}
@SkylarkCallable(
name = "run_shell",
doc =
"Creates an action that runs a shell command. "
+ "See example of use",
parameters = {
@Param(
name = "outputs",
type = SkylarkList.class,
generic1 = Artifact.class,
named = true,
positional = false,
doc = "List of the output files of the action."
),
@Param(
name = "inputs",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
generic1 = Artifact.class,
defaultValue = "[]",
named = true,
positional = false,
doc = "List or depset of the input files of the action."
),
@Param(
name = "tools",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
generic1 = Artifact.class,
defaultValue = "unbound",
named = true,
positional = false,
doc =
"List or depset of any tools needed by the action. Tools are inputs with additional "
+ "runfiles that are automatically made available to the action."
),
@Param(
name = "arguments",
allowedTypes = {
@ParamType(type = SkylarkList.class),
},
defaultValue = "[]",
named = true,
positional = false,
doc =
"Command line arguments of the action. "
+ "Must be a list of strings or "
+ "actions.args()
objects.
"
+ "Blaze passes the elements in this attribute as arguments to the command."
+ "The command can access these arguments as $1
, $2
, etc."
),
@Param(
name = "mnemonic",
type = String.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc = "A one-word description of the action, for example, CppCompile or GoLink."
),
@Param(
name = "command",
type = Object.class,
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = SkylarkList.class, generic1 = String.class),
@ParamType(type = Runtime.NoneType.class),
},
named = true,
positional = false,
doc =
"Shell command to execute.
"
+ "Passing a sequence of strings to this attribute is deprecated and Blaze may "
+ "stop accepting such values in the future.
"
+ "The command can access the elements of the arguments
object via "
+ "$1
, $2
, etc.
"
+ "When this argument is a string, it must be a valid shell command. For example: "
+ "\"echo foo > $1
\". Blaze uses the same shell to execute the "
+ "command as it does for genrules."
),
@Param(
name = "progress_message",
type = String.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"Progress message to show to the user during the build, "
+ "for example, \"Compiling foo.cc to create foo.o\"."
),
@Param(
name = "use_default_shell_env",
type = Boolean.class,
defaultValue = "False",
named = true,
positional = false,
doc = "Whether the action should use the built in shell environment or not."
),
@Param(
name = "env",
type = SkylarkDict.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc = "Sets the dictionary of environment variables."
),
@Param(
name = "execution_requirements",
type = SkylarkDict.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"Information for scheduling the action. See "
+ "tags "
+ "for useful keys."
),
@Param(
// TODO(bazel-team): The name here isn't accurate anymore.
// This is technically experimental, so folks shouldn't be too attached,
// but consider renaming to be more accurate/opaque.
name = "input_manifests",
type = SkylarkList.class,
noneable = true,
defaultValue = "None",
named = true,
positional = false,
doc =
"(Experimental) sets the input runfiles metadata; "
+ "they are typically generated by resolve_command."
)
},
useLocation = true
)
public void runShell(
SkylarkList outputs,
Object inputs,
Object toolsUnchecked,
Object arguments,
Object mnemonicUnchecked,
Object commandUnchecked,
Object progressMessage,
Boolean useDefaultShellEnv,
Object envUnchecked,
Object executionRequirementsUnchecked,
Object inputManifestsUnchecked,
Location location)
throws EvalException {
context.checkMutable("actions.run_shell");
SkylarkList argumentList = (SkylarkList) arguments;
SpawnAction.Builder builder = new SpawnAction.Builder();
buildCommandLine(builder, argumentList);
if (commandUnchecked instanceof String) {
Mapsubstitutions
dictionary. Whenever a key of the "
+ "dictionary appears in the template, it is replaced with the associated value. "
+ "There is no special syntax for the keys. You may, for example, use curly braces "
+ "to avoid conflicts (for example, {KEY}
). "
+ ""
+ "See example of use",
parameters = {
@Param(
name = "template",
type = Artifact.class,
named = true,
positional = false,
doc = "The template file, which is a UTF-8 encoded text file."
),
@Param(
name = "output",
type = Artifact.class,
named = true,
positional = false,
doc = "The output file, which is a UTF-8 encoded text file."
),
@Param(
name = "substitutions",
type = SkylarkDict.class,
named = true,
positional = false,
doc = "Substitutions to make when expanding the template."
),
@Param(
name = "is_executable",
type = Boolean.class,
defaultValue = "False",
named = true,
positional = false,
doc = "Whether the output file should be executable."
)
}
)
public void expandTemplate(
Artifact template,
Artifact output,
SkylarkDict, ?> substitutionsUnchecked,
Boolean executable)
throws EvalException {
context.checkMutable("actions.expand_template");
ImmutableList.Builderdepset"
+ "
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."
+ ""
+ "Args
"
+ "objects in addition to strings. Each Args
object represents a "
+ "concatenation of strings and depsets, with optional transformations for "
+ "manipulating the data. Args
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 Optimizing Performance page "
+ "for more information."
+ ""
+ "Args
are constructed by calling "
+ "ctx.actions.args()
. They can be passed as the arguments
"
+ "parameter of ctx.actions.run()
or "
+ "ctx.actions.run_shell()
. Each "
+ "mutation of an Args
object appends values to the eventual command "
+ "line."
+ ""
+ "map_each
feature allows you to customize how items are "
+ "transformed into strings. If you do not provide a map_each
function, "
+ "the standard conversion is as follows: "
+ ""
+ "
"
+ ""
+ "File
objects are turned into their "
+ " File.path
values."
+ "File
type to add()
, and if you pass them to "
+ " add_all()
or add_joined()
then you should provide a "
+ " map_each
function."
+ "format
, format_each
, and "
+ "format_joined
params of the add*()
methods), the format "
+ "template is interpreted in the same way as %
-substitution on strings, "
+ "except that the template must have exactly one substitution placeholder and it must "
+ "be %s
. Literal percents may be escaped as %%
. Formatting "
+ "is applied after the value is converted to a string as per the above."
+ ""
+ "add*()
methods have an alternate form that accepts an "
+ "extra positional parameter, an \"arg name\" string to insert before the rest of the "
+ "arguments. For add_all
and add_joined
the extra string "
+ "will not be added if the sequence turns out to be empty. "
+ "For instance, the same usage can add either --foo val1 val2 val3 --bar"
+ "
or just --bar
to the command line, depending on whether the "
+ "given sequence contains val1..val3
or is empty."
+ ""
+ "use_param_file()
and "
+ "set_param_file_format()
."
+ ""
+ "\n"
+ "--foo foo1.txt foo2.txt ... fooN.txt --bar bar1.txt,bar2.txt,...,barM.txt --baz\n"
+ "
"
+ "We could use the following Args
object: "
+ "\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"
+ "
"
)
@VisibleForTesting
public static class Args extends SkylarkMutable {
private final Mutability mutability;
private final SkylarkSemantics skylarkSemantics;
private final SkylarkCustomCommandLine.Builder commandLine;
private ParameterFileType parameterFileType = ParameterFileType.SHELL_QUOTED;
private String flagFormatString;
private boolean useAlways;
@SkylarkCallable(
name = "add",
doc =
"Appends an argument to this command line."
+ ""
+ "before_each
, join_with
"
+ "and map_fn
params are replaced by the "
+ "add_all()
and add_joined()
"
+ "methods. These parameters will be removed, and are currently disallowed if the "
+ ""
+ "--incompatible_disallow_old_style_args_add
flag is set. Likewise, "
+ "value
should now be a scalar value, not a list, tuple, or depset of "
+ "items.",
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 "
+ "value
(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 map_each
parameter "
+ "for this function, value
should be either a string or a "
+ "File
."
+ ""
+ "value
may also be a list, tuple, "
+ "or depset of multiple items to append."
),
@Param(
name = "format",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"A format string pattern, to be applied to the stringified version of value"
+ "
."
+ ""
+ "value
is a list or depset, "
+ "formatting is applied to each item."
),
@Param(
name = "before_each",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"Deprecated: Only supported when value
is a list, tuple, or "
+ "depset. This string will be appended prior to appending each item."
),
@Param(
name = "join_with",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"Deprecated: Only supported when value
is a list, tuple, or "
+ "depset. All items will be joined together using this string to form a single "
+ "arg to append."
),
@Param(
name = "map_fn",
type = BaseFunction.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"Deprecated: Only supported when value
is a list, tuple, or "
+ "depset. This is a function that transforms the sequence of items into a list "
+ "of strings. The sequence of items is given as a positional argument -- the "
+ "function must not take any other parameters -- and the returned list's length "
+ "must equal the number of items. Use map_each
of add_all"
+ "
or add_joined
instead."
)
},
useLocation = true
)
public NoneType addArgument(
Object argNameOrValue,
Object value,
Object format,
Object beforeEach,
Object joinWith,
Object mapFn,
Location loc)
throws EvalException {
if (isImmutable()) {
throw new EvalException(null, "cannot modify frozen value");
}
final String argName;
if (value == Runtime.UNBOUND) {
value = argNameOrValue;
argName = null;
} else {
validateArgName(argNameOrValue, loc);
argName = (String) argNameOrValue;
}
if (argName != null) {
commandLine.add(argName);
}
if (value instanceof SkylarkNestedSet || value instanceof SkylarkList) {
if (skylarkSemantics.incompatibleDisallowOldStyleArgsAdd()) {
throw new EvalException(
loc,
"Args#add no longer accepts vectorized arguments when "
+ "--incompatible_disallow_old_style_args_add is set. "
+ "Please use Args#add_all or Args#add_joined.");
}
addVectorArg(
value,
/* argName= */ null,
mapFn != Runtime.NONE ? (BaseFunction) mapFn : null,
/* mapEach= */ null,
format != Runtime.NONE ? (String) format : null,
beforeEach != Runtime.NONE ? (String) beforeEach : null,
joinWith != Runtime.NONE ? (String) joinWith : null,
/* formatJoined= */ null,
/* omitIfEmpty= */ false,
/* uniquify= */ false,
/* terminateWith= */ null,
loc);
} else {
if (mapFn != Runtime.NONE && skylarkSemantics.incompatibleDisallowOldStyleArgsAdd()) {
throw new EvalException(
loc,
"Args#add no longer accepts map_fn when"
+ "--incompatible_disallow_old_style_args_add is set. "
+ "Please eagerly map the value.");
}
if (beforeEach != Runtime.NONE) {
throw new EvalException(null, "'before_each' is not supported for scalar arguments");
}
if (joinWith != Runtime.NONE) {
throw new EvalException(null, "'join_with' is not supported for scalar arguments");
}
addScalarArg(
value,
format != Runtime.NONE ? (String) format : null,
mapFn != Runtime.NONE ? (BaseFunction) mapFn : null,
loc);
}
return Runtime.NONE;
}
@SkylarkCallable(
name = "add_all",
doc =
"Appends multiple arguments to this command line. For depsets, the items are "
+ "evaluated lazily during the execution phase."
+ ""
+ ""
+ "
"
+ "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 map_each
is given, it is applied to each input 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."
+ "format_each
, if "
+ " present."
+ "uniquify
is true, duplicate arguments are removed. The first "
+ " occurrence is the one that remains."
+ "before_each
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."
+ "omit_if_empty
is "
+ " true (the default), the arg name and terminate_with
are "
+ " inserted as the first and last arguments, respectively, if they are given."
+ "values
without any processing. "
+ "This arg name will not be added if omit_if_empty
is true "
+ "(the default) and no other items are appended (as happens if "
+ "values
is empty or all of its items are filtered). "
+ "If only one positional parameter is passed, it is interpreted as "
+ "values
(see below)."
),
@Param(
name = "values",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
defaultValue = "unbound",
doc = "The list, tuple, or depset whose items will be appended."
),
@Param(
name = "map_each",
type = BaseFunction.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
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."
+ ""
+ ""
+ "
"
+ "Returning a single string or None
."
+ "None
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."
+ ""
+ "print()
"
+ "statements that are executed during the call to map_each
will "
+ "not produce any visible output."
),
@Param(
name = "format_each",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"An optional format string pattern, applied to each string returned by the "
+ "map_each
function. "
+ "The format string must have exactly one '%s' placeholder."
),
@Param(
name = "before_each",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"An optional string to append before each argument derived from values
"
+ "is appended."
),
@Param(
name = "omit_if_empty",
type = Boolean.class,
named = true,
positional = false,
defaultValue = "True",
doc =
"If true, if there are no arguments derived from values
to be appended, "
+ "then all further processing is suppressed and the command line will be "
+ "unchanged. If false, the arg name and terminate_with
, "
+ "if provided, will still be appended regardless of whether or not there are "
+ "other arguments."
),
@Param(
name = "uniquify",
type = Boolean.class,
named = true,
positional = false,
defaultValue = "False",
doc =
"If true, duplicate arguments that are derived from values
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 map_each
emits the same string for multiple items."
),
@Param(
name = "terminate_with",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc =
"An optional string to append after all other arguments. This string will not be "
+ "added if omit_if_empty
is true (the default) and no other items "
+ "are appended (as happens if values
is empty or all of its items "
+ "are filtered)."
),
},
useLocation = true
)
public NoneType addAll(
Object argNameOrValue,
Object values,
Object mapEach,
Object formatEach,
Object beforeEach,
Boolean omitIfEmpty,
Boolean uniquify,
Object terminateWith,
Location loc)
throws EvalException {
if (isImmutable()) {
throw new EvalException(null, "cannot modify frozen value");
}
final String argName;
if (values == Runtime.UNBOUND) {
values = argNameOrValue;
validateValues(values, loc);
argName = null;
} else {
validateArgName(argNameOrValue, loc);
argName = (String) argNameOrValue;
}
addVectorArg(
values,
argName,
/* mapAll= */ null,
mapEach != Runtime.NONE ? (BaseFunction) mapEach : null,
formatEach != Runtime.NONE ? (String) formatEach : null,
beforeEach != Runtime.NONE ? (String) beforeEach : null,
/* joinWith= */ null,
/* formatJoined= */ null,
omitIfEmpty,
uniquify,
terminateWith != Runtime.NONE ? (String) terminateWith : null,
loc);
return Runtime.NONE;
}
@SkylarkCallable(
name = "add_joined",
doc =
"Appends an argument to this command line by concatenating together multiple values "
+ "using a separator. For depsets, the items are evaluated lazily during the "
+ "execution phase."
+ ""
+ "add_all()
, but "
+ "the list of arguments derived from values
is combined into a single "
+ "argument as if by join_with.join(...)
, and then formatted using the "
+ "given format_joined
string template. Unlike add_all()
, "
+ "there is no before_each
or terminate_with
parameter "
+ "since these are not generally useful when the items are combined into a single "
+ "argument."
+ ""
+ "omit_if_empty
is true (the default), no processing is done. "
+ "Otherwise if there are no strings to join but omit_if_empty
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 values
without any processing. "
+ "This arg will not be added if omit_if_empty
is true "
+ "(the default) and there are no strings derived from values
"
+ "to join together (which can happen if values
is empty "
+ "or all of its items are filtered)."
+ "If only one positional parameter is passed, it is interpreted as "
+ "values
(see below)."
),
@Param(
name = "values",
allowedTypes = {
@ParamType(type = SkylarkList.class),
@ParamType(type = SkylarkNestedSet.class),
},
defaultValue = "unbound",
doc = "The list, tuple, or depset whose items will be joined."
),
@Param(
name = "join_with",
type = String.class,
named = true,
positional = false,
doc =
"A delimiter string used to join together the strings obtained from applying "
+ "map_each
and format_each
, in the same manner as "
+ "string.join()
."
),
@Param(
name = "map_each",
type = BaseFunction.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc = "Same as for add_all
."
),
@Param(
name = "format_each",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
doc = "Same as for add_all
."
),
@Param(
name = "format_joined",
type = String.class,
named = true,
positional = false,
defaultValue = "None",
noneable = true,
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",
type = Boolean.class,
named = true,
positional = false,
defaultValue = "True",
doc =
"If true, if there are no strings to join together (either because values"
+ "
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",
type = Boolean.class,
named = true,
positional = false,
defaultValue = "False",
doc = "Same as for add_all
."
)
},
useLocation = true
)
public NoneType addJoined(
Object argNameOrValue,
Object values,
String joinWith,
Object mapEach,
Object formatEach,
Object formatJoined,
Boolean omitIfEmpty,
Boolean uniquify,
Location loc)
throws EvalException {
if (isImmutable()) {
throw new EvalException(null, "cannot modify frozen value");
}
final String argName;
if (values == Runtime.UNBOUND) {
values = argNameOrValue;
validateValues(values, loc);
argName = null;
} else {
validateArgName(argNameOrValue, loc);
argName = (String) argNameOrValue;
}
addVectorArg(
values,
argName,
/* mapAll= */ null,
mapEach != Runtime.NONE ? (BaseFunction) mapEach : null,
formatEach != Runtime.NONE ? (String) formatEach : null,
/* beforeEach= */ null,
joinWith,
formatJoined != Runtime.NONE ? (String) formatJoined : null,
omitIfEmpty,
uniquify,
/* terminateWith= */ null,
loc);
return Runtime.NONE;
}
private void addVectorArg(
Object value,
String argName,
BaseFunction mapAll,
BaseFunction mapEach,
String formatEach,
String beforeEach,
String joinWith,
String formatJoined,
boolean omitIfEmpty,
boolean uniquify,
String terminateWith,
Location loc)
throws EvalException {
SkylarkCustomCommandLine.VectorArg.Builder vectorArg;
if (value instanceof SkylarkNestedSet) {
NestedSet> nestedSet = ((SkylarkNestedSet) value).getSet(Object.class);
vectorArg = new SkylarkCustomCommandLine.VectorArg.Builder(nestedSet);
} else {
SkylarkList skylarkList = (SkylarkList) value;
vectorArg = new SkylarkCustomCommandLine.VectorArg.Builder(skylarkList);
}
validateMapEach(mapEach, loc);
validateFormatString("format_each", formatEach);
validateFormatString("format_joined", formatJoined);
vectorArg
.setLocation(loc)
.setArgName(argName)
.setMapAll(mapAll)
.setFormatEach(formatEach)
.setBeforeEach(beforeEach)
.setJoinWith(joinWith)
.setFormatJoined(formatJoined)
.omitIfEmpty(omitIfEmpty)
.uniquify(uniquify)
.setTerminateWith(terminateWith)
.setMapEach(mapEach);
commandLine.add(vectorArg);
}
private void validateArgName(Object argName, Location loc) throws EvalException {
if (!(argName instanceof String)) {
throw new EvalException(
loc,
String.format(
"expected value of type 'string' for arg name, got '%s'",
argName.getClass().getSimpleName()));
}
}
private void validateValues(Object values, Location loc) throws EvalException {
if (!(values instanceof SkylarkList || values instanceof SkylarkNestedSet)) {
throw new EvalException(
loc,
String.format(
"expected value of type 'sequence or depset' for values, got '%s'",
values.getClass().getSimpleName()));
}
}
private void validateMapEach(@Nullable BaseFunction mapEach, Location loc)
throws EvalException {
if (mapEach == null) {
return;
}
Shape shape = mapEach.getSignature().getSignature().getShape();
boolean valid =
shape.getMandatoryPositionals() == 1
&& shape.getOptionalPositionals() == 0
&& shape.getMandatoryNamedOnly() == 0
&& shape.getOptionalPositionals() == 0;
if (!valid) {
throw new EvalException(
loc, "map_each must be a function that accepts a single positional argument");
}
}
private void validateFormatString(String argumentName, @Nullable String formatStr)
throws EvalException {
if (formatStr != null
&& skylarkSemantics.incompatibleDisallowOldStyleArgsAdd()
&& !CommandLineItemSimpleFormatter.isValid(formatStr)) {
throw new EvalException(
null,
String.format(
"Invalid value for parameter \"%s\": Expected string with a single \"%%s\"",
argumentName));
}
}
private void addScalarArg(Object value, String format, BaseFunction mapFn, Location loc)
throws EvalException {
validateFormatString("format", format);
if (format == null && mapFn == null) {
commandLine.add(value);
} else {
ScalarArg.Builder scalarArg =
new ScalarArg.Builder(value).setLocation(loc).setFormat(format).setMapFn(mapFn);
commandLine.add(scalarArg);
}
}
@SkylarkCallable(
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 ",
parameters = {
@Param(
name = "param_file_arg",
type = String.class,
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."
),
@Param(
name = "use_always",
type = Boolean.class,
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."
)
}
)
public void useParamsFile(String paramFileArg, Boolean useAlways) throws EvalException {
if (isImmutable()) {
throw new EvalException(null, "cannot modify frozen value");
}
if (!paramFileArg.contains("%s")) {
throw new EvalException(
null,
"Invalid value for parameter \"param_file_arg\": Expected string with a single \"%s\"");
}
this.flagFormatString = paramFileArg;
this.useAlways = useAlways;
}
@SkylarkCallable(
name = "set_param_file_format",
doc = "Sets the format of the param file when written to disk",
parameters = {
@Param(
name = "format",
type = String.class,
named = true,
doc =
"The format of the param file. Must be one of:
"
+ "\"shell\": All arguments are shell quoted and separated by whitespace
"
+ "\"multiline\": All arguments are unquoted and separated by newline characters"
+ "The format defaults to \"shell\" if not called."
)
}
)
public void setParamFileFormat(String format) throws EvalException {
if (isImmutable()) {
throw new EvalException(null, "cannot modify frozen value");
}
final ParameterFileType parameterFileType;
switch (format) {
case "shell":
parameterFileType = ParameterFileType.SHELL_QUOTED;
break;
case "multiline":
parameterFileType = ParameterFileType.UNQUOTED;
break;
default:
throw new EvalException(
null,
"Invalid value for parameter \"format\": Expected one of \"shell\", \"multiline\"");
}
this.parameterFileType = parameterFileType;
}
private Args(@Nullable Mutability mutability, SkylarkSemantics skylarkSemantics) {
this.mutability = mutability != null ? mutability : Mutability.IMMUTABLE;
this.skylarkSemantics = skylarkSemantics;
this.commandLine = new SkylarkCustomCommandLine.Builder(skylarkSemantics);
}
public SkylarkCustomCommandLine build() {
return commandLine.build();
}
@Override
public Mutability mutability() {
return mutability;
}
@Override
public void repr(SkylarkPrinter printer) {
printer.append("context.args() object");
}
}
@SkylarkCallable(
name = "args",
doc = "Returns an Args object that can be used to build memory-efficient command lines.",
useEnvironment = true
)
public Args args(Environment env) {
return new Args(env.mutability(), skylarkSemantics);
}
@Override
public boolean isImmutable() {
return context.isImmutable();
}
@Override
public void repr(SkylarkPrinter printer) {
printer.append("actions for");
context.repr(printer);
}
void nullify() {
ruleContext = null;
}
}