aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java367
1 files changed, 367 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
new file mode 100644
index 0000000000..1f7d1609b0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
@@ -0,0 +1,367 @@
+// Copyright 2014 Google Inc. 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.rules;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.CommandHelper;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.MakeVariableExpander;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+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.events.Location;
+import com.google.devtools.build.lib.packages.Type.ConversionException;
+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.Label;
+import com.google.devtools.build.lib.syntax.SkylarkBuiltin;
+import com.google.devtools.build.lib.syntax.SkylarkBuiltin.Param;
+import com.google.devtools.build.lib.syntax.SkylarkFunction;
+import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction;
+import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+// TODO(bazel-team): function argument names are often duplicated,
+// figure out a nicely readable way to get rid of the duplications.
+/**
+ * A helper class to provide an easier API for Skylark rule implementations
+ * and hide the original Java API. This is experimental code.
+ */
+public class SkylarkRuleImplementationFunctions {
+
+ // TODO(bazel-team): add all the remaining parameters
+ // TODO(bazel-team): merge executable and arguments
+ /**
+ * A Skylark built-in function to create and register a SpawnAction using a
+ * dictionary of parameters:
+ * createSpawnAction(
+ * inputs = [input1, input2, ...],
+ * outputs = [output1, output2, ...],
+ * executable = executable,
+ * arguments = [argument1, argument2, ...],
+ * mnemonic = 'mnemonic',
+ * command = 'command',
+ * register = 1
+ * )
+ */
+ @SkylarkBuiltin(name = "action",
+ doc = "Creates an action that runs an executable or a shell command.",
+ objectType = SkylarkRuleContext.class,
+ returnType = Environment.NoneType.class,
+ mandatoryParams = {
+ @Param(name = "outputs", type = SkylarkList.class, generic1 = Artifact.class,
+ doc = "list of the output files of the action")},
+ optionalParams = {
+ @Param(name = "inputs", type = SkylarkList.class, generic1 = Artifact.class,
+ doc = "list of the input files of the action"),
+ @Param(name = "executable", doc = "the executable file to be called by the action"),
+ @Param(name = "arguments", type = SkylarkList.class, generic1 = String.class,
+ doc = "command line arguments of the action"),
+ @Param(name = "mnemonic", type = String.class, doc = "mnemonic"),
+ @Param(name = "command", doc = "shell command to execute"),
+ @Param(name = "command_line", doc = "a command line to execute"),
+ @Param(name = "progress_message", type = String.class,
+ doc = "progress message to show to the user during the build"),
+ @Param(name = "use_default_shell_env", type = Boolean.class,
+ doc = "whether the action should use the built in shell environment or not"),
+ @Param(name = "env", type = Map.class, doc = "sets the dictionary of environment variables"),
+ @Param(name = "execution_requirements", type = Map.class,
+ doc = "information for scheduling the action"),
+ @Param(name = "input_manifests", type = Map.class,
+ doc = "sets the map of input manifests files; "
+ + "they are typicially generated by the command_helper")})
+ private static final SkylarkFunction createSpawnAction =
+ new SimpleSkylarkFunction("action") {
+
+ @Override
+ public Object call(Map<String, Object> params, Location loc) throws EvalException,
+ ConversionException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ SpawnAction.Builder builder = new SpawnAction.Builder();
+ // TODO(bazel-team): builder still makes unnecessary copies of inputs, outputs and args.
+ builder.addInputs(castList(params.get("inputs"), Artifact.class));
+ builder.addOutputs(castList(params.get("outputs"), Artifact.class));
+ builder.addArguments(castList(params.get("arguments"), String.class));
+ if (params.containsKey("executable")) {
+ Object exe = params.get("executable");
+ if (exe instanceof Artifact) {
+ Artifact executable = (Artifact) exe;
+ builder.addInput(executable);
+ FilesToRunProvider provider = ctx.getExecutableRunfiles(executable);
+ if (provider == null) {
+ builder.setExecutable((Artifact) exe);
+ } else {
+ builder.setExecutable(provider);
+ }
+ } else if (exe instanceof PathFragment) {
+ builder.setExecutable((PathFragment) exe);
+ } else {
+ throw new EvalException(loc, "expected file or PathFragment for "
+ + "executable but got " + EvalUtils.getDatatypeName(exe) + " instead");
+ }
+ }
+ if (params.containsKey("command") == params.containsKey("executable")) {
+ throw new EvalException(loc, "You must specify either 'command' or 'executable' argument");
+ }
+ if (params.containsKey("command")) {
+ Object command = params.get("command");
+ if (command instanceof String) {
+ builder.setShellCommand((String) command);
+ } else if (command instanceof SkylarkList) {
+ SkylarkList commandList = (SkylarkList) command;
+ if (commandList.size() < 3) {
+ throw new EvalException(loc, "'command' list has to be of size at least 3");
+ }
+ builder.setShellCommand(castList(commandList, String.class, "command"));
+ } else {
+ throw new EvalException(loc, "expected string or list of strings for "
+ + "command instead of " + EvalUtils.getDatatypeName(command));
+ }
+ }
+ if (params.containsKey("command_line")) {
+ builder.setCommandLine(CommandLine.ofCharSequences(ImmutableList.copyOf(castList(
+ params.get("command_line"), CharSequence.class, "command line"))));
+ }
+ if (params.containsKey("mnemonic")) {
+ builder.setMnemonic((String) params.get("mnemonic"));
+ }
+ if (params.containsKey("env")) {
+ builder.setEnvironment(
+ toMap(castMap(params.get("env"), String.class, String.class, "env")));
+ }
+ if (params.containsKey("progress_message")) {
+ builder.setProgressMessage((String) params.get("progress_message"));
+ }
+ if (params.containsKey("use_default_shell_env")
+ && EvalUtils.toBoolean(params.get("use_default_shell_env"))) {
+ builder.useDefaultShellEnvironment();
+ }
+ if (params.containsKey("execution_requirements")) {
+ builder.setExecutionInfo(toMap(castMap(params.get("execution_requirements"),
+ String.class, String.class, "execution_requirements")));
+ }
+ if (params.containsKey("input_manifests")) {
+ for (Map.Entry<PathFragment, Artifact> entry : castMap(params.get("input_manifests"),
+ PathFragment.class, Artifact.class, "input manifest file map")) {
+ builder.addInputManifest(entry.getValue(), entry.getKey());
+ }
+ }
+ // Always register the action
+ ctx.getRuleContext().registerAction(builder.build(ctx.getRuleContext()));
+ return Environment.NONE;
+ }
+ };
+
+ // TODO(bazel-team): improve this method to be more memory friendly
+ @SkylarkBuiltin(name = "file_action",
+ doc = "Creates a file write action.",
+ objectType = SkylarkRuleContext.class,
+ returnType = Environment.NoneType.class,
+ optionalParams = {
+ @Param(name = "executable", type = Boolean.class,
+ doc = "whether the output file should be executable (default is False)"),
+ },
+ mandatoryParams = {
+ @Param(name = "output", type = Artifact.class, doc = "the output file"),
+ @Param(name = "content", type = String.class, doc = "the contents of the file")})
+ private static final SkylarkFunction createFileWriteAction =
+ new SimpleSkylarkFunction("file_action") {
+
+ @Override
+ public Object call(Map<String, Object> params, Location loc) throws EvalException,
+ ConversionException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ boolean executable = params.containsKey("executable") && (Boolean) params.get("executable");
+ FileWriteAction action = new FileWriteAction(
+ ctx.getRuleContext().getActionOwner(),
+ (Artifact) params.get("output"),
+ (String) params.get("content"),
+ executable);
+ ctx.getRuleContext().registerAction(action);
+ return action;
+ }
+ };
+
+ @SkylarkBuiltin(name = "template_action",
+ doc = "Creates a template expansion action.",
+ objectType = SkylarkRuleContext.class,
+ returnType = Environment.NoneType.class,
+ mandatoryParams = {
+ @Param(name = "template", type = Artifact.class, doc = "the template file"),
+ @Param(name = "output", type = Artifact.class, doc = "the output file"),
+ @Param(name = "substitutions", type = Map.class,
+ doc = "substitutions to make when expanding the template")},
+ optionalParams = {
+ @Param(name = "executable", type = Boolean.class,
+ doc = "whether the output file should be executable (default is False)")})
+ private static final SkylarkFunction createTemplateAction =
+ new SimpleSkylarkFunction("template_action") {
+
+ @Override
+ public Object call(Map<String, Object> params, Location loc) throws EvalException,
+ ConversionException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ ImmutableList.Builder<Substitution> substitutions = ImmutableList.builder();
+ for (Map.Entry<String, String> substitution
+ : castMap(params.get("substitutions"), String.class, String.class, "substitutions")) {
+ substitutions.add(Substitution.of(substitution.getKey(), substitution.getValue()));
+ }
+
+ boolean executable = params.containsKey("executable") && (Boolean) params.get("executable");
+ TemplateExpansionAction action = new TemplateExpansionAction(
+ ctx.getRuleContext().getActionOwner(),
+ (Artifact) params.get("template"),
+ (Artifact) params.get("output"),
+ substitutions.build(),
+ executable);
+ ctx.getRuleContext().registerAction(action);
+ return action;
+ }
+ };
+
+ /**
+ * A built in Skylark helper function to access the
+ * Transitive info providers of Transitive info collections.
+ */
+ @SkylarkBuiltin(name = "provider",
+ doc = "Returns the transitive info provider provided by the target.",
+ mandatoryParams = {
+ @Param(name = "target", type = TransitiveInfoCollection.class,
+ doc = "the configured target which provides the provider"),
+ @Param(name = "type", type = String.class, doc = "the class type of the provider")})
+ private static final SkylarkFunction provider = new SimpleSkylarkFunction("provider") {
+ @Override
+ public Object call(Map<String, Object> params, Location loc) throws EvalException {
+ TransitiveInfoCollection target = (TransitiveInfoCollection) params.get("target");
+ String type = (String) params.get("type");
+ try {
+ Class<?> classType = SkylarkRuleContext.classCache.get(type);
+ Class<? extends TransitiveInfoProvider> convertedClass =
+ classType.asSubclass(TransitiveInfoProvider.class);
+ Object result = target.getProvider(convertedClass);
+ return result == null ? Environment.NONE : result;
+ } catch (ExecutionException e) {
+ throw new EvalException(loc, "Unknown class type " + type);
+ } catch (ClassCastException e) {
+ throw new EvalException(loc, "Not a TransitiveInfoProvider " + type);
+ }
+ }
+ };
+
+ // TODO(bazel-team): Remove runfile states from Skylark.
+ @SkylarkBuiltin(name = "runfiles",
+ doc = "Creates a runfiles object.",
+ objectType = SkylarkRuleContext.class,
+ returnType = Runfiles.class,
+ optionalParams = {
+ @Param(name = "files", type = SkylarkList.class, generic1 = Artifact.class,
+ doc = "The list of files to be added to the runfiles."),
+ // TODO(bazel-team): If we have a memory efficient support for lazy list containing NestedSets
+ // we can remove this and just use files = [file] + list(set)
+ @Param(name = "transitive_files", type = SkylarkNestedSet.class, generic1 = Artifact.class,
+ doc = "The (transitive) set of files to be added to the runfiles."),
+ @Param(name = "collect_data", type = Boolean.class, doc = "Whether to collect the data "
+ + "runfiles from the dependencies in srcs, data and deps attributes."),
+ @Param(name = "collect_default", type = Boolean.class, doc = "Whether to collect the default "
+ + "runfiles from the dependencies in srcs, data and deps attributes.")})
+ private static final SkylarkFunction runfiles = new SimpleSkylarkFunction("runfiles") {
+ @Override
+ public Object call(Map<String, Object> params, Location loc) throws EvalException,
+ ConversionException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ Runfiles.Builder builder = new Runfiles.Builder();
+ if (params.containsKey("collect_data") && (Boolean) params.get("collect_data")) {
+ builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DATA_RUNFILES);
+ }
+ if (params.containsKey("collect_default") && (Boolean) params.get("collect_default")) {
+ builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES);
+ }
+ if (params.containsKey("files")) {
+ builder.addArtifacts(castList(params.get("files"), Artifact.class));
+ }
+ if (params.containsKey("transitive_files")) {
+ builder.addTransitiveArtifacts(cast(params.get("transitive_files"),
+ SkylarkNestedSet.class, "files", loc).getSet(Artifact.class));
+ }
+ return builder.build();
+ }
+ };
+
+ @SkylarkBuiltin(name = "command_helper", doc = "Creates a command helper class.",
+ objectType = SkylarkRuleContext.class,
+ returnType = CommandHelper.class,
+ mandatoryParams = {
+ @Param(name = "tools", type = SkylarkList.class, generic1 = TransitiveInfoCollection.class,
+ doc = "list of tools (list of targets)"),
+ @Param(name = "label_dict", type = Map.class,
+ doc = "dictionary of resolved labels and the corresponding list of artifacts "
+ + "(a dict of Label : list of files)")})
+ private static final SkylarkFunction createCommandHelper =
+ new SimpleSkylarkFunction("command_helper") {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Object call(Map<String, Object> params, Location loc)
+ throws ConversionException, EvalException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ return new CommandHelper(ctx.getRuleContext(),
+ AnalysisUtils.getProviders(
+ castList(params.get("tools"), TransitiveInfoCollection.class),
+ FilesToRunProvider.class),
+ // TODO(bazel-team): this cast to Map is unchecked and is not safe.
+ // The best way to fix this probably is to convert CommandHelper to Skylark.
+ ImmutableMap.copyOf((Map<Label, Iterable<Artifact>>) params.get("label_dict")));
+ }
+ };
+
+
+ @SkylarkBuiltin(name = "var",
+ doc = "get the value bound to a configuration variable in the context",
+ objectType = SkylarkRuleContext.class,
+ mandatoryParams = {
+ @Param(name = "name", type = String.class, doc = "the name of the variable")
+ },
+ returnType = String.class)
+ private static final SkylarkFunction configurationMakeVariableContext =
+ new SimpleSkylarkFunction("var") {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Object call(Map<String, Object> params, Location loc)
+ throws ConversionException, EvalException {
+ SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+ String name = (String) params.get("name");
+ try {
+ return ctx.getRuleContext().getConfigurationMakeVariableContext()
+ .lookupMakeVariable(name);
+ } catch (MakeVariableExpander.ExpansionException e) {
+ throw new EvalException(loc, "configuration variable "
+ + ShellEscaper.escapeString(name) + " not defined");
+ }
+ }
+ };
+}