diff options
author | 2015-03-05 21:46:34 +0000 | |
---|---|---|
committer | 2015-03-06 09:16:40 +0000 | |
commit | 42bece1e02afbf55b29fe7fea154e9de4e91871d (patch) | |
tree | 0cc403255ae655f3eb12ddef3c6db240f35030a9 /src/main/java/com/google | |
parent | 24b69a72cbb785e4aa2f74f2fb5b3aef8d72e88a (diff) |
Add --target_environment flag. This declares the environment (or set of environments)
the build is being done for.
In other words:
blaze build //foo:all --target_environment=//buildenv/target:gce
declares that this build targets GCE, so all top-level targets must also support GCE.
This essentially allows constraint enforcement to apply to top-level targets, too.
So users can protect against accidentally building targets in configurations they're
not meant to work with.
--
MOS_MIGRATED_REVID=87862252
Diffstat (limited to 'src/main/java/com/google')
5 files changed, 236 insertions, 120 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java index 967074a9db..35edea0032 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java @@ -411,9 +411,9 @@ public final class BuildConfiguration implements Serializable { } @Option(name = "cpu", - defaultValue = "null", - category = "semantics", - help = "The target CPU.") + defaultValue = "null", + category = "semantics", + help = "The target CPU.") public String cpu; @Option(name = "min_param_file_size", @@ -433,15 +433,15 @@ public final class BuildConfiguration implements Serializable { defaultValue = "true", category = "undocumented", help = "Flag to help transition from allowing to disallowing runtime_deps on neverlink" - + " Java archives. The depot needs to be cleaned up to roll this out by default.") + + " Java archives. The depot needs to be cleaned up to roll this out by default.") public boolean allowRuntimeDepsOnNeverLink; @Option(name = "strict_filesets", - defaultValue = "false", - category = "semantics", - help = "If this option is enabled, filesets crossing package boundaries are reported " - + "as errors. It does not work when check_fileset_dependencies_recursively is " - + "disabled.") + defaultValue = "false", + category = "semantics", + help = "If this option is enabled, filesets crossing package boundaries are reported " + + "as errors. It does not work when check_fileset_dependencies_recursively is " + + "disabled.") public boolean strictFilesets; // Plugins are build using the host config. To avoid cycles we just don't propagate @@ -449,19 +449,19 @@ public final class BuildConfiguration implements Serializable { // host tools, we can improve this by (for example) creating a compiler configuration that is // used only for building plugins. @Option(name = "plugin", - converter = LabelConverter.class, - allowMultiple = true, - defaultValue = "", - category = "flags", - help = "Plugins to use in the build. Currently works with java_plugin.") + converter = LabelConverter.class, + allowMultiple = true, + defaultValue = "", + category = "flags", + help = "Plugins to use in the build. Currently works with java_plugin.") public List<Label> pluginList; @Option(name = "plugin_copt", - converter = PluginOptionConverter.class, - allowMultiple = true, - category = "flags", - defaultValue = ":", - help = "Plugin options") + converter = PluginOptionConverter.class, + allowMultiple = true, + category = "flags", + defaultValue = ":", + help = "Plugin options") public List<Map.Entry<String, String>> pluginCoptList; @Option(name = "stamp", @@ -518,9 +518,9 @@ public final class BuildConfiguration implements Serializable { public String shortName; @Option(name = "platform_suffix", - defaultValue = "null", - category = "misc", - help = "Specifies a suffix to be added to the configuration directory.") + defaultValue = "null", + category = "misc", + help = "Specifies a suffix to be added to the configuration directory.") public String platformSuffix; @Option(name = "test_env", @@ -540,9 +540,9 @@ public final class BuildConfiguration implements Serializable { defaultValue = "false", category = "testing", help = "If specified, Bazel will instrument code (using offline instrumentation where " - + "possible) and will collect coverage information during tests. Only targets that " - + " match --instrumentation_filter will be affected. Usually this option should " - + " not be specified directly - 'bazel coverage' command should be used instead." + + "possible) and will collect coverage information during tests. Only targets that " + + " match --instrumentation_filter will be affected. Usually this option should " + + " not be specified directly - 'bazel coverage' command should be used instead." ) public boolean collectCodeCoverage; @@ -562,13 +562,13 @@ public final class BuildConfiguration implements Serializable { category = "testing", abbrev = 't', // it's useful to toggle this on/off quickly help = "If 'auto', Bazel will only rerun a test if any of the following conditions apply: " - + "(1) Bazel detects changes in the test or its dependencies " - + "(2) the test is marked as external " - + "(3) multiple test runs were requested with --runs_per_test" - + "(4) the test failed" - + "If 'yes', the caching behavior will be the same as 'auto' except that " - + "it may cache test failures and test runs with --runs_per_test." - + "If 'no', all tests will be always executed.") + + "(1) Bazel detects changes in the test or its dependencies " + + "(2) the test is marked as external " + + "(3) multiple test runs were requested with --runs_per_test" + + "(4) the test failed" + + "If 'yes', the caching behavior will be the same as 'auto' except that " + + "it may cache test failures and test runs with --runs_per_test." + + "If 'no', all tests will be always executed.") public TriState cacheTestResults; @Deprecated @@ -609,10 +609,10 @@ public final class BuildConfiguration implements Serializable { public List<PerLabelOptions> runsPerTest; @Option(name = "build_runfile_links", - defaultValue = "true", - category = "strategy", - help = "If true, build runfiles symlink forests for all targets. " - + "If false, write only manifests when possible.") + defaultValue = "true", + category = "strategy", + help = "If true, build runfiles symlink forests for all targets. " + + "If false, write only manifests when possible.") public boolean buildRunfiles; @Option(name = "test_arg", @@ -631,13 +631,13 @@ public final class BuildConfiguration implements Serializable { defaultValue = "null", category = "testing", help = "Specifies a filter to forward to the test framework. Used to limit " - + "the tests run. Note that this does not affect which targets are built.") + + "the tests run. Note that this does not affect which targets are built.") public String testFilter; @Option(name = "check_fileset_dependencies_recursively", - defaultValue = "true", - category = "semantics", - help = "If false, fileset targets will, whenever possible, create " + defaultValue = "true", + category = "semantics", + help = "If false, fileset targets will, whenever possible, create " + "symlinks to directories instead of creating one symlink for each " + "file inside the directory. Disabling this will significantly " + "speed up fileset builds, but targets that depend on filesets will " @@ -646,23 +646,23 @@ public final class BuildConfiguration implements Serializable { public boolean checkFilesetDependenciesRecursively; @Option(name = "run_under", - category = "run", - defaultValue = "null", - converter = RunUnderConverter.class, - help = "Prefix to insert in front of command before running. " - + "Examples:\n" - + "\t--run_under=valgrind\n" - + "\t--run_under=strace\n" - + "\t--run_under='strace -c'\n" - + "\t--run_under='valgrind --quiet --num-callers=20'\n" - + "\t--run_under=//package:target\n" - + "\t--run_under='//package:target --options'\n") + category = "run", + defaultValue = "null", + converter = RunUnderConverter.class, + help = "Prefix to insert in front of command before running. " + + "Examples:\n" + + "\t--run_under=valgrind\n" + + "\t--run_under=strace\n" + + "\t--run_under='strace -c'\n" + + "\t--run_under='valgrind --quiet --num-callers=20'\n" + + "\t--run_under=//package:target\n" + + "\t--run_under='//package:target --options'\n") public RunUnder runUnder; @Option(name = "distinct_host_configuration", - defaultValue = "true", - category = "strategy", - help = "Build all the tools used during the build for a distinct configuration from " + defaultValue = "true", + category = "strategy", + help = "Build all the tools used during the build for a distinct configuration from " + "that used for the target program. By default, the same configuration is used " + "for host and target programs, but this may cause undesirable rebuilds of tool " + "such as the protocol compiler (and then everything downstream) whenever a minor " @@ -676,9 +676,9 @@ public final class BuildConfiguration implements Serializable { public boolean useDistinctHostConfiguration; @Option(name = "check_visibility", - defaultValue = "true", - category = "checking", - help = "If disabled, visibility errors are demoted to warnings.") + defaultValue = "true", + category = "checking", + help = "If disabled, visibility errors are demoted to warnings.") public boolean checkVisibility; // Moved from viewOptions to here because license information is very expensive to serialize. @@ -688,8 +688,8 @@ public final class BuildConfiguration implements Serializable { defaultValue = "false", category = "checking", help = "Check that licensing constraints imposed by dependent packages " - + "do not conflict with distribution modes of the targets being built. " - + "By default, licenses are not checked.") + + "do not conflict with distribution modes of the targets being built. " + + "By default, licenses are not checked.") public boolean checkLicenses; @Option(name = "experimental_enforce_constraints", @@ -700,11 +700,11 @@ public final class BuildConfiguration implements Serializable { public boolean enforceConstraints; @Option(name = "experimental_action_listener", - allowMultiple = true, - defaultValue = "", - category = "experimental", - converter = LabelConverter.class, - help = "Use action_listener to attach an extra_action to existing build actions.") + allowMultiple = true, + defaultValue = "", + category = "experimental", + converter = LabelConverter.class, + help = "Use action_listener to attach an extra_action to existing build actions.") public List<Label> actionListeners; @Option(name = "is host configuration", @@ -724,12 +724,23 @@ public final class BuildConfiguration implements Serializable { defaultValue = "", category = "flags", help = "The given features will be enabled or disabled by default for all packages. " - + "Specifying -<feature> will disable the feature globally. " - + "Negative features always override positive ones. " - + "This flag is used to enable rolling out default feature changes without a " - + "Blaze release.") + + "Specifying -<feature> will disable the feature globally. " + + "Negative features always override positive ones. " + + "This flag is used to enable rolling out default feature changes without a " + + "Blaze release.") public List<String> defaultFeatures; + @Option(name = "target_environment", + converter = LabelConverter.class, + allowMultiple = true, + defaultValue = "", + category = "flags", + help = "Declares this build's target environment. Must be a label reference to an " + + "\"environment\" rule. If specified, all top-level targets must be " + + "compatible with this environment." + ) + public List<Label> targetEnvironments; + @Override public FragmentOptions getHost(boolean fallback) { Options host = (Options) getDefault(); @@ -1940,4 +1951,12 @@ public final class BuildConfiguration implements Serializable { public List<String> getDefaultFeatures() { return options.defaultFeatures; } + + /** + * Returns the "top-level" environment space, i.e. the set of environments all top-level + * targets must be compatible with. An empty value implies no restrictions. + */ + public List<Label> getTargetEnvironments() { + return options.targetEnvironments; + } } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java index 14ac2bc6fa..103c796a2e 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java @@ -26,6 +26,7 @@ import com.google.devtools.build.lib.packages.AttributeMap; import com.google.devtools.build.lib.packages.EnvironmentGroup; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.syntax.Label; @@ -277,6 +278,38 @@ public class ConstraintSemantics { } /** + * Exception indicating errors finding/parsing environments or their containing groups. + */ + public static class EnvironmentLookupException extends Exception { + private EnvironmentLookupException(String message) { + super(message); + } + } + + /** + * Returns the environment group that owns the given environment. Both must belong to + * the same package. + * + * @throws EnvironmentLookupException if the input is not an {@link EnvironmentRule} or no + * matching group is found + */ + public static EnvironmentGroup getEnvironmentGroup(Target envTarget) + throws EnvironmentLookupException { + if (!(envTarget instanceof Rule) + || !((Rule) envTarget).getRuleClass().equals(EnvironmentRule.RULE_NAME)) { + throw new EnvironmentLookupException( + envTarget.getLabel() + " is not a valid environment definition"); + } + for (EnvironmentGroup group : envTarget.getPackage().getTargets(EnvironmentGroup.class)) { + if (group.getEnvironments().contains(envTarget.getLabel())) { + return group; + } + } + throw new EnvironmentLookupException( + "cannot find the group for environment " + envTarget.getLabel()); + } + + /** * Returns the set of environments this rule supports, applying the logic described in * {@link ConstraintSemantics}. * @@ -395,14 +428,12 @@ public class ConstraintSemantics { * Performs constraint checking on the given rule's dependencies and reports any errors. * * @param ruleContext the rule to analyze - * @param supportedEnvironments the rule's supported environments, as defined by the return + * @param ruleEnvironments the rule's supported environments, as defined by the return * value of {@link #getSupportedEnvironments}. In particular, for any environment group that's * not in this collection, the rule is assumed to support the defaults for that group. */ public static void checkConstraints(RuleContext ruleContext, - EnvironmentCollection supportedEnvironments) { - - Set<EnvironmentGroup> knownGroups = supportedEnvironments.getGroups(); + EnvironmentCollection ruleEnvironments) { for (TransitiveInfoCollection dependency : getAllPrerequisites(ruleContext)) { SupportedEnvironmentsProvider depProvider = @@ -412,43 +443,53 @@ public class ConstraintSemantics { // opt them into constraint checking, but for now just pass them by. continue; } - Collection<Label> depEnvironments = depProvider.getEnvironments().getEnvironments(); - Set<EnvironmentGroup> groupsKnownToDep = depProvider.getEnvironments().getGroups(); - - // Environments we support that the dependency does not support. - Set<Label> disallowedEnvironments = new LinkedHashSet<>(); - - // For every environment we support, either the dependency must also support it OR it must be - // a default for a group the dependency doesn't know about. - for (EnvironmentWithGroup supportedEnv : supportedEnvironments.getGroupedEnvironments()) { - EnvironmentGroup group = supportedEnv.group(); - Label environment = supportedEnv.environment(); - if (!depEnvironments.contains(environment) - && (groupsKnownToDep.contains(group) || !group.isDefault(environment))) { - disallowedEnvironments.add(environment); - } + Collection<Label> unsupportedEnvironments = + getUnsupportedEnvironments(depProvider.getEnvironments(), ruleEnvironments); + + if (!unsupportedEnvironments.isEmpty()) { + ruleContext.ruleError("dependency " + dependency.getLabel() + + " doesn't support expected environment" + + (unsupportedEnvironments.size() == 1 ? "" : "s") + + ": " + Joiner.on(", ").join(unsupportedEnvironments)); } + } + } - // For any environment group we don't know about, we implicitly support its defaults. Check - // that the dep does, too. - for (EnvironmentGroup depGroup : groupsKnownToDep) { - if (!knownGroups.contains(depGroup)) { - for (Label defaultEnv : depGroup.getDefaults()) { - if (!depEnvironments.contains(defaultEnv)) { - disallowedEnvironments.add(defaultEnv); - } - } - } + /** + * Given a collection of environments and a collection of expected environments, returns the + * missing environments that would cause constraint expectations to be violated. Includes + * the effects of environment group defaults. + */ + public static Collection<Label> getUnsupportedEnvironments( + EnvironmentCollection actualEnvironments, EnvironmentCollection expectedEnvironments) { + + Set<Label> missingEnvironments = new LinkedHashSet<>(); + + // For each expected environment, it must either be a supported environment OR a default + // for a group the supported environment set doesn't know about. + for (EnvironmentWithGroup expectedEnv : expectedEnvironments.getGroupedEnvironments()) { + EnvironmentGroup group = expectedEnv.group(); + Label environment = expectedEnv.environment(); + if (!actualEnvironments.getEnvironments().contains(environment) + && (actualEnvironments.getGroups().contains(group) || !group.isDefault(environment))) { + missingEnvironments.add(environment); } + } - // Report errors on bad environments. - if (!disallowedEnvironments.isEmpty()) { - ruleContext.ruleError("dependency " + dependency.getLabel() - + " doesn't support expected environment" - + (disallowedEnvironments.size() == 1 ? "" : "s") - + ": " + Joiner.on(", ").join(disallowedEnvironments)); + // For any environment group not referenced by the expected environments, its defaults are + // implicitly applied. We can ignore it if it's also missing from the supported environments + // (since in that case the same defaults apply), otherwise have to check. + for (EnvironmentGroup group : actualEnvironments.getGroups()) { + if (!expectedEnvironments.getGroups().contains(group)) { + for (Label defaultEnv : group.getDefaults()) { + if (!actualEnvironments.getEnvironments().contains(defaultEnv)) { + missingEnvironments.add(defaultEnv); + } + } } } + + return missingEnvironments; } /** diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java index 912ed723fe..ab91f454ea 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java @@ -25,7 +25,6 @@ import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.packages.EnvironmentGroup; -import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.syntax.Label; @@ -42,18 +41,12 @@ public class Environment implements RuleConfiguredTargetFactory { // // This will likely expand when we add support for environments fulfilling other environments. Label label = ruleContext.getLabel(); - Package pkg = ruleContext.getRule().getPackage(); - EnvironmentGroup group = null; - for (EnvironmentGroup pkgGroup : pkg.getTargets(EnvironmentGroup.class)) { - if (pkgGroup.getEnvironments().contains(label)) { - group = pkgGroup; - break; - } - } - - if (group == null) { - ruleContext.ruleError("no matching environment group from the same package"); + EnvironmentGroup group; + try { + group = ConstraintSemantics.getEnvironmentGroup(ruleContext.getRule()); + } catch (ConstraintSemantics.EnvironmentLookupException e) { + ruleContext.ruleError(e.getMessage()); return null; } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java index 1ce5f1cb4a..38216e17af 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java @@ -91,14 +91,17 @@ public class EnvironmentCollection { static final EnvironmentCollection EMPTY = new EnvironmentCollection(ImmutableMultimap.<EnvironmentGroup, Label>of()); - static class Builder { + /** + * Builder for {@link EnvironmentCollection}. + */ + public static class Builder { private final ImmutableMultimap.Builder<EnvironmentGroup, Label> mapBuilder = ImmutableMultimap.builder(); /** * Inserts the given environment / owning group pair. */ - Builder put(EnvironmentGroup group, Label environment) { + public Builder put(EnvironmentGroup group, Label environment) { mapBuilder.put(group, environment); return this; } @@ -106,7 +109,7 @@ public class EnvironmentCollection { /** * Inserts the given set of environments, all belonging to the specified group. */ - Builder putAll(EnvironmentGroup group, Iterable<Label> environments) { + public Builder putAll(EnvironmentGroup group, Iterable<Label> environments) { mapBuilder.putAll(group, environments); return this; } @@ -114,12 +117,12 @@ public class EnvironmentCollection { /** * Inserts the contents of another {@link EnvironmentCollection} into this one. */ - Builder putAll(EnvironmentCollection other) { + public Builder putAll(EnvironmentCollection other) { mapBuilder.putAll(other.map); return this; } - EnvironmentCollection build() { + public EnvironmentCollection build() { return new EnvironmentCollection(mapBuilder.build()); } } diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java index a27cc50433..63c17df710 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java @@ -14,9 +14,11 @@ package com.google.devtools.build.lib.buildtool; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; +import com.google.common.base.Verify; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.eventbus.EventBus; @@ -41,6 +43,9 @@ import com.google.devtools.build.lib.analysis.config.BuildConfigurationKey; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.DefaultsPackage; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; +import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics; +import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection; +import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider; import com.google.devtools.build.lib.buildtool.BuildRequest.BuildRequestOptions; import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent; import com.google.devtools.build.lib.buildtool.buildevent.BuildInterruptedEvent; @@ -53,6 +58,8 @@ import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.License; import com.google.devtools.build.lib.packages.License.DistributionType; +import com.google.devtools.build.lib.packages.NoSuchPackageException; +import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.PackageIdentifier; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; @@ -61,10 +68,12 @@ import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.pkgcache.LoadingFailedException; import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.Callback; import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.LoadingResult; +import com.google.devtools.build.lib.pkgcache.PackageManager; import com.google.devtools.build.lib.profiler.ProfilePhase; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.runtime.BlazeRuntime; import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.vfs.Path; @@ -186,6 +195,8 @@ public class BuildTool { result.setActualTargets(analysisResult.getTargetsToBuild()); result.setTestTargets(analysisResult.getTargetsToTest()); + checkTargetEnvironmentRestrictions(analysisResult.getTargetsToBuild(), + runtime.getPackageManager()); reportTargets(analysisResult); // Execution phase. @@ -229,6 +240,55 @@ public class BuildTool { } } + /** + * Checks that if this is an environment-restricted build, all top-level targets support + * the expected environments. + * + * @param topLevelTargets the build's top-level targets + * @throws ViewCreationFailedException if constraint enforcement is on, the build declares + * environment-restricted top level configurations, and any top-level target doesn't + * support the expected environments + */ + private void checkTargetEnvironmentRestrictions(Iterable<ConfiguredTarget> topLevelTargets, + PackageManager packageManager) throws ViewCreationFailedException { + for (ConfiguredTarget topLevelTarget : topLevelTargets) { + BuildConfiguration config = topLevelTarget.getConfiguration(); + if (config == null) { + // TODO(bazel-team): support file targets (they should apply package-default constraints). + continue; + } else if (!config.enforceConstraints() || config.getTargetEnvironments().isEmpty()) { + continue; + } + + // Parse and collect this configuration's environments. + EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder(); + for (Label envLabel : config.getTargetEnvironments()) { + try { + Target env = packageManager.getLoadedTarget(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. + SupportedEnvironmentsProvider provider = + Verify.verifyNotNull(topLevelTarget.getProvider(SupportedEnvironmentsProvider.class)); + Collection<Label> missingEnvironments = ConstraintSemantics.getUnsupportedEnvironments( + provider.getEnvironments(), expectedEnvironments); + if (!missingEnvironments.isEmpty()) { + throw new ViewCreationFailedException( + String.format("This is a restricted-environment build. %s does not support" + + " required environment%s %s", + topLevelTarget.getLabel(), + missingEnvironments.size() == 1 ? "" : "s", + Joiner.on(", ").join(missingEnvironments))); + } + } + } + private ImmutableMap<PathFragment, Path> mergePackageRoots( ImmutableMap<PackageIdentifier, Path> first, ImmutableMap<PackageIdentifier, Path> second) { |