// 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.Preconditions; 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.LabelAndLocation; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup; import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider.RemovedEnvironmentCulprit; 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.EnvironmentLabels; 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 java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringJoiner; 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: * *

* *

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: * *

 *   java_library(
 *       name = "lib",
 *       restricted_to = [":A", ":B"],
 *       deps = select({
 *           ":config_a": [":depA"],
 *           ":config_b": [":depB"],
 *       }))
 *   java_library(
 *       name = "depA",
 *       restricted_to = [":A"])
 *   java_library(
 *       name = "depB",
 *       restricted_to = [":B"])
 * 
* * "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: * *
    *
  1. The above example is considered constraint-valid. *
  2. When building with "config_a", "lib"'s refined environment set is {@code [":A"]}. *
  3. When building with "config_b", "lib"'s refined environment set is {@code [":B"]}. *
  4. 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"]}. *
  5. 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 { public ConstraintSemantics() { } /** * Logs an error message that the current rule violates constraints. */ public void ruleError(RuleContext ruleContext, String message) { ruleContext.ruleError(message); } /** * Logs an error message that an attribute on the current rule doesn't properly declare * constraints. */ public void attributeError(RuleContext ruleContext, String attribute, String message) { ruleContext.attributeError(attribute, message); } /** * Provides a set of default environments for a given environment group. */ private interface DefaultsProvider { Collection