Convert ToolchainResolver to ToolchainResolutionFunction. Part of work on execution transitions, #7935. Closes #8070. PiperOrigin-RevId: 244355735
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java index 0bef37d..9a19b10 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
@@ -32,6 +32,7 @@ import com.google.devtools.build.lib.rules.AliasConfiguredTarget; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.skyframe.ToolchainException; +import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import com.google.devtools.build.lib.skylarkbuildapi.ToolchainContextApi; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.skylarkinterface.StarlarkContext;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java index 04e548a..4a92d5b 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java
@@ -42,6 +42,7 @@ import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionFunction.NoToolchainFoundException; import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionValue; import com.google.devtools.build.lib.skyframe.ToolchainException; +import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import com.google.devtools.build.skyframe.SkyFunction.Environment; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.ValueOrException2;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java deleted file mode 100644 index fe71c76..0000000 --- a/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java +++ /dev/null
@@ -1,60 +0,0 @@ -// Copyright 2019 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 com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.ImmutableSet; -import com.google.devtools.build.lib.analysis.platform.PlatformInfo; -import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo; -import com.google.devtools.build.lib.cmdline.Label; -import java.util.Set; - -/** - * Represents the state of toolchain resolution once the specific required toolchains have been - * determined, but before the toolchain dependencies have been resolved. - */ -@AutoValue -public abstract class UnloadedToolchainContext implements ToolchainContext { - - static Builder builder() { - return new AutoValue_UnloadedToolchainContext.Builder(); - } - - /** Builder class to help create the {@link UnloadedToolchainContext}. */ - @AutoValue.Builder - public interface Builder { - /** Sets the selected execution platform that these toolchains use. */ - Builder setExecutionPlatform(PlatformInfo executionPlatform); - - /** Sets the target platform that these toolchains generate output for. */ - Builder setTargetPlatform(PlatformInfo targetPlatform); - - /** Sets the toolchain types that were requested. */ - Builder setRequiredToolchainTypes(Set<ToolchainTypeInfo> requiredToolchainTypes); - - Builder setToolchainTypeToResolved( - ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved); - - UnloadedToolchainContext build(); - } - - /** The map of toolchain type to resolved toolchain to be used. */ - abstract ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved(); - - @Override - public ImmutableSet<Label> resolvedToolchainLabels() { - return toolchainTypeToResolved().values(); - } -}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java index 6dde831..743c7d2 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -32,7 +32,6 @@ import com.google.devtools.build.lib.analysis.ResolvedToolchainContext; import com.google.devtools.build.lib.analysis.TargetAndConfiguration; import com.google.devtools.build.lib.analysis.ToolchainResolver; -import com.google.devtools.build.lib.analysis.UnloadedToolchainContext; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java index 5c6481b..a266129 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -38,7 +38,6 @@ import com.google.devtools.build.lib.analysis.ResolvedToolchainContext; import com.google.devtools.build.lib.analysis.TargetAndConfiguration; import com.google.devtools.build.lib.analysis.ToolchainResolver; -import com.google.devtools.build.lib.analysis.UnloadedToolchainContext; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java index 4bd0451..7b4ddd2 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java
@@ -14,9 +14,13 @@ package com.google.devtools.build.lib.skyframe; +import static java.util.stream.Collectors.joining; + import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.events.Event; @@ -41,6 +45,15 @@ private static final Predicate<SkyKey> IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY = SkyFunctions.isSkyFunction(SkyFunctions.SINGLE_TOOLCHAIN_RESOLUTION); + private static final Predicate<SkyKey> IS_TOOLCHAIN_RESOLUTION_SKY_KEY = + SkyFunctions.isSkyFunction(SkyFunctions.TOOLCHAIN_RESOLUTION); + + private static final Predicate<SkyKey> IS_TOOLCHAIN_RELATED = + Predicates.or( + IS_REGISTERED_TOOLCHAINS_SKY_KEY, + IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY, + IS_TOOLCHAIN_RESOLUTION_SKY_KEY); + @Override public boolean maybeReportCycle( SkyKey topLevelKey, @@ -50,9 +63,7 @@ ImmutableList<SkyKey> cycle = cycleInfo.getCycle(); if (alreadyReported) { return true; - } else if (!Iterables.any(cycle, IS_REGISTERED_TOOLCHAINS_SKY_KEY) - || !Iterables.any(cycle, IS_CONFIGURED_TARGET_SKY_KEY) - || !Iterables.any(cycle, IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY)) { + } else if (!Iterables.any(cycle, IS_TOOLCHAIN_RELATED)) { return false; } @@ -63,24 +74,28 @@ } Function<SkyKey, String> printer = - new Function<SkyKey, String>() { - @Override - public String apply(SkyKey input) { - if (input.argument() instanceof ConfiguredTargetKey) { - Label label = ((ConfiguredTargetKey) input.argument()).getLabel(); - return label.toString(); - } - if (input.argument() instanceof RegisteredToolchainsValue.Key) { - return "RegisteredToolchains"; - } - if (input.argument() instanceof SingleToolchainResolutionValue.Key) { - Label toolchainType = - ((SingleToolchainResolutionValue.Key) input.argument()).toolchainTypeLabel(); - return String.format("toolchain type %s", toolchainType.toString()); - } else { - throw new UnsupportedOperationException(); - } + input -> { + if (input.argument() instanceof ConfiguredTargetKey) { + Label label = ((ConfiguredTargetKey) input.argument()).getLabel(); + return label.toString(); } + if (input.argument() instanceof RegisteredToolchainsValue.Key) { + return "RegisteredToolchains"; + } + if (input.argument() instanceof SingleToolchainResolutionValue.Key) { + Label toolchainType = + ((SingleToolchainResolutionValue.Key) input.argument()).toolchainTypeLabel(); + return String.format("toolchain type %s", toolchainType); + } + if (input.argument() instanceof UnloadedToolchainContext.Key) { + ImmutableSet<Label> toolchainTypes = + ((UnloadedToolchainContext.Key) input.argument()).requiredToolchainTypeLabels(); + return String.format( + "toolchain types %s", + toolchainTypes.stream().map(Label::toString).collect(joining(", "))); + } + + throw new UnsupportedOperationException(); }; StringBuilder cycleMessage =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java index ee43272..da00c9f 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -138,6 +138,8 @@ SkyFunctionName.createHermetic("REGISTERED_TOOLCHAINS"); static final SkyFunctionName SINGLE_TOOLCHAIN_RESOLUTION = SkyFunctionName.createHermetic("SINGLE_TOOLCHAIN_RESOLUTION"); + static final SkyFunctionName TOOLCHAIN_RESOLUTION = + SkyFunctionName.createHermetic("TOOLCHAIN_RESOLUTION"); public static final SkyFunctionName REPOSITORY_MAPPING = SkyFunctionName.createHermetic("REPOSITORY_MAPPING"); public static final SkyFunctionName RESOLVED_FILE =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java index eadebad..04b5338 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -593,6 +593,7 @@ SkyFunctions.REGISTERED_EXECUTION_PLATFORMS, new RegisteredExecutionPlatformsFunction()); map.put(SkyFunctions.REGISTERED_TOOLCHAINS, new RegisteredToolchainsFunction()); map.put(SkyFunctions.SINGLE_TOOLCHAIN_RESOLUTION, new SingleToolchainResolutionFunction()); + map.put(SkyFunctions.TOOLCHAIN_RESOLUTION, new ToolchainResolutionFunction()); map.put(SkyFunctions.REPOSITORY_MAPPING, new RepositoryMappingFunction()); map.put(SkyFunctions.RESOLVED_HASH_VALUES, new ResolvedHashesFunction()); map.put(SkyFunctions.RESOLVED_FILE, new ResolvedFileFunction());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java index cc9db65..46a4cf6 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java
@@ -13,6 +13,9 @@ // limitations under the License. package com.google.devtools.build.lib.skyframe; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException; +import javax.annotation.Nullable; + /** Base class for exceptions that happen during toolchain resolution. */ public class ToolchainException extends Exception { @@ -27,4 +30,21 @@ public ToolchainException(String message, Throwable cause) { super(message, cause); } + + /** + * Attempt to find a {@link ConfiguredValueCreationException} in this exception, or its causes. + * + * <p>If one cannot be found, a new one will be created. + */ + @Nullable + public ConfiguredValueCreationException asConfiguredValueCreationException() { + for (Throwable cause = getCause(); + cause != null && cause != cause.getCause(); + cause = cause.getCause()) { + if (cause instanceof ConfiguredValueCreationException) { + return (ConfiguredValueCreationException) cause; + } + } + return null; + } }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java new file mode 100644 index 0000000..2420696 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
@@ -0,0 +1,513 @@ +// Copyright 2019 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 com.google.common.collect.ImmutableList.toImmutableList; +import static java.util.stream.Collectors.joining; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Table; +import com.google.devtools.build.lib.analysis.PlatformConfiguration; +import com.google.devtools.build.lib.analysis.PlatformOptions; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo; +import com.google.devtools.build.lib.analysis.platform.PlatformInfo; +import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException; +import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException; +import com.google.devtools.build.lib.skyframe.RegisteredToolchainsFunction.InvalidToolchainLabelException; +import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionFunction.NoToolchainFoundException; +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.ValueOrException2; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * Sky function which performs toolchain resolution for multiple toolchain types, including + * selecting the execution platform. + */ +public class ToolchainResolutionFunction implements SkyFunction { + + @Nullable + @Override + public UnloadedToolchainContext compute(SkyKey skyKey, Environment env) + throws ToolchainResolutionFunctionException, InterruptedException { + UnloadedToolchainContext.Key key = (UnloadedToolchainContext.Key) skyKey.argument(); + + try { + UnloadedToolchainContext.Builder unloadedToolchainContext = + UnloadedToolchainContext.builder(); + + // Determine the configuration being used. + BuildConfigurationValue value = + (BuildConfigurationValue) env.getValue(key.configurationKey()); + if (value == null) { + throw new ValueMissingException(); + } + BuildConfiguration configuration = value.getConfiguration(); + PlatformConfiguration platformConfiguration = + configuration.getFragment(PlatformConfiguration.class); + if (platformConfiguration == null) { + throw new ValueMissingException(); + } + + // Check if debug output should be generated. + boolean debug = + configuration.getOptions().get(PlatformOptions.class).toolchainResolutionDebug; + + // Create keys for all platforms that will be used, and validate them early. + PlatformKeys platformKeys = + loadPlatformKeys( + env, + debug, + key.configurationKey(), + configuration, + platformConfiguration, + key.execConstraintLabels(), + key.shouldSanityCheckConfiguration()); + if (env.valuesMissing()) { + return null; + } + + // Determine the actual toolchain implementations to use. + determineToolchainImplementations( + env, + debug, + key.configurationKey(), + key.requiredToolchainTypeLabels(), + unloadedToolchainContext, + platformKeys, + key.shouldSanityCheckConfiguration()); + + return unloadedToolchainContext.build(); + } catch (ToolchainException e) { + throw new ToolchainResolutionFunctionException(e); + } catch (ValueMissingException e) { + return null; + } + } + + @AutoValue + abstract static class PlatformKeys { + abstract ConfiguredTargetKey hostPlatformKey(); + + abstract ConfiguredTargetKey targetPlatformKey(); + + abstract ImmutableList<ConfiguredTargetKey> executionPlatformKeys(); + + static PlatformKeys create( + ConfiguredTargetKey hostPlatformKey, + ConfiguredTargetKey targetPlatformKey, + List<ConfiguredTargetKey> executionPlatformKeys) { + return new AutoValue_ToolchainResolutionFunction_PlatformKeys( + hostPlatformKey, targetPlatformKey, ImmutableList.copyOf(executionPlatformKeys)); + } + } + + private PlatformKeys loadPlatformKeys( + SkyFunction.Environment environment, + boolean debug, + BuildConfigurationValue.Key configurationKey, + BuildConfiguration configuration, + PlatformConfiguration platformConfiguration, + ImmutableSet<Label> execConstraintLabels, + boolean shouldSanityCheckConfiguration) + throws InterruptedException, ValueMissingException, InvalidConstraintValueException, + InvalidPlatformException { + // Determine the target and host platform keys. + Label hostPlatformLabel = platformConfiguration.getHostPlatform(); + Label targetPlatformLabel = platformConfiguration.getTargetPlatform(); + + ConfiguredTargetKey hostPlatformKey = ConfiguredTargetKey.of(hostPlatformLabel, configuration); + ConfiguredTargetKey targetPlatformKey = + ConfiguredTargetKey.of(targetPlatformLabel, configuration); + + // Load the host and target platforms early, to check for errors. + PlatformLookupUtil.getPlatformInfo( + ImmutableList.of(hostPlatformKey, targetPlatformKey), + environment, + shouldSanityCheckConfiguration); + if (environment.valuesMissing()) { + throw new ValueMissingException(); + } + + ImmutableList<ConfiguredTargetKey> executionPlatformKeys = + loadExecutionPlatformKeys( + environment, + debug, + configurationKey, + configuration, + hostPlatformKey, + execConstraintLabels, + shouldSanityCheckConfiguration); + + return PlatformKeys.create(hostPlatformKey, targetPlatformKey, executionPlatformKeys); + } + + private ImmutableList<ConfiguredTargetKey> loadExecutionPlatformKeys( + SkyFunction.Environment environment, + boolean debug, + BuildConfigurationValue.Key configurationKey, + BuildConfiguration configuration, + ConfiguredTargetKey defaultPlatformKey, + ImmutableSet<Label> execConstraintLabels, + boolean shouldSanityCheckConfiguration) + throws InterruptedException, ValueMissingException, InvalidConstraintValueException, + InvalidPlatformException { + RegisteredExecutionPlatformsValue registeredExecutionPlatforms = + (RegisteredExecutionPlatformsValue) + environment.getValueOrThrow( + RegisteredExecutionPlatformsValue.key(configurationKey), + InvalidPlatformException.class); + if (registeredExecutionPlatforms == null) { + throw new ValueMissingException(); + } + + ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys = + new ImmutableList.Builder<ConfiguredTargetKey>() + .addAll(registeredExecutionPlatforms.registeredExecutionPlatformKeys()) + .add(defaultPlatformKey) + .build(); + + // Filter out execution platforms that don't satisfy the extra constraints. + ImmutableList<ConfiguredTargetKey> execConstraintKeys = + execConstraintLabels.stream() + .map(label -> ConfiguredTargetKey.of(label, configuration)) + .collect(toImmutableList()); + + return filterAvailablePlatforms( + environment, + debug, + availableExecutionPlatformKeys, + execConstraintKeys, + shouldSanityCheckConfiguration); + } + + /** Returns only the platform keys that match the given constraints. */ + private ImmutableList<ConfiguredTargetKey> filterAvailablePlatforms( + SkyFunction.Environment environment, + boolean debug, + ImmutableList<ConfiguredTargetKey> platformKeys, + ImmutableList<ConfiguredTargetKey> constraintKeys, + boolean shouldSanityCheckConfiguration) + throws InterruptedException, ValueMissingException, InvalidConstraintValueException, + InvalidPlatformException { + + // Short circuit if not needed. + if (constraintKeys.isEmpty()) { + return platformKeys; + } + + // At this point the host and target platforms have been loaded, but not necessarily the chosen + // execution platform (it might be the same as the host platform, and might not). + // + // It's not worth trying to optimize away this call, since in the optimizable case (the exec + // platform is the host platform), Skyframe will return the correct results immediately without + // need of a restart. + Map<ConfiguredTargetKey, PlatformInfo> platformInfoMap = + PlatformLookupUtil.getPlatformInfo( + platformKeys, environment, shouldSanityCheckConfiguration); + if (platformInfoMap == null) { + throw new ValueMissingException(); + } + List<ConstraintValueInfo> constraints = + ConstraintValueLookupUtil.getConstraintValueInfo(constraintKeys, environment); + if (constraints == null) { + throw new ValueMissingException(); + } + + return platformKeys.stream() + .filter(key -> filterPlatform(environment, debug, platformInfoMap.get(key), constraints)) + .collect(toImmutableList()); + } + + /** Returns {@code true} if the given platform has all of the constraints. */ + private boolean filterPlatform( + SkyFunction.Environment environment, + boolean debug, + PlatformInfo platformInfo, + List<ConstraintValueInfo> constraints) { + ImmutableList<ConstraintValueInfo> missingConstraints = + platformInfo.constraints().findMissing(constraints); + if (debug) { + for (ConstraintValueInfo constraint : missingConstraints) { + // The value for this setting is not present in the platform, or doesn't match the expected + // value. + environment + .getListener() + .handle( + Event.info( + String.format( + "ToolchainResolver: Removed execution platform %s from" + + " available execution platforms, it is missing constraint %s", + platformInfo.label(), constraint.label()))); + } + } + + return missingConstraints.isEmpty(); + } + + private void determineToolchainImplementations( + SkyFunction.Environment environment, + boolean debug, + BuildConfigurationValue.Key configurationKey, + ImmutableSet<Label> requiredToolchainTypeLabels, + UnloadedToolchainContext.Builder unloadedToolchainContext, + PlatformKeys platformKeys, + boolean shouldSanityCheckConfiguration) + throws InterruptedException, ValueMissingException, InvalidPlatformException, + NoMatchingPlatformException, UnresolvedToolchainsException, + InvalidToolchainLabelException { + + // Find the toolchains for the required toolchain types. + List<SingleToolchainResolutionValue.Key> registeredToolchainKeys = new ArrayList<>(); + for (Label toolchainTypeLabel : requiredToolchainTypeLabels) { + registeredToolchainKeys.add( + SingleToolchainResolutionValue.key( + configurationKey, + toolchainTypeLabel, + platformKeys.targetPlatformKey(), + platformKeys.executionPlatformKeys())); + } + + Map<SkyKey, ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException>> + results = + environment.getValuesOrThrow( + registeredToolchainKeys, + NoToolchainFoundException.class, + InvalidToolchainLabelException.class); + boolean valuesMissing = false; + + // Determine the potential set of toolchains. + Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains = + HashBasedTable.create(); + ImmutableSet.Builder<ToolchainTypeInfo> requiredToolchainTypesBuilder = ImmutableSet.builder(); + List<Label> missingToolchains = new ArrayList<>(); + for (Map.Entry< + SkyKey, ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException>> + entry : results.entrySet()) { + try { + ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException> + valueOrException = entry.getValue(); + SingleToolchainResolutionValue singleToolchainResolutionValue = + (SingleToolchainResolutionValue) valueOrException.get(); + if (singleToolchainResolutionValue == null) { + valuesMissing = true; + continue; + } + + ToolchainTypeInfo requiredToolchainType = singleToolchainResolutionValue.toolchainType(); + requiredToolchainTypesBuilder.add(requiredToolchainType); + resolvedToolchains.putAll( + findPlatformsAndLabels(requiredToolchainType, singleToolchainResolutionValue)); + } catch (NoToolchainFoundException e) { + // Save the missing type and continue looping to check for more. + missingToolchains.add(e.missingToolchainTypeLabel()); + } + } + + if (!missingToolchains.isEmpty()) { + throw new UnresolvedToolchainsException(missingToolchains); + } + + if (valuesMissing) { + throw new ValueMissingException(); + } + + ImmutableSet<ToolchainTypeInfo> requiredToolchainTypes = requiredToolchainTypesBuilder.build(); + + // Find and return the first execution platform which has all required toolchains. + Optional<ConfiguredTargetKey> selectedExecutionPlatformKey; + if (!requiredToolchainTypeLabels.isEmpty()) { + selectedExecutionPlatformKey = + findExecutionPlatformForToolchains( + environment, + debug, + requiredToolchainTypes, + platformKeys.executionPlatformKeys(), + resolvedToolchains); + } else if (platformKeys.executionPlatformKeys().contains(platformKeys.hostPlatformKey())) { + // Fall back to the legacy behavior: use the host platform if it's available, otherwise the + // first execution platform. + selectedExecutionPlatformKey = Optional.of(platformKeys.hostPlatformKey()); + } else if (!platformKeys.executionPlatformKeys().isEmpty()) { + // Just use the first execution platform. + selectedExecutionPlatformKey = Optional.of(platformKeys.executionPlatformKeys().get(0)); + } else { + selectedExecutionPlatformKey = Optional.empty(); + } + + if (!selectedExecutionPlatformKey.isPresent()) { + throw new NoMatchingPlatformException( + requiredToolchainTypeLabels, + platformKeys.executionPlatformKeys(), + platformKeys.targetPlatformKey()); + } + + Map<ConfiguredTargetKey, PlatformInfo> platforms = + PlatformLookupUtil.getPlatformInfo( + ImmutableList.of(selectedExecutionPlatformKey.get(), platformKeys.targetPlatformKey()), + environment, + shouldSanityCheckConfiguration); + if (platforms == null) { + throw new ValueMissingException(); + } + + unloadedToolchainContext.setRequiredToolchainTypes(requiredToolchainTypes); + unloadedToolchainContext.setExecutionPlatform( + platforms.get(selectedExecutionPlatformKey.get())); + unloadedToolchainContext.setTargetPlatform(platforms.get(platformKeys.targetPlatformKey())); + + Map<ToolchainTypeInfo, Label> toolchains = + resolvedToolchains.row(selectedExecutionPlatformKey.get()); + unloadedToolchainContext.setToolchainTypeToResolved(ImmutableBiMap.copyOf(toolchains)); + } + + /** + * Adds all of toolchain labels from{@code toolchainResolutionValue} to {@code + * resolvedToolchains}. + */ + private static Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> findPlatformsAndLabels( + ToolchainTypeInfo requiredToolchainType, + SingleToolchainResolutionValue singleToolchainResolutionValue) { + + Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains = + HashBasedTable.create(); + for (Map.Entry<ConfiguredTargetKey, Label> entry : + singleToolchainResolutionValue.availableToolchainLabels().entrySet()) { + resolvedToolchains.put(entry.getKey(), requiredToolchainType, entry.getValue()); + } + return resolvedToolchains; + } + + /** + * Finds the first platform from {@code availableExecutionPlatformKeys} that is present in {@code + * resolvedToolchains} and has all required toolchain types. + */ + private static Optional<ConfiguredTargetKey> findExecutionPlatformForToolchains( + SkyFunction.Environment environment, + boolean debug, + ImmutableSet<ToolchainTypeInfo> requiredToolchainTypes, + ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys, + Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains) { + for (ConfiguredTargetKey executionPlatformKey : availableExecutionPlatformKeys) { + if (!resolvedToolchains.containsRow(executionPlatformKey)) { + continue; + } + + Map<ToolchainTypeInfo, Label> toolchains = resolvedToolchains.row(executionPlatformKey); + if (!toolchains.keySet().containsAll(requiredToolchainTypes)) { + // Not all toolchains are present, ignore this execution platform. + continue; + } + + if (debug) { + String selectedToolchains = + toolchains.entrySet().stream() + .map( + e -> + String.format( + "type %s -> toolchain %s", e.getKey().typeLabel(), e.getValue())) + .collect(joining(", ")); + environment + .getListener() + .handle( + Event.info( + String.format( + "ToolchainResolver: Selected execution platform %s, %s", + executionPlatformKey.getLabel(), selectedToolchains))); + } + return Optional.of(executionPlatformKey); + } + + return Optional.empty(); + } + + @Nullable + @Override + public String extractTag(SkyKey skyKey) { + return null; + } + + private static final class ValueMissingException extends Exception { + private ValueMissingException() { + super(); + } + } + + /** Exception used when no execution platform can be found. */ + static final class NoMatchingPlatformException extends ToolchainException { + NoMatchingPlatformException( + Set<Label> requiredToolchainTypeLabels, + ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys, + ConfiguredTargetKey targetPlatformKey) { + super( + formatError( + requiredToolchainTypeLabels, availableExecutionPlatformKeys, targetPlatformKey)); + } + + private static String formatError( + Set<Label> requiredToolchainTypeLabels, + ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys, + ConfiguredTargetKey targetPlatformKey) { + if (requiredToolchainTypeLabels.isEmpty()) { + return String.format( + "Unable to find an execution platform for target platform %s" + + " from available execution platforms [%s]", + targetPlatformKey.getLabel(), + availableExecutionPlatformKeys.stream() + .map(key -> key.getLabel().toString()) + .collect(Collectors.joining(", "))); + } + return String.format( + "Unable to find an execution platform for toolchains [%s] and target platform %s" + + " from available execution platforms [%s]", + requiredToolchainTypeLabels.stream().map(Label::toString).collect(joining(", ")), + targetPlatformKey.getLabel(), + availableExecutionPlatformKeys.stream() + .map(key -> key.getLabel().toString()) + .collect(Collectors.joining(", "))); + } + } + + /** Exception used when a toolchain type is required but no matching toolchain is found. */ + static final class UnresolvedToolchainsException extends ToolchainException { + UnresolvedToolchainsException(List<Label> missingToolchainTypes) { + super( + String.format( + "no matching toolchains found for types %s", + missingToolchainTypes.stream().map(Label::toString).collect(joining(", ")))); + } + } + + /** Used to indicate errors during the computation of an {@link UnloadedToolchainContext}. */ + private static final class ToolchainResolutionFunctionException extends SkyFunctionException { + public ToolchainResolutionFunctionException(ToolchainException e) { + super(e, Transience.PERSISTENT); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java new file mode 100644 index 0000000..cad4345 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java
@@ -0,0 +1,109 @@ +// Copyright 2019 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.auto.value.AutoValue; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.analysis.ToolchainContext; +import com.google.devtools.build.lib.analysis.platform.PlatformInfo; +import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.skyframe.SkyFunctionName; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; +import java.util.Set; + +/** + * Represents the state of toolchain resolution once the specific required toolchains have been + * determined, but before the toolchain dependencies have been resolved. + */ +@AutoValue +public abstract class UnloadedToolchainContext implements ToolchainContext, SkyValue { + + /** Returns a new {@link Key.Builder}. */ + public static Key.Builder key() { + return new AutoValue_UnloadedToolchainContext_Key.Builder() + .requiredToolchainTypeLabels(ImmutableSet.of()) + .execConstraintLabels(ImmutableSet.of()) + .shouldSanityCheckConfiguration(false); + } + + /** {@link SkyKey} implementation used for {@link ToolchainResolutionFunction}. */ + @AutoValue + public abstract static class Key implements SkyKey { + + @Override + public SkyFunctionName functionName() { + return SkyFunctions.TOOLCHAIN_RESOLUTION; + } + + abstract BuildConfigurationValue.Key configurationKey(); + + abstract ImmutableSet<Label> requiredToolchainTypeLabels(); + + abstract ImmutableSet<Label> execConstraintLabels(); + + abstract boolean shouldSanityCheckConfiguration(); + + /** Builder for {@link UnloadedToolchainContext.Key}. */ + @AutoValue.Builder + public interface Builder { + Builder configurationKey(BuildConfigurationValue.Key key); + + Builder requiredToolchainTypeLabels(ImmutableSet<Label> requiredToolchainTypeLabels); + + Builder requiredToolchainTypeLabels(Label... requiredToolchainTypeLabels); + + Builder execConstraintLabels(ImmutableSet<Label> execConstraintLabels); + + Builder execConstraintLabels(Label... execConstraintLabels); + + Builder shouldSanityCheckConfiguration(boolean shouldSanityCheckConfiguration); + + Key build(); + } + } + + public static Builder builder() { + return new AutoValue_UnloadedToolchainContext.Builder(); + } + + /** Builder class to help create the {@link UnloadedToolchainContext}. */ + @AutoValue.Builder + public interface Builder { + /** Sets the selected execution platform that these toolchains use. */ + Builder setExecutionPlatform(PlatformInfo executionPlatform); + + /** Sets the target platform that these toolchains generate output for. */ + Builder setTargetPlatform(PlatformInfo targetPlatform); + + /** Sets the toolchain types that were requested. */ + Builder setRequiredToolchainTypes(Set<ToolchainTypeInfo> requiredToolchainTypes); + + Builder setToolchainTypeToResolved( + ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved); + + UnloadedToolchainContext build(); + } + + /** The map of toolchain type to resolved toolchain to be used. */ + // TODO(https://github.com/bazelbuild/bazel/issues/7935): Make this package-protected again. + public abstract ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved(); + + @Override + public ImmutableSet<Label> resolvedToolchainLabels() { + return toolchainTypeToResolved().values(); + } +}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java index ba4351b..863aa88 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.rules.platform.ToolchainTestCase; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.skyframe.ToolchainException; +import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java index 6cd63c9..f50a399 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java
@@ -30,6 +30,7 @@ import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException; import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException; import com.google.devtools.build.lib.skyframe.ToolchainException; +import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.SkyFunction;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java index dcf0a3f..849b3502 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java
@@ -43,7 +43,6 @@ import com.google.devtools.build.lib.analysis.TargetAndConfiguration; import com.google.devtools.build.lib.analysis.ToolchainResolver; import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; -import com.google.devtools.build.lib.analysis.UnloadedToolchainContext; import com.google.devtools.build.lib.analysis.ViewCreationFailedException; import com.google.devtools.build.lib.analysis.WorkspaceStatusAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; @@ -83,6 +82,7 @@ import com.google.devtools.build.lib.skyframe.SkyframeExecutor; import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue; import com.google.devtools.build.lib.skyframe.ToolchainException; +import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.util.OrderedSetMultimap; import com.google.devtools.build.skyframe.SkyKey;
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java new file mode 100644 index 0000000..84d8777 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
@@ -0,0 +1,423 @@ +// Copyright 2019 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 com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.rules.platform.ToolchainTestCase; +import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException; +import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException; +import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.NoMatchingPlatformException; +import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.UnresolvedToolchainsException; +import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; +import com.google.devtools.build.skyframe.EvaluationResult; +import com.google.devtools.build.skyframe.SkyKey; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link UnloadedToolchainContext} and {@link ToolchainResolutionFunction}. */ +@RunWith(JUnit4.class) +public class ToolchainResolutionFunctionTest extends ToolchainTestCase { + + private EvaluationResult<UnloadedToolchainContext> invokeToolchainResolution(SkyKey key) + throws InterruptedException { + try { + getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(true); + return SkyframeExecutorTestUtils.evaluate( + getSkyframeExecutor(), key, /*keepGoing=*/ false, reporter); + } finally { + getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(false); + } + } + + @Test + public void resolve() throws Exception { + // This should select platform mac, toolchain extra_toolchain_mac, because platform + // mac is listed first. + addToolchain( + "extra", + "extra_toolchain_linux", + ImmutableList.of("//constraints:linux"), + ImmutableList.of("//constraints:linux"), + "baz"); + addToolchain( + "extra", + "extra_toolchain_mac", + ImmutableList.of("//constraints:mac"), + ImmutableList.of("//constraints:linux"), + "baz"); + rewriteWorkspace( + "register_toolchains('//extra:extra_toolchain_linux', '//extra:extra_toolchain_mac')", + "register_execution_platforms('//platforms:mac', '//platforms:linux')"); + + useConfiguration("--platforms=//platforms:linux"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasNoError(); + UnloadedToolchainContext unloadedToolchainContext = result.get(key); + assertThat(unloadedToolchainContext).isNotNull(); + + assertThat(unloadedToolchainContext.requiredToolchainTypes()) + .containsExactly(testToolchainType); + assertThat(unloadedToolchainContext.resolvedToolchainLabels()) + .containsExactly(Label.parseAbsoluteUnchecked("//extra:extra_toolchain_mac_impl")); + + assertThat(unloadedToolchainContext.executionPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.executionPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:mac")); + + assertThat(unloadedToolchainContext.targetPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.targetPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux")); + } + + @Test + public void resolve_noToolchainType() throws Exception { + scratch.file("host/BUILD", "platform(name = 'host')"); + rewriteWorkspace("register_execution_platforms('//platforms:mac', '//platforms:linux')"); + + useConfiguration("--host_platform=//host:host", "--platforms=//platforms:linux"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key().configurationKey(targetConfigKey).build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasNoError(); + UnloadedToolchainContext unloadedToolchainContext = result.get(key); + assertThat(unloadedToolchainContext).isNotNull(); + + assertThat(unloadedToolchainContext.requiredToolchainTypes()).isEmpty(); + + // With no toolchains requested, should fall back to the host platform. + assertThat(unloadedToolchainContext.executionPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.executionPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//host:host")); + + assertThat(unloadedToolchainContext.targetPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.targetPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux")); + } + + @Test + public void resolve_noToolchainType_hostNotAvailable() throws Exception { + scratch.file("host/BUILD", "platform(name = 'host')"); + scratch.file( + "sample/BUILD", + "constraint_setting(name='demo')", + "constraint_value(name = 'demo_a', constraint_setting=':demo')", + "constraint_value(name = 'demo_b', constraint_setting=':demo')", + "platform(name = 'sample_a',", + " constraint_values = [':demo_a'],", + ")", + "platform(name = 'sample_b',", + " constraint_values = [':demo_b'],", + ")"); + rewriteWorkspace( + "register_execution_platforms('//platforms:mac', '//platforms:linux',", + " '//sample:sample_a', '//sample:sample_b')"); + + useConfiguration("--host_platform=//host:host", "--platforms=//platforms:linux"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .execConstraintLabels(Label.parseAbsoluteUnchecked("//sample:demo_b")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasNoError(); + UnloadedToolchainContext unloadedToolchainContext = result.get(key); + assertThat(unloadedToolchainContext).isNotNull(); + + assertThat(unloadedToolchainContext.requiredToolchainTypes()).isEmpty(); + + assertThat(unloadedToolchainContext.executionPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.executionPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//sample:sample_b")); + + assertThat(unloadedToolchainContext.targetPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.targetPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux")); + } + + @Test + public void resolve_unavailableToolchainType_single() throws Exception { + useConfiguration("--host_platform=//platforms:linux", "--platforms=//platforms:mac"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels( + testToolchainTypeLabel, Label.parseAbsoluteUnchecked("//fake/toolchain:type_1")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(UnresolvedToolchainsException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("no matching toolchains found for types //fake/toolchain:type_1"); + } + + @Test + public void resolve_unavailableToolchainType_multiple() throws Exception { + useConfiguration("--host_platform=//platforms:linux", "--platforms=//platforms:mac"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels( + testToolchainTypeLabel, + Label.parseAbsoluteUnchecked("//fake/toolchain:type_1"), + Label.parseAbsoluteUnchecked("//fake/toolchain:type_2")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(UnresolvedToolchainsException.class); + // Only one of the missing types will be reported, so do not check the specific error message. + } + + @Test + public void resolve_invalidTargetPlatform_badTarget() throws Exception { + scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')"); + useConfiguration("--platforms=//invalid:not_a_platform"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(InvalidPlatformException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains( + "//invalid:not_a_platform was referenced as a platform, " + + "but does not provide PlatformInfo"); + } + + @Test + public void resolve_invalidTargetPlatform_badPackage() throws Exception { + scratch.resolve("invalid").delete(); + useConfiguration("--platforms=//invalid:not_a_platform"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(InvalidPlatformException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("BUILD file not found"); + } + + @Test + public void resolve_invalidHostPlatform() throws Exception { + scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')"); + useConfiguration("--host_platform=//invalid:not_a_platform"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(InvalidPlatformException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("//invalid:not_a_platform"); + } + + @Test + public void resolve_invalidExecutionPlatform() throws Exception { + scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')"); + useConfiguration("--extra_execution_platforms=//invalid:not_a_platform"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(InvalidPlatformException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("//invalid:not_a_platform"); + } + + @Test + public void resolve_execConstraints() throws Exception { + // This should select platform linux, toolchain extra_toolchain_linux, due to extra constraints, + // even though platform mac is registered first. + addToolchain( + /* packageName= */ "extra", + /* toolchainName= */ "extra_toolchain_linux", + /* execConstraints= */ ImmutableList.of("//constraints:linux"), + /* targetConstraints= */ ImmutableList.of("//constraints:linux"), + /* data= */ "baz"); + addToolchain( + /* packageName= */ "extra", + /* toolchainName= */ "extra_toolchain_mac", + /* execConstraints= */ ImmutableList.of("//constraints:mac"), + /* targetConstraints= */ ImmutableList.of("//constraints:linux"), + /* data= */ "baz"); + rewriteWorkspace( + "register_toolchains('//extra:extra_toolchain_linux', '//extra:extra_toolchain_mac')", + "register_execution_platforms('//platforms:mac', '//platforms:linux')"); + + useConfiguration("--platforms=//platforms:linux"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .execConstraintLabels(Label.parseAbsoluteUnchecked("//constraints:linux")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasNoError(); + UnloadedToolchainContext unloadedToolchainContext = result.get(key); + assertThat(unloadedToolchainContext).isNotNull(); + + assertThat(unloadedToolchainContext.requiredToolchainTypes()) + .containsExactly(testToolchainType); + assertThat(unloadedToolchainContext.resolvedToolchainLabels()) + .containsExactly(Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl")); + + assertThat(unloadedToolchainContext.executionPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.executionPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux")); + + assertThat(unloadedToolchainContext.targetPlatform()).isNotNull(); + assertThat(unloadedToolchainContext.targetPlatform().label()) + .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux")); + } + + @Test + public void resolve_execConstraints_invalid() throws Exception { + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels(testToolchainTypeLabel) + .execConstraintLabels(Label.parseAbsoluteUnchecked("//platforms:linux")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(InvalidConstraintValueException.class); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("//platforms:linux"); + } + + @Test + public void resolve_noMatchingPlatform() throws Exception { + // Write toolchain A, and a toolchain implementing it. + scratch.appendFile( + "a/BUILD", + "toolchain_type(name = 'toolchain_type_A')", + "toolchain(", + " name = 'toolchain',", + " toolchain_type = ':toolchain_type_A',", + " exec_compatible_with = ['//constraints:mac'],", + " target_compatible_with = [],", + " toolchain = ':toolchain_impl')", + "filegroup(name='toolchain_impl')"); + // Write toolchain B, and a toolchain implementing it. + scratch.appendFile( + "b/BUILD", + "load('//toolchain:toolchain_def.bzl', 'test_toolchain')", + "toolchain_type(name = 'toolchain_type_B')", + "toolchain(", + " name = 'toolchain',", + " toolchain_type = ':toolchain_type_B',", + " exec_compatible_with = ['//constraints:linux'],", + " target_compatible_with = [],", + " toolchain = ':toolchain_impl')", + "filegroup(name='toolchain_impl')"); + + rewriteWorkspace( + "register_toolchains('//a:toolchain', '//b:toolchain')", + "register_execution_platforms('//platforms:mac', '//platforms:linux')"); + + useConfiguration("--platforms=//platforms:linux"); + UnloadedToolchainContext.Key key = + UnloadedToolchainContext.key() + .configurationKey(targetConfigKey) + .requiredToolchainTypeLabels( + Label.parseAbsoluteUnchecked("//a:toolchain_type_A"), + Label.parseAbsoluteUnchecked("//b:toolchain_type_B")) + .build(); + + EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key); + assertThatEvaluationResult(result).hasError(); + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .isInstanceOf(NoMatchingPlatformException.class); + } +}