// 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.collect.ImmutableSet; import com.google.devtools.build.lib.packages.Attribute.ComputedDefault; import com.google.devtools.build.lib.query2.proto.proto2api.Build; import com.google.devtools.build.lib.util.Preconditions; /** Serialize a {@link Rule} as its protobuf representation. */ public class RuleFormatter { // Skylark doesn't support defining rule classes with ComputedDefault attributes. Therefore, the // only ComputedDefault attributes we expect to see for Skylark-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().isSkylark(); if (isSkylark) { builder.setSkylarkEnvironmentHashCode( Preconditions.checkNotNull( rule.getRuleClassObject() .getRuleDefinitionEnvironment() .getTransitiveContentHashCode(), 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 Skylark-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 Skylark-defined (i.e. rule.getRuleClassObject().isSkylark() is // true), and the attribute has a ComputedDefault value, then we must serialize what it // evaluates to. The Skylark-defined ComputedDefault function won't be available after // deserialization due to Skylark's non-serializability. valueToSerialize = evaluateSkylarkComputedDefault(rule, rawAttributeMapper, attr); } else { // If the rule class is Skylark-defined and the attribute value is implicit, then we // must serialize it. The Skylark-defined rule class won't be available after // deserialization due to Skylark'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 * Skylark-defined {@link RuleClass}. * * <p>Fortunately (from the perspective of rule serialization), Skylark doesn't support defining * rule classes with {@link ComputedDefault} attributes, and so the only {@link * ComputedDefault} attributes we need to worry about for Skylark-defined rule classes are * declared in those rule classes' natively-defined base rule classes. * * <p>See the comment for {@link #SKYLARK_RULE_CLASS_COMPUTED_DEFAULT_ATTRIBUTES} for the * locations of these expected attributes. None of them 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. */ private static Object evaluateSkylarkComputedDefault( Rule rule, RawAttributeMapper rawAttributeMapper, Attribute attr) { Preconditions.checkState( SKYLARK_RULE_CLASS_COMPUTED_DEFAULT_ATTRIBUTES.contains(attr.getName()), "Unexpected ComputedDefault value for %s in %s", attr, rule); return rawAttributeMapper.get(attr.getName(), attr.getType()); } }