| // 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.packages; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.devtools.build.lib.analysis.testing.DeclaredExecGroupSubject.assertThat; |
| import static com.google.devtools.build.lib.analysis.testing.RuleClassSubject.assertThat; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; |
| import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.OUTPUT_LIST; |
| import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.substitutePlaceholderIntoTemplate; |
| import static com.google.devtools.build.lib.packages.RuleClass.Builder.STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME; |
| import static com.google.devtools.build.lib.packages.Type.BOOLEAN; |
| import static com.google.devtools.build.lib.packages.Type.INTEGER; |
| import static com.google.devtools.build.lib.packages.Type.STRING; |
| import static com.google.devtools.build.lib.packages.Types.STRING_LIST; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.devtools.build.lib.analysis.config.Fragment; |
| import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement; |
| import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.RepositoryMapping; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventCollector; |
| import com.google.devtools.build.lib.events.EventKind; |
| import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate.CannotPrecomputeDefaultsException; |
| import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; |
| import com.google.devtools.build.lib.packages.RuleClass.AutoExecGroupsMode; |
| import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory; |
| import com.google.devtools.build.lib.packages.RuleClass.ToolchainResolutionMode; |
| import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; |
| import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.StarlarkFunction; |
| import net.starlark.java.eval.StarlarkInt; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.syntax.Location; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link RuleClass}. */ |
| @RunWith(JUnit4.class) |
| public final class RuleClassTest extends PackageLoadingTestCase { |
| private static final RuleClass.ConfiguredTargetFactory<Object, Object, Exception> |
| DUMMY_CONFIGURED_TARGET_FACTORY = |
| ruleContext -> { |
| throw new IllegalStateException(); |
| }; |
| |
| private static final ImmutableList<StarlarkThread.CallStackEntry> DUMMY_STACK = |
| ImmutableList.of( |
| StarlarkThread.callStackEntry( |
| StarlarkThread.TOP_LEVEL, Location.fromFileLineColumn("BUILD", 10, 1)), |
| StarlarkThread.callStackEntry("bar", Location.fromFileLineColumn("bar.bzl", 42, 1)), |
| StarlarkThread.callStackEntry("rule", Location.BUILTIN)); |
| |
| private static final class DummyFragment extends Fragment {} |
| |
| private static RuleClass createRuleClassA() throws LabelSyntaxException { |
| return newRuleClass( |
| "ruleA", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("my-string-attr", STRING).mandatory().build(), |
| attr("my-label-attr", LABEL) |
| .mandatory() |
| .legacyAllowAnyFileType() |
| .value(Label.parseCanonical("//default:label")) |
| .build(), |
| attr("my-labellist-attr", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), |
| attr("my-integer-attr", INTEGER).value(StarlarkInt.of(42)).build(), |
| attr("my-string-attr2", STRING).mandatory().value((String) null).build(), |
| attr("my-stringlist-attr", STRING_LIST).build(), |
| attr("my-sorted-stringlist-attr", STRING_LIST).orderIndependent().build()); |
| } |
| |
| private static RuleClass createRuleClassB(RuleClass ruleClassA) { |
| // emulates attribute inheritance |
| List<Attribute> attributes = new ArrayList<>(ruleClassA.getAttributeProvider().getAttributes()); |
| attributes.add(attr("another-string-attr", STRING).mandatory().build()); |
| return newRuleClass( |
| "ruleB", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attributes.toArray(new Attribute[0])); |
| } |
| |
| // Helper method to paper over how some test cases don't supply the mandatory name attribute for |
| // historic reasons. |
| private static ImmutableMap<String, Object> ensureNameAttrValuePresent( |
| Map<String, Object> attrValues) { |
| if (attrValues.containsKey("name")) { |
| return ImmutableMap.copyOf(attrValues); |
| } else { |
| return ImmutableMap.<String, Object>builder() |
| .putAll(attrValues) |
| .put("name", "my-name") |
| .buildOrThrow(); |
| } |
| } |
| |
| @Test |
| public void testRuleClassBasics() throws Exception { |
| RuleClass ruleClassA = createRuleClassA(); |
| |
| assertThat(ruleClassA.getName()).isEqualTo("ruleA"); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeCount()).isEqualTo(8); |
| |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("name")).isEqualTo(0); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-string-attr")).isEqualTo(1); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-label-attr")).isEqualTo(2); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-labellist-attr")) |
| .isEqualTo(3); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-integer-attr")).isEqualTo(4); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-string-attr2")).isEqualTo(5); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-stringlist-attr")) |
| .isEqualTo(6); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeIndex("my-sorted-stringlist-attr")) |
| .isEqualTo(7); |
| |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("name")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(0)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-string-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(1)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-label-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(2)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-labellist-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(3)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-integer-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(4)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-string-attr2")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(5)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-stringlist-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(6)); |
| assertThat(ruleClassA.getAttributeProvider().getAttributeByName("my-sorted-stringlist-attr")) |
| .isEqualTo(ruleClassA.getAttributeProvider().getAttribute(7)); |
| |
| // default based on type |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(0).getDefaultValue(null)) |
| .isEqualTo(""); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(1).getDefaultValue(null)) |
| .isEqualTo(""); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(2).getDefaultValue(null)) |
| .isEqualTo(Label.parseCanonical("//default:label")); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(3).getDefaultValue(null)) |
| .isEqualTo(ImmutableList.of()); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(4).getDefaultValue(null)) |
| .isEqualTo(StarlarkInt.of(42)); |
| // default explicitly specified |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(5).getDefaultValue(null)).isNull(); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(6).getDefaultValue(null)) |
| .isEqualTo(ImmutableList.of()); |
| assertThat(ruleClassA.getAttributeProvider().getAttribute(7).getDefaultValue(null)) |
| .isEqualTo(ImmutableList.of()); |
| } |
| |
| @Test |
| public void testRuleClassInheritance() throws Exception { |
| RuleClass ruleClassA = createRuleClassA(); |
| RuleClass ruleClassB = createRuleClassB(ruleClassA); |
| |
| assertThat(ruleClassB.getName()).isEqualTo("ruleB"); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeCount()).isEqualTo(9); |
| |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("name")).isEqualTo(0); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-string-attr")).isEqualTo(1); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-label-attr")).isEqualTo(2); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-labellist-attr")) |
| .isEqualTo(3); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-integer-attr")).isEqualTo(4); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-string-attr2")).isEqualTo(5); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-stringlist-attr")) |
| .isEqualTo(6); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("my-sorted-stringlist-attr")) |
| .isEqualTo(7); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeIndex("another-string-attr")) |
| .isEqualTo(8); |
| |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("name")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(0)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-string-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(1)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-label-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(2)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-labellist-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(3)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-integer-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(4)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-string-attr2")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(5)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-stringlist-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(6)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("my-sorted-stringlist-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(7)); |
| assertThat(ruleClassB.getAttributeProvider().getAttributeByName("another-string-attr")) |
| .isEqualTo(ruleClassB.getAttributeProvider().getAttribute(8)); |
| } |
| |
| private static final String TEST_PACKAGE_NAME = "testpackage"; |
| |
| private static final String TEST_RULE_NAME = "my-rule-A"; |
| |
| private static final int TEST_RULE_DEFINED_AT_LINE = 42; |
| |
| private static final String TEST_RULE_LABEL = "@//" + TEST_PACKAGE_NAME + ":" + TEST_RULE_NAME; |
| |
| private Path testBuildfilePath; |
| private Location testRuleLocation; |
| |
| @Before |
| public void setRuleLocation() { |
| testBuildfilePath = root.getRelative("testpackage/BUILD"); |
| testRuleLocation = |
| Location.fromFileLineColumn(testBuildfilePath.toString(), TEST_RULE_DEFINED_AT_LINE, 0); |
| } |
| |
| private Package.Builder createDummyPackageBuilder() { |
| return packageFactory.newPackageBuilder( |
| PackageIdentifier.createInMainRepo(TEST_PACKAGE_NAME), |
| RootedPath.toRootedPath(root, testBuildfilePath), |
| "TESTING", |
| Optional.empty(), |
| Optional.empty(), |
| StarlarkSemantics.DEFAULT, |
| /* repositoryMapping= */ RepositoryMapping.ALWAYS_FALLBACK, |
| /* mainRepositoryMapping= */ null, |
| /* cpuBoundSemaphore= */ null, |
| /* generatorMap= */ null, |
| /* configSettingVisibilityPolicy= */ null, |
| /* globber= */ null); |
| } |
| |
| @Test |
| public void testDuplicatedDeps() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), |
| attr("list2", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), |
| attr("list3", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| // LinkedHashMap -> predictable iteration order for testing |
| Map<String, Object> attributeValues = new LinkedHashMap<>(); |
| attributeValues.put("list1", Lists.newArrayList("//testpackage:dup1", ":dup1", ":nodup")); |
| attributeValues.put("list2", Lists.newArrayList(":nodup1", ":nodup2")); |
| attributeValues.put("list3", Lists.newArrayList(":dup1", ":dup1", ":dup2", ":dup2")); |
| |
| reporter.removeHandler(failFastHandler); |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| |
| assertThat(eventCollector.count()).isSameInstanceAs(3); |
| assertDupError("//testpackage:dup1", "list1", "depsRule"); |
| assertDupError("//testpackage:dup1", "list3", "depsRule"); |
| assertDupError("//testpackage:dup2", "list3", "depsRule"); |
| } |
| |
| private void assertDupError(String label, String attrName, String ruleName) { |
| assertContainsEvent(String.format("Label '%s' is duplicated in the '%s' attribute of rule '%s'", |
| label, attrName, ruleName)); |
| } |
| |
| @Test |
| public void testDuplicatedDepsWithinSingleSelectConditionError() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| SelectorList selectorList1 = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of("//conditions:a", ImmutableList.of(":dup1", ":dup1")), "")); |
| |
| // expect errors |
| reporter.removeHandler(failFastHandler); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("list1", selectorList1); |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| |
| assertThat(eventCollector.count()).isSameInstanceAs(1); |
| assertDupError("//testpackage:dup1", "list1", "depsRule"); |
| } |
| |
| @Test |
| public void testDuplicatedDepsWithinConditionMultipleSelectsErrors() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| SelectorList selectorList1a = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":dup1", "dup1"), |
| "//conditions:b", ImmutableList.of(":nodup1")), |
| "")); |
| SelectorList selectorList1b = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:c", ImmutableList.of(":dup2", "dup2"), |
| "//conditions:d", ImmutableList.of(":nodup1")), |
| "")); |
| SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); |
| |
| // expect errors |
| reporter.removeHandler(failFastHandler); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("list1", selectorList1); |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| |
| assertThat(eventCollector.count()).isSameInstanceAs(2); |
| assertDupError("//testpackage:dup1", "list1", "depsRule"); |
| assertDupError("//testpackage:dup2", "list1", "depsRule"); |
| } |
| |
| @Test |
| public void testSameDepAcrossMultipleSelectsNoDuplicateNoError() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| // ignore duplicatess across selects where values appear duplicated but are not |
| SelectorList selectorList1a = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":nodup1"), |
| "//conditions:b", ImmutableList.of(":nodup2")), |
| "")); |
| SelectorList selectorList1b = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":nodup2"), |
| "//conditions:b", ImmutableList.of(":nodup1")), |
| "")); |
| SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("list1", selectorList1); |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| } |
| |
| @Test |
| public void testSameDepAcrossMultipleSelectsIsDuplicateNoError() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| // repetition of dup1 is identified at analysis time, not loading time |
| SelectorList selectorList1a = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":dup1"), |
| "//conditions:b", ImmutableList.of(":nodup1")), |
| "")); |
| SelectorList selectorList1b = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":dup1"), |
| "//conditions:b", ImmutableList.of(":nodup2")), |
| "")); |
| SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("list1", selectorList1); |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| } |
| |
| @Test |
| public void testSameDepAcrossConditionsInSelectNoError() throws Exception { |
| RuleClass depsRuleClass = |
| newRuleClass( |
| "ruleDeps", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); |
| |
| SelectorList selectorList1 = |
| SelectorList.of( |
| new SelectorValue( |
| ImmutableMap.of( |
| "//conditions:a", ImmutableList.of(":nodup1"), |
| "//conditions:b", ImmutableList.of(":nodup1")), |
| "")); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("list1", selectorList1); |
| |
| createRule(depsRuleClass, "depsRule", attributeValues); |
| } |
| |
| @Test |
| public void testCreateRule() throws Exception { |
| RuleClass ruleClassA = createRuleClassA(); |
| |
| // LinkedHashMap -> predictable iteration order for testing |
| Map<String, Object> attributeValues = new LinkedHashMap<>(); |
| attributeValues.put("my-labellist-attr", "foobar"); // wrong type |
| attributeValues.put("bogus-attr", "foobar"); // no such attr |
| attributeValues.put("my-stringlist-attr", Arrays.asList("foo", "bar")); |
| |
| reporter.removeHandler(failFastHandler); |
| EventCollector collector = new EventCollector(EventKind.ERRORS); |
| reporter.addHandler(collector); |
| |
| Rule rule = createRule(ruleClassA, TEST_RULE_NAME, attributeValues); |
| |
| // TODO(blaze-team): (2009) refactor to use assertContainsEvent |
| Iterator<String> expectedMessages = |
| Arrays.asList( |
| """ |
| expected value of type 'list(label)' for attribute 'my-labellist-attr' \ |
| of 'ruleA', but got \"foobar\" (string)\ |
| """, |
| "no such attribute 'bogus-attr' in 'ruleA' rule", |
| "missing value for mandatory attribute 'my-string-attr' in 'ruleA' rule", |
| "missing value for mandatory attribute 'my-label-attr' in 'ruleA' rule", |
| "missing value for mandatory attribute 'my-labellist-attr' in 'ruleA' rule", |
| "missing value for mandatory attribute 'my-string-attr2' in 'ruleA' rule") |
| .iterator(); |
| |
| for (Event event : collector) { |
| assertThat(event.getLocation().line()).isEqualTo(TEST_RULE_DEFINED_AT_LINE); |
| assertThat(event.getLocation().file()).isEqualTo(testBuildfilePath.toString()); |
| assertThat(event.getMessage()) |
| .isEqualTo(TEST_RULE_LABEL.substring(1) + ": " + expectedMessages.next()); |
| } |
| |
| // Test basic rule properties: |
| assertThat(rule.getRuleClass()).isEqualTo("ruleA"); |
| assertThat(rule.getName()).isEqualTo(TEST_RULE_NAME); |
| assertThat(rule.getLabel().toString()).isEqualTo(TEST_RULE_LABEL.substring(1)); |
| |
| // Test attribute access: |
| AttributeMap attributes = RawAttributeMapper.of(rule); |
| assertThat(attributes.get("my-label-attr", BuildType.LABEL).toString()) |
| .isEqualTo("//default:label"); |
| assertThat(attributes.get("my-integer-attr", Type.INTEGER).toIntUnchecked()).isEqualTo(42); |
| // missing attribute -> default chosen based on type |
| assertThat(attributes.get("my-string-attr", Type.STRING)).isEmpty(); |
| assertThat(attributes.get("my-labellist-attr", BuildType.LABEL_LIST)).isEmpty(); |
| assertThat(attributes.get("my-stringlist-attr", Types.STRING_LIST)) |
| .isEqualTo(Arrays.asList("foo", "bar")); |
| IllegalArgumentException e = |
| assertThrows( |
| IllegalArgumentException.class, () -> attributes.get("my-labellist-attr", Type.STRING)); |
| assertThat(e) |
| .hasMessageThat() |
| .isEqualTo( |
| "Attribute my-labellist-attr is of type list(label) " |
| + "and not of type string in ruleA rule //testpackage:my-rule-A"); |
| } |
| |
| @Test |
| public void testImplicitOutputs() throws Exception { |
| RuleClass ruleClassC = |
| newRuleClass( |
| "ruleC", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| ImplicitOutputsFunction.fromTemplates( |
| "foo-%{name}.bar", "lib%{name}-wazoo-%{name}.mumble", "stuff-%{outs}-bar"), |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("outs", OUTPUT_LIST).build()); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("outs", Collections.singletonList("explicit_out")); |
| attributeValues.put("name", "myrule"); |
| |
| Rule rule = createRule(ruleClassC, "myrule", attributeValues); |
| |
| Set<String> set = new HashSet<>(); |
| for (OutputFile outputFile : rule.getOutputFiles()) { |
| set.add(outputFile.getName()); |
| assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule); |
| } |
| assertThat(set).containsExactly("foo-myrule.bar", "libmyrule-wazoo-myrule.mumble", |
| "stuff-explicit_out-bar", "explicit_out"); |
| } |
| |
| @Test |
| public void testImplicitOutsWithBasenameDirname() throws Exception { |
| RuleClass ruleClass = |
| newRuleClass( |
| "ruleClass", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| ImplicitOutputsFunction.fromTemplates("%{dirname}lib%{basename}.bar"), |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true); |
| |
| Rule rule = createRule(ruleClass, "myRule", ImmutableMap.of()); |
| assertThat(Iterables.getOnlyElement(rule.getOutputFiles()).getName()) |
| .isEqualTo("libmyRule.bar"); |
| |
| Rule ruleWithSlash = createRule(ruleClass, "myRule/with/slash", ImmutableMap.of()); |
| assertThat(Iterables.getOnlyElement(ruleWithSlash.getOutputFiles()).getName()) |
| .isEqualTo("myRule/with/libslash.bar"); |
| } |
| |
| /** |
| * Helper routine that instantiates a rule class with the given computed default and supporting |
| * attributes for the default to reference. |
| */ |
| private static RuleClass getRuleClassWithComputedDefault(Attribute computedDefault) { |
| return newRuleClass( |
| "ruleClass", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| ImplicitOutputsFunction.fromTemplates("empty"), |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("condition", BOOLEAN).value(false).build(), |
| attr("declared1", BOOLEAN).value(false).build(), |
| attr("declared2", BOOLEAN).value(false).build(), |
| attr("nonconfigurable", BOOLEAN).nonconfigurable("test").value(false).build(), |
| computedDefault); |
| } |
| |
| /** |
| * Helper routine that checks that a computed default is valid and bound to the expected value. |
| */ |
| private void checkValidComputedDefault(Object expectedValue, Attribute computedDefault, |
| ImmutableMap<String, Object> attrValueMap) throws Exception { |
| assertThat(computedDefault.getDefaultValueUnchecked()) |
| .isInstanceOf(Attribute.ComputedDefault.class); |
| Rule rule = |
| createRule(getRuleClassWithComputedDefault(computedDefault), "myRule", attrValueMap); |
| AttributeMap attributes = RawAttributeMapper.of(rule); |
| assertThat(attributes.get(computedDefault.getName(), computedDefault.getType())) |
| .isEqualTo(expectedValue); |
| } |
| |
| /** |
| * Helper routine that checks that a computed default is invalid due to declared dependency issues |
| * and fails with the expected message. |
| */ |
| private void checkInvalidComputedDefault(Attribute computedDefault, String expectedMessage) { |
| IllegalArgumentException e = |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> |
| createRule( |
| getRuleClassWithComputedDefault(computedDefault), "myRule", ImmutableMap.of())); |
| assertThat(e).hasMessageThat().isEqualTo(expectedMessage); |
| } |
| |
| /** Tests computed default values are computed as expected. */ |
| @Test |
| public void testComputedDefault() throws Exception { |
| Attribute computedDefault = |
| attr("$result", BOOLEAN) |
| .value( |
| new Attribute.ComputedDefault("condition") { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| return rule.get("condition", Type.BOOLEAN); |
| } |
| }) |
| .build(); |
| |
| checkValidComputedDefault( |
| Boolean.FALSE, computedDefault, ImmutableMap.of("condition", Boolean.FALSE)); |
| checkValidComputedDefault( |
| Boolean.TRUE, computedDefault, ImmutableMap.of("condition", Boolean.TRUE)); |
| } |
| |
| /** |
| * Tests that computed defaults can only read attribute values for configurable attributes that |
| * have been explicitly declared. |
| */ |
| @Test |
| public void testComputedDefaultDeclarations() throws Exception { |
| checkValidComputedDefault( |
| Boolean.FALSE, |
| attr("$good_default_no_declares", BOOLEAN) |
| .value( |
| new Attribute.ComputedDefault() { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| // OK: not a value check: |
| return rule.isAttributeValueExplicitlySpecified("undeclared"); |
| } |
| }) |
| .build(), |
| ImmutableMap.of()); |
| |
| checkValidComputedDefault( |
| Boolean.FALSE, |
| attr("$good_default_one_declare", BOOLEAN) |
| .value( |
| new Attribute.ComputedDefault("declared1") { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| return rule.get("declared1", Type.BOOLEAN); |
| } |
| }) |
| .build(), |
| ImmutableMap.of()); |
| |
| checkValidComputedDefault( |
| Boolean.FALSE, |
| attr("$good_default_two_declares", BOOLEAN) |
| .value( |
| new Attribute.ComputedDefault("declared1", "declared2") { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| return rule.get("declared1", Type.BOOLEAN) |
| && rule.get("declared2", Type.BOOLEAN); |
| } |
| }) |
| .build(), |
| ImmutableMap.of()); |
| |
| checkInvalidComputedDefault( |
| attr("$bad_default_no_declares", BOOLEAN).value( |
| new Attribute.ComputedDefault() { |
| @Override public Object getDefault(AttributeMap rule) { |
| return rule.get("declared1", Type.BOOLEAN); |
| } |
| }).build(), |
| "attribute \"declared1\" isn't available in this computed default context"); |
| |
| checkInvalidComputedDefault( |
| attr("$bad_default_one_declare", BOOLEAN).value( |
| new Attribute.ComputedDefault("declared1") { |
| @Override public Object getDefault(AttributeMap rule) { |
| return rule.get("declared1", Type.BOOLEAN) || rule.get("declared2", Type.BOOLEAN); |
| } |
| }).build(), |
| "attribute \"declared2\" isn't available in this computed default context"); |
| |
| checkInvalidComputedDefault( |
| attr("$bad_default_two_declares", BOOLEAN).value( |
| new Attribute.ComputedDefault("declared1", "declared2") { |
| @Override public Object getDefault(AttributeMap rule) { |
| return rule.get("condition", Type.BOOLEAN); |
| } |
| }).build(), |
| "attribute \"condition\" isn't available in this computed default context"); |
| } |
| |
| /** |
| * Tests that computed defaults *can* read attribute values for non-configurable attributes |
| * without needing to explicitly declare them. |
| */ |
| @Test |
| public void testComputedDefaultWithNonConfigurableAttributes() throws Exception { |
| checkValidComputedDefault( |
| Boolean.FALSE, |
| attr("$good_default_reading_undeclared_nonconfigurable_attribute", BOOLEAN) |
| .value( |
| new Attribute.ComputedDefault() { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| return rule.get("nonconfigurable", Type.BOOLEAN); |
| } |
| }) |
| .build(), |
| ImmutableMap.of()); |
| } |
| |
| @Test |
| public void testOutputsAreOrdered() throws Exception { |
| RuleClass ruleClassC = |
| newRuleClass( |
| "ruleC", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| ImplicitOutputsFunction.fromTemplates("first-%{name}", "second-%{name}", "out-%{outs}"), |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("outs", OUTPUT_LIST).build()); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("outs", ImmutableList.of("third", "fourth")); |
| attributeValues.put("name", "myrule"); |
| |
| Rule rule = createRule(ruleClassC, "myrule", attributeValues); |
| |
| List<String> actual = new ArrayList<>(); |
| for (OutputFile outputFile : rule.getOutputFiles()) { |
| actual.add(outputFile.getName()); |
| assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule); |
| } |
| assertWithMessage("unexpected output set").that(actual).containsExactly("first-myrule", |
| "second-myrule", "out-third", "out-fourth", "third", "fourth"); |
| assertWithMessage("invalid output ordering").that(actual).containsExactly("first-myrule", |
| "second-myrule", "out-third", "out-fourth", "third", "fourth").inOrder(); |
| } |
| |
| @Test |
| public void testSubstitutePlaceholderIntoTemplate() throws Exception { |
| RuleClass ruleClass = |
| newRuleClass( |
| "ruleA", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(), |
| true, |
| attr("a", STRING_LIST).mandatory().build(), |
| attr("b", STRING_LIST).mandatory().build(), |
| attr("c", STRING_LIST).mandatory().build(), |
| attr("baz", STRING_LIST).mandatory().build(), |
| attr("empty", STRING_LIST).build()); |
| |
| Map<String, Object> attributeValues = new LinkedHashMap<>(); |
| attributeValues.put("a", ImmutableList.of("a", "A")); |
| attributeValues.put("b", ImmutableList.of("b", "B")); |
| attributeValues.put("c", ImmutableList.of("c", "C")); |
| attributeValues.put("baz", ImmutableList.of("baz", "BAZ")); |
| attributeValues.put("empty", ImmutableList.<String>of()); |
| |
| AttributeMap rule = RawAttributeMapper.of(createRule(ruleClass, "testrule", attributeValues)); |
| |
| assertThat(substitutePlaceholderIntoTemplate("foo", rule)).containsExactly("foo"); |
| assertThat(substitutePlaceholderIntoTemplate("foo-%{baz}-bar", rule)).containsExactly( |
| "foo-baz-bar", "foo-BAZ-bar").inOrder(); |
| assertThat(substitutePlaceholderIntoTemplate("%{a}-%{b}-%{c}", rule)).containsExactly("a-b-c", |
| "a-b-C", "a-B-c", "a-B-C", "A-b-c", "A-b-C", "A-B-c", "A-B-C").inOrder(); |
| assertThat(substitutePlaceholderIntoTemplate("%{a", rule)).containsExactly("%{a"); |
| assertThat(substitutePlaceholderIntoTemplate("%{a}}", rule)).containsExactly("a}", "A}") |
| .inOrder(); |
| assertThat(substitutePlaceholderIntoTemplate("x%{a}y%{empty}", rule)).isEmpty(); |
| } |
| |
| @Test |
| public void testOrderIndependentAttribute() throws Exception { |
| RuleClass ruleClassA = createRuleClassA(); |
| |
| List<String> list = Arrays.asList("foo", "bar", "baz"); |
| Map<String, Object> attributeValues = new LinkedHashMap<>(); |
| // mandatory values |
| attributeValues.put("my-string-attr", ""); |
| attributeValues.put("my-label-attr", "//project"); |
| attributeValues.put("my-string-attr2", ""); |
| attributeValues.put("my-labellist-attr", Collections.emptyList()); |
| // to compare the effect of .orderIndependent() |
| attributeValues.put("my-stringlist-attr", list); |
| attributeValues.put("my-sorted-stringlist-attr", list); |
| |
| Rule rule = createRule(ruleClassA, "testrule", attributeValues); |
| AttributeMap attributes = RawAttributeMapper.of(rule); |
| |
| assertThat(attributes.get("my-stringlist-attr", Types.STRING_LIST)).isEqualTo(list); |
| assertThat(attributes.get("my-sorted-stringlist-attr", Types.STRING_LIST)) |
| .isEqualTo(Arrays.asList("bar", "baz", "foo")); |
| } |
| |
| @CanIgnoreReturnValue |
| private Rule createRule(RuleClass ruleClass, String name, Map<String, Object> attributeValues) |
| throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException { |
| Package.Builder pkgBuilder = createDummyPackageBuilder(); |
| Label ruleLabel; |
| try { |
| ruleLabel = pkgBuilder.createLabel(name); |
| } catch (LabelSyntaxException e) { |
| throw new IllegalArgumentException("Rule has illegal label", e); |
| } |
| attributeValues = ensureNameAttrValuePresent(attributeValues); |
| Rule rule = |
| ruleClass.createRule( |
| pkgBuilder, |
| ruleLabel, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| true, |
| ImmutableList.of( |
| StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, testRuleLocation))); |
| pkgBuilder.getLocalEventHandler().replayOn(reporter); |
| return rule; |
| } |
| |
| @Test |
| public void testOverrideWithWrongType() { |
| RuleClass parentRuleClass = createParentRuleClass(); |
| |
| RuleClass.Builder childRuleClassBuilder = |
| new RuleClass.Builder("child_rule", RuleClassType.NORMAL, false, parentRuleClass); |
| IllegalStateException e = |
| assertThrows( |
| IllegalStateException.class, |
| () -> childRuleClassBuilder.override(attr("attr", INTEGER))); |
| assertThat(e) |
| .hasMessageThat() |
| .isEqualTo( |
| "The type of the new attribute 'int' is different from " |
| + "the original one 'string'."); |
| } |
| |
| @Test |
| public void testOverrideWithRightType() { |
| RuleClass parentRuleClass = createParentRuleClass(); |
| |
| RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder( |
| "child_rule", RuleClassType.NORMAL, false, parentRuleClass); |
| childRuleClassBuilder.override(attr("attr", STRING)); |
| } |
| |
| @Test |
| public void testCopyAndOverrideAttribute() throws Exception { |
| RuleClass parentRuleClass = createParentRuleClass(); |
| RuleClass childRuleClass = createChildRuleClass(parentRuleClass); |
| |
| Map<String, Object> parentValues = new LinkedHashMap<>(); |
| Map<String, Object> childValues = new LinkedHashMap<>(); |
| childValues.put("attr", "somevalue"); |
| createRule(parentRuleClass, "parent_rule", parentValues); |
| createRule(childRuleClass, "child_rule", childValues); |
| } |
| |
| @Test |
| public void testCopyAndOverrideAttributeMandatoryMissing() throws Exception { |
| RuleClass parentRuleClass = createParentRuleClass(); |
| RuleClass childRuleClass = createChildRuleClass(parentRuleClass); |
| |
| Map<String, Object> childValues = new LinkedHashMap<>(); |
| reporter.removeHandler(failFastHandler); |
| createRule(childRuleClass, "child_rule", childValues); |
| |
| assertThat(eventCollector.count()).isSameInstanceAs(1); |
| assertContainsEvent("//testpackage:child_rule: missing value for mandatory " |
| + "attribute 'attr' in 'child_rule' rule"); |
| } |
| |
| @Test |
| public void testRequiredFragmentInheritance() { |
| RuleClass parentRuleClass = createParentRuleClass(); |
| RuleClass childRuleClass = createChildRuleClass(parentRuleClass); |
| assertThat(parentRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments()) |
| .containsExactly(DummyFragment.class); |
| assertThat(childRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments()) |
| .containsExactly(DummyFragment.class); |
| } |
| |
| private static RuleClass newRuleClass( |
| String name, |
| boolean starlarkExecutable, |
| boolean documented, |
| boolean binaryOutput, |
| boolean workspaceOnly, |
| boolean outputsDefaultExecutable, |
| boolean isAnalysisTest, |
| ImplicitOutputsFunction implicitOutputsFunction, |
| TransitionFactory<RuleTransitionData> transitionFactory, |
| ConfiguredTargetFactory<?, ?, ?> configuredTargetFactory, |
| AdvertisedProviderSet advertisedProviders, |
| @Nullable StarlarkFunction configuredTargetFunction, |
| Set<Class<? extends Fragment>> allowedConfigurationFragments, |
| boolean supportsConstraintChecking, |
| Attribute... attributes) { |
| return new RuleClass( |
| name, |
| DUMMY_STACK, |
| /* key= */ name, |
| RuleClassType.NORMAL, |
| /* starlarkParent= */ null, |
| /* initializer= */ null, |
| /* labelConverterForInitializer= */ null, |
| /* isStarlark= */ starlarkExecutable, |
| /* starlarkExtensionLabel= */ null, |
| /* starlarkDocumentation= */ null, |
| /* extendable= */ false, |
| /* extendableAllowlist= */ null, |
| /* starlarkTestable= */ false, |
| documented, |
| binaryOutput, |
| workspaceOnly, |
| /* dependencyResolutionRule= */ false, |
| outputsDefaultExecutable, |
| isAnalysisTest, |
| /* hasAnalysisTestTransition= */ false, |
| /* allowlistCheckers= */ ImmutableList.of(), |
| /* ignoreLicenses= */ false, |
| implicitOutputsFunction, |
| transitionFactory, |
| configuredTargetFactory, |
| advertisedProviders, |
| configuredTargetFunction, |
| /* optionReferenceFunction= */ RuleClass.NO_OPTION_REFERENCE, |
| /* ruleDefinitionEnvironmentLabel= */ null, |
| /* ruleDefinitionEnvironmentDigest= */ null, |
| /* ruleDefinitionEnvironmentRepoMappingEntries= */ null, |
| new ConfigurationFragmentPolicy.Builder() |
| .requiresConfigurationFragments(allowedConfigurationFragments) |
| .build(), |
| supportsConstraintChecking, |
| /* toolchainTypes= */ ImmutableSet.of(), |
| /* toolchainResolutionMode= */ ToolchainResolutionMode.ENABLED, |
| /* executionPlatformConstraints= */ ImmutableSet.of(), |
| /* declaredExecGroups= */ ImmutableMap.of(), |
| AutoExecGroupsMode.DYNAMIC, |
| OutputFile.Kind.FILE, |
| attributes.length > 0 && attributes[0].equals(RuleClass.NAME_ATTRIBUTE) |
| ? ImmutableList.copyOf(attributes) |
| : ImmutableList.<Attribute>builder() |
| .add(RuleClass.NAME_ATTRIBUTE) |
| .add(attributes) |
| .build(), |
| /* buildSetting= */ null, |
| /* subrules= */ ImmutableList.of()); |
| } |
| |
| private static RuleClass createParentRuleClass() { |
| return newRuleClass( |
| "parent_rule", |
| false, |
| false, |
| false, |
| false, |
| false, |
| false, |
| SafeImplicitOutputsFunction.NONE, |
| null, |
| DUMMY_CONFIGURED_TARGET_FACTORY, |
| AdvertisedProviderSet.EMPTY, |
| null, |
| ImmutableSet.of(DummyFragment.class), |
| true, |
| attr("attr", STRING).build()); |
| } |
| |
| private static RuleClass createChildRuleClass(RuleClass parentRuleClass) { |
| RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder( |
| "child_rule", RuleClassType.NORMAL, false, parentRuleClass); |
| return childRuleClassBuilder.override( |
| childRuleClassBuilder |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .copy("attr").mandatory()) |
| .add(attr("tags", STRING_LIST)) |
| .build(); |
| } |
| |
| @Test |
| public void testBadRuleClassNames() { |
| expectError(RuleClassType.NORMAL, "8abc"); |
| expectError(RuleClassType.NORMAL, "!abc"); |
| expectError(RuleClassType.NORMAL, "a b"); |
| } |
| |
| private static void expectError(RuleClassType type, String name) { |
| assertThrows(IllegalArgumentException.class, () -> type.checkName(name)); |
| } |
| |
| @Test |
| public void testToolchainTypes() throws Exception { |
| RuleClass.Builder ruleClassBuilder = |
| new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)); |
| |
| ruleClassBuilder.addToolchainTypes( |
| ToolchainTypeRequirement.create(Label.parseCanonical("//toolchain:tc1")), |
| ToolchainTypeRequirement.create(Label.parseCanonical("//toolchain:tc2"))); |
| |
| RuleClass ruleClass = ruleClassBuilder.build(); |
| |
| assertThat(ruleClass).hasToolchainType("//toolchain:tc1"); |
| assertThat(ruleClass).hasToolchainType("//toolchain:tc2"); |
| } |
| |
| @Test |
| public void testExecutionPlatformConstraints() throws Exception { |
| RuleClass.Builder ruleClassBuilder = |
| new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)); |
| |
| ruleClassBuilder.addExecutionPlatformConstraints( |
| Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2")); |
| |
| RuleClass ruleClass = ruleClassBuilder.build(); |
| |
| assertThat(ruleClass.getExecutionPlatformConstraints()) |
| .containsExactly( |
| Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2")); |
| } |
| |
| @Test |
| public void testExecutionPlatformConstraints_inheritConstraintsFromParent() throws Exception { |
| RuleClass parentRuleClass = |
| new RuleClass.Builder("$parentRuleClass", RuleClassType.ABSTRACT, false) |
| .add(attr("tags", STRING_LIST)) |
| .addExecutionPlatformConstraints( |
| Label.parseCanonical("//constraints:cv1"), |
| Label.parseCanonical("//constraints:cv2")) |
| .build(); |
| |
| RuleClass childRuleClass = |
| new RuleClass.Builder("childRuleClass", RuleClassType.NORMAL, false, parentRuleClass) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .build(); |
| |
| assertThat(childRuleClass.getExecutionPlatformConstraints()) |
| .containsExactly( |
| Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2")); |
| } |
| |
| @Test |
| public void testExecutionPlatformConstraints_inheritAndAddConstraints() throws Exception { |
| RuleClass parentRuleClass = |
| new RuleClass.Builder("$parentRuleClass", RuleClassType.ABSTRACT, false) |
| .add(attr("tags", STRING_LIST)) |
| .build(); |
| |
| RuleClass.Builder childRuleClassBuilder = |
| new RuleClass.Builder("childRuleClass", RuleClassType.NORMAL, false, parentRuleClass) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .addExecutionPlatformConstraints( |
| Label.parseCanonical("//constraints:cv1"), |
| Label.parseCanonical("//constraints:cv2")); |
| |
| RuleClass childRuleClass = childRuleClassBuilder.build(); |
| |
| assertThat(childRuleClass.getExecutionPlatformConstraints()) |
| .containsExactly( |
| Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2")); |
| } |
| |
| @Test |
| public void testDeclaredExecGroups() { |
| RuleClass.Builder ruleClassBuilder = |
| new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)); |
| |
| Label toolchain = Label.parseCanonicalUnchecked("//toolchain"); |
| Label constraint = Label.parseCanonicalUnchecked("//constraint"); |
| |
| // TODO(https://github.com/bazelbuild/bazel/issues/14726): Add tests of optional toolchains. |
| ruleClassBuilder.addExecGroups( |
| ImmutableMap.of( |
| "cherry", |
| DeclaredExecGroup.builder() |
| .addToolchainType(ToolchainTypeRequirement.create(toolchain)) |
| .execCompatibleWith(ImmutableSet.of(constraint)) |
| .build())); |
| |
| RuleClass ruleClass = ruleClassBuilder.build(); |
| |
| assertThat(ruleClass.getDeclaredExecGroups()).hasSize(1); |
| assertThat(ruleClass.getDeclaredExecGroups().get("cherry")).hasToolchainType(toolchain); |
| assertThat(ruleClass.getDeclaredExecGroups().get("cherry")) |
| .toolchainType(toolchain) |
| .isMandatory(); |
| assertThat(ruleClass.getDeclaredExecGroups().get("cherry")).hasExecCompatibleWith(constraint); |
| } |
| |
| @Test |
| public void testBuildSetting_createsDefaultAttribute() { |
| RuleClass labelFlag = |
| new RuleClass.Builder("label_flag", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)) |
| .setBuildSetting(BuildSetting.create(true, NODEP_LABEL)) |
| .build(); |
| RuleClass stringSetting = |
| new RuleClass.Builder("string_setting", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)) |
| .setBuildSetting(BuildSetting.create(false, STRING)) |
| .build(); |
| |
| assertThat( |
| labelFlag |
| .getAttributeProvider() |
| .hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, NODEP_LABEL)) |
| .isTrue(); |
| assertThat( |
| stringSetting |
| .getAttributeProvider() |
| .hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, STRING)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void testBuildSetting_doesNotCreateDefaultAttributeIfNotBuildSetting() { |
| RuleClass stringSetting = |
| new RuleClass.Builder("non_build_setting", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)) |
| .build(); |
| |
| assertThat( |
| stringSetting |
| .getAttributeProvider() |
| .hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, LABEL)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void testBuildTooManyAttributesRejected() { |
| RuleClass.Builder builder = |
| new RuleClass.Builder("myclass", RuleClassType.NORMAL, /*starlark=*/ false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)); |
| for (int i = 0; i < 200; i++) { |
| builder.add(attr("attr" + i, STRING)); |
| } |
| |
| IllegalArgumentException expected = |
| assertThrows(IllegalArgumentException.class, builder::build); |
| |
| assertThat(expected) |
| .hasMessageThat() |
| .isEqualTo("Rule class myclass declared too many attributes (202 > 200)"); |
| } |
| |
| @Test |
| public void testBuildTooLongAttributeNameRejected() { |
| IllegalArgumentException expected = |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> |
| new RuleClass.Builder("myclass", RuleClassType.NORMAL, /*starlark=*/ false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)) |
| .add(attr("x".repeat(150), STRING)) |
| .build()); |
| |
| assertThat(expected) |
| .hasMessageThat() |
| .matches("Attribute myclass\\.x{150}'s name is too long \\(150 > 128\\)"); |
| } |
| |
| @Test |
| public void testPackageMetadataAlternateName() throws Exception { |
| RuleClass noopClass = |
| new RuleClass.Builder("noop", RuleClassType.NORMAL, false) |
| .factory(DUMMY_CONFIGURED_TARGET_FACTORY) |
| .add(attr("tags", STRING_LIST)) |
| .add(attr(RuleClass.APPLICABLE_METADATA_ATTR, LABEL_LIST).legacyAllowAnyFileType()) |
| .build(); |
| Map<String, Object> attributeValues = new LinkedHashMap<>(); |
| attributeValues.put("applicable_licenses", Lists.newArrayList(":info")); |
| Rule noopRule = createRule(noopClass, "noop", attributeValues); |
| |
| assertThat(noopRule.getAttr(RuleClass.APPLICABLE_METADATA_ATTR, LABEL_LIST)) |
| .isEqualTo(Lists.newArrayList(Label.parseCanonical("//testpackage:info"))); |
| } |
| } |