Skip to content

Commit 3e969ff

Browse files
philsccopybara-github
authored andcommitted
Fix a couple of bugs with Incompatible Target Skipping
While adding `target_compatible_with` attributes in the FRC team 971's code base I came across bug #12553. It wasn't possible to make a Python target depend on another incompatible Python target. This patch fixes that issue by teaching `RuleContext` about incompatible prerequisites. This also fixes an issue with the validation of file extensions. It's possible that `validateDirectPrerequisite()` skips a bit too much validation. It was unfortunately the cleanest way I could think of. I struggled a bit to come up with what ended up becoming `RuleContextConstraintSemantics.IncompatibleCheckResult`. The purpose of that class is to centralize the logic for checking for `OutputFileConfiguredTarget` dependencies. Such dependencies need a bit of special logic in order to find `IncompatiblePlatformProvider` instances. When I implemented this patch, I realized that the `TopLevelConstraintSemantics` logic had a very similar problem. It didn't deal with the `OutputFileConfiguredTarget` problem at all. I took the liberty of fixing that issue in this patch also because it nicely re-used the same new helper. I could not figure out a good way to avoid making `RuleContext` depend on `RuleContextConstraintSemantics`. Fixes #12553 Closes #12560. PiperOrigin-RevId: 346796174
1 parent 230be16 commit 3e969ff

File tree

4 files changed

+162
-21
lines changed

4 files changed

+162
-21
lines changed

