| // Copyright 2015 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 static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.analysis.testing.ExecGroupCollectionSubject.assertThat; | |
| import static com.google.devtools.build.lib.packages.ExecGroup.DEFAULT_EXEC_GROUP_NAME; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.packages.Provider; | |
| import com.google.devtools.build.lib.packages.StarlarkProvider; | |
| import com.google.devtools.build.lib.packages.StructImpl; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Test for exec groups. Functionality related to rule context tested in {@link | |
| * com.google.devtools.build.lib.starlark.StarlarkRuleContextTest}. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class StarlarkExecGroupTest extends BuildViewTestCase { | |
| /** | |
| * Sets up two toolchains types, each with a single toolchain implementation and a single | |
| * exec_compatible_with platform. | |
| * | |
| * <p>toolchain_type_1 -> foo_toolchain -> exec_compatible_with platform_1 toolchain_type_2 -> | |
| * bar_toolchain -> exec_compatible_with platform_2 | |
| */ | |
| private void createToolchainsAndPlatforms() throws Exception { | |
| scratch.file( | |
| "rule/test_toolchain.bzl", | |
| "def _impl(ctx):", | |
| " return [platform_common.ToolchainInfo()]", | |
| "test_toolchain = rule(", | |
| " implementation = _impl,", | |
| ")"); | |
| scratch.file( | |
| "rule/BUILD", | |
| "exports_files(['test_toolchain/bzl'])", | |
| "toolchain_type(name = 'toolchain_type_1')", | |
| "toolchain_type(name = 'toolchain_type_2')"); | |
| scratch.file( | |
| "toolchain/BUILD", | |
| "load('//rule:test_toolchain.bzl', 'test_toolchain')", | |
| "test_toolchain(", | |
| " name = 'foo',", | |
| ")", | |
| "toolchain(", | |
| " name = 'foo_toolchain',", | |
| " toolchain_type = '//rule:toolchain_type_1',", | |
| " target_compatible_with = ['//platform:constraint_1'],", | |
| " exec_compatible_with = ['//platform:constraint_1'],", | |
| " toolchain = ':foo',", | |
| ")", | |
| "test_toolchain(", | |
| " name = 'bar',", | |
| ")", | |
| "toolchain(", | |
| " name = 'bar_toolchain',", | |
| " toolchain_type = '//rule:toolchain_type_2',", | |
| " target_compatible_with = ['//platform:constraint_1'],", | |
| " exec_compatible_with = ['//platform:constraint_2'],", | |
| " toolchain = ':bar',", | |
| ")"); | |
| scratch.file( | |
| "platform/BUILD", | |
| "constraint_setting(name = 'setting')", | |
| "constraint_value(", | |
| " name = 'constraint_1',", | |
| " constraint_setting = ':setting',", | |
| ")", | |
| "constraint_value(", | |
| " name = 'constraint_2',", | |
| " constraint_setting = ':setting',", | |
| ")", | |
| "platform(", | |
| " name = 'platform_1',", | |
| " constraint_values = [':constraint_1'],", | |
| ")", | |
| "platform(", | |
| " name = 'platform_2',", | |
| " constraint_values = [':constraint_2'],", | |
| " exec_properties = {", | |
| " 'watermelon.ripeness': 'unripe',", | |
| " 'watermelon.color': 'red',", | |
| " },", | |
| ")"); | |
| useConfiguration( | |
| "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain", | |
| "--platforms=//platform:platform_1", | |
| "--extra_execution_platforms=//platform:platform_1,//platform:platform_2"); | |
| } | |
| @Test | |
| public void testExecGroupTransition() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " return [MyInfo(dep = ctx.attr.dep, exec_group_dep = ctx.attr.exec_group_dep)]", | |
| "with_transition = rule(", | |
| " implementation = _impl,", | |
| " attrs = {", | |
| " 'exec_group_dep': attr.label(cfg = config.exec('watermelon')),", | |
| " 'dep': attr.label(cfg = 'exec'),", | |
| " },", | |
| " exec_groups = {", | |
| " 'watermelon': exec_group(toolchains = ['//rule:toolchain_type_2']),", | |
| " },", | |
| " toolchains = ['//rule:toolchain_type_1'],", | |
| ")", | |
| "def _impl2(ctx):", | |
| " return []", | |
| "simple_rule = rule(implementation = _impl2)"); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_transition', 'simple_rule')", | |
| "with_transition(name = 'parent', dep = ':child', exec_group_dep = ':other-child')", | |
| "simple_rule(name = 'child')", | |
| "simple_rule(name = 'other-child')"); | |
| ConfiguredTarget target = getConfiguredTarget("//test:parent"); | |
| Provider.Key key = new StarlarkProvider.Key(Label.parseCanonical("//test:defs.bzl"), "MyInfo"); | |
| BuildConfigurationValue dep = | |
| getConfiguration((ConfiguredTarget) ((StructImpl) target.get(key)).getValue("dep")); | |
| BuildConfigurationValue execGroupDep = | |
| getConfiguration( | |
| (ConfiguredTarget) ((StructImpl) target.get(key)).getValue("exec_group_dep")); | |
| assertThat(dep.getFragment(PlatformConfiguration.class).getTargetPlatform()) | |
| .isEqualTo(Label.parseAbsoluteUnchecked("//platform:platform_1")); | |
| assertThat(execGroupDep.getFragment(PlatformConfiguration.class).getTargetPlatform()) | |
| .isEqualTo(Label.parseAbsoluteUnchecked("//platform:platform_2")); | |
| } | |
| @Test | |
| public void testInvalidExecGroupTransition() throws Exception { | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " return []", | |
| "with_transition = rule(", | |
| " implementation = _impl,", | |
| " attrs = {", | |
| " 'exec_group_dep': attr.label(cfg = config.exec('blueberry')),", | |
| " },", | |
| ")", | |
| "def _impl2(ctx):", | |
| " return []", | |
| "simple_rule = rule(implementation = _impl2)"); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_transition', 'simple_rule')", | |
| "with_transition(name = 'parent', exec_group_dep = ':child')", | |
| "simple_rule(name = 'child')"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//test:parent"); | |
| assertContainsEvent( | |
| "Attr 'exec_group_dep' declares a transition for non-existent exec group 'blueberry'"); | |
| } | |
| @Test | |
| public void testExecGroupActionHasExecGroupPlatform() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| writeRuleWithActionsAndWatermelonExecGroup(); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " output = 'out.txt',", | |
| " watermelon_output = 'watermelon_out.txt'", | |
| ")"); | |
| ConfiguredTarget target = getConfiguredTarget("//test:papaya"); | |
| assertThat( | |
| getGeneratingAction(target, "test/watermelon_out.txt") | |
| .getOwner() | |
| .getExecutionPlatform() | |
| .label()) | |
| .isEqualTo(Label.parseAbsoluteUnchecked("//platform:platform_2")); | |
| assertThat( | |
| getGeneratingAction(target, "test/out.txt").getOwner().getExecutionPlatform().label()) | |
| .isEqualTo(Label.parseAbsoluteUnchecked("//platform:platform_1")); | |
| } | |
| @Test | |
| public void testActionDeclaresInvalidExecGroup() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " watermelon_out_file = ctx.outputs.watermelon_output", | |
| " ctx.actions.run_shell(", | |
| " inputs = [],", | |
| " outputs = [watermelon_out_file],", | |
| " arguments = [watermelon_out_file.path],", | |
| " command = 'echo hello > \"$1\"',", | |
| " exec_group = 'honeydew',", | |
| " )", | |
| "with_actions = rule(", | |
| " implementation = _impl,", | |
| " attrs = {", | |
| " 'watermelon_output': attr.output(),", | |
| " },", | |
| " exec_groups = {", | |
| " 'watermelon': exec_group(toolchains = ['//rule:toolchain_type_2']),", | |
| " },", | |
| ")"); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " watermelon_output = 'watermelon_out.txt'", | |
| ")"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//test:papaya"); | |
| assertContainsEvent("Action declared for non-existent exec group 'honeydew'"); | |
| } | |
| @Test | |
| public void testCannotNameExecGroupDefaultName() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " return []", | |
| "my_rule = rule(", | |
| " implementation = _impl,", | |
| " exec_groups = {", | |
| " '" | |
| + DEFAULT_EXEC_GROUP_NAME | |
| + "': exec_group(toolchains = ['//rule:toolchain_type_2']),", | |
| " },", | |
| ")"); | |
| scratch.file( | |
| "test/BUILD", // | |
| "load('//test:defs.bzl', 'my_rule')", | |
| "my_rule(name = 'papaya')"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//test:papaya"); | |
| assertContainsEvent("Exec group name '" + DEFAULT_EXEC_GROUP_NAME + "' is not a valid name"); | |
| } | |
| private void writeRuleWithActionsAndWatermelonExecGroup() throws Exception { | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " watermelon_out_file = ctx.outputs.watermelon_output", | |
| " ctx.actions.run_shell(", | |
| " inputs = [],", | |
| " outputs = [watermelon_out_file],", | |
| " arguments = [watermelon_out_file.path],", | |
| " command = 'echo hello > \"$1\"',", | |
| " exec_group = 'watermelon',", | |
| " )", | |
| " out_file = ctx.outputs.output", | |
| " ctx.actions.run_shell(", | |
| " inputs = [],", | |
| " outputs = [out_file],", | |
| " arguments = [out_file.path],", | |
| " command = 'echo hello > \"$1\"',", | |
| " )", | |
| "with_actions = rule(", | |
| " implementation = _impl,", | |
| " attrs = {", | |
| " 'watermelon_output': attr.output(),", | |
| " 'output': attr.output(),", | |
| " },", | |
| " exec_groups = {", | |
| " 'watermelon': exec_group(toolchains = ['//rule:toolchain_type_2']),", | |
| " },", | |
| " toolchains = ['//rule:toolchain_type_1'],", | |
| ")"); | |
| } | |
| @Test | |
| public void testSetExecGroupExecProperty() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| writeRuleWithActionsAndWatermelonExecGroup(); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " output = 'out.txt',", | |
| " watermelon_output = 'watermelon_out.txt',", | |
| " exec_properties = {", | |
| " 'color': 'orange',", | |
| " 'ripeness': 'ripe',", | |
| " 'watermelon.color': 'pink',", | |
| " 'watermelon.season': 'summer',", | |
| " },", | |
| ")"); | |
| ConfiguredTarget target = getConfiguredTarget("//test:papaya"); | |
| assertThat( | |
| getGeneratingAction(target, "test/watermelon_out.txt").getOwner().getExecProperties()) | |
| .containsExactly("color", "pink", "season", "summer", "ripeness", "ripe"); | |
| assertThat(getGeneratingAction(target, "test/out.txt").getOwner().getExecProperties()) | |
| .containsExactly("color", "orange", "ripeness", "ripe"); | |
| } | |
| @Test | |
| public void testSetUnknownExecGroup() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| writeRuleWithActionsAndWatermelonExecGroup(); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " output = 'out.txt',", | |
| " watermelon_output = 'watermelon_out.txt',", | |
| " exec_properties = {", | |
| " 'color': 'orange',", | |
| " 'watermelon.color': 'pink',", | |
| " 'blueberry.season': 'summer',", // non-existent exec group | |
| " },", | |
| ")"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//test:papaya"); | |
| assertContainsEvent("errors encountered while analyzing target '//test:papaya'"); | |
| } | |
| @Test | |
| public void testInheritsRuleRequirements() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| scratch.file( | |
| "test/defs.bzl", | |
| "MyInfo = provider()", | |
| "def _impl(ctx):", | |
| " return []", | |
| "my_rule = rule(", | |
| " implementation = _impl,", | |
| " exec_groups = {", | |
| " 'watermelon': exec_group(copy_from_rule = True),", | |
| " },", | |
| " exec_compatible_with = ['//platform:constraint_1'],", | |
| " toolchains = ['//rule:toolchain_type_1'],", | |
| ")"); | |
| scratch.file("test/BUILD", "load('//test:defs.bzl', 'my_rule')", "my_rule(name = 'papaya')"); | |
| ConfiguredTarget ct = getConfiguredTarget("//test:papaya"); | |
| ExecGroupCollection execGroups = getRuleContext(ct).getExecGroups(); | |
| assertThat(execGroups).isNotNull(); | |
| assertThat(execGroups).hasExecGroup("watermelon"); | |
| // TODO(https://github.com/bazelbuild/bazel/issues/14726): Add tests of optional toolchains. | |
| assertThat(execGroups).execGroup("watermelon").hasToolchainType("//rule:toolchain_type_1"); | |
| assertThat(execGroups) | |
| .execGroup("watermelon") | |
| .toolchainType("//rule:toolchain_type_1") | |
| .isMandatory(); | |
| assertThat(execGroups).execGroup("watermelon").hasExecCompatibleWith("//platform:constraint_1"); | |
| } | |
| @Test | |
| public void testInheritsPlatformExecGroupExecProperty() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| writeRuleWithActionsAndWatermelonExecGroup(); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " output = 'out.txt',", | |
| " watermelon_output = 'watermelon_out.txt',", | |
| ")"); | |
| ConfiguredTarget target = getConfiguredTarget("//test:papaya"); | |
| assertThat( | |
| getGeneratingAction(target, "test/watermelon_out.txt").getOwner().getExecProperties()) | |
| .containsExactly("ripeness", "unripe", "color", "red"); | |
| assertThat(getGeneratingAction(target, "test/out.txt").getOwner().getExecProperties()) | |
| .containsExactly(); | |
| } | |
| @Test | |
| public void testOverridePlatformExecGroupExecProperty() throws Exception { | |
| createToolchainsAndPlatforms(); | |
| writeRuleWithActionsAndWatermelonExecGroup(); | |
| scratch.file( | |
| "test/BUILD", | |
| "load('//test:defs.bzl', 'with_actions')", | |
| "with_actions(", | |
| " name = 'papaya',", | |
| " output = 'out.txt',", | |
| " watermelon_output = 'watermelon_out.txt',", | |
| " exec_properties = {", | |
| " 'watermelon.ripeness': 'ripe',", | |
| " 'ripeness': 'unknown',", | |
| " },", | |
| ")"); | |
| ConfiguredTarget target = getConfiguredTarget("//test:papaya"); | |
| assertThat( | |
| getGeneratingAction(target, "test/watermelon_out.txt").getOwner().getExecProperties()) | |
| .containsExactly("ripeness", "ripe", "color", "red"); | |
| assertThat(getGeneratingAction(target, "test/out.txt").getOwner().getExecProperties()) | |
| .containsExactly("ripeness", "unknown"); | |
| } | |
| } |