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 849cb8e3fa1ffa..18327b8318ab75 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 @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement; import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils; import com.google.devtools.build.lib.analysis.platform.ToolchainInfo; import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo; @@ -48,25 +49,26 @@ public static ResolvedToolchainContext load( Iterable toolchainTargets) throws ToolchainException { - ImmutableMap.Builder toolchains = + ImmutableMap.Builder toolchainsBuilder = new ImmutableMap.Builder<>(); ImmutableList.Builder templateVariableProviders = new ImmutableList.Builder<>(); + for (ConfiguredTargetAndData target : toolchainTargets) { // Aliases are in toolchainTypeToResolved by the original alias label, not via the final // target's label. Label discoveredLabel = target.getConfiguredTarget().getOriginalLabel(); + ToolchainInfo toolchainInfo = PlatformProviderUtils.toolchain(target.getConfiguredTarget()); for (ToolchainTypeInfo toolchainType : unloadedToolchainContext.toolchainTypeToResolved().inverse().get(discoveredLabel)) { - ToolchainInfo toolchainInfo = PlatformProviderUtils.toolchain(target.getConfiguredTarget()); // If the toolchainType hadn't been resolved to an actual target, resolution would have // failed with an error much earlier. However, the target might still not be an actual // toolchain. if (toolchainType != null) { if (toolchainInfo != null) { - toolchains.put(toolchainType, toolchainInfo); + toolchainsBuilder.put(toolchainType, toolchainInfo); } else { throw new TargetNotToolchainException(toolchainType, discoveredLabel); } @@ -81,6 +83,20 @@ public static ResolvedToolchainContext load( } } + // Verify that all mandatory toolchain type requirements are present. + ImmutableMap toolchains = toolchainsBuilder.buildOrThrow(); + for (ToolchainTypeRequirement toolchainTypeRequirement : + unloadedToolchainContext.toolchainTypes()) { + if (toolchainTypeRequirement.mandatory()) { + Label toolchainTypeLabel = toolchainTypeRequirement.toolchainType(); + ToolchainTypeInfo toolchainTypeInfo = + unloadedToolchainContext.requestedLabelToToolchainType().get(toolchainTypeLabel); + if (!toolchains.containsKey(toolchainTypeInfo)) { + throw new MissingToolchainTypeRequirementException(toolchainTypeRequirement); + } + } + } + return new AutoValue_ResolvedToolchainContext( // super: unloadedToolchainContext.key(), @@ -91,7 +107,7 @@ public static ResolvedToolchainContext load( // this: targetDescription, unloadedToolchainContext.requestedLabelToToolchainType(), - toolchains.buildOrThrow(), + toolchains, templateVariableProviders.build()); } @@ -125,7 +141,7 @@ public ToolchainInfo forToolchainType(ToolchainTypeInfo toolchainType) { } /** - * Exception used when a toolchain type is required but the resolved target does not have + * Exception used when a toolchain type is requested but the resolved target does not have * ToolchainInfo. */ static final class TargetNotToolchainException extends ToolchainException { @@ -143,4 +159,20 @@ protected Code getDetailedCode() { return Code.MISSING_PROVIDER; } } + + /** Exception used when a toolchain type is required but noimplementation was found. */ + private static class MissingToolchainTypeRequirementException extends ToolchainException { + + MissingToolchainTypeRequirementException(ToolchainTypeRequirement toolchainTypeRequirement) { + super( + String.format( + "toolchain type %s was mandatory but is not present", + toolchainTypeRequirement.toolchainType())); + } + + @Override + protected Code getDetailedCode() { + return Code.NO_MATCHING_TOOLCHAIN; + } + } } 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 3412155c0dc5be..6569cfe3e321ef 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 @@ -30,6 +30,7 @@ import com.google.devtools.build.lib.skyframe.ToolchainException; import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext; import com.google.devtools.build.lib.skyframe.UnloadedToolchainContextImpl; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -38,7 +39,17 @@ @RunWith(JUnit4.class) public class ResolvedToolchainContextTest extends ToolchainTestCase { - // TODO(https://github.com/bazelbuild/bazel/issues/14726): Add tests for optional toolchain types. + Label optionalToolchainTypeLabel; + ToolchainTypeRequirement optionalToolchainType; + ToolchainTypeInfo optionalToolchainTypeInfo; + + @Before + public void createOptionalToolchainType() { + optionalToolchainTypeLabel = Label.parseAbsoluteUnchecked("//toolchain:optional_toolchain"); + optionalToolchainType = + ToolchainTypeRequirement.builder(optionalToolchainTypeLabel).mandatory(false).build(); + optionalToolchainTypeInfo = ToolchainTypeInfo.create(optionalToolchainTypeLabel); + } @Test public void load() throws Exception { @@ -90,6 +101,165 @@ public void load() throws Exception { .isEqualTo("baz"); } + @Test + public void load_mandatory_missing() throws Exception { + ToolchainContextKey toolchainContextKey = + ToolchainContextKey.key() + .configurationKey(targetConfigKey) + .toolchainTypes(testToolchainType) + .build(); + + // Create a static UnloadedToolchainContext. + UnloadedToolchainContext unloadedToolchainContext = + UnloadedToolchainContextImpl.builder(toolchainContextKey) + .setExecutionPlatform(linuxPlatform) + .setTargetPlatform(linuxPlatform) + .setToolchainTypes(ImmutableSet.of(testToolchainType)) + .setRequestedLabelToToolchainType( + ImmutableMap.of(testToolchainTypeLabel, testToolchainTypeInfo)) + .build(); + + // Resolve toolchains. + assertThrows( + ToolchainException.class, + () -> ResolvedToolchainContext.load(unloadedToolchainContext, "test", ImmutableList.of())); + } + + @Test + public void load_optional_present() throws Exception { + addToolchain( + "extra", + "extra_toolchain_linux", + ImmutableList.of("//constraints:linux"), + ImmutableList.of("//constraints:linux"), + "baz"); + + ToolchainContextKey toolchainContextKey = + ToolchainContextKey.key() + .configurationKey(targetConfigKey) + .toolchainTypes(optionalToolchainType) + .build(); + + // Create a static UnloadedToolchainContext. + UnloadedToolchainContext unloadedToolchainContext = + UnloadedToolchainContextImpl.builder(toolchainContextKey) + .setExecutionPlatform(linuxPlatform) + .setTargetPlatform(linuxPlatform) + .setToolchainTypes(ImmutableSet.of(optionalToolchainType)) + .setRequestedLabelToToolchainType( + ImmutableMap.of(optionalToolchainTypeLabel, optionalToolchainTypeInfo)) + .setToolchainTypeToResolved( + ImmutableSetMultimap.builder() + .put( + optionalToolchainTypeInfo, + Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl")) + .build()) + .build(); + + // Create the prerequisites. + ConfiguredTargetAndData toolchain = + getConfiguredTargetAndData( + Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl"), targetConfig); + + // Resolve toolchains. + ResolvedToolchainContext toolchainContext = + ResolvedToolchainContext.load( + unloadedToolchainContext, "test", ImmutableList.of(toolchain)); + assertThat(toolchainContext).isNotNull(); + assertThat(toolchainContext).hasToolchainType(optionalToolchainTypeLabel); + assertThat(toolchainContext) + .forToolchainType(optionalToolchainTypeLabel) + .getValue("data") + .isEqualTo("baz"); + } + + @Test + public void load_optional_missing() throws Exception { + ToolchainContextKey toolchainContextKey = + ToolchainContextKey.key() + .configurationKey(targetConfigKey) + .toolchainTypes(optionalToolchainType) + .build(); + + // Create a static UnloadedToolchainContext. + UnloadedToolchainContext unloadedToolchainContext = + UnloadedToolchainContextImpl.builder(toolchainContextKey) + .setExecutionPlatform(linuxPlatform) + .setTargetPlatform(linuxPlatform) + .setToolchainTypes(ImmutableSet.of(optionalToolchainType)) + .setRequestedLabelToToolchainType( + ImmutableMap.of(optionalToolchainTypeLabel, optionalToolchainTypeInfo)) + .build(); + + // Resolve toolchains. + ResolvedToolchainContext toolchainContext = + ResolvedToolchainContext.load(unloadedToolchainContext, "test", ImmutableList.of()); + assertThat(toolchainContext).isNotNull(); + + // Missing optional toolchain type requirement is present. + assertThat(toolchainContext).hasToolchainType(optionalToolchainTypeLabel); + // Missing optional toolchain implementation is null. + assertThat(toolchainContext).forToolchainType(optionalToolchainTypeLabel).isNull(); + } + + @Test + public void load_mixed() throws Exception { + addToolchain( + "extra", + "extra_toolchain_linux", + ImmutableList.of("//constraints:linux"), + ImmutableList.of("//constraints:linux"), + "baz"); + + ToolchainContextKey toolchainContextKey = + ToolchainContextKey.key() + .configurationKey(targetConfigKey) + .toolchainTypes(testToolchainType, optionalToolchainType) + .build(); + + // Create a static UnloadedToolchainContext. + UnloadedToolchainContext unloadedToolchainContext = + UnloadedToolchainContextImpl.builder(toolchainContextKey) + .setExecutionPlatform(linuxPlatform) + .setTargetPlatform(linuxPlatform) + .setToolchainTypes(ImmutableSet.of(testToolchainType, optionalToolchainType)) + .setRequestedLabelToToolchainType( + ImmutableMap.builder() + .put(testToolchainTypeLabel, testToolchainTypeInfo) + .put(optionalToolchainTypeLabel, optionalToolchainTypeInfo) + .build()) + .setToolchainTypeToResolved( + ImmutableSetMultimap.builder() + .put( + testToolchainTypeInfo, + Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl")) + .build()) + .build(); + + // Create the prerequisites. + ConfiguredTargetAndData testToolchain = + getConfiguredTargetAndData( + Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl"), targetConfig); + + // Resolve toolchains. + ResolvedToolchainContext toolchainContext = + ResolvedToolchainContext.load( + unloadedToolchainContext, "test", ImmutableList.of(testToolchain)); + assertThat(toolchainContext).isNotNull(); + + // Test toolchain is present. + assertThat(toolchainContext).hasToolchainType(testToolchainTypeLabel); + assertThat(toolchainContext) + .forToolchainType(testToolchainTypeLabel) + .getValue("data") + .isEqualTo("baz"); + + // Missing optional toolchain type requirement is present. + assertThat(toolchainContext).hasToolchainType(optionalToolchainTypeLabel); + // Missing optional toolchain implementation is null. + assertThat(toolchainContext).forToolchainType(optionalToolchainTypeLabel).isNull(); + } + @Test public void load_aliasedToolchain() throws Exception { scratch.file(