| // Copyright 2018 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.starlark; |
| |
| import static com.google.devtools.build.lib.analysis.starlark.FunctionTransitionUtil.applyAndValidate; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.BuildOptionsView; |
| import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.RawAttributeMapper; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.RuleTransitionData; |
| import com.google.devtools.build.lib.packages.StructImpl; |
| import com.google.devtools.build.lib.packages.StructProvider; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| /** |
| * Implements {@link TransitionFactory} to provide a starlark-defined transition that rules can |
| * apply to their own configuration. This transition has access to (1) a map of the current |
| * configuration's build settings and (2) the configured attributes of the given rule (not its |
| * dependencies'). |
| * |
| * <p>In some corner cases, we can't access the configured attributes the configuration of the child |
| * may be different than the configuration of the parent. For now, forbid all access to attributes |
| * that read selects. |
| * |
| * <p>For starlark-defined attribute transitions, see {@link StarlarkAttributeTransitionProvider}. |
| */ |
| public final class StarlarkRuleTransitionProvider implements TransitionFactory<RuleTransitionData> { |
| |
| private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition; |
| |
| StarlarkRuleTransitionProvider(StarlarkDefinedConfigTransition starlarkDefinedConfigTransition) { |
| this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition; |
| } |
| |
| @VisibleForTesting |
| public StarlarkDefinedConfigTransition getStarlarkDefinedConfigTransitionForTesting() { |
| return starlarkDefinedConfigTransition; |
| } |
| |
| @Override |
| public PatchTransition create(RuleTransitionData ruleData) { |
| // This wouldn't be safe if rule transitions could read attributes with select(), in which case |
| // the rule alone isn't sufficient to define the transition's semantics (both the rule and its |
| // configuration are needed). Rule transitions can't read select()s, so this is a non-issue. |
| // |
| // We could cache-optimize further by distinguishing transitions that read attributes vs. those |
| // that don't. Every transition has a {@code def impl(settings, attr) } signature, even if the |
| // transition never reads {@code attr}. If we had a way to formally identify such transitions, |
| // we wouldn't need {@code rule} in the cache key. |
| return starlarkDefinedConfigTransition |
| .getRuleTransitionCache() |
| .get(ruleData.rule(), this::createTransition); |
| } |
| |
| @Override |
| public TransitionType transitionType() { |
| return TransitionType.RULE; |
| } |
| |
| private FunctionPatchTransition createTransition(Rule rule) { |
| LinkedHashMap<String, Object> attributes = new LinkedHashMap<>(); |
| RawAttributeMapper attributeMapper = RawAttributeMapper.of(rule); |
| for (Attribute attribute : rule.getAttributes()) { |
| Object val = attributeMapper.getRawAttributeValue(rule, attribute); |
| if (val instanceof BuildType.SelectorList) { |
| // For now, don't allow access to attributes that read selects. |
| // TODO(b/121134880): make this restriction more fine grained. |
| continue; |
| } |
| attributes.put( |
| Attribute.getStarlarkName(attribute.getPublicName()), Attribute.valueToStarlark(val)); |
| } |
| StructImpl attrObject = |
| StructProvider.STRUCT.create( |
| attributes, |
| "No attribute '%s'. Either this attribute does " |
| + "not exist for this rule or is set by a select. Starlark rule transitions " |
| + "currently cannot read attributes behind selects."); |
| return new FunctionPatchTransition(attrObject); |
| } |
| |
| /** The actual transition used by the rule. */ |
| private final class FunctionPatchTransition extends StarlarkTransition |
| implements PatchTransition { |
| private final StructImpl attrObject; |
| private final int hashCode; |
| |
| private FunctionPatchTransition(StructImpl attrObject) { |
| super(starlarkDefinedConfigTransition); |
| this.attrObject = attrObject; |
| this.hashCode = Objects.hash(attrObject, super.hashCode()); |
| } |
| |
| /** |
| * @return the post-transition build options or a clone of the original build options if an |
| * error was encountered during transition application/validation. |
| */ |
| // TODO(b/121134880): validate that the targets these transitions are applied on don't read any |
| // attributes that are then configured by the outputs of these transitions. |
| @Override |
| public BuildOptions patch(BuildOptionsView buildOptionsView, EventHandler eventHandler) |
| throws InterruptedException { |
| // Starlark transitions already have logic to enforce they only access declared inputs and |
| // outputs. Rather than complicate BuildOptionsView with more access points to BuildOptions, |
| // we just use the original BuildOptions and trust the transition's enforcement logic. |
| BuildOptions buildOptions = buildOptionsView.underlying(); |
| Map<String, BuildOptions> result = |
| applyAndValidate(buildOptions, starlarkDefinedConfigTransition, attrObject, eventHandler); |
| if (result == null) { |
| return buildOptions.clone(); |
| } |
| if (result.size() != 1) { |
| eventHandler.handle( |
| Event.error( |
| starlarkDefinedConfigTransition.getLocation(), |
| "Rule transition only allowed to return a single transitioned configuration.")); |
| return buildOptions.clone(); |
| } |
| return Iterables.getOnlyElement(result.values()); |
| } |
| |
| @Override |
| public boolean equals(Object object) { |
| if (object == this) { |
| return true; |
| } |
| if (!(object instanceof FunctionPatchTransition)) { |
| return false; |
| } |
| FunctionPatchTransition other = (FunctionPatchTransition) object; |
| return Objects.equals(attrObject, other.attrObject) && super.equals(other); |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| } |
| } |