blob: e5b25ce7487e739f12635975bd181b652fe2863c [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.analysis.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.analysis.ConfigurationsCollector;
import com.google.devtools.build.lib.analysis.ConfigurationsResult;
import com.google.devtools.build.lib.analysis.Dependency;
import com.google.devtools.build.lib.analysis.DependencyKey;
import com.google.devtools.build.lib.analysis.DependencyKind;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.config.transitions.ComposingTransition;
import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
import com.google.devtools.build.lib.analysis.config.transitions.NullTransition;
import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionUtil;
import com.google.devtools.build.lib.analysis.starlark.FunctionTransitionUtil;
import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition;
import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition.TransitionException;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeTransitionData;
import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
import com.google.devtools.build.lib.skyframe.PackageValue;
import com.google.devtools.build.lib.skyframe.PlatformMappingValue;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.ValueOrException;
import com.google.devtools.common.options.OptionsParsingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* Turns configuration transition requests into actual configurations.
*
* <p>This involves:
*
* <ol>
* <li>Patching a source configuration's options with the transition.
* <li>Getting the destination configuration from Skyframe.
* </ol>
*
* <p>For the work of determining the transition requests themselves, see {@link
* TransitionResolver}.
*/
public final class ConfigurationResolver {
/**
* Determines the output ordering of each {@code <attribute, depLabel> -> [dep<config1>,
* dep<config2>, ...]} collection produced by a split transition.
*/
@VisibleForTesting
public static final Comparator<Dependency> SPLIT_DEP_ORDERING =
Comparator.comparing(
Functions.compose(BuildConfiguration::getMnemonic, Dependency::getConfiguration))
.thenComparing(
Functions.compose(BuildConfiguration::checksum, Dependency::getConfiguration));
private final SkyFunction.Environment env;
private final TargetAndConfiguration ctgValue;
private final BuildConfiguration hostConfiguration;
private final ImmutableMap<Label, ConfigMatchingProvider> configConditions;
/** The key for {@link #starlarkTransitionCache}. */
private static class StarlarkTransitionCacheKey {
private final ConfigurationTransition transition;
private final BuildOptions fromOptions;
private final int hashCode;
StarlarkTransitionCacheKey(ConfigurationTransition transition, BuildOptions fromOptions) {
// For rule self-transitions, the transition instance encapsulates both the transition logic
// and attributes of the target it's attached to. This is important: the same transition in
// the same configuration applied to distinct targets may produce different outputs. See
// StarlarkRuleTransitionProvider.FunctionPatchTransition for details.
// TODO(bazel-team): the transition code (i.e. StarlarkTransitionFunction) hashes on identity.
// Check that unnecessary copies of the transition function don't dilute this cache. Quick
// experimentation shows the # of such instances is very small. But it's unclear how strong
// of an interning contract there is.
this.transition = transition;
this.fromOptions = fromOptions;
this.hashCode = Objects.hash(transition, fromOptions);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof StarlarkTransitionCacheKey)) {
return false;
}
return (this.transition.equals(((StarlarkTransitionCacheKey) other).transition)
&& this.fromOptions.equals(((StarlarkTransitionCacheKey) other).fromOptions));
}
@Override
public int hashCode() {
return hashCode;
}
}
/** The result of a {@link #starlarkTransitionCache} lookup. */
private static class StarlarkTransitionCacheValue {
final Map<String, BuildOptions> result;
/**
* Stores events for successful transitions. Transitions that fail aren't added to the cache.
* This is meant for non-error events like Starlark {@code print()} output. See {@link
* StarlarkIntegrationTest#testPrintFromTransitionImpl} for a test that covers this.
*
* <p>This is null if the transition lacks non-error events.
*/
@Nullable final StoredEventHandler nonErrorEvents;
StarlarkTransitionCacheValue(
Map<String, BuildOptions> result, @Nullable StoredEventHandler nonErrorEvents) {
this.result = result;
this.nonErrorEvents = nonErrorEvents;
}
}
/**
* Caches the application of transitions that use Starlark.
*
* <p>This trivially includes {@link StarlarkTransition}s. But it also includes transitions that
* delegate to {@link StarlarkTransition}s, like some {@link ComposingTransition}s.
*
* <p>This cache was added to keep builds that heavily rely on Starlark transitions performant.
* The inspiring build is a large Apple binary that heavily relies on {@code objc_library.bzl},
* which applies a self-transition. The build applies this transition ~600,000 times. Each
* application has a cost, mostly from setup in translating Java objects to Starlark objects in
* {@link FunctionTransitionUtil#applyAndValidate}. This cache saves most of that work, reducing
* analysis phase CPU time by 17%.
*/
private static final Cache<StarlarkTransitionCacheKey, StarlarkTransitionCacheValue>
starlarkTransitionCache = Caffeine.newBuilder().softValues().build();
public ConfigurationResolver(
SkyFunction.Environment env,
TargetAndConfiguration ctgValue,
BuildConfiguration hostConfiguration,
ImmutableMap<Label, ConfigMatchingProvider> configConditions) {
this.env = env;
this.ctgValue = ctgValue;
this.hostConfiguration = hostConfiguration;
this.configConditions = configConditions;
}
private BuildConfiguration getCurrentConfiguration() {
return ctgValue.getConfiguration();
}
/**
* Translates a set of {@link DependencyKey} objects with configuration transition requests to the
* same objects with resolved configurations.
*
* <p>This method must preserve the original label ordering of each attribute. For example, if
* {@code dependencyKeys.get("data")} is {@code [":a", ":b"]}, the resolved variant must also be
* {@code [":a", ":b"]} in the same order.
*
* <p>For split transitions, {@code dependencyKeys.get("data") = [":a", ":b"]} can produce the
* output {@code [":a"<config1>, ":a"<config2>, ..., ":b"<config1>, ":b"<config2>, ...]}. All
* instances of ":a" still appear before all instances of ":b". But the {@code [":a"<config1>,
* ":a"<config2>"]} subset may be in any (deterministic) order. In particular, this may not be the
* same order as {@link SplitTransition#split}. If needed, this code can be modified to use that
* order, but that involves more runtime in performance-critical code, so we won't make that
* change without a clear need.
*
* <p>These configurations unconditionally include all fragments.
*
* <p>This method is heavily performance-optimized. Because {@link
* com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction} calls it over every edge in
* the configured target graph, small inefficiencies can have observable impact on analysis time.
* Keep this in mind when making modifications and performance-test any changes you make.
*
* @param dependencyKeys the transition requests for each dep and each dependency kind
* @return a mapping from each dependency kind in the source target to the {@link
* BuildConfiguration}s and {@link Label}s for the deps under that dependency kind . Returns
* null if not all Skyframe dependencies are available.
*/
@Nullable
public OrderedSetMultimap<DependencyKind, Dependency> resolveConfigurations(
OrderedSetMultimap<DependencyKind, DependencyKey> dependencyKeys)
throws ConfiguredValueCreationException, InterruptedException {
OrderedSetMultimap<DependencyKind, Dependency> resolvedDeps = OrderedSetMultimap.create();
boolean needConfigsFromSkyframe = false;
for (Map.Entry<DependencyKind, DependencyKey> entry : dependencyKeys.entries()) {
DependencyKind dependencyKind = entry.getKey();
DependencyKey dependencyKey = entry.getValue();
ImmutableList<Dependency> depConfig = resolveConfiguration(dependencyKind, dependencyKey);
if (depConfig == null) {
// Instead of returning immediately, give the loop a chance to queue up every missing
// dependency, then return all at once. That prevents re-executing this code an unnecessary
// number of times. i.e. this is equivalent to calling env.getValues() once over all deps.
needConfigsFromSkyframe = true;
} else {
resolvedDeps.putAll(dependencyKind, depConfig);
}
}
return needConfigsFromSkyframe ? null : resolvedDeps;
}
/**
* Translates a {@link DependencyKey} with configuration transition to the same objects with
* resolved configurations.
*
* <p>This is the single-argument version of {@link #resolveConfigurations}, whose documentation
* has more details.
*/
@Nullable
public ImmutableList<Dependency> resolveConfiguration(
DependencyKind dependencyKind, DependencyKey dependencyKey)
throws ConfiguredValueCreationException, InterruptedException {
Dependency.Builder dependencyBuilder = dependencyKey.getDependencyBuilder();
ConfigurationTransition transition = dependencyKey.getTransition();
if (transition == NullTransition.INSTANCE) {
Dependency resolvedDep = resolveNullTransition(dependencyBuilder, dependencyKind);
if (resolvedDep == null) {
return null; // Need Skyframe deps.
}
return ImmutableList.of(resolvedDep);
} else if (transition.isHostTransition()) {
return ImmutableList.of(resolveHostTransition(dependencyBuilder, dependencyKey));
}
return resolveGenericTransition(
getCurrentConfiguration().fragmentClasses(), dependencyBuilder, dependencyKey);
}
@Nullable
private Dependency resolveNullTransition(
Dependency.Builder dependencyBuilder, DependencyKind dependencyKind)
throws ConfiguredValueCreationException, InterruptedException {
// The null configuration can be trivially computed (it's, well, null), so special-case that
// transition here and skip the rest of the logic. A *lot* of targets have null deps, so
// this produces real savings. Profiling tests over a simple cc_binary show this saves ~1% of
// total analysis phase time.
if (dependencyKind.getAttribute() != null) {
ImmutableList<String> transitionKeys = collectTransitionKeys(dependencyKind.getAttribute());
if (transitionKeys == null) {
return null; // Need Skyframe deps.
}
dependencyBuilder.setTransitionKeys(transitionKeys);
}
return dependencyBuilder.withNullConfiguration().build();
}
private Dependency resolveHostTransition(
Dependency.Builder dependencyBuilder, DependencyKey dependencyKey) {
return dependencyBuilder
.setConfiguration(hostConfiguration)
.setAspects(dependencyKey.getAspects())
.build();
}
@Nullable
private ImmutableList<Dependency> resolveGenericTransition(
FragmentClassSet depFragments,
Dependency.Builder dependencyBuilder,
DependencyKey dependencyKey)
throws ConfiguredValueCreationException, InterruptedException {
Map<String, BuildOptions> toOptions;
try {
toOptions =
applyTransitionWithSkyframe(
getCurrentConfiguration().getOptions(),
dependencyKey.getTransition(),
env,
env.getListener());
if (toOptions == null) {
return null; // Need more Skyframe deps for a Starlark transition.
}
} catch (TransitionException e) {
throw new ConfiguredValueCreationException(ctgValue, e.getMessage());
}
if (depFragments.equals(getCurrentConfiguration().fragmentClasses())
&& SplitTransition.equals(getCurrentConfiguration().getOptions(), toOptions.values())) {
// The dep uses the same exact configuration. Let's re-use the current configuration and
// skip adding a Skyframe dependency edge on it.
return ImmutableList.of(
dependencyBuilder
.setConfiguration(getCurrentConfiguration())
.setAspects(dependencyKey.getAspects())
// Explicitly do not set the transition key, since there is only one configuration
// and it matches the current one. This ignores the transition key set if this
// was a split transition.
.build());
}
PathFragment platformMappingPath =
getCurrentConfiguration().getOptions().get(PlatformOptions.class).platformMappings;
PlatformMappingValue platformMappingValue =
(PlatformMappingValue) env.getValue(PlatformMappingValue.Key.create(platformMappingPath));
if (platformMappingValue == null) {
return null; // Need platform mappings from Skyframe.
}
Map<String, BuildConfigurationValue.Key> configurationKeys = new HashMap<>();
try {
for (Map.Entry<String, BuildOptions> optionsEntry : toOptions.entrySet()) {
String transitionKey = optionsEntry.getKey();
BuildConfigurationValue.Key buildConfigurationValueKey =
BuildConfigurationValue.keyWithPlatformMapping(
platformMappingValue, depFragments, optionsEntry.getValue());
configurationKeys.put(transitionKey, buildConfigurationValueKey);
}
} catch (OptionsParsingException e) {
throw new ConfiguredValueCreationException(ctgValue, e.getMessage());
}
Map<SkyKey, ValueOrException<InvalidConfigurationException>> depConfigValues =
env.getValuesOrThrow(configurationKeys.values(), InvalidConfigurationException.class);
List<Dependency> dependencies = new ArrayList<>();
try {
for (Map.Entry<String, BuildConfigurationValue.Key> entry : configurationKeys.entrySet()) {
String transitionKey = entry.getKey();
ValueOrException<InvalidConfigurationException> valueOrException =
depConfigValues.get(entry.getValue());
if (valueOrException.get() == null) {
continue;
}
BuildConfiguration configuration =
((BuildConfigurationValue) valueOrException.get()).getConfiguration();
if (configuration != null) {
Dependency resolvedDep =
dependencyBuilder
// Copy the builder so we don't overwrite the other dependencies.
.copy()
.setConfiguration(configuration)
.setAspects(dependencyKey.getAspects())
.setTransitionKey(transitionKey)
.build();
dependencies.add(resolvedDep);
}
}
if (env.valuesMissing()) {
return null; // Need dependency configurations.
}
} catch (InvalidConfigurationException e) {
throw new ConfiguredValueCreationException(ctgValue, e.getMessage());
}
return ImmutableList.sortedCopyOf(SPLIT_DEP_ORDERING, dependencies);
}
@Nullable
private ImmutableList<String> collectTransitionKeys(Attribute attribute)
throws ConfiguredValueCreationException, InterruptedException {
TransitionFactory<AttributeTransitionData> transitionFactory = attribute.getTransitionFactory();
if (transitionFactory.isSplit()) {
AttributeTransitionData transitionData =
AttributeTransitionData.builder()
.attributes(
ConfiguredAttributeMapper.of(
ctgValue.getTarget().getAssociatedRule(),
configConditions,
ctgValue.getConfiguration().checksum()))
.build();
ConfigurationTransition baseTransition = transitionFactory.create(transitionData);
Map<String, BuildOptions> toOptions;
try {
toOptions =
applyTransitionWithSkyframe(
getCurrentConfiguration().getOptions(), baseTransition, env, env.getListener());
if (toOptions == null) {
return null; // Need more Skyframe deps for a Starlark transition.
}
} catch (TransitionException e) {
throw new ConfiguredValueCreationException(ctgValue, e.getMessage());
}
if (!SplitTransition.equals(getCurrentConfiguration().getOptions(), toOptions.values())) {
return ImmutableList.copyOf(toOptions.keySet());
}
}
return ImmutableList.of();
}
/**
* Applies a configuration transition over a set of build options.
*
* <p>This is only for callers that can't use {@link #applyTransitionWithSkyframe}. The difference
* is {@link #applyTransitionWithSkyframe} internally computes {@code buildSettingPackages} with
* Skyframe, while this version requires it as a precomputed input.
*
* <p>prework - load all default values for reading build settings in Starlark transitions (by
* design, {@link BuildOptions} never holds default values of build settings)
*
* <p>postwork - replay events/throw errors from transition implementation function and validate
* the outputs of the transition. This only applies to Starlark transitions.
*
* @return the build options for the transitioned configuration.
*/
public static Map<String, BuildOptions> applyTransitionWithoutSkyframe(
BuildOptions fromOptions,
ConfigurationTransition transition,
Map<PackageValue.Key, PackageValue> buildSettingPackages,
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
if (StarlarkTransition.doesStarlarkTransition(transition)) {
return applyStarlarkTransition(fromOptions, transition, buildSettingPackages, eventHandler);
}
return transition.apply(TransitionUtil.restrict(transition, fromOptions), eventHandler);
}
/**
* Applies a configuration transition over a set of build options.
*
* <p>Callers should use this over {@link #applyTransitionWithoutSkyframe}. Unlike that variation,
* this would may return null if it needs more Skyframe deps.
*
* <p>postwork - replay events/throw errors from transition implementation function and validate
* the outputs of the transition. This only applies to Starlark transitions.
*
* @return the build options for the transitioned configuration, or null if Skyframe dependencies
* for build_setting default values for Starlark transitions. These can be read from their
* respective packages.
*/
@Nullable
public static Map<String, BuildOptions> applyTransitionWithSkyframe(
BuildOptions fromOptions,
ConfigurationTransition transition,
SkyFunction.Environment env,
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
if (StarlarkTransition.doesStarlarkTransition(transition)) {
// TODO(blaze-team): find a way to dedupe this with SkyframeExecutor.getBuildSettingPackages.
Map<PackageValue.Key, PackageValue> buildSettingPackages =
StarlarkTransition.getBuildSettingPackages(env, transition);
return buildSettingPackages == null
? null
: applyStarlarkTransition(fromOptions, transition, buildSettingPackages, eventHandler);
}
return transition.apply(TransitionUtil.restrict(transition, fromOptions), eventHandler);
}
/**
* Applies a Starlark transition.
*
* @param fromOptions source options before the transition
* @param transition the transition itself
* @param buildSettingPackages packages for build_settings read by the transition. This is used to
* read default values for build_settings that aren't explicitly set on the build.
* @param eventHandler handler for errors evaluating the transition.
* @return transition output
*/
private static Map<String, BuildOptions> applyStarlarkTransition(
BuildOptions fromOptions,
ConfigurationTransition transition,
Map<PackageValue.Key, PackageValue> buildSettingPackages,
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
StarlarkTransitionCacheKey cacheKey = new StarlarkTransitionCacheKey(transition, fromOptions);
StarlarkTransitionCacheValue cachedResult = starlarkTransitionCache.getIfPresent(cacheKey);
if (cachedResult != null) {
if (cachedResult.nonErrorEvents != null) {
cachedResult.nonErrorEvents.replayOn(eventHandler);
}
return cachedResult.result;
}
BuildOptions adjustedOptions =
StarlarkTransition.addDefaultStarlarkOptions(fromOptions, transition, buildSettingPackages);
// TODO(bazel-team): Add safety-check that this never mutates fromOptions.
StoredEventHandler handlerWithErrorStatus = new StoredEventHandler();
Map<String, BuildOptions> result =
transition.apply(
TransitionUtil.restrict(transition, adjustedOptions), handlerWithErrorStatus);
// We use a temporary StoredEventHandler instead of the caller's event handler because
// StarlarkTransition.validate assumes no errors occurred. We need a StoredEventHandler to be
// able to check that, and fail out early if there are errors.
//
// TODO(bazel-team): harden StarlarkTransition.validate so we can eliminate this step.
// StarlarkRuleTransitionProviderTest#testAliasedBuildSetting_outputReturnMismatch shows the
// effect.
handlerWithErrorStatus.replayOn(eventHandler);
if (handlerWithErrorStatus.hasErrors()) {
throw new TransitionException("Errors encountered while applying Starlark transition");
}
result = StarlarkTransition.validate(transition, buildSettingPackages, result);
// If the transition errored (like bad Starlark code), this method already exited with an
// exception so the results won't go into the cache. We still want to collect non-error events
// like print() output.
StoredEventHandler nonErrorEvents =
!handlerWithErrorStatus.isEmpty() ? handlerWithErrorStatus : null;
starlarkTransitionCache.put(cacheKey, new StarlarkTransitionCacheValue(result, nonErrorEvents));
return result;
}
/**
* This method allows resolution of configurations outside of a skyfunction call.
*
* <p>Unlike {@link #resolveConfigurations}, this doesn't expect the current context to be
* evaluating dependencies of a parent target. So this method is also suitable for top-level
* targets.
*
* <p>Resolution consists of applying the per-target transitions specified in {@code
* targetsToEvaluate}. This can be used, e.g., to apply {@link
* com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory}s over global
* top-level configurations.
*
* <p>Preserves the original input order (but merges duplicate nodes that might occur due to
* top-level configuration transitions) . Uses original (untrimmed, pre-transition) configurations
* for targets that can't be evaluated (e.g. due to loading phase errors).
*
* <p>This is suitable for feeding {@link
* com.google.devtools.build.lib.analysis.ConfiguredTargetValue} keys: as general principle {@link
* com.google.devtools.build.lib.analysis.ConfiguredTarget}s should have exactly as much
* information in their configurations as they need to evaluate and no more (e.g. there's no need
* for Android settings in a C++ configured target).
*
* @param defaultContext the original targets and starting configurations before applying rule
* transitions and trimming. When actual configurations can't be evaluated, these values are
* returned as defaults. See TODO below.
* @param targetsToEvaluate the inputs repackaged as dependencies, including rule-specific
* transitions
* @param eventHandler the error event handler
* @param configurationsCollector the collector which finds configurations for dependencies
*/
// TODO(bazel-team): error out early for targets that fail - failed configuration evaluations
// should never make it through analysis (and especially not seed ConfiguredTargetValues)
// TODO(gregce): merge this more with resolveConfigurations? One crucial difference is
// resolveConfigurations can null-return on missing deps since it executes inside Skyfunctions.
// Keep this in sync with {@link PrepareAnalysisPhaseFunction#resolveConfigurations}.
public static TopLevelTargetsAndConfigsResult getConfigurationsFromExecutor(
Iterable<TargetAndConfiguration> defaultContext,
Multimap<BuildConfiguration, DependencyKey> targetsToEvaluate,
ExtendedEventHandler eventHandler,
ConfigurationsCollector configurationsCollector)
throws InvalidConfigurationException, InterruptedException {
Map<Label, Target> labelsToTargets = new HashMap<>();
for (TargetAndConfiguration targetAndConfig : defaultContext) {
labelsToTargets.put(targetAndConfig.getLabel(), targetAndConfig.getTarget());
}
// Maps <target, originalConfig> pairs to <target, finalConfig> pairs for targets that
// could be successfully Skyframe-evaluated.
Map<TargetAndConfiguration, TargetAndConfiguration> successfullyEvaluatedTargets =
new LinkedHashMap<>();
boolean hasError = false;
if (!targetsToEvaluate.isEmpty()) {
for (BuildConfiguration fromConfig : targetsToEvaluate.keySet()) {
ConfigurationsResult configurationsResult =
configurationsCollector.getConfigurations(
eventHandler, fromConfig.getOptions(), targetsToEvaluate.get(fromConfig));
hasError |= configurationsResult.hasError();
for (Map.Entry<DependencyKey, BuildConfiguration> evaluatedTarget :
configurationsResult.getConfigurationMap().entries()) {
Target target = labelsToTargets.get(evaluatedTarget.getKey().getLabel());
successfullyEvaluatedTargets.put(
new TargetAndConfiguration(target, fromConfig),
new TargetAndConfiguration(target, evaluatedTarget.getValue()));
}
}
}
LinkedHashSet<TargetAndConfiguration> result = new LinkedHashSet<>();
for (TargetAndConfiguration originalInput : defaultContext) {
// If the configuration couldn't be determined (e.g. loading phase error), use the original.
result.add(successfullyEvaluatedTargets.getOrDefault(originalInput, originalInput));
}
return new TopLevelTargetsAndConfigsResult(result, hasError);
}
/**
* The result of {@link #getConfigurationsFromExecutor} which also registers if an error was
* recorded.
*/
public static class TopLevelTargetsAndConfigsResult {
private final Collection<TargetAndConfiguration> configurations;
private final boolean hasError;
public TopLevelTargetsAndConfigsResult(
Collection<TargetAndConfiguration> configurations, boolean hasError) {
this.configurations = configurations;
this.hasError = hasError;
}
public boolean hasError() {
return hasError;
}
public Collection<TargetAndConfiguration> getTargetsAndConfigs() {
return configurations;
}
}
}