blob: 88715d70ff50500d7892ce2792b3450799e4fca0 [file] [log] [blame]
// 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.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);
}
}
@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);
}
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;
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);
}
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;
}
}
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.getAdvertisedProviders()),
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;
}
}
}