blob: b9601be57ff8b9aaa24f85987fade089072b8623 [file] [log] [blame]
// Copyright 2021 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.bazel.bzlmod;
import com.google.common.collect.ImmutableCollection;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.LabelConverter;
import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
import java.util.Map;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.Structure;
import net.starlark.java.spelling.SpellChecker;
import net.starlark.java.syntax.Location;
/**
* A {@link Tag} whose attribute values have been type-checked against the attribute schema define
* in the {@link TagClass}.
*/
@StarlarkBuiltin(name = "bazel_module_tag", documented = false)
public class TypeCheckedTag implements Structure {
private final TagClass tagClass;
private final Object[] attrValues;
private final boolean devDependency;
// The properties below are only used for error reporting.
private final Location location;
private final String tagClassName;
private TypeCheckedTag(
TagClass tagClass,
Object[] attrValues,
boolean devDependency,
Location location,
String tagClassName) {
this.tagClass = tagClass;
this.attrValues = attrValues;
this.devDependency = devDependency;
this.location = location;
this.tagClassName = tagClassName;
}
/** Creates a {@link TypeCheckedTag}. */
public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter labelConverter)
throws ExternalDepsException {
Object[] attrValues = new Object[tagClass.getAttributes().size()];
for (Map.Entry<String, Object> attrValue : tag.getAttributeValues().attributes().entrySet()) {
Integer attrIndex = tagClass.getAttributeIndices().get(attrValue.getKey());
if (attrIndex == null) {
throw ExternalDepsException.withMessage(
Code.BAD_MODULE,
"in tag at %s, unknown attribute %s provided%s",
tag.getLocation(),
attrValue.getKey(),
SpellChecker.didYouMean(attrValue.getKey(), tagClass.getAttributeIndices().keySet()));
}
Attribute attr = tagClass.getAttributes().get(attrIndex);
Object nativeValue;
try {
nativeValue =
attr.getType().convert(attrValue.getValue(), attr.getPublicName(), labelConverter);
} catch (ConversionException e) {
throw ExternalDepsException.withCauseAndMessage(
Code.BAD_MODULE,
e,
"in tag at %s, error converting value for attribute %s",
tag.getLocation(),
attr.getPublicName());
}
// Check that the value is actually allowed.
if (attr.checkAllowedValues() && !attr.getAllowedValues().apply(nativeValue)) {
throw ExternalDepsException.withMessage(
Code.BAD_MODULE,
"in tag at %s, the value for attribute %s %s",
tag.getLocation(),
attr.getPublicName(),
attr.getAllowedValues().getErrorReason(nativeValue));
}
attrValues[attrIndex] = Attribute.valueToStarlark(nativeValue);
}
// Check that all mandatory attributes have been specified, and fill in default values.
for (int i = 0; i < attrValues.length; i++) {
Attribute attr = tagClass.getAttributes().get(i);
if (attr.isMandatory() && attrValues[i] == null) {
throw ExternalDepsException.withMessage(
Code.BAD_MODULE,
"in tag at %s, mandatory attribute %s isn't being specified",
tag.getLocation(),
attr.getPublicName());
}
if (attrValues[i] == null) {
attrValues[i] = Attribute.valueToStarlark(attr.getDefaultValueUnchecked());
}
}
return new TypeCheckedTag(
tagClass, attrValues, tag.isDevDependency(), tag.getLocation(), tag.getTagName());
}
/**
* Whether the tag was specified on an extension proxy created with <code>dev_dependency=True
* </code>.
*/
public boolean isDevDependency() {
return devDependency;
}
@Override
public boolean isImmutable() {
return true;
}
@Nullable
@Override
public Object getValue(String name) throws EvalException {
Integer attrIndex = tagClass.getAttributeIndices().get(name);
if (attrIndex == null) {
return null;
}
return attrValues[attrIndex];
}
@Override
public ImmutableCollection<String> getFieldNames() {
return tagClass.getAttributeIndices().keySet();
}
@Nullable
@Override
public String getErrorMessageForUnknownField(String field) {
return "unknown attribute " + field;
}
@Override
public void debugPrint(Printer printer, StarlarkSemantics semantics) {
printer.append(String.format("'%s' tag at %s", tagClassName, location));
}
}