$ blaze cquery --show_config_fragments: include aspects
Aspects can add new rule dependencies via implicit label
attributes (see
https://docs.bazel.build/versions/2.0.0/skylark/aspects.html#aspect-definition-1).
We need to make sure these are included in whatever the rule the aspect
attaches to.
Note that *native* aspects can also declare *direct* fragment dependencies (
Starlark aspects can't). This change doesn't handle that. TBD.
Supports https://github.com/bazelbuild/bazel/issues/10613.
PiperOrigin-RevId: 293669124
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
index 0e07c6d..696e930 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
@@ -26,6 +26,8 @@
import com.google.devtools.build.lib.actions.Actions.GeneratingActions;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
+import com.google.devtools.build.lib.analysis.config.CoreOptions;
+import com.google.devtools.build.lib.analysis.config.CoreOptions.IncludeConfigFragmentsEnum;
import com.google.devtools.build.lib.analysis.skylark.SkylarkApiProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -281,6 +283,18 @@
ruleContext.getOwner(),
/*outputFiles=*/ null);
+ if (ruleContext
+ .getConfiguration()
+ .getOptions()
+ .get(CoreOptions.class)
+ .includeRequiredConfigFragmentsProvider
+ != IncludeConfigFragmentsEnum.OFF) {
+ // This guarantees aspects pass through the requirements of their dependencies. But native
+ // aspects can also declare direct requirements.
+ // TODO(gregce): support native aspect direct requirements.
+ addProvider(new RequiredConfigFragmentsProvider(ruleContext.getRequiredConfigFragments()));
+ }
+
return new ConfiguredAspect(
descriptor,
generatingActions.getActions(),
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
index c85ebe2..bba38e4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
@@ -342,6 +342,35 @@
requiredFragments.add(rule.getLabel().toString());
}
+ // Optionally add transitively required fragments:
+ requiredFragments.addAll(getRequiredConfigFragmentsFromDeps(configuration, prerequisites));
+ return ImmutableSet.copyOf(requiredFragments);
+ }
+
+ /**
+ * Subset of {@link #getRequiredConfigFragments} that only returns fragments required by deps.
+ * This includes:
+ *
+ * <ul>
+ * <li>Requirements transitively required by deps iff {@link
+ * CoreOptions#includeRequiredConfigFragmentsProvider} is {@link
+ * CoreOptions.IncludeConfigFragmentsEnum#TRANSITIVE},
+ * <li>Dependencies on Starlark build settings iff {@link
+ * CoreOptions#includeRequiredConfigFragmentsProvider} is not {@link
+ * CoreOptions.IncludeConfigFragmentsEnum#OFF}. These are considered direct requirements on
+ * the rule.
+ * </ul>
+ */
+ private static ImmutableSet<String> getRequiredConfigFragmentsFromDeps(
+ BuildConfiguration configuration, Iterable<ConfiguredTargetAndData> prerequisites) {
+
+ TreeSet<String> requiredFragments = new TreeSet<>();
+ CoreOptions coreOptions = configuration.getOptions().get(CoreOptions.class);
+ if (coreOptions.includeRequiredConfigFragmentsProvider
+ == CoreOptions.IncludeConfigFragmentsEnum.OFF) {
+ return ImmutableSet.of();
+ }
+
for (ConfiguredTargetAndData prereq : prerequisites) {
// If the rule depends on a Starlark build setting, conceptually that means the rule directly
// requires that as an option (even though it's technically a dependency).
@@ -599,6 +628,10 @@
.setUniversalFragments(ruleClassProvider.getUniversalFragments())
.setToolchainContext(toolchainContext)
.setConstraintSemantics(ruleClassProvider.getConstraintSemantics())
+ .setRequiredConfigFragments(
+ // Aspects have no direct fragment requirements: all requirements come from implicit
+ // label dependencies.
+ getRequiredConfigFragmentsFromDeps(aspectConfiguration, prerequisiteMap.values()))
.build();
// If allowing analysis failures, targets should be created as normal as possible, and errors
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RequiredConfigFragmentsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/RequiredConfigFragmentsProvider.java
index dc0f53c..1143bc4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RequiredConfigFragmentsProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RequiredConfigFragmentsProvider.java
@@ -14,10 +14,13 @@
package com.google.devtools.build.lib.analysis;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import java.util.List;
/**
* Provides a user-friendly list of the {@link BuildConfiguration.Fragment}s and
@@ -36,4 +39,18 @@
public ImmutableSet<String> getRequiredConfigFragments() {
return requiredConfigFragments;
}
+
+ /** Merges the values of multiple {@link RequiredConfigFragmentsProvider}s. */
+ public static RequiredConfigFragmentsProvider merge(
+ List<RequiredConfigFragmentsProvider> providers) {
+ checkArgument(!providers.isEmpty());
+ if (providers.size() == 1) {
+ return providers.get(0);
+ }
+ ImmutableSet.Builder<String> merged = ImmutableSet.builder();
+ for (RequiredConfigFragmentsProvider provider : providers) {
+ merged.addAll(provider.getRequiredConfigFragments());
+ }
+ return new RequiredConfigFragmentsProvider(merged.build());
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
index ee6eb34..31d4745 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
@@ -22,6 +22,7 @@
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
+import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoProviderMap;
import com.google.devtools.build.lib.analysis.TransitiveInfoProviderMapBuilder;
@@ -45,7 +46,13 @@
public final class MergedConfiguredTarget extends AbstractConfiguredTarget {
private final ConfiguredTarget base;
private final ImmutableList<ConfiguredAspect> aspects;
- private final TransitiveInfoProviderMap providers;
+ /**
+ * Providers that come from any source that isn't a pure pointer to the base rule's providers.
+ *
+ * <p>Examples include providers from aspects and merged providers that appear in both the base
+ * rule and aspects.
+ */
+ private final TransitiveInfoProviderMap nonBaseProviders;
/**
* This exception is thrown when configured targets and aspects
@@ -61,18 +68,18 @@
private MergedConfiguredTarget(
ConfiguredTarget base,
Iterable<ConfiguredAspect> aspects,
- TransitiveInfoProviderMap providers) {
+ TransitiveInfoProviderMap nonBaseProviders) {
super(base.getLabel(), base.getConfigurationKey());
this.base = base;
this.aspects = ImmutableList.copyOf(aspects);
- this.providers = providers;
+ this.nonBaseProviders = nonBaseProviders;
}
@Override
public <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) {
AnalysisUtils.checkProvider(providerClass);
- P provider = providers.getProvider(providerClass);
+ P provider = nonBaseProviders.getProvider(providerClass);
if (provider == null) {
provider = base.getProvider(providerClass);
}
@@ -85,8 +92,8 @@
if (base instanceof AbstractConfiguredTarget) {
((AbstractConfiguredTarget) base).addExtraSkylarkKeys(result);
}
- for (int i = 0; i < providers.getProviderCount(); i++) {
- Object classAt = providers.getProviderKeyAt(i);
+ for (int i = 0; i < nonBaseProviders.getProviderCount(); i++) {
+ Object classAt = nonBaseProviders.getProviderKeyAt(i);
if (classAt instanceof String) {
result.accept((String) classAt);
}
@@ -96,7 +103,7 @@
@Override
protected Info rawGetSkylarkProvider(Provider.Key providerKey) {
- Info provider = providers.get(providerKey);
+ Info provider = nonBaseProviders.get(providerKey);
if (provider == null) {
provider = base.get(providerKey);
}
@@ -120,7 +127,7 @@
}
return actions.build();
}
- Object provider = providers.get(providerKey);
+ Object provider = nonBaseProviders.get(providerKey);
if (provider == null) {
provider = base.get(providerKey);
}
@@ -135,20 +142,27 @@
return base;
}
+ TransitiveInfoProviderMapBuilder nonBaseProviders = new TransitiveInfoProviderMapBuilder();
+
// Merge output group providers.
OutputGroupInfo mergedOutputGroupInfo =
OutputGroupInfo.merge(getAllOutputGroupProviders(base, aspects));
+ if (mergedOutputGroupInfo != null) {
+ nonBaseProviders.put(mergedOutputGroupInfo);
+ }
// Merge extra-actions provider.
ExtraActionArtifactsProvider mergedExtraActionProviders = ExtraActionArtifactsProvider.merge(
getAllProviders(base, aspects, ExtraActionArtifactsProvider.class));
-
- TransitiveInfoProviderMapBuilder aspectProviders = new TransitiveInfoProviderMapBuilder();
- if (mergedOutputGroupInfo != null) {
- aspectProviders.put(mergedOutputGroupInfo);
- }
if (mergedExtraActionProviders != null) {
- aspectProviders.add(mergedExtraActionProviders);
+ nonBaseProviders.add(mergedExtraActionProviders);
+ }
+
+ // Merge required config fragments provider.
+ List<RequiredConfigFragmentsProvider> requiredConfigFragmentProviders =
+ getAllProviders(base, aspects, RequiredConfigFragmentsProvider.class);
+ if (!requiredConfigFragmentProviders.isEmpty()) {
+ nonBaseProviders.add(RequiredConfigFragmentsProvider.merge(requiredConfigFragmentProviders));
}
for (ConfiguredAspect aspect : aspects) {
@@ -156,7 +170,8 @@
for (int i = 0; i < providers.getProviderCount(); ++i) {
Object providerKey = providers.getProviderKeyAt(i);
if (OutputGroupInfo.SKYLARK_CONSTRUCTOR.getKey().equals(providerKey)
- || ExtraActionArtifactsProvider.class.equals(providerKey)) {
+ || ExtraActionArtifactsProvider.class.equals(providerKey)
+ || RequiredConfigFragmentsProvider.class.equals(providerKey)) {
continue;
}
@@ -164,28 +179,27 @@
@SuppressWarnings("unchecked")
Class<? extends TransitiveInfoProvider> providerClass =
(Class<? extends TransitiveInfoProvider>) providerKey;
- if (base.getProvider(providerClass) != null
- || aspectProviders.contains(providerClass)) {
+ if (base.getProvider(providerClass) != null || nonBaseProviders.contains(providerClass)) {
throw new DuplicateException("Provider " + providerKey + " provided twice");
}
- aspectProviders.put(
+ nonBaseProviders.put(
providerClass, (TransitiveInfoProvider) providers.getProviderInstanceAt(i));
} else if (providerKey instanceof String) {
String legacyId = (String) providerKey;
- if (base.get(legacyId) != null || aspectProviders.contains(legacyId)) {
+ if (base.get(legacyId) != null || nonBaseProviders.contains(legacyId)) {
throw new DuplicateException("Provider " + legacyId + " provided twice");
}
- aspectProviders.put(legacyId, providers.getProviderInstanceAt(i));
+ nonBaseProviders.put(legacyId, providers.getProviderInstanceAt(i));
} else if (providerKey instanceof Provider.Key) {
Provider.Key key = (Key) providerKey;
- if (base.get(key) != null || aspectProviders.contains(key)) {
+ if (base.get(key) != null || nonBaseProviders.contains(key)) {
throw new DuplicateException("Provider " + key + " provided twice");
}
- aspectProviders.put((Info) providers.getProviderInstanceAt(i));
+ nonBaseProviders.put((Info) providers.getProviderInstanceAt(i));
}
}
}
- return new MergedConfiguredTarget(base, aspects, aspectProviders.build());
+ return new MergedConfiguredTarget(base, aspects, nonBaseProviders.build());
}
private static ImmutableList<OutputGroupInfo> getAllOutputGroupProviders(
diff --git a/src/test/shell/integration/configured_query_test.sh b/src/test/shell/integration/configured_query_test.sh
index 018143c..481554a 100755
--- a/src/test/shell/integration/configured_query_test.sh
+++ b/src/test/shell/integration/configured_query_test.sh
@@ -649,6 +649,70 @@
assert_contains "//$pkg:buildme .*JavaConfiguration" output
}
+# Starlark aspects can't directly require configuration fragments. But they can
+# have implicit label dependencies on rules that do. This test ensures that
+# gets factored in.
+#
+# Note that cquery doesn't support queries over aspects: `deps(//foo)` won't
+# list aspects or traverse their dependencies. This makes it hard to understand
+# *why* a rule requires a fragment if only through an aspect. That's an argument
+# for making cquery generally aspect-aware.
+function test_show_config_fragments_includes_starlark_aspects() {
+ local -r pkg=$FUNCNAME
+ mkdir -p $pkg
+ cat > $pkg/defs.bzl <<EOF
+def _aspect_impl(target, ctx):
+ return []
+
+cc_depending_aspect = aspect(
+ attrs = {
+ # This creates an implicit dependency on a C++ library that
+ # only comes through the aspect.
+ "_extra_dep": attr.label(default = "//$pkg:cclib"),
+ },
+ implementation = _aspect_impl,
+)
+
+def _impl(ctx):
+ pass
+
+simple_rule = rule(
+ implementation = _impl,
+ attrs = {
+ "deps": attr.label_list(aspects = [cc_depending_aspect])
+ }
+)
+EOF
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:defs.bzl", "simple_rule")
+
+simple_rule(
+ name = "buildme",
+ deps = [":simple_dep"],
+)
+
+simple_rule(
+ name = "simple_dep",
+)
+
+cc_library(
+ name = "cclib",
+ srcs = ["cclib.cc"],
+)
+EOF
+
+ # No direct requirement:
+ bazel cquery "//$pkg:buildme" --show_config_fragments=direct \
+ > output 2>"$TEST_log" || fail "Expected success"
+ assert_not_contains "//$pkg:buildme .*CppConfiguration" output
+
+ # But there is a transitive requirement through the aspect:
+ bazel cquery "//$pkg:buildme" --show_config_fragments=transitive \
+ > output 2>"$TEST_log" || fail "Expected success"
+ assert_contains "//$pkg:buildme .*CppConfiguration" output
+}
+
function test_manual_tagged_targets_always_included_for_queries() {
local -r pkg=$FUNCNAME
mkdir -p $pkg