src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
5555
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
5656
import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics;
57+
import com.google.devtools.build.lib.analysis.constraints.RuleContextConstraintSemantics;
5758
import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
5859
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
5960
import com.google.devtools.build.lib.analysis.stringtemplate.TemplateContext;
@@ -2306,6 +2307,14 @@ private boolean checkRuleDependencyMandatoryProviders(
23062307

23072308
private void validateDirectPrerequisite(
23082309
Attribute attribute, ConfiguredTargetAndData prerequisite) {
2310+
if (RuleContextConstraintSemantics.checkForIncompatibility(prerequisite.getConfiguredTarget())
2311+
.isIncompatible()) {
2312+
// If the prerequisite is incompatible (e.g. has an incompatible provider), we pretend that
2313+
// there is no further validation needed. Otherwise, it would be difficult to make the
2314+
// incompatible target satisfy things like required providers and file extensions.
2315+
return;
2316+
}
2317+
23092318
validateDirectPrerequisiteType(prerequisite, attribute);
23102319
validateDirectPrerequisiteFileTypes(prerequisite, attribute);
23112320
if (attribute.performPrereqValidatorCheck()) {

src/main/java/com/google/devtools/build/lib/analysis/constraints/RuleContextConstraintSemantics.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.collect.ImmutableList.toImmutableList;
1818
import static com.google.common.collect.Streams.stream;
1919

20+
import com.google.auto.value.AutoValue;
2021
import com.google.common.base.Joiner;
2122
import com.google.common.base.Preconditions;
2223
import com.google.common.base.Verify;
@@ -830,6 +831,45 @@ private static void addSelectValuesToSet(BuildType.Selector<?> select, final Set
830831
}
831832
}
832833

834+
/**
835+
* Provides information about a target's incompatibility.
836+
*
837+
* <p>After calling {@code checkForIncompatibility()}, the {@code isIncompatible} getter tells you
838+
* whether the target is incompatible. If the target is incompatible, then {@code
839+
* underlyingTarget} tells you which underlying target provided the incompatibility. For the vast
840+
* majority of targets this is the same one passed to {@code checkForIncompatibility()}. In some
841+
* instances like {@link OutputFileConfiguredTarget}, however, the {@code underlyingTarget} is the
842+
* rule that generated the file.
843+
*/
844+
@AutoValue
845+
public abstract static class IncompatibleCheckResult {
846+
private static IncompatibleCheckResult create(
847+
boolean isIncompatible, ConfiguredTarget underlyingTarget) {
848+
return new AutoValue_RuleContextConstraintSemantics_IncompatibleCheckResult(
849+
isIncompatible, underlyingTarget);
850+
}
851+
852+
public abstract boolean isIncompatible();
853+
854+
public abstract ConfiguredTarget underlyingTarget();
855+
}
856+
857+
/**
858+
* Checks whether the target is incompatible.
859+
*
860+
* <p>See the documentation for {@link RuleContextConstraintSemantics.IncompatibleCheckResult} for
861+
* more information.
862+
*/
863+
public static IncompatibleCheckResult checkForIncompatibility(ConfiguredTarget target) {
864+
if (target instanceof OutputFileConfiguredTarget) {
865+
// For generated files, we want to query the generating rule for providers. genrule() for
866+
// example doesn't attach providers like IncompatiblePlatformProvider to its outputs.
867+
target = ((OutputFileConfiguredTarget) target).getGeneratingRule();
868+
}
869+
return IncompatibleCheckResult.create(
870+
target.getProvider(IncompatiblePlatformProvider.class) != null, target);
871+
}
872+
833873
/**
834874
* Creates an incompatible {@link ConfiguredTarget} if the corresponding rule is incompatible.
835875
*
@@ -870,22 +910,12 @@ public static ConfiguredTarget incompatibleConfiguredTarget(
870910
}
871911

872912
// This is incompatible if one of the dependencies is as well.
873-
ImmutableList.Builder<ConfiguredTarget> incompatibleDependenciesBuilder =
874-
ImmutableList.builder();
875-
for (ConfiguredTargetAndData infoCollection : prerequisiteMap.values()) {
876-
ConfiguredTarget dependency = infoCollection.getConfiguredTarget();
877-
if (dependency instanceof OutputFileConfiguredTarget) {
878-
// For generated files, we want to query the generating rule for providers. genrule() for
879-
// example doesn't attach providers like IncompatiblePlatformProvider to its outputs.
880-
dependency = ((OutputFileConfiguredTarget) dependency).getGeneratingRule();
881-
}
882-
if (dependency.getProvider(IncompatiblePlatformProvider.class) != null) {
883-
incompatibleDependenciesBuilder.add(dependency);
884-
}
885-
}
886-
887913
ImmutableList<ConfiguredTarget> incompatibleDependencies =
888-
incompatibleDependenciesBuilder.build();
914+
prerequisiteMap.values().stream()
915+
.map(value -> checkForIncompatibility(value.getConfiguredTarget()))
916+
.filter(result -> result.isIncompatible())
917+
.map(result -> result.underlyingTarget())
918+
.collect(toImmutableList());
889919
if (!incompatibleDependencies.isEmpty()) {
890920
return createIncompatibleConfiguredTarget(ruleContext, incompatibleDependencies, null);
891921
}
@@ -987,7 +1017,7 @@ private static ConfiguredTarget createIncompatibleConfiguredTarget(
9871017
new FailAction(
9881018
ruleContext.getActionOwner(),
9891019
outputArtifacts,
990-
"Can't build this. This target is incompatible.",
1020+
"Can't build this. This target is incompatible. Please file a bug upstream.",
9911021
Code.CANT_BUILD_INCOMPATIBLE_TARGET));
9921022
}
9931023
return builder.build();

src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ public PlatformRestrictionsResult checkPlatformRestrictions(
123123
ImmutableSet.Builder<ConfiguredTarget> incompatibleButRequestedTargets = ImmutableSet.builder();
124124

125125
for (ConfiguredTarget target : topLevelTargets) {
126-
IncompatiblePlatformProvider incompatibleProvider =
127-
target.getProvider(IncompatiblePlatformProvider.class);
126+
RuleContextConstraintSemantics.IncompatibleCheckResult result =
127+
RuleContextConstraintSemantics.checkForIncompatibility(target);
128128

129129
// Move on to the next target if this one is compatible.
130-
if (incompatibleProvider == null) {
130+
if (!result.isIncompatible()) {
131131
continue;
132132
}
133133

@@ -143,7 +143,9 @@ public PlatformRestrictionsResult checkPlatformRestrictions(
143143
String.format(
144144
TARGET_INCOMPATIBLE_ERROR_TEMPLATE,
145145
target.getLabel().toString(),
146-
reportOnIncompatibility(target));
146+
// We need access to the provider so we pass in the underlying target here that is
147+
// responsible for the incompatibility.
148+
reportOnIncompatibility(result.underlyingTarget()));
147149
throw new ViewCreationFailedException(
148150
targetIncompatibleMessage,
149151
FailureDetail.newBuilder()

src/test/shell/integration/target_compatible_with_test.sh

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,92 @@ EOF
447447
expect_log 'FAILED: Build did NOT complete successfully'
448448
}
449449
450+
# Validates that rules with custom providers are skipped when incompatible.
451+
# This is valuable because we use providers to convey incompatibility.
452+
function test_dependencies_with_providers() {
453+
cat > target_skipping/rules.bzl <<EOF
454+
DummyProvider = provider()
455+
456+
def _dummy_rule_impl(ctx):
457+
return [DummyProvider()]
458+
459+
dummy_rule = rule(
460+
implementation = _dummy_rule_impl,
461+
attrs = {
462+
"deps": attr.label_list(providers=[DummyProvider]),
463+
},
464+
)
465+
EOF
466+
467+
cat >> target_skipping/BUILD <<EOF
468+
load("//target_skipping:rules.bzl", "dummy_rule")
469+
470+
dummy_rule(
471+
name = "dummy1",
472+
target_compatible_with = [
473+
"//target_skipping:foo1",
474+
],
475+
)
476+
477+
dummy_rule(
478+
name = "dummy2",
479+
deps = [
480+
":dummy1",
481+
],
482+
)
483+
EOF
484+
485+
cd target_skipping || fail "couldn't cd into workspace"
486+
487+
pwd >&2
488+
bazel build \
489+
--show_result=10 \
490+
--host_platform=@//target_skipping:foo3_platform \
491+
--platforms=@//target_skipping:foo3_platform \
492+
//target_skipping/... &> "${TEST_log}" || fail "Bazel failed unexpectedly."
493+
expect_log '^Target //target_skipping:dummy2 was skipped'
494+
}
495+
496+
function test_dependencies_with_extensions() {
497+
cat > target_skipping/rules.bzl <<EOF
498+
def _dummy_rule_impl(ctx):
499+
out = ctx.actions.declare_file(ctx.attr.name + ".cc")
500+
ctx.actions.write(out, "Dummy content")
501+
return DefaultInfo(files = depset([out]))
502+
503+
dummy_rule = rule(
504+
implementation = _dummy_rule_impl,
505+
)
506+
EOF
507+
508+
cat >> target_skipping/BUILD <<EOF
509+
load("//target_skipping:rules.bzl", "dummy_rule")
510+
511+
# Generates a dummy.cc file.
512+
dummy_rule(
513+
name = "dummy_file",
514+
target_compatible_with = [":foo1"],
515+
)
516+
517+
cc_library(
518+
name = "dummy_cc_lib",
519+
srcs = [
520+
"dummy_file",
521+
],
522+
)
523+
EOF
524+
525+
cd target_skipping || fail "couldn't cd into workspace"
526+
527+
pwd >&2
528+
bazel build \
529+
--show_result=10 \
530+
--host_platform=@//target_skipping:foo3_platform \
531+
--platforms=@//target_skipping:foo3_platform \
532+
//target_skipping/... &> "${TEST_log}" || fail "Bazel failed unexpectedly."
533+
expect_log '^Target //target_skipping:dummy_cc_lib was skipped'
534+
}
535+
450536
# Validates the same thing as test_non_top_level_skipping, but with a cc_test
451537
# and adding one more level of dependencies.
452538
function test_cc_test() {
@@ -480,6 +566,21 @@ EOF
480566
481567
cd target_skipping || fail "couldn't cd into workspace"
482568
569+
# Validate the generated file that makes up the test.
570+
bazel test \
571+
--show_result=10 \
572+
--host_platform=@//target_skipping:foo2_bar1_platform \
573+
--platforms=@//target_skipping:foo2_bar1_platform \
574+
//target_skipping:generated_test.cc &> "${TEST_log}" && fail "Bazel passed unexpectedly."
575+
expect_log "ERROR: Target //target_skipping:generated_test.cc is incompatible and cannot be built, but was explicitly requested"
576+
577+
# Validate that we get the dependency chain printed out.
578+
expect_log '^Dependency chain:$'
579+
expect_log '^ //target_skipping:generate_with_tool$'
580+
expect_log "^ //target_skipping:generator_tool <-- target platform didn't satisfy constraint //target_skipping:foo1$"
581+
expect_log 'FAILED: Build did NOT complete successfully'
582+
583+
# Validate the test.
483584
bazel test \
484585
--show_result=10 \
485586
--host_platform=@//target_skipping:foo2_bar1_platform \
@@ -727,7 +828,6 @@ EOF
727828
728829
bazel test --show_result=10 \
729830
--host_platform=@//target_skipping:foo3_platform \
730-
--toolchain_resolution_debug \
731831
--platforms=@//target_skipping:foo3_platform \
732832
//target_skipping:foo3_analysistest_test &> "${TEST_log}" \
733833
|| fail "Bazel failed unexpectedly."

0 commit comments

Comments
 (0)