// 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.bazel.rules;
import com.google.common.cache.Cache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Table;
import com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.ConfigurationHolder;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.config.PackageProviderForConfigurations;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
import com.google.devtools.build.lib.packages.Attribute.Transition;
import com.google.devtools.build.lib.rules.cpp.CppRuleClasses.LipoTransition;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Configuration collection used by the rules Bazel knows.
*/
public class BazelConfigurationCollection implements ConfigurationCollectionFactory {
@Override
@Nullable
public BuildConfiguration createConfigurations(
ConfigurationFactory configurationFactory,
Cache<String, BuildConfiguration> cache,
PackageProviderForConfigurations packageProvider,
BuildOptions buildOptions,
EventHandler eventHandler)
throws InvalidConfigurationException, InterruptedException {
// Target configuration
BuildConfiguration targetConfiguration = configurationFactory.getConfiguration(
packageProvider, buildOptions, cache);
if (targetConfiguration == null) {
return null;
}
BuildConfiguration dataConfiguration = targetConfiguration;
// Host configuration
// Note that this passes in the dataConfiguration, not the target
// configuration. This is intentional.
BuildConfiguration hostConfiguration = getHostConfigurationFromRequest(configurationFactory,
packageProvider, dataConfiguration, buildOptions, cache);
if (hostConfiguration == null) {
return null;
}
ListMultimap<SplitTransition<?>, BuildConfiguration> splitTransitionsTable =
ArrayListMultimap.create();
for (SplitTransition<BuildOptions> transition : buildOptions.getPotentialSplitTransitions()) {
for (BuildOptions splitOptions : transition.split(buildOptions)) {
BuildConfiguration splitConfig = configurationFactory.getConfiguration(
packageProvider, splitOptions, cache);
splitTransitionsTable.put(transition, splitConfig);
}
}
if (packageProvider.valuesMissing()) {
return null;
}
BuildConfiguration result = setupTransitions(
targetConfiguration, dataConfiguration, hostConfiguration, splitTransitionsTable);
result.reportInvalidOptions(eventHandler);
return result;
}
private static class BazelTransitions extends BuildConfigurationCollection.Transitions {
public BazelTransitions(BuildConfiguration configuration,
Map<? extends Transition, ConfigurationHolder> transitionTable,
ListMultimap<? extends SplitTransition<?>, BuildConfiguration> splitTransitionTable) {
super(configuration, transitionTable, splitTransitionTable);
}
@Override
public Transition getDynamicTransition(Transition configurationTransition) {
if (configurationTransition == ConfigurationTransition.DATA) {
return ConfigurationTransition.NONE;
} else {
return super.getDynamicTransition(configurationTransition);
}
}
}
@Override
public Transitions getDynamicTransitionLogic(BuildConfiguration config) {
return new BazelTransitions(config, ImmutableMap.<Transition, ConfigurationHolder>of(),
ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>of());
}
/**
* Gets the correct host configuration for this build. The behavior depends on the value of the
* --distinct_host_configuration flag.
*
* <p>With --distinct_host_configuration=false, we use identical configurations for the host and
* target, and you can ignore everything below. But please note: if you're cross-compiling for k8
* on a piii machine, your build will fail. This is a stopgap measure.
*
* <p>Currently, every build is (in effect) a cross-compile, in the strict sense that host and
* target configurations are unequal, thus we do not issue a "cross-compiling" warning. (Perhaps
* we should?) *
*
* @param requestConfig the requested target (not host!) configuration for this build.
* @param buildOptions the configuration options used for the target configuration
*/
@Nullable
private static BuildConfiguration getHostConfigurationFromRequest(
ConfigurationFactory configurationFactory,
PackageProviderForConfigurations loadedPackageProvider,
BuildConfiguration requestConfig,
BuildOptions buildOptions,
Cache<String, BuildConfiguration> cache)
throws InvalidConfigurationException, InterruptedException {
BuildConfiguration.Options commonOptions = buildOptions.get(BuildConfiguration.Options.class);
if (!commonOptions.useDistinctHostConfiguration) {
return requestConfig;
} else {
BuildConfiguration hostConfig = configurationFactory.getConfiguration(
loadedPackageProvider, buildOptions.createHostOptions(false), cache);
if (hostConfig == null) {
return null;
}
return hostConfig;
}
}
static BuildConfiguration setupTransitions(BuildConfiguration targetConfiguration,
BuildConfiguration dataConfiguration, BuildConfiguration hostConfiguration,
ListMultimap<SplitTransition<?>, BuildConfiguration> splitTransitionsTable) {
Set<BuildConfiguration> allConfigurations = new LinkedHashSet<>();
allConfigurations.add(targetConfiguration);
allConfigurations.add(dataConfiguration);
allConfigurations.add(hostConfiguration);
allConfigurations.addAll(splitTransitionsTable.values());
Table<BuildConfiguration, Transition, ConfigurationHolder> transitionBuilder =
HashBasedTable.create();
for (BuildConfiguration from : allConfigurations) {
for (ConfigurationTransition transition : ConfigurationTransition.values()) {
BuildConfiguration to;
if (transition == ConfigurationTransition.HOST) {
to = hostConfiguration;
} else if (transition == ConfigurationTransition.DATA && from == targetConfiguration) {
to = dataConfiguration;
} else {
to = from;
}
transitionBuilder.put(from, transition, new ConfigurationHolder(to));
}
}
// TODO(bazel-team): This makes LIPO totally not work. Just a band-aid until we get around to
// implementing a way for the C++ rules to contribute this transition to the configuration
// collection.
for (BuildConfiguration config : allConfigurations) {
transitionBuilder.put(config, LipoTransition.LIPO_COLLECTOR,
new ConfigurationHolder(config));
transitionBuilder.put(config, LipoTransition.TARGET_CONFIG_FOR_LIPO,
new ConfigurationHolder(config.isHostConfiguration() ? null : config));
}
for (BuildConfiguration config : allConfigurations) {
// We allow host configurations to be shared between target configurations. In that case, the
// transitions may already be set.
// TODO(bazel-team): Check that the transitions are identical, or even better, change the
// code to set the host configuration transitions before we even create the target
// configuration.
if (config.isHostConfiguration() && config.getTransitions() != null) {
continue;
}
boolean isSplitConfig = splitTransitionsTable.values().contains(config);
// When --experimental_multi_cpu is set, we create multiple target configurations that only
// differ by --cpu. We may therefore end up with multiple identical split configurations, if
// the split transition overwrites the cpu, which it usually does.
if (isSplitConfig && config.getTransitions() != null) {
continue;
}
Transitions outgoingTransitions =
new BazelTransitions(
config,
transitionBuilder.row(config),
// Split transitions must not have their own split transitions because then they
// would be applied twice due to a quirk in DependencyResolver. See the comment in
// DependencyResolver.resolveLateBoundAttributes().
isSplitConfig
? ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>of()
: splitTransitionsTable);
config.setConfigurationTransitions(outgoingTransitions);
}
return targetConfiguration;
}
}