// Copyright 2015 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.packages; import com.google.common.base.Predicate; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.Location; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Model for the "environment_group' rule: the piece of Bazel's rule constraint system that binds * thematically related environments together and determines which environments a rule supports by * default. See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for * precise semantic details of how this information is used. * *

Note that "environment_group" is implemented as a loading-time function, not a rule. This is * to support proper discovery of defaults: Say rule A has no explicit constraints and depends on * rule B, which is explicitly constrained to environment ":bar". Since A declares nothing * explicitly, it's implicitly constrained to DEFAULTS (whatever that is). Therefore, the dependency * is only allowed if DEFAULTS doesn't include environments beyond ":bar". To figure that out, we * need to be able to look up the environment group for ":bar", which is what this class provides. * *

If we implemented this as a rule, we'd have to provide that lookup via rule dependencies, e.g. * something like: * environment( * name = 'bar', * group = [':sample_environments'], * is_default = 1 * ) * * *

But this won't work. This would let us find the environment group for ":bar", but the only way * to determine what other environments belong to the group is to have the group somehow reference * them. That would produce circular dependencies in the build graph, which is no good. */ @Immutable // This is a lie, but this object is only mutable until its containing package is loaded. public class EnvironmentGroup implements Target { private final EnvironmentLabels environmentLabels; private final Location location; private final Package containingPackage; /** * Predicate that matches labels from a different package than the initialized package. */ private static final class DifferentPackage implements Predicate

Does not check that the referenced environments exist (see * {@link #processMemberEnvironments}). * * @return a list of validation errors that occurred */ List validateMembership() { List events = new ArrayList<>(); // All environments should belong to the same package as this group. for (Label environment : Iterables.filter(environmentLabels.environments, new DifferentPackage(containingPackage))) { events.add( Event.error( location, environment + " is not in the same package as group " + environmentLabels.label)); } // The defaults must be a subset of the member environments. for (Label unknownDefault : Sets.difference(environmentLabels.defaults, environmentLabels.environments)) { events.add(Event.error(location, "default " + unknownDefault + " is not a " + "declared environment for group " + getLabel())); } return events; } /** * Checks that the group's declared environments are legitimate same-package environment rules and * prepares the "fulfills" relationships between these environments to support {@link * EnvironmentLabels#getFulfillers}. * * @param pkgTargets mapping from label name to target instance for this group's package * @return a list of validation errors that occurred */ List processMemberEnvironments(Map pkgTargets) { List events = new ArrayList<>(); // Maps an environment to the environments that directly fulfill it. Multimap directFulfillers = HashMultimap.create(); for (Label envName : environmentLabels.environments) { Target env = pkgTargets.get(envName.getName()); if (isValidEnvironment(env, envName, "", events)) { AttributeMap attr = NonconfigurableAttributeMapper.of((Rule) env); for (Label fulfilledEnv : attr.get("fulfills", BuildType.LABEL_LIST)) { if (isValidEnvironment(pkgTargets.get(fulfilledEnv.getName()), fulfilledEnv, "in \"fulfills\" attribute of " + envName + ": ", events)) { directFulfillers.put(fulfilledEnv, envName); } } } } Map> fulfillersMap = new HashMap<>(); // Now that we know which environments directly fulfill each other, compute which environments // transitively fulfill each other. We could alternatively compute this on-demand, but since // we don't expect these chains to be very large we opt toward computing them once at package // load time. environmentLabels.assertNotInitialized(); for (Label envName : environmentLabels.environments) { setTransitiveFulfillers(envName, directFulfillers, fulfillersMap); } environmentLabels.setFulfillersMap(fulfillersMap); return events; } /** * Given an environment and set of environments that directly fulfill it, computes a nested * set of environments that transitively fulfill it, places it into transitiveFulfillers, * and returns that set. */ private static NestedSet