// 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.skyframe; import com.google.common.base.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; 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.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; import com.google.devtools.build.lib.analysis.config.HostTransition; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.PackageProviderForConfigurations; import com.google.devtools.build.lib.analysis.config.PatchTransition; import com.google.devtools.build.lib.events.ErrorSensingEventHandler; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.skyframe.ConfigurationCollectionValue.ConfigurationCollectionKey; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; /** * A builder for {@link ConfigurationCollectionValue} instances. */ public class ConfigurationCollectionFunction implements SkyFunction { private final Supplier<ConfigurationFactory> configurationFactory; private final RuleClassProvider ruleClassProvider; public ConfigurationCollectionFunction(Supplier<ConfigurationFactory> configurationFactory, RuleClassProvider ruleClassProvider) { this.configurationFactory = configurationFactory; this.ruleClassProvider = ruleClassProvider; } @Override public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException, ConfigurationCollectionFunctionException { ConfigurationCollectionKey collectionKey = (ConfigurationCollectionKey) skyKey.argument(); try { BuildConfigurationCollection result = getConfigurations(env, new SkyframePackageLoaderWithValueEnvironment(env, ruleClassProvider), collectionKey.getBuildOptions(), collectionKey.getMultiCpu()); // BuildConfigurationCollection can be created, but dependencies to some files might be // missing. In that case we need to build configurationCollection again. if (env.valuesMissing()) { return null; } return new ConfigurationCollectionValue(result); } catch (InvalidConfigurationException e) { throw new ConfigurationCollectionFunctionException(e); } } /** Create the build configurations with the given options. */ private BuildConfigurationCollection getConfigurations( Environment env, PackageProviderForConfigurations loadedPackageProvider, BuildOptions buildOptions, ImmutableSet<String> multiCpu) throws InvalidConfigurationException, InterruptedException { // We cache all the related configurations for this target configuration in a cache that is // dropped at the end of this method call. We instead rely on the cache for entire collections // for caching the target and related configurations, and on a dedicated host configuration // cache for the host configuration. Cache<String, BuildConfiguration> cache = CacheBuilder.newBuilder().<String, BuildConfiguration>build(); List<BuildConfiguration> targetConfigurations = new ArrayList<>(); if (!multiCpu.isEmpty()) { for (String cpu : multiCpu) { BuildConfiguration targetConfiguration = createConfiguration( cache, env.getListener(), loadedPackageProvider, buildOptions, cpu); if (targetConfiguration == null || targetConfigurations.contains(targetConfiguration)) { continue; } targetConfigurations.add(targetConfiguration); } if (loadedPackageProvider.valuesMissing()) { return null; } } else { BuildConfiguration targetConfiguration = createConfiguration( cache, env.getListener(), loadedPackageProvider, buildOptions, null); if (targetConfiguration == null) { return null; } targetConfigurations.add(targetConfiguration); } BuildConfiguration hostConfiguration = getHostConfiguration(env, targetConfigurations.get(0)); if (hostConfiguration == null) { return null; } return new BuildConfigurationCollection(targetConfigurations, hostConfiguration); } /** Returns the host configuration, or null on missing Skyframe deps. */ private BuildConfiguration getHostConfiguration( Environment env, BuildConfiguration targetConfiguration) throws InvalidConfigurationException, InterruptedException { if (targetConfiguration.useDynamicConfigurations()) { BuildOptions targetOptions = targetConfiguration.getOptions(); // The host configuration builds from the data, not the target options. This is done // so that host tools are always built without LIPO. BuildOptions dataOptions = targetOptions; Attribute.Transition dataTransition = targetConfiguration.getTransitions() .getDynamicTransition(Attribute.ConfigurationTransition.DATA); if (dataTransition != Attribute.ConfigurationTransition.NONE) { dataOptions = ((PatchTransition) dataTransition).apply(targetOptions); } BuildOptions hostOptions = targetOptions.get(BuildConfiguration.Options.class).useDistinctHostConfiguration ? HostTransition.INSTANCE.apply(dataOptions) : dataOptions; SkyKey hostConfigKey = BuildConfigurationValue.key( targetConfiguration.trimConfigurations() ? targetConfiguration.fragmentClasses() : ((ConfiguredRuleClassProvider) ruleClassProvider).getAllFragments(), hostOptions); BuildConfigurationValue skyValHost = (BuildConfigurationValue) env.getValueOrThrow(hostConfigKey, InvalidConfigurationException.class); // Also preload the target configuration so the configured target functions for // top-level targets don't have to waste cycles from a missing Skyframe dep. SkyKey targetConfigKey = BuildConfigurationValue.key(targetConfiguration.fragmentClasses(), targetOptions); BuildConfigurationValue skyValTarget = (BuildConfigurationValue) env.getValueOrThrow(targetConfigKey, InvalidConfigurationException.class); if (skyValHost == null || skyValTarget == null) { return null; } return skyValHost.getConfiguration(); } else { return targetConfiguration.getConfiguration(Attribute.ConfigurationTransition.HOST); } } @Nullable private BuildConfiguration createConfiguration( Cache<String, BuildConfiguration> cache, ExtendedEventHandler originalEventListener, PackageProviderForConfigurations loadedPackageProvider, BuildOptions buildOptions, String cpuOverride) throws InvalidConfigurationException, InterruptedException { ErrorSensingEventHandler eventHandler = new ErrorSensingEventHandler(originalEventListener); if (cpuOverride != null) { // TODO(bazel-team): Options classes should be immutable. This is a bit of a hack. buildOptions = buildOptions.clone(); buildOptions.get(BuildConfiguration.Options.class).cpu = cpuOverride; buildOptions.get(BuildConfiguration.Options.class).experimentalMultiCpuDistinguisher = cpuOverride; } BuildConfiguration targetConfig = configurationFactory.get().createConfigurations( cache, loadedPackageProvider, buildOptions, eventHandler); if (targetConfig == null) { return null; } // The ConfigurationFactory may report an error rather than throwing an exception to support // --keep_going. If so, we throw an error here. if (eventHandler.hasErrors()) { throw new InvalidConfigurationException("Build options are invalid"); } return targetConfig; } @Override public String extractTag(SkyKey skyKey) { return null; } /** * Used to declare all the exception types that can be wrapped in the exception thrown by * {@link ConfigurationCollectionFunction#compute}. */ private static final class ConfigurationCollectionFunctionException extends SkyFunctionException { public ConfigurationCollectionFunctionException(InvalidConfigurationException e) { super(e, Transience.PERSISTENT); } } }