For native rule classes, (de)serialize only explicit attrs
Native rule classes can provide default values for rules after they're
deserialized, so there isn't a need to serialize those default values.
This doesn't apply yet to rules with Skylark-defined rule classes, due
to the non-serializablity of Skylark rule classes.
--
MOS_MIGRATED_REVID=112066930
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 1b07ae0..8e00db4 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -36,6 +36,7 @@
import com.google.devtools.build.lib.packages.BuildType.SelectorList;
import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy.MissingFragmentPolicy;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.packages.RuleFactory.AttributeValuesMap;
import com.google.devtools.build.lib.syntax.Argument;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.Environment;
@@ -45,6 +46,7 @@
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.syntax.Type.ConversionException;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -1273,104 +1275,189 @@
}
/**
- * Helper function for {@link RuleFactory#createAndAddRule}.
+ * Creates a new {@link Rule} {@code r} where {@code r.getPackage()} is the {@link Package}
+ * associated with {@code pkgBuilder}.
+ *
+ * <p>The created {@link Rule} will be populated with attribute values from {@code
+ * attributeValues} or the default attribute values associated with this {@link RuleClass} and
+ * {@code pkgBuilder}.
+ *
+ * <p>The created {@link Rule} will also be populated with output files. These output files
+ * will have been collected from the explicitly provided values of type {@link BuildType#OUTPUT}
+ * and {@link BuildType#OUTPUT_LIST} as well as from the implicit outputs determined by this
+ * {@link RuleClass} and the values in {@code attributeValues}.
+ *
+ * <p>This performs several validity checks. Invalid output file labels result in a thrown {@link
+ * LabelSyntaxException}. All other errors are reported on {@code eventHandler}.
*/
- Rule createRuleWithLabel(Package.Builder pkgBuilder, Label ruleLabel,
- Map<String, Object> attributeValues, EventHandler eventHandler, FuncallExpression ast,
- Location location) throws LabelSyntaxException, InterruptedException {
- Rule rule = pkgBuilder.newRuleWithLabel(ruleLabel, this, location);
- createRuleCommon(rule, pkgBuilder, attributeValues, eventHandler, ast);
- return rule;
- }
-
- private void createRuleCommon(Rule rule, Package.Builder pkgBuilder,
- Map<String, Object> attributeValues, EventHandler eventHandler, FuncallExpression ast)
+ Rule createRule(
+ Package.Builder pkgBuilder,
+ Label ruleLabel,
+ AttributeValuesMap attributeValues,
+ EventHandler eventHandler,
+ @Nullable FuncallExpression ast,
+ Location location,
+ AttributeContainer attributeContainer)
throws LabelSyntaxException, InterruptedException {
- populateRuleAttributeValues(
- rule, pkgBuilder, attributeValues, eventHandler, ast);
+ Rule rule = pkgBuilder.createRule(ruleLabel, this, location, attributeContainer);
+ populateRuleAttributeValues(rule, pkgBuilder, attributeValues, eventHandler);
rule.populateOutputFiles(eventHandler, pkgBuilder);
- rule.checkForNullLabels();
- rule.checkValidityPredicate(eventHandler);
- }
-
- /**
- * Populates the attributes table of new rule "rule" from the
- * "attributeValues" mapping from attribute names to values in the build
- * language. Errors are reported on "reporter". "ast" is used to associate
- * location information with each rule attribute.
- */
- private void populateRuleAttributeValues(Rule rule,
- Package.Builder pkgBuilder,
- Map<String, Object> attributeValues,
- EventHandler eventHandler,
- FuncallExpression ast)
- throws InterruptedException {
- BitSet definedAttrs = new BitSet(); // set of attr indices
-
- for (Map.Entry<String, Object> entry : attributeValues.entrySet()) {
- String attributeName = entry.getKey();
- Object attributeValue = entry.getValue();
- if (attributeValue == Runtime.NONE) { // Ignore all None values.
- continue;
- }
- Integer attrIndex = setRuleAttributeValue(rule, eventHandler, attributeName, attributeValue);
- if (attrIndex != null) {
- definedAttrs.set(attrIndex);
- checkAttrValNonEmpty(rule, eventHandler, attributeValue, attrIndex);
- }
- }
-
- // Save the location of each non-default attribute definition:
if (ast != null) {
- for (Argument.Passed arg : ast.getArguments()) {
- if (arg.isKeyword()) {
- String name = arg.getName();
- Integer attrIndex = getAttributeIndex(name);
- if (attrIndex != null) {
- rule.setAttributeLocation(attrIndex, arg.getValue().getLocation());
- }
- }
- }
+ populateAttributeLocations(rule, ast);
}
-
- List<Attribute> attrsWithComputedDefaults = new ArrayList<>();
-
- // Set defaults; ensure that every mandatory attribute has a value. Use
- // the default if none is specified.
- int numAttributes = getAttributeCount();
- for (int attrIndex = 0; attrIndex < numAttributes; ++attrIndex) {
- if (!definedAttrs.get(attrIndex)) {
- Attribute attr = getAttribute(attrIndex);
- if (attr.isMandatory()) {
- rule.reportError(rule.getLabel() + ": missing value for mandatory "
- + "attribute '" + attr.getName() + "' in '"
- + name + "' rule", eventHandler);
- }
-
- if (attr.hasComputedDefault()) {
- attrsWithComputedDefaults.add(attr);
- } else {
- Object defaultValue = getAttributeNoncomputedDefaultValue(attr, pkgBuilder);
- checkAttrValNonEmpty(rule, eventHandler, defaultValue, attrIndex);
- rule.setAttributeValue(attr, defaultValue, /*explicit=*/false);
- checkAllowedValues(rule, attr, eventHandler);
- }
- }
- }
-
- // Evaluate and set any computed defaults now that all non-computed
- // TODO(bazel-team): remove this special casing. Thanks to configurable attributes refactoring,
- // computed defaults don't get bound to their final values at this point, so we no longer
- // have to wait until regular attributes have been initialized.
- for (Attribute attr : attrsWithComputedDefaults) {
- rule.setAttributeValue(attr, attr.getDefaultValue(rule), /*explicit=*/false);
- }
-
- // Now that all attributes are bound to values, collect and store configurable attribute keys.
- populateConfigDependenciesAttribute(rule);
checkForDuplicateLabels(rule, eventHandler);
checkThirdPartyRuleHasLicense(rule, pkgBuilder, eventHandler);
checkForValidSizeAndTimeoutValues(rule, eventHandler);
+ rule.checkForNullLabels();
+ rule.checkValidityPredicate(eventHandler);
+ return rule;
+ }
+
+ /**
+ * Populates the attributes table of the new {@link Rule} with the values in the {@code
+ * attributeValues} map and with default values provided by this {@link RuleClass} and the {@code
+ * pkgBuilder}.
+ *
+ * <p>Errors are reported on {@code eventHandler}.
+ */
+ private void populateRuleAttributeValues(
+ Rule rule,
+ Package.Builder pkgBuilder,
+ AttributeValuesMap attributeValues,
+ EventHandler eventHandler) {
+ BitSet definedAttrIndices =
+ populateDefinedRuleAttributeValues(rule, attributeValues, eventHandler);
+ populateDefaultRuleAttributeValues(rule, pkgBuilder, definedAttrIndices, eventHandler);
+ // Now that all attributes are bound to values, collect and store configurable attribute keys.
+ populateConfigDependenciesAttribute(rule);
+ }
+
+ /**
+ * Populates the attributes table of the new {@link Rule} with the values in the {@code
+ * attributeValues} map.
+ *
+ * <p>Handles the special cases of the attribute named {@code "name"} and attributes with value
+ * {@link Runtime#NONE}.
+ *
+ * <p>Returns a bitset {@code b} where {@code b.get(i)} is {@code true} if this method set a
+ * value for the attribute with index {@code i} in this {@link RuleClass}. Errors are reported
+ * on {@code eventHandler}.
+ */
+ private BitSet populateDefinedRuleAttributeValues(
+ Rule rule, AttributeValuesMap attributeValues, EventHandler eventHandler) {
+ BitSet definedAttrIndices = new BitSet();
+ for (String attributeName : attributeValues.getAttributeNames()) {
+ // The attribute named "name" was handled in a special way already.
+ if (attributeName.equals("name")) {
+ continue;
+ }
+
+ Object attributeValue = attributeValues.getAttributeValue(attributeName);
+ // Ignore all None values.
+ if (attributeValue == Runtime.NONE) {
+ continue;
+ }
+
+ // Check that the attribute's name belongs to a valid attribute for this rule class.
+ Integer attrIndex = getAttributeIndex(attributeName);
+ if (attrIndex == null) {
+ rule.reportError(
+ String.format(
+ "%s: no such attribute '%s' in '%s' rule", rule.getLabel(), attributeName, name),
+ eventHandler);
+ continue;
+ }
+ Attribute attr = getAttribute(attrIndex);
+
+ // Convert the build-lang value to a native value, if necessary.
+ Object nativeAttributeValue;
+ if (attributeValues.valuesAreBuildLanguageTyped()) {
+ try {
+ nativeAttributeValue = convertFromBuildLangType(rule, attr, attributeValue);
+ } catch (ConversionException e) {
+ rule.reportError(String.format("%s: %s", rule.getLabel(), e.getMessage()), eventHandler);
+ continue;
+ }
+ } else {
+ nativeAttributeValue = attributeValue;
+ }
+
+ boolean explicit = attributeValues.isAttributeExplicitlySpecified(attributeName);
+ setRuleAttributeValue(rule, eventHandler, attr, nativeAttributeValue, explicit);
+ definedAttrIndices.set(attrIndex);
+ checkAttrValNonEmpty(rule, eventHandler, attributeValue, attr);
+ }
+ return definedAttrIndices;
+ }
+
+ /** Populates attribute locations for attributes defined in {@code ast}. */
+ private void populateAttributeLocations(Rule rule, FuncallExpression ast) {
+ for (Argument.Passed arg : ast.getArguments()) {
+ if (arg.isKeyword()) {
+ String name = arg.getName();
+ Integer attrIndex = getAttributeIndex(name);
+ if (attrIndex != null) {
+ rule.setAttributeLocation(attrIndex, arg.getValue().getLocation());
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates the attributes table of the new {@link Rule} with default values provided by this
+ * {@link RuleClass} and the {@code pkgBuilder}. This will only provide values for attributes
+ * that haven't already been populated, using {@code definedAttrIndices} to determine whether an
+ * attribute was populated.
+ *
+ * <p>Errors are reported on {@code eventHandler}.
+ */
+ private void populateDefaultRuleAttributeValues(
+ Rule rule, Package.Builder pkgBuilder, BitSet definedAttrIndices, EventHandler eventHandler) {
+ // Set defaults; ensure that every mandatory attribute has a value. Use the default if none
+ // is specified.
+ List<Attribute> attrsWithComputedDefaults = new ArrayList<>();
+ int numAttributes = getAttributeCount();
+ for (int attrIndex = 0; attrIndex < numAttributes; ++attrIndex) {
+ if (definedAttrIndices.get(attrIndex)) {
+ continue;
+ }
+ Attribute attr = getAttribute(attrIndex);
+ if (attr.isMandatory()) {
+ rule.reportError(
+ String.format(
+ "%s: missing value for mandatory attribute '%s' in '%s' rule",
+ rule.getLabel(),
+ attr.getName(),
+ name),
+ eventHandler);
+ }
+
+ if (attr.hasComputedDefault()) {
+ // Note that it is necessary to set all non-computed default values before calling
+ // Attribute#getDefaultValue for computed default attributes. Computed default attributes
+ // may have a condition predicate (i.e. the predicate returned by Attribute#getCondition)
+ // that depends on non-computed default attribute values, and that condition predicate is
+ // evaluated by the call to Attribute#getDefaultValue.
+ attrsWithComputedDefaults.add(attr);
+ } else {
+ Object defaultValue = getAttributeNoncomputedDefaultValue(attr, pkgBuilder);
+ checkAttrValNonEmpty(rule, eventHandler, defaultValue, attr);
+ rule.setAttributeValue(attr, defaultValue, /*explicit=*/ false);
+ checkAllowedValues(rule, attr, eventHandler);
+ }
+ }
+
+ // Set computed default attribute values now that all other (i.e. non-computed) default values
+ // have been set.
+ for (Attribute attr : attrsWithComputedDefaults) {
+ // If Attribute#hasComputedDefault is true, Attribute#getDefaultValue returns the computed
+ // default function object. Note that we don't evaluate the computed default function here
+ // because it may depend on other attribute values that are configurable (i.e. they came
+ // from select({..}) expressions in the build language, and they require configuration data
+ // from the analysis phase to be resolved). We're setting the attribute value to a
+ // reference to the computed default function.
+ rule.setAttributeValue(attr, attr.getDefaultValue(rule), /*explicit=*/ false);
+ }
}
/**
@@ -1398,9 +1485,7 @@
}
private void checkAttrValNonEmpty(
- Rule rule, EventHandler eventHandler, Object attributeValue, Integer attrIndex) {
-
- Attribute attr = getAttribute(attrIndex);
+ Rule rule, EventHandler eventHandler, Object attributeValue, Attribute attr) {
if (!attr.isNonEmpty()) {
return;
}
@@ -1525,65 +1610,64 @@
}
/**
- * Sets the value of attribute "attrName" in rule "rule", by converting the
- * build-language value "attrVal" to the appropriate type for the attribute.
- * Returns the attribute index iff successful, null otherwise.
+ * Sets the value of attribute {@code attr} in {@code rule} to the native value {@code
+ * nativeAttrVal}, and sets the value's explicitness to {@code explicit}.
*
- * <p>In case of failure, error messages are reported on "handler", and "rule"
- * is marked as containing errors.
+ * <p>Handles the special case of the "visibility" attribute by also setting the rule's
+ * visibility with {@link Rule#setVisibility}.
+ *
+ * <p>Checks that {@code nativeAttrVal} is an allowed value via {@link #checkAllowedValues}.
*/
- @SuppressWarnings("unchecked")
- private Integer setRuleAttributeValue(Rule rule,
- EventHandler eventHandler,
- String attrName,
- Object attrVal) {
- if (attrName.equals("name")) {
- return null; // "name" is handled specially
- }
-
- Integer attrIndex = getAttributeIndex(attrName);
- if (attrIndex == null) {
- rule.reportError(rule.getLabel() + ": no such attribute '" + attrName
- + "' in '" + name + "' rule", eventHandler);
- return null;
- }
-
- Attribute attr = getAttribute(attrIndex);
- Object converted;
- try {
- String what = "attribute '" + attrName + "' in '" + name + "' rule";
- converted = BuildType.selectableConvert(attr.getType(), attrVal, what, rule.getLabel());
-
- if ((converted instanceof SelectorList<?>) && !attr.isConfigurable()) {
- rule.reportError(rule.getLabel() + ": attribute \"" + attr.getName()
- + "\" is not configurable", eventHandler);
- return null;
- }
-
- if ((converted instanceof List<?>) && !(converted instanceof GlobList<?>)) {
- if (attr.isOrderIndependent()) {
- converted = Ordering.natural().sortedCopy((List<? extends Comparable<?>>) converted);
- }
- converted = ImmutableList.copyOf((List<?>) converted);
- }
- } catch (Type.ConversionException e) {
- rule.reportError(rule.getLabel() + ": " + e.getMessage(), eventHandler);
- return null;
- }
-
- if (attrName.equals("visibility")) {
- List<Label> attrList = (List<Label>) converted;
+ private static void setRuleAttributeValue(
+ Rule rule,
+ EventHandler eventHandler,
+ Attribute attr,
+ Object nativeAttrVal,
+ boolean explicit) {
+ if (attr.getName().equals("visibility")) {
+ @SuppressWarnings("unchecked")
+ List<Label> attrList = (List<Label>) nativeAttrVal;
if (!attrList.isEmpty()
&& ConstantRuleVisibility.LEGACY_PUBLIC_LABEL.equals(attrList.get(0))) {
- rule.reportError(rule.getLabel() + ": //visibility:legacy_public only allowed in package "
- + "declaration", eventHandler);
+ rule.reportError(
+ rule.getLabel() + ": //visibility:legacy_public only allowed in package declaration",
+ eventHandler);
}
rule.setVisibility(PackageFactory.getVisibility(rule.getLabel(), attrList));
}
-
- rule.setAttributeValue(attr, converted, /*explicit=*/true);
+ rule.setAttributeValue(attr, nativeAttrVal, explicit);
checkAllowedValues(rule, attr, eventHandler);
- return attrIndex;
+ }
+
+ /**
+ * Converts the build-language-typed {@code buildLangValue} to a native value via {@link
+ * BuildType#selectableConvert}. Canonicalizes the value's order if it is a {@link List} type
+ * (but not a {@link GlobList}) and {@code attr.isOrderIndependent()} returns {@code true}.
+ *
+ * <p>Throws {@link ConversionException} if the conversion fails, or if {@code buildLangValue}
+ * is a selector expression but {@code attr.isConfigurable()} is {@code false}.
+ */
+ private static Object convertFromBuildLangType(Rule rule, Attribute attr, Object buildLangValue)
+ throws ConversionException {
+ String what = String.format("attribute '%s' in '%s' rule", attr.getName(), rule.getRuleClass());
+ Object converted =
+ BuildType.selectableConvert(attr.getType(), buildLangValue, what, rule.getLabel());
+
+ if ((converted instanceof SelectorList<?>) && !attr.isConfigurable()) {
+ throw new ConversionException(
+ String.format("attribute \"%s\" is not configurable", attr.getName()));
+ }
+
+ if ((converted instanceof List<?>) && !(converted instanceof GlobList<?>)) {
+ if (attr.isOrderIndependent()) {
+ @SuppressWarnings("unchecked")
+ List<? extends Comparable<?>> list = (List<? extends Comparable<?>>) converted;
+ converted = Ordering.natural().sortedCopy(list);
+ }
+ converted = ImmutableList.copyOf((List<?>) converted);
+ }
+
+ return converted;
}
/**
@@ -1595,7 +1679,8 @@
* <p>If the rule is configurable, all of its potential values are evaluated, and errors for each
* of the invalid values are reported.
*/
- void checkAllowedValues(Rule rule, Attribute attribute, EventHandler eventHandler) {
+ private static void checkAllowedValues(
+ Rule rule, Attribute attribute, EventHandler eventHandler) {
if (attribute.checkAllowedValues()) {
PredicateWithMessage<Object> allowedValues = attribute.getAllowedValues();
Iterable<?> values =
@@ -1603,9 +1688,13 @@
attribute.getName(), attribute.getType());
for (Object value : values) {
if (!allowedValues.apply(value)) {
- rule.reportError(String.format(rule.getLabel() + ": invalid value in '%s' attribute: %s",
- attribute.getName(),
- allowedValues.getErrorReason(value)), eventHandler);
+ rule.reportError(
+ String.format(
+ "%s: invalid value in '%s' attribute: %s",
+ rule.getLabel(),
+ attribute.getName(),
+ allowedValues.getErrorReason(value)),
+ eventHandler);
}
}
}