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);
+ }
+}