private ListMultimap<Attribute, LabelAndConfiguration> resolveAttributes( Rule rule, AspectDefinition aspect, BuildConfiguration configuration, BuildConfiguration hostConfiguration, Set<ConfigMatchingProvider> configConditions) throws EvalException, InterruptedException { ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions); attributeMap.validateAttributes(); List<Attribute> attributes; if (aspect == null) { attributes = rule.getRuleClassObject().getAttributes(); } else { attributes = new ArrayList<>(); attributes.addAll(rule.getRuleClassObject().getAttributes()); if (aspect != null) { attributes.addAll(aspect.getAttributes().values()); } } ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result = ImmutableSortedKeyListMultimap.builder(); resolveExplicitAttributes(rule, configuration, attributeMap, result); resolveImplicitAttributes(rule, configuration, attributeMap, attributes, result); resolveLateBoundAttributes( rule, configuration, hostConfiguration, attributeMap, attributes, result); // Add the rule's visibility labels (which may come from the rule or from package defaults). addExplicitDeps( result, rule, "visibility", rule.getVisibility().getDependencyLabels(), configuration); // Add package default constraints when the rule doesn't explicitly declare them. // // Note that this can have subtle implications for constraint semantics. For example: say that // package defaults declare compatibility with ':foo' and rule R declares compatibility with // ':bar'. Does that mean that R is compatible with [':foo', ':bar'] or just [':bar']? In other // words, did R's author intend to add additional compatibility to the package defaults or to // override them? More severely, what if package defaults "restrict" support to just [':baz']? // Should R's declaration signify [':baz'] + ['bar'], [ORIGINAL_DEFAULTS] + ['bar'], or // something else? // // Rather than try to answer these questions with possibly confusing logic, we take the // simple approach of assigning the rule's "restriction" attribute to the rule-declared value if // it exists, else the package defaults value (and likewise for "compatibility"). This may not // always provide what users want, but it makes it easy for them to understand how rule // declarations and package defaults intermix (and how to refactor them to get what they want). // // An alternative model would be to apply the "rule declaration" / "rule class defaults" // relationship, i.e. the rule class' "compatibility" and "restriction" declarations are merged // to generate a set of default environments, then the rule's declarations are independently // processed on top of that. This protects against obscure coupling behavior between // declarations from wildly different places (e.g. it offers clear answers to the examples posed // above). But within the scope of a single package it seems better to keep the model simple and // make the user responsible for resolving ambiguities. if (!rule.isAttributeValueExplicitlySpecified(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)) { addExplicitDeps( result, rule, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, rule.getPackage().getDefaultCompatibleWith(), configuration); } if (!rule.isAttributeValueExplicitlySpecified(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)) { addExplicitDeps( result, rule, RuleClass.RESTRICTED_ENVIRONMENT_ATTR, rule.getPackage().getDefaultRestrictedTo(), configuration); } return result.build(); }