blob: 20dc0f0ef0812e562f840e123d1b34f5d8eb5c4b [file] [log] [blame]
// Copyright 2021 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;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.devtools.build.lib.packages.ExecGroup.DEFAULT_EXEC_GROUP_NAME;
import static java.util.stream.Collectors.joining;
import com.google.auto.value.AutoValue;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.ExecGroup;
import com.google.devtools.build.lib.server.FailureDetails.Analysis;
import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.AbstractSaneAnalysisException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* A container class for groups of {@link ExecGroup} instances. This correctly handles exec group
* inheritance between rules and targets. See https://bazel.build/reference/exec-groups for further
* details.
*/
@AutoValue
public abstract class ExecGroupCollection {
/**
* Prepares the input exec groups to serve as {@link Builder#execGroups}.
*
* <p>Applies any inheritance specified via {@link ExecGroup#copyFrom} and adds auto exec groups
* when {@code useAutoExecGroups} is true.
*/
public static ImmutableMap<String, ExecGroup> process(
ImmutableMap<String, ExecGroup> execGroups,
ImmutableSet<Label> defaultExecWith,
ImmutableSet<ToolchainTypeRequirement> defaultToolchainTypes,
boolean useAutoExecGroups) {
var processedGroups =
Maps.<String, ExecGroup>newHashMapWithExpectedSize(
useAutoExecGroups
? (execGroups.size() + defaultToolchainTypes.size())
: execGroups.size());
for (Map.Entry<String, ExecGroup> entry : execGroups.entrySet()) {
String name = entry.getKey();
ExecGroup execGroup = entry.getValue();
if (execGroup.copyFrom() != null) {
if (execGroup.copyFrom().equals(DEFAULT_EXEC_GROUP_NAME)) {
execGroup =
ExecGroup.builder()
.execCompatibleWith(defaultExecWith)
.toolchainTypes(defaultToolchainTypes)
.build();
} else {
execGroup = execGroup.inheritFrom(execGroups.get(execGroup.copyFrom()));
}
}
processedGroups.put(name, execGroup);
}
if (useAutoExecGroups) {
// Creates one exec group for each toolchain (automatic exec groups).
for (ToolchainTypeRequirement toolchainType : defaultToolchainTypes) {
processedGroups.put(
toolchainType.toolchainType().toString(),
ExecGroup.builder()
.addToolchainType(toolchainType)
.copyFrom(null)
.execCompatibleWith(defaultExecWith)
.build());
}
}
return ImmutableMap.copyOf(processedGroups);
}
/** Builder class for correctly constructing ExecGroupCollection instances. */
// Note that this is _not_ an actual @AutoValue.Builder: it provides more logic and has different
// fields.
public abstract static class Builder {
public abstract ImmutableMap<String, ExecGroup> execGroups();
public ExecGroupCollection build(
@Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
ImmutableMap<String, String> rawExecProperties)
throws InvalidExecGroupException {
// For each exec group, compute the combined execution properties.
ImmutableTable<String, String, String> combinedExecProperties =
computeCombinedExecProperties(toolchainContexts, rawExecProperties);
return new AutoValue_ExecGroupCollection(execGroups(), combinedExecProperties);
}
}
/**
* Gets the combined exec properties of the platform and the target's exec properties. If a
* property is set in both, the target properties take precedence.
*/
private static ImmutableTable<String, String, String> computeCombinedExecProperties(
@Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
ImmutableMap<String, String> rawExecProperties)
throws InvalidExecGroupException {
ImmutableSet<String> execGroupNames;
if (toolchainContexts == null) {
execGroupNames = ImmutableSet.of(DEFAULT_EXEC_GROUP_NAME);
} else {
execGroupNames = toolchainContexts.getExecGroupNames();
}
// Parse the target-level exec properties.
ImmutableTable<String, String, String> parsedTargetProperties =
parseExecProperties(rawExecProperties);
// Validate the exec group names in the properties.
if (toolchainContexts != null) {
ImmutableSet<String> unknownTargetExecGroupNames =
parsedTargetProperties.rowKeySet().stream()
.filter(name -> !name.equals(DEFAULT_EXEC_GROUP_NAME))
.filter(name -> !execGroupNames.contains(name))
.collect(toImmutableSet());
if (!unknownTargetExecGroupNames.isEmpty()) {
throw new InvalidExecGroupException(unknownTargetExecGroupNames);
}
}
// Parse each execution platform's exec properties.
ImmutableSet<PlatformInfo> executionPlatforms;
if (toolchainContexts == null) {
executionPlatforms = ImmutableSet.of();
} else {
executionPlatforms =
execGroupNames.stream()
.map(name -> toolchainContexts.getToolchainContext(name).executionPlatform())
.distinct()
.collect(toImmutableSet());
}
Map<PlatformInfo, ImmutableTable<String, String, String>> parsedPlatformProperties =
new LinkedHashMap<>();
for (PlatformInfo executionPlatform : executionPlatforms) {
ImmutableTable<String, String, String> parsed =
parseExecProperties(executionPlatform.execProperties());
parsedPlatformProperties.put(executionPlatform, parsed);
}
// First, get the defaults.
ImmutableMap<String, String> defaultExecProperties =
parsedTargetProperties.row(DEFAULT_EXEC_GROUP_NAME);
Table<String, String, String> result = HashBasedTable.create();
putAll(result, DEFAULT_EXEC_GROUP_NAME, defaultExecProperties);
for (String execGroupName : execGroupNames) {
ImmutableMap<String, String> combined =
computeProperties(
execGroupName,
defaultExecProperties,
toolchainContexts,
parsedPlatformProperties,
parsedTargetProperties);
putAll(result, execGroupName, combined);
}
return ImmutableTable.copyOf(result);
}
private static <R, C, V> void putAll(Table<R, C, V> builder, R row, Map<C, V> values) {
for (Map.Entry<C, V> entry : values.entrySet()) {
builder.put(row, entry.getKey(), entry.getValue());
}
}
private static ImmutableMap<String, String> computeProperties(
String execGroupName,
ImmutableMap<String, String> defaultExecProperties,
@Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
Map<PlatformInfo, ImmutableTable<String, String, String>> parsedPlatformProperties,
ImmutableTable<String, String, String> parsedTargetProperties) {
ImmutableMap<String, String> defaultExecGroupPlatformProperties;
ImmutableMap<String, String> platformProperties;
if (toolchainContexts == null) {
defaultExecGroupPlatformProperties = ImmutableMap.of();
platformProperties = ImmutableMap.of();
} else {
PlatformInfo executionPlatform =
toolchainContexts.getToolchainContext(execGroupName).executionPlatform();
defaultExecGroupPlatformProperties =
parsedPlatformProperties.get(executionPlatform).row(DEFAULT_EXEC_GROUP_NAME);
platformProperties = parsedPlatformProperties.get(executionPlatform).row(execGroupName);
}
Map<String, String> targetProperties =
new LinkedHashMap<>(parsedTargetProperties.row(execGroupName));
for (String propertyName : defaultExecProperties.keySet()) {
// If the property exists in the default and not in the target, copy it.
targetProperties.computeIfAbsent(propertyName, defaultExecProperties::get);
}
// Combine the target and exec platform properties. Target properties take precedence.
// Use a HashMap instead of an ImmutableMap.Builder because we expect duplicate keys.
Map<String, String> combined = new LinkedHashMap<>();
combined.putAll(defaultExecGroupPlatformProperties);
combined.putAll(defaultExecProperties);
combined.putAll(platformProperties);
combined.putAll(targetProperties);
return ImmutableMap.copyOf(combined);
}
protected abstract ImmutableMap<String, ExecGroup> execGroups();
protected abstract ImmutableTable<String, String, String> execProperties();
public ExecGroup getExecGroup(String execGroupName) {
return execGroups().get(execGroupName);
}
public ImmutableMap<String, String> getExecProperties(String execGroupName) {
return execProperties().row(execGroupName);
}
/**
* Parse raw exec properties attribute value into a map of exec group names to their properties.
* The raw map can have keys of two forms: (1) 'property' and (2) 'exec_group_name.property'. The
* former get parsed into the default exec group, the latter get parsed into their relevant exec
* groups.
*/
private static ImmutableTable<String, String, String> parseExecProperties(
Map<String, String> rawExecProperties) {
ImmutableTable.Builder<String, String, String> execProperties = ImmutableTable.builder();
for (Map.Entry<String, String> execProperty : rawExecProperties.entrySet()) {
String rawProperty = execProperty.getKey();
int delimiterIndex = rawProperty.indexOf('.');
if (delimiterIndex == -1) {
execProperties.put(DEFAULT_EXEC_GROUP_NAME, rawProperty, execProperty.getValue());
} else {
String execGroup = rawProperty.substring(0, delimiterIndex);
String property = rawProperty.substring(delimiterIndex + 1);
execProperties.put(execGroup, property, execProperty.getValue());
}
}
return execProperties.buildOrThrow();
}
/** An error for when the user tries to access a non-existent exec group. */
public static final class InvalidExecGroupException extends AbstractSaneAnalysisException {
public InvalidExecGroupException(Collection<String> invalidNames) {
super(
String.format(
"Tried to set properties for non-existent exec groups: %s.",
invalidNames.stream().collect(joining(","))));
}
@Override
public DetailedExitCode getDetailedExitCode() {
return DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(getMessage())
.setAnalysis(Analysis.newBuilder().setCode(Code.EXEC_GROUP_MISSING))
.build());
}
}
}