/** * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some * dependencies do not have a corresponding attribute here, and we use the null attribute to * represent those edges. Visibility attributes are only visited if {@code visitVisibility} is * {@code true}. * * <p>If {@code aspect} is null, returns the dependent nodes of the configured target node * representing the given target and configuration, otherwise that of the aspect node accompanying * the aforementioned configured target node for the specified aspect. * * <p>The values are not simply labels because this also implements the first step of applying * configuration transitions, namely, split transitions. This needs to be done before the labels * are resolved because late bound attributes depend on the configuration. A good example for this * is @{code :cc_toolchain}. * * <p>The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. */ public final ListMultimap<Attribute, Dependency> dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, AspectDefinition aspect, AspectParameters aspectParameters, Set<ConfigMatchingProvider> configConditions) throws EvalException, InterruptedException { Target target = node.getTarget(); BuildConfiguration config = node.getConfiguration(); ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create(); if (target instanceof OutputFile) { Preconditions.checkNotNull(config); visitTargetVisibility(node, outgoingEdges.get(null)); Rule rule = ((OutputFile) target).getGeneratingRule(); outgoingEdges.put(null, new Dependency(rule.getLabel(), config)); } else if (target instanceof InputFile) { visitTargetVisibility(node, outgoingEdges.get(null)); } else if (target instanceof EnvironmentGroup) { visitTargetVisibility(node, outgoingEdges.get(null)); } else if (target instanceof Rule) { Preconditions.checkNotNull(config); visitTargetVisibility(node, outgoingEdges.get(null)); Rule rule = (Rule) target; ListMultimap<Attribute, LabelAndConfiguration> labelMap = resolveAttributes(rule, aspect, config, hostConfig, configConditions); visitRule(rule, aspect, aspectParameters, labelMap, outgoingEdges); } else if (target instanceof PackageGroup) { visitPackageGroup(node, (PackageGroup) target, outgoingEdges.get(null)); } else { throw new IllegalStateException(target.getLabel().toString()); } return outgoingEdges; }
private Set<Label> getAllowedDeps(Rule rule) { Set<Label> allowedLabels = new HashSet<>(rule.getTransitions(dependencyFilter).values()); allowedLabels.addAll(rule.getVisibility().getDependencyLabels()); // We should add deps from aspects, otherwise they are going to be filtered out. allowedLabels.addAll(rule.getAspectLabelsSuperset(dependencyFilter)); return allowedLabels; }
/** * Returns the configurable attribute conditions necessary to evaluate the given configured * target, or null if not all dependencies have yet been SkyFrame-evaluated. */ @Nullable private Set<ConfigMatchingProvider> getConfigurableAttributeConditions( TargetAndConfiguration ctg, Environment env) { if (!(ctg.getTarget() instanceof Rule)) { return ImmutableSet.of(); } Rule rule = (Rule) ctg.getTarget(); RawAttributeMapper mapper = RawAttributeMapper.of(rule); Set<SkyKey> depKeys = new LinkedHashSet<>(); for (Attribute attribute : rule.getAttributes()) { for (Label label : mapper.getConfigurabilityKeys(attribute.getName(), attribute.getType())) { if (!BuildType.Selector.isReservedLabel(label)) { depKeys.add(ConfiguredTargetValue.key(label, ctg.getConfiguration())); } } } Map<SkyKey, SkyValue> cts = env.getValues(depKeys); if (env.valuesMissing()) { return null; } ImmutableSet.Builder<ConfigMatchingProvider> conditions = ImmutableSet.builder(); for (SkyValue ctValue : cts.values()) { ConfiguredTarget ct = ((ConfiguredTargetValue) ctValue).getConfiguredTarget(); conditions.add(Preconditions.checkNotNull(ct.getProvider(ConfigMatchingProvider.class))); } return conditions.build(); }
@Nullable @Override public SkyValue compute(SkyKey skyKey, SkyFunction.Environment env) throws RepositoryFunctionException { RepositoryName repositoryName = (RepositoryName) skyKey.argument(); Rule rule = getRule(repositoryName, NewHttpArchiveRule.NAME, env); if (rule == null) { return null; } Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName()); try { FileSystemUtils.createDirectoryAndParents(outputDirectory); } catch (IOException e) { throw new RepositoryFunctionException( new IOException( "Could not create directory for " + rule.getName() + ": " + e.getMessage()), Transience.TRANSIENT); } FileValue repositoryDirectory = getRepositoryDirectory(outputDirectory, env); if (repositoryDirectory == null) { return null; } // Download. HttpDownloadValue downloadedFileValue; try { downloadedFileValue = (HttpDownloadValue) env.getValueOrThrow( HttpDownloadFunction.key(rule, outputDirectory), IOException.class); } catch (IOException e) { throw new RepositoryFunctionException(e, Transience.PERSISTENT); } if (downloadedFileValue == null) { return null; } // Decompress. DecompressorValue decompressed; try { decompressed = (DecompressorValue) env.getValueOrThrow( DecompressorValue.key( rule.getTargetKind(), rule.getName(), downloadedFileValue.getPath(), outputDirectory), IOException.class); if (decompressed == null) { return null; } } catch (IOException e) { throw new RepositoryFunctionException(new IOException(e.getMessage()), Transience.TRANSIENT); } // Add WORKSPACE and BUILD files. createWorkspaceFile(decompressed.getDirectory(), rule); return symlinkBuildFile(rule, getWorkspace(), repositoryDirectory, env); }
private void collectTransitiveClosure( PackageProviderForConfigurations packageProvider, Set<Label> reachableLabels, Label from) throws NoSuchThingException { if (!reachableLabels.add(from)) { return; } Target fromTarget = packageProvider.getTarget(from); if (fromTarget instanceof Rule) { Rule rule = (Rule) fromTarget; if (rule.getRuleClassObject().hasAttr("srcs", BuildType.LABEL_LIST)) { // TODO(bazel-team): refine this. This visits "srcs" reachable under *any* configuration, // not necessarily the configuration actually applied to the rule. We should correlate the // two. However, doing so requires faithfully reflecting the configuration transitions that // might happen as we traverse the dependency chain. // TODO(bazel-team): Why don't we use AbstractAttributeMapper#visitLabels() here? for (List<Label> labelsForConfiguration : AggregatingAttributeMapper.of(rule).visitAttribute("srcs", BuildType.LABEL_LIST)) { for (Label label : labelsForConfiguration) { collectTransitiveClosure( packageProvider, reachableLabels, from.resolveRepositoryRelative(label)); } } } if (rule.getRuleClass().equals("bind")) { Label actual = AggregatingAttributeMapper.of(rule).get("actual", BuildType.LABEL); if (actual != null) { collectTransitiveClosure(packageProvider, reachableLabels, actual); } } } }
@Override public void setUp() throws Exception { super.setUp(); rule = createRule( "x", "myrule", "cc_binary(name = 'myrule',", " srcs = ['a', 'b', 'c'])"); RuleClass ruleClass = rule.getRuleClassObject(); mapper = new TestMapper(pkg, ruleClass, rule.getLabel(), rule.getAttributeContainer()); }
/** Adds the given rule to the stack trace of the exception (if there is one). */ private static void addRuleToStackTrace(EvalException ex, Rule rule, BaseFunction ruleImpl) { if (ex instanceof EvalExceptionWithStackTrace) { ((EvalExceptionWithStackTrace) ex) .registerPhantomFuncall( String.format("%s(name = '%s')", rule.getRuleClass(), rule.getName()), rule.getLocation(), ruleImpl); } }
@Override public AspectParameters apply(Rule rule) { if (rule.isAttrDefined("baz", STRING)) { String value = rule.getAttributeContainer().getAttr("baz").toString(); if (!value.equals("")) { return new AspectParameters.Builder().addAttribute("baz", value).build(); } } return AspectParameters.EMPTY; }
/** * Returns the rule class defaults specified for this rule, or null if there are no such defaults. */ @Nullable private static EnvironmentCollector maybeGetRuleClassDefaults(RuleContext ruleContext) { Rule rule = ruleContext.getRule(); String restrictionAttr = RuleClass.DEFAULT_RESTRICTED_ENVIRONMENT_ATTR; String compatibilityAttr = RuleClass.DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR; if (rule.isAttrDefined(restrictionAttr, BuildType.LABEL_LIST) || rule.isAttrDefined(compatibilityAttr, BuildType.LABEL_LIST)) { return new EnvironmentCollector( ruleContext, restrictionAttr, compatibilityAttr, new GroupDefaultsProvider()); } else { return null; } }
@Override public SkyValue fetch(Rule rule, Path outputDirectory, Environment env) throws SkyFunctionException { prepareLocalRepositorySymlinkTree(rule, outputDirectory); PathFragment pathFragment = getTargetPath(rule, getWorkspace()); if (!symlinkLocalRepositoryContents( outputDirectory, getOutputBase().getFileSystem().getPath(pathFragment))) { return null; } AttributeMap attributes = NonconfigurableAttributeMapper.of(rule); String buildToolsVersion = attributes.get("build_tools_version", Type.STRING); Integer apiLevel = attributes.get("api_level", Type.INTEGER); String template = getStringResource("android_sdk_repository_template.txt"); String buildFile = template .replaceAll("%repository_name%", rule.getName()) .replaceAll("%build_tools_version%", buildToolsVersion) .replaceAll("%api_level%", apiLevel.toString()); writeBuildFile(outputDirectory, buildFile); return RepositoryDirectoryValue.create(outputDirectory); }
/** * Adds new dependencies to the given rule under the given attribute name * * @param result the builder for the attribute --> dependency labels map * @param rule the rule being evaluated * @param attrName the name of the attribute to add dependency labels to * @param labels the dependencies to add * @param configuration the configuration to apply to those dependencies */ private void addExplicitDeps( ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result, Rule rule, String attrName, Iterable<Label> labels, BuildConfiguration configuration) { if (!rule.isAttrDefined(attrName, BuildType.LABEL_LIST) && !rule.isAttrDefined(attrName, BuildType.NODEP_LABEL_LIST)) { return; } Attribute attribute = rule.getRuleClassObject().getAttributeByName(attrName); for (Label label : labels) { // The configuration must be the configuration after the first transition step (applying // split configurations). The proper configuration (null) for package groups will be set // later. result.put(attribute, LabelAndConfiguration.of(label, configuration)); } }
private void resolveExplicitAttributes( Rule rule, final BuildConfiguration configuration, AttributeMap attributes, final ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) { attributes.visitLabels( new AttributeMap.AcceptsLabelAttribute() { @Override public void acceptLabelAttribute(Label label, Attribute attribute) { String attributeName = attribute.getName(); if (attributeName.equals("abi_deps")) { // abi_deps is handled specially: we visit only the branch that // needs to be taken based on the configuration. return; } if (attribute.getType() == BuildType.NODEP_LABEL) { return; } if (attribute.isImplicit() || attribute.isLateBound()) { return; } builder.put(attribute, LabelAndConfiguration.of(label, configuration)); } }); // TODO(bazel-team): Remove this in favor of the new configurable attributes. if (attributes.getAttributeDefinition("abi_deps") != null) { Attribute depsAttribute = attributes.getAttributeDefinition("deps"); MakeVariableExpander.Context context = new ConfigurationMakeVariableContext(rule.getPackage(), configuration); String abi = null; try { abi = MakeVariableExpander.expand(attributes.get("abi", Type.STRING), context); } catch (MakeVariableExpander.ExpansionException e) { // Ignore this. It will be handled during the analysis phase. } if (abi != null) { for (Map.Entry<String, List<Label>> entry : attributes.get("abi_deps", BuildType.LABEL_LIST_DICT).entrySet()) { try { if (Pattern.matches(entry.getKey(), abi)) { for (Label label : entry.getValue()) { builder.put(depsAttribute, LabelAndConfiguration.of(label, configuration)); } } } catch (PatternSyntaxException e) { // Ignore this. It will be handled during the analysis phase. } } } } }
protected void createDirectory(Path path, Rule rule) throws RepositoryFunctionException { try { FileSystemUtils.createDirectoryAndParents(path); } catch (IOException e) { throw new RepositoryFunctionException( new IOException( "Could not create directory for " + rule.getName() + ": " + e.getMessage()), Transience.TRANSIENT); } }
@Nullable @Override public Map<String, Label> apply(Rule rule) { ImmutableMap.Builder<String, Label> result = ImmutableMap.builder(); for (String tool : TOOLS) { result.put( "android/" + tool, Label.parseAbsoluteUnchecked("@" + rule.getName() + "//tools/android:" + tool)); } return result.build(); }
private LicensesProvider initializeLicensesProvider() { if (!ruleContext.getConfiguration().checkLicenses()) { return LicensesProviderImpl.EMPTY; } NestedSetBuilder<TargetLicense> builder = NestedSetBuilder.linkOrder(); BuildConfiguration configuration = ruleContext.getConfiguration(); Rule rule = ruleContext.getRule(); License toolOutputLicense = rule.getToolOutputLicense(ruleContext.attributes()); if (configuration.isHostConfiguration() && toolOutputLicense != null) { if (toolOutputLicense != License.NO_LICENSE) { builder.add(new TargetLicense(rule.getLabel(), toolOutputLicense)); } } else { if (rule.getLicense() != License.NO_LICENSE) { builder.add(new TargetLicense(rule.getLabel(), rule.getLicense())); } for (TransitiveInfoCollection dep : ruleContext.getConfiguredTargetMap().values()) { LicensesProvider provider = dep.getProvider(LicensesProvider.class); if (provider != null) { builder.addTransitive(provider.getTransitiveLicenses()); } } } return new LicensesProviderImpl(builder.build()); }
/** * Symlinks a BUILD file from the local filesystem into the external repository's root. * * @param rule the rule that declares the build_file path. * @param workspaceDirectory the workspace root for the build. * @param directoryValue the FileValue corresponding to the external repository's root directory. * @param env the Skyframe environment. * @return the file value of the symlink created. * @throws * com.google.devtools.build.lib.bazel.repository.RepositoryFunction.RepositoryFunctionException * if the BUILD file specified does not exist or cannot be linked. */ protected RepositoryValue symlinkBuildFile( Rule rule, Path workspaceDirectory, FileValue directoryValue, Environment env) throws RepositoryFunctionException { AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); PathFragment buildFile = new PathFragment(mapper.get("build_file", Type.STRING)); Path buildFileTarget = workspaceDirectory.getRelative(buildFile); if (!buildFileTarget.exists()) { throw new RepositoryFunctionException( new EvalException( rule.getLocation(), String.format( "In %s the 'build_file' attribute does not specify an existing file " + "(%s does not exist)", rule, buildFileTarget)), Transience.PERSISTENT); } RootedPath rootedBuild; if (buildFile.isAbsolute()) { rootedBuild = RootedPath.toRootedPath( buildFileTarget.getParentDirectory(), new PathFragment(buildFileTarget.getBaseName())); } else { rootedBuild = RootedPath.toRootedPath(workspaceDirectory, buildFile); } SkyKey buildFileKey = FileValue.key(rootedBuild); FileValue buildFileValue; try { buildFileValue = (FileValue) env.getValueOrThrow( buildFileKey, IOException.class, FileSymlinkException.class, InconsistentFilesystemException.class); if (buildFileValue == null) { return null; } } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) { throw new RepositoryFunctionException( new IOException("Cannot lookup " + buildFile + ": " + e.getMessage()), Transience.TRANSIENT); } Path buildFilePath = directoryValue.realRootedPath().asPath().getRelative("BUILD"); if (createSymbolicLink(buildFilePath, buildFileTarget, env) == null) { return null; } return RepositoryValue.createNew(directoryValue, buildFileValue); }
protected SkyValue compute(Environment env, Rule rule) throws RepositoryFunctionException { // The output directory is always under .external-repository (to stay out of the way of // artifacts from this repository) and uses the rule's name to avoid conflicts with other // remote repository rules. For example, suppose you had the following WORKSPACE file: // // http_archive(name = "png", url = "http://example.com/downloads/png.tar.gz", sha256 = "...") // // This would download png.tar.gz to .external-repository/png/png.tar.gz. Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName()); FileValue directoryValue = createDirectory(outputDirectory, env); if (directoryValue == null) { return null; } AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); URL url = null; try { url = new URL(mapper.get("url", Type.STRING)); } catch (MalformedURLException e) { throw new RepositoryFunctionException( new EvalException(rule.getLocation(), "Error parsing URL: " + e.getMessage()), Transience.PERSISTENT); } String sha256 = mapper.get("sha256", Type.STRING); HttpDownloader downloader = new HttpDownloader(url, sha256, outputDirectory); try { Path archiveFile = downloader.download(); outputDirectory = DecompressorFactory.create( rule.getTargetKind(), rule.getName(), archiveFile, outputDirectory) .decompress(); } catch (IOException e) { // Assumes all IO errors transient. throw new RepositoryFunctionException(e, Transience.TRANSIENT); } catch (DecompressorException e) { throw new RepositoryFunctionException(new IOException(e.getMessage()), Transience.TRANSIENT); } return new RepositoryValue(outputDirectory, directoryValue); }
protected static PathFragment getTargetPath(Rule rule) throws RepositoryFunctionException { AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); String path = mapper.get("path", Type.STRING); PathFragment pathFragment = new PathFragment(path); if (!pathFragment.isAbsolute()) { throw new RepositoryFunctionException( new EvalException( rule.getLocation(), "In " + rule + " the 'path' attribute must specify an absolute path"), Transience.PERSISTENT); } return pathFragment; }
/** * Uses a remote repository name to fetch the corresponding Rule describing how to get it. This * should be called from {@link SkyFunction#compute} functions, which should return null if this * returns null. If {@code ruleClassName} is set, the rule found must have a matching rule class * name. */ @Nullable public static Rule getRule( RepositoryName repositoryName, @Nullable String ruleClassName, Environment env) throws RepositoryFunctionException { ExternalPackage externalPackage = getExternalPackage(env); if (externalPackage == null) { return null; } Rule rule = externalPackage.getRepositoryInfo(repositoryName); if (rule == null) { throw new RepositoryFunctionException( new BuildFileContainsErrorsException( ExternalPackage.PACKAGE_IDENTIFIER, "The repository named '" + repositoryName + "' could not be resolved"), Transience.PERSISTENT); } Preconditions.checkState( ruleClassName == null || rule.getRuleClass().equals(ruleClassName), "Got %s, was expecting a %s", rule, ruleClassName); return rule; }
@Deprecated private RuleIdeInfo.Kind getRuleKind(Rule rule, ConfiguredTarget base) { switch (rule.getRuleClassObject().getName()) { case "java_library": return Kind.JAVA_LIBRARY; case "java_import": return Kind.JAVA_IMPORT; case "java_test": return Kind.JAVA_TEST; case "java_binary": return Kind.JAVA_BINARY; case "android_library": return Kind.ANDROID_LIBRARY; case "android_binary": return Kind.ANDROID_BINARY; case "android_test": return Kind.ANDROID_TEST; case "android_robolectric_test": return Kind.ANDROID_ROBOELECTRIC_TEST; case "proto_library": return Kind.PROTO_LIBRARY; case "java_plugin": return Kind.JAVA_PLUGIN; case "android_resources": return Kind.ANDROID_RESOURCES; case "cc_library": return Kind.CC_LIBRARY; case "cc_binary": return Kind.CC_BINARY; case "cc_test": return Kind.CC_TEST; case "cc_inc_library": return Kind.CC_INC_LIBRARY; case "cc_toolchain": return Kind.CC_TOOLCHAIN; case "java_wrap_cc": return Kind.JAVA_WRAP_CC; default: { if (base.getProvider(AndroidSdkProvider.class) != null) { return RuleIdeInfo.Kind.ANDROID_SDK; } else { return RuleIdeInfo.Kind.UNRECOGNIZED; } } } }
protected FileValue prepareLocalRepositorySymlinkTree(Rule rule, Environment env) throws RepositoryFunctionException { Path repositoryDirectory = getExternalRepositoryDirectory().getRelative(rule.getName()); try { FileSystemUtils.deleteTree(repositoryDirectory); FileSystemUtils.createDirectoryAndParents(repositoryDirectory); } catch (IOException e) { throw new RepositoryFunctionException(e, Transience.TRANSIENT); } FileValue directoryValue = getRepositoryDirectory(repositoryDirectory, env); if (directoryValue == null) { return null; } // Add x/WORKSPACE. createWorkspaceFile(repositoryDirectory, rule); return directoryValue; }
public void testRuleProperties() throws Exception { assertEquals(rule.getName(), mapper.getName()); assertEquals(rule.getLabel(), mapper.getLabel()); }
private void resolveLateBoundAttributes( Rule rule, BuildConfiguration configuration, BuildConfiguration hostConfiguration, AttributeMap attributeMap, Iterable<Attribute> attributes, ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) throws EvalException, InterruptedException { for (Attribute attribute : attributes) { if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { continue; } List<BuildConfiguration> actualConfigurations = ImmutableList.of(configuration); if (attribute.getConfigurationTransition() instanceof SplitTransition<?>) { Preconditions.checkState(attribute.getConfigurator() == null); // TODO(bazel-team): This ends up applying the split transition twice, both here and in the // visitRule method below - this is not currently a problem, because the configuration graph // never contains nested split transitions, so the second application is idempotent. actualConfigurations = configuration.getSplitConfigurations( (SplitTransition<?>) attribute.getConfigurationTransition()); } for (BuildConfiguration actualConfig : actualConfigurations) { @SuppressWarnings("unchecked") LateBoundDefault<BuildConfiguration> lateBoundDefault = (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault(); if (lateBoundDefault.useHostConfiguration()) { actualConfig = hostConfiguration; } // TODO(bazel-team): This might be too expensive - can we cache this somehow? if (!lateBoundDefault.getRequiredConfigurationFragments().isEmpty()) { if (!actualConfig.hasAllFragments(lateBoundDefault.getRequiredConfigurationFragments())) { continue; } } // TODO(bazel-team): We should check if the implementation tries to access an undeclared // fragment. Object actualValue = lateBoundDefault.getDefault(rule, actualConfig); if (EvalUtils.isNullOrNone(actualValue)) { continue; } try { if (attribute.getType() == BuildType.LABEL) { Label label = BuildType.LABEL.cast(actualValue); builder.put(attribute, LabelAndConfiguration.of(label, actualConfig)); } else if (attribute.getType() == BuildType.LABEL_LIST) { for (Label label : BuildType.LABEL_LIST.cast(actualValue)) { builder.put(attribute, LabelAndConfiguration.of(label, actualConfig)); } } else { throw new IllegalStateException( String.format( "Late bound attribute '%s' is not a label or a label list", attribute.getName())); } } catch (ClassCastException e) { throw new EvalException( rule.getLocation(), String.format( "When computing the default value of %s, expected '%s', got '%s'", attribute.getName(), attribute.getType(), EvalUtils.getDataTypeName(actualValue, true))); } } } }
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(); }