// 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.rules;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.actions.extra.SpawnInfo;
import com.google.devtools.build.lib.analysis.PseudoAction;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
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.EvalException;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* Provides a Skylark interface for all action creation needs.
*/
@SkylarkModule(
name = "actions",
category = SkylarkModuleCategory.BUILTIN,
doc = "Module providing functions to create actions."
)
public class SkylarkActionFactory implements SkylarkValue {
private final SkylarkRuleContext context;
private RuleContext ruleContext;
public SkylarkActionFactory(SkylarkRuleContext context, RuleContext ruleContext) {
this.context = context;
this.ruleContext = ruleContext;
}
Root newFileRoot() throws EvalException {
return context.isForAspect()
? ruleContext.getConfiguration().getBinDirectory(ruleContext.getRule().getRepository())
: ruleContext.getBinOrGenfilesDirectory();
}
@SkylarkCallable(
name = "declare_file",
doc =
"Declares that rule or aspect creates a file with the given filename. "
+ "If sibling
is not specified, file name is relative to "
+ "package directory, otherwise the file is in the same directory as "
+ "sibling
. "
+ "You must create an action that generates the file.
"
+ "Files that are specified in rule's outputs do not need to be declared and are "
+ "available through ctx.outputs.",
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.",
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 file.
"
+ "Files that are specified in rule's outputs do not need to be declared and are "
+ "available through ctx.outputs.",
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, e.g. 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");
NestedSet inputSet = inputs instanceof SkylarkNestedSet
? ((SkylarkNestedSet) inputs).getSet(Artifact.class)
: NestedSetBuilder.compileOrder()
.addAll(((SkylarkList) inputs).getContents(Artifact.class, "inputs"))
.build();
Action action =
new PseudoAction<>(
UUID.nameUUIDFromBytes(
String.format("empty action %s", ruleContext.getLabel())
.getBytes(StandardCharsets.UTF_8)),
ruleContext.getActionOwner(),
inputSet,
ImmutableList.of(PseudoAction.getDummyOutput(ruleContext)),
mnemonic,
SpawnInfo.spawnInfo,
SpawnInfo.newBuilder().build());
ruleContext.registerAction(action);
}
@SkylarkCallable(
name = "write",
doc = "Creates a file write action.",
parameters = {
@Param(
name = "output",
type = Artifact.class,
doc = "the output file.",
named = true
),
@Param(
name = "content",
type = String.class,
doc = "the contents of the file.",
named = true
),
@Param(
name = "is_executable",
type = Boolean.class,
defaultValue = "False",
doc = "whether the output file should be executable (default is False).",
named = true
)
}
)
public void write(Artifact output, String content, Boolean isExecutable)
throws EvalException {
context.checkMutable("actions.write");
FileWriteAction action =
FileWriteAction.create(ruleContext, output, content, isExecutable);
ruleContext.registerAction(action);
}
@Override
public boolean isImmutable() {
return context.isImmutable();
}
@Override
public void repr(SkylarkPrinter printer) {
printer.append("actions for");
context.repr(printer);
}
void nullify() {
ruleContext = null;
}
}