blob: 3c224844f9eba6923a71af852492d4e6ac15fea1 [file] [log] [blame]
// Copyright 2018 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.exec;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableClassToInstanceMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.MutableClassToInstanceMap;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.ActionContext;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.ExecutionOptions.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* Registry containing all available {@linkplain ActionContext action contexts}.
*
* <p>Contexts can be {@linkplain #getContext queried} by a common subtype of {@link ActionContext}
* that they implement (which can be the implementation class itself). It is possible to {@linkplain
* Builder#restrictTo restrict) the available contexts for a type to those who were {@linkplain
* Builder#register registered with specific command-line identifiers}. If more than one context was
* {@link Builder#register registered} for the same type and they are not distinguished by the
* restriction then this registry will return the last registered context.
*
* <p>An instance of this registry can be created using its {@linkplain Builder builder}, which is
* available to Blaze modules during server startup.
*/
public final class ModuleActionContextRegistry
implements ActionContext, ActionContext.ActionContextRegistry {
private final ImmutableClassToInstanceMap<ActionContext> identifyingTypeToContext;
private ModuleActionContextRegistry(
ImmutableClassToInstanceMap<ActionContext> identifyingTypeToContext) {
this.identifyingTypeToContext = identifyingTypeToContext;
}
@Override
public <T extends ActionContext> T getContext(Class<T> identifyingType) {
return identifyingTypeToContext.getInstance(identifyingType);
}
/**
* Notifies all contexts stored in this registry that they are {@linkplain
* ActionContext#usedContext used}.
*/
public void notifyUsed() {
for (ActionContext context : identifyingTypeToContext.values()) {
context.usedContext(this);
}
}
/**
* Records the list of all contexts that can be {@linkplain #getContext returned by this registry}
* to the given reporter.
*/
void writeActionContextsTo(Reporter reporter) {
for (Map.Entry<Class<? extends ActionContext>, ActionContext> typeToContext :
identifyingTypeToContext.entrySet()) {
reporter.handle(
Event.info(
String.format(
"IdentifyingTypeToContext: \"%s\" = [%s]",
typeToContext.getKey(), typeToContext.getValue().getClass().getSimpleName())));
}
}
/**
* Returns a new {@link Builder} suitable for creating instances of ModuleActionContextRegistry.
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder collecting the contexts and restrictions thereon for a {@link
* ModuleActionContextRegistry}.
*/
public static final class Builder {
private final List<ActionContextInformation<?>> actionContexts = new ArrayList<>();
private final Map<Class<?>, String> typeToRestriction = new HashMap<>();
/**
* Restricts the registry to only return implementations for the given type if they were
* {@linkplain #register registered} with the provided restriction as a command-line identifier.
*
* <p>Note that if no registered action context matches the requested command-line identifiers
* when it is {@linkplain #build() built} then the registry will return {@code null} when
* queried for this identifying type.
*
* <p>This behavior can be reset by passing an empty restriction to this method which will cause
* the default behavior (last implementation registered for the identifying type) to be used.
*
* @param restriction command-line identifier used during registration of the desired
* implementation or {@code ""} to allow any implementation of the identifying type
*/
@CanIgnoreReturnValue
public Builder restrictTo(Class<?> identifyingType, String restriction) {
typeToRestriction.put(identifyingType, restriction);
return this;
}
/**
* Registers an action context implementation identified by the given type and which can be
* {@linkplain #restrictTo restricted} by its provided command-line identifiers.
*/
@CanIgnoreReturnValue
public <T extends ActionContext> Builder register(
Class<T> identifyingType, T context, String... commandLineIdentifiers) {
actionContexts.add(
new AutoValue_ModuleActionContextRegistry_ActionContextInformation<>(
context, identifyingType, ImmutableList.copyOf(commandLineIdentifiers)));
return this;
}
/** Constructs the registry configured by this builder. */
public ModuleActionContextRegistry build() throws AbruptExitException {
HashSet<Class<?>> usedTypes = new HashSet<>();
MutableClassToInstanceMap<ActionContext> contextToInstance =
MutableClassToInstanceMap.create();
for (ActionContextInformation<?> actionContextInformation : actionContexts) {
Class<? extends ActionContext> identifyingType = actionContextInformation.identifyingType();
if (typeToRestriction.containsKey(identifyingType)) {
String restriction = typeToRestriction.get(identifyingType);
if (!actionContextInformation.commandLineIdentifiers().contains(restriction)
&& !restriction.isEmpty()) {
continue;
}
}
usedTypes.add(identifyingType);
actionContextInformation.addToMap(contextToInstance);
}
Sets.SetView<Class<?>> unusedRestrictions =
Sets.difference(typeToRestriction.keySet(), usedTypes);
if (!unusedRestrictions.isEmpty()) {
throw new AbruptExitException(
DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(getMissingIdentifierErrorMessage(unusedRestrictions))
.setExecutionOptions(
FailureDetails.ExecutionOptions.newBuilder()
.setCode(Code.RESTRICTION_UNMATCHED_TO_ACTION_CONTEXT))
.build()));
}
return new ModuleActionContextRegistry(ImmutableClassToInstanceMap.copyOf(contextToInstance));
}
private String getMissingIdentifierErrorMessage(Sets.SetView<Class<?>> unusedRestrictions) {
Multimap<Class<?>, String> typeToAvailableIdentifiers = ArrayListMultimap.create();
for (Class<?> type : unusedRestrictions) {
for (ActionContextInformation<?> actionContextInformation : actionContexts) {
if (actionContextInformation.identifyingType().equals(type)) {
typeToAvailableIdentifiers.putAll(
type, actionContextInformation.commandLineIdentifiers());
}
}
}
StringBuilder message = new StringBuilder();
for (Map.Entry<Class<?>, Collection<String>> typeToIdentifiers :
typeToAvailableIdentifiers.asMap().entrySet()) {
Class<?> type = typeToIdentifiers.getKey();
message.append(
String.format(
"No context of type %s registered for requested value '%s', available identifiers"
+ " are: [%s]%n",
type.getSimpleName(),
typeToRestriction.get(type),
Joiner.on(", ").join(typeToIdentifiers.getValue())));
}
message.append("unused ").append(unusedRestrictions);
return message.toString();
}
}
@AutoValue
abstract static class ActionContextInformation<T extends ActionContext> {
abstract T context();
abstract Class<T> identifyingType();
abstract ImmutableList<String> commandLineIdentifiers();
private void addToMap(MutableClassToInstanceMap<ActionContext> map) {
map.putInstance(identifyingType(), context());
}
}
}