// Copyright 2017 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; import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; 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.ConfigurationFactory; import com.google.devtools.build.lib.analysis.config.PatchTransition; import com.google.devtools.build.lib.analysis.util.AnalysisTestCase; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.AttributeMap; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; import com.google.devtools.build.lib.testutil.Suite; import com.google.devtools.build.lib.testutil.TestRuleClassProvider; import com.google.devtools.build.lib.testutil.TestSpec; import com.google.devtools.build.lib.testutil.UnknownRuleConfiguredTarget; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests <target, sourceConfig> -> <dep, depConfig> relationships over latebound attributes. * * <p>Ideally these tests would be in * {@link com.google.devtools.build.lib.skyframe.ConfigurationsForTargetsTest}. But that's a * Skyframe test (ConfiguredTargetFunction is a Skyframe function). And the Skyframe library doesn't * know anything about latebound attributes. So we need to place these properly under the analysis * package. */ @TestSpec(size = Suite.SMALL_TESTS) @RunWith(JUnit4.class) public class ConfigurationsForLateBoundTargetsTest extends AnalysisTestCase { private static final PatchTransition CHANGE_FOO_FLAG_TRANSITION = new PatchTransition() { @Override public BuildOptions apply(BuildOptions options) { BuildOptions toOptions = options.clone(); toOptions.get(LateBoundSplitUtil.TestOptions.class).fooFlag = "PATCHED!"; return toOptions; } @Override public boolean defaultsToSelf() { return false; } }; /** * Rule definition with a latebound dependency. */ private static class LateBoundDepRule implements RuleDefinition { private static final Attribute.LateBoundLabel<BuildConfiguration> LATEBOUND_VALUE_RESOLVER = new Attribute.LateBoundLabel<BuildConfiguration>() { @Override public Label resolve(Rule rule, AttributeMap attributes, BuildConfiguration config) { return Label.parseAbsoluteUnchecked("//foo:latebound_dep"); } }; @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { return builder .add( attr(":latebound_attr", LABEL) .value(LATEBOUND_VALUE_RESOLVER) .cfg(CHANGE_FOO_FLAG_TRANSITION)) .requiresConfigurationFragments(LateBoundSplitUtil.TestFragment.class) .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("rule_with_latebound_attr") .ancestors(BaseRuleClasses.RuleBase.class) .factoryClass(UnknownRuleConfiguredTarget.class) .build(); } } @Before public void setupCustomLateBoundRules() throws Exception { ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); TestRuleClassProvider.addStandardRules(builder); builder.addRuleDefinition(new LateBoundSplitUtil.RuleWithLateBoundSplitAttribute()); builder.addRuleDefinition(new LateBoundSplitUtil.RuleWithTestFragment()); builder.addConfigurationFragment(new LateBoundSplitUtil.FragmentLoader()); builder.addConfigurationOptions(LateBoundSplitUtil.TestOptions.class); builder.addRuleDefinition(new LateBoundDepRule()); useRuleClassProvider(builder.build()); // Register the latebound split fragment with the config creation environment. useConfigurationFactory(new ConfigurationFactory( ruleClassProvider.getConfigurationCollectionFactory(), ruleClassProvider.getConfigurationFragments())); } @Test public void lateBoundAttributeInTargetConfiguration() throws Exception { scratch.file("foo/BUILD", "rule_with_latebound_attr(", " name = 'foo')", "rule_with_test_fragment(", " name = 'latebound_dep')"); update("//foo:foo"); assertNotNull(getConfiguredTarget("//foo:foo", getTargetConfiguration())); ConfiguredTarget dep = Iterables.getOnlyElement( SkyframeExecutorTestUtils.getExistingConfiguredTargets( skyframeExecutor, Label.parseAbsolute("//foo:latebound_dep"))); assertThat(dep.getConfiguration()).isNotEqualTo(getTargetConfiguration()); assertThat(LateBoundSplitUtil.getOptions(dep.getConfiguration()).fooFlag).isEqualTo("PATCHED!"); } @Test public void lateBoundAttributeInHostConfiguration() throws Exception { scratch.file("foo/BUILD", "genrule(", " name = 'gen',", " srcs = [],", " outs = ['gen.out'],", " cmd = 'echo hi > $@',", " tools = [':foo'])", "rule_with_latebound_attr(", " name = 'foo')", "rule_with_test_fragment(", " name = 'latebound_dep')"); update("//foo:gen"); assertNotNull(getConfiguredTarget("//foo:foo", getHostConfiguration())); ConfiguredTarget dep = Iterables.getOnlyElement( SkyframeExecutorTestUtils.getExistingConfiguredTargets( skyframeExecutor, Label.parseAbsolute("//foo:latebound_dep"))); assertThat(dep.getConfiguration()).isEqualTo(getHostConfiguration()); // This is technically redundant, but slightly stronger in sanity checking that the host // configuration doesn't happen to match what the patch would have done. assertThat(LateBoundSplitUtil.getOptions(dep.getConfiguration()).fooFlag).isEmpty(); } @Test public void lateBoundSplitAttributeInTargetConfiguration() throws Exception { scratch.file("foo/BUILD", "rule_with_latebound_split(", " name = 'foo')", "rule_with_test_fragment(", " name = 'latebound_dep')"); update("//foo:foo"); assertNotNull(getConfiguredTarget("//foo:foo")); Iterable<ConfiguredTarget> deps = SkyframeExecutorTestUtils.getExistingConfiguredTargets( skyframeExecutor, Label.parseAbsolute("//foo:latebound_dep")); assertThat(deps).hasSize(2); assertThat( ImmutableList.of( LateBoundSplitUtil.getOptions(Iterables.get(deps, 0).getConfiguration()).fooFlag, LateBoundSplitUtil.getOptions(Iterables.get(deps, 1).getConfiguration()).fooFlag)) .containsExactly("one", "two"); } @Test public void lateBoundSplitAttributeInHostConfiguration() throws Exception { scratch.file("foo/BUILD", "genrule(", " name = 'gen',", " srcs = [],", " outs = ['gen.out'],", " cmd = 'echo hi > $@',", " tools = [':foo'])", "rule_with_latebound_split(", " name = 'foo')", "rule_with_test_fragment(", " name = 'latebound_dep')"); update("//foo:gen"); assertNotNull(getConfiguredTarget("//foo:foo", getHostConfiguration())); ConfiguredTarget dep = Iterables.getOnlyElement( SkyframeExecutorTestUtils.getExistingConfiguredTargets( skyframeExecutor, Label.parseAbsolute("//foo:latebound_dep"))); assertThat(dep.getConfiguration()).isEqualTo(getHostConfiguration()); // This is technically redundant, but slightly stronger in sanity checking that the host // configuration doesn't happen to match what the split would have done. assertThat(LateBoundSplitUtil.getOptions(dep.getConfiguration()).fooFlag).isEmpty(); } }