| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.build.lib.analysis.constraints; |
| |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Verify; |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimap; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.IncompatiblePlatformProvider; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider.RemovedEnvironmentCulprit; |
| import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; |
| import com.google.devtools.build.lib.packages.EnvironmentGroup; |
| import com.google.devtools.build.lib.packages.EnvironmentLabels; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.pkgcache.PackageManager; |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis; |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.skyframe.SaneAnalysisException; |
| import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.StringJoiner; |
| import java.util.function.Function; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Constraint semantics that apply to top-level targets. |
| * |
| * <p>Top-level targets are "special" because they have no parents that can assert expected |
| * environment compatibility. So these expectations have to be declared by other means. |
| * |
| * <p>For all other targets see {@link ConstraintSemantics}. |
| */ |
| public class TopLevelConstraintSemantics { |
| private final RuleContextConstraintSemantics constraintSemantics; |
| private final PackageManager packageManager; |
| private final Function<BuildConfigurationKey, BuildConfigurationValue> configurationProvider; |
| private final ExtendedEventHandler eventHandler; |
| private static final String TARGET_INCOMPATIBLE_ERROR_TEMPLATE = |
| "Target %s is incompatible and cannot be built, but was explicitly requested.%s"; |
| |
| /** |
| * Constructor with helper classes for loading targets. |
| * |
| * @param constraintSemantics core constraints implementation logic |
| * @param packageManager object for retrieving loaded targets |
| * @param configurationProvider gets configurations from {@link ConfiguredTarget}s |
| * @param eventHandler the build's event handler |
| */ |
| public TopLevelConstraintSemantics( |
| RuleContextConstraintSemantics constraintSemantics, |
| PackageManager packageManager, |
| Function<BuildConfigurationKey, BuildConfigurationValue> configurationProvider, |
| ExtendedEventHandler eventHandler) { |
| this.constraintSemantics = constraintSemantics; |
| this.packageManager = packageManager; |
| this.configurationProvider = configurationProvider; |
| this.eventHandler = eventHandler; |
| } |
| |
| static class MissingEnvironment { |
| private final Label environment; |
| @Nullable |
| // If null, the top-level target just didn't declare a required environment. If not null, that |
| // means the declaration got "refined" away due to some select() somewhere in its deps. See |
| // ConstraintSemantics's documentation for an explanation of refinement. |
| private final RemovedEnvironmentCulprit culprit; |
| private MissingEnvironment(Label environment, RemovedEnvironmentCulprit culprit) { |
| this.environment = environment; |
| this.culprit = culprit; |
| } |
| } |
| |
| /** |
| * Returns the compatibility of a ConfiguredTarget with the platform. |
| * |
| * <p>See {@link #checkPlatformRestrictions}. |
| */ |
| public static PlatformCompatibility compatibilityWithPlatformRestrictions( |
| ConfiguredTarget configuredTarget, |
| ExtendedEventHandler eventHandler, |
| boolean eagerlyThrowError, |
| boolean explicitlyRequested, |
| boolean skipIncompatibleExplicitTargets) |
| throws TargetCompatibilityCheckException { |
| |
| RuleContextConstraintSemantics.IncompatibleCheckResult incompatibleCheckResult = |
| RuleContextConstraintSemantics.checkForIncompatibility(configuredTarget); |
| if (!incompatibleCheckResult.isIncompatible()) { |
| return PlatformCompatibility.COMPATIBLE; |
| } |
| |
| // We need the label in unambiguous form here. I.e. with the "@" prefix for targets in the |
| // main repository. explicitTargetPatterns is also already in the unambiguous form to make |
| // comparison succeed regardless of the provided form. |
| if (!skipIncompatibleExplicitTargets && explicitlyRequested) { |
| if (eagerlyThrowError) { |
| // Use the slightly simpler form for printing error messages. I.e. no "@" prefix for |
| // targets in the main repository. |
| throw getExceptionForExplicitlyRequestedIncompatibleTarget( |
| configuredTarget, incompatibleCheckResult.underlyingTarget()); |
| } |
| eventHandler.handle( |
| Event.warn( |
| String.format(TARGET_INCOMPATIBLE_ERROR_TEMPLATE, configuredTarget.getLabel(), ""))); |
| return PlatformCompatibility.INCOMPATIBLE_EXPLICIT; |
| } |
| // We can safely skip this target if it wasn't explicitly requested or we've been instructed |
| // to skip explicitly requested targets. |
| return PlatformCompatibility.INCOMPATIBLE_IMPLICIT; |
| } |
| |
| private static TargetCompatibilityCheckException |
| getExceptionForExplicitlyRequestedIncompatibleTarget( |
| ConfiguredTarget configuredTarget, ConfiguredTarget underlyingTarget) { |
| String targetIncompatibleMessage = |
| String.format( |
| TARGET_INCOMPATIBLE_ERROR_TEMPLATE, |
| configuredTarget.getLabel(), |
| // We need access to the provider so we pass in the underlying target here that is |
| // responsible for the incompatibility. |
| reportOnIncompatibility(underlyingTarget)); |
| return new TargetCompatibilityCheckException( |
| targetIncompatibleMessage, |
| FailureDetail.newBuilder() |
| .setMessage(targetIncompatibleMessage) |
| .setAnalysis(Analysis.newBuilder().setCode(Code.INCOMPATIBLE_TARGET_REQUESTED)) |
| .build()); |
| } |
| |
| /** |
| * Returns the compatibility with the target environment. |
| * |
| * <p>See {@link #checkTargetEnvironmentRestrictions}. |
| * |
| * @return null if the {@code targetLookup} performs a Skyframe lookup and the value is missing. |
| */ |
| @Nullable |
| public static EnvironmentCompatibility compatibilityWithTargetEnvironment( |
| ConfiguredTarget configuredTarget, |
| @Nullable BuildConfigurationValue buildConfigurationValue, |
| TargetLookup targetLookup, |
| ExtendedEventHandler eventHandler) |
| throws InterruptedException, TargetCompatibilityCheckException { |
| Target target; |
| try { |
| target = Preconditions.checkNotNull(targetLookup.getTarget(configuredTarget.getLabel())); |
| } catch (NoSuchPackageException | NoSuchTargetException e) { |
| eventHandler.handle( |
| Event.error( |
| "Unable to get target from package when checking environment restrictions. " + e)); |
| return EnvironmentCompatibility.compatible(); |
| } |
| // TODO(bazel-team): support file targets (they should apply package-default constraints. |
| if (buildConfigurationValue == null |
| || !buildConfigurationValue |
| .enforceConstraints() // Constraint checking is disabled for all targets. |
| || target.getAssociatedRule() == null |
| || !target |
| .getAssociatedRule() |
| .getRuleClassObject() |
| .supportsConstraintChecking() // This target doesn't participate in constraints. |
| ) { |
| return EnvironmentCompatibility.compatible(); |
| } |
| |
| // Check explicitly expected environments. |
| ImmutableSet<MissingEnvironment> severeMissingEnvironments = |
| getMissingEnvironments( |
| configuredTarget, buildConfigurationValue.getTargetEnvironments(), targetLookup); |
| // Missing value. |
| if (severeMissingEnvironments == null) { |
| return null; |
| } |
| |
| if (!severeMissingEnvironments.isEmpty()) { |
| return EnvironmentCompatibility.severeIncompatible(severeMissingEnvironments); |
| } |
| |
| // Check auto-detected CPU environments. |
| try { |
| ImmutableSet<MissingEnvironment> nonSevereMissingEnvironment = |
| getMissingEnvironments( |
| configuredTarget, |
| autoConfigureTargetEnvironments( |
| buildConfigurationValue, |
| buildConfigurationValue.getAutoCpuEnvironmentGroup(), |
| targetLookup), |
| targetLookup); |
| if (nonSevereMissingEnvironment == null) { |
| return null; |
| } |
| if (!nonSevereMissingEnvironment.isEmpty()) { |
| return EnvironmentCompatibility.nonSevereIncompatible(); |
| } |
| } catch (NoSuchPackageException | NoSuchTargetException e) { |
| throw new TargetCompatibilityCheckException( |
| "invalid target environment", e.getDetailedExitCode().getFailureDetail(), e); |
| } |
| return EnvironmentCompatibility.compatible(); |
| } |
| |
| /** |
| * Checks that the all top-level targets are compatible with the target platform. |
| * |
| * <p>If any target doesn't support the target platform it will be either marked as "to be |
| * skipped" or marked as "errored". |
| * |
| * <p>Targets that are incompatible with the target platform and are not explicitly requested on |
| * the command line should be skipped. |
| * |
| * <p>Targets that are incompatible with the target platform and *are* explicitly requested on the |
| * command line are errored unless --skip_incompatible_explicit_targets is enabled. Having one or |
| * more errored targets will cause the entire build to fail with an error message. |
| * |
| * @param topLevelTargets the build's top-level targets |
| * @param explicitTargetPatterns the set of explicit target patterns specified by the user on the |
| * command line. Every target must be in the unambiguous canonical form (i.e., with the "@" |
| * prefix for all targets including in the main repository). |
| * @return the set of to-be-skipped and errored top-level targets. |
| * @throws ViewCreationFailedException if any top-level target was explicitly requested on the |
| * command line. |
| */ |
| public PlatformRestrictionsResult checkPlatformRestrictions( |
| ImmutableSet<ConfiguredTarget> topLevelTargets, |
| ImmutableSet<Label> explicitTargetPatterns, |
| boolean keepGoing, |
| boolean skipIncompatibleExplicitTargets) |
| throws ViewCreationFailedException { |
| ImmutableSet.Builder<ConfiguredTarget> incompatibleTargets = ImmutableSet.builder(); |
| ImmutableSet.Builder<ConfiguredTarget> incompatibleButRequestedTargets = ImmutableSet.builder(); |
| |
| try { |
| for (ConfiguredTarget target : topLevelTargets) { |
| PlatformCompatibility platformCompatibility = |
| compatibilityWithPlatformRestrictions( |
| target, |
| eventHandler, |
| /* eagerlyThrowError= */ !keepGoing, |
| explicitTargetPatterns.contains(target.getLabel()), |
| skipIncompatibleExplicitTargets); |
| if (PlatformCompatibility.INCOMPATIBLE_EXPLICIT.equals(platformCompatibility)) { |
| incompatibleButRequestedTargets.add(target); |
| } else if (PlatformCompatibility.INCOMPATIBLE_IMPLICIT.equals(platformCompatibility)) { |
| incompatibleTargets.add(target); |
| } |
| } |
| } catch (TargetCompatibilityCheckException e) { |
| throw new ViewCreationFailedException(e.getFailureDetail(), /*cause=*/ e); |
| } |
| |
| return PlatformRestrictionsResult.builder() |
| .targetsToSkip(ImmutableSet.copyOf(incompatibleTargets.build())) |
| .targetsWithErrors(ImmutableSet.copyOf(incompatibleButRequestedTargets.build())) |
| .build(); |
| } |
| |
| /** |
| * Assembles the explanation for a platform incompatibility. |
| * |
| * <p>This is useful when trying to explain to the user why an explicitly requested target on the |
| * command line is considered incompatible. The goal is to print out the dependency chain and the |
| * constraint that wasn't satisfied so that the user can immediately figure out what happened. |
| * |
| * @param target the incompatible target that was explicitly requested on the command line. |
| * @return the verbose error message to show to the user. |
| */ |
| private static String reportOnIncompatibility(ConfiguredTarget target) { |
| Preconditions.checkNotNull(target); |
| |
| String message = "\nDependency chain:"; |
| IncompatiblePlatformProvider provider = null; |
| |
| // TODO(austinschuh): While the first error is helpful, reporting all the errors at once would |
| // save the user bazel round trips. |
| while (target != null) { |
| message += |
| String.format( |
| "\n %s (%s)", |
| target.getLabel(), target.getConfigurationChecksum().substring(0, 6)); |
| provider = target.get(IncompatiblePlatformProvider.PROVIDER); |
| ImmutableList<ConfiguredTarget> targetList = provider.targetsResponsibleForIncompatibility(); |
| if (targetList == null) { |
| target = null; |
| } else { |
| target = targetList.get(0); |
| } |
| } |
| |
| message += |
| String.format( |
| " <-- target platform (%s) didn't satisfy constraint", provider.targetPlatform()); |
| if (provider.constraintsResponsibleForIncompatibility().size() == 1) { |
| message += " " + provider.constraintsResponsibleForIncompatibility().get(0).label(); |
| return message; |
| } |
| |
| message += "s ["; |
| |
| boolean first = true; |
| for (ConstraintValueInfo constraintValueInfo : |
| provider.constraintsResponsibleForIncompatibility()) { |
| if (first) { |
| first = false; |
| } else { |
| message += ", "; |
| } |
| message += constraintValueInfo.label(); |
| } |
| |
| message += "]"; |
| |
| return message; |
| } |
| |
| /** |
| * Checks that if this is an environment-restricted build, all top-level targets support expected |
| * top-level environments. Expected top-level environments can be declared explicitly through |
| * {@code --target_environment} or implicitly through {@code --auto_cpu_environment_group}. For |
| * the latter, top-level targets must be compatible with the build's target configuration CPU. |
| * |
| * <p>If any target doesn't support an explicitly expected environment declared through {@link |
| * CoreOptions#targetEnvironments}, the entire build fails with an error. |
| * |
| * <p>If any target doesn't support an implicitly expected environment declared through {@link |
| * CoreOptions#autoCpuEnvironmentGroup}, the target is skipped during execution while remaining |
| * targets execute as normal. |
| * |
| * @param topLevelTargets the build's top-level targets |
| * @return the set of bad top-level targets. |
| * @throws ViewCreationFailedException if any target doesn't support an explicitly expected |
| * environment declared through {@link CoreOptions#targetEnvironments} |
| */ |
| public Set<ConfiguredTarget> checkTargetEnvironmentRestrictions( |
| ImmutableSet<ConfiguredTarget> topLevelTargets) |
| throws ViewCreationFailedException, InterruptedException { |
| ImmutableSet.Builder<ConfiguredTarget> badTargets = ImmutableSet.builder(); |
| // Maps targets that are missing *explicitly* required environments to the set of environments |
| // they're missing. These targets trigger a ViewCreationFailedException, which halts the build. |
| // Targets with missing *implicitly* required environments don't belong here, since the build |
| // continues while skipping them. |
| Multimap<ConfiguredTarget, MissingEnvironment> exceptionInducingTargets = |
| ArrayListMultimap.create(); |
| try { |
| for (ConfiguredTarget topLevelTarget : topLevelTargets) { |
| EnvironmentCompatibility compatibility = |
| Preconditions.checkNotNull( |
| compatibilityWithTargetEnvironment( |
| topLevelTarget, |
| configurationProvider.apply(topLevelTarget.getConfigurationKey()), |
| label -> packageManager.getTarget(eventHandler, label), |
| eventHandler)); |
| if (compatibility.isCompatible()) { |
| continue; |
| } |
| if (compatibility.severeMissingEnvironments() != null) { |
| exceptionInducingTargets.putAll( |
| topLevelTarget, compatibility.severeMissingEnvironments()); |
| } |
| badTargets.add(topLevelTarget); |
| } |
| } catch (TargetCompatibilityCheckException e) { |
| throw new ViewCreationFailedException(e.getMessage(), e.getFailureDetail(), e); |
| } |
| |
| if (!exceptionInducingTargets.isEmpty()) { |
| String badTargetsUserMessage = |
| getBadTargetsUserMessage(constraintSemantics, exceptionInducingTargets); |
| throw new ViewCreationFailedException( |
| badTargetsUserMessage, |
| FailureDetail.newBuilder() |
| .setMessage(badTargetsUserMessage) |
| .setAnalysis(Analysis.newBuilder().setCode(Code.TARGETS_MISSING_ENVIRONMENTS)) |
| .build()); |
| } |
| return badTargets.build(); |
| } |
| |
| /** |
| * Helper method for {@link #checkTargetEnvironmentRestrictions} that populates inferred expected |
| * environments. |
| */ |
| @Nullable |
| private static ImmutableList<Label> autoConfigureTargetEnvironments( |
| BuildConfigurationValue config, |
| @Nullable Label environmentGroupLabel, |
| TargetLookup targetLookup) |
| throws InterruptedException, NoSuchTargetException, NoSuchPackageException { |
| if (environmentGroupLabel == null) { |
| return ImmutableList.of(); |
| } |
| |
| EnvironmentGroup environmentGroup = |
| (EnvironmentGroup) targetLookup.getTarget(environmentGroupLabel); |
| // Missing value. |
| if (environmentGroup == null) { |
| return null; |
| } |
| |
| ImmutableList.Builder<Label> targetEnvironments = new ImmutableList.Builder<>(); |
| for (Label environmentLabel : environmentGroup.getEnvironments()) { |
| if (environmentLabel.getName().equals(config.getCpu())) { |
| targetEnvironments.add(environmentLabel); |
| } |
| } |
| |
| return targetEnvironments.build(); |
| } |
| |
| /** |
| * Returns the expected environments that the given top-level target doesn't support. |
| * |
| * @param topLevelTarget the top-level target to check |
| * @param expectedEnvironmentLabels the environments this target is expected to support |
| * @param targetLookup a function that is used to look up a Target given its Label. |
| * @throws InterruptedException if environment target resolution fails |
| * @throws TargetCompatibilityCheckException if an expected environment isn't a valid target |
| */ |
| @Nullable |
| private static ImmutableSet<MissingEnvironment> getMissingEnvironments( |
| ConfiguredTarget topLevelTarget, |
| @Nullable Collection<Label> expectedEnvironmentLabels, |
| TargetLookup targetLookup) |
| throws InterruptedException, TargetCompatibilityCheckException { |
| // Missing value. |
| if (expectedEnvironmentLabels == null) { |
| return null; |
| } |
| if (expectedEnvironmentLabels.isEmpty()) { |
| return ImmutableSet.of(); |
| } |
| |
| // Convert expected environment labels to actual environments. |
| EnvironmentCollection.Builder expectedEnvironmentsBuilder = new EnvironmentCollection.Builder(); |
| for (Label envLabel : expectedEnvironmentLabels) { |
| try { |
| Target env = targetLookup.getTarget(envLabel); |
| // Missing value. |
| if (env == null) { |
| return null; |
| } |
| expectedEnvironmentsBuilder.put( |
| ConstraintSemantics.getEnvironmentGroup(env).getEnvironmentLabels(), envLabel); |
| } catch (NoSuchPackageException |
| | NoSuchTargetException |
| | ConstraintSemantics.EnvironmentLookupException e) { |
| throw new TargetCompatibilityCheckException( |
| "invalid target environment: " + e.getMessage(), |
| e.getDetailedExitCode().getFailureDetail(), |
| e); |
| } |
| } |
| EnvironmentCollection expectedEnvironments = expectedEnvironmentsBuilder.build(); |
| |
| // Dereference any aliases that might be present. |
| topLevelTarget = topLevelTarget.getActual(); |
| // Now check the target against expected environments. |
| TransitiveInfoCollection asProvider; |
| if (topLevelTarget instanceof OutputFileConfiguredTarget) { |
| asProvider = ((OutputFileConfiguredTarget) topLevelTarget).getGeneratingRule(); |
| } else { |
| asProvider = topLevelTarget; |
| } |
| SupportedEnvironmentsProvider provider = |
| Verify.verifyNotNull(asProvider.getProvider(SupportedEnvironmentsProvider.class)); |
| ImmutableSet.Builder<MissingEnvironment> ans = ImmutableSet.builder(); |
| for (Label unsupportedEnv : |
| RuleContextConstraintSemantics.getUnsupportedEnvironments( |
| provider.getRefinedEnvironments(), expectedEnvironments)) { |
| // We apply this filter because the target might also not support default environments in |
| // other environment groups. We don't care about those. We only care about the environments |
| // explicitly referenced. |
| if (!expectedEnvironmentLabels.contains(unsupportedEnv)) { |
| continue; |
| } |
| |
| List<Label> envAndFulfillers = new ArrayList<>(); |
| envAndFulfillers.add(unsupportedEnv); |
| for (EnvironmentLabels envGroup : provider.getStaticEnvironments().getGroups()) { |
| envAndFulfillers.addAll(envGroup.getFulfillers(unsupportedEnv).toList()); |
| } |
| RemovedEnvironmentCulprit culprit = null; |
| for (int i = 0; i < envAndFulfillers.size() && culprit == null; i++) { |
| culprit = provider.getRemovedEnvironmentCulprit(envAndFulfillers.get(i)); |
| } |
| // culprit could still be null here. See MissingEnvironment class comments for implications. |
| ans.add(new MissingEnvironment(unsupportedEnv, culprit)); |
| } |
| return ans.build(); |
| } |
| |
| /** |
| * Prepares a user-friendly error message for a list of targets missing support for required |
| * environments. |
| */ |
| private static String getBadTargetsUserMessage( |
| RuleContextConstraintSemantics constraintSemantics, |
| Multimap<ConfiguredTarget, MissingEnvironment> badTargets) { |
| StringJoiner msg = new StringJoiner("\n"); |
| msg.add("This is a restricted-environment build."); |
| for (Map.Entry<ConfiguredTarget, Collection<MissingEnvironment>> entry : |
| badTargets.asMap().entrySet()) { |
| msg.add(getErrorMessageForTarget(constraintSemantics, entry.getKey(), entry.getValue())); |
| } |
| return msg.add(" ").toString(); |
| } |
| |
| public static String getErrorMessageForTarget( |
| RuleContextConstraintSemantics constraintSemantics, |
| ConfiguredTarget configuredTarget, |
| Collection<MissingEnvironment> missingEnvironments) { |
| StringJoiner msg = new StringJoiner("\n"); |
| ConfiguredTarget targetWithProvider = configuredTarget.getActual(); |
| if (targetWithProvider instanceof OutputFileConfiguredTarget) { |
| targetWithProvider = ((OutputFileConfiguredTarget) targetWithProvider).getGeneratingRule(); |
| } |
| SupportedEnvironmentsProvider supportedEnvironments = |
| targetWithProvider.getProvider(SupportedEnvironmentsProvider.class); |
| String declaredEnvs = |
| supportedEnvironments.getStaticEnvironments().getEnvironments().stream() |
| .map(Label::toString) |
| .collect(joining(", ")); |
| ; |
| msg.add(" ") |
| .add(configuredTarget.getLabel() + " declares compatibility with:") |
| .add(" [" + declaredEnvs + "]") |
| .add("but does not support:"); |
| boolean isFirst = true; |
| boolean lastEntryWasMultiline = false; |
| for (MissingEnvironment missingEnvironment : missingEnvironments) { |
| if (missingEnvironment.culprit == null) { |
| // The target didn't declare support for this environment. |
| if (lastEntryWasMultiline) { |
| // Pretty-format: if the last environment message was multi-line, make it clear this |
| // one is a different entry. But we don't want to do that if all entries are single-line |
| // because that would be pointlessly long. |
| msg.add(" "); |
| } |
| msg.add(" " + missingEnvironment.environment); |
| lastEntryWasMultiline = false; |
| } else { |
| // The target declared support, but it was refined out by a select() somewhere in its |
| // transitive deps. |
| if (!isFirst) { |
| msg.add(" "); // Pretty-format for clarity. |
| } |
| msg.add( |
| constraintSemantics.getMissingEnvironmentCulpritMessage( |
| configuredTarget.getLabel(), |
| missingEnvironment.environment, |
| missingEnvironment.culprit)); |
| lastEntryWasMultiline = true; |
| } |
| isFirst = false; |
| } |
| return msg.toString(); |
| } |
| |
| /** Tells the compatibility of a ConfiguredTarget with the target environment. */ |
| @AutoValue |
| public abstract static class EnvironmentCompatibility { |
| public abstract boolean isCompatible(); |
| |
| @Nullable |
| public abstract ImmutableSet<MissingEnvironment> severeMissingEnvironments(); |
| |
| public static EnvironmentCompatibility compatible() { |
| return new AutoValue_TopLevelConstraintSemantics_EnvironmentCompatibility( |
| /*isCompatible=*/ true, /*severeMissingEnvironments=*/ null); |
| } |
| |
| public static EnvironmentCompatibility nonSevereIncompatible() { |
| return new AutoValue_TopLevelConstraintSemantics_EnvironmentCompatibility( |
| /*isCompatible=*/ false, /*severeMissingEnvironments=*/ null); |
| } |
| |
| public static EnvironmentCompatibility severeIncompatible( |
| ImmutableSet<MissingEnvironment> severeMissingEnvironments) { |
| return new AutoValue_TopLevelConstraintSemantics_EnvironmentCompatibility( |
| /*isCompatible=*/ false, severeMissingEnvironments); |
| } |
| } |
| |
| /** Tells the compatibility of a ConfiguredTarget with the platform. */ |
| public enum PlatformCompatibility { |
| COMPATIBLE, |
| INCOMPATIBLE_IMPLICIT, |
| INCOMPATIBLE_EXPLICIT |
| } |
| |
| /** For Exceptions that arise during the compatibility checking of a target. */ |
| public static class TargetCompatibilityCheckException extends Exception |
| implements SaneAnalysisException { |
| private final FailureDetail failureDetail; |
| |
| public TargetCompatibilityCheckException(String message, FailureDetail failureDetail) { |
| super(message); |
| this.failureDetail = failureDetail; |
| } |
| |
| public TargetCompatibilityCheckException( |
| String message, FailureDetail failureDetail, Throwable cause) { |
| super(message, cause); |
| this.failureDetail = failureDetail; |
| } |
| |
| public FailureDetail getFailureDetail() { |
| return failureDetail; |
| } |
| |
| @Override |
| public DetailedExitCode getDetailedExitCode() { |
| return DetailedExitCode.of(failureDetail); |
| } |
| } |
| |
| /** Provides a method to look up a Target, given its Label. */ |
| @FunctionalInterface |
| public interface TargetLookup { |
| // Returns null if the implementation involves a Skyframe lookup and the value is missing. |
| @Nullable |
| Target getTarget(Label label) |
| throws NoSuchPackageException, NoSuchTargetException, InterruptedException; |
| } |
| } |