// 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.base.Verify; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; 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 public class EnvironmentGroup implements Target { private final Label label; private final Location location; private final Package containingPackage; private final Set

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(environments, new DifferentPackage(containingPackage))) { events.add(Event.error(location, environment + " is not in the same package as group " + label)); } // The defaults must be a subset of the member environments. for (Label unknownDefault : Sets.difference(defaults, 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 #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 : 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); } } } } // 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. Verify.verify(fulfillersMap.isEmpty()); for (Label envName : environments) { setTransitiveFulfillers(envName, directFulfillers, 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

>For example, if the input is ":foo" and ":bar" fulfills * ":foo" and ":baz" fulfills ":bar", this returns * [":foo", ":bar", ":baz"]. * *

If no environments fulfill the input, returns an empty set. */ public Iterable