// 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.analysis.constraints;
import com.google.common.base.Joiner;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.OutputFileConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.DependencyFilter;
import com.google.devtools.build.lib.packages.EnvironmentGroup;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
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.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.LabelClass;
import com.google.devtools.build.lib.syntax.Type.LabelVisitor;
import com.google.devtools.build.lib.util.Preconditions;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Implementation of the semantics of Bazel's constraint specification and enforcement system.
*
*
This is how the system works:
*
*
All build rules can declare which "static environments" they can be built for, where a
* "static environment" is a label instance of an {@link EnvironmentRule} rule declared in a
* BUILD file. There are various ways to do this:
*
*
*
Through a "restricted to" attribute setting
* ({@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR}). This is the most direct form of
* specification - it declares the exact set of environments the rule supports (for its group -
* see precise details below).
*
Through a "compatible with" attribute setting
* ({@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR}. This declares additional
* environments a rule supports in addition to "standard" environments that are supported by
* default (see below).
*
Through "default" specifications in {@link EnvironmentGroup} rules. Every environment
* belongs to a group of thematically related peers (e.g. "target architectures", "JDK versions",
* or "mobile devices"). An environment group's definition includes which of these
* environments should be supported "by default" if not otherwise specified by one of the above
* mechanisms. In particular, a rule with no environment-related attributes automatically
* inherits all defaults.
*
Through a rule class default ({@link RuleClass.Builder#restrictedTo} and
* {@link RuleClass.Builder#compatibleWith}). This overrides global defaults for all instances
* of the given rule class. This can be used, for example, to make all *_test rules "testable"
* without each instance having to explicitly declare this capability.
*
*
*
Groups exist to model the idea that some environments are related while others have nothing
* to do with each other. Say, for example, we want to say a rule works for PowerPC platforms but
* not x86. We can do so by setting its "restricted to" attribute to
* {@code ['//sample/path:powerpc']}. Because both PowerPC and x86 are in the same
* "target architectures" group, this setting removes x86 from the set of supported environments.
* But since JDK support belongs to its own group ("JDK versions") it says nothing about which JDK
* the rule supports.
*
*
More precisely, if a rule has a "restricted to" value of [A, B, C], this removes support
* for all default environments D such that group(D) is in [group(A), group(B), group(C)] AND
* D is not in [A, B, C] (in other words, D isn't explicitly opted back in). The rule's full
* set of supported environments thus becomes [A, B, C] + all defaults that belong to unrelated
* groups.
*
*
If the rule has a "compatible with" value of [E, F, G], these are unconditionally
* added to its set of supported environments (in addition to the results from above).
*
*
An environment may not appear in both a rule's "restricted to" and "compatible with" values.
* If two environments belong to the same group, they must either both be in "restricted to",
* both be in "compatible with", or not explicitly specified.
*
*
Given all the above, constraint enforcement is this: rule A can depend on rule B if, for
* every static environment A supports, B also supports that environment.
*
*
Configurable attributes introduce the additional concept of "refined environments". Given:
*
*
*
* "lib"'s static environments are what are declared via restricted_to: {@code [":A", ":B"]}.
* But normal constraint checking doesn't work well here: neither "depA" or "depB" supports both
* environments, so each is technically invalid. But the two of them together do support
* both environments. So constraint checking with selects checks that "lib"'s environments
* are supported by the union of its selectable dependencies, then refines its
* environments to whichever deps get chosen. In other words:
*
*
*
The above example is considered constraint-valid.
*
When building with "config_a", "lib"'s refined environment set is {@code [":A"]}.
*
When building with "config_b", "lib"'s refined environment set is {@code [":B"]}.
*
Any rule depending on "lib" has its environments refined by the intersection with "lib".
* So if "depender" has {@code restricted_to = [":A", ":B"]} and {@code deps = [":lib"]},
* then when building with "config_a", "depender"'s refined environment set is {@code [":A"]}.
*
For each environment group, every rule's refined environment set must be non-empty. This
* ensures the "chosen" dep in a select matches all rules up the dependency chain. So if
* "depender" had {@code restricted_to = [":B"]}, it wouldn't be allowed in a "config_a"
* build.
*
* .
*/
public class ConstraintSemantics {
private ConstraintSemantics() {
}
/**
* Provides a set of default environments for a given environment group.
*/
private interface DefaultsProvider {
Collection