blob: 0a3e1406e5338ea5912eb91b3428533c0206a8d7 [file] [log] [blame]
// Copyright 2015 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.packages;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.devtools.build.lib.packages.Attribute.ComputedDefault;
import com.google.devtools.build.lib.query2.proto.proto2api.Build;
import javax.annotation.Nullable;
/** Serialize a {@link Rule} as its protobuf representation. */
public class RuleFormatter {
// Starlark doesn't support defining rule classes with ComputedDefault attributes. Therefore, the
// only ComputedDefault attributes we expect to see for Starlark-defined rule classes are
// those declared in those rule classes' natively defined base rule classes, which are:
//
// 1. The "timeout" attribute in SkylarkRuleClassFunctions.testBaseRule
// 2. The "deprecation" attribute in BaseRuleClasses.commonCoreAndSkylarkAttributes
// 3. The "testonly" attribute in BaseRuleClasses.commonCoreAndSkylarkAttributes
private static final ImmutableSet<String> SKYLARK_RULE_CLASS_COMPUTED_DEFAULT_ATTRIBUTES =
ImmutableSet.of("timeout", "deprecation", "testonly");
public static Build.Rule.Builder serializeRule(Rule rule) {
Build.Rule.Builder builder = Build.Rule.newBuilder();
builder.setName(rule.getLabel().getName());
builder.setRuleClass(rule.getRuleClass());
builder.setPublicByDefault(rule.getRuleClassObject().isPublicByDefault());
RawAttributeMapper rawAttributeMapper = RawAttributeMapper.of(rule);
boolean isSkylark = rule.getRuleClassObject().isStarlark();
if (isSkylark) {
builder.setSkylarkEnvironmentHashCode(
// hexify
BaseEncoding.base16()
.lowerCase()
.encode(
Preconditions.checkNotNull(
rule.getRuleClassObject().getRuleDefinitionEnvironmentDigest(), rule)));
}
for (Attribute attr : rule.getAttributes()) {
Object rawAttributeValue = rawAttributeMapper.getRawAttributeValue(rule, attr);
boolean isExplicit = rule.isAttributeValueExplicitlySpecified(attr);
if (!isSkylark && !isExplicit) {
// If the rule class is native (i.e. not Starlark-defined), then we can skip serialization
// of implicit attribute values. The native rule class can provide the same default value
// for the attribute after deserialization.
continue;
}
Object valueToSerialize;
if (isExplicit) {
valueToSerialize = rawAttributeValue;
} else if (rawAttributeValue instanceof ComputedDefault) {
// If the rule class is Starlark-defined (i.e. rule.getRuleClassObject().isSkylark() is
// true), and the attribute has a ComputedDefault value, then we must serialize what it
// evaluates to. The Starlark-defined ComputedDefault function won't be available after
// deserialization due to Starlark's non-serializability.
valueToSerialize = evaluateSkylarkComputedDefault(rawAttributeMapper, attr);
if (valueToSerialize == null) {
continue;
}
} else {
// If the rule class is Starlark-defined and the attribute value is implicit, then we
// must serialize it. The Starlark-defined rule class won't be available after
// deserialization due to Starlark's non-serializability.
valueToSerialize = rawAttributeValue;
}
builder.addAttribute(
AttributeFormatter.getAttributeProto(
attr,
valueToSerialize,
isExplicit,
/*encodeBooleanAndTriStateAsIntegerAndString=*/ false));
}
return builder;
}
/**
* Evaluates a {@link ComputedDefault} attribute value for a {@link Rule} with a Starlark-defined
* {@link RuleClass}.
*
* <p>We can't serialize ComputedDefault attributes defined in Starlark, because those can depend
* on other attributes which are configurable.
*
* <p>For a few attributes ({@link #SKYLARK_RULE_CLASS_COMPUTED_DEFAULT_ATTRIBUTES}), we know for
* certain that they don't have dependencies on other attributes which are configurable, so they
* can be evaluated here without loss of fidelity.
*
* <p>The {@link RawAttributeMapper#get} method, inherited from {@link AbstractAttributeMapper},
* evaluates the {@link ComputedDefault} function, so we use that, after verifying the attribute's
* name is expected.
*
* @return the attribute's default value if we know it can be safely serialized, or null
* otherwise.
*/
@Nullable
private static Object evaluateSkylarkComputedDefault(
RawAttributeMapper rawAttributeMapper, Attribute attr) {
if (SKYLARK_RULE_CLASS_COMPUTED_DEFAULT_ATTRIBUTES.contains(attr.getName())) {
return rawAttributeMapper.get(attr.getName(), attr.getType());
}
return null;
}
}