blob: c6cfd9cc7e6bb1e24ee537f11fa287cd0d513423 [file] [log] [blame]
// Copyright 2020 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.runtime.commands.info;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.ProtoUtils;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleFunction;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.AllowedRuleClassInfo;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.AttributeDefinition;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.AttributeValue;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.BuildLanguage;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.RuleDefinition;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.InfoItem;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.skyframe.StarlarkBuiltinsValue;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import net.starlark.java.eval.StarlarkFunction;
import net.starlark.java.eval.StarlarkInt;
/**
* Info item for the build language. It is deprecated, it still works, when explicitly requested,
* but are not shown by default. It prints multi-line messages and thus don't play well with grep.
* We don't print them unless explicitly requested.
*/
@Deprecated
public final class BuildLanguageInfoItem extends InfoItem {
public BuildLanguageInfoItem() {
super("build-language", "A protobuffer with the build language structure", true);
}
@Override
public boolean needsSyncPackageLoading() {
// Requires CommandEnvironment.syncPackageLoading to be called in order to initialize the
// skyframe executor.
return true;
}
@Override
public byte[] get(Supplier<BuildConfigurationValue> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
StarlarkBuiltinsValue builtins = loadStarlarkBuiltins(env);
return print(getBuildLanguageDefinition(getRuleClasses(builtins, env)));
}
private StarlarkBuiltinsValue loadStarlarkBuiltins(CommandEnvironment env)
throws AbruptExitException {
EvaluationResult<SkyValue> result =
env.getSkyframeExecutor()
.evaluateSkyKeys(
env.getReporter(),
ImmutableList.of(StarlarkBuiltinsValue.key()),
/* keepGoing= */ false);
if (result.hasError()) {
throw new AbruptExitException(
DetailedExitCode.of(
FailureDetails.FailureDetail.newBuilder()
.setMessage("Failed to load Starlark builtins")
.setInfoCommand(FailureDetails.InfoCommand.getDefaultInstance())
.build()));
}
return (StarlarkBuiltinsValue) result.get(StarlarkBuiltinsValue.key());
}
private ImmutableList<RuleClass> getRuleClasses(
StarlarkBuiltinsValue builtins, CommandEnvironment env) {
ImmutableMap<String, RuleClass> nativeRuleClasses =
env.getRuntime().getRuleClassProvider().getRuleClassMap();
// The conditional for selecting whether or not to load symbols from @_builtins is the same as
// in PackageFunction.compileBuildFile
if (builtins
.starlarkSemantics
.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_BZL_PATH)
.isEmpty()) {
return ImmutableList.sortedCopyOf(
Comparator.comparing(RuleClass::getName), nativeRuleClasses.values());
} else {
ImmutableList.Builder<RuleClass> ruleClasses = ImmutableList.builder();
for (Map.Entry<String, Object> entry : builtins.predeclaredForBuild.entrySet()) {
if (entry.getValue() instanceof RuleFunction) {
ruleClasses.add(((RuleFunction) entry.getValue()).getRuleClass());
} else if (entry.getValue() instanceof StarlarkFunction) {
if (nativeRuleClasses.containsKey(entry.getKey())) {
// entry.getValue() is a Starlark macro in @_builtins overriding a native rule. We
// cannot extract the macro's metadata (other than by, perhaps, parsing its Starlark
// docstring via starlark_doc_extract, but that does not have sufficient fidelity to
// get rule attribute metadata), so we extract it from the legacy rule instead.
// Note that we *cannot* rely on StarlarkFunction.getName() because under which the
// macro is defined may not match the name under which @_builtins exports it.
ruleClasses.add(nativeRuleClasses.get(entry.getKey()));
}
}
}
return ImmutableList.sortedCopyOf(
Comparator.comparing(RuleClass::getName), ruleClasses.build());
}
}
/**
* Returns a byte array containing a proto-buffer describing the build language.
*
* @param ruleClasses a sorted list of rule classes
*/
private static byte[] getBuildLanguageDefinition(ImmutableList<RuleClass> ruleClasses) {
BuildLanguage.Builder resultPb = BuildLanguage.newBuilder();
for (RuleClass ruleClass : ruleClasses) {
if (isAbstractRule(ruleClass)) {
continue;
}
RuleDefinition.Builder rulePb = RuleDefinition.newBuilder();
rulePb.setName(ruleClass.getName());
ImmutableList<Attribute> sortedAttributeDefinitions =
ImmutableList.sortedCopyOf(
Comparator.comparing(Attribute::getName), ruleClass.getAttributes());
for (Attribute attr : sortedAttributeDefinitions) {
Type<?> t = attr.getType();
AttributeDefinition.Builder attrPb = AttributeDefinition.newBuilder();
attrPb.setName(attr.getName());
attrPb.setType(ProtoUtils.getDiscriminatorFromType(t));
attrPb.setMandatory(attr.isMandatory());
attrPb.setAllowEmpty(!attr.isNonEmpty());
attrPb.setAllowSingleFile(attr.isSingleArtifact());
attrPb.setConfigurable(attr.isConfigurable());
// Encode default value, if simple.
Object v = attr.getDefaultValueUnchecked();
if (!(v == null
|| v instanceof Attribute.ComputedDefault
|| v instanceof StarlarkComputedDefaultTemplate
|| v instanceof Attribute.LateBoundDefault
|| v == t.getDefaultValue())) {
attrPb.setDefault(convertAttrValue(t, v));
}
attrPb.setExecutable(attr.isExecutable());
if (BuildType.isLabelType(t)) {
attrPb.setAllowedRuleClasses(getAllowedRuleClasses(ruleClasses, attr));
attrPb.setNodep(t.getLabelClass() == Type.LabelClass.NONDEP_REFERENCE);
}
rulePb.addAttribute(attrPb);
}
resultPb.addRule(rulePb);
}
return resultPb.build().toByteArray();
}
// convertAttrValue converts attribute value v of type to t an AttributeValue message.
private static AttributeValue convertAttrValue(Type<?> t, Object v) {
AttributeValue.Builder b = AttributeValue.newBuilder();
if (v instanceof Map) {
Type.DictType<?, ?> dictType = (Type.DictType<?, ?>) t;
for (Map.Entry<?, ?> entry : ((Map<?, ?>) v).entrySet()) {
b.addDictBuilder()
.setKey(entry.getKey().toString())
.setValue(convertAttrValue(dictType.getValueType(), entry.getValue()));
}
} else if (v instanceof List) {
for (Object elem : (List<?>) v) {
b.addList(convertAttrValue(t.getListElementType(), elem));
}
} else if (t == BuildType.LICENSE) {
// TODO(adonovan): need dual function of parseLicense.
// Treat as empty list for now.
} else if (t == BuildType.DISTRIBUTIONS) {
// TODO(adonovan): need dual function of parseDistributions.
// Treat as empty list for now.
} else if (t == Type.STRING) {
b.setString((String) v);
} else if (t == Type.INTEGER) {
b.setInt(((StarlarkInt) v).toIntUnchecked());
} else if (t == Type.BOOLEAN) {
b.setBool((Boolean) v);
} else if (t == BuildType.TRISTATE) {
b.setInt(((TriState) v).toInt());
} else if (BuildType.isLabelType(t)) { // case order matters!
b.setString(v.toString());
} else {
// No native rule attribute of this type (FilesetEntry?) has a default value.
throw new IllegalStateException("unexpected type of attribute default value: " + t);
}
return b.build();
}
private static AllowedRuleClassInfo getAllowedRuleClasses(
Collection<RuleClass> ruleClasses, Attribute attr) {
AllowedRuleClassInfo.Builder info = AllowedRuleClassInfo.newBuilder();
info.setPolicy(AllowedRuleClassInfo.AllowedRuleClasses.ANY);
Predicate<RuleClass> filter;
if (attr.isStrictLabelCheckingEnabled()
&& (filter = attr.getAllowedRuleClassObjectPredicate())
!= Predicates.<RuleClass>alwaysTrue()) {
info.setPolicy(AllowedRuleClassInfo.AllowedRuleClasses.SPECIFIED);
for (RuleClass otherClass : Iterables.filter(ruleClasses, filter)) {
if (!isAbstractRule(otherClass)) {
info.addAllowedRuleClass(otherClass.getName());
}
}
}
return info.build();
}
private static boolean isAbstractRule(RuleClass c) {
return c.getName().startsWith("$");
}
}