Fix check for exec groups in DependencyResolver
In `DependencyResolver`, if the aspects list passed to [`dependentNodeMap()`](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java#L197) is not empty, the `toolchainContexts` will contain only the exec groups of the main aspect (placed as the last aspect in aspects list) ([from AspectFunction](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java#L596)). Otherwise, `toolchainContexts` will contain the exec group of the target's rule ([from ConfiguredTargetFunction](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java#L587)). Therefore, [this check](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java#L349) needs to be updated so that if the aspects list is not empty and the current dependency is not a dependency of the main aspect, no need to check as the exec group will always not be in `toolchainContexts`.
PiperOrigin-RevId: 499270890
Change-Id: Ibe657eb7f3ec3721e816cf4a9b4b86af58957730
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
index 87156b9..3667b5e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -271,6 +271,7 @@
throws Failure {
OrderedSetMultimap<DependencyKind, PartiallyResolvedDependency> partiallyResolvedDeps =
OrderedSetMultimap.create();
+ ImmutableList<Aspect> aspectsList = ImmutableList.copyOf(aspects);
for (Map.Entry<DependencyKind, Label> entry : outgoingLabels.entries()) {
Label toLabel = entry.getValue();
@@ -334,11 +335,8 @@
Attribute attribute = entry.getKey().getAttribute();
ImmutableList.Builder<Aspect> propagatingAspects = ImmutableList.builder();
propagatingAspects.addAll(attribute.getAspects(fromRule));
- collectPropagatingAspects(
- ImmutableList.copyOf(aspects),
- attribute.getName(),
- entry.getKey().getOwningAspect(),
- propagatingAspects);
+ AspectClass owningAspect = entry.getKey().getOwningAspect();
+ collectPropagatingAspects(aspectsList, attribute.getName(), owningAspect, propagatingAspects);
Label executionPlatformLabel = null;
// TODO(jcater): refactor this nested if structure into something simpler.
@@ -347,11 +345,25 @@
String execGroup =
((ExecutionTransitionFactory) attribute.getTransitionFactory()).getExecGroup();
if (!toolchainContexts.hasToolchainContext(execGroup)) {
- throw new Failure(
- fromRule != null ? fromRule.getLocation() : null,
- String.format(
- "Attr '%s' declares a transition for non-existent exec group '%s'",
- attribute.getName(), execGroup));
+ // If {@code aspectsList} is not empty, {@code toolchainContexts} contains only the exec
+ // groups of the main aspect (placed as the last aspect in {@code aspectsList}).
+ // Otherwise, {@code toolchainContexts} contains the exec group of the target's rule.
+ // Therefore, if the {@aspectsList} is not empty and the current entry is not a
+ // dependency of the main aspect, {@code execGroup} will never exist in {@code
+ // toolchainContexts} and this dependency will be skipped.
+ // TODO(b/256617733): Make a decision on whether the exec groups of the target
+ // and the base aspects should be merged in {@code toolchainContexts}.
+ if (aspectsList.isEmpty()
+ || (owningAspect != null
+ && owningAspect.equals(Iterables.getLast(aspects).getAspectClass()))) {
+ throw new Failure(
+ fromRule != null ? fromRule.getLocation() : null,
+ String.format(
+ "Attr '%s' declares a transition for non-existent exec group '%s'",
+ attribute.getName(), execGroup));
+ } else {
+ continue;
+ }
}
if (toolchainContexts.getToolchainContext(execGroup).executionPlatform() != null) {
executionPlatformLabel =
diff --git a/src/test/shell/integration/aspect_test.sh b/src/test/shell/integration/aspect_test.sh
index 83eeb16..39833ff 100755
--- a/src/test/shell/integration/aspect_test.sh
+++ b/src/test/shell/integration/aspect_test.sh
@@ -1280,4 +1280,202 @@
expect_not_log 'aspect_a on target @//test:t3, aspects_path = \["//test:defs.bzl%aspect_b\[p=\\\"4\\\"\]", "//test:defs.bzl%aspect_b\[p=\\\"3\\\"\]", "//test:defs.bzl%aspect_a"\]'
}
+function test_aspect_on_target_with_exec_gp() {
+ local package="test"
+ mkdir -p "${package}"
+
+ cat > "${package}/defs.bzl" <<EOF
+def _aspect_a_impl(target, ctx):
+ print('aspect_a on target {}'.format(target.label))
+ return []
+
+aspect_a = aspect(
+ implementation = _aspect_a_impl,
+ attr_aspects = ['deps'],
+)
+
+def _rule_impl(ctx):
+ pass
+
+r1 = rule(
+ implementation = _rule_impl,
+ attrs = {
+ 'deps' : attr.label_list(aspects=[aspect_a]),
+ },
+)
+
+r2 = rule(
+ implementation = _rule_impl,
+ attrs = {
+ '_tool' : attr.label(
+ default = "//${package}:tool",
+ cfg = config.exec(exec_group = "exec_gp")),
+ },
+ exec_groups = {"exec_gp": exec_group()},
+)
+EOF
+
+ cat > "${package}/tool.sh" <<EOF
+EOF
+
+ cat > "${package}/BUILD" <<EOF
+load('//test:defs.bzl', 'r1', 'r2')
+r1(
+ name = 't1',
+ deps = [':t2'],
+)
+r2(
+ name = 't2',
+)
+
+sh_binary(name = "tool", srcs = ["tool.sh"])
+EOF
+
+ bazel build "//${package}:t1" \
+ &> $TEST_log || fail "Build failed"
+
+ expect_log 'aspect_a on target @//test:t2'
+}
+
+function test_aspect_on_aspect_with_exec_gp() {
+ local package="test"
+ mkdir -p "${package}"
+
+ cat > "${package}/defs.bzl" <<EOF
+prov_b = provider()
+
+def _aspect_a_impl(target, ctx):
+ print('aspect_a on target {}'.format(target.label))
+ return []
+
+aspect_a = aspect(
+ implementation = _aspect_a_impl,
+ attr_aspects = ['deps'],
+ required_aspect_providers = [prov_b],
+)
+
+def _aspect_b_impl(target, ctx):
+ print('aspect_b on target {}'.format(target.label))
+ return [prov_b()]
+
+aspect_b = aspect(
+ implementation = _aspect_b_impl,
+ attr_aspects = ['deps'],
+ provides = [prov_b],
+ attrs = {
+ '_tool' : attr.label(
+ default = "//${package}:tool",
+ cfg = config.exec(exec_group = "exec_gp")),
+ },
+ exec_groups = {"exec_gp": exec_group()},
+)
+
+def _rule_impl(ctx):
+ pass
+
+r1 = rule(
+ implementation = _rule_impl,
+ attrs = {
+ 'deps' : attr.label_list(aspects=[aspect_b, aspect_a]),
+ },
+)
+
+r2 = rule(
+ implementation = _rule_impl,
+)
+EOF
+
+ cat > "${package}/tool.sh" <<EOF
+EOF
+
+ cat > "${package}/BUILD" <<EOF
+load('//test:defs.bzl', 'r1', 'r2')
+r1(
+ name = 't1',
+ deps = [':t2'],
+)
+r2(
+ name = 't2',
+)
+
+sh_binary(name = "tool", srcs = ["tool.sh"])
+EOF
+
+ bazel build "//${package}:t1" \
+ &> $TEST_log || fail "Build failed"
+
+ expect_log 'aspect_a on target @//test:t2'
+ expect_log 'aspect_b on target @//test:t2'
+}
+
+function test_aspect_on_aspect_with_missing_exec_gp() {
+ local package="test"
+ mkdir -p "${package}"
+
+ cat > "${package}/defs.bzl" <<EOF
+prov_b = provider()
+
+def _aspect_a_impl(target, ctx):
+ print('aspect_a on target {}'.format(target.label))
+ return []
+
+aspect_a = aspect(
+ implementation = _aspect_a_impl,
+ attr_aspects = ['deps'],
+ required_aspect_providers = [prov_b],
+ attrs = {
+ '_tool' : attr.label(
+ default = "//${package}:tool",
+ # exec_gp not declared
+ cfg = config.exec(exec_group = "exec_gp")),
+ },
+)
+
+def _aspect_b_impl(target, ctx):
+ print('aspect_b on target {}'.format(target.label))
+ return [prov_b()]
+
+aspect_b = aspect(
+ implementation = _aspect_b_impl,
+ attr_aspects = ['deps'],
+ provides = [prov_b],
+)
+
+def _rule_impl(ctx):
+ pass
+
+r1 = rule(
+ implementation = _rule_impl,
+ attrs = {
+ 'deps' : attr.label_list(aspects=[aspect_b, aspect_a]),
+ },
+)
+
+r2 = rule(
+ implementation = _rule_impl,
+)
+EOF
+
+ cat > "${package}/tool.sh" <<EOF
+EOF
+
+ cat > "${package}/BUILD" <<EOF
+load('//test:defs.bzl', 'r1', 'r2')
+r1(
+ name = 't1',
+ deps = [':t2'],
+)
+r2(
+ name = 't2',
+)
+
+sh_binary(name = "tool", srcs = ["tool.sh"])
+EOF
+
+ bazel build "//${package}:t1" \
+ &> $TEST_log && fail "Build succeeded, expected to fail"
+
+ expect_log "Attr '\$tool' declares a transition for non-existent exec group 'exec_gp'"
+}
+
run_suite "Tests for aspects"