blob: 40d6701c330546e89239fd28499db0611180be39 [file] [log] [blame]
// Copyright 2017 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 static java.util.stream.Collectors.joining;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.PlatformConfiguration;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.ToolchainContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
import com.google.devtools.build.lib.skyframe.RegisteredToolchainsFunction.InvalidToolchainLabelException;
import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.NoToolchainFoundException;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.skyframe.SkyFunction.Environment;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.ValueOrException;
import com.google.devtools.build.skyframe.ValueOrException4;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Common code to create a {@link ToolchainContext} given a set of required toolchain type labels.
*/
public class ToolchainUtil {
/**
* Returns a new {@link ToolchainContext}, with the correct toolchain labels based on the results
* of the {@link ToolchainResolutionFunction}.
*/
@Nullable
static ToolchainContext createToolchainContext(
Environment env,
String targetDescription,
Set<Label> requiredToolchains,
@Nullable BuildConfigurationValue.Key configurationKey)
throws ToolchainContextException, InterruptedException {
// In some cases this is called with a missing configuration, so we skip toolchain context.
if (configurationKey == null) {
return null;
}
// This call could be combined with the call below, but this SkyFunction is evaluated so rarely
// it's not worth optimizing.
BuildConfigurationValue value = (BuildConfigurationValue) env.getValue(configurationKey);
if (env.valuesMissing()) {
return null;
}
BuildConfiguration configuration = value.getConfiguration();
// Load the target and host platform keys.
PlatformConfiguration platformConfiguration =
configuration.getFragment(PlatformConfiguration.class);
if (platformConfiguration == null) {
return null;
}
Label hostPlatformLabel = platformConfiguration.getHostPlatform();
Label targetPlatformLabel = platformConfiguration.getTargetPlatforms().get(0);
ConfiguredTargetKey hostPlatformKey = ConfiguredTargetKey.of(hostPlatformLabel, configuration);
ConfiguredTargetKey targetPlatformKey =
ConfiguredTargetKey.of(targetPlatformLabel, configuration);
// Load the host and target platforms early, to check for errors.
getPlatformInfo(ImmutableList.of(hostPlatformKey, targetPlatformKey), env);
// Load all available execution platform keys. This will find any errors in the execution
// platform definitions.
RegisteredExecutionPlatformsValue registeredExecutionPlatforms =
loadRegisteredExecutionPlatforms(env, configurationKey);
if (registeredExecutionPlatforms == null) {
return null;
}
ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys =
new ImmutableList.Builder<ConfiguredTargetKey>()
.addAll(registeredExecutionPlatforms.registeredExecutionPlatformKeys())
.add(hostPlatformKey)
.build();
Optional<ResolvedToolchains> resolvedToolchains =
resolveToolchainLabels(
env,
requiredToolchains,
configuration,
configurationKey,
availableExecutionPlatformKeys,
targetPlatformKey);
if (resolvedToolchains == null) {
return null;
}
if (resolvedToolchains.isPresent()) {
return createContext(
env,
targetDescription,
resolvedToolchains.get().executionPlatformKey(),
resolvedToolchains.get().targetPlatformKey(),
requiredToolchains,
resolvedToolchains.get().toolchains());
} else {
// No toolchain could be resolved, but no error happened, so fall back to host platform.
return createContext(
env,
targetDescription,
hostPlatformKey,
targetPlatformKey,
requiredToolchains,
ImmutableBiMap.of());
}
}
private static RegisteredExecutionPlatformsValue loadRegisteredExecutionPlatforms(
Environment env, BuildConfigurationValue.Key configurationKey)
throws InterruptedException, ToolchainContextException {
try {
RegisteredExecutionPlatformsValue registeredExecutionPlatforms =
(RegisteredExecutionPlatformsValue)
env.getValueOrThrow(
RegisteredExecutionPlatformsValue.key(configurationKey),
InvalidPlatformException.class);
if (registeredExecutionPlatforms == null) {
return null;
}
return registeredExecutionPlatforms;
} catch (InvalidPlatformException e) {
throw new ToolchainContextException(e);
}
}
@Nullable
static Map<ConfiguredTargetKey, PlatformInfo> getPlatformInfo(
Iterable<ConfiguredTargetKey> platformKeys, Environment env)
throws InterruptedException, ToolchainContextException {
Map<SkyKey, ValueOrException<ConfiguredValueCreationException>> values =
env.getValuesOrThrow(platformKeys, ConfiguredValueCreationException.class);
boolean valuesMissing = env.valuesMissing();
Map<ConfiguredTargetKey, PlatformInfo> platforms = valuesMissing ? null : new HashMap<>();
try {
for (ConfiguredTargetKey key : platformKeys) {
PlatformInfo platformInfo = findPlatformInfo(values.get(key));
if (!valuesMissing && platformInfo != null) {
platforms.put(key, platformInfo);
}
}
} catch (ConfiguredValueCreationException e) {
throw new ToolchainContextException(e);
}
if (valuesMissing) {
return null;
}
return platforms;
}
/**
* Returns the {@link PlatformInfo} provider from the {@link ConfiguredTarget} in the {@link
* ValueOrException}, or {@code null} if the {@link ConfiguredTarget} is not present. If the
* {@link ConfiguredTarget} does not have a {@link PlatformInfo} provider, a {@link
* InvalidPlatformException} is thrown, wrapped in a {@link ToolchainContextException}.
*/
@Nullable
private static PlatformInfo findPlatformInfo(
ValueOrException<ConfiguredValueCreationException> valueOrException)
throws ConfiguredValueCreationException, ToolchainContextException {
ConfiguredTargetValue ctv = (ConfiguredTargetValue) valueOrException.get();
if (ctv == null) {
return null;
}
ConfiguredTarget configuredTarget = ctv.getConfiguredTarget();
PlatformInfo platformInfo = PlatformProviderUtils.platform(configuredTarget);
if (platformInfo == null) {
throw new ToolchainContextException(
new InvalidPlatformException(configuredTarget.getLabel()));
}
return platformInfo;
}
/** Data class to hold the result of resolving toolchain labels. */
@AutoValue
protected abstract static class ResolvedToolchains {
abstract ConfiguredTargetKey executionPlatformKey();
abstract ConfiguredTargetKey targetPlatformKey();
abstract ImmutableBiMap<Label, Label> toolchains();
protected static ResolvedToolchains create(
ConfiguredTargetKey executionPlatformKey,
ConfiguredTargetKey targetPlatformKey,
Map<Label, Label> toolchains) {
return new AutoValue_ToolchainUtil_ResolvedToolchains(
executionPlatformKey, targetPlatformKey, ImmutableBiMap.copyOf(toolchains));
}
}
@Nullable
private static Optional<ResolvedToolchains> resolveToolchainLabels(
Environment env,
Set<Label> requiredToolchains,
BuildConfiguration configuration,
BuildConfigurationValue.Key configurationKey,
ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys,
ConfiguredTargetKey targetPlatformKey)
throws InterruptedException, ToolchainContextException {
// If there are no required toolchains, bail out early.
if (requiredToolchains.isEmpty()) {
return Optional.absent();
}
// Find the toolchains for the required toolchain types.
List<ToolchainResolutionValue.Key> registeredToolchainKeys = new ArrayList<>();
for (Label toolchainType : requiredToolchains) {
registeredToolchainKeys.add(
ToolchainResolutionValue.key(
configurationKey, toolchainType, targetPlatformKey, availableExecutionPlatformKeys));
}
Map<
SkyKey,
ValueOrException4<
NoToolchainFoundException, ConfiguredValueCreationException,
InvalidToolchainLabelException, EvalException>>
results =
env.getValuesOrThrow(
registeredToolchainKeys,
NoToolchainFoundException.class,
ConfiguredValueCreationException.class,
InvalidToolchainLabelException.class,
EvalException.class);
boolean valuesMissing = false;
// Determine the potential set of toolchains.
Table<ConfiguredTargetKey, Label, Label> resolvedToolchains = HashBasedTable.create();
List<Label> missingToolchains = new ArrayList<>();
for (Map.Entry<
SkyKey,
ValueOrException4<
NoToolchainFoundException, ConfiguredValueCreationException,
InvalidToolchainLabelException, EvalException>>
entry : results.entrySet()) {
try {
Label requiredToolchainType =
((ToolchainResolutionValue.Key) entry.getKey().argument()).toolchainType();
ValueOrException4<
NoToolchainFoundException, ConfiguredValueCreationException,
InvalidToolchainLabelException, EvalException>
valueOrException = entry.getValue();
if (valueOrException.get() == null) {
valuesMissing = true;
continue;
}
ToolchainResolutionValue toolchainResolutionValue =
(ToolchainResolutionValue) valueOrException.get();
addPlatformsAndLabels(resolvedToolchains, requiredToolchainType, toolchainResolutionValue);
} catch (NoToolchainFoundException e) {
// Save the missing type and continue looping to check for more.
missingToolchains.add(e.missingToolchainType());
} catch (ConfiguredValueCreationException e) {
throw new ToolchainContextException(e);
} catch (InvalidToolchainLabelException e) {
throw new ToolchainContextException(e);
} catch (EvalException e) {
throw new ToolchainContextException(e);
}
}
if (!missingToolchains.isEmpty()) {
throw new ToolchainContextException(new UnresolvedToolchainsException(missingToolchains));
}
if (valuesMissing) {
return null;
}
boolean debug = configuration.getOptions().get(PlatformOptions.class).toolchainResolutionDebug;
// Find and return the first execution platform which has all required toolchains.
for (ConfiguredTargetKey executionPlatformKey : availableExecutionPlatformKeys) {
// PlatformInfo executionPlatform = platforms.get(executionPlatformKey);
Map<Label, Label> toolchains = resolvedToolchains.row(executionPlatformKey);
if (!toolchains.keySet().containsAll(requiredToolchains)) {
// Not all toolchains are present, keep going
continue;
}
if (debug) {
env.getListener()
.handle(
Event.info(
String.format(
"ToolchainUtil: Selected execution platform %s, %s",
executionPlatformKey.getLabel(),
toolchains
.entrySet()
.stream()
.map(
e ->
String.format(
"type %s -> toolchain %s", e.getKey(), e.getValue()))
.collect(joining(", ")))));
}
return Optional.of(
ResolvedToolchains.create(executionPlatformKey, targetPlatformKey, toolchains));
}
return Optional.absent();
}
private static void addPlatformsAndLabels(
Table<ConfiguredTargetKey, Label, Label> resolvedToolchains,
Label requiredToolchainType,
ToolchainResolutionValue toolchainResolutionValue) {
for (Map.Entry<ConfiguredTargetKey, Label> entry :
toolchainResolutionValue.availableToolchainLabels().entrySet()) {
resolvedToolchains.put(entry.getKey(), requiredToolchainType, entry.getValue());
}
}
@Nullable
private static ToolchainContext createContext(
Environment env,
String targetDescription,
ConfiguredTargetKey executionPlatformKey,
ConfiguredTargetKey targetPlatformKey,
Set<Label> requiredToolchains,
ImmutableBiMap<Label, Label> toolchains)
throws ToolchainContextException, InterruptedException {
Map<ConfiguredTargetKey, PlatformInfo> platforms =
getPlatformInfo(ImmutableList.of(executionPlatformKey, targetPlatformKey), env);
if (platforms == null) {
return null;
}
return ToolchainContext.create(
targetDescription,
platforms.get(executionPlatformKey),
platforms.get(targetPlatformKey),
requiredToolchains,
toolchains);
}
@Nullable
static ImmutableList<Label> expandTargetPatterns(
Environment env, List<String> targetPatterns, FilteringPolicy filteringPolicy)
throws InvalidTargetPatternException, InterruptedException {
// First parse the patterns, and throw any errors immediately.
List<TargetPatternValue.TargetPatternKey> patternKeys = new ArrayList<>();
for (TargetPatternValue.TargetPatternSkyKeyOrException keyOrException :
TargetPatternValue.keys(targetPatterns, filteringPolicy, "")) {
try {
patternKeys.add(keyOrException.getSkyKey());
} catch (TargetParsingException e) {
throw new InvalidTargetPatternException(keyOrException.getOriginalPattern(), e);
}
}
// Then, resolve the patterns.
Map<SkyKey, ValueOrException<TargetParsingException>> resolvedPatterns =
env.getValuesOrThrow(patternKeys, TargetParsingException.class);
boolean valuesMissing = env.valuesMissing();
ImmutableList.Builder<Label> labels = valuesMissing ? null : new ImmutableList.Builder<>();
for (TargetPatternValue.TargetPatternKey pattern : patternKeys) {
TargetPatternValue value;
try {
value = (TargetPatternValue) resolvedPatterns.get(pattern).get();
if (!valuesMissing && value != null) {
labels.addAll(value.getTargets().getTargets());
}
} catch (TargetParsingException e) {
throw new InvalidTargetPatternException(pattern.getPattern(), e);
}
}
if (valuesMissing) {
return null;
}
return labels.build();
}
/**
* Exception used when an error occurs in {@link #expandTargetPatterns(Environment, List,
* FilteringPolicy)}.
*/
static final class InvalidTargetPatternException extends Exception {
private String invalidPattern;
private TargetParsingException tpe;
public InvalidTargetPatternException(String invalidPattern, TargetParsingException tpe) {
super(tpe);
this.invalidPattern = invalidPattern;
this.tpe = tpe;
}
public String getInvalidPattern() {
return invalidPattern;
}
public TargetParsingException getTpe() {
return tpe;
}
}
/** Exception used when a platform label is not a valid platform. */
static final class InvalidPlatformException extends Exception {
InvalidPlatformException(Label label) {
super(formatError(label));
}
InvalidPlatformException(Label label, ConfiguredValueCreationException e) {
super(formatError(label), e);
}
private static String formatError(Label label) {
return String.format(
"Target %s was referenced as a platform, but does not provide PlatformInfo", label);
}
}
/** Exception used when a toolchain type is required but no matching toolchain is found. */
public static final class UnresolvedToolchainsException extends Exception {
private final ImmutableList<Label> missingToolchainTypes;
public UnresolvedToolchainsException(List<Label> missingToolchainTypes) {
super(
String.format(
"no matching toolchains found for types %s",
Joiner.on(", ").join(missingToolchainTypes)));
this.missingToolchainTypes = ImmutableList.copyOf(missingToolchainTypes);
}
public ImmutableList<Label> missingToolchainTypes() {
return missingToolchainTypes;
}
}
/** Exception used to wrap exceptions during toolchain resolution. */
public static class ToolchainContextException extends Exception {
public ToolchainContextException(InvalidPlatformException e) {
super(e);
}
public ToolchainContextException(UnresolvedToolchainsException e) {
super(e);
}
public ToolchainContextException(ConfiguredValueCreationException e) {
super(e);
}
public ToolchainContextException(InvalidToolchainLabelException e) {
super(e);
}
public ToolchainContextException(EvalException e) {
super(e);
}
}
}