// Copyright 2014 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.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.SplitTransition; import com.google.devtools.build.lib.packages.Attribute.Transition; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.util.Preconditions; import java.io.PrintStream; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * The primary container for all main {@link BuildConfiguration} instances, * currently "target", "data", and "host". * * <p>The target configuration is used for all targets specified on the command * line. Data dependencies of targets in the target configuration use the data * configuration instead. * * <p>The host configuration is used for tools that are executed during the * build, e. g, compilers. * * <p>The "related" configurations are also contained in this class. */ @ThreadSafe public final class BuildConfigurationCollection { private final ImmutableList<BuildConfiguration> targetConfigurations; private final BuildConfiguration hostConfiguration; public BuildConfigurationCollection(List<BuildConfiguration> targetConfigurations, BuildConfiguration hostConfiguration) throws InvalidConfigurationException { this.targetConfigurations = ImmutableList.copyOf(targetConfigurations); this.hostConfiguration = hostConfiguration; // Except for the host configuration (which may be identical across target configs), the other // configurations must all have different cache keys or we will end up with problems. HashMap<String, BuildConfiguration> cacheKeyConflictDetector = new HashMap<>(); for (BuildConfiguration config : getAllConfigurations()) { String cacheKey = config.checksum(); if (cacheKeyConflictDetector.containsKey(cacheKey)) { throw new InvalidConfigurationException("Conflicting configurations: " + config + " & " + cacheKeyConflictDetector.get(cacheKey)); } cacheKeyConflictDetector.put(cacheKey, config); } } public static BuildConfiguration configureTopLevelTarget(BuildConfiguration topLevelConfiguration, Target toTarget) { if (!toTarget.isConfigurable()) { return null; } return topLevelConfiguration.getTransitions().toplevelConfigurationHook(toTarget); } public ImmutableList<BuildConfiguration> getTargetConfigurations() { return targetConfigurations; } /** * Returns the host configuration for this collection. * * <p>Don't use this method. It's limited in that it assumes a single host configuration for * the entire collection. This may not be true in the future and more flexible interfaces based * on dynamic configurations will likely supplant this interface anyway. Its main utility is * to keep Bazel working while dynamic configuration progress is under way. */ public BuildConfiguration getHostConfiguration() { return hostConfiguration; } /** * Returns all configurations that can be reached from the target configuration through any kind * of configuration transition. */ public Collection<BuildConfiguration> getAllConfigurations() { Set<BuildConfiguration> result = new LinkedHashSet<>(); for (BuildConfiguration config : targetConfigurations) { result.addAll(config.getAllReachableConfigurations()); } return result; } /** * Returns whether this build uses dynamic configurations. */ public boolean useDynamicConfigurations() { return getTargetConfigurations().get(0).useDynamicConfigurations(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof BuildConfigurationCollection)) { return false; } BuildConfigurationCollection that = (BuildConfigurationCollection) obj; return this.targetConfigurations.equals(that.targetConfigurations); } @Override public int hashCode() { return targetConfigurations.hashCode(); } /** * Prints the configuration graph in dot format to the given print stream. This is only intended * for debugging. */ public void dumpAsDotGraph(PrintStream out) { out.println("digraph g {"); out.println(" ratio = 0.3;"); for (BuildConfiguration config : getAllConfigurations()) { String from = config.checksum(); for (Map.Entry<? extends Transition, ConfigurationHolder> entry : config.getTransitions().getTransitionTable().entrySet()) { BuildConfiguration toConfig = entry.getValue().getConfiguration(); if (toConfig == config) { continue; } String to = toConfig == null ? "ERROR" : toConfig.checksum(); out.println(" \"" + from + "\" -> \"" + to + "\" [label=\"" + entry.getKey() + "\"]"); } } out.println("}"); } /** * The outgoing transitions for a build configuration. */ public abstract static class Transitions implements Serializable { protected final BuildConfiguration configuration; /** * Look up table for the configuration transitions, i.e., HOST, DATA, etc. */ private final Map<? extends Transition, ConfigurationHolder> transitionTable; // TODO(bazel-team): Consider merging transitionTable into this. private final ListMultimap<? super SplitTransition<?>, BuildConfiguration> splitTransitionTable; public Transitions(BuildConfiguration configuration, Map<? extends Transition, ConfigurationHolder> transitionTable, ListMultimap<? extends SplitTransition<?>, BuildConfiguration> splitTransitionTable) { this.configuration = configuration; this.transitionTable = ImmutableMap.copyOf(transitionTable); // Do not remove <SplitTransition<?>, BuildConfiguration>: // workaround for Java 7 type inference. this.splitTransitionTable = ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>copyOf( splitTransitionTable); } public Map<? extends Transition, ConfigurationHolder> getTransitionTable() { return transitionTable; } public List<BuildConfiguration> getSplitConfigurationsNoSelf(SplitTransition<?> transition) { if (splitTransitionTable.containsKey(transition)) { return splitTransitionTable.get(transition); } else { return ImmutableList.of(); } } public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) { if (splitTransitionTable.containsKey(transition)) { return splitTransitionTable.get(transition); } else { Preconditions.checkState(transition.defaultsToSelf()); return ImmutableList.of(configuration); } } /** * Adds all configurations that are directly reachable from this configuration through * any kind of configuration transition. */ public void addDirectlyReachableConfigurations(Collection<BuildConfiguration> queue) { for (ConfigurationHolder holder : transitionTable.values()) { if (holder.configuration != null) { queue.add(holder.configuration); } } queue.addAll(splitTransitionTable.values()); } /** * Artifacts need an owner in Skyframe. By default it's the same configuration as what * the configured target has, but it can be overridden if necessary. * * @return the artifact owner configuration */ public BuildConfiguration getArtifactOwnerConfiguration() { return configuration; } /** * Returns the new configuration after traversing a dependency edge with a * given configuration transition. * * <p>Only used for static configuration builds. * * @param configurationTransition the configuration transition * @return the new configuration */ public BuildConfiguration getStaticConfiguration(Transition configurationTransition) { Preconditions.checkState(!configuration.useDynamicConfigurations()); ConfigurationHolder holder = transitionTable.get(configurationTransition); if (holder == null && configurationTransition.defaultsToSelf()) { return configuration; } return holder.configuration; } /** * Translates a static configuration {@link Transition} reference into the corresponding * dynamic configuration transition. * * <p>The difference is that with static configurations, the transition just models a desired * type of transition that subsequently gets linked to a pre-built global configuration through * custom logic in {@link BuildConfigurationCollection.Transitions} and * {@link com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory}. * * <p>With dynamic configurations, the transition directly embeds the semantics, e.g. * it includes not just a name but also the logic of how it should transform its input * configuration. * * <p>This is a connecting method meant to keep the two models in sync for the current time * in which they must co-exist. Once dynamic configurations are production-ready, we'll remove * the static configuration code entirely. */ public Transition getDynamicTransition(Transition transition) { Preconditions.checkState(configuration.useDynamicConfigurations()); if (transition == Attribute.ConfigurationTransition.NONE) { return transition; } else if (transition == Attribute.ConfigurationTransition.NULL) { return transition; } else if (transition == Attribute.ConfigurationTransition.HOST) { return HostTransition.INSTANCE; } else { throw new UnsupportedOperationException("No dynamic mapping for " + transition.toString()); } } /** * Arbitrary configuration transitions can be implemented by overriding this hook. */ @SuppressWarnings("unused") public void configurationHook(Rule fromTarget, Attribute attribute, Target toTarget, BuildConfiguration.TransitionApplier transitionApplier) { } /** * Associating configurations to top-level targets can be implemented by overriding this hook. */ @SuppressWarnings("unused") public BuildConfiguration toplevelConfigurationHook(Target toTarget) { return configuration; } } /** * A holder class for {@link BuildConfiguration} instances that allows {@code null} values, * because none of the Table implementations allow them. */ public static final class ConfigurationHolder implements Serializable { private final BuildConfiguration configuration; public ConfigurationHolder(BuildConfiguration configuration) { this.configuration = configuration; } public BuildConfiguration getConfiguration() { return configuration; } @Override public int hashCode() { return configuration == null ? 0 : configuration.hashCode(); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ConfigurationHolder)) { return false; } return Objects.equals(configuration, ((ConfigurationHolder) o).configuration); } } }