com.google.devtools.build.lib.packages.EnvironmentGroup.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.packages.EnvironmentGroup.java

Source

// 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.
 *
 * <p>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.
 *
 * <p>If we implemented this as a rule, we'd have to provide that lookup via rule dependencies,
 * e.g. something like:
 *
 * <code>
 *   environment(
 *       name = 'bar',
 *       group = [':sample_environments'],
 *       is_default = 1
 *   )
 * </code>
 *
 * <p>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<Label> environments;
    private final Set<Label> defaults;

    /**
     * Maps a member environment to the set of environments that directly fulfill it. Note that
     * we can't populate this map until all Target instances for member environments have been
     * initialized, which may occur after group instantiation (this makes the class mutable).
     */
    private final Map<Label, NestedSet<Label>> fulfillersMap = new HashMap<>();

    /**
     * Predicate that matches labels from a different package than the initialized package.
     */
    private static final class DifferentPackage implements Predicate<Label> {
        private final Package containingPackage;

        private DifferentPackage(Package containingPackage) {
            this.containingPackage = containingPackage;
        }

        @Override
        public boolean apply(Label environment) {
            return !environment.getPackageName().equals(containingPackage.getName());
        }
    }

    /**
     * Instantiates a new group without verifying the soundness of its contents. See the validation
     * methods below for appropriate checks.
     *
     * @param label the build label identifying this group
     * @param pkg the package this group belongs to
     * @param environments the set of environments that belong to this group
     * @param defaults the environments a rule implicitly supports unless otherwise specified
     * @param location location in the BUILD file of this group
     */
    EnvironmentGroup(Label label, Package pkg, final List<Label> environments, List<Label> defaults,
            Location location) {
        this.label = label;
        this.location = location;
        this.containingPackage = pkg;
        this.environments = ImmutableSet.copyOf(environments);
        this.defaults = ImmutableSet.copyOf(defaults);
    }

    /**
     * Checks that all environments declared by this group are in the same package as the group (so
     * we can perform an environment --> environment_group lookup and know the package is available)
     * and checks that all defaults are legitimate members of the group.
     *
     * <p>Does <b>not</b> check that the referenced environments exist (see
     * {@link #processMemberEnvironments}).
     *
     * @return a list of validation errors that occurred
     */
    List<Event> validateMembership() {
        List<Event> 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<Event> processMemberEnvironments(Map<String, Target> pkgTargets) {
        List<Event> events = new ArrayList<>();
        // Maps an environment to the environments that directly fulfill it.
        Multimap<Label, Label> 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 <i>transitively</i> fulfill it, places it into transitiveFulfillers,
     * and returns that set.
     */
    private static NestedSet<Label> setTransitiveFulfillers(Label env, Multimap<Label, Label> directFulfillers,
            Map<Label, NestedSet<Label>> transitiveFulfillers) {
        if (transitiveFulfillers.containsKey(env)) {
            return transitiveFulfillers.get(env);
        } else if (!directFulfillers.containsKey(env)) {
            // Nobody fulfills this environment.
            NestedSet<Label> emptySet = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
            transitiveFulfillers.put(env, emptySet);
            return emptySet;
        } else {
            NestedSetBuilder<Label> set = NestedSetBuilder.stableOrder();
            for (Label fulfillingEnv : directFulfillers.get(env)) {
                set.add(fulfillingEnv);
                set.addTransitive(setTransitiveFulfillers(fulfillingEnv, directFulfillers, transitiveFulfillers));
            }
            NestedSet<Label> builtSet = set.build();
            transitiveFulfillers.put(env, builtSet);
            return builtSet;
        }
    }

    private boolean isValidEnvironment(Target env, Label envName, String prefix, List<Event> events) {
        if (env == null) {
            events.add(Event.error(location, prefix + "environment " + envName + " does not exist"));
            return false;
        } else if (!env.getTargetKind().equals("environment rule")) {
            events.add(Event.error(location, prefix + env.getLabel() + " is not a valid environment"));
            return false;
        } else if (!environments.contains(env.getLabel())) {
            events.add(Event.error(location, prefix + env.getLabel() + " is not a member of this group"));
            return false;
        }
        return true;
    }

    /**
     * Returns the environments that belong to this group.
     */
    public Set<Label> getEnvironments() {
        return environments;
    }

    /**
     * Returns the environments a rule supports by default, i.e. if it has no explicit references to
     * environments in this group.
     */
    public Set<Label> getDefaults() {
        return defaults;
    }

    /**
     * Determines whether or not an environment is a default. Returns false if the environment
     * doesn't belong to this group.
     */
    public boolean isDefault(Label environment) {
        return defaults.contains(environment);
    }

    /**
     * Returns the set of environments that transitively fulfill the specified environment.
     * The environment must be a valid member of this group.
     *
     * <p>>For example, if the input is <code>":foo"</code> and <code>":bar"</code> fulfills
     * <code>":foo"</code> and <code>":baz"</code> fulfills <code>":bar"</code>, this returns
     * <code>[":foo", ":bar", ":baz"]</code>.
     *
     * <p>If no environments fulfill the input, returns an empty set.
     */
    public Iterable<Label> getFulfillers(Label environment) {
        return Verify.verifyNotNull(fulfillersMap.get(environment));
    }

    @Override
    public Label getLabel() {
        return label;
    }

    @Override
    public String getName() {
        return label.getName();
    }

    @Override
    public Package getPackage() {
        return containingPackage;
    }

    @Override
    public String getTargetKind() {
        return targetKind();
    }

    @Override
    public Rule getAssociatedRule() {
        return null;
    }

    @Override
    public License getLicense() {
        return License.NO_LICENSE;
    }

    @Override
    public Location getLocation() {
        return location;
    }

    @Override
    public String toString() {
        return targetKind() + " " + getLabel();
    }

    @Override
    public Set<License.DistributionType> getDistributions() {
        return Collections.emptySet();
    }

    @Override
    public RuleVisibility getVisibility() {
        return ConstantRuleVisibility.PRIVATE; // No rule should be referencing an environment_group.
    }

    @Override
    public boolean isConfigurable() {
        return false;
    }

    public static String targetKind() {
        return "environment group";
    }
}