// Copyright 2014 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.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.analysis.ActionsProvider;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext;
import com.google.devtools.build.lib.analysis.DefaultInfo;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.LabelExpander;
import com.google.devtools.build.lib.analysis.LabelExpander.NotUniqueExpansionException;
import com.google.devtools.build.lib.analysis.LocationExpander;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.ShToolchain;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.FragmentCollection;
import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.stringtemplate.ExpansionException;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectDescriptor;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.OutputFile;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.StructProvider;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException;
import com.google.devtools.build.lib.skylarkbuildapi.FileApi;
import com.google.devtools.build.lib.skylarkbuildapi.FileRootApi;
import com.google.devtools.build.lib.skylarkbuildapi.SkylarkRuleContextApi;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.ClassObject;
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.FuncallExpression.FuncallException;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkIndexable;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
import com.google.devtools.build.lib.syntax.Type.LabelClass;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A Skylark API for the ruleContext.
*
*
"This object becomes featureless once the rule implementation function that it was created for
* has completed. To achieve this, the {@link #nullify()} should be called once the evaluation of
* the function is completed. The method both frees memory by deleting all significant fields of the
* object and makes it impossible to accidentally use this object where it's not supposed to be used
* (such attempts will result in {@link EvalException}s).
*/
public final class SkylarkRuleContext implements SkylarkRuleContextApi {
public static final Function ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT =
new Function() {
@Nullable
@Override
public Object apply(Attribute attribute) {
return attribute.getDefaultValue(null);
}
};
public static final String EXECUTABLE_OUTPUT_NAME = "executable";
// This field is a copy of the info from ruleContext, stored separately so it can be accessed
// after this object has been nullified.
private final String ruleLabelCanonicalName;
private final boolean isForAspect;
private final SkylarkActionFactory actionFactory;
// The fields below intended to be final except that they can be cleared by calling `nullify()`
// when the object becomes featureless.
private RuleContext ruleContext;
private FragmentCollection fragments;
private FragmentCollection hostFragments;
private AspectDescriptor aspectDescriptor;
private final SkylarkSemantics skylarkSemantics;
private SkylarkDict makeVariables;
private SkylarkAttributesCollection attributesCollection;
private SkylarkAttributesCollection ruleAttributesCollection;
private Info splitAttributes;
// TODO(bazel-team): we only need this because of the css_binary rule.
private ImmutableMap artifactsLabelMap;
private Outputs outputsObject;
/**
* Creates a new SkylarkRuleContext using ruleContext.
* @param aspectDescriptor aspect for which the context is created, or null
* if it is for a rule.
* @throws InterruptedException
*/
public SkylarkRuleContext(RuleContext ruleContext,
@Nullable AspectDescriptor aspectDescriptor,
SkylarkSemantics skylarkSemantics)
throws EvalException, InterruptedException {
this.actionFactory = new SkylarkActionFactory(this, skylarkSemantics, ruleContext);
this.ruleContext = Preconditions.checkNotNull(ruleContext);
this.ruleLabelCanonicalName = ruleContext.getLabel().getCanonicalForm();
this.fragments = new FragmentCollection(ruleContext, NoTransition.INSTANCE);
this.hostFragments = new FragmentCollection(ruleContext, HostTransition.INSTANCE);
this.aspectDescriptor = aspectDescriptor;
this.skylarkSemantics = skylarkSemantics;
if (aspectDescriptor == null) {
this.isForAspect = false;
Collection attributes = ruleContext.getRule().getAttributes();
Outputs outputs = new Outputs(this);
ImplicitOutputsFunction implicitOutputsFunction =
ruleContext.getRule().getImplicitOutputsFunction();
if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) {
SkylarkImplicitOutputsFunction func =
(SkylarkImplicitOutputsFunction) implicitOutputsFunction;
for (Map.Entry entry :
func.calculateOutputs(
ruleContext.getAnalysisEnvironment().getEventHandler(),
RawAttributeMapper.of(ruleContext.getRule()))
.entrySet()) {
outputs.addOutput(
entry.getKey(),
ruleContext.getImplicitOutputArtifact(entry.getValue()));
}
}
ImmutableMap.Builder artifactLabelMapBuilder = ImmutableMap.builder();
for (Attribute a : attributes) {
String attrName = a.getName();
Type> type = a.getType();
if (type.getLabelClass() != LabelClass.OUTPUT) {
continue;
}
ImmutableList.Builder artifactsBuilder = ImmutableList.builder();
for (OutputFile outputFile : ruleContext.getRule().getOutputFileMap().get(attrName)) {
Artifact artifact = ruleContext.createOutputArtifact(outputFile);
artifactsBuilder.add(artifact);
artifactLabelMapBuilder.put(artifact, outputFile.getLabel());
}
ImmutableList artifacts = artifactsBuilder.build();
if (type == BuildType.OUTPUT) {
if (artifacts.size() == 1) {
outputs.addOutput(attrName, Iterables.getOnlyElement(artifacts));
} else {
outputs.addOutput(attrName, Runtime.NONE);
}
} else if (type == BuildType.OUTPUT_LIST) {
outputs.addOutput(attrName, SkylarkList.createImmutable(artifacts));
} else {
throw new IllegalArgumentException(
"Type of " + attrName + "(" + type + ") is not output type ");
}
}
this.artifactsLabelMap = artifactLabelMapBuilder.build();
this.outputsObject = outputs;
SkylarkAttributesCollection.Builder builder = SkylarkAttributesCollection.builder(this);
for (Attribute attribute : ruleContext.getRule().getAttributes()) {
Object value = ruleContext.attributes().get(attribute.getName(), attribute.getType());
builder.addAttribute(attribute, value);
}
this.attributesCollection = builder.build();
this.splitAttributes = buildSplitAttributeInfo(attributes, ruleContext);
this.ruleAttributesCollection = null;
} else { // ASPECT
this.isForAspect = true;
this.artifactsLabelMap = ImmutableMap.of();
this.outputsObject = null;
ImmutableCollection attributes =
ruleContext.getMainAspect().getDefinition().getAttributes().values();
SkylarkAttributesCollection.Builder aspectBuilder = SkylarkAttributesCollection.builder(this);
for (Attribute attribute : attributes) {
aspectBuilder.addAttribute(attribute, attribute.getDefaultValue(null));
}
this.attributesCollection = aspectBuilder.build();
this.splitAttributes = null;
SkylarkAttributesCollection.Builder ruleBuilder = SkylarkAttributesCollection.builder(this);
for (Attribute attribute : ruleContext.getRule().getAttributes()) {
Object value = ruleContext.attributes().get(attribute.getName(), attribute.getType());
ruleBuilder.addAttribute(attribute, value);
}
for (Aspect aspect : ruleContext.getAspects()) {
if (aspect.equals(ruleContext.getMainAspect())) {
// Aspect's own attributes are in attributesCollection.
continue;
}
for (Attribute attribute : aspect.getDefinition().getAttributes().values()) {
ruleBuilder.addAttribute(attribute, attribute.getDefaultValue(null));
}
}
this.ruleAttributesCollection = ruleBuilder.build();
}
makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables();
}
/**
* Represents `ctx.outputs`.
*
*
A {@link ClassObject} (struct-like data structure) with "executable" field created
* lazily on-demand.
*
*
Note: There is only one {@code Outputs} object per rule context, so default
* (object identity) equals and hashCode suffice.
*/
private static class Outputs implements ClassObject, SkylarkValue {
private final Map outputs;
private final SkylarkRuleContext context;
private boolean executableCreated = false;
public Outputs(SkylarkRuleContext context) {
this.outputs = new LinkedHashMap<>();
this.context = context;
}
private void addOutput(String key, Object value)
throws EvalException {
Preconditions.checkState(!context.isImmutable(),
"Cannot add outputs to immutable Outputs object");
if (outputs.containsKey(key)
|| (context.isExecutable() && EXECUTABLE_OUTPUT_NAME.equals(key))) {
throw new EvalException(null, "Multiple outputs with the same key: " + key);
}
outputs.put(key, value);
}
@Override
public boolean isImmutable() {
return context.isImmutable();
}
@Override
public ImmutableCollection getFieldNames() throws EvalException {
checkMutable();
ImmutableList.Builder result = ImmutableList.builder();
if (context.isExecutable() && executableCreated) {
result.add(EXECUTABLE_OUTPUT_NAME);
}
result.addAll(outputs.keySet());
return result.build();
}
@Nullable
@Override
public Object getValue(String name) throws EvalException {
checkMutable();
if (context.isExecutable() && EXECUTABLE_OUTPUT_NAME.equals(name)) {
executableCreated = true;
// createOutputArtifact() will cache the created artifact.
return context.getRuleContext().createOutputArtifact();
}
return outputs.get(name);
}
@Nullable
@Override
public String getErrorMessageForUnknownField(String name) {
return String.format(
"No attribute '%s' in outputs. Make sure you declared a rule output with this name.",
name);
}
@Override
public void repr(SkylarkPrinter printer) {
if (isImmutable()) {
printer.append("ctx.outputs(for ");
printer.append(context.ruleLabelCanonicalName);
printer.append(")");
return;
}
boolean first = true;
printer.append("ctx.outputs(");
// Sort by field name to ensure deterministic output.
try {
for (String field : Ordering.natural().sortedCopy(getFieldNames())) {
if (!first) {
printer.append(", ");
}
first = false;
printer.append(field);
printer.append(" = ");
printer.repr(getValue(field));
}
printer.append(")");
} catch (EvalException e) {
throw new AssertionError("mutable ctx.outputs should not throw", e);
}
}
private void checkMutable() throws EvalException {
if (isImmutable()) {
throw new EvalException(
null,
String.format(
"cannot access outputs of rule '%s' outside of its own "
+ "rule implementation function",
context.ruleLabelCanonicalName));
}
}
}
public boolean isExecutable() {
return ruleContext.getRule().getRuleClassObject().isExecutableSkylark();
}
public boolean isDefaultExecutableCreated() {
return this.outputsObject.executableCreated;
}
/**
* Nullifies fields of the object when it's not supposed to be used anymore to free unused memory
* and to make sure this object is not accessed when it's not supposed to (after the corresponding
* rule implementation function has exited).
*/
public void nullify() {
actionFactory.nullify();
ruleContext = null;
fragments = null;
hostFragments = null;
aspectDescriptor = null;
makeVariables = null;
attributesCollection = null;
ruleAttributesCollection = null;
splitAttributes = null;
artifactsLabelMap = null;
outputsObject = null;
}
public void checkMutable(String attrName) throws EvalException {
if (isImmutable()) {
throw new EvalException(null, String.format(
"cannot access field or method '%s' of rule context for '%s' outside of its own rule "
+ "implementation function", attrName, ruleLabelCanonicalName));
}
}
@Nullable
public AspectDescriptor getAspectDescriptor() {
return aspectDescriptor;
}
public String getRuleLabelCanonicalName() {
return ruleLabelCanonicalName;
}
private static Info buildSplitAttributeInfo(
Collection attributes, RuleContext ruleContext) {
ImmutableMap.Builder splitAttrInfos = ImmutableMap.builder();
for (Attribute attr : attributes) {
if (attr.hasSplitConfigurationTransition()) {
Map, ? extends List extends TransitiveInfoCollection>> splitPrereqs =
ruleContext.getSplitPrerequisites(attr.getName());
Map