| // Copyright 2023 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.auto.value.AutoValue; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.packages.Rule.RuleData; | |
| import com.google.devtools.build.lib.skyframe.serialization.AsyncDeserializationContext; | |
| import com.google.devtools.build.lib.skyframe.serialization.DeferredObjectCodec; | |
| import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; | |
| import com.google.devtools.build.lib.skyframe.serialization.SerializationException; | |
| import com.google.protobuf.CodedInputStream; | |
| import com.google.protobuf.CodedOutputStream; | |
| import java.io.IOException; | |
| import net.starlark.java.syntax.Location; | |
| /** | |
| * A codec for {@link RuleData}. | |
| * | |
| * <p>For native rules, serializes {@link RuleClassData} by name using a {@link RuleClassProvider} | |
| * dependency to look up the {@link RuleClass} on deserialization. | |
| * | |
| * <p>For Starlark rules, {@link RuleClassData} is reduced to {@link | |
| * RuleDataCodec.StarlarkRuleClassData}. | |
| */ | |
| final class RuleDataCodec extends DeferredObjectCodec<RuleData> { | |
| private static final byte RULE_CLASS_IS_STARLARK = 0b0000_0001; | |
| private static final byte RULE_TAGS_MASK = 0b0000_0010; | |
| private static final byte DEPRECATION_WARNING_MASK = 0b0000_0100; | |
| private static final byte IS_TEST_ONLY_MASK = 0b0000_1000; | |
| private static final byte TEST_TIMEOUT_MASK = 0b0001_0000; | |
| @Override | |
| public Class<RuleData> getEncodedClass() { | |
| return RuleData.class; | |
| } | |
| @Override | |
| public void serialize(SerializationContext context, RuleData obj, CodedOutputStream codedOut) | |
| throws SerializationException, IOException { | |
| // There are quite a few fields here that are often null or empty. Writing the mask makes it so | |
| // that nulls and empty sets take 0 additional storage. | |
| byte presenceMask = 0; | |
| RuleClassData ruleClassData = obj.getRuleClassData(); | |
| if (ruleClassData.isStarlark()) { | |
| presenceMask |= RULE_CLASS_IS_STARLARK; | |
| } | |
| ImmutableSet<String> ruleTags = obj.getRuleTags(); | |
| if (!ruleTags.isEmpty()) { | |
| presenceMask |= RULE_TAGS_MASK; | |
| } | |
| String deprecationWarning = obj.getDeprecationWarning(); | |
| if (deprecationWarning != null) { | |
| presenceMask |= DEPRECATION_WARNING_MASK; | |
| } | |
| if (obj.isTestOnly()) { | |
| presenceMask |= IS_TEST_ONLY_MASK; | |
| } | |
| TestTimeout testTimeout = obj.getTestTimeout(); | |
| if (testTimeout != null) { | |
| presenceMask |= TEST_TIMEOUT_MASK; | |
| } | |
| codedOut.writeRawByte(presenceMask); | |
| serializeRuleClassData(context, ruleClassData, codedOut); | |
| context.serialize(obj.getLocation(), codedOut); | |
| context.serialize(obj.getLabel(), codedOut); | |
| if (!ruleTags.isEmpty()) { | |
| context.serialize(ruleTags, codedOut); | |
| } | |
| if (deprecationWarning != null) { | |
| context.serialize(deprecationWarning, codedOut); | |
| } | |
| if (testTimeout != null) { | |
| context.serialize(testTimeout, codedOut); | |
| } | |
| context.serialize(obj.getOnlyTagsAttribute(), codedOut); | |
| } | |
| @Override | |
| public DeferredValue<RuleData> deserializeDeferred( | |
| AsyncDeserializationContext context, CodedInputStream codedIn) | |
| throws SerializationException, IOException { | |
| byte presenceMask = codedIn.readRawByte(); | |
| Builder builder; | |
| if ((presenceMask & RULE_CLASS_IS_STARLARK) != 0) { | |
| StarlarkRuleClassBuilder starlarkBuilder = new StarlarkRuleClassBuilder(presenceMask); | |
| context.deserialize(codedIn, starlarkBuilder, StarlarkRuleClassBuilder::setRuleClassData); | |
| builder = starlarkBuilder; | |
| } else { | |
| NativeRuleClassBuilder nativeBuilder = | |
| new NativeRuleClassBuilder( | |
| presenceMask, context.getDependency(RuleClassProvider.class).getRuleClassMap()); | |
| context.deserialize(codedIn, nativeBuilder, NativeRuleClassBuilder::setRuleClassName); | |
| builder = nativeBuilder; | |
| } | |
| context.deserialize(codedIn, builder, Builder::setLocation); | |
| context.deserialize(codedIn, builder, Builder::setLabel); | |
| if ((presenceMask & RULE_TAGS_MASK) != 0) { | |
| context.deserialize(codedIn, builder, Builder::setRuleTags); | |
| } else { | |
| builder.ruleTags = ImmutableSet.of(); | |
| } | |
| if ((presenceMask & DEPRECATION_WARNING_MASK) != 0) { | |
| context.deserialize(codedIn, builder, Builder::setDeprecationWarning); | |
| } | |
| if ((presenceMask & TEST_TIMEOUT_MASK) != 0) { | |
| context.deserialize(codedIn, builder, Builder::setTestTimeout); | |
| } | |
| context.deserialize(codedIn, builder, Builder::setOnlyTagsAttribute); | |
| return builder; | |
| } | |
| /** | |
| * Builder for deserialized {@link RuleData}. | |
| * | |
| * <p>This is abstract due to the differences in deserialization of {@link RuleClassData} for | |
| * Starlark and native. | |
| * | |
| * <ul> | |
| * <li>The {@link NativeRuleClassBuilder} uses the {@link RuleClassProvider} serialization | |
| * dependency to deserialize a rule class from its name alone. | |
| * <li>The {@link StarlarkRuleClassBuilder} directly deserializes the {@link | |
| * StarlarkRuleClassData} object. | |
| * </ul> | |
| */ | |
| private abstract static class Builder implements DeferredValue<RuleData> { | |
| private final byte presenceMask; | |
| private Location location; | |
| private Label label; | |
| private ImmutableSet<String> ruleTags; | |
| private String deprecationWarning; | |
| private TestTimeout testTimeout; | |
| private ImmutableList<String> onlyTagsAttribute; | |
| Builder(byte presenceMask) { | |
| this.presenceMask = presenceMask; | |
| } | |
| @Override | |
| public RuleData call() { | |
| return new RuleData( | |
| getRuleClassData(), | |
| location, | |
| ruleTags, | |
| label, | |
| deprecationWarning, | |
| (presenceMask & IS_TEST_ONLY_MASK) != 0, | |
| testTimeout, | |
| onlyTagsAttribute); | |
| } | |
| abstract RuleClassData getRuleClassData(); | |
| private static void setLocation(Builder builder, Object value) { | |
| builder.location = (Location) value; | |
| } | |
| private static void setLabel(Builder builder, Object value) { | |
| builder.label = (Label) value; | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static void setRuleTags(Builder builder, Object value) { | |
| builder.ruleTags = (ImmutableSet<String>) value; | |
| } | |
| private static void setDeprecationWarning(Builder builder, Object value) { | |
| builder.deprecationWarning = (String) value; | |
| } | |
| private static void setTestTimeout(Builder builder, Object value) { | |
| builder.testTimeout = (TestTimeout) value; | |
| } | |
| @SuppressWarnings("unchecked") // parameter type for deserialized object | |
| private static void setOnlyTagsAttribute(Builder builder, Object value) { | |
| builder.onlyTagsAttribute = (ImmutableList<String>) value; | |
| } | |
| } | |
| private static class NativeRuleClassBuilder extends Builder { | |
| private final ImmutableMap<String, RuleClass> ruleClassMap; | |
| private String ruleClassName; | |
| private NativeRuleClassBuilder( | |
| byte presenceMask, ImmutableMap<String, RuleClass> ruleClassMap) { | |
| super(presenceMask); | |
| this.ruleClassMap = ruleClassMap; | |
| } | |
| @Override | |
| RuleClassData getRuleClassData() { | |
| return ruleClassMap.get(ruleClassName); | |
| } | |
| private static void setRuleClassName(NativeRuleClassBuilder builder, Object value) { | |
| builder.ruleClassName = (String) value; | |
| } | |
| } | |
| private static class StarlarkRuleClassBuilder extends Builder { | |
| private StarlarkRuleClassData ruleClassData; | |
| private StarlarkRuleClassBuilder(byte presenceMask) { | |
| super(presenceMask); | |
| } | |
| @Override | |
| StarlarkRuleClassData getRuleClassData() { | |
| return ruleClassData; | |
| } | |
| private static void setRuleClassData(StarlarkRuleClassBuilder builder, Object value) { | |
| builder.ruleClassData = (StarlarkRuleClassData) value; | |
| } | |
| } | |
| private static void serializeRuleClassData( | |
| SerializationContext context, RuleClassData obj, CodedOutputStream codedOut) | |
| throws SerializationException, IOException { | |
| if (!obj.isStarlark()) { | |
| context.serialize(obj.getName(), codedOut); | |
| return; | |
| } | |
| // Handles the case of previously serialized Starlark rule data. | |
| if (obj instanceof StarlarkRuleClassData) { | |
| context.serialize(obj, codedOut); | |
| return; | |
| } | |
| // Serializes rule data for Starlark. | |
| context.serialize( | |
| new AutoValue_RuleDataCodec_StarlarkRuleClassData( | |
| obj.getName(), | |
| obj.getTargetKind(), | |
| obj.isDependencyResolutionRule(), | |
| obj.isMaterializerRule(), | |
| obj.materializerRuleAllowsRealDeps(), | |
| obj.getAdvertisedProviders(), | |
| obj.getRuleDefinitionEnvironmentLabel()), | |
| codedOut); | |
| } | |
| // TODO(b/297857068): to reduce possible value aliasing (which could happen when an instance of | |
| // this class co-resides on the same JVM as the actual Starlark RuleClass instance), use a .bzl | |
| // Starlark reference instead. | |
| @AutoValue | |
| abstract static class StarlarkRuleClassData implements RuleClassData { | |
| @Override | |
| public final boolean isStarlark() { | |
| return true; | |
| } | |
| } | |
| } |