diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java new file mode 100644 index 0000000000..6fd2a9b7fb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java @@ -0,0 +1,190 @@ +// 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 com.google.common.base.Joiner; +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.OutputFileConfiguredTarget; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.ViewCreationFailedException; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.events.ExtendedEventHandler; +import com.google.devtools.build.lib.packages.EnvironmentGroup; +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 java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +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 { + + /** + * 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 --experimental_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 BuildConfiguration.Options#targetEnvironments}, the entire build fails with an error. + * + * <p>If any target doesn't support an implicitly expected environment declared through + * {@link BuildConfiguration.Options#autoCpuEnvironmentGroup}, the target is skipped during + * execution while remaining targets execute as normal. + * + * @param topLevelTargets the build's top-level targets + * @param packageManager object for retrieving loaded targets + * @param eventHandler the build's event handler + * + * @return the set of bad top-level targets. + * @throws ViewCreationFailedException if any target doesn't support an explicitly expected + * environment declared through {@link BuildConfiguration.Options#targetEnvironments} + */ + public static Set<ConfiguredTarget> checkTargetEnvironmentRestrictions( + Iterable<ConfiguredTarget> topLevelTargets, PackageManager packageManager, + ExtendedEventHandler eventHandler) + 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, Label> exceptionInducingTargets = ArrayListMultimap.create(); + for (ConfiguredTarget topLevelTarget : topLevelTargets) { + BuildConfiguration config = topLevelTarget.getConfiguration(); + boolean failBuildIfTargetIsBad = true; + if (config == null) { + // TODO(bazel-team): support file targets (they should apply package-default constraints). + continue; + } else if (!config.enforceConstraints()) { + continue; + } + + List<Label> targetEnvironments = config.getTargetEnvironments(); + if (targetEnvironments.isEmpty()) { + try { + targetEnvironments = autoConfigureTargetEnvironments(config, + config.getAutoCpuEnvironmentGroup(), packageManager, eventHandler); + failBuildIfTargetIsBad = false; + } catch (NoSuchPackageException + | NoSuchTargetException + | ConstraintSemantics.EnvironmentLookupException e) { + throw new ViewCreationFailedException("invalid target environment", e); + } + } + + if (targetEnvironments.isEmpty()) { + continue; + } + + // Parse and collect this configuration's environments. + EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder(); + for (Label envLabel : targetEnvironments) { + try { + Target env = packageManager.getTarget(eventHandler, envLabel); + builder.put(ConstraintSemantics.getEnvironmentGroup(env), envLabel); + } catch (NoSuchPackageException | NoSuchTargetException + | ConstraintSemantics.EnvironmentLookupException e) { + throw new ViewCreationFailedException("invalid target environment", e); + } + } + EnvironmentCollection expectedEnvironments = builder.build(); + + // Now check the target against those environments. + TransitiveInfoCollection asProvider; + if (topLevelTarget instanceof OutputFileConfiguredTarget) { + asProvider = ((OutputFileConfiguredTarget) topLevelTarget).getGeneratingRule(); + } else { + asProvider = topLevelTarget; + } + SupportedEnvironmentsProvider provider = + Verify.verifyNotNull(asProvider.getProvider(SupportedEnvironmentsProvider.class)); + Collection<Label> missingEnvironments = + ConstraintSemantics.getUnsupportedEnvironments( + provider.getRefinedEnvironments(), expectedEnvironments); + if (!missingEnvironments.isEmpty()) { + badTargets.add(topLevelTarget); + if (failBuildIfTargetIsBad) { + exceptionInducingTargets.putAll(topLevelTarget, missingEnvironments); + } + } + } + + if (!exceptionInducingTargets.isEmpty()) { + throw new ViewCreationFailedException(getBadTargetsUserMessage(exceptionInducingTargets)); + } + return ImmutableSet.copyOf(badTargets.build()); + } + + /** + * Helper method for {@link #checkTargetEnvironmentRestrictions} that populates inferred + * expected environments. + */ + private static List<Label> autoConfigureTargetEnvironments(BuildConfiguration config, + @Nullable Label environmentGroupLabel, PackageManager packageManager, + ExtendedEventHandler eventHandler) + throws InterruptedException, NoSuchTargetException, NoSuchPackageException, + ConstraintSemantics.EnvironmentLookupException { + if (environmentGroupLabel == null) { + return ImmutableList.of(); + } + + EnvironmentGroup environmentGroup = (EnvironmentGroup) + packageManager.getTarget(eventHandler, environmentGroupLabel); + + ImmutableList.Builder<Label> targetEnvironments = new ImmutableList.Builder<>(); + for (Label environmentLabel : environmentGroup.getEnvironments()) { + if (environmentLabel.getName().equals(config.getCpu())) { + targetEnvironments.add(environmentLabel); + } + } + + return targetEnvironments.build(); + } + + /** + * Prepares a user-friendly error message for a list of targets missing support for required + * environments. + */ + private static String getBadTargetsUserMessage(Multimap<ConfiguredTarget, Label> badTargets) { + StringBuilder msg = new StringBuilder(); + msg.append("This is a restricted-environment build."); + for (Map.Entry<ConfiguredTarget, Collection<Label>> entry : badTargets.asMap().entrySet()) { + msg.append(String.format("\n - %s does not support required environment%s %s.", + entry.getKey().getLabel(), + entry.getValue().size() == 1 ? "" : "s", + Joiner.on(", ").join(entry.getValue()))); + } + return msg.toString(); + } +} |