| // Copyright 2020 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.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.packages.AspectDefinition; |
| import com.google.devtools.build.lib.packages.AspectParameters; |
| import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet; |
| import com.google.devtools.build.lib.packages.NativeAspectClass; |
| import com.google.devtools.build.lib.packages.RuleClass; |
| import com.google.devtools.build.lib.packages.Type; |
| import com.google.devtools.build.lib.rules.java.JavaConfiguration; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| import com.google.devtools.build.lib.util.FileTypeSet; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link RequiredConfigFragmentsProvider}. */ |
| @RunWith(JUnit4.class) |
| public final class RequiredConfigFragmentsTest extends BuildViewTestCase { |
| @Test |
| public void provideTransitiveRequiredFragmentsMode() throws Exception { |
| useConfiguration("--include_config_fragments_provider=transitive"); |
| scratch.file( |
| "a/BUILD", |
| "config_setting(name = 'config', values = {'start_end_lib': '1'})", |
| "py_library(name = 'pylib', srcs = ['pylib.py'])", |
| "cc_library(name = 'a', srcs = ['A.cc'], data = [':pylib'])"); |
| |
| ImmutableSet<String> ccLibTransitiveFragments = |
| getConfiguredTarget("//a:a") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(ccLibTransitiveFragments).containsAtLeast("CppConfiguration", "PythonConfiguration"); |
| |
| ImmutableSet<String> configSettingTransitiveFragments = |
| getConfiguredTarget("//a:config") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(configSettingTransitiveFragments).contains("CppOptions"); |
| } |
| |
| @Test |
| public void provideDirectRequiredFragmentsMode() throws Exception { |
| useConfiguration("--include_config_fragments_provider=direct"); |
| scratch.file( |
| "a/BUILD", |
| "config_setting(name = 'config', values = {'start_end_lib': '1'})", |
| "py_library(name = 'pylib', srcs = ['pylib.py'])", |
| "cc_library(name = 'a', srcs = ['A.cc'], data = [':pylib'])"); |
| |
| ImmutableSet<String> ccLibDirectFragments = |
| getConfiguredTarget("//a:a") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(ccLibDirectFragments).contains("CppConfiguration"); |
| assertThat(ccLibDirectFragments).doesNotContain("PythonConfiguration"); |
| |
| ImmutableSet<String> configSettingDirectFragments = |
| getConfiguredTarget("//a:config") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(configSettingDirectFragments).contains("CppOptions"); |
| } |
| |
| @Test |
| public void provideDirectHostOnlyRequiredFragmentsMode() throws Exception { |
| useConfiguration("--include_config_fragments_provider=direct_host_only"); |
| scratch.file( |
| "a/BUILD", |
| "py_library(name = 'pylib', srcs = ['pylib.py'])", |
| "cc_library(name = 'cclib', srcs = ['cclb.cc'], data = [':pylib'])"); |
| |
| RequiredConfigFragmentsProvider targetConfigProvider = |
| getConfiguredTarget("//a:cclib").getProvider(RequiredConfigFragmentsProvider.class); |
| RequiredConfigFragmentsProvider hostConfigProvider = |
| getHostConfiguredTarget("//a:cclib").getProvider(RequiredConfigFragmentsProvider.class); |
| |
| assertThat(targetConfigProvider).isNull(); |
| assertThat(hostConfigProvider).isNotNull(); |
| assertThat(hostConfigProvider.getRequiredConfigFragments()).contains("CppConfiguration"); |
| assertThat(hostConfigProvider.getRequiredConfigFragments()) |
| .doesNotContain("PythonConfiguration"); |
| } |
| |
| /** |
| * Helper method that returns a combined set of the common fragments all genrules require plus |
| * instance-specific requirements passed here. |
| */ |
| private ImmutableSortedSet<String> genRuleFragments(String... targetSpecificRequirements) |
| throws Exception { |
| scratch.file( |
| "base_genrule/BUILD", |
| "genrule(", |
| " name = 'base_genrule',", |
| " srcs = [],", |
| " outs = ['base_genrule.out'],", |
| " cmd = 'echo hi > $@')"); |
| ImmutableSortedSet.Builder<String> builder = ImmutableSortedSet.naturalOrder(); |
| builder.add(targetSpecificRequirements); |
| builder.addAll( |
| getConfiguredTarget("//base_genrule") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments()); |
| return builder.build(); |
| } |
| |
| @Test |
| public void requiresMakeVariablesSuppliedByDefine() throws Exception { |
| useConfiguration("--include_config_fragments_provider=direct", "--define", "myvar=myval"); |
| scratch.file( |
| "a/BUILD", |
| "genrule(", |
| " name = 'myrule',", |
| " srcs = [],", |
| " outs = ['myrule.out'],", |
| " cmd = 'echo $(myvar) $(COMPILATION_MODE) > $@')"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:myrule") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments) |
| .containsExactlyElementsIn(genRuleFragments("--define:myvar")) |
| .inOrder(); |
| } |
| |
| @Test |
| public void starlarkExpandMakeVariables() throws Exception { |
| useConfiguration("--include_config_fragments_provider=direct", "--define", "myvar=myval"); |
| scratch.file( |
| "a/defs.bzl", |
| "def _impl(ctx):", |
| " print(ctx.expand_make_variables('dummy attribute', 'string with $(myvar)!', {}))", |
| "", |
| "simple_rule = rule(", |
| " implementation = _impl,", |
| " attrs = {}", |
| ")"); |
| scratch.file("a/BUILD", "load('//a:defs.bzl', 'simple_rule')", "simple_rule(name = 'simple')"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:simple") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments).contains("--define:myvar"); |
| } |
| |
| /** |
| * Aspect that requires fragments both in its definition and through an optionally set <code> |
| * --define custom_define</code>. |
| */ |
| private static final class AspectWithConfigFragmentRequirements extends NativeAspectClass |
| implements ConfiguredAspectFactory { |
| @Override |
| public AspectDefinition getDefinition(AspectParameters params) { |
| return new AspectDefinition.Builder(this) |
| .requiresConfigurationFragments(JavaConfiguration.class) |
| .add(attr("custom_define", Type.STRING).allowedValues(new AllowedValueSet("", "myvar"))) |
| .build(); |
| } |
| |
| @Override |
| public ConfiguredAspect create( |
| ConfiguredTargetAndData ctadBase, |
| RuleContext ruleContext, |
| AspectParameters params, |
| String toolsRepository) |
| throws ActionConflictException, InterruptedException { |
| ConfiguredAspect.Builder builder = new ConfiguredAspect.Builder(ruleContext); |
| String customDefine = ruleContext.attributes().get("custom_define", Type.STRING); |
| if (!customDefine.isEmpty()) { |
| builder.addRequiredConfigFragments(ImmutableSet.of("--define:" + customDefine)); |
| } |
| return builder.build(); |
| } |
| } |
| |
| private static final AspectWithConfigFragmentRequirements |
| ASPECT_WITH_CONFIG_FRAGMENT_REQUIREMENTS = new AspectWithConfigFragmentRequirements(); |
| |
| /** Rule that attaches {@link AspectWithConfigFragmentRequirements} to its deps. */ |
| public static final class RuleThatAttachesAspect |
| implements RuleDefinition, RuleConfiguredTargetFactory { |
| @Override |
| public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { |
| return builder |
| .add(attr("custom_define", Type.STRING).allowedValues(new AllowedValueSet("", "myvar"))) |
| .add( |
| attr("deps", LABEL_LIST) |
| .allowedFileTypes(FileTypeSet.NO_FILE) |
| .aspect(ASPECT_WITH_CONFIG_FRAGMENT_REQUIREMENTS)) |
| .build(); |
| } |
| |
| @Override |
| public Metadata getMetadata() { |
| return RuleDefinition.Metadata.builder() |
| .name("rule_that_attaches_aspect") |
| .ancestors(BaseRuleClasses.NativeBuildRule.class) |
| .factoryClass(RuleThatAttachesAspect.class) |
| .build(); |
| } |
| |
| @Override |
| public ConfiguredTarget create(RuleContext ruleContext) |
| throws ActionConflictException, InterruptedException { |
| return new RuleConfiguredTargetBuilder(ruleContext) |
| .addProvider(RunfilesProvider.EMPTY) |
| .build(); |
| } |
| } |
| |
| @Override |
| protected ConfiguredRuleClassProvider createRuleClassProvider() { |
| ConfiguredRuleClassProvider.Builder builder = |
| new ConfiguredRuleClassProvider.Builder() |
| .addRuleDefinition(new RuleThatAttachesAspect()) |
| .addNativeAspectClass(ASPECT_WITH_CONFIG_FRAGMENT_REQUIREMENTS); |
| TestRuleClassProvider.addStandardRules(builder); |
| return builder.build(); |
| } |
| |
| @Test |
| public void aspectDefinitionRequiresFragments() throws Exception { |
| scratch.file( |
| "a/BUILD", |
| "rule_that_attaches_aspect(", |
| " name = 'parent',", |
| " deps = [':dep'])", |
| "rule_that_attaches_aspect(", |
| " name = 'dep')"); |
| useConfiguration("--include_config_fragments_provider=transitive"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:parent") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments).contains("JavaConfiguration"); |
| assertThat(requiredFragments).doesNotContain("--define:myvar"); |
| } |
| |
| @Test |
| public void aspectImplementationRequiresFragments() throws Exception { |
| scratch.file( |
| "a/BUILD", |
| "rule_that_attaches_aspect(", |
| " name = 'parent',", |
| " deps = [':dep'])", |
| "rule_that_attaches_aspect(", |
| " name = 'dep',", |
| " custom_define = 'myvar')"); |
| useConfiguration("--include_config_fragments_provider=transitive"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:parent") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments).contains("JavaConfiguration"); |
| assertThat(requiredFragments).contains("--define:myvar"); |
| } |
| |
| private void writeStarlarkTransitionsAndAllowList() throws Exception { |
| scratch.overwriteFile( |
| "tools/allowlists/function_transition_allowlist/BUILD", |
| "package_group(", |
| " name = 'function_transition_allowlist',", |
| " packages = [", |
| " '//a/...',", |
| " ],", |
| ")"); |
| scratch.file( |
| "transitions/defs.bzl", |
| "def _java_write_transition_impl(settings, attr):", |
| " return {'//command_line_option:javacopt': ['foo'] }", |
| "java_write_transition = transition(", |
| " implementation = _java_write_transition_impl,", |
| " inputs = [],", |
| " outputs = ['//command_line_option:javacopt'],", |
| ")", |
| "def _cpp_read_transition_impl(settings, attr):", |
| " return {}", |
| "cpp_read_transition = transition(", |
| " implementation = _cpp_read_transition_impl,", |
| " inputs = ['//command_line_option:copt'],", |
| " outputs = [],", |
| ")"); |
| scratch.file("transitions/BUILD"); |
| } |
| |
| @Test |
| public void starlarkRuleTransitionReadsFragment() throws Exception { |
| writeStarlarkTransitionsAndAllowList(); |
| scratch.file( |
| "a/defs.bzl", |
| "load('//transitions:defs.bzl', 'cpp_read_transition')", |
| "def _impl(ctx):", |
| " pass", |
| "has_cpp_aware_rule_transition = rule(", |
| " implementation = _impl,", |
| " cfg = cpp_read_transition,", |
| " attrs = {", |
| " '_allowlist_function_transition': attr.label(", |
| " default = '//tools/allowlists/function_transition_allowlist',", |
| " ),", |
| " })"); |
| scratch.file( |
| "a/BUILD", |
| "load('//a:defs.bzl', 'has_cpp_aware_rule_transition')", |
| "has_cpp_aware_rule_transition(name = 'cctarget')"); |
| useConfiguration("--include_config_fragments_provider=direct"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:cctarget") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments).contains("CppOptions"); |
| assertThat(requiredFragments).doesNotContain("JavaOptions"); |
| } |
| |
| @Test |
| public void starlarkRuleTransitionWritesFragment() throws Exception { |
| writeStarlarkTransitionsAndAllowList(); |
| scratch.file( |
| "a/defs.bzl", |
| "load('//transitions:defs.bzl', 'java_write_transition')", |
| "def _impl(ctx):", |
| " pass", |
| "has_java_aware_rule_transition = rule(", |
| " implementation = _impl,", |
| " cfg = java_write_transition,", |
| " attrs = {", |
| " '_allowlist_function_transition': attr.label(", |
| " default = '//tools/allowlists/function_transition_allowlist',", |
| " ),", |
| " })"); |
| scratch.file( |
| "a/BUILD", |
| "load('//a:defs.bzl', 'has_java_aware_rule_transition')", |
| "has_java_aware_rule_transition(name = 'javatarget')"); |
| useConfiguration("--include_config_fragments_provider=direct"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:javatarget") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| assertThat(requiredFragments).contains("JavaOptions"); |
| assertThat(requiredFragments).doesNotContain("CppOptions"); |
| } |
| |
| @Test |
| public void starlarkAttrTransition() throws Exception { |
| writeStarlarkTransitionsAndAllowList(); |
| scratch.file( |
| "a/defs.bzl", |
| "load('//transitions:defs.bzl', 'cpp_read_transition', 'java_write_transition')", |
| "def _impl(ctx):", |
| " pass", |
| "has_java_aware_attr_transition = rule(", |
| " implementation = _impl,", |
| " attrs = {", |
| " 'deps': attr.label_list(cfg = java_write_transition),", |
| " '_allowlist_function_transition': attr.label(", |
| " default = '//tools/allowlists/function_transition_allowlist',", |
| " ),", |
| " })", |
| "has_cpp_aware_rule_transition = rule(", |
| " implementation = _impl,", |
| " cfg = cpp_read_transition,", |
| " attrs = {", |
| " '_allowlist_function_transition': attr.label(", |
| " default = '//tools/allowlists/function_transition_allowlist',", |
| " ),", |
| " })"); |
| scratch.file( |
| "a/BUILD", |
| "load('//a:defs.bzl', 'has_cpp_aware_rule_transition', 'has_java_aware_attr_transition')", |
| "has_cpp_aware_rule_transition(name = 'ccchild')", |
| "has_java_aware_attr_transition(", |
| " name = 'javaparent',", |
| " deps = [':ccchild'])"); |
| useConfiguration("--include_config_fragments_provider=direct"); |
| ImmutableSet<String> requiredFragments = |
| getConfiguredTarget("//a:javaparent") |
| .getProvider(RequiredConfigFragmentsProvider.class) |
| .getRequiredConfigFragments(); |
| // We consider the attribute transition over the parent -> child edge a property of the parent. |
| assertThat(requiredFragments).contains("JavaOptions"); |
| // But not the child's rule transition. |
| assertThat(requiredFragments).doesNotContain("CppOptions"); |
| } |
| } |