Rename Skylark to Starlark in src/test/java/com/google/devtools/build/lib/skylark
RELNOTES: None.
PiperOrigin-RevId: 324017771
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/BUILD b/src/test/java/com/google/devtools/build/lib/starlark/BUILD
new file mode 100644
index 0000000..bc25685
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/BUILD
@@ -0,0 +1,98 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(["**"]) + [
+ "//src/test/java/com/google/devtools/build/lib/starlark/util:srcs",
+ ],
+ visibility = ["//src:__subpackages__"],
+)
+
+java_test(
+ name = "StarlarkTests",
+ timeout = "long",
+ srcs = glob(["*.java"]),
+ shard_count = 25,
+ test_class = "com.google.devtools.build.lib.AllTests",
+ runtime_deps = [
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ ],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib:syntax",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/actions:action_lookup_key",
+ "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
+ "//src/main/java/com/google/devtools/build/lib/analysis:actions/parameter_file_write_action",
+ "//src/main/java/com/google/devtools/build/lib/analysis:actions/substitution",
+ "//src/main/java/com/google/devtools/build/lib/analysis:actions/template_expansion_action",
+ "//src/main/java/com/google/devtools/build/lib/analysis:actions_provider",
+ "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
+ "//src/main/java/com/google/devtools/build/lib/analysis:config/host_transition",
+ "//src/main/java/com/google/devtools/build/lib/analysis:config/starlark_defined_config_transition",
+ "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/no_transition",
+ "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+ "//src/main/java/com/google/devtools/build/lib/analysis:file_provider",
+ "//src/main/java/com/google/devtools/build/lib/analysis:resolved_toolchain_context",
+ "//src/main/java/com/google/devtools/build/lib/analysis:starlark/args",
+ "//src/main/java/com/google/devtools/build/lib/analysis:test/analysis_failure",
+ "//src/main/java/com/google/devtools/build/lib/analysis:test/analysis_failure_info",
+ "//src/main/java/com/google/devtools/build/lib/analysis:test/analysis_test_result_info",
+ "//src/main/java/com/google/devtools/build/lib/analysis:test/instrumented_files_info",
+ "//src/main/java/com/google/devtools/build/lib/analysis:toolchain_collection",
+ "//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_collection",
+ "//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+ "//src/main/java/com/google/devtools/build/lib/events",
+ "//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:build_type",
+ "//src/main/java/com/google/devtools/build/lib/packages:type",
+ "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+ "//src/main/java/com/google/devtools/build/lib/rules/java:java-compilation",
+ "//src/main/java/com/google/devtools/build/lib/rules/objc",
+ "//src/main/java/com/google/devtools/build/lib/rules/python",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:aspect_value_key",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
+ "//src/main/java/com/google/devtools/build/lib/util",
+ "//src/main/java/com/google/devtools/build/lib/util:classpath",
+ "//src/main/java/com/google/devtools/build/lib/util:filetype",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/main/java/net/starlark/java/annot",
+ "//src/test/java/com/google/devtools/build/lib/actions/util",
+ "//src/test/java/com/google/devtools/build/lib/analysis/util",
+ "//src/test/java/com/google/devtools/build/lib/packages:testutil",
+ "//src/test/java/com/google/devtools/build/lib/starlark/util",
+ "//src/test/java/com/google/devtools/build/lib/syntax/util",
+ "//src/test/java/com/google/devtools/build/lib/testutil",
+ "//src/test/java/com/google/devtools/build/lib/testutil:JunitUtils",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
+test_suite(
+ name = "windows_tests",
+ tags = [
+ "-no_windows",
+ "-slow",
+ ],
+)
+
+test_suite(
+ name = "all_windows_tests",
+ tests = [
+ ":windows_tests",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkActionProviderTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkActionProviderTest.java
new file mode 100644
index 0000000..15f6f76
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkActionProviderTest.java
@@ -0,0 +1,171 @@
+// Copyright 2018 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisResult;
+import com.google.devtools.build.lib.analysis.ConfiguredAspect;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.StarlarkProvider;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.syntax.Dict;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.Sequence;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the Starlark-accessible actions provider on rule configured targets. */
+@RunWith(JUnit4.class)
+public class StarlarkActionProviderTest extends AnalysisTestCase {
+
+ @Test
+ public void aspectGetsActionProviderForNativeRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "def _impl(target, ctx):",
+ " return [foo(actions = target.actions)]",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file(
+ "test/BUILD",
+ "genrule(",
+ " name = 'xxx',",
+ " cmd = 'echo \"hello\" > $@',",
+ " outs = ['mygen.out']",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ StarlarkProvider.Key fooKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+
+ StructImpl fooProvider = (StructImpl) configuredAspect.get(fooKey);
+ assertThat(fooProvider.getValue("actions")).isNotNull();
+ @SuppressWarnings("unchecked")
+ Sequence<ActionAnalysisMetadata> actions =
+ (Sequence<ActionAnalysisMetadata>) fooProvider.getValue("actions");
+ assertThat(actions).isNotEmpty();
+
+ ActionAnalysisMetadata action = actions.get(0);
+ assertThat(action.getMnemonic()).isEqualTo("Genrule");
+ assertThat(action).isInstanceOf(AbstractAction.class);
+ assertThat(((AbstractAction) action).getExecutionInfo()).isNotNull();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void aspectGetsActionProviderForStarlarkRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "def _impl(target, ctx):",
+ " mnemonics = [a.mnemonic for a in target.actions]",
+ " envs = [a.env for a in target.actions]",
+ " execution_info = [a.execution_info for a in target.actions]",
+ " inputs = [a.inputs.to_list() for a in target.actions]",
+ " outputs = [a.outputs.to_list() for a in target.actions]",
+ " argv = [a.argv for a in target.actions]",
+ " return [foo(",
+ " actions = target.actions,",
+ " mnemonics = mnemonics,",
+ " envs = envs,",
+ " execution_info = execution_info,",
+ " inputs = inputs,",
+ " outputs = outputs,",
+ " argv = argv",
+ " )]",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file(
+ "test/rule.bzl",
+ "def impl(ctx):",
+ " output_file0 = ctx.actions.declare_file('myfile0')",
+ " output_file1 = ctx.actions.declare_file('myfile1')",
+ " executable = ctx.actions.declare_file('executable')",
+ " ctx.actions.run(outputs=[output_file0], executable=executable,",
+ " mnemonic='MyAction0', env={'foo':'bar', 'pet':'puppy'})",
+ " ctx.actions.run_shell(outputs=[executable, output_file1],",
+ " command='fakecmd', mnemonic='MyAction1', env={'pet':'bunny'})",
+ " return None",
+ "my_rule = rule(impl)");
+ scratch.file(
+ "test/BUILD", "load('//test:rule.bzl', 'my_rule')", "my_rule(", " name = 'xxx',", ")");
+
+ useConfiguration("--experimental_google_legacy_api");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ StarlarkProvider.Key fooKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+ StructImpl fooProvider = (StructImpl) configuredAspect.get(fooKey);
+ assertThat(fooProvider.getValue("actions")).isNotNull();
+
+ Sequence<ActionAnalysisMetadata> actions =
+ (Sequence<ActionAnalysisMetadata>) fooProvider.getValue("actions");
+ assertThat(actions).hasSize(2);
+
+ Sequence<String> mnemonics = (Sequence<String>) fooProvider.getValue("mnemonics");
+ assertThat(mnemonics).containsExactly("MyAction0", "MyAction1");
+
+ Sequence<Dict<String, String>> envs =
+ (Sequence<Dict<String, String>>) fooProvider.getValue("envs");
+ assertThat(envs)
+ .containsExactly(
+ Dict.of((Mutability) null, "foo", "bar", "pet", "puppy"),
+ Dict.of((Mutability) null, "pet", "bunny"));
+
+ Sequence<Dict<String, String>> executionInfo =
+ (Sequence<Dict<String, String>>) fooProvider.getValue("execution_info");
+ assertThat(executionInfo).isNotNull();
+
+ Sequence<Sequence<Artifact>> inputs =
+ (Sequence<Sequence<Artifact>>) fooProvider.getValue("inputs");
+ assertThat(flattenArtifactNames(inputs)).containsExactly("executable");
+
+ Sequence<Sequence<Artifact>> outputs =
+ (Sequence<Sequence<Artifact>>) fooProvider.getValue("outputs");
+ assertThat(flattenArtifactNames(outputs)).containsExactly("myfile0", "executable", "myfile1");
+
+ Sequence<Sequence<String>> argv = (Sequence<Sequence<String>>) fooProvider.getValue("argv");
+ assertThat(argv.get(0)).hasSize(1);
+ assertThat(argv.get(0).get(0)).endsWith("executable");
+ assertThat(argv.get(1)).contains("fakecmd");
+ }
+
+ private static List<String> flattenArtifactNames(Sequence<Sequence<Artifact>> artifactLists) {
+ return artifactLists.stream()
+ .flatMap(artifacts -> artifacts.stream())
+ .map(artifact -> artifact.getFilename())
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkAnnotationContractTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkAnnotationContractTest.java
new file mode 100644
index 0000000..6bd9012
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkAnnotationContractTest.java
@@ -0,0 +1,84 @@
+// Copyright 2018 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.starlark;
+
+import com.google.devtools.build.lib.util.Classpath;
+import java.lang.reflect.Method;
+import net.starlark.java.annot.StarlarkBuiltin;
+import net.starlark.java.annot.StarlarkInterfaceUtils;
+import net.starlark.java.annot.StarlarkMethod;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that bazel usages of {@link StarlarkMethod} and {@link StarlarkBuiltin} abide by the
+ * contracts specified in their documentation.
+ *
+ * <p>Tests in this class use the java reflection API.
+ *
+ * <p>This verification *would* be done via annotation processor, but annotation processors in java
+ * don't have access to the full set of information that the java reflection API has.
+ */
+@RunWith(JUnit4.class)
+public class StarlarkAnnotationContractTest {
+
+ // Common prefix of packages in bazel that may have classes that implement or extend a
+ // Starlark type.
+ private static final String MODULES_PACKAGE_PREFIX = "com/google/devtools/build";
+
+ /**
+ * Verifies that every class in bazel that implements or extends a Starlark type has a clearly
+ * resolvable type.
+ *
+ * <p>If this test fails, it indicates the following error scenario:
+ *
+ * <p>Suppose class A is a subclass of both B and C, where B and C are annotated with {@link
+ * StarlarkBuiltin} annotations (and are thus considered "Starlark types"). If B is not a subclass
+ * of C (nor visa versa), then it's impossible to resolve whether A is of type B or if A is of
+ * type C. It's both! The way to resolve this is usually to have A be its own type (annotated with
+ * {@link StarlarkBuiltin}), and thus have the explicit type of A be semantically "B and C".
+ */
+ @Test
+ public void testResolvableStarlarkBuiltins() throws Exception {
+ for (Class<?> candidateClass : Classpath.findClasses(MODULES_PACKAGE_PREFIX)) {
+ StarlarkInterfaceUtils.getStarlarkBuiltin(candidateClass);
+ }
+ }
+
+ /**
+ * Verifies that no class or interface has a method annotated with {@link StarlarkMethod} unless
+ * that class or interface is annotated with either {@link StarlarkGlobalLibrary} or with {@link
+ * StarlarkBuiltin}.
+ */
+ @Test
+ public void testStarlarkCallableScope() throws Exception {
+ for (Class<?> candidateClass : Classpath.findClasses(MODULES_PACKAGE_PREFIX)) {
+ if (StarlarkInterfaceUtils.getStarlarkBuiltin(candidateClass) == null
+ && !StarlarkInterfaceUtils.hasStarlarkGlobalLibrary(candidateClass)) {
+ for (Method method : candidateClass.getMethods()) {
+ StarlarkMethod callable = StarlarkInterfaceUtils.getStarlarkMethod(method);
+ if (callable != null && method.getDeclaringClass() == candidateClass) {
+ throw new AssertionError(
+ String.format(
+ "Class %s has a StarlarkMethod method %s but is neither a @StarlarkBuiltin"
+ + " nor a @StarlarkGlobalLibrary",
+ candidateClass, method.getName()));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
new file mode 100644
index 0000000..cd963d6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
@@ -0,0 +1,3120 @@
+// 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.devtools.build.lib.analysis.OutputGroupInfo.INTERNAL_SUFFIX;
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.AnalysisResult;
+import com.google.devtools.build.lib.analysis.AspectValue;
+import com.google.devtools.build.lib.analysis.ConfiguredAspect;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.OutputGroupInfo;
+import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
+import com.google.devtools.build.lib.analysis.config.HostTransition;
+import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
+import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.AspectDefinition;
+import com.google.devtools.build.lib.packages.StarlarkProvider;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.packages.util.MockObjcSupport;
+import com.google.devtools.build.lib.packages.util.MockProtoSupport;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
+import com.google.devtools.build.lib.rules.java.JavaConfiguration;
+import com.google.devtools.build.lib.rules.objc.ObjcProtoProvider;
+import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
+import com.google.devtools.build.lib.syntax.Sequence;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for Starlark aspects */
+@RunWith(JUnit4.class)
+public class StarlarkDefinedAspectsTest extends AnalysisTestCase {
+ protected boolean keepGoing() {
+ return false;
+ }
+
+ private static final String LINE_SEPARATOR = System.lineSeparator();
+
+ @Before
+ public final void initializeToolsConfigMock() throws Exception {
+ // Required for tests including the objc_library rule.
+ MockObjcSupport.setup(mockToolsConfig);
+ // Required for tests including the proto_library rule.
+ MockProtoSupport.setup(mockToolsConfig);
+ }
+
+ @Test
+ public void simpleAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ @Test
+ public void aspectWithSingleDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "def _impl(target, ctx):",
+ " return foo()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ StarlarkProvider.Key fooKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+
+ assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+ }
+
+ @Test
+ public void aspectWithDeclaredProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "bar = provider()",
+ "def _impl(target, ctx):",
+ " return [foo(), bar()]",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ StarlarkProvider.Key fooKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+ StarlarkProvider.Key barKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "bar");
+
+ assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+ assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
+ }
+
+ @Test
+ public void aspectWithDeclaredProvidersInAStruct() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "bar = provider()",
+ "def _impl(target, ctx):",
+ " return struct(foobar='foobar', providers=[foo(), bar()])",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ StarlarkProvider.Key fooKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+ StarlarkProvider.Key barKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "bar");
+
+ assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+ assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
+ }
+
+ private Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) {
+ return Iterables.transform(
+ analysisResult.getAspectsMap().keySet(),
+ aspectKey ->
+ String.format("%s(%s)", aspectKey.getAspectClass().getName(), aspectKey.getLabel()));
+ }
+
+ @Test
+ public void aspectCommandLineLabel() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ @Test
+ public void aspectCommandLineRepoLabel() throws Exception {
+ scratch.overwriteFile(
+ "WORKSPACE",
+ scratch.readFile("WORKSPACE"),
+ "local_repository(name='local', path='local/repo')");
+ scratch.file("local/repo/WORKSPACE");
+ scratch.file(
+ "local/repo/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("local/repo/BUILD");
+
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("@local//:aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("@local//:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ private Iterable<String> getLabelsToBuild(AnalysisResult analysisResult) {
+ return Iterables.transform(
+ analysisResult.getTargetsToBuild(),
+ configuredTarget -> configuredTarget.getLabel().toString());
+ }
+
+ @Test
+ public void aspectAllowsFragmentsToBeSpecified() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl, fragments=['java'], host_fragments=['cpp'])");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+
+ AspectKey key = Iterables.getOnlyElement(analysisResult.getAspectsMap().keySet());
+ AspectValue aspectValue =
+ (AspectValue) skyframeExecutor.getEvaluatorForTesting().getExistingValue(key);
+ AspectDefinition aspectDefinition = aspectValue.getAspect().getDefinition();
+ assertThat(
+ aspectDefinition
+ .getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(JavaConfiguration.class, NoTransition.INSTANCE))
+ .isTrue();
+ assertThat(
+ aspectDefinition
+ .getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(JavaConfiguration.class, HostTransition.INSTANCE))
+ .isFalse();
+ assertThat(
+ aspectDefinition
+ .getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(CppConfiguration.class, NoTransition.INSTANCE))
+ .isFalse();
+ assertThat(
+ aspectDefinition
+ .getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(CppConfiguration.class, HostTransition.INSTANCE))
+ .isTrue();
+ }
+
+ @Test
+ public void aspectPropagating() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = depset([target.label], transitive = [i.target_labels for i in ctx.rule.attr.deps])",
+ " c = depset([ctx.rule.kind], transitive = [i.rule_kinds for i in ctx.rule.attr.deps])",
+ " return struct(target_labels = s, rule_kinds = c)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = [':yyy'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ assertThat(configuredAspect).isNotNull();
+ Object names = configuredAspect.get("target_labels");
+ assertThat(names).isInstanceOf(Depset.class);
+ assertThat(
+ Iterables.transform(
+ ((Depset) names).toList(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return o.toString();
+ }))
+ .containsExactly("//test:xxx", "//test:yyy");
+ Object ruleKinds = configuredAspect.get("rule_kinds");
+ assertThat(ruleKinds).isInstanceOf(Depset.class);
+ assertThat(((Depset) ruleKinds).toList()).containsExactly("java_library");
+ }
+
+ @Test
+ public void aspectsPropagatingForDefaultAndImplicit() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = []",
+ " c = []",
+ " a = ctx.rule.attr",
+ " if getattr(a, '_defaultattr', None):",
+ " s += [a._defaultattr.target_labels]",
+ " c += [a._defaultattr.rule_kinds]",
+ " if getattr(a, '_cc_toolchain', None):",
+ " s += [a._cc_toolchain.target_labels]",
+ " c += [a._cc_toolchain.rule_kinds]",
+ " return struct(",
+ " target_labels = depset([target.label], transitive = s),",
+ " rule_kinds = depset([ctx.rule.kind], transitive = c))",
+ "",
+ "def _rule_impl(ctx):",
+ " pass",
+ "",
+ "my_rule = rule(implementation = _rule_impl,",
+ " attrs = { '_defaultattr' : attr.label(default = Label('//test:xxx')) },",
+ ")",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['_defaultattr', '_cc_toolchain'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "cc_library(",
+ " name = 'xxx',",
+ ")",
+ "my_rule(",
+ " name = 'yyy',",
+ ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:yyy");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ assertThat(configuredAspect).isNotNull();
+ Object nameSet = configuredAspect.get("target_labels");
+ ImmutableList<String> names =
+ ImmutableList.copyOf(
+ Iterables.transform(
+ ((Depset) nameSet).toList(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return ((Label) o).getName();
+ }));
+
+ assertThat(names).containsAtLeast("xxx", "yyy");
+ // Third is the C++ toolchain; its name changes between Blaze and Bazel.
+ assertThat(names).hasSize(3);
+ }
+
+ @Test
+ public void aspectsDirOnMergedTargets() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct(aspect_provider = 'data')",
+ "",
+ "p = provider()",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " if ctx.attr.dep:",
+ " return [p(dir = dir(ctx.attr.dep))]",
+ " return [p()]",
+ "",
+ "my_rule = rule(implementation = _rule_impl,",
+ " attrs = { 'dep' : attr.label(aspects = [MyAspect]) },",
+ ")");
+ StarlarkProvider.Key providerKey =
+ new StarlarkProvider.Key(Label.parseAbsoluteUnchecked("//test:aspect.bzl"), "p");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx',)",
+ "my_rule(name = 'yyy', dep = ':xxx')");
+ AnalysisResult analysisResult = update("//test:yyy");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+
+ StructImpl names = (StructImpl) target.get(providerKey);
+ assertThat((Iterable<?>) names.getValue("dir"))
+ .containsExactly(
+ "actions",
+ "aspect_provider",
+ "data_runfiles",
+ "default_runfiles",
+ "files",
+ "files_to_run",
+ "label",
+ "output_groups");
+ }
+
+ @Test
+ public void aspectWithOutputGroupsDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " f = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " return [OutputGroupInfo(my_result = f)]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD", "java_library(", " name = 'xxx',", " srcs = ['A.java'],", ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(configuredAspect);
+
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names.toList()).isNotEmpty();
+ NestedSet<Artifact> expectedSet =
+ OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names.toList()).containsExactlyElementsIn(expectedSet.toList());
+ }
+
+ @Test
+ public void aspectWithOutputGroupsAsListDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " g = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " return [OutputGroupInfo(my_result=g.to_list())]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD", "java_library(", " name = 'xxx',", " srcs = ['A.java'],", ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(
+ Iterables.transform(
+ analysisResult.getTargetsToBuild(),
+ configuredTarget -> configuredTarget.getLabel().toString()))
+ .containsExactly("//test:xxx");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(configuredAspect);
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names.toList()).isNotEmpty();
+ NestedSet<Artifact> expectedSet =
+ OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names.toList()).containsExactlyElementsIn(expectedSet.toList());
+ }
+
+ @Test
+ public void aspectsFromStarlarkRules() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " s = depset([target.label], transitive = [i.target_labels for i in ctx.rule.attr.deps])",
+ " return struct(target_labels = s)",
+ "",
+ "def _rule_impl(ctx):",
+ " s = depset(transitive = [i.target_labels for i in ctx.attr.attr])",
+ " return struct(rule_deps = s)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ " attr_aspects=['deps'],",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object names = target.get("rule_deps");
+ assertThat(names).isInstanceOf(Depset.class);
+ assertThat(
+ Iterables.transform(
+ ((Depset) names).toList(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return o.toString();
+ }))
+ .containsExactly("//test:yyy");
+ }
+
+ @Test
+ public void aspectsNonExported() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return []",
+ "",
+ "def _rule_impl(ctx):",
+ " pass",
+ "",
+ "def mk_aspect():",
+ " return aspect(implementation=_aspect_impl)",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : attr.label_list(aspects = [mk_aspect()]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException | TargetParsingException e) {
+ // expected
+ }
+
+ assertContainsEvent("ERROR /workspace/test/aspect.bzl:11:38");
+ assertContainsEvent("Aspects should be top-level values in extension files that define them.");
+ }
+
+ @Test
+ public void providerNonExported() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def mk_provider():",
+ " return provider()",
+ "def _rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : attr.label_list(providers = [mk_provider()]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException | TargetParsingException e) {
+ // expected
+ }
+
+ assertContainsEvent("ERROR /workspace/test/rule.bzl:7:38");
+ assertContainsEvent(
+ "Providers should be top-level values in extension files that define them.");
+ }
+
+ @Test
+ public void aspectOnLabelAttr() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(aspect_data='foo')",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(data=ctx.attr.attr.aspect_data)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label(aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = ':yyy',",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object value = target.get("data");
+ assertThat(value).isEqualTo("foo");
+ }
+
+ @Test
+ public void labelKeyedStringDictAllowsAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(aspect_data=target.label.name)",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(",
+ " data=','.join(['{}:{}'.format(dep.aspect_data, val)",
+ " for dep, val in ctx.attr.attr.items()]))",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_keyed_string_dict(aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = {':yyy': 'zzz'},",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object value = target.get("data");
+ assertThat(value).isEqualTo("yyy:zzz");
+ }
+
+ @Test
+ public void aspectsDoNotAttachToFiles() throws Exception {
+ FileSystemUtils.appendIsoLatin1(
+ scratch.resolve("WORKSPACE"), "bind(name = 'yyy', actual = '//test:zzz.jar')");
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file("test/zzz.jar");
+ scratch.file(
+ "test/BUILD",
+ "exports_files(['zzz.jar'])",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = ['//external:yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectsDoNotAttachToTopLevelFiles() throws Exception {
+ FileSystemUtils.appendIsoLatin1(
+ scratch.resolve("WORKSPACE"), "bind(name = 'yyy', actual = '//test:zzz.jar')");
+ scratch.file(
+ "test/aspect.bzl",
+ "p = provider()",
+ "def _impl(target, ctx):",
+ " return [p()]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file("test/zzz.jar");
+ scratch.file(
+ "test/BUILD",
+ "exports_files(['zzz.jar'])",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = ['//external:yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:zzz.jar");
+ assertThat(result.hasError()).isFalse();
+ assertThat(
+ Iterables.getOnlyElement(result.getAspectsMap().values())
+ .getProviders()
+ .getProviderCount())
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void aspectFailingExecution() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return 1 // 0",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:1:13: in "
+ + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
+ + "Traceback (most recent call last):"
+ + LINE_SEPARATOR
+ + "\tFile \"/workspace/test/BUILD\", line 1"
+ + LINE_SEPARATOR
+ + "\t\t//test:aspect.bzl%MyAspect(...)"
+ + LINE_SEPARATOR
+ + "\tFile \"/workspace/test/aspect.bzl\", line 2, in _impl"
+ + LINE_SEPARATOR
+ + "\t\t1 // 0"
+ + LINE_SEPARATOR
+ + "integer division by zero");
+ }
+
+ @Test
+ public void aspectFailingReturnsNotAStruct() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return 0",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "Aspect implementation should return a struct, a list, or a provider "
+ + "instance, but got int");
+ }
+
+ @Test
+ public void aspectFailingOrphanArtifacts() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " ctx.actions.declare_file('missing_in_action.txt')",
+ " return struct()",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:1:13: in "
+ + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
+ + "\n"
+ + "\n"
+ + "The following files have no generating action:\n"
+ + "test/missing_in_action.txt\n");
+ }
+
+ @Test
+ public void aspectSkippingOrphanArtifactsWithLocation() throws Exception {
+ scratch.file(
+ "simple/print.bzl",
+ "def _print_expanded_location_impl(target, ctx):",
+ " return struct(result=ctx.expand_location(ctx.rule.attr.cmd, []))",
+ "",
+ "print_expanded_location = aspect(",
+ " implementation = _print_expanded_location_impl,",
+ ")");
+ scratch.file(
+ "simple/BUILD",
+ "filegroup(",
+ " name = \"files\",",
+ " srcs = [\"afile\"],",
+ ")",
+ "",
+ "genrule(",
+ " name = \"concat_all_files\",",
+ " srcs = [\":files\"],",
+ " outs = [\"concatenated.txt\"],",
+ " cmd = \"$(location :files)\"",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ AnalysisResult analysisResult =
+ update(
+ ImmutableList.of("//simple:print.bzl%print_expanded_location"),
+ "//simple:concat_all_files");
+ assertThat(analysisResult.hasError()).isFalse();
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ String result = (String) configuredAspect.get("result");
+
+ assertThat(result).isEqualTo("simple/afile");
+ }
+
+ @Test
+ public void topLevelAspectIsNotAnAspect() throws Exception {
+ scratch.file("test/aspect.bzl", "MyAspect = 4");
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("MyAspect from //test:aspect.bzl is not an aspect");
+ }
+
+ @Test
+ public void duplicateOutputGroups() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " f = ctx.actions.declare_file('f.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'duplicate' : depset([f]) })",
+ "",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " g = ctx.actions.declare_file('g.txt')",
+ " ctx.actions.write(g, 'g')",
+ " return struct(output_groups = { 'duplicate' : depset([g]) })",
+ "my_rule = rule(_rule_impl)",
+ "def _noop(ctx):",
+ " pass",
+ "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule', 'rbase')",
+ "my_rule(name = 'xxx')",
+ "rbase(name = 'yyy', dep = ':xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update("//test:yyy");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:6: Output group duplicate provided twice");
+ }
+
+ @Test
+ public void outputGroupsFromOneAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ }
+
+ @Test
+ public void outputGroupsDeclaredProviderFromOneAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return [OutputGroupInfo(a1_group = depset([f]))]",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " return [OutputGroupInfo(a1_group = ctx.attr.dep[OutputGroupInfo].a1_group)]",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ }
+
+ @Test
+ public void outputGroupsFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return struct(output_groups = { 'a2_group' : depset([g]) })",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')");
+
+ AnalysisResult analysisResult = update("//test:yyy");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
+ .containsExactly("test/xxx_a2.txt");
+ }
+
+ @Test
+ public void outputGroupsDeclaredProvidersFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return [OutputGroupInfo(a1_group = depset([f]))]",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = dict()",
+ " dep_og = ctx.attr.dep[OutputGroupInfo]",
+ " if hasattr(dep_og, 'a1_group'):",
+ " og['a1_group'] = dep_og.a1_group",
+ " if hasattr(dep_og, 'a2_group'):",
+ " og['a2_group'] = dep_og.a2_group",
+ " return [OutputGroupInfo(**og)]",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return [OutputGroupInfo(a2_group = depset([g]))]",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')");
+
+ AnalysisResult analysisResult = update("//test:yyy");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
+ .containsExactly("test/xxx_a2.txt");
+ }
+
+ @Test
+ public void duplicateOutputGroupsFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([g]) })",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:yyy");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:9: Output group a1_group provided twice");
+ }
+
+ private static Iterable<String> getOutputGroupContents(
+ OutputGroupInfo outputGroupInfo, String groupName) {
+ return Iterables.transform(
+ outputGroupInfo.getOutputGroup(groupName).toList(), Artifact::getRootRelativePathString);
+ }
+
+ @Test
+ public void duplicateStarlarkProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct(duplicate = 'x')",
+ "",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " return struct(duplicate = 'y')",
+ "my_rule = rule(_rule_impl)",
+ "def _noop(ctx):",
+ " pass",
+ "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule', 'rbase')",
+ "my_rule(name = 'xxx')",
+ "rbase(name = 'yyy', dep = ':xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update("//test:yyy");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:6: Provider duplicate provided twice");
+ }
+
+ @Test
+ public void topLevelAspectDoesNotExist() throws Exception {
+ scratch.file("test/aspect.bzl", "");
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("MyAspect is not exported from //test:aspect.bzl");
+ }
+
+ @Test
+ public void topLevelAspectDoesNotExist2() throws Exception {
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("cannot load '//test:aspect.bzl': no such file");
+ }
+
+ @Test
+ public void topLevelAspectDoesNotExistNoBuildFile() throws Exception {
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("foo/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("Every .bzl file must have a corresponding package");
+ }
+
+ @Test
+ public void aspectParametersUncovered() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent( // "ERROR /workspace/test/aspect.bzl:9:11: "
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type string.");
+ }
+
+ @Test
+ public void aspectParametersTypeMismatch() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectMismatch = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectMismatch]),",
+ " 'my_attr' : attr.int() },",
+ ")");
+ scratch.file(
+ "test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx', my_attr = 4)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectMismatch requires rule my_rule to specify attribute "
+ + "'my_attr' with type string.");
+ }
+
+ @Test
+ public void aspectParametersDontSupportSelect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectMismatch = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectMismatch]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = select({'//conditions:default': 'foo'}))");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "//test:xxx: attribute 'my_attr' has a select() and aspect "
+ + "//test:aspect.bzl%MyAspectMismatch also declares '//test:xxx'. Aspect attributes "
+ + "don't currently support select().");
+ }
+
+ @Test
+ public void aspectParametersBadDefault() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectBadDefault = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a'], default='b') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadDefault]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/aspect.bzl:5:28: "
+ + "Aspect parameter attribute 'my_attr' has a bad default value: has to be one of 'a' "
+ + "instead of 'b'");
+ }
+
+ @Test
+ public void aspectParametersBadValue() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectBadValue = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadValue]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file(
+ "test/BUILD", //
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr='b')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:2:8: //test:xxx: invalid value in 'my_attr' "
+ + "attribute: has to be one of 'a' instead of 'b'");
+ }
+
+ @Test
+ public void aspectParameters() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspect]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = 'aaa')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectParametersConfigurationField() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_my_attr' : attr.label(default=",
+ " configuration_field(fragment='cpp', name = 'cc_toolchain')) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspect]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectParameterComputedDefault() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "def _defattr():",
+ " return Label('//foo/bar:baz')",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_extra' : attr.label(default = _defattr) }",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspect]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
+ reporter.removeHandler(failFastHandler);
+
+ if (keepGoing()) {
+ AnalysisResult result = update("//test:xxx");
+ assertThat(result.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update("//test:xxx"));
+ }
+ assertContainsEvent(
+ "Aspect attribute '_extra' (label) with computed default value is unsupported.");
+ }
+
+ @Test
+ public void aspectParametersOptional() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectOptParam = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa'], default='aaa') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptParam]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name = 'xxx')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectParametersOptionalOverride() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " if (ctx.attr.my_attr == 'a'):",
+ " fail('Rule is not overriding default, still has value ' + ctx.attr.my_attr)",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectOptOverride = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a', 'b'], default='a') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptOverride]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = 'b')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void testMultipleExecutablesInTarget() throws Exception {
+ scratch.file(
+ "foo/extension.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_aspect_impl)",
+ "def _main_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(_main_rule_impl,",
+ " attrs = { ",
+ " 'exe1' : attr.label(executable = True, allow_files = True, cfg = 'host'),",
+ " 'exe2' : attr.label(executable = True, allow_files = True, cfg = 'host'),",
+ " },",
+ ")");
+
+ scratch.file("foo/tool.sh", "#!/bin/bash");
+ scratch.file(
+ "foo/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'main', exe1 = ':tool.sh', exe2 = ':tool.sh')");
+ AnalysisResult analysisResultOfRule = update(ImmutableList.<String>of(), "//foo:main");
+ assertThat(analysisResultOfRule.hasError()).isFalse();
+
+ AnalysisResult analysisResultOfAspect =
+ update(ImmutableList.<String>of("/foo/extension.bzl%my_aspect"), "//foo:main");
+ assertThat(analysisResultOfAspect.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectFragmentAccessSuccess() throws Exception {
+ getConfiguredTargetForAspectFragment(
+ "ctx.fragments.java.strict_java_deps", "'java'", "", "", "");
+ assertNoEvents();
+ }
+
+ @Test
+ public void aspectHostFragmentAccessSuccess() throws Exception {
+ getConfiguredTargetForAspectFragment(
+ "ctx.host_fragments.java.strict_java_deps", "", "'java'", "", "");
+ assertNoEvents();
+ }
+
+ @Test
+ public void aspectFragmentAccessError() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ assertThrows(
+ ViewCreationFailedException.class,
+ () ->
+ getConfiguredTargetForAspectFragment(
+ "ctx.fragments.java.strict_java_deps", "'cpp'", "'java'", "'java'", ""));
+ assertContainsEvent(
+ "//test:aspect.bzl%MyAspect aspect on my_rule has to declare 'java' as a "
+ + "required fragment in target configuration in order to access it. Please update the "
+ + "'fragments' argument of the rule definition "
+ + "(for example: fragments = [\"java\"])");
+ }
+
+ @Test
+ public void aspectHostFragmentAccessError() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ assertThrows(
+ ViewCreationFailedException.class,
+ () ->
+ getConfiguredTargetForAspectFragment(
+ "ctx.host_fragments.java.java_strict_deps", "'java'", "'cpp'", "", "'java'"));
+ assertContainsEvent(
+ "//test:aspect.bzl%MyAspect aspect on my_rule has to declare 'java' as a "
+ + "required fragment in host configuration in order to access it. Please update the "
+ + "'host_fragments' argument of the rule definition "
+ + "(for example: host_fragments = [\"java\"])");
+ }
+
+ private ConfiguredTarget getConfiguredTargetForAspectFragment(
+ String fullFieldName,
+ String fragments,
+ String hostFragments,
+ String ruleFragments,
+ String ruleHostFragments)
+ throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(result = str(" + fullFieldName + "))",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(stuff = '...')",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ " attr_aspects=['deps'],",
+ " fragments=[" + fragments + "],",
+ " host_fragments=[" + hostFragments + "],",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
+ " fragments=[" + ruleFragments + "],",
+ " host_fragments=[" + ruleHostFragments + "],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "exports_files(['zzz'])",
+ "my_rule(",
+ " name = 'yyy',",
+ " attr = ['zzz'],",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = ['yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ if (result.hasError()) {
+ assertThat(keepGoing()).isTrue();
+ throw new ViewCreationFailedException("Analysis failed");
+ }
+
+ return getConfiguredTarget("//test:xxx");
+ }
+
+ @Test
+ public void invalidateAspectOnBzlFileChange() throws Exception {
+ scratch.file("test/build_defs.bzl", aspectBzlFile("'deps'"));
+ scratch.file(
+ "test/BUILD",
+ "load(':build_defs.bzl', 'repro', 'repro_no_aspect')",
+ "repro_no_aspect(name = 'r0')",
+ "repro_no_aspect(name = 'r1', deps = [':r0'])",
+ "repro(name = 'r2', deps = [':r1'])");
+ buildTargetAndCheckRuleInfo("//test:r0", "//test:r1");
+
+ // Make aspect propagation list empty.
+ scratch.overwriteFile("test/build_defs.bzl", aspectBzlFile(""));
+
+ // The aspect should not propagate to //test:r0 anymore.
+ buildTargetAndCheckRuleInfo("//test:r1");
+ }
+
+ private void buildTargetAndCheckRuleInfo(String... expectedLabels) throws Exception {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:r2");
+ ConfiguredTarget configuredTarget = result.getTargetsToBuild().iterator().next();
+ Depset ruleInfoValue = (Depset) configuredTarget.get("rule_info");
+ assertThat(ruleInfoValue.getSet(String.class).toList())
+ .containsExactlyElementsIn(expectedLabels);
+ }
+
+ private String[] aspectBzlFile(String attrAspects) {
+ return new String[] {
+ "def _repro_aspect_impl(target, ctx):",
+ " s = depset([str(target.label)], transitive =",
+ " [d.aspect_info for d in ctx.rule.attr.deps if hasattr(d, 'aspect_info')])",
+ " return struct(aspect_info = s)",
+ "",
+ "_repro_aspect = aspect(",
+ " _repro_aspect_impl,",
+ " attr_aspects = [" + attrAspects + "],",
+ ")",
+ "",
+ "def repro_impl(ctx):",
+ " s = depset(transitive = ",
+ " [d.aspect_info for d in ctx.attr.deps if hasattr(d, 'aspect_info')])",
+ " return struct(rule_info = s)",
+ "",
+ "def repro_no_aspect_impl(ctx):",
+ " pass",
+ "",
+ "repro_no_aspect = rule(implementation = repro_no_aspect_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(",
+ " allow_files = True,",
+ " )",
+ " },",
+ ")",
+ "",
+ "repro = rule(implementation = repro_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(",
+ " allow_files = True,",
+ " aspects = [_repro_aspect],",
+ " )",
+ " },",
+ ")"
+ };
+ }
+
+ @Test
+ public void aspectOutputsToBinDirectory() throws Exception {
+ scratch.file(
+ "foo/extension.bzl",
+ "def _aspect_impl(target, ctx):",
+ " file = ctx.actions.declare_file('aspect-output-' + target.label.name)",
+ " ctx.actions.write(file, 'data')",
+ " return struct(aspect_file = file)",
+ "my_aspect = aspect(_aspect_impl)",
+ "def _rule_impl(ctx):",
+ " pass",
+ "rule_bin_out = rule(_rule_impl, output_to_genfiles=False)",
+ "rule_gen_out = rule(_rule_impl, output_to_genfiles=True)",
+ "def _main_rule_impl(ctx):",
+ " s = depset([d.aspect_file for d in ctx.attr.deps])",
+ " return struct(aspect_files = s)",
+ "main_rule = rule(_main_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects = [my_aspect]) },",
+ ")");
+
+ scratch.file(
+ "foo/BUILD",
+ "load(':extension.bzl', 'rule_bin_out', 'rule_gen_out', 'main_rule')",
+ "rule_bin_out(name = 'rbin')",
+ "rule_gen_out(name = 'rgen')",
+ "main_rule(name = 'main', deps = [':rbin', ':rgen'])");
+ AnalysisResult analysisResult = update(ImmutableList.<String>of(), "//foo:main");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ NestedSet<Artifact> aspectFiles = ((Depset) target.get("aspect_files")).getSet(Artifact.class);
+ assertThat(Iterables.transform(aspectFiles.toList(), Artifact::getFilename))
+ .containsExactly("aspect-output-rbin", "aspect-output-rgen");
+ for (Artifact aspectFile : aspectFiles.toList()) {
+ String rootPath = aspectFile.getRoot().getExecPath().toString();
+ assertWithMessage("Artifact %s should not be in genfiles", aspectFile)
+ .that(rootPath)
+ .doesNotContain("genfiles");
+ assertWithMessage("Artifact %s should be in bin", aspectFile).that(rootPath).endsWith("bin");
+ }
+ }
+
+ @Test
+ public void toplevelAspectOnFile() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "exports_files(['file.txt'])");
+ scratch.file("test/file.txt", "");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:file.txt");
+ assertThat(analysisResult.hasError()).isFalse();
+ assertThat(
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values())
+ .getProviders()
+ .getProviderCount())
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void sharedAttributeDefinitionWithAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)",
+ "_ATTR = { 'deps' : attr.label_list(aspects = [my_aspect]) }",
+ "def _dummy_impl(ctx):",
+ " pass",
+ "r1 = rule(_dummy_impl, attrs = _ATTR)",
+ "r2 = rule(_dummy_impl, attrs = _ATTR)");
+
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 't1')",
+ "r2(name = 't2', deps = [':t1'])");
+ AnalysisResult analysisResult = update("//test:t2");
+ assertThat(analysisResult.hasError()).isFalse();
+ }
+
+ @Test
+ public void multipleAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)",
+ "def _dummy_impl(ctx):",
+ " pass",
+ "r1 = rule(_dummy_impl, ",
+ " attrs = { 'deps' : attr.label_list(aspects = [my_aspect, my_aspect]) })");
+
+ scratch.file("test/BUILD", "load(':aspect.bzl', 'r1')", "r1(name = 't1')");
+ reporter.removeHandler(failFastHandler);
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a TargetParsingException.
+ if (keepGoing()) {
+ AnalysisResult result = update("//test:r1");
+ assertThat(result.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update("//test:r1"));
+ }
+ assertContainsEvent("aspect //test:aspect.bzl%my_aspect added more than once");
+ }
+
+ @Test
+ public void topLevelAspectsAndExtraActions() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " f = ctx.actions.declare_file('dummy.txt')",
+ " ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
+ " mnemonic='AspectAction')",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)");
+ scratch.file(
+ "test/BUILD",
+ "extra_action(",
+ " name = 'xa',",
+ " cmd = 'echo $(EXTRA_ACTION_FILE) > $(output file.xa)',",
+ " out_templates = ['file.xa'],",
+ ")",
+ "action_listener(",
+ " name = 'al',",
+ " mnemonics = [ 'AspectAction' ],",
+ " extra_actions = [ ':xa' ])",
+ "java_library(name = 'xxx')");
+ useConfiguration("--experimental_action_listener=//test:al");
+ AnalysisResult analysisResult =
+ update(ImmutableList.<String>of("test/aspect.bzl%my_aspect"), "//test:xxx");
+ assertThat(
+ Iterables.transform(
+ analysisResult.getTopLevelArtifactsToOwnerLabels().getArtifacts(),
+ Artifact::getFilename))
+ .contains("file.xa");
+ }
+
+ /** Regression test for b/137960630. */
+ @Test
+ public void topLevelAspectsAndExtraActionsWithConflict() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " f = ctx.actions.declare_file('dummy.txt')",
+ " ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
+ " mnemonic='AspectAction')",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)");
+ scratch.file(
+ "test/BUILD",
+ "extra_action(",
+ " name = 'xa',",
+ " cmd = 'echo $(EXTRA_ACTION_FILE) > $(output file.xa)',",
+ " out_templates = ['file.xa'],",
+ ")",
+ "action_listener(",
+ " name = 'al',",
+ " mnemonics = ['AspectAction'],",
+ " extra_actions = [':xa'],",
+ ")",
+ "java_library(name = 'xxx')",
+ "java_library(name = 'yyy')");
+ useConfiguration("--experimental_action_listener=//test:al");
+ reporter.removeHandler(failFastHandler); // We expect an error.
+
+ if (keepGoing()) {
+ AnalysisResult result =
+ update(ImmutableList.of("test/aspect.bzl%my_aspect"), "//test:xxx", "//test:yyy");
+ assertThat(result.hasError()).isTrue();
+ } else {
+ assertThrows(
+ ViewCreationFailedException.class,
+ () -> update(ImmutableList.of("test/aspect.bzl%my_aspect"), "//test:xxx", "//test:yyy"));
+ }
+ assertContainsEvent(
+ "file 'extra_actions/test/xa/test/file.xa' is generated by these conflicting actions");
+ }
+
+ @Test
+ public void aspectsPropagatingToAllAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = depset([target.label], transitive =",
+ " [i.target_labels for i in ctx.rule.attr.runtime_deps]",
+ " if hasattr(ctx.rule.attr, 'runtime_deps') else [])",
+ " return struct(target_labels = s)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_tool' : attr.label(default = Label('//test:tool')) },",
+ " attr_aspects=['*'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'tool',",
+ ")",
+ "java_library(",
+ " name = 'bar',",
+ " runtime_deps = [':tool'],",
+ ")",
+ "java_library(",
+ " name = 'foo',",
+ " runtime_deps = [':bar'],",
+ ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:foo");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ assertThat(configuredAspect).isNotNull();
+ Object names = configuredAspect.get("target_labels");
+ assertThat(names).isInstanceOf(Depset.class);
+ assertThat(
+ Iterables.transform(
+ ((Depset) names).toList(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return ((Label) o).getName();
+ }))
+ .containsExactly("foo", "bar", "tool");
+ }
+
+ /** Simple straightforward linear aspects-on-aspects. */
+ @Test
+ public void aspectOnAspectLinear() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence) target.get("result");
+
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result)
+ .containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ /**
+ * Diamond case. rule r1 depends or r0 with aspect a1. rule r2 depends or r0 with aspect a2. rule
+ * rcollect depends on r1, r2 with aspect a3.
+ *
+ * <p>Aspect a3 should be applied twice to target r0: once in [a1, a3] sequence and once in [a2,
+ * a3] sequence.
+ */
+ @Test
+ public void aspectOnAspectDiamond() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = 'text from a1')",
+ "a1 = aspect(_a1_impl, attr_aspects = ['deps'], provides = ['a1p'])",
+ "",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = 'text from a2')",
+ "a2 = aspect(_a2_impl, attr_aspects = ['deps'], provides = ['a2p'])",
+ "",
+ "def _a3_impl(target,ctx):",
+ " value = []",
+ " f = ctx.actions.declare_file('a3.out')",
+ " ctx.actions.write(f, 'text')",
+ " for dep in ctx.rule.attr.deps:",
+ " if hasattr(dep, 'a3p'):",
+ " value += dep.a3p",
+ " s = str(target.label) + str(ctx.aspect_ids) + '='",
+ " if hasattr(target, 'a1p'):",
+ " s += 'a1p'",
+ " if hasattr(target, 'a2p'):",
+ " s += 'a2p'",
+ " value.append(s)",
+ " return struct(a3p = value)",
+ "a3 = aspect(_a3_impl, attr_aspects = ['deps'],",
+ " required_aspect_providers = [['a1p'], ['a2p']])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _rcollect_impl(ctx):",
+ " value = []",
+ " for dep in ctx.attr.deps:",
+ " if hasattr(dep, 'a3p'):",
+ " value += dep.a3p",
+ " return struct(result = value)",
+ "r1 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a1])})",
+ "r2 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a2])})",
+ "rcollect = rule(_rcollect_impl, attrs = { 'deps' : attr.label_list(aspects = [a3])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2', 'rcollect')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', deps = [':r0'])",
+ "r2(name = 'r2', deps = [':r0'])",
+ "rcollect(name = 'rcollect', deps = [':r1', ':r2'])");
+ AnalysisResult analysisResult = update("//test:rcollect");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence) target.get("result");
+ assertThat(result)
+ .containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a3\"]=a1p",
+ "//test:r1[\"//test:aspect.bzl%a3\"]=",
+ "//test:r0[\"//test:aspect.bzl%a2\", \"//test:aspect.bzl%a3\"]=a2p",
+ "//test:r2[\"//test:aspect.bzl%a3\"]=");
+ }
+
+ /**
+ * Linear with duplicates. r2_1 depends on r0 with aspect a2. r1 depends on r2_1 with aspect a1.
+ * r2 depends on r1 with aspect a2.
+ *
+ * <p>a2 is not interested in a1. There should be just one instance of aspect a2 on r0, and is
+ * should *not* see a1.
+ */
+ @Test
+ public void aspectOnAspectLinearDuplicates() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = 'a1p')",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = [])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r2(name = 'r2_1', dep = ':r0')",
+ "r1(name = 'r1', dep = ':r2_1')",
+ "r2(name = 'r2', dep = ':r1')");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence) target.get("result");
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result)
+ .containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a2\"]=no",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no", "//test:r2_1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ /** Linear aspects-on-aspects with alias rule. */
+ @Test
+ public void aspectOnAspectLinearAlias() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "alias(name = 'a0', actual = ':r0')",
+ "r1(name = 'r1', dep = ':a0')",
+ "r2(name = 'r2', dep = ':r1')");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence<?>) target.get("result");
+
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result)
+ .containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ @Test
+ public void aspectDescriptions() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a_impl(target,ctx):",
+ " s = str(target.label) + str(ctx.aspect_ids) + '='",
+ " value = []",
+ " if ctx.rule.attr.dep:",
+ " d = ctx.rule.attr.dep",
+ " this_id = ctx.aspect_ids[len(ctx.aspect_ids) - 1]",
+ " s += str(d.label) + str(d.my_ids) + ',' + str(this_id in d.my_ids)",
+ " value += ctx.rule.attr.dep.ap",
+ " else:",
+ " s += 'None'",
+ " value.append(s)",
+ " return struct(ap = value, my_ids = ctx.aspect_ids)",
+ "a = aspect(_a_impl, attr_aspects = ['dep'])",
+ "def _r_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct(result = [])",
+ " return struct(result = ctx.attr.dep.ap)",
+ "r = rule(_r_impl, attrs = { 'dep' : attr.label(aspects = [a])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r')",
+ "r(name = 'r0')",
+ "r(name = 'r1', dep = ':r0')",
+ "r(name = 'r2', dep = ':r1')");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence<?>) target.get("result");
+
+ assertThat(result)
+ .containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a\"]=None",
+ "//test:r1[\"//test:aspect.bzl%a\"]=//test:r0[\"//test:aspect.bzl%a\"],True");
+ }
+
+ @Test
+ public void attributesWithAspectsReused() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_impl)",
+ "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }");
+
+ scratch.file(
+ "test/r1.bzl",
+ "load(':aspect.bzl', 'my_aspect', 'a_dict')",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r1 = rule(_rule_impl, attrs = a_dict)");
+
+ scratch.file(
+ "test/r2.bzl",
+ "load(':aspect.bzl', 'my_aspect', 'a_dict')",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r2 = rule(_rule_impl, attrs = a_dict)");
+
+ scratch.file(
+ "test/BUILD",
+ "load(':r1.bzl', 'r1')",
+ "load(':r2.bzl', 'r2')",
+ "r1(name = 'x1')",
+ "r2(name = 'x2', foo = [':x1'])");
+ AnalysisResult analysisResult = update("//test:x2");
+ assertThat(analysisResult.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectAdvertisingProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_impl, provides = ['foo'])",
+ "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%my_aspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect exception
+ }
+ assertContainsEvent(
+ "Aspect '//test:aspect.bzl%my_aspect', applied to '//test:xxx', "
+ + "does not provide advertised provider 'foo'");
+ }
+
+ @Test
+ public void aspectOnAspectInconsistentVisibility() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = a2p(value = 'random'))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')");
+ reporter.removeHandler(failFastHandler);
+
+ try {
+ AnalysisResult analysisResult = update("//test:r2_1");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating to //test:r1)");
+ }
+
+ @Test
+ public void aspectOnAspectInconsistentVisibilityIndirect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = a2p(value = 'random'))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})",
+ "def _r0_impl(ctx):",
+ " pass",
+ "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')",
+ "r0(name = 'r0_2', dep = ':r2_1')");
+ reporter.removeHandler(failFastHandler);
+
+ try {
+ AnalysisResult analysisResult = update("//test:r0_2");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating to //test:r1)");
+ }
+
+ /**
+ * Aspect a3 sees aspect a2, aspect a2 sees aspect a1, but a3 does not see a1. All three aspects
+ * should still propagate together.
+ */
+ @Test
+ public void aspectOnAspectOnAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "p1 = provider()",
+ "def _a1_impl(target, ctx):",
+ " return [p1()]",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = [p1])",
+ "p2 = provider()",
+ "def _a2_impl(target, ctx):",
+ " value = True if p1 in target else False",
+ " return [p2(has_p1 = value)]",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'],",
+ " required_aspect_providers = [p1], provides = [p2])",
+ "p3 = provider()",
+ "def _a3_impl(target, ctx):",
+ " list = []",
+ " if ctx.rule.attr.dep:",
+ " list = ctx.rule.attr.dep[p3].value",
+ " my_value = str(target.label) +'=' + str(target[p2].has_p1 if p2 in target else False)",
+ " return [p3(value = list + [my_value])]",
+ "a3 = aspect(_a3_impl, attr_aspects = ['dep'],",
+ " required_aspect_providers = [p2])",
+ "def _r0_impl(ctx):",
+ " pass",
+ "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " pass",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0_1')",
+ "r0(name = 'r0_2', dep = ':r0_1')",
+ "r0(name = 'r0_3', dep = ':r0_2')",
+ "r1(name = 'r1_1', dep = ':r0_3')",
+ "r2(name = 'r2_1', dep = ':r1_1')");
+
+ AnalysisResult analysisResult = update(ImmutableList.of("//test:aspect.bzl%a3"), "//test:r2_1");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+ StarlarkProvider.Key p3 =
+ new StarlarkProvider.Key(Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "p3");
+ StructImpl p3Provider = (StructImpl) configuredAspect.get(p3);
+ assertThat((Sequence<?>) p3Provider.getValue("value"))
+ .containsExactly(
+ "//test:r0_1=True",
+ "//test:r0_2=True",
+ "//test:r0_3=True",
+ "//test:r1_1=False",
+ "//test:r2_1=False");
+ }
+
+ /**
+ * r0 is a dependency of r1 via two attributes, dep1 and dep2. r1 sends an aspect 'a' along dep1
+ * but not along dep2.
+ *
+ * <p>rcollect depends upon r1 and sends another aspect, 'collector', along its dep dependency.
+ * 'collector' wants to see aspect 'a' and propagates along dep1 and dep2. It should be applied
+ * both to r0 and to r0+a.
+ */
+ @Test
+ public void multipleDepsDifferentAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider()",
+ "PCollector = provider()",
+ "def _aspect_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_aspect_impl, attr_aspects = ['dep'], provides = [PAspect])",
+ "def _collector_impl(target, ctx):",
+ " suffix = '+PAspect' if PAspect in target else ''",
+ " result = [str(target.label)+suffix]",
+ " for a in ['dep', 'dep1', 'dep2']:",
+ " if hasattr(ctx.rule.attr, a):",
+ " result += getattr(ctx.rule.attr, a)[PCollector].result",
+ " return [PCollector(result=result)]",
+ "collector = aspect(_collector_impl, attr_aspects = ['*'], ",
+ " required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r1 = rule(_rimpl, ",
+ " attrs = {",
+ " 'dep1' : attr.label(),",
+ " 'dep2' : attr.label(aspects = [a]),",
+ " },",
+ ")",
+ "def _rcollect_impl(ctx):",
+ " return [ctx.attr.dep[PCollector]]",
+ "rcollect = rule(_rcollect_impl,",
+ " attrs = {",
+ " 'dep' : attr.label(aspects = [collector]),",
+ " })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'rcollect')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep1 = ':r0', dep2 = ':r0')",
+ "rcollect(name = 'rcollect', dep = ':r1')");
+
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:rcollect");
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkProvider.Key pCollector =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
+ StructImpl pCollectorProvider = (StructImpl) configuredTarget.get(pCollector);
+ assertThat((Sequence<?>) pCollectorProvider.getValue("result"))
+ .containsExactly("//test:r1", "//test:r0", "//test:r0+PAspect");
+ }
+
+ @Test
+ public void aspectSeesOtherAspectAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['aspect_attr'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_a_attr' : attr.label(default = '//test:foo')})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, '_a_attr'):",
+ " return [PCollector(aspect_attr = ctx.rule.attr._a_attr.label)]",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'foo')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+ StarlarkProvider.Key pCollector =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
+ StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
+ assertThat(collector.getValue("aspect_attr"))
+ .isEqualTo(Label.parseAbsolute("//test:foo", ImmutableMap.of()));
+ }
+
+ @Test
+ public void ruleAttributesWinOverAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['attr_value'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_same_attr' : attr.int(default = 239)})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, '_same_attr'):",
+ " return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a]), ",
+ " '_same_attr' : attr.int(default = 30)",
+ " })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'foo')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+ StarlarkProvider.Key pCollector =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
+ StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
+ assertThat(collector.getValue("attr_value")).isEqualTo(30);
+ }
+
+ @Test
+ public void earlyAspectAttributesWin() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect1 = provider(fields = [])",
+ "PAspect2 = provider(fields = [])",
+ "PCollector = provider(fields = ['attr_value'])",
+ "def _a1_impl(target, ctx):",
+ " return [PAspect1()]",
+ "def _a2_impl(target, ctx):",
+ " return [PAspect2()]",
+ "a1 = aspect(_a1_impl, ",
+ " provides = [PAspect1],",
+ " attrs = {'_same_attr' : attr.int(default = 30)})",
+ "a2 = aspect(_a2_impl, ",
+ " provides = [PAspect2],",
+ " attrs = {'_same_attr' : attr.int(default = 239)})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " if hasattr(ctx.rule.attr, '_same_attr'):",
+ " return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
+ " fail('???')",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], ",
+ " required_aspect_providers = [[PAspect1], [PAspect2]])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r1 = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a1]), ",
+ " })",
+ "r2 = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a2]), ",
+ " })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'bar')",
+ "r1(name = 'baz', dep = ':bar')",
+ "r2(name = 'quux', dep = ':baz')");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:quux");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+ StarlarkProvider.Key pCollector =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
+ StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
+ assertThat(collector.getValue("attr_value")).isEqualTo(30);
+ }
+
+ @Test
+ public void aspectPropagatesOverOtherAspectAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['visited'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_a_attr' : attr.label(default = '//test:referenced_from_aspect_only')})",
+ "def _rcollect(target, ctx):",
+ " transitive = []",
+ " if hasattr(ctx.rule.attr, 'dep') and ctx.rule.attr.dep:",
+ " transitive += [ctx.rule.attr.dep[PCollector].visited]",
+ " if hasattr(ctx.rule.attr, '_a_attr') and ctx.rule.attr._a_attr:",
+ " transitive += [ctx.rule.attr._a_attr[PCollector].visited] ",
+ " visited = depset([target.label], transitive = transitive, )",
+ " return [PCollector(visited = visited)]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'referenced_from_aspect_only')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+ StarlarkProvider.Key pCollector =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
+ StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
+ assertThat(((Depset) collector.getValue("visited")).toList())
+ .containsExactly(
+ Label.parseAbsolute("//test:referenced_from_aspect_only", ImmutableMap.of()),
+ Label.parseAbsolute("//test:bar", ImmutableMap.of()),
+ Label.parseAbsolute("//test:baz", ImmutableMap.of()));
+ }
+
+ @Test
+ // This test verifies that aspects which are defined natively and exported for use in Starlark
+ // can be referenced at the top level using the --aspects flag. For ease of testing,
+ // apple_common.objc_proto_aspect is used as an example.
+ public void testTopLevelStarlarkObjcProtoAspect() throws Exception {
+ MockObjcSupport.setupObjcProtoLibrary(scratch);
+ scratch.file("test_starlark/BUILD");
+ scratch.file("x/data_filter.pbascii");
+ scratch.file(
+ "test_starlark/top_level_stub.bzl",
+ "top_level_aspect = apple_common.objc_proto_aspect",
+ "",
+ "def top_level_stub_impl(ctx):",
+ " return struct()",
+ "top_level_stub = rule(",
+ " top_level_stub_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(),",
+ " },",
+ " fragments = ['apple'],",
+ ")");
+
+ scratch.file(
+ "x/BUILD",
+ "load('//objc_proto_library:objc_proto_library.bzl', 'objc_proto_library')",
+ "proto_library(",
+ " name = 'protos',",
+ " srcs = ['data.proto'],",
+ MockProtoSupport.MIGRATION_TAG,
+ ")",
+ "objc_proto_library(",
+ " name = 'x',",
+ " deps = [':protos'],",
+ " portable_proto_filters = ['data_filter.pbascii'],",
+ ")");
+
+ scratch.file(
+ "bin/BUILD",
+ "load('//test_starlark:top_level_stub.bzl', 'top_level_stub')",
+ "top_level_stub(",
+ " name = 'link_target',",
+ " deps = ['//x:x'],",
+ ")");
+
+ useConfiguration(MockObjcSupport.requiredObjcCrosstoolFlags().toArray(new String[1]));
+ AnalysisResult analysisResult =
+ update(
+ ImmutableList.of("test_starlark/top_level_stub.bzl%top_level_aspect"),
+ "//bin:link_target");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values());
+
+ ObjcProtoProvider objcProtoProvider =
+ (ObjcProtoProvider) configuredAspect.get(ObjcProtoProvider.STARLARK_CONSTRUCTOR.getKey());
+ assertThat(objcProtoProvider).isNotNull();
+ }
+
+ @Test
+ public void testAspectActionProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " ctx.actions.run_shell(",
+ " outputs = [ctx.actions.declare_file('a1')],",
+ " command = 'touch $@'",
+ " )",
+ " return struct(a1p=a1p())",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target, ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr, 'dep') and hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " value += target.actions",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r0_impl(ctx):",
+ " ctx.actions.run_shell(",
+ " outputs = [ctx.actions.declare_file('r0')],",
+ " command = 'touch $@'",
+ " )",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r0 = rule(_r0_impl)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ Sequence<?> result = (Sequence<?>) target.get("result");
+
+ // We should see both the action from the 'r0' rule, and the action from the 'a1' aspect
+ assertThat(result).hasSize(2);
+ assertThat(
+ result.stream()
+ .map(a -> ((Action) a).getPrimaryOutput().getExecPath().getBaseName())
+ .collect(toList()))
+ .containsExactly("r0", "a1");
+ }
+
+ @Test
+ public void testRuleAndAspectAttrConflict() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "MyInfo = provider()",
+ "def _impl(target, ctx):",
+ " return [MyInfo(hidden_attr_label = str(ctx.attr._hiddenattr.label))]",
+ "",
+ "def _rule_impl(ctx):",
+ " return []",
+ "",
+ "my_rule = rule(implementation = _rule_impl,",
+ " attrs = { '_hiddenattr' : attr.label(default = Label('//test:xxx')) },",
+ ")",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_hiddenattr' : attr.label(default = Label('//test:zzz')) },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "cc_library(",
+ " name = 'xxx',",
+ ")",
+ "my_rule(",
+ " name = 'yyy',",
+ ")",
+ "cc_library(",
+ " name = 'zzz',",
+ ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:yyy");
+ ConfiguredAspect configuredAspect = analysisResult.getAspectsMap().values().iterator().next();
+ assertThat(configuredAspect).isNotNull();
+
+ StarlarkProvider.Key myInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "MyInfo");
+ StructImpl myInfo = (StructImpl) configuredAspect.get(myInfoKey);
+ assertThat(myInfo.getValue("hidden_attr_label")).isEqualTo("//test:zzz");
+ }
+
+ /** Simple straightforward linear aspects-on-aspects. */
+ @Test
+ public void aspectOnAspectAttrConflict() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "MyInfo = provider()",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl,",
+ " attrs = { '_hiddenattr' : attr.label(default = Label('//test:xxx')) },",
+ " attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " return [MyInfo(hidden_attr_label = str(ctx.attr._hiddenattr.label))]",
+ "a2 = aspect(_a2_impl,",
+ " attrs = { '_hiddenattr' : attr.label(default = Label('//test:zzz')) },",
+ " attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep[MyInfo].hidden_attr_label)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "cc_library(",
+ " name = 'xxx',",
+ ")",
+ "cc_library(",
+ " name = 'zzz',",
+ ")");
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ String result = (String) target.get("result");
+
+ assertThat(result).isEqualTo("//test:zzz");
+ }
+
+ @Test
+ public void testAllCcLibraryAttrsAreValidTypes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " for entry in dir(ctx.rule.attr):",
+ " val = getattr(ctx.rule.attr, entry, None)",
+ " # Only legitimate Starlark values can be passed to dir(), so this effectively",
+ " # verifies val is an appropriate Starlark type.",
+ " _test_dir = dir(val)",
+ " return []",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file("test/BUILD", "cc_library(", " name = 'xxx',", ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(analysisResult.getAspectsMap().values().iterator().next()).isNotNull();
+ }
+
+ @Test
+ public void testApplyToGeneratingRules() throws Exception {
+ // Create test rules:
+ // dep_rule: a rule which may depend on other dep_rule targets and may optionally create
+ // an output file.
+ // root_{with,no}_files: a rule which depends on dep_rule targets and attaches an aspect.
+ // The rule returns a RootInfo provider which contains two fields:
+ // 'from_aspect' : a list of all labels that the aspect propagated to
+ // 'non_aspect' : a list of all labels that information was obtained from without aspect
+ // root_with_files uses an aspect with apply_to_generating_rules=True, and root_no_files
+ // uses an aspect with apply_to_generating_rules=False.
+ scratch.file(
+ "test/lib.bzl",
+ "RootInfo = provider()",
+ "NonAspectInfo = provider()",
+ "FromAspectInfo = provider()",
+ "def _aspect_impl(target, ctx):",
+ " dep_labels = []",
+ " for dep in ctx.rule.attr.deps:",
+ " if FromAspectInfo in dep:",
+ " dep_labels += [dep[FromAspectInfo].labels]",
+ " return FromAspectInfo(labels = depset(direct = [ctx.label], transitive = dep_labels))",
+ "",
+ "def _rule_impl(ctx):",
+ " non_aspect = []",
+ " from_aspect = []",
+ " for dep in ctx.attr.deps:",
+ " if NonAspectInfo in dep:",
+ " non_aspect += dep[NonAspectInfo].labels.to_list()",
+ " if FromAspectInfo in dep:",
+ " from_aspect += dep[FromAspectInfo].labels.to_list()",
+ " return RootInfo(from_aspect = from_aspect, non_aspect = non_aspect)",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " if ctx.outputs.output:",
+ " ctx.actions.run_shell(outputs = [ctx.outputs.output], command = 'dont run me')",
+ " dep_labels = []",
+ " for dep in ctx.attr.deps:",
+ " if NonAspectInfo in dep:",
+ " dep_labels += [dep[NonAspectInfo].labels]",
+ " return NonAspectInfo(labels = depset(direct = [ctx.label], transitive = dep_labels))",
+ "",
+ "aspect_with_files = aspect(",
+ " implementation = _aspect_impl,",
+ " attr_aspects = ['deps'],",
+ " apply_to_generating_rules = True)",
+ "",
+ "aspect_no_files = aspect(",
+ " implementation = _aspect_impl,",
+ " attr_aspects = ['deps'],",
+ " apply_to_generating_rules = False)",
+ "",
+ "root_with_files = rule(implementation = _rule_impl,",
+ " attrs = {'deps' : attr.label_list(aspects = [aspect_with_files])})",
+ "",
+ "root_no_files = rule(implementation = _rule_impl,",
+ " attrs = {'deps' : attr.label_list(aspects = [aspect_no_files])})",
+ "",
+ "dep_rule = rule(implementation = _dep_rule_impl,",
+ " attrs = {'deps' : attr.label_list(allow_files = True), 'output' : attr.output()})");
+
+ // Create a target graph such that two graph roots each point to a common subgraph
+ // alpha -> beta_output -> charlie, where beta_output is a generated output file of target
+ // 'beta'.
+ scratch.file(
+ "test/BUILD",
+ "load('//test:lib.bzl', 'root_with_files', 'root_no_files', 'dep_rule')",
+ "",
+ "root_with_files(name = 'test_with_files', deps = [':alpha'])",
+ "root_no_files(name = 'test_no_files', deps = [':alpha'])",
+ "dep_rule(name = 'alpha', deps = [':beta_output'])",
+ "dep_rule(name = 'beta', deps = [':charlie'], output = 'beta_output')",
+ "dep_rule(name = 'charlie')");
+
+ StarlarkProvider.Key rootInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "RootInfo");
+
+ AnalysisResult analysisResultWithFiles = update("//test:test_with_files");
+ ConfiguredTarget targetWithFiles =
+ Iterables.getOnlyElement(analysisResultWithFiles.getTargetsToBuild());
+ StructImpl rootInfoWithFiles = (StructImpl) targetWithFiles.get(rootInfoKey);
+ // With apply_to_generating_rules=True, the aspect should have traversed :beta_output and
+ // applied to both :beta and :charlie.
+ assertThat(rootInfoWithFiles.getValue("from_aspect", Sequence.class))
+ .containsExactly(
+ Label.parseAbsolute("//test:charlie", ImmutableMap.of()),
+ Label.parseAbsolute("//test:beta", ImmutableMap.of()),
+ Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ assertThat(rootInfoWithFiles.getValue("non_aspect", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+
+ AnalysisResult analysisResultNoFiles = update("//test:test_no_files");
+ ConfiguredTarget targetNoFiles =
+ Iterables.getOnlyElement(analysisResultNoFiles.getTargetsToBuild());
+ StructImpl rootInfoNoFiles = (StructImpl) targetNoFiles.get(rootInfoKey);
+ // With apply_to_generating_rules=False, the aspect should have only accessed :alpha, as it
+ // must have stopped before :beta_output.
+ assertThat(rootInfoNoFiles.getValue("from_aspect", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ assertThat(rootInfoWithFiles.getValue("non_aspect", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ }
+
+ private void setupAspectOnAspectTargetGraph(
+ boolean applyRootToGeneratingRules, boolean applyDepToGeneratingRules) throws Exception {
+ // RootAspectInfo.both_labels returns a list of target labels which
+ // were evaluated as [root_aspect(dep_aspect(target))].
+ // RootAspectInfo.root_only_labels returns a list of target labels which
+ // were evaluated as [root_aspect(target)].
+ // DepAspectInfo.labels returns a list of target labels which were evaluated by dep_aspect.
+ scratch.file(
+ "test/lib.bzl",
+ "RootAspectInfo = provider()",
+ "DepAspectInfo = provider()",
+ "def _root_aspect_impl(target, ctx):",
+ " both_labels = []",
+ " root_only_labels = []",
+ " for dep in ctx.rule.attr.deps:",
+ " if RootAspectInfo in dep:",
+ " both_labels += dep[RootAspectInfo].both_labels",
+ " root_only_labels += dep[RootAspectInfo].root_only_labels",
+ " if DepAspectInfo in dep:",
+ " both_labels += [dep.label]",
+ " else:",
+ " root_only_labels += [dep.label]",
+ " return RootAspectInfo(both_labels = both_labels, root_only_labels = root_only_labels)",
+ "",
+ "def _dep_aspect_impl(target, ctx):",
+ " dep_labels = [ctx.label]",
+ " for dep in ctx.rule.attr.deps:",
+ " if DepAspectInfo in dep:",
+ " dep_labels += dep[DepAspectInfo].labels",
+ " return DepAspectInfo(labels = dep_labels)",
+ "",
+ "def _root_rule_impl(ctx):",
+ " return [ctx.attr.deps[0][RootAspectInfo], ctx.attr.deps[0][DepAspectInfo]]",
+ "",
+ "def _dep_with_aspect_rule_impl(ctx):",
+ " return [ctx.attr.deps[0][DepAspectInfo]]",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " if ctx.outputs.output:",
+ " ctx.actions.run_shell(outputs = [ctx.outputs.output], command = 'dont run me')",
+ " return []",
+ "",
+ "root_aspect = aspect(",
+ " implementation = _root_aspect_impl,",
+ " attr_aspects = ['deps'],",
+ " required_aspect_providers = [DepAspectInfo],",
+ " apply_to_generating_rules = " + (applyRootToGeneratingRules ? "True" : "False") + ")",
+ "",
+ "dep_aspect = aspect(",
+ " implementation = _dep_aspect_impl,",
+ " attr_aspects = ['deps'],",
+ " provides = [DepAspectInfo],",
+ " apply_to_generating_rules = " + (applyDepToGeneratingRules ? "True" : "False") + ")",
+ "",
+ "root_rule = rule(implementation = _root_rule_impl,",
+ " attrs = {'deps' : attr.label_list(aspects = [root_aspect])})",
+ "",
+ "dep_with_aspect_rule = rule(implementation = _dep_with_aspect_rule_impl,",
+ " attrs = {'deps' : attr.label_list(aspects = [dep_aspect])})",
+ "",
+ "dep_rule = rule(implementation = _dep_rule_impl,",
+ " attrs = {'deps' : attr.label_list(allow_files = True), 'output' : attr.output()})");
+
+ // Target graph: test -> aspect_propagating_target -> alpha -> beta_output
+ // beta_output is an output of target `beta`
+ // beta -> charlie
+ scratch.file(
+ "test/BUILD",
+ "load('//test:lib.bzl', 'root_rule', 'dep_with_aspect_rule', 'dep_rule')",
+ "",
+ "root_rule(name = 'test', deps = [':aspect_propagating_target'])",
+ "dep_with_aspect_rule(name = 'aspect_propagating_target', deps = [':alpha'])",
+ "dep_rule(name = 'alpha', deps = [':beta_output'])",
+ "dep_rule(name = 'beta', deps = [':charlie'], output = 'beta_output')",
+ "dep_rule(name = 'charlie')");
+ }
+
+ @Test
+ public void testAspectOnAspectApplyToGeneratingRules_bothPropagate() throws Exception {
+ setupAspectOnAspectTargetGraph(
+ /* applyRootToGeneratingRules= */ true, /* applyDepToGeneratingRules= */ true);
+
+ StarlarkProvider.Key rootInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "RootAspectInfo");
+ StarlarkProvider.Key depInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "DepAspectInfo");
+
+ AnalysisResult analysisResult = update("//test:test");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
+ StructImpl depInfo = (StructImpl) target.get(depInfoKey);
+
+ assertThat(rootInfo.getValue("both_labels", Sequence.class))
+ .containsExactly(
+ Label.parseAbsolute("//test:alpha", ImmutableMap.of()),
+ Label.parseAbsolute("//test:beta_output", ImmutableMap.of()),
+ Label.parseAbsolute("//test:charlie", ImmutableMap.of()));
+ assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
+ assertThat(depInfo.getValue("labels", Sequence.class))
+ .containsExactly(
+ Label.parseAbsolute("//test:alpha", ImmutableMap.of()),
+ Label.parseAbsolute("//test:beta", ImmutableMap.of()),
+ Label.parseAbsolute("//test:charlie", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testAspectOnAspectApplyToGeneratingRules_neitherPropagate() throws Exception {
+ setupAspectOnAspectTargetGraph(
+ /* applyRootToGeneratingRules= */ false, /* applyDepToGeneratingRules= */ false);
+
+ StarlarkProvider.Key rootInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "RootAspectInfo");
+ StarlarkProvider.Key depInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "DepAspectInfo");
+
+ AnalysisResult analysisResult = update("//test:test");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
+ StructImpl depInfo = (StructImpl) target.get(depInfoKey);
+
+ assertThat(rootInfo.getValue("both_labels", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
+ assertThat(depInfo.getValue("labels", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testAspectOnAspectApplyToGeneratingRules_rootOnly() throws Exception {
+ setupAspectOnAspectTargetGraph(
+ /* applyRootToGeneratingRules= */ true, /* applyDepToGeneratingRules= */ false);
+
+ StarlarkProvider.Key rootInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "RootAspectInfo");
+ StarlarkProvider.Key depInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "DepAspectInfo");
+
+ AnalysisResult analysisResult = update("//test:test");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
+ StructImpl depInfo = (StructImpl) target.get(depInfoKey);
+
+ assertThat(rootInfo.getValue("both_labels", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ assertThat(rootInfo.getValue("root_only_labels", Sequence.class))
+ .containsExactly(
+ Label.parseAbsolute("//test:beta_output", ImmutableMap.of()),
+ Label.parseAbsolute("//test:charlie", ImmutableMap.of()));
+ assertThat(depInfo.getValue("labels", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testAspectOnAspectApplyToGeneratingRules_depOnly() throws Exception {
+ setupAspectOnAspectTargetGraph(
+ /* applyRootToGeneratingRules= */ false, /* applyDepToGeneratingRules= */ true);
+
+ StarlarkProvider.Key rootInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "RootAspectInfo");
+ StarlarkProvider.Key depInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:lib.bzl", ImmutableMap.of()), "DepAspectInfo");
+
+ AnalysisResult analysisResult = update("//test:test");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StructImpl rootInfo = (StructImpl) target.get(rootInfoKey);
+ StructImpl depInfo = (StructImpl) target.get(depInfoKey);
+
+ assertThat(rootInfo.getValue("both_labels", Sequence.class))
+ .containsExactly(Label.parseAbsolute("//test:alpha", ImmutableMap.of()));
+ assertThat(rootInfo.getValue("root_only_labels", Sequence.class)).isEmpty();
+ assertThat(depInfo.getValue("labels", Sequence.class))
+ .containsExactly(
+ Label.parseAbsolute("//test:alpha", ImmutableMap.of()),
+ Label.parseAbsolute("//test:beta", ImmutableMap.of()),
+ Label.parseAbsolute("//test:charlie", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testAspectActionsDontInheritExecProperties() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'my_rule')",
+ "my_rule(",
+ " name = 'my_target',",
+ " deps = [':my_dep'],",
+ ")",
+ "cc_binary(",
+ " name = 'my_dep',",
+ " srcs = ['dep.cc'],",
+ " exec_properties = {'mem': '16g'},",
+ ")");
+ scratch.file(
+ "test/defs.bzl",
+ "def _aspect_impl(target, ctx):",
+ " f = ctx.actions.declare_file('f.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return []",
+ "my_aspect = aspect(",
+ " implementation = _aspect_impl,",
+ " attr_aspects = ['deps'],",
+ ")",
+ "def _rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(aspects = [my_aspect])",
+ " },",
+ ")");
+ scratch.file("test/dep.cc", "int main() {return 0;}");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/defs.bzl%my_aspect"), "//test:my_target");
+ assertThat(analysisResult).isNotNull();
+ ActionOwner owner =
+ Iterables.getOnlyElement(
+ Iterables.getOnlyElement(analysisResult.getAspectsMap().values()).getActions())
+ .getOwner();
+ assertThat(owner.getExecProperties()).isEmpty();
+ }
+
+ /** StarlarkAspectTest with "keep going" flag */
+ @RunWith(JUnit4.class)
+ public static final class WithKeepGoing extends StarlarkDefinedAspectsTest {
+ @Override
+ protected FlagBuilder defaultFlags() {
+ return super.defaultFlags().with(Flag.KEEP_GOING);
+ }
+
+ @Override
+ protected boolean keepGoing() {
+ return true;
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
new file mode 100644
index 0000000..983bd02
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
@@ -0,0 +1,3485 @@
+// Copyright 2016 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.devtools.build.lib.analysis.OutputGroupInfo.INTERNAL_SUFFIX;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.truth.Correspondence;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupInfo;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
+import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkAttributeTransitionProvider;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleTransitionProvider;
+import com.google.devtools.build.lib.analysis.test.AnalysisFailure;
+import com.google.devtools.build.lib.analysis.test.AnalysisFailureInfo;
+import com.google.devtools.build.lib.analysis.test.AnalysisTestResultInfo;
+import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.AttributeContainer;
+import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
+import com.google.devtools.build.lib.packages.BuildSetting;
+import com.google.devtools.build.lib.packages.FunctionSplitTransitionAllowlist;
+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 com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.objc.ObjcProvider;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.syntax.NoneType;
+import com.google.devtools.build.lib.syntax.Sequence;
+import com.google.devtools.build.lib.syntax.Starlark;
+import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for Starlark. */
+@RunWith(JUnit4.class)
+public class StarlarkIntegrationTest extends BuildViewTestCase {
+ protected boolean keepGoing() {
+ return false;
+ }
+
+ @Before
+ public void setupMyInfo() throws IOException {
+ scratch.file("myinfo/myinfo.bzl", "MyInfo = provider()");
+
+ scratch.file("myinfo/BUILD");
+ }
+
+ private StructImpl getMyInfoFromTarget(ConfiguredTarget configuredTarget) throws Exception {
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//myinfo:myinfo.bzl", ImmutableMap.of()), "MyInfo");
+ return (StructImpl) configuredTarget.get(key);
+ }
+
+ @Test
+ public void testRemoteLabelAsDefaultAttributeValue() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def _impl(ctx):",
+ " pass",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'dep' : attr.label_list(default=[\"@r//:t\"]) })");
+
+ // We are only interested in whether the label string in the default value can be converted
+ // to a proper Label without an exception (see GitHub issue #1442).
+ // Consequently, we expect getTarget() to fail later since the repository does not exist.
+ checkError(
+ "test/starlark",
+ "the_rule",
+ "no such package '@r//': The repository '@r' could not be resolved",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "",
+ "my_rule(name='the_rule')");
+ }
+
+ @Test
+ public void testMainRepoLabelWorkspaceRoot() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " return [MyInfo(result = ctx.label.workspace_root)]",
+ "my_rule = rule(implementation = _impl, attrs = { })");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "my_rule(name='t')");
+
+ ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:t");
+ String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void testExternalRepoLabelWorkspaceRoot_subdirRepoLayout() throws Exception {
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='/r')")
+ .build());
+
+ scratch.file("/r/WORKSPACE");
+ scratch.file(
+ "/r/test/starlark/extension.bzl",
+ "load('@//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " return [MyInfo(result = ctx.label.workspace_root)]",
+ "my_rule = rule(implementation = _impl, attrs = { })");
+ scratch.file(
+ "/r/BUILD", "load('//:test/starlark/extension.bzl', 'my_rule')", "my_rule(name='t')");
+
+ // Required since we have a new WORKSPACE file.
+ invalidatePackages(true);
+
+ ConfiguredTarget myTarget = getConfiguredTarget("@r//:t");
+ String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result).isEqualTo("external/r");
+ }
+
+ @Test
+ public void testExternalRepoLabelWorkspaceRoot_siblingRepoLayout() throws Exception {
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='/r')")
+ .build());
+
+ scratch.file("/r/WORKSPACE");
+ scratch.file(
+ "/r/test/starlark/extension.bzl",
+ "load('@//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " return [MyInfo(result = ctx.label.workspace_root)]",
+ "my_rule = rule(implementation = _impl, attrs = { })");
+ scratch.file(
+ "/r/BUILD", "load('//:test/starlark/extension.bzl', 'my_rule')", "my_rule(name='t')");
+
+ // Required since we have a new WORKSPACE file.
+ invalidatePackages(true);
+
+ setStarlarkSemanticsOptions("--experimental_sibling_repository_layout");
+
+ ConfiguredTarget myTarget = getConfiguredTarget("@r//:t");
+ String result = (String) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result).isEqualTo("../r");
+ }
+
+ @Test
+ public void testSameMethodNames() throws Exception {
+ // The alias feature of load() may hide the fact that two methods in the stack trace have the
+ // same name. This is perfectly legal as long as these two methods are actually distinct.
+ // Consequently, no "Recursion was detected" error must be thrown.
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//test/starlark:other.bzl', other_impl = 'impl')",
+ "def impl(ctx):",
+ " other_impl(ctx)",
+ "empty = rule(implementation = impl)");
+ scratch.file("test/starlark/other.bzl", "def impl(ctx):", " print('This rule does nothing')");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'empty')",
+ "empty(name = 'test_target')");
+
+ getConfiguredTarget("//test/starlark:test_target");
+ }
+
+ private AttributeContainer getContainerForTarget(String targetName) throws Exception {
+ ConfiguredTargetAndData target = getConfiguredTargetAndData("//test/starlark:" + targetName);
+ return target.getTarget().getAssociatedRule().getAttributeContainer();
+ }
+
+ @Test
+ public void testMacroHasGeneratorAttributes() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def _impl(ctx):",
+ " print('This rule does nothing')",
+ "",
+ "empty = rule(implementation = _impl)",
+ "no_macro = rule(implementation = _impl)",
+ "",
+ "def macro(name, visibility=None):",
+ " empty(name = name, visibility=visibility)",
+ "def native_macro(name):",
+ " native.cc_library(name = name + '_suffix')");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', macro_rule = 'macro', no_macro_rule = 'no_macro',",
+ " native_macro_rule = 'native_macro')",
+ "macro_rule(name = 'macro_target')",
+ "no_macro_rule(name = 'no_macro_target')",
+ "native_macro_rule(name = 'native_macro_target')",
+ "cc_binary(name = 'cc_target', deps = ['cc_dep'])",
+ "cc_library(name = 'cc_dep')");
+
+ AttributeContainer withMacro = getContainerForTarget("macro_target");
+ assertThat(withMacro.getAttr("generator_name")).isEqualTo("macro_target");
+ assertThat(withMacro.getAttr("generator_function")).isEqualTo("macro");
+ assertThat(withMacro.getAttr("generator_location")).isEqualTo("test/starlark/BUILD:3:11");
+
+ // Attributes are only set when the rule was created by a macro
+ AttributeContainer noMacro = getContainerForTarget("no_macro_target");
+ assertThat(noMacro.getAttr("generator_name")).isEqualTo("");
+ assertThat(noMacro.getAttr("generator_function")).isEqualTo("");
+ assertThat(noMacro.getAttr("generator_location")).isEqualTo("");
+
+ AttributeContainer nativeMacro = getContainerForTarget("native_macro_target_suffix");
+ assertThat(nativeMacro.getAttr("generator_name")).isEqualTo("native_macro_target");
+ assertThat(nativeMacro.getAttr("generator_function")).isEqualTo("native_macro");
+ assertThat(nativeMacro.getAttr("generator_location")).isEqualTo("test/starlark/BUILD:5:18");
+
+ AttributeContainer ccTarget = getContainerForTarget("cc_target");
+ assertThat(ccTarget.getAttr("generator_name")).isEqualTo("");
+ assertThat(ccTarget.getAttr("generator_function")).isEqualTo("");
+ assertThat(ccTarget.getAttr("generator_location")).isEqualTo("");
+ }
+
+ @Test
+ public void sanityCheckUserDefinedTestRule() throws Exception {
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _impl(ctx):",
+ " output = ctx.outputs.out",
+ " ctx.actions.write(output = output, content = 'hello', is_executable=True)",
+ " return [DefaultInfo(executable = output)]",
+ "",
+ "fake_test = rule(",
+ " implementation = _impl,",
+ " test=True,",
+ " attrs = {'_xcode_config': attr.label(default = configuration_field(",
+ " fragment = 'apple', name = \"xcode_config_label\"))},",
+ " outputs = {\"out\": \"%{name}.txt\"})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'fake_test')",
+ "fake_test(name = 'test_name')");
+ getConfiguredTarget("//test/starlark:test_name");
+ }
+
+ @Test
+ public void testOutputGroupsDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " f = ctx.attr.dep[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " return [MyInfo(result = f),",
+ " OutputGroupInfo(my_group = f)]",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'dep' : attr.label() })");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "cc_binary(name = 'lib', data = ['a.txt'])",
+ "my_rule(name='my', dep = ':lib')");
+ NestedSet<Artifact> hiddenTopLevelArtifacts =
+ OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
+ Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result.getSet(Artifact.class).toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ }
+
+
+ @Test
+ public void testOutputGroupsAsDictionary() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " f = ctx.attr.dep.output_groups['_hidden_top_level" + INTERNAL_SUFFIX + "']",
+ " has_key1 = '_hidden_top_level" + INTERNAL_SUFFIX + "' in ctx.attr.dep.output_groups",
+ " has_key2 = 'foobar' in ctx.attr.dep.output_groups",
+ " all_keys = [k for k in ctx.attr.dep.output_groups]",
+ " return [MyInfo(result = f, ",
+ " has_key1 = has_key1,",
+ " has_key2 = has_key2,",
+ " all_keys = all_keys),",
+ " OutputGroupInfo(my_group = f)]",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'dep' : attr.label() })");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "cc_binary(name = 'lib', data = ['a.txt'])",
+ "my_rule(name='my', dep = ':lib')");
+ NestedSet<Artifact> hiddenTopLevelArtifacts =
+ OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
+ StructImpl myInfo = getMyInfoFromTarget(myTarget);
+ Depset result = (Depset) myInfo.getValue("result");
+ assertThat(result.getSet(Artifact.class).toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(myInfo.getValue("has_key1")).isEqualTo(Boolean.TRUE);
+ assertThat(myInfo.getValue("has_key2")).isEqualTo(Boolean.FALSE);
+ assertThat((Sequence) myInfo.getValue("all_keys"))
+ .containsExactly(
+ OutputGroupInfo.HIDDEN_TOP_LEVEL,
+ OutputGroupInfo.COMPILATION_PREREQUISITES,
+ OutputGroupInfo.FILES_TO_COMPILE,
+ OutputGroupInfo.TEMP_FILES);
+ }
+
+ @Test
+ public void testOutputGroupsAsDictionaryPipe() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " g = depset(ctx.attr.dep.output_groups['_hidden_top_level" + INTERNAL_SUFFIX + "'])",
+ " return [MyInfo(result = g),",
+ " OutputGroupInfo(my_group = g)]",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'dep' : attr.label() })");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "cc_binary(name = 'lib', data = ['a.txt'])",
+ "my_rule(name='my', dep = ':lib')");
+ NestedSet<Artifact> hiddenTopLevelArtifacts =
+ OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
+ Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result.getSet(Artifact.class).toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ }
+
+ @Test
+ public void testOutputGroupsDeclaredProviderWithList() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " f = ctx.attr.dep[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " g = f.to_list()",
+ " return [MyInfo(result = f),",
+ " OutputGroupInfo(my_group = g, my_empty_group = [])]",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'dep' : attr.label() })");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "cc_binary(name = 'lib', data = ['a.txt'])",
+ "my_rule(name='my', dep = ':lib')");
+ NestedSet<Artifact> hiddenTopLevelArtifacts =
+ OutputGroupInfo.get(getConfiguredTarget("//test/starlark:lib"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ ConfiguredTarget myTarget = getConfiguredTarget("//test/starlark:my");
+ Depset result = (Depset) getMyInfoFromTarget(myTarget).getValue("result");
+ assertThat(result.getSet(Artifact.class).toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_group").toList())
+ .containsExactlyElementsIn(hiddenTopLevelArtifacts.toList());
+ assertThat(OutputGroupInfo.get(myTarget).getOutputGroup("my_empty_group").toList()).isEmpty();
+ }
+
+ @Test
+ public void testStackTraceErrorInFunction() throws Exception {
+ runStackTraceTest(
+ "str",
+ "\t\tstr.index(1)"
+ + System.lineSeparator()
+ + "in call to index(), parameter 'sub' got value of type 'int', want 'string'");
+ }
+
+ @Test
+ public void testStackTraceMissingMethod() throws Exception {
+ runStackTraceTest(
+ "None",
+ "\t\tNone.index"
+ + System.lineSeparator()
+ + "'NoneType' value has no field or method 'index'");
+ }
+
+ protected void runStackTraceTest(String object, String errorMessage) throws Exception {
+ reporter.removeHandler(failFastHandler);
+ String expectedTrace =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "Traceback (most recent call last):",
+ "\tFile \"/workspace/test/starlark/BUILD\", line 3",
+ "\t\tcustom_rule(name = 'cr')",
+ "\tFile \"/workspace/test/starlark/extension.bzl\", line 6, in custom_rule_impl",
+ "\t\tfoo()",
+ "\tFile \"/workspace/test/starlark/extension.bzl\", line 9, in foo",
+ "\t\tbar(2, 4)",
+ "\tFile \"/workspace/test/starlark/extension.bzl\", line 11, in bar",
+ "\t\tfirst(x, y, z)",
+ "\tFile \"/workspace/test/starlark/functions.bzl\", line 2, in first",
+ "\t\tsecond(a, b)",
+ "\tFile \"/workspace/test/starlark/functions.bzl\", line 5, in second",
+ "\t\tthird(\"legal\")",
+ "\tFile \"/workspace/test/starlark/functions.bzl\", line 7, in third",
+ errorMessage);
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//test/starlark:functions.bzl', 'first')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " ftb = depset(attr1)",
+ " foo()",
+ " return [MyInfo(provider_key = ftb)]",
+ "def foo():",
+ " bar(2,4)",
+ "def bar(x,y,z=1):",
+ " first(x,y, z)",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
+ scratch.file(
+ "test/starlark/functions.bzl",
+ "def first(a, b, c):",
+ " second(a, b)",
+ " third(b)",
+ "def second(a, b):",
+ " third('legal')",
+ "def third(str):",
+ " " + object + ".index(1)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ getConfiguredTarget("//test/starlark:cr");
+ assertContainsEvent(expectedTrace);
+ }
+
+ @Test
+ public void testFilesToBuild() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " ftb = depset(attr1)",
+ " return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testRunfiles() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " rf = ctx.runfiles(files = attr1)",
+ " return [DefaultInfo(runfiles = rf)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getAllArtifacts()))
+ .containsExactly("a.txt");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(RunfilesProvider.class).getDataRunfiles().getAllArtifacts()))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testAccessRunfiles() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " runfiles = ctx.attr.x.default_runfiles.files",
+ " return [DefaultInfo(files = runfiles)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'x': attr.label(allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "cc_library(name = 'lib', data = ['a.txt'])",
+ "custom_rule(name = 'cr1', x = ':lib')",
+ "custom_rule(name = 'cr2', x = 'b.txt')");
+
+ scratch.file("test/starlark/a.txt");
+ scratch.file("test/starlark/b.txt");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr1");
+ List<String> baseArtifactNames =
+ ActionsTestUtil.baseArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild());
+ assertThat(baseArtifactNames).containsExactly("a.txt");
+
+ target = getConfiguredTarget("//test/starlark:cr2");
+ baseArtifactNames =
+ ActionsTestUtil.baseArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild());
+ assertThat(baseArtifactNames).isEmpty();
+ }
+
+ @Test
+ public void testStatefulRunfiles() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " rf1 = ctx.runfiles(files = attr1)",
+ " rf2 = ctx.runfiles()",
+ " return [DefaultInfo(data_runfiles = rf1, default_runfiles = rf2)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory = True, allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ assertThat(target.getProvider(RunfilesProvider.class).getDefaultRunfiles().isEmpty()).isTrue();
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(RunfilesProvider.class).getDataRunfiles().getAllArtifacts()))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testExecutableGetsInRunfilesAndFilesToBuild() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " ctx.actions.write(output = ctx.outputs.executable, content = 'echo hello')",
+ " rf = ctx.runfiles(ctx.files.data)",
+ " return [DefaultInfo(runfiles = rf)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl, executable = True,",
+ " attrs = {'data': attr.label_list(allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', data = [':a.txt'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getAllArtifacts()))
+ .containsExactly("a.txt", "cr")
+ .inOrder();
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("cr");
+ }
+
+ @Test
+ public void testCannotSpecifyRunfilesWithDataOrDefaultRunfiles_struct() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " rf = ctx.runfiles()",
+ " return struct(runfiles = rf, default_runfiles = rf)",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ checkError(
+ "test/starlark",
+ "cr",
+ "Cannot specify the provider 'runfiles' together with "
+ + "'data_runfiles' or 'default_runfiles'",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+ }
+
+ @Test
+ public void testCannotSpecifyRunfilesWithDataOrDefaultRunfiles_defaultInfo() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " rf = ctx.runfiles()",
+ " return [DefaultInfo(runfiles = rf, default_runfiles = rf)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ checkError(
+ "test/starlark",
+ "cr",
+ "Cannot specify the provider 'runfiles' together with "
+ + "'data_runfiles' or 'default_runfiles'",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+ }
+
+ @Test
+ public void testDefaultInfoWithRunfilesConstructor() throws Exception {
+ scratch.file(
+ "pkg/BUILD",
+ "sh_binary(name = 'tryme',",
+ " srcs = [':tryme.sh'],",
+ " visibility = ['//visibility:public'],",
+ ")");
+
+ scratch.file(
+ "src/rulez.bzl",
+ "def _impl(ctx):",
+ " info = DefaultInfo(runfiles = ctx.runfiles(files=[ctx.executable.dep]))",
+ " if info.default_runfiles.files.to_list()[0] != ctx.executable.dep:",
+ " fail('expected runfile to be in info.default_runfiles')",
+ " return [info]",
+ "r = rule(_impl,",
+ " attrs = {",
+ " 'dep' : attr.label(executable = True, mandatory = True, cfg = 'host'),",
+ " }",
+ ")");
+
+ scratch.file(
+ "src/BUILD", "load(':rulez.bzl', 'r')", "r(name = 'r_tools', dep = '//pkg:tryme')");
+
+ assertThat(getConfiguredTarget("//src:r_tools")).isNotNull();
+ }
+
+ @Test
+ public void testInstrumentedFilesProviderWithCodeCoverageDisabled() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(instrumented_files=struct(",
+ " extensions = ['txt'],",
+ " source_attributes = ['attr1'],",
+ " dependency_attributes = ['attr2']))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.label_list(mandatory = True, allow_files=True),",
+ " 'attr2': attr.label_list(mandatory = True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "java_library(name='jl', srcs = [':A.java'])",
+ "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");
+
+ useConfiguration("--nocollect_code_coverage");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
+ assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
+ assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();
+ }
+
+ @Test
+ public void testInstrumentedFilesProviderWithCodeCoverageEnabled() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(instrumented_files=struct(",
+ " extensions = ['txt'],",
+ " source_attributes = ['attr1'],",
+ " dependency_attributes = ['attr2']))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.label_list(mandatory = True, allow_files=True),",
+ " 'attr2': attr.label_list(mandatory = True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "java_library(name='jl', srcs = [':A.java'])",
+ "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");
+
+ useConfiguration("--collect_code_coverage");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
+ assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
+ assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles()))
+ .containsExactly("a.txt", "A.java");
+ }
+
+ @Test
+ public void testInstrumentedFilesInfo_coverageDisabled() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(instrumented_files=struct(",
+ " extensions = ['txt'],",
+ " source_attributes = ['attr1'],",
+ " dependency_attributes = ['attr2']))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.label_list(mandatory = True, allow_files=True),",
+ " 'attr2': attr.label_list(mandatory = True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "java_library(name='jl', srcs = [':A.java'])",
+ "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':jl'])");
+
+ useConfiguration("--nocollect_code_coverage");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
+ assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
+ assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();
+ }
+
+ @Test
+ public void testInstrumentedFilesInfo_coverageEnabled() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "",
+ "def custom_rule_impl(ctx):",
+ " return [coverage_common.instrumented_files_info(ctx,",
+ " extensions = ['txt'],",
+ " source_attributes = ['attr1'],",
+ " dependency_attributes = ['attr2'])]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.label_list(mandatory = True, allow_files=True),",
+ " 'attr2': attr.label_list(mandatory = True)})",
+ "",
+ "def test_rule_impl(ctx):",
+ " return [MyInfo(",
+ // The point of this is to assert that these fields can be read in analysistest.
+ // Normally, this information wouldn't be forwarded via a different provider.
+ " instrumented_files = ctx.attr.target[InstrumentedFilesInfo].instrumented_files,",
+ " metadata_files = ctx.attr.target[InstrumentedFilesInfo].metadata_files)]",
+ "",
+ "test_rule = rule(implementation = test_rule_impl,",
+ " attrs = {'target': attr.label(mandatory = True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule', 'test_rule')",
+ "",
+ "cc_library(name='cl', srcs = [':A.cc'])",
+ "custom_rule(name = 'cr', attr1 = [':a.txt', ':a.random'], attr2 = [':cl'])",
+ "test_rule(name = 'test', target = ':cr')");
+
+ useConfiguration("--collect_code_coverage");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");
+ StructImpl myInfo = getMyInfoFromTarget(target);
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ ((Depset) myInfo.getValue("instrumented_files")).getSet(Artifact.class)))
+ .containsExactly("a.txt", "A.cc");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ ((Depset) myInfo.getValue("metadata_files")).getSet(Artifact.class)))
+ .containsExactly("A.gcno");
+ }
+
+ @Test
+ public void testTransitiveInfoProviders() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " ftb = depset(attr1)",
+ " return [MyInfo(provider_key = ftb)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ RuleConfiguredTarget target = (RuleConfiguredTarget) getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ ((Depset) getMyInfoFromTarget(target).getValue("provider_key"))
+ .getSet(Artifact.class)))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testInstrumentedFilesForwardedFromDepsByDefaultExperimentFlag() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def wrapper_impl(ctx):",
+ // This wrapper doesn't configure InstrumentedFilesInfo.
+ " return []",
+ "",
+ "wrapper = rule(implementation = wrapper_impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files = True),",
+ " 'wrapped': attr.label(mandatory = True),",
+ " 'wrapped_list': attr.label_list(),",
+ // Host deps aren't forwarded by default, since they don't provide code/binaries executed
+ // at runtime.
+ " 'tool': attr.label(cfg = 'host', executable = True, mandatory = True),",
+ " })");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'wrapper')",
+ "",
+ "cc_binary(name = 'tool', srcs = [':tool.cc'])",
+ "cc_binary(name = 'wrapped', srcs = [':wrapped.cc'])",
+ "cc_binary(name = 'wrapped_list', srcs = [':wrapped_list.cc'])",
+ "wrapper(",
+ " name = 'wrapper',",
+ " srcs = ['ignored.cc'],",
+ " wrapped = ':wrapped',",
+ " wrapped_list = [':wrapped_list'],",
+ " tool = ':tool',",
+ ")",
+ "cc_binary(name = 'outer', data = [':wrapper'])");
+
+ // Current behavior is that nothing gets forwarded if IntstrumentedFilesInfo is not configured.
+ // That means that source files are not collected for the coverage manifest unless the entire
+ // dependency chain between the test and the source file explicitly configures coverage.
+ // New behavior is protected by --experimental_forward_instrumented_files_info_by_default.
+ useConfiguration("--collect_code_coverage");
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:outer");
+ InstrumentedFilesInfo provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
+ assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
+ assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles())).isEmpty();
+
+ // Instead, the default behavior could be to forward InstrumentedFilesInfo from all
+ // dependencies. Coverage still needs to be configured for rules that handle source files for
+ // languages which support coverage instrumentation, but not every wrapper rule in the
+ // dependency chain needs to configure that for instrumentation to be correct.
+ useConfiguration(
+ "--collect_code_coverage", "--experimental_forward_instrumented_files_info_by_default");
+ target = getConfiguredTarget("//test/starlark:outer");
+ provider = target.get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR);
+ assertWithMessage("InstrumentedFilesInfo should be set.").that(provider).isNotNull();
+ assertThat(ActionsTestUtil.baseArtifactNames(provider.getInstrumentedFiles()))
+ .containsExactly("wrapped.cc", "wrapped_list.cc");
+ }
+
+ @Test
+ public void testMandatoryProviderMissing() throws Exception {
+ scratch.file("test/starlark/BUILD");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def rule_impl(ctx):",
+ " return []",
+ "",
+ "dependent_rule = rule(implementation = rule_impl)",
+ "",
+ "main_rule = rule(implementation = rule_impl,",
+ " attrs = {'dependencies': attr.label_list(providers = ['some_provider'],",
+ " allow_files=True)})");
+
+ checkError(
+ "test",
+ "b",
+ "in dependencies attribute of main_rule rule //test:b: "
+ + "'//test:a' does not have mandatory providers: 'some_provider'",
+ "load('//test/starlark:extension.bzl', 'dependent_rule')",
+ "load('//test/starlark:extension.bzl', 'main_rule')",
+ "",
+ "dependent_rule(name = 'a')",
+ "main_rule(name = 'b', dependencies = [':a'])");
+ }
+
+ @Test
+ public void testSpecialMandatoryProviderMissing() throws Exception {
+ // Test that rules satisfy `providers = [...]` condition if a special provider that always
+ // exists for all rules is requested. Also check external rules.
+
+ FileSystemUtils.appendIsoLatin1(scratch.resolve("WORKSPACE"),
+ "bind(name = 'bar', actual = '//test/ext:bar')");
+ scratch.file(
+ "test/ext/BUILD",
+ "load('//test/starlark:extension.bzl', 'foobar')",
+ "",
+ "foobar(name = 'bar', visibility = ['//visibility:public'],)");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def rule_impl(ctx):",
+ " pass",
+ "",
+ "foobar = rule(implementation = rule_impl)",
+ "main_rule = rule(implementation = rule_impl, attrs = {",
+ " 'deps': attr.label_list(providers = [",
+ " 'files', 'data_runfiles', 'default_runfiles',",
+ " 'files_to_run', 'output_groups',",
+ " ])",
+ "})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load(':extension.bzl', 'foobar', 'main_rule')",
+ "",
+ "foobar(name = 'foo')",
+ "main_rule(name = 'main', deps = [':foo', '//external:bar'])");
+
+ invalidatePackages();
+ getConfiguredTarget("//test/starlark:main");
+ }
+
+ @Test
+ public void testActions() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " output = ctx.outputs.o",
+ " ctx.actions.run_shell(",
+ " inputs = attr1,",
+ " outputs = [output],",
+ " command = 'echo')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)},",
+ " outputs = {'o': 'o.txt'})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ getConfiguredTarget("//test/starlark:cr");
+
+ FileConfiguredTarget target = getFileConfiguredTarget("//test/starlark:o.txt");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ getGeneratingAction(target.getArtifact()).getInputs()))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputFunction() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " files = [ctx.outputs.o]",
+ " ctx.actions.run_shell(",
+ " outputs = files,",
+ " command = 'echo')",
+ " ftb = depset(files)",
+ " return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
+ "",
+ "def output_func(name, public_attr, _private_attr):",
+ " if _private_attr != None: return {}",
+ " return {'o': name + '-' + public_attr + '.txt'}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'public_attr': attr.string(),",
+ " '_private_attr': attr.label()},",
+ " outputs = output_func)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', public_attr = 'bar')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("cr-bar.txt");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputFunctionDependingOnComputedAttribute() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " files = [ctx.outputs.o]",
+ " ctx.actions.run_shell(",
+ " outputs = files,",
+ " command = 'echo')",
+ " ftb = depset(files)",
+ " return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
+ "",
+ "def attr_func(public_attr):",
+ " return public_attr",
+ "",
+ "def output_func(_private_attr):",
+ " return {'o': _private_attr.name + '.txt'}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'public_attr': attr.label(),",
+ " '_private_attr': attr.label(default = attr_func)},",
+ " outputs = output_func)",
+ "",
+ "def empty_rule_impl(ctx):",
+ " pass",
+ "",
+ "empty_rule = rule(implementation = empty_rule_impl)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule', 'empty_rule')",
+ "",
+ "empty_rule(name = 'foo')",
+ "custom_rule(name = 'cr', public_attr = '//test/starlark:foo')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("foo.txt");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputs() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " files = [ctx.outputs.lbl, ctx.outputs.list, ctx.outputs.str]",
+ " print('==!=!=!=')",
+ " print(files)",
+ " ctx.actions.run_shell(",
+ " outputs = files,",
+ " command = 'echo')",
+ " return [DefaultInfo(files = depset(files))]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.label(allow_files=True),",
+ " 'attr2': attr.label_list(allow_files=True),",
+ " 'attr3': attr.string(),",
+ " },",
+ " outputs = {",
+ " 'lbl': '%{attr1}.a',",
+ " 'list': '%{attr2}.b',",
+ " 'str': '%{attr3}.c',",
+ "})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(",
+ " name='cr',",
+ " attr1='f1.txt',",
+ " attr2=['f2.txt'],",
+ " attr3='f3.txt',",
+ ")");
+
+ scratch.file("test/starlark/f1.txt");
+ scratch.file("test/starlark/f2.txt");
+ scratch.file("test/starlark/f3.txt");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("f1.a", "f2.b", "f3.txt.c");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputFunctionAndDefaultValue() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " ctx.actions.run_shell(",
+ " outputs = [ctx.outputs.o],",
+ " command = 'echo')",
+ " return [DefaultInfo(runfiles = ctx.runfiles())]",
+ "",
+ "def output_func(attr1):",
+ " return {'o': attr1 + '.txt'}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.string(default='bar')},",
+ " outputs = output_func)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = None)");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("bar.txt");
+ }
+
+ @Test
+ public void testPrintProviderCollection() throws Exception {
+ scratch.file(
+ "test/starlark/rules.bzl",
+ "",
+ "FooInfo = provider()",
+ "BarInfo = provider()",
+ "",
+ "def _top_level_rule_impl(ctx):",
+ " print('My Dep Providers:', ctx.attr.my_dep)",
+ "",
+ "def _dep_rule_impl(name):",
+ " providers = [",
+ " FooInfo(),",
+ " BarInfo(),",
+ " ]",
+ " return providers",
+ "",
+ "top_level_rule = rule(",
+ " implementation=_top_level_rule_impl,",
+ " attrs={'my_dep':attr.label()}",
+ ")",
+ "",
+ "dep_rule = rule(",
+ " implementation=_dep_rule_impl,",
+ ")");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:rules.bzl', 'top_level_rule', 'dep_rule')",
+ "",
+ "top_level_rule(name = 'tl', my_dep=':d')",
+ "",
+ "dep_rule(name = 'd')");
+
+ getConfiguredTarget("//test/starlark:tl");
+ assertContainsEvent(
+ "My Dep Providers: <target //test/starlark:d, keys:[FooInfo, BarInfo, OutputGroupInfo]>");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputFunctionPrints() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " print('implementation', ctx.label)",
+ " files = [ctx.outputs.o]",
+ " ctx.actions.run_shell(",
+ " outputs = files,",
+ " command = 'echo')",
+ "",
+ "def output_func(name):",
+ " print('output function', name)",
+ " return {'o': name + '.txt'}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " outputs = output_func)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+
+ getConfiguredTarget("//test/starlark:cr");
+ assertContainsEvent("output function cr");
+ assertContainsEvent("implementation //test/starlark:cr");
+ }
+
+ @Test
+ public void testNoOutputAttrDefault() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def custom_rule_impl(ctx):",
+ " out_file = ctx.actions.declare_file(ctx.attr._o1.name)",
+ " ctx.actions.write(output=out_file, content='hi')",
+ " return [MyInfo(o1=ctx.attr._o1)]",
+ "",
+ "def output_fn():",
+ " return Label('//test/starlark:foo.txt')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'_o1': attr.output(default = output_fn)})");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("got unexpected keyword argument 'default'");
+ }
+
+ @Test
+ public void testNoOutputListAttrDefault() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return []",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'outs': attr.output_list(default = [])})");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("got unexpected keyword argument 'default'");
+ }
+
+ @Test
+ public void testRuleClassNonMandatoryEmptyOutputs() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def custom_rule_impl(ctx):",
+ " return [MyInfo(",
+ " o1=ctx.outputs.o1,",
+ " o2=ctx.outputs.o2)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'o1': attr.output(), 'o2': attr.output_list()})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+ StructImpl myInfo = getMyInfoFromTarget(target);
+ assertThat(myInfo.getValue("o1")).isEqualTo(Starlark.NONE);
+ assertThat(myInfo.getValue("o2")).isEqualTo(StarlarkList.empty());
+ }
+
+ @Test
+ public void testRuleClassImplicitAndExplicitOutputNamesCollide() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return []",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'o': attr.output_list()},",
+ " outputs = {'o': '%{name}.txt'})");
+
+ checkError(
+ "test/starlark",
+ "cr",
+ "Multiple outputs with the same key: o",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', o = [':bar.txt'])");
+ }
+
+ @Test
+ public void testRuleClassDefaultFilesToBuild() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " files = [ctx.outputs.o]",
+ " ctx.actions.run_shell(",
+ " outputs = files,",
+ " command = 'echo')",
+ " ftb = depset(files)",
+ " for i in ctx.outputs.out:",
+ " ctx.actions.write(output=i, content='hi there')",
+ "",
+ "def output_func(attr1):",
+ " return {'o': attr1 + '.txt'}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'attr1': attr.string(),",
+ " 'out': attr.output_list()",
+ " },",
+ " outputs = output_func)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = 'bar', out=['other'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("bar.txt", "other")
+ .inOrder();
+ }
+
+ @Test
+ public void rulesReturningDeclaredProviders() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1)]",
+ "my_rule = rule(_impl)"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
+ "my_provider");
+ StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
+ assertThat(declaredProvider).isNotNull();
+ assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
+ assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+ }
+
+ @Test
+ public void rulesReturningDeclaredProvidersCompatMode() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1)]",
+ "my_rule = rule(_impl)");
+ scratch.file(
+ "test/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
+ "my_provider");
+ StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
+ assertThat(declaredProvider).isNotNull();
+ assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
+ assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+ }
+
+ @Test
+ public void testRuleReturningUnwrappedDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "def _impl(ctx):",
+ " return my_provider(x = 1)",
+ "my_rule = rule(_impl)"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
+ "my_provider");
+ StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
+ assertThat(declaredProvider).isNotNull();
+ assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
+ assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+ }
+
+ @Test
+ public void testConflictingProviderKeys_fromStruct_disallowed() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
+ "my_rule = rule(_impl)");
+
+ checkError(
+ "test",
+ "r",
+ "Multiple conflicting returned providers with key my_provider",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')");
+ }
+
+ @Test
+ public void testConflictingProviderKeys_fromIterable_disallowed() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
+ "my_rule = rule(_impl)"
+ );
+
+ checkError(
+ "test",
+ "r",
+ "Multiple conflicting returned providers with key my_provider",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')");
+ }
+
+ @Test
+ public void testRecursionDetection() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def _impl(ctx):",
+ " _impl(ctx)",
+ "empty = rule(implementation = _impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'empty')",
+ "empty(name = 'test_target')");
+
+ getConfiguredTarget("//test/starlark:test_target");
+ assertContainsEvent("function '_impl' called recursively");
+ }
+
+ @Test
+ public void testBadCallbackFunction() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl", "def impl(): return 0", "", "custom_rule = rule(impl)");
+
+ checkError(
+ "test/starlark",
+ "cr",
+ "impl() does not accept positional arguments",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+ }
+
+ @Test
+ public void testRuleClassImplicitOutputFunctionBadAttr() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return None",
+ "",
+ "def output_func(bad_attr):",
+ " return {'a': bad_attr}",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.string()},",
+ " outputs = output_func)");
+
+ checkError(
+ "test/starlark",
+ "cr",
+ "Attribute 'bad_attr' either doesn't exist or uses a select()",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = 'bar')");
+ }
+
+ @Test
+ public void testHelperFunctionInRuleImplementation() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def helper_func(attr1):",
+ " return depset(attr1)",
+ "",
+ "def custom_rule_impl(ctx):",
+ " attr1 = ctx.files.attr1",
+ " ftb = helper_func(attr1)",
+ " return [DefaultInfo(runfiles = ctx.runfiles(), files = ftb)]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'attr1': attr.label_list(mandatory=True, allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr', attr1 = [':a.txt'])");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:cr");
+
+ assertThat(target.getLabel().toString()).isEqualTo("//test/starlark:cr");
+ assertThat(
+ ActionsTestUtil.baseArtifactNames(
+ target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("a.txt");
+ }
+
+ @Test
+ public void testMultipleLoadsOfSameRule() throws Exception {
+ scratch.file("test/starlark/BUILD");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return None",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'dep': attr.label_list(allow_files=True)})");
+
+ scratch.file(
+ "test/starlark1/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "custom_rule(name = 'cr1')");
+
+ scratch.file(
+ "test/starlark2/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "custom_rule(name = 'cr2', dep = ['//test/starlark1:cr1'])");
+
+ getConfiguredTarget("//test/starlark2:cr2");
+ }
+
+ @Test
+ public void testFunctionGeneratingRules() throws Exception {
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def impl(ctx): return None",
+ "def gen(): return rule(impl)",
+ "r = gen()",
+ "s = gen()");
+
+ scratch.file(
+ "test/starlark/BUILD", "load(':extension.bzl', 'r', 's')",
+ "r(name = 'r')", "s(name = 's')");
+
+ getConfiguredTarget("//test/starlark:r");
+ getConfiguredTarget("//test/starlark:s");
+ }
+
+ @Test
+ public void testLoadInStarlark() throws Exception {
+ scratch.file("test/starlark/implementation.bzl", "def custom_rule_impl(ctx):", " return None");
+
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//test/starlark:implementation.bzl', 'custom_rule_impl')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " attrs = {'dep': attr.label_list(allow_files=True)})");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "custom_rule(name = 'cr')");
+
+ getConfiguredTarget("//test/starlark:cr");
+ }
+
+ @Test
+ public void testRuleAliasing() throws Exception {
+ scratch.file(
+ "test/starlark/implementation.bzl",
+ "def impl(ctx): return []",
+ "custom_rule = rule(implementation = impl)");
+
+ scratch.file(
+ "test/starlark/ext.bzl",
+ "load('//test/starlark:implementation.bzl', 'custom_rule')",
+ "def impl(ctx): return []",
+ "custom_rule1 = rule(implementation = impl)",
+ "custom_rule2 = custom_rule1",
+ "custom_rule3 = custom_rule");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ext.bzl', 'custom_rule1', 'custom_rule2', 'custom_rule3')",
+ "custom_rule4 = custom_rule3",
+ "custom_rule1(name = 'cr1')",
+ "custom_rule2(name = 'cr2')",
+ "custom_rule3(name = 'cr3')",
+ "custom_rule4(name = 'cr4')");
+
+ getConfiguredTarget("//test/starlark:cr1");
+ getConfiguredTarget("//test/starlark:cr2");
+ getConfiguredTarget("//test/starlark:cr3");
+ getConfiguredTarget("//test/starlark:cr4");
+ }
+
+ @Test
+ public void testRecursiveLoad() throws Exception {
+ scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext1.bzl', 'symbol2')");
+
+ scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol1')");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ext1.bzl', 'custom_rule')",
+ "genrule(name = 'rule')");
+
+ reporter.removeHandler(failFastHandler);
+ assertThrows(BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
+ assertContainsEvent(
+ "cycle detected in extension files: \n"
+ + " test/starlark/BUILD\n"
+ + ".-> //test/starlark:ext1.bzl\n"
+ + "| //test/starlark:ext2.bzl\n"
+ + "`-- //test/starlark:ext1.bzl");
+ }
+
+ @Test
+ public void testRecursiveLoad2() throws Exception {
+ scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
+ scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext3.bzl', 'symbol3')");
+ scratch.file("test/starlark/ext3.bzl", "load('//test/starlark:ext4.bzl', 'symbol4')");
+ scratch.file("test/starlark/ext4.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ext1.bzl', 'custom_rule')",
+ "genrule(name = 'rule')");
+
+ reporter.removeHandler(failFastHandler);
+ assertThrows(BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
+ assertContainsEvent(
+ "cycle detected in extension files: \n"
+ + " test/starlark/BUILD\n"
+ + " //test/starlark:ext1.bzl\n"
+ + ".-> //test/starlark:ext2.bzl\n"
+ + "| //test/starlark:ext3.bzl\n"
+ + "| //test/starlark:ext4.bzl\n"
+ + "`-- //test/starlark:ext2.bzl");
+ }
+
+ @Test
+ public void testLoadSymbolTypo() throws Exception {
+ scratch.file("test/starlark/ext1.bzl", "myvariable = 2");
+
+ scratch.file("test/starlark/BUILD", "load('//test/starlark:ext1.bzl', 'myvariables')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:test_target");
+ assertContainsEvent(
+ "file '//test/starlark:ext1.bzl' does not contain symbol 'myvariables' "
+ + "(did you mean 'myvariable'?)");
+ }
+
+ @Test
+ public void testOutputsObjectOrphanExecutableReportError() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " o = ctx.outputs.executable",
+ " return [DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:xxx");
+ assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
+ assertContainsEvent("The following files have no generating action:");
+ assertContainsEvent("test/xxx");
+ }
+
+ @Test
+ public void testCustomExecutableUsed() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " o = ctx.actions.declare_file('x.sh')",
+ " ctx.actions.write(o, 'echo Stuff', is_executable = True)",
+ " return [DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
+ Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
+ assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
+ }
+
+ @Test
+ public void testCustomAndDefaultExecutableReportsError() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " e = ctx.outputs.executable",
+ " o = ctx.actions.declare_file('x.sh')",
+ " ctx.actions.write(o, 'echo Stuff', is_executable = True)",
+ " return [DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:xxx");
+ assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
+ assertContainsEvent(
+ "/workspace/test/rule.bzl:5:23: The rule 'my_rule' both accesses "
+ + "'ctx.outputs.executable' and provides a different executable 'test/x.sh'. "
+ + "Do not use 'ctx.output.executable'.");
+ }
+
+
+ @Test
+ public void testCustomExecutableStrNoEffect() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " o = ctx.actions.declare_file('x.sh')",
+ " ctx.actions.write(o, 'echo Stuff', is_executable = True)",
+ " print(str(ctx.outputs))",
+ " return [DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
+ Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
+ assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
+ }
+
+ @Test
+ public void testCustomExecutableDirNoEffect() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " o = ctx.actions.declare_file('x.sh')",
+ " ctx.actions.write(o, 'echo Stuff', is_executable = True)",
+ " print(dir(ctx.outputs))",
+ " return [DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:xxx");
+ Artifact executable = configuredTarget.getProvider(FilesToRunProvider.class).getExecutable();
+ assertThat(executable.getRootRelativePathString()).isEqualTo("test/x.sh");
+ }
+
+ @Test
+ public void testOutputsObjectInDifferentRuleInaccessible() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "PInfo = provider(fields = ['outputs'])",
+ "def _impl(ctx):",
+ " o = ctx.actions.declare_file('x.sh')",
+ " ctx.actions.write(o, 'echo Stuff', is_executable = True)",
+ " return [PInfo(outputs = ctx.outputs), DefaultInfo(executable = o)]",
+ "my_rule = rule(_impl, executable = True)",
+ "def _dep_impl(ctx):",
+ " o = ctx.attr.dep[PInfo].outputs.executable",
+ " pass",
+ "my_dep_rule = rule(_dep_impl, attrs = { 'dep' : attr.label() })"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule', 'my_dep_rule')",
+ "my_rule(name = 'xxx')",
+ "my_dep_rule(name = 'yyy', dep = ':xxx')"
+ );
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:yyy");
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:12: in my_dep_rule rule //test:yyy: ");
+ assertContainsEvent("File \"/workspace/test/rule.bzl\", line 8, in _dep_impl");
+ assertContainsEvent("ctx.attr.dep[PInfo].outputs.executable");
+ assertContainsEvent("cannot access outputs of rule '//test:xxx' outside "
+ + "of its own rule implementation function");
+ }
+
+ @Test
+ public void testOutputsObjectStringRepresentation() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "PInfo = provider(fields = ['outputs', 's'])",
+ "def _impl(ctx):",
+ " ctx.actions.write(ctx.outputs.executable, 'echo Stuff', is_executable = True)",
+ " ctx.actions.write(ctx.outputs.other, 'Other')",
+ " return [PInfo(outputs = ctx.outputs, s = str(ctx.outputs))]",
+ "my_rule = rule(_impl, executable = True, outputs = { 'other' : '%{name}.other' })",
+ "def _dep_impl(ctx):",
+ " return [PInfo(s = str(ctx.attr.dep[PInfo].outputs))]",
+ "my_dep_rule = rule(_dep_impl, attrs = { 'dep' : attr.label() })"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule', 'my_dep_rule')",
+ "my_rule(name = 'xxx')",
+ "my_dep_rule(name = 'yyy', dep = ':xxx')"
+ );
+
+ StarlarkProvider.Key pInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:rule.bzl", ImmutableMap.of()), "PInfo");
+
+ ConfiguredTarget targetXXX = getConfiguredTarget("//test:xxx");
+ StructImpl structXXX = (StructImpl) targetXXX.get(pInfoKey);
+
+ assertThat(structXXX.getValue("s"))
+ .isEqualTo(
+ "ctx.outputs(executable = <generated file test/xxx>, "
+ + "other = <generated file test/xxx.other>)");
+
+ ConfiguredTarget targetYYY = getConfiguredTarget("//test:yyy");
+ StructImpl structYYY = (StructImpl) targetYYY.get(pInfoKey);
+ assertThat(structYYY.getValue("s"))
+ .isEqualTo("ctx.outputs(for //test:xxx)");
+ }
+
+ @Test
+ public void testExecutableRuleWithNoExecutableReportsError() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " pass",
+ "my_rule = rule(_impl, executable = True)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':rule.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')"
+ );
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:xxx");
+ assertContainsEvent("ERROR /workspace/test/BUILD:2:8: in my_rule rule //test:xxx: ");
+ assertContainsEvent("/rule.bzl:1:5: The rule 'my_rule' is executable. "
+ + "It needs to create an executable File and pass it as the 'executable' "
+ + "parameter to the DefaultInfo it returns.");
+ }
+
+ @Test
+ public void testExecutableFromDifferentRuleIsForbidden() throws Exception {
+ scratch.file(
+ "pkg/BUILD",
+ "sh_binary(name = 'tryme',",
+ " srcs = [':tryme.sh'],",
+ " visibility = ['//visibility:public'],",
+ ")");
+
+ scratch.file(
+ "src/rulez.bzl",
+ "def _impl(ctx):",
+ " return [DefaultInfo(executable = ctx.executable.runme,",
+ " files = depset([ctx.executable.runme]),",
+ " )]",
+ "r = rule(_impl,",
+ " executable = True,",
+ " attrs = {",
+ " 'runme' : attr.label(executable = True, mandatory = True, cfg = 'host'),",
+ " }",
+ ")");
+
+ scratch.file(
+ "src/BUILD", "load(':rulez.bzl', 'r')", "r(name = 'r_tools', runme = '//pkg:tryme')");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//src:r_tools");
+ assertContainsEvent(
+ "/workspace/src/rulez.bzl:2:23: 'executable' provided by an executable"
+ + " rule 'r' should be created by the same rule.");
+ }
+
+ @Test
+ public void testFileAndDirectory() throws Exception {
+ scratch.file(
+ "ext.bzl",
+ "def _extrule(ctx):",
+ " dir = ctx.actions.declare_directory('foo/bar/baz')",
+ " ctx.actions.run_shell(",
+ " outputs = [dir],",
+ " command = 'mkdir -p ' + dir.path + ' && echo wtf > ' + dir.path + '/wtf.txt')",
+ "",
+ "extrule = rule(",
+ " _extrule,",
+ " outputs = {",
+ " 'out': 'foo/bar/baz',",
+ " },",
+ ")");
+ scratch.file(
+ "BUILD", //
+ "load(':ext.bzl', 'extrule')",
+ "",
+ "extrule(",
+ " name = 'test'",
+ ")");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//:test");
+ assertContainsEvent("ERROR /workspace/BUILD:3:8: in extrule rule //:test:");
+ assertContainsEvent("he following directories were also declared as files:");
+ assertContainsEvent("foo/bar/baz");
+ }
+
+ @Test
+ public void testEnvironmentConstraintsFromStarlarkRule() throws Exception {
+ scratch.file(
+ "buildenv/foo/BUILD",
+ "environment_group(name = 'env_group',",
+ " defaults = [':default'],",
+ " environments = ['default', 'other'])",
+ "environment(name = 'default')",
+ "environment(name = 'other')");
+ // The example Starlark rule explicitly provides the MyProvider provider as a regression test
+ // for a bug where a Starlark rule with unsatisfied constraints but explicit providers would
+ // result in Bazel throwing a null pointer exception.
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "MyProvider = provider()",
+ "",
+ "def _impl(ctx):",
+ " return [MyProvider(foo = 'bar')]",
+ "my_rule = rule(implementation = _impl,",
+ " attrs = { 'deps' : attr.label_list() },",
+ " provides = [MyProvider])");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'my_rule')",
+ "java_library(name = 'dep', srcs = ['a.java'], restricted_to = ['//buildenv/foo:other'])",
+ "my_rule(name='my', deps = [':dep'])");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test/starlark:my")).isNull();
+ assertContainsEvent(
+ "//test/starlark:dep doesn't support expected environment: //buildenv/foo:default");
+ }
+
+ @Test
+ public void testAnalysisFailureInfo() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " fail('This Is My Failure Message')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'r')");
+
+ useConfiguration("--allow_analysis_failures=true");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:r");
+ AnalysisFailureInfo info =
+ (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
+ AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
+ assertThat(failure.getMessage()).contains("This Is My Failure Message");
+ assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
+ }
+
+ @Test
+ public void testAnalysisFailureInfo_forTest() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " fail('This Is My Failure Message')",
+ "",
+ "custom_test = rule(implementation = custom_rule_impl,",
+ " test = True)");
+
+ scratch.file(
+ "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");
+
+ useConfiguration("--allow_analysis_failures=true");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:r");
+ AnalysisFailureInfo info =
+ (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
+ AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
+ assertThat(failure.getMessage()).contains("This Is My Failure Message");
+ assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
+ }
+
+ @Test
+ public void testAnalysisFailureInfoWithOutput() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " fail('This Is My Failure Message')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl,",
+ " outputs = {'my_output': '%{name}.txt'})");
+
+ scratch.file(
+ "test/BUILD", "load('//test:extension.bzl', 'custom_rule')", "", "custom_rule(name = 'r')");
+
+ useConfiguration("--allow_analysis_failures=true");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:r");
+ AnalysisFailureInfo info =
+ (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
+ AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
+ assertThat(failure.getMessage()).contains("This Is My Failure Message");
+ assertThat(failure.getLabel()).isEqualTo(Label.parseAbsoluteUnchecked("//test:r"));
+ }
+
+ @Test
+ public void testTransitiveAnalysisFailureInfo() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " fail('This Is My Failure Message')",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)",
+ "",
+ "def depending_rule_impl(ctx):",
+ " return []",
+ "",
+ "depending_rule = rule(implementation = depending_rule_impl,",
+ " attrs = {'deps' : attr.label_list()})");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule', 'depending_rule')",
+ "",
+ "custom_rule(name = 'one')",
+ "custom_rule(name = 'two')",
+ "depending_rule(name = 'failures_are_direct_deps',",
+ " deps = [':one', ':two'])",
+ "depending_rule(name = 'failures_are_indirect_deps',",
+ " deps = [':failures_are_direct_deps'])");
+
+ useConfiguration("--allow_analysis_failures=true");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:failures_are_indirect_deps");
+ AnalysisFailureInfo info =
+ (AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
+
+ Correspondence<AnalysisFailure, AnalysisFailure> correspondence =
+ Correspondence.from(
+ (actual, expected) ->
+ actual.getLabel().equals(expected.getLabel())
+ && actual.getMessage().contains(expected.getMessage()),
+ "is equivalent to");
+
+ AnalysisFailure expectedOne =
+ new AnalysisFailure(
+ Label.parseAbsoluteUnchecked("//test:one"), "This Is My Failure Message");
+ AnalysisFailure expectedTwo =
+ new AnalysisFailure(
+ Label.parseAbsoluteUnchecked("//test:two"), "This Is My Failure Message");
+
+ assertThat(info.getCauses().getSet(AnalysisFailure.class).toList())
+ .comparingElementsUsing(correspondence)
+ .containsExactly(expectedOne, expectedTwo);
+ }
+
+ @Test
+ public void testTestResultInfo() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'r')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:r");
+ AnalysisTestResultInfo info =
+ (AnalysisTestResultInfo) target.get(AnalysisTestResultInfo.STARLARK_CONSTRUCTOR.getKey());
+ assertThat(info.getSuccess()).isTrue();
+ assertThat(info.getMessage()).isEqualTo("message contents");
+ }
+
+ @Test
+ public void testAnalysisTestRuleWithActionRegistration() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " out_file = ctx.actions.declare_file('file.txt')",
+ " ctx.actions.write(output=out_file, content='hi')",
+ "",
+ "custom_test = rule(implementation = custom_rule_impl, analysis_test = True)");
+
+ scratch.file(
+ "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent(
+ "implementation function of a rule with analysis_test=true may not register actions");
+ }
+
+ @Test
+ public void testAnalysisTestRuleWithFlag() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "",
+ "custom_test = rule(implementation = custom_rule_impl, analysis_test = True)");
+
+ scratch.file(
+ "test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test:r");
+ AnalysisTestResultInfo info =
+ (AnalysisTestResultInfo) target.get(AnalysisTestResultInfo.STARLARK_CONSTRUCTOR.getKey());
+ assertThat(info.getSuccess()).isTrue();
+ assertThat(info.getMessage()).isEqualTo("message contents");
+
+ // TODO(cparsons): Verify implicit action registration via AnalysisTestResultInfo.
+ }
+
+ @Test
+ public void testAnalysisTestTransitionOnAnalysisTest() throws Exception {
+ useConfiguration("--copt=yeehaw");
+
+ scratch.file(
+ "test/extension.bzl",
+ "MyInfo = provider()",
+ "MyDep = provider()",
+ "",
+ "def outer_rule_impl(ctx):",
+ " return [MyInfo(copts = ctx.fragments.cpp.copts),",
+ " MyDep(info = ctx.attr.dep[0][MyInfo]),",
+ " AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "def inner_rule_impl(ctx):",
+ " return [MyInfo(copts = ctx.fragments.cpp.copts)]",
+ "",
+ "my_transition = analysis_test_transition(",
+ " settings = {",
+ " '//command_line_option:copt' : ['cowabunga'] }",
+ ")",
+ "inner_rule = rule(implementation = inner_rule_impl,",
+ " fragments = ['cpp'])",
+ "outer_rule_test = rule(",
+ " implementation = outer_rule_impl,",
+ " fragments = ['cpp'],",
+ " analysis_test = True,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " })");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'inner_rule', 'outer_rule_test')",
+ "",
+ "inner_rule(name = 'inner')",
+ "outer_rule_test(name = 'r', dep = ':inner')");
+
+ StarlarkProvider.Key myInfoKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:extension.bzl", ImmutableMap.of()), "MyInfo");
+ StarlarkProvider.Key myDepKey =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:extension.bzl", ImmutableMap.of()), "MyDep");
+
+ ConfiguredTarget outerTarget = getConfiguredTarget("//test:r");
+ StructImpl outerInfo = (StructImpl) outerTarget.get(myInfoKey);
+ StructImpl outerDepInfo = (StructImpl) outerTarget.get(myDepKey);
+ StructImpl innerInfo = (StructImpl) outerDepInfo.getValue("info");
+
+ assertThat((Sequence) outerInfo.getValue("copts")).containsExactly("yeehaw");
+ assertThat((Sequence) innerInfo.getValue("copts")).containsExactly("cowabunga");
+ }
+
+ @Test
+ public void testAnalysisTestTransitionOnNonAnalysisTest() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return []",
+ "my_transition = analysis_test_transition(",
+ " settings = {",
+ " '//command_line_option:test_arg' : ['yeehaw'] }",
+ ")",
+ "",
+ "custom_rule = rule(",
+ " implementation = custom_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " })");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent(
+ "Only rule definitions with analysis_test=True may have attributes "
+ + "with analysis_test_transition transitions");
+ }
+
+ @Test
+ public void testBuildSettingRule_flag() throws Exception {
+ scratch.file("test/rules.bzl",
+ "def _impl(ctx): return None",
+ "build_setting_rule = rule(_impl, build_setting = config.string(flag=True))");
+ scratch.file("test/BUILD",
+ "load('//test:rules.bzl', 'build_setting_rule')",
+ "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");
+
+ BuildSetting buildSetting =
+ getTarget("//test:my_build_setting")
+ .getAssociatedRule()
+ .getRuleClassObject()
+ .getBuildSetting();
+
+ assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
+ assertThat(buildSetting.isFlag()).isTrue();
+ }
+
+ @Test
+ public void testBuildSettingRule_settingByDefault() throws Exception {
+ scratch.file("test/rules.bzl",
+ "def _impl(ctx): return None",
+ "build_setting_rule = rule(_impl, build_setting = config.string())");
+ scratch.file("test/BUILD",
+ "load('//test:rules.bzl', 'build_setting_rule')",
+ "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");
+
+ BuildSetting buildSetting =
+ getTarget("//test:my_build_setting")
+ .getAssociatedRule()
+ .getRuleClassObject()
+ .getBuildSetting();
+
+ assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
+ assertThat(buildSetting.isFlag()).isFalse();
+ }
+
+ @Test
+ public void testBuildSettingRule_settingByFlagParameter() throws Exception {
+ scratch.file("test/rules.bzl",
+ "def _impl(ctx): return None",
+ "build_setting_rule = rule(_impl, build_setting = config.string(flag=False))");
+ scratch.file("test/BUILD",
+ "load('//test:rules.bzl', 'build_setting_rule')",
+ "build_setting_rule(name = 'my_build_setting', build_setting_default = 'default')");
+
+ BuildSetting buildSetting =
+ getTarget("//test:my_build_setting")
+ .getAssociatedRule()
+ .getRuleClassObject()
+ .getBuildSetting();
+
+ assertThat(buildSetting.getType()).isEqualTo(Type.STRING);
+ assertThat(buildSetting.isFlag()).isFalse();
+ }
+
+
+ @Test
+ public void testBuildSettingRule_noDefault() throws Exception {
+ scratch.file("test/rules.bzl",
+ "def _impl(ctx): return None",
+ "build_setting_rule = rule(_impl, build_setting = config.string())");
+ scratch.file("test/BUILD",
+ "load('//test:rules.bzl', 'build_setting_rule')",
+ "build_setting_rule(name = 'my_build_setting')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_build_setting");
+ assertContainsEvent("missing value for mandatory attribute "
+ + "'build_setting_default' in 'build_setting_rule' rule");
+
+ }
+
+ @Test
+ public void testAnalysisTestCannotDependOnAnalysisTest() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "",
+ "def analysis_test_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "def middle_rule_impl(ctx):",
+ " return []",
+ "def inner_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "",
+ "my_transition = analysis_test_transition(",
+ " settings = {",
+ " '//command_line_option:test_arg' : ['yeehaw'] }",
+ ")",
+ "",
+ "inner_rule_test = rule(",
+ " implementation = analysis_test_rule_impl,",
+ " analysis_test = True,",
+ ")",
+ "middle_rule = rule(",
+ " implementation = middle_rule_impl,",
+ " attrs = {'dep': attr.label()}",
+ ")",
+ "outer_rule_test = rule(",
+ " implementation = analysis_test_rule_impl,",
+ " analysis_test = True,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " })");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'outer_rule_test', 'middle_rule', 'inner_rule_test')",
+ "",
+ "outer_rule_test(name = 'outer', dep = ':middle')",
+ "middle_rule(name = 'middle', dep = ':inner')",
+ "inner_rule_test(name = 'inner')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:outer");
+ assertContainsEvent(
+ "analysis_test rule '//test:inner' cannot be transitively "
+ + "depended on by another analysis test rule");
+ }
+
+ @Test
+ public void testAnalysisTestOverDepsLimit() throws Exception {
+ setupAnalysisTestDepsLimitTest(10, 12, true);
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent(
+ "analysis test rule excedeed maximum dependency edge count. " + "Count: 14. Limit is 10.");
+ }
+
+ @Test
+ public void testAnalysisTestUnderDepsLimit() throws Exception {
+ setupAnalysisTestDepsLimitTest(10, 8, true);
+
+ assertThat(getConfiguredTarget("//test:r")).isNotNull();
+ }
+
+ @Test
+ public void testAnalysisDepsLimitOnlyForTransition() throws Exception {
+ setupAnalysisTestDepsLimitTest(3, 10, false);
+
+ assertThat(getConfiguredTarget("//test:r")).isNotNull();
+ }
+
+ private void setupAnalysisTestDepsLimitTest(
+ int limit, int dependencyChainSize, boolean useTransition) throws Exception {
+ Preconditions.checkArgument(dependencyChainSize > 2);
+ useConfiguration("--analysis_testing_deps_limit=" + limit);
+
+ String transitionDefinition;
+ if (useTransition) {
+ transitionDefinition =
+ "my_transition = analysis_test_transition("
+ + "settings = {'//command_line_option:test_arg' : ['yeehaw'] })";
+ } else {
+ transitionDefinition = "my_transition = None";
+ }
+
+ scratch.file(
+ "test/extension.bzl",
+ "",
+ "def outer_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "def dep_rule_impl(ctx):",
+ " return []",
+ "",
+ transitionDefinition,
+ "",
+ "dep_rule = rule(",
+ " implementation = dep_rule_impl,",
+ " attrs = {'deps': attr.label_list()}",
+ ")",
+ "outer_rule_test = rule(",
+ " implementation = outer_rule_impl,",
+ " fragments = ['java'],",
+ " analysis_test = True,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " })");
+
+ // Create a chain of targets where 'innerN' depends on 'inner{N+1}' until the max length.
+ StringBuilder dependingRulesChain = new StringBuilder();
+ for (int i = 0; i < dependencyChainSize - 1; i++) {
+ // Each dep_rule target also depends on the leaf.
+ // The leaf should not be counted multiple times.
+ dependingRulesChain.append(
+ String.format(
+ "dep_rule(name = 'inner%s', deps = [':inner%s', ':inner%s'])\n",
+ i, (i + 1), dependencyChainSize));
+ }
+ dependingRulesChain.append(
+ String.format(
+ "dep_rule(name = 'inner%s', deps = [':inner%s'])\n",
+ dependencyChainSize - 1, dependencyChainSize));
+ dependingRulesChain.append(String.format("dep_rule(name = 'inner%s')", dependencyChainSize));
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'dep_rule', 'outer_rule_test')",
+ "",
+ "outer_rule_test(name = 'r', dep = ':inner0')",
+ dependingRulesChain.toString());
+ }
+
+ @Test
+ public void testBadAllowlistTransition_onNonLabelAttr() throws Exception {
+ String allowlistAttributeName =
+ FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "",
+ "my_rule = rule(_impl, attrs = {'"
+ + allowlistAttributeName
+ + "':attr.string(default = 'blah')})");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'my_rule')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_rule");
+ assertContainsEvent("_allowlist_function_transition attribute must be a label type");
+ }
+
+ @Test
+ public void testBadAllowlistTransition_noDefaultValue() throws Exception {
+ String allowlistAttributeName =
+ FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "",
+ "my_rule = rule(_impl, attrs = {'" + allowlistAttributeName + "':attr.label()})");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'my_rule')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_rule");
+ assertContainsEvent("_allowlist_function_transition attribute must have a default value");
+ }
+
+ @Test
+ public void testBadAllowlistTransition_wrongDefaultValue() throws Exception {
+ String allowlistAttributeName =
+ FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME.replace("$", "_");
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "",
+ "my_rule = rule(_impl, attrs = {'"
+ + allowlistAttributeName
+ + "':attr.label(default = Label('//test:my_other_rule'))})");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule')",
+ "my_rule(name = 'my_rule')",
+ "my_rule(name = 'my_other_rule')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_rule");
+ assertContainsEvent(
+ " _allowlist_function_transition attribute (//test:my_other_rule) does not have the"
+ + " expected value");
+ }
+
+ @Test
+ public void testBadAnalysisTestRule_notAnalysisTest() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "",
+ "def outer_rule_impl(ctx):",
+ " return [AnalysisTestResultInfo(success = True, message = 'message contents')]",
+ "def dep_rule_impl(ctx):",
+ " return []",
+ "",
+ "my_transition = analysis_test_transition(",
+ " settings = {",
+ " '//command_line_option:test_arg' : ['yeehaw'] }",
+ ")",
+ "dep_rule = rule(",
+ " implementation = dep_rule_impl,",
+ " attrs = {'dep': attr.label()}",
+ ")",
+ "outer_rule = rule(",
+ " implementation = outer_rule_impl,",
+ "# analysis_test = True,",
+ " fragments = ['java'],",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " })");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'dep_rule', 'outer_rule_test')",
+ "",
+ "outer_rule(name = 'r', dep = ':inner')",
+ "dep_rule(name = 'inner')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:outer_rule");
+ assertContainsEvent(
+ "Only rule definitions with analysis_test=True may have attributes with "
+ + "analysis_test_transition transitions");
+ }
+
+ @Test
+ public void testBadAllowlistTransition_noAllowlist() throws Exception {
+ scratch.file(
+ "tools/allowlists/function_transition_allowlist/BUILD",
+ "package_group(",
+ " name = 'function_transition_allowlist',",
+ " packages = [",
+ " '//test/...',",
+ " ],",
+ ")");
+ scratch.file(
+ "test/rules.bzl",
+ "def transition_func(settings):",
+ " return {'t0': {'//command_line_option:cpu': 'k8'}}",
+ "my_transition = transition(implementation = transition_func, inputs = [],",
+ " outputs = ['//command_line_option:cpu'])",
+ "def _my_rule_impl(ctx): ",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ "# '_allowlist_function_transition': attr.label(",
+ "# default = '//tools/allowlists/function_transition_allowlist',",
+ "# ),",
+ " })",
+ "def _simple_rule_impl(ctx):",
+ " return []",
+ "simple_rule = rule(_simple_rule_impl)");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule', 'simple_rule')",
+ "my_rule(name = 'my_rule', dep = ':dep')",
+ "simple_rule(name = 'dep')");
+ setStarlarkSemanticsOptions("--experimental_starlark_config_transitions");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_rule");
+ assertContainsEvent("Use of Starlark transition without allowlist");
+ }
+
+ @Test
+ public void testPrintFromTransitionImpl() throws Exception {
+ setStarlarkSemanticsOptions("--experimental_starlark_config_transitions");
+ scratch.file(
+ "tools/allowlists/function_transition_allowlist/BUILD",
+ "package_group(",
+ " name = 'function_transition_allowlist',",
+ " packages = [",
+ " '//test/...',",
+ " ],",
+ ")");
+ scratch.file(
+ "test/rules.bzl",
+ "def _transition_impl(settings, attr):",
+ " print('printing from transition impl', settings['//command_line_option:test_arg'])",
+ " return {'//command_line_option:test_arg': "
+ + "settings['//command_line_option:test_arg']+['meow']}",
+ "my_transition = transition(",
+ " implementation = _transition_impl,",
+ " inputs = ['//command_line_option:test_arg'],",
+ " outputs = ['//command_line_option:test_arg'],",
+ ")",
+ "def _rule_impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ " cfg = my_transition,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " '_allowlist_function_transition': attr.label(",
+ " default = '//tools/allowlists/function_transition_allowlist',",
+ " ),",
+ " }",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule')",
+ "my_rule(name = 'test', dep = ':dep')",
+ "my_rule(name = 'dep')");
+
+ useConfiguration("--test_arg=meow");
+
+ getConfiguredTarget("//test");
+ // Test print from top level transition
+ assertContainsEvent("printing from transition impl [\"meow\"]");
+ // Test print from dep transition
+ assertContainsEvent("printing from transition impl [\"meow\", \"meow\"]");
+ // Test print from (non-top level) rule class transition
+ assertContainsEvent("printing from transition impl [\"meow\", \"meow\", \"meow\"]");
+ }
+
+ @Test
+ public void testTransitionEquality() throws Exception {
+ setStarlarkSemanticsOptions("--experimental_starlark_config_transitions");
+ scratch.file(
+ "tools/allowlists/function_transition_allowlist/BUILD",
+ "package_group(",
+ " name = 'function_transition_allowlist',",
+ " packages = [",
+ " '//test/...',",
+ " ],",
+ ")");
+ scratch.file(
+ "test/rules.bzl",
+ "def _transition_impl(settings, attr):",
+ " return {'//command_line_option:test_arg': ['meow']}",
+ "my_transition = transition(",
+ " implementation = _transition_impl,",
+ " inputs = [],",
+ " outputs = ['//command_line_option:test_arg'],",
+ ")",
+ "def _rule_impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ " cfg = my_transition,",
+ " attrs = {",
+ " 'dep': attr.label(cfg = my_transition),",
+ " '_allowlist_function_transition': attr.label(",
+ " default = '//tools/allowlists/function_transition_allowlist',",
+ " ),",
+ " }",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule')",
+ "my_rule(name = 'test', dep = ':dep')",
+ "my_rule(name = 'dep')");
+
+ useConfiguration("--test_arg=meow");
+
+ StarlarkDefinedConfigTransition ruleTransition =
+ ((StarlarkAttributeTransitionProvider)
+ getTarget("//test")
+ .getAssociatedRule()
+ .getRuleClassObject()
+ .getAttributeByName("dep")
+ .getTransitionFactory())
+ .getStarlarkDefinedConfigTransitionForTesting();
+
+ StarlarkDefinedConfigTransition attrTransition =
+ ((StarlarkRuleTransitionProvider)
+ getTarget("//test").getAssociatedRule().getRuleClassObject().getTransitionFactory())
+ .getStarlarkDefinedConfigTransitionForTesting();
+
+ assertThat(ruleTransition).isEqualTo(attrTransition);
+ assertThat(attrTransition).isEqualTo(ruleTransition);
+ assertThat(ruleTransition.hashCode()).isEqualTo(attrTransition.hashCode());
+ }
+
+ @Test
+ public void testBadAllowlistTransition_allowlistNoCfg() throws Exception {
+ scratch.file(
+ "tools/allowlists/function_transition_allowlist/BUILD",
+ "package_group(",
+ " name = 'function_transition_allowlist',",
+ " packages = [",
+ " '//test/...',",
+ " ],",
+ ")");
+ scratch.file(
+ "test/rules.bzl",
+ "def _my_rule_impl(ctx): ",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ "# 'dep': attr.label(cfg = my_transition),",
+ " '_allowlist_function_transition': attr.label(",
+ " default = '//tools/allowlists/function_transition_allowlist',",
+ " ),",
+ " })",
+ "def _simple_rule_impl(ctx):",
+ " return []",
+ "simple_rule = rule(_simple_rule_impl)");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule', 'simple_rule')",
+ "my_rule(name = 'my_rule', dep = ':dep')",
+ "simple_rule(name = 'dep')");
+ setStarlarkSemanticsOptions("--experimental_starlark_config_transitions");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_rule");
+ assertContainsEvent("Unused function-based split transition allowlist");
+ }
+
+ @Test
+ public void testLicenseType() throws Exception {
+ // Note that attr.license is deprecated, and thus this test is subject to imminent removal.
+ // (See --incompatible_no_attr_license). However, this verifies that until the attribute
+ // is removed, values of the attribute are a valid Starlark type.
+ setStarlarkSemanticsOptions("--incompatible_no_attr_license=false");
+ scratch.file(
+ "test/rule.bzl",
+ "def _my_rule_impl(ctx): ",
+ " print(ctx.attr.my_license)",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ " 'my_license': attr.license(),",
+ " })");
+ scratch.file("test/BUILD", "load(':rule.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ getConfiguredTarget("//test:test");
+
+ assertContainsEvent("[none]");
+ }
+
+ @Test
+ public void testNativeModuleFields() throws Exception {
+ // Check that
+ scratch.file(
+ "test/file.bzl",
+ "def valid(s):",
+ " if not s[0].isalpha(): return False",
+ " for c in s.elems():",
+ " if not (c.isalpha() or c == '_' or c.isdigit()): return False",
+ " return True",
+ "",
+ "bad_names = [name for name in dir(native) if not valid(name)]",
+ "print('bad_names =', bad_names)");
+ scratch.file("test/BUILD", "load('//test:file.bzl', 'bad_names')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:anything");
+ assertContainsEvent("bad_names = []");
+ }
+
+ @Test
+ public void testDisallowStructProviderSyntax() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=true");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct()",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'cr')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:cr");
+ assertContainsEvent(
+ "Returning a struct from a rule implementation function is deprecated and will be "
+ + "removed soon. It may be temporarily re-enabled by setting "
+ + "--incompatible_disallow_struct_provider_syntax=false");
+ }
+
+ @Test
+ public void testDisableTargetProviderFields() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disable_target_provider_fields=true");
+ scratch.file(
+ "test/starlark/rule.bzl",
+ "MyProvider = provider()",
+ "",
+ "def _my_rule_impl(ctx): ",
+ " print(ctx.attr.dep.my_info)",
+ "def _dep_rule_impl(ctx): ",
+ " my_info = MyProvider(foo = 'bar')",
+ " return struct(my_info = my_info, providers = [my_info])",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " })",
+ "dep_rule = rule(implementation = _dep_rule_impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load(':rule.bzl', 'my_rule', 'dep_rule')",
+ "",
+ "my_rule(name = 'r', dep = ':d')",
+ "dep_rule(name = 'd')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:r");
+ assertContainsEvent(
+ "Accessing providers via the field syntax on structs is deprecated and will be removed "
+ + "soon. It may be temporarily re-enabled by setting "
+ + "--incompatible_disable_target_provider_fields=false. "
+ + "See https://github.com/bazelbuild/bazel/issues/9014 for details.");
+ }
+
+ // Verifies that non-provider fields on the 'target' type are still available even with
+ // --incompatible_disable_target_provider_fields.
+ @Test
+ public void testDisableTargetProviderFields_actionsField() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disable_target_provider_fields=true");
+ scratch.file(
+ "test/starlark/rule.bzl",
+ "MyProvider = provider()",
+ "",
+ "def _my_rule_impl(ctx): ",
+ " print(ctx.attr.dep.actions)",
+ "def _dep_rule_impl(ctx): ",
+ " my_info = MyProvider(foo = 'bar')",
+ " return struct(my_info = my_info, providers = [my_info])",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " })",
+ "dep_rule = rule(implementation = _dep_rule_impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load(':rule.bzl', 'my_rule', 'dep_rule')",
+ "",
+ "my_rule(name = 'r', dep = ':d')",
+ "dep_rule(name = 'd')");
+
+ assertThat(getConfiguredTarget("//test/starlark:r")).isNotNull();
+ }
+
+ @Test
+ public void testDisableTargetProviderFields_disabled() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disable_target_provider_fields=false");
+ scratch.file(
+ "test/starlark/rule.bzl",
+ "MyProvider = provider()",
+ "",
+ "def _my_rule_impl(ctx): ",
+ " print(ctx.attr.dep.my_info)",
+ "def _dep_rule_impl(ctx): ",
+ " my_info = MyProvider(foo = 'bar')",
+ " return struct(my_info = my_info, providers = [my_info])",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " })",
+ "dep_rule = rule(implementation = _dep_rule_impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load(':rule.bzl', 'my_rule', 'dep_rule')",
+ "",
+ "my_rule(name = 'r', dep = ':d')",
+ "dep_rule(name = 'd')");
+
+ assertThat(getConfiguredTarget("//test/starlark:r")).isNotNull();
+ }
+
+ @Test
+ public void testNoRuleOutputsParam() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_no_rule_outputs_param=true");
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _impl(ctx):",
+ " output = ctx.outputs.out",
+ " ctx.actions.write(output = output, content = 'hello')",
+ "",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " outputs = {\"out\": \"%{name}.txt\"})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:target");
+ assertContainsEvent(
+ "parameter 'outputs' is deprecated and will be removed soon. It may be temporarily "
+ + "re-enabled by setting --incompatible_no_rule_outputs_param=false");
+ }
+
+ @Test
+ public void testExecutableNotInRunfiles() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_file('exe')",
+ " ctx.actions.run_shell(outputs=[exe], command='touch exe')",
+ " runfile = ctx.actions.declare_file('rrr')",
+ " ctx.actions.run_shell(outputs=[runfile], command='touch rrr')",
+ " return struct(executable = exe, default_runfiles = ctx.runfiles(files = [runfile]))",
+ "my_rule = rule(implementation = _my_rule_impl, executable = True)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:target");
+ assertContainsEvent("exe not included in runfiles");
+ }
+
+ @Test
+ public void testCommandStringList() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_run_shell_command_string");
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_file('exe')",
+ " ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
+ " return []",
+ "my_rule = rule(implementation = _my_rule_impl)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:target");
+ assertContainsEvent("'command' must be of type string");
+ }
+
+ /** Starlark integration test that forces inlining. */
+ @RunWith(JUnit4.class)
+ public static class StarlarkIntegrationTestsWithInlineCalls extends StarlarkIntegrationTest {
+
+ @Override
+ protected boolean usesInliningBzlLoadFunction() {
+ return true;
+ }
+
+ @Override
+ @Test
+ public void testRecursiveLoad() throws Exception {
+ scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext1.bzl', 'symbol2')");
+
+ scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol1')");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ext1.bzl', 'custom_rule')",
+ "genrule(name = 'rule')");
+
+ reporter.removeHandler(failFastHandler);
+ BuildFileContainsErrorsException e =
+ assertThrows(
+ BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Starlark load cycle: [//test/starlark:ext1.bzl, //test/starlark:ext2.bzl]");
+ }
+
+ @Override
+ @Test
+ public void testRecursiveLoad2() throws Exception {
+ scratch.file("test/starlark/ext1.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
+ scratch.file("test/starlark/ext2.bzl", "load('//test/starlark:ext3.bzl', 'symbol3')");
+ scratch.file("test/starlark/ext3.bzl", "load('//test/starlark:ext4.bzl', 'symbol4')");
+ scratch.file("test/starlark/ext4.bzl", "load('//test/starlark:ext2.bzl', 'symbol2')");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ext1.bzl', 'custom_rule')",
+ "genrule(name = 'rule')");
+
+ reporter.removeHandler(failFastHandler);
+ BuildFileContainsErrorsException e =
+ assertThrows(
+ BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:rule"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "Starlark load cycle: [//test/starlark:ext2.bzl, "
+ + "//test/starlark:ext3.bzl, //test/starlark:ext4.bzl]");
+ }
+ }
+
+ @Test
+ public void testUnhashableInDictForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "y = [] in {}");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("unhashable type: 'list'");
+ }
+
+ @Test
+ public void testDictGetUnhashableForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "y = {}.get({})");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("unhashable type: 'dict'");
+ }
+
+ @Test
+ public void testUnknownStringEscapesForbidden() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_restrict_string_escapes=true");
+
+ scratch.file("test/extension.bzl", "y = \"\\z\"");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("invalid escape sequence: \\z");
+ }
+
+ @Test
+ public void testUnknownStringEscapes() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_restrict_string_escapes=false");
+
+ scratch.file("test/extension.bzl", "y = \"\\z\"");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ getConfiguredTarget("//test:r");
+ }
+
+ @Test
+ public void testSplitEmptySeparatorForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "y = 'abc'.split('')");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("Empty separator");
+ }
+
+ @Test
+ public void testIdentifierAssignmentFromOuterScope2() throws Exception {
+ scratch.file(
+ "test/extension.bzl",
+ "a = [1, 2, 3]",
+ "def f(): a[0] = 9",
+ "y = f()",
+ "fail() if a[0] != 9 else None");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ getConfiguredTarget("//test:r");
+ }
+
+ @Test
+ public void testIdentifierAssignmentFromOuterScopeForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "a = []", "def f(): a += [1]", "y = f()");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("local variable 'a' is referenced before assignment");
+ }
+
+ @Test
+ public void testHashFrozenListForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "y = []");
+
+ scratch.file(
+ "test/BUILD", "load('//test:extension.bzl', 'y')", "{y: 1}", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("unhashable type: 'list'");
+ }
+
+ @Test
+ public void testHashFrozenDeepMutableForbidden() throws Exception {
+ scratch.file("test/extension.bzl", "y = {}");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:extension.bzl', 'y')",
+ "{('a', (y,), True): None}",
+ "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("unhashable type: 'tuple'");
+ }
+
+ @Test
+ public void testNoOutputsError() throws Exception {
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " ctx.actions.run_shell(outputs=[], command='foo')",
+ "my_rule = rule(implementation = _my_rule_impl, executable = True)");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:target");
+ assertContainsEvent("param 'outputs' may not be empty");
+ }
+
+ @Test
+ public void testDeclareFileInvalidDirectory_withSibling() throws Exception {
+ scratch.file("test/dep/test_file.txt", "Test file");
+
+ scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");
+
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_file('exe', sibling = ctx.file.dep)",
+ " ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
+ " return []",
+ "my_rule = rule(implementation = _my_rule_impl,",
+ " attrs = {'dep': attr.label(allow_single_file = True)})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test/starlark:target");
+ assertContainsEvent(
+ "the output artifact 'test/dep/exe' is not under package directory "
+ + "'test/starlark' for target '//test/starlark:target'");
+ }
+
+ @Test
+ public void testDeclareFileInvalidDirectory_noSibling() throws Exception {
+ scratch.file("test/dep/test_file.txt", "Test file");
+
+ scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");
+
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_file('/foo/exe')",
+ " ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
+ " return []",
+ "my_rule = rule(implementation = _my_rule_impl,",
+ " attrs = {'dep': attr.label(allow_single_file = True)})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
+ assertContainsEvent(
+ "the output artifact '/foo/exe' is not under package directory "
+ + "'test/starlark' for target '//test/starlark:target'");
+ }
+
+ @Test
+ public void testDeclareDirectoryInvalidParent_withSibling() throws Exception {
+ scratch.file("test/dep/test_file.txt", "Test file");
+
+ scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");
+
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_directory('/foo/exe', sibling = ctx.file.dep)",
+ " ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
+ " return []",
+ "my_rule = rule(implementation = _my_rule_impl,",
+ " attrs = {'dep': attr.label(allow_single_file = True)})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
+ assertContainsEvent(
+ "the output directory '/foo/exe' is not under package directory "
+ + "'test/starlark' for target '//test/starlark:target'");
+ }
+
+ @Test
+ public void testDeclareDirectoryInvalidParent_noSibling() throws Exception {
+ scratch.file("test/dep/test_file.txt", "Test file");
+
+ scratch.file("test/dep/BUILD", "exports_files(['test_file.txt'])");
+
+ scratch.file(
+ "test/starlark/test_rule.bzl",
+ "def _my_rule_impl(ctx):",
+ " exe = ctx.actions.declare_directory('/foo/exe')",
+ " ctx.actions.run_shell(outputs=[exe], command=['touch', 'exe'])",
+ " return []",
+ "my_rule = rule(implementation = _my_rule_impl,",
+ " attrs = {'dep': attr.label(allow_single_file = True)})");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:test_rule.bzl', 'my_rule')",
+ "my_rule(name = 'target', dep = '//test/dep:test_file.txt')");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test/starlark:target")).isNull();
+ assertContainsEvent(
+ "the output directory '/foo/exe' is not under package directory "
+ + "'test/starlark' for target '//test/starlark:target'");
+ }
+
+ @Test
+ public void testLegacyProvider_AddCanonicalLegacyKeyAndModernKey() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(foo = apple_common.new_objc_provider(linkopt=depset(['foo'])))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'test')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");
+
+ ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
+ ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
+ ObjcProvider providerFromFoo = (ObjcProvider) target.get("foo");
+
+ // The modern key and the canonical legacy key "objc" are set to the one available ObjcProvider.
+ assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
+ assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
+ assertThat(providerFromFoo.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
+ }
+
+ @Test
+ public void testLegacyProvider_DontAutomaticallyAddKeysAlreadyPresent() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(providers = [apple_common.new_objc_provider(linkopt=depset(['prov']))],",
+ " bah = apple_common.new_objc_provider(linkopt=depset(['bah'])),",
+ " objc = apple_common.new_objc_provider(linkopt=depset(['objc'])))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'test')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");
+
+ ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
+ ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
+ ObjcProvider providerFromBah = (ObjcProvider) target.get("bah");
+
+ assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("prov");
+ assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("objc");
+ assertThat(providerFromBah.get(ObjcProvider.LINKOPT).toList()).containsExactly("bah");
+ }
+
+ @Test
+ public void testLegacyProvider_FirstNoncanonicalKeyBecomesCanonical() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "def custom_rule_impl(ctx):",
+ " return struct(providers = [apple_common.new_objc_provider(linkopt=depset(['prov']))],",
+ " foo = apple_common.new_objc_provider(linkopt=depset(['foo'])),",
+ " bar = apple_common.new_objc_provider(linkopt=depset(['bar'])))",
+ "",
+ "custom_rule = rule(implementation = custom_rule_impl)");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'custom_rule')",
+ "",
+ "custom_rule(name = 'test')");
+
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:test");
+
+ ObjcProvider providerFromModernKey = target.get(ObjcProvider.STARLARK_CONSTRUCTOR);
+ ObjcProvider providerFromObjc = (ObjcProvider) target.get("objc");
+ ObjcProvider providerFromFoo = (ObjcProvider) target.get("foo");
+ ObjcProvider providerFromBar = (ObjcProvider) target.get("bar");
+
+ assertThat(providerFromModernKey.get(ObjcProvider.LINKOPT).toList()).containsExactly("prov");
+ // The first defined provider is set to the legacy "objc" key.
+ assertThat(providerFromObjc.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
+ assertThat(providerFromFoo.get(ObjcProvider.LINKOPT).toList()).containsExactly("foo");
+ assertThat(providerFromBar.get(ObjcProvider.LINKOPT).toList()).containsExactly("bar");
+ }
+
+ @Test
+ public void testCustomMallocUnset() throws Exception {
+ setUpCustomMallocRule();
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:malloc");
+ StructImpl provider = getMyInfoFromTarget(target);
+ Object customMalloc = provider.getValue("malloc");
+ assertThat(customMalloc).isInstanceOf(NoneType.class);
+ }
+
+ @Test
+ public void testCustomMallocSet() throws Exception {
+ setUpCustomMallocRule();
+ useConfiguration("--custom_malloc=//base:system_malloc");
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:malloc");
+ StructImpl provider = getMyInfoFromTarget(target);
+ RuleConfiguredTarget customMalloc = provider.getValue("malloc", RuleConfiguredTarget.class);
+ assertThat(customMalloc.getLabel().getCanonicalForm()).isEqualTo("//base:system_malloc");
+ }
+
+ private void setUpCustomMallocRule() throws IOException {
+ scratch.overwriteFile("base/BUILD", "cc_library(name = 'system_malloc')");
+ scratch.file(
+ "test/starlark/extension.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "",
+ "def _malloc_rule_impl(ctx):",
+ " return [MyInfo(malloc = ctx.attr._custom_malloc)]",
+ "",
+ "malloc_rule = rule(",
+ " implementation = _malloc_rule_impl,",
+ " attrs = {",
+ " '_custom_malloc': attr.label(",
+ " default = configuration_field(",
+ " fragment = 'cpp',",
+ " name = 'custom_malloc',",
+ " ),",
+ " providers = [CcInfo],",
+ " ),",
+ " }",
+ ")");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:extension.bzl', 'malloc_rule')",
+ "",
+ "malloc_rule(name = 'malloc')");
+ }
+
+ // Test for an interesting situation for the inlining implementation's attempt to process
+ // subsequent load statements even when an earlier one has a missing Skyframe dep.
+ @Test
+ public void bzlFileWithErrorsLoadedThroughMultipleLoadPathsWithTheLatterOneHavingMissingDeps()
+ throws Exception {
+ scratch.file("test/starlark/error.bzl", "nope");
+ scratch.file("test/starlark/ok.bzl", "ok = 42");
+ scratch.file(
+ "test/starlark/loads-error-and-has-missing-deps.bzl",
+ "load('//test/starlark:error.bzl', 'doesntmatter')",
+ "load('//test/starlark:ok.bzl', 'ok')");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:error.bzl', 'doesntmatter')",
+ "load('//test/starlark:loads-error-and-has-missing-deps.bzl', 'doesntmatter')");
+
+ reporter.removeHandler(failFastHandler);
+ BuildFileContainsErrorsException e =
+ assertThrows(
+ BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:BUILD"));
+ assertThat(e).hasMessageThat().contains("Extension 'test/starlark/error.bzl' has errors");
+ }
+
+ // Test for an interesting situation for the inlining implementation's attempt to process
+ // subsequent load statements even when an earlier one has a missing Skyframe dep.
+ @Test
+ public void bzlFileWithErrorsLoadedThroughMultipleLoadPathsWithTheLatterOneNotHavingMissingDeps()
+ throws Exception {
+ scratch.file("test/starlark/error.bzl", "nope");
+ scratch.file("test/starlark/ok.bzl", "ok = 42");
+ scratch.file(
+ "test/starlark/loads-error-and-has-missing-deps.bzl",
+ "load('//test/starlark:error.bzl', 'doesntmatter')",
+ "load('//test/starlark:ok.bzl', 'ok')");
+ scratch.file(
+ "test/starlark/BUILD",
+ "load('//test/starlark:ok.bzl', 'ok')",
+ "load('//test/starlark:error.bzl', 'doesntmatter')",
+ "load('//test/starlark:loads-error-and-has-missing-deps.bzl', 'doesntmatter')");
+
+ reporter.removeHandler(failFastHandler);
+ BuildFileContainsErrorsException e =
+ assertThrows(
+ BuildFileContainsErrorsException.class, () -> getTarget("//test/starlark:BUILD"));
+ assertThat(e).hasMessageThat().contains("Extension 'test/starlark/error.bzl' has errors");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
new file mode 100644
index 0000000..ea16805
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
@@ -0,0 +1,342 @@
+// Copyright 2018 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
+import com.google.devtools.build.lib.starlark.util.StarlarkOptionsTestCase;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.common.options.OptionsParsingException;
+import com.google.devtools.common.options.OptionsParsingResult;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit test for the {@code StarlarkOptionsParser}. */
+@RunWith(JUnit4.class)
+public class StarlarkOptionsParsingTest extends StarlarkOptionsTestCase {
+
+ // test --flag=value
+ @Test
+ public void testFlagEqualsValueForm() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=666");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(666);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --flag value
+ @Test
+ public void testFlagSpaceValueForm() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting 666");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(666);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --@workspace//flag=value
+ @Test
+ public void testFlagNameWithWorkspace() throws Exception {
+ writeBasicIntFlag();
+ rewriteWorkspace("workspace(name = 'starlark_options_test')");
+
+ OptionsParsingResult result =
+ parseStarlarkOptions("--@starlark_options_test//test:my_int_setting=666");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("@starlark_options_test//test:my_int_setting"))
+ .isEqualTo(666);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --fake_flag=value
+ @Test
+ public void testBadFlag_equalsForm() throws Exception {
+ scratch.file("test/BUILD");
+ reporter.removeHandler(failFastHandler);
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("--//fake_flag=blahblahblah"));
+
+ assertThat(e).hasMessageThat().contains("Error loading option //fake_flag");
+ assertThat(e.getInvalidArgument()).isEqualTo("//fake_flag");
+ }
+
+ // test --fake_flag value
+ @Test
+ public void testBadFlag_spaceForm() throws Exception {
+ scratch.file("test/BUILD");
+ reporter.removeHandler(failFastHandler);
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("--//fake_flag blahblahblah"));
+
+ assertThat(e).hasMessageThat().contains("Error loading option //fake_flag");
+ assertThat(e.getInvalidArgument()).isEqualTo("//fake_flag");
+ }
+
+ // test --fake_flag
+ @Test
+ public void testBadFlag_boolForm() throws Exception {
+ scratch.file("test/BUILD");
+ reporter.removeHandler(failFastHandler);
+
+ OptionsParsingException e =
+ assertThrows(OptionsParsingException.class, () -> parseStarlarkOptions("--//fake_flag"));
+
+ assertThat(e).hasMessageThat().contains("Error loading option //fake_flag");
+ assertThat(e.getInvalidArgument()).isEqualTo("//fake_flag");
+ }
+
+ @Test
+ public void testSingleDash_notAllowed() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("-//test:my_int_setting=666");
+
+ assertThat(result.getStarlarkOptions()).isEmpty();
+ assertThat(result.getResidue()).containsExactly("-//test:my_int_setting=666");
+ }
+
+ // test --non_flag_setting=value
+ @Test
+ public void testNonFlagParsing() throws Exception {
+ scratch.file(
+ "test/build_setting.bzl",
+ "def _build_setting_impl(ctx):",
+ " return []",
+ "int_flag = rule(",
+ " implementation = _build_setting_impl,",
+ " build_setting = config.int(flag=False)",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:build_setting.bzl', 'int_flag')",
+ "int_flag(name = 'my_int_setting', build_setting_default = 42)");
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("--//test:my_int_setting=666"));
+
+ assertThat(e).hasMessageThat().isEqualTo("Unrecognized option: //test:my_int_setting=666");
+ }
+
+ // test --bool_flag
+ @Test
+ public void testBooleanFlag() throws Exception {
+ writeBasicBoolFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("--//test:my_bool_setting=false");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("//test:my_bool_setting")).isEqualTo(false);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --nobool_flag
+ @Test
+ public void testNoPrefixedBooleanFlag() throws Exception {
+ writeBasicBoolFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("--no//test:my_bool_setting");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("//test:my_bool_setting")).isEqualTo(false);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --noint_flag
+ @Test
+ public void testNoPrefixedNonBooleanFlag() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class, () -> parseStarlarkOptions("--no//test:my_int_setting"));
+
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("Illegal use of 'no' prefix on non-boolean option: //test:my_int_setting");
+ }
+
+ // test --int_flag
+ @Test
+ public void testFlagWithoutValue() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class, () -> parseStarlarkOptions("--//test:my_int_setting"));
+
+ assertThat(e).hasMessageThat().isEqualTo("Expected value after --//test:my_int_setting");
+ }
+
+ // test --flag --flag
+ @Test
+ public void testRepeatFlagLastOneWins() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingResult result =
+ parseStarlarkOptions("--//test:my_int_setting=4 --//test:my_int_setting=7");
+
+ assertThat(result.getStarlarkOptions()).hasSize(1);
+ assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(7);
+ assertThat(result.getResidue()).isEmpty();
+ }
+
+ // test --flagA=valueA --flagB=valueB
+ @Test
+ public void testMultipleFlags() throws Exception {
+ scratch.file(
+ "test/build_setting.bzl",
+ "def _build_setting_impl(ctx):",
+ " return []",
+ "int_flag = rule(",
+ " implementation = _build_setting_impl,",
+ " build_setting = config.int(flag=True)",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:build_setting.bzl', 'int_flag')",
+ "int_flag(name = 'my_int_setting', build_setting_default = 42)",
+ "int_flag(name = 'my_other_int_setting', build_setting_default = 77)");
+
+ OptionsParsingResult result =
+ parseStarlarkOptions("--//test:my_int_setting=0 --//test:my_other_int_setting=0");
+
+ assertThat(result.getResidue()).isEmpty();
+ assertThat(result.getStarlarkOptions()).hasSize(2);
+ assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(0);
+ assertThat(result.getStarlarkOptions().get("//test:my_other_int_setting")).isEqualTo(0);
+ }
+
+ // test --non_build_setting
+ @Test
+ public void testNonBuildSetting() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ ")");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'my_rule')");
+ OptionsParsingException e =
+ assertThrows(OptionsParsingException.class, () -> parseStarlarkOptions("--//test:my_rule"));
+ assertThat(e).hasMessageThat().isEqualTo("Unrecognized option: //test:my_rule");
+ }
+
+ // test --non_rule_configured_target
+ @Test
+ public void testNonRuleConfiguredTarget() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "genrule(",
+ " name = 'my_gen',",
+ " srcs = ['x.in'],",
+ " outs = ['x.cc'],",
+ " cmd = '$(locations :tool) $< >$@',",
+ " tools = [':tool'],",
+ ")",
+ "cc_library(name = 'tool-dep')");
+ OptionsParsingException e =
+ assertThrows(OptionsParsingException.class, () -> parseStarlarkOptions("--//test:x.in"));
+ assertThat(e).hasMessageThat().isEqualTo("Unrecognized option: //test:x.in");
+ }
+
+ // test --int_flag=non_int_value
+ @Test
+ public void testWrongValueType_int() throws Exception {
+ writeBasicIntFlag();
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("--//test:my_int_setting=woohoo"));
+
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("While parsing option //test:my_int_setting=woohoo: 'woohoo' is not a int");
+ }
+
+ // test --bool_flag=non_bool_value
+ @Test
+ public void testWrongValueType_bool() throws Exception {
+ writeBasicBoolFlag();
+
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("--//test:my_bool_setting=woohoo"));
+
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("While parsing option //test:my_bool_setting=woohoo: 'woohoo' is not a boolean");
+ }
+
+ // test --int-flag=same value as default
+ @Test
+ public void testDontStoreDefaultValue() throws Exception {
+ // build_setting_default = 42
+ writeBasicIntFlag();
+
+ OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=42");
+
+ assertThat(result.getStarlarkOptions()).isEmpty();
+ }
+
+ @Test
+ public void testOptionsAreParsedWithBuildTestsOnly() throws Exception {
+ writeBasicIntFlag();
+ optionsParser.parse("--build_tests_only");
+
+ OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=15");
+
+ assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(15);
+ }
+
+ @Test
+ public void testRemoveStarlarkOptionsWorks() throws Exception {
+ Pair<ImmutableList<String>, ImmutableList<String>> residueAndStarlarkOptions =
+ StarlarkOptionsParser.removeStarlarkOptions(
+ ImmutableList.of(
+ "--//local/starlark/option",
+ "--@some_repo//external/starlark/option",
+ "--@//main/repo/option",
+ "some-random-residue",
+ "--mangled//external/starlark/option"));
+ assertThat(residueAndStarlarkOptions.getFirst())
+ .containsExactly(
+ "--//local/starlark/option",
+ "--@some_repo//external/starlark/option",
+ "--@//main/repo/option");
+ assertThat(residueAndStarlarkOptions.getSecond())
+ .containsExactly("some-random-residue", "--mangled//external/starlark/option");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
new file mode 100644
index 0000000..af4d729
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -0,0 +1,1849 @@
+// Copyright 2014 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkAttrModule;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleClassFunctions.StarlarkRuleFunction;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.packages.AdvertisedProviderSet;
+import com.google.devtools.build.lib.packages.AspectParameters;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.ExecGroup;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.PredicateWithMessage;
+import com.google.devtools.build.lib.packages.RequiredProviders;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.packages.StarlarkAspectClass;
+import com.google.devtools.build.lib.packages.StarlarkDefinedAspect;
+import com.google.devtools.build.lib.packages.StarlarkInfo;
+import com.google.devtools.build.lib.packages.StarlarkProvider;
+import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.packages.StructProvider;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.skyframe.BzlLoadFunction;
+import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
+import com.google.devtools.build.lib.syntax.ClassObject;
+import com.google.devtools.build.lib.syntax.Dict;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.FileOptions;
+import com.google.devtools.build.lib.syntax.Module;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.ParserInput;
+import com.google.devtools.build.lib.syntax.StarlarkFile;
+import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.syntax.SyntaxError;
+import com.google.devtools.build.lib.syntax.Tuple;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for StarlarkRuleClassFunctions. */
+@RunWith(JUnit4.class)
+public final class StarlarkRuleClassFunctionsTest extends BuildViewTestCase {
+
+ private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+
+ private StarlarkRuleContext createRuleContext(String label) throws Exception {
+ return new StarlarkRuleContext(
+ getRuleContextForStarlark(getConfiguredTarget(label)), null, getStarlarkSemantics());
+ }
+
+ @Override
+ protected void setStarlarkSemanticsOptions(String... options) throws Exception {
+ super.setStarlarkSemanticsOptions(options); // for BuildViewTestCase
+ ev.setSemantics(options); // for StarlarkThread
+ }
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public final void createBuildFile() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ "genrule(name = 'foo',",
+ " cmd = 'dummy_cmd',",
+ " srcs = ['a.txt', 'b.img'],",
+ " tools = ['t.exe'],",
+ " outs = ['c.txt'])",
+ "genrule(name = 'bar',",
+ " cmd = 'dummy_cmd',",
+ " srcs = [':jl', ':gl'],",
+ " outs = ['d.txt'])",
+ "java_library(name = 'jl',",
+ " srcs = ['a.java'])",
+ "genrule(name = 'gl',",
+ " cmd = 'touch $(OUTS)',",
+ " srcs = ['a.go'],",
+ " outs = [ 'gl.a', 'gl.gcgox', ],",
+ " output_to_bindir = 1,",
+ ")");
+ }
+
+ @Test
+ public void testCannotOverrideBuiltInAttribute() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "def impl(ctx):", //
+ " return",
+ "r = rule(impl, attrs = {'tags': attr.string_list()})");
+ ev.assertContainsError(
+ "There is already a built-in attribute 'tags' which cannot be overridden");
+ }
+
+ @Test
+ public void testCannotOverrideBuiltInAttributeName() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "def impl(ctx):", //
+ " return",
+ "r = rule(impl, attrs = {'name': attr.string()})");
+ ev.assertContainsError(
+ "There is already a built-in attribute 'name' which cannot be overridden");
+ }
+
+ @Test
+ public void testImplicitArgsAttribute() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "def _impl(ctx):",
+ " pass",
+ "exec_rule = rule(implementation = _impl, executable = True)",
+ "non_exec_rule = rule(implementation = _impl)");
+ assertThat(getRuleClass("exec_rule").hasAttr("args", Type.STRING_LIST)).isTrue();
+ assertThat(getRuleClass("non_exec_rule").hasAttr("args", Type.STRING_LIST)).isFalse();
+ }
+
+ private RuleClass getRuleClass(String name) throws Exception {
+ return ((StarlarkRuleFunction) ev.lookup(name)).getRuleClass();
+ }
+
+ private void registerDummyStarlarkFunction() throws Exception {
+ ev.exec("def impl():", " pass");
+ }
+
+ @Test
+ public void testAttrWithOnlyType() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string_list()");
+ assertThat(attr.getType()).isEqualTo(Type.STRING_LIST);
+ }
+
+ private Attribute buildAttribute(String name, String... lines) throws Exception {
+ String[] strings = lines.clone();
+ strings[strings.length - 1] = String.format("%s = %s", name, strings[strings.length - 1]);
+ evalAndExport(ev, strings);
+ StarlarkAttrModule.Descriptor lookup = (StarlarkAttrModule.Descriptor) ev.lookup(name);
+ return lookup != null ? lookup.build(name) : null;
+ }
+
+ @Test
+ public void testOutputListAttr() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.output_list()");
+ assertThat(attr.getType()).isEqualTo(BuildType.OUTPUT_LIST);
+ }
+
+ @Test
+ public void testIntListAttr() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.int_list()");
+ assertThat(attr.getType()).isEqualTo(Type.INTEGER_LIST);
+ }
+
+ @Test
+ public void testOutputAttr() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.output()");
+ assertThat(attr.getType()).isEqualTo(BuildType.OUTPUT);
+ }
+
+ @Test
+ public void testStringDictAttr() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string_dict(default = {'a': 'b'})");
+ assertThat(attr.getType()).isEqualTo(Type.STRING_DICT);
+ }
+
+ @Test
+ public void testStringListDictAttr() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string_list_dict(default = {'a': ['b', 'c']})");
+ assertThat(attr.getType()).isEqualTo(Type.STRING_LIST_DICT);
+ }
+
+ @Test
+ public void testAttrAllowedFileTypesAnyFile() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.label_list(allow_files = True)");
+ assertThat(attr.getAllowedFileTypesPredicate()).isEqualTo(FileTypeSet.ANY_FILE);
+ }
+
+ @Test
+ public void testAttrAllowedFileTypesWrongType() throws Exception {
+ ev.checkEvalErrorContains(
+ "allow_files should be a boolean or a string list", "attr.label_list(allow_files = 18)");
+ }
+
+ @Test
+ public void testAttrNameSpecialCharactersAreForbidden() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(ev, "def impl(ctx): return", "r = rule(impl, attrs = {'ab$c': attr.int()})");
+ ev.assertContainsError("attribute name `ab$c` is not a valid identifier");
+ }
+
+ @Test
+ public void testAttrNameCannotStartWithDigit() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(ev, "def impl(ctx): return", "r = rule(impl, attrs = {'2_foo': attr.int()})");
+ ev.assertContainsError("attribute name `2_foo` is not a valid identifier");
+ }
+
+ @Test
+ public void testRuleClassTooManyAttributes() throws Exception {
+ ev.setFailFast(false);
+
+ ImmutableList.Builder<String> linesBuilder =
+ ImmutableList.<String>builder()
+ .add("def impl(ctx): return")
+ .add("r = rule(impl, attrs = {");
+ for (int i = 0; i < 200; i++) {
+ linesBuilder.add(" 'attr" + i + "': attr.int(),");
+ }
+ linesBuilder.add("})");
+
+ evalAndExport(ev, linesBuilder.build().toArray(new String[0]));
+
+ assertThat(ev.getEventCollector()).hasSize(1);
+ Event event = ev.getEventCollector().iterator().next();
+ assertThat(event.getKind()).isEqualTo(EventKind.ERROR);
+ assertThat(event.getMessage()).contains("Rule class r declared too many attributes");
+ }
+
+ @Test
+ public void testRuleClassTooLongAttributeName() throws Exception {
+ ev.setFailFast(false);
+
+ evalAndExport(
+ ev,
+ "def impl(ctx): return;",
+ "r = rule(impl, attrs = { '" + Strings.repeat("x", 150) + "': attr.int() })");
+
+ assertThat(ev.getEventCollector()).hasSize(1);
+ Event event = ev.getEventCollector().iterator().next();
+ assertThat(event.getKind()).isEqualTo(EventKind.ERROR);
+ assertThat(event.getMessage())
+ .matches("Attribute r\\.x{150}'s name is too long \\(150 > 128\\)");
+ }
+
+ @Test
+ public void testAttrAllowedSingleFileTypesWrongType() throws Exception {
+ ev.checkEvalErrorContains(
+ "allow_single_file should be a boolean or a string list",
+ "attr.label(allow_single_file = 18)");
+ }
+
+ @Test
+ public void testAttrWithList() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.label_list(allow_files = ['.xml'])");
+ assertThat(attr.getAllowedFileTypesPredicate().apply("a.xml")).isTrue();
+ assertThat(attr.getAllowedFileTypesPredicate().apply("a.txt")).isFalse();
+ assertThat(attr.isSingleArtifact()).isFalse();
+ }
+
+ @Test
+ public void testAttrSingleFileWithList() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.label(allow_single_file = ['.xml'])");
+ assertThat(attr.getAllowedFileTypesPredicate().apply("a.xml")).isTrue();
+ assertThat(attr.getAllowedFileTypesPredicate().apply("a.txt")).isFalse();
+ assertThat(attr.isSingleArtifact()).isTrue();
+ }
+
+ private static StarlarkProviderIdentifier legacy(String legacyId) {
+ return StarlarkProviderIdentifier.forLegacy(legacyId);
+ }
+
+ private static StarlarkProviderIdentifier declared(String exportedName) {
+ return StarlarkProviderIdentifier.forKey(new StarlarkProvider.Key(FAKE_LABEL, exportedName));
+ }
+
+ @Test
+ public void testAttrWithProviders() throws Exception {
+ Attribute attr =
+ buildAttribute("a1",
+ "b = provider()",
+ "attr.label_list(allow_files = True, providers = ['a', b])");
+ assertThat(attr.getRequiredProviders().isSatisfiedBy(set(legacy("a"), declared("b")))).isTrue();
+ assertThat(attr.getRequiredProviders().isSatisfiedBy(set(legacy("a")))).isFalse();
+ }
+
+ @Test
+ public void testAttrWithProvidersOneEmpty() throws Exception {
+ Attribute attr =
+ buildAttribute(
+ "a1",
+ "b = provider()",
+ "attr.label_list(allow_files = True, providers = [['a', b],[]])");
+ assertThat(attr.getRequiredProviders().acceptsAny()).isTrue();
+ }
+
+ @Test
+ public void testAttrWithProvidersList() throws Exception {
+ Attribute attr =
+ buildAttribute("a1",
+ "b = provider()",
+ "attr.label_list(allow_files = True, providers = [['a', b], ['c']])");
+ assertThat(attr.getRequiredProviders().isSatisfiedBy(set(legacy("a"), declared("b")))).isTrue();
+ assertThat(attr.getRequiredProviders().isSatisfiedBy(set(legacy("c")))).isTrue();
+ assertThat(attr.getRequiredProviders().isSatisfiedBy(set(legacy("a")))).isFalse();
+ }
+
+ private static AdvertisedProviderSet set(StarlarkProviderIdentifier... ids) {
+ AdvertisedProviderSet.Builder builder = AdvertisedProviderSet.builder();
+ for (StarlarkProviderIdentifier id : ids) {
+ builder.addStarlark(id);
+ }
+ return builder.build();
+ }
+
+ private void checkAttributeError(String expectedMessage, String... lines) throws Exception {
+ ev.setFailFast(false);
+ buildAttribute("fakeAttribute", lines);
+ MoreAsserts.assertContainsEvent(ev.getEventCollector(), expectedMessage);
+ }
+
+ @Test
+ public void testAttrWithWrongProvidersList() throws Exception {
+ checkAttributeError(
+ "element in 'providers' is of unexpected type. Either all elements should be providers,"
+ + " or all elements should be lists of providers,"
+ + " but got list with an element of type int.",
+ "attr.label_list(allow_files = True, providers = [['a', 1], ['c']])");
+
+ checkAttributeError(
+ "element in 'providers' is of unexpected type. Either all elements should be providers,"
+ + " or all elements should be lists of providers,"
+ + " but got an element of type string.",
+ "b = provider()",
+ "attr.label_list(allow_files = True, providers = [['a', b], 'c'])");
+
+ checkAttributeError(
+ "element in 'providers' is of unexpected type. Either all elements should be providers,"
+ + " or all elements should be lists of providers,"
+ + " but got an element of type string.",
+ "c = provider()",
+ "attr.label_list(allow_files = True, providers = [['a', b], c])");
+ }
+
+ @Test
+ public void testLabelListWithAspects() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(implementation = _impl)",
+ "a = attr.label_list(aspects = [my_aspect])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+ StarlarkDefinedAspect aspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ assertThat(aspect).isNotNull();
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactly(aspect.getAspectClass());
+ }
+
+ @Test
+ public void testLabelWithAspects() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(implementation = _impl)",
+ "a = attr.label(aspects = [my_aspect])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+ StarlarkDefinedAspect aspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ assertThat(aspect).isNotNull();
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactly(aspect.getAspectClass());
+ }
+
+ @Test
+ public void testLabelListWithAspectsError() throws Exception {
+ ev.checkEvalErrorContains(
+ "at index 0 of aspects, got element of type int, want Aspect",
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(implementation = _impl)",
+ "attr.label_list(aspects = [my_aspect, 123])");
+ }
+
+ @Test
+ public void testAspectExtraDeps() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { '_extra_deps' : attr.label(default = Label('//foo/bar:baz')) }",
+ ")");
+ StarlarkDefinedAspect aspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ Attribute attribute = Iterables.getOnlyElement(aspect.getAttributes());
+ assertThat(attribute.getName()).isEqualTo("$extra_deps");
+ assertThat(attribute.getDefaultValue(null))
+ .isEqualTo(
+ Label.parseAbsolute(
+ "//foo/bar:baz",
+ /* defaultToMain= */ false,
+ /* repositoryMapping= */ ImmutableMap.of()));
+ }
+
+ @Test
+ public void testAspectParameter() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { 'param' : attr.string(values=['a', 'b']) }",
+ ")");
+ StarlarkDefinedAspect aspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ Attribute attribute = Iterables.getOnlyElement(aspect.getAttributes());
+ assertThat(attribute.getName()).isEqualTo("param");
+ }
+
+ @Test
+ public void testAspectParameterRequiresValues() throws Exception {
+ ev.checkEvalErrorContains(
+ "Aspect parameter attribute 'param' must have type 'string' and use the 'values' "
+ + "restriction.",
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { 'param' : attr.string(default = 'c') }",
+ ")");
+ }
+
+ @Test
+ public void testAspectParameterBadType() throws Exception {
+ ev.checkEvalErrorContains(
+ "Aspect parameter attribute 'param' must have type 'string' and use the 'values' "
+ + "restriction.",
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { 'param' : attr.label(default = Label('//foo/bar:baz')) }",
+ ")");
+ }
+
+ @Test
+ public void testAspectParameterAndExtraDeps() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { 'param' : attr.string(values=['a', 'b']),",
+ " '_extra' : attr.label(default = Label('//foo/bar:baz')) }",
+ ")");
+ StarlarkDefinedAspect aspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ assertThat(aspect.getAttributes()).hasSize(2);
+ assertThat(aspect.getParamAttributes()).containsExactly("param");
+ }
+
+ @Test
+ public void testAspectNoDefaultValueAttribute() throws Exception {
+ ev.checkEvalErrorContains(
+ "Aspect attribute '_extra_deps' has no default value",
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl,",
+ " attrs = { '_extra_deps' : attr.label() }",
+ ")");
+ }
+
+ @Test
+ public void testAspectAddToolchain() throws Exception {
+ scratch.file("test/BUILD", "toolchain_type(name = 'my_toolchain_type')");
+ evalAndExport(
+ ev, "def _impl(ctx): pass", "a1 = aspect(_impl, toolchains=['//test:my_toolchain_type'])");
+ StarlarkDefinedAspect a = (StarlarkDefinedAspect) ev.lookup("a1");
+ assertThat(a.getRequiredToolchains()).containsExactly(makeLabel("//test:my_toolchain_type"));
+ }
+
+ @Test
+ public void testNonLabelAttrWithProviders() throws Exception {
+ ev.checkEvalErrorContains(
+ "unexpected keyword argument 'providers'", "attr.string(providers = ['a'])");
+ }
+
+ private static final RuleClass.ConfiguredTargetFactory<Object, Object, Exception>
+ DUMMY_CONFIGURED_TARGET_FACTORY =
+ ruleContext -> {
+ throw new IllegalStateException();
+ };
+
+ private RuleClass ruleClass(String name) {
+ return new RuleClass.Builder(name, RuleClassType.NORMAL, false)
+ .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
+ .add(Attribute.attr("tags", Type.STRING_LIST))
+ .build();
+ }
+
+ @Test
+ public void testAttrAllowedRuleClassesSpecificRuleClasses() throws Exception {
+ Attribute attr = buildAttribute("a",
+ "attr.label_list(allow_rules = ['java_binary'], allow_files = True)");
+ assertThat(attr.getAllowedRuleClassesPredicate().apply(ruleClass("java_binary"))).isTrue();
+ assertThat(attr.getAllowedRuleClassesPredicate().apply(ruleClass("genrule"))).isFalse();
+ }
+
+ @Test
+ public void testAttrDefaultValue() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string(default = 'some value')");
+ assertThat(attr.getDefaultValueUnchecked()).isEqualTo("some value");
+ }
+
+ @Test
+ public void testLabelAttrDefaultValueAsString() throws Exception {
+ Attribute sligleAttr = buildAttribute("a1", "attr.label(default = '//foo:bar')");
+ assertThat(sligleAttr.getDefaultValueUnchecked())
+ .isEqualTo(
+ Label.parseAbsolute(
+ "//foo:bar",
+ /* defaultToMain= */ false,
+ /* repositoryMapping= */ ImmutableMap.of()));
+
+ Attribute listAttr =
+ buildAttribute("a2", "attr.label_list(default = ['//foo:bar', '//bar:foo'])");
+ assertThat(listAttr.getDefaultValueUnchecked())
+ .isEqualTo(
+ ImmutableList.of(
+ Label.parseAbsolute(
+ "//foo:bar",
+ /* defaultToMain= */ false,
+ /* repositoryMapping= */ ImmutableMap.of()),
+ Label.parseAbsolute(
+ "//bar:foo",
+ /* defaultToMain= */ false,
+ /*repositoryMapping= */ ImmutableMap.of())));
+
+ Attribute dictAttr =
+ buildAttribute("a3", "attr.label_keyed_string_dict(default = {'//foo:bar': 'my value'})");
+ assertThat(dictAttr.getDefaultValueUnchecked())
+ .isEqualTo(
+ ImmutableMap.of(
+ Label.parseAbsolute(
+ "//foo:bar",
+ /* defaultToMain= */ false,
+ /* repositoryMapping= */ ImmutableMap.of()),
+ "my value"));
+ }
+
+ @Test
+ public void testLabelAttrDefaultValueAsStringBadValue() throws Exception {
+ ev.checkEvalErrorContains(
+ "invalid label '/foo:bar' in parameter 'default' of attribute 'label': "
+ + "invalid target name '/foo:bar'",
+ "attr.label(default = '/foo:bar')");
+
+ ev.checkEvalErrorContains(
+ "invalid label '/bar:foo' in element 1 of parameter 'default' of attribute "
+ + "'label_list': invalid target name '/bar:foo'",
+ "attr.label_list(default = ['//foo:bar', '/bar:foo'])");
+
+ ev.checkEvalErrorContains(
+ "invalid label '/bar:foo' in dict key element: invalid target name '/bar:foo'",
+ "attr.label_keyed_string_dict(default = {'//foo:bar': 'a', '/bar:foo': 'b'})");
+ }
+
+ @Test
+ public void testAttrDefaultValueBadType() throws Exception {
+ ev.checkEvalErrorContains("got value of type 'int', want 'string'", "attr.string(default = 1)");
+ }
+
+ @Test
+ public void testAttrMandatory() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string(mandatory=True)");
+ assertThat(attr.isMandatory()).isTrue();
+ assertThat(attr.isNonEmpty()).isFalse();
+ }
+
+ @Test
+ public void testAttrAllowEmpty() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string_list(allow_empty=False)");
+ assertThat(attr.isNonEmpty()).isTrue();
+ assertThat(attr.isMandatory()).isFalse();
+ }
+
+ @Test
+ public void testAttrBadKeywordArguments() throws Exception {
+ ev.checkEvalErrorContains(
+ "string() got unexpected keyword argument 'bad_keyword'", "attr.string(bad_keyword = '')");
+ }
+
+ @Test
+ public void testAttrCfg() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.label(cfg = 'host', allow_files = True)");
+ assertThat(attr.getTransitionFactory().isHost()).isTrue();
+ }
+
+ @Test
+ public void testAttrCfgTarget() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.label(cfg = 'target', allow_files = True)");
+ assertThat(NoTransition.isInstance(attr.getTransitionFactory())).isTrue();
+ }
+
+ @Test
+ public void incompatibleDataTransition() throws Exception {
+ EvalException expected =
+ assertThrows(EvalException.class, () -> ev.eval("attr.label(cfg = 'data')"));
+ assertThat(expected).hasMessageThat().contains("cfg must be either 'host' or 'target'");
+ }
+
+ @Test
+ public void testAttrValues() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.string(values = ['ab', 'cd'])");
+ PredicateWithMessage<Object> predicate = attr.getAllowedValues();
+ assertThat(predicate.apply("ab")).isTrue();
+ assertThat(predicate.apply("xy")).isFalse();
+ }
+
+ @Test
+ public void testAttrIntValues() throws Exception {
+ Attribute attr = buildAttribute("a1", "attr.int(values = [1, 2])");
+ PredicateWithMessage<Object> predicate = attr.getAllowedValues();
+ assertThat(predicate.apply(2)).isTrue();
+ assertThat(predicate.apply(3)).isFalse();
+ }
+
+ @Test
+ public void testAttrDoc() throws Exception {
+ // We don't actually store the doc in the attr definition; right now it's just meant to be
+ // extracted by documentation generating tools. So we don't have anything to assert and we just
+ // verify that no exceptions were thrown from building them.
+ buildAttribute("a1", "attr.bool(doc='foo')");
+ buildAttribute("a2", "attr.int(doc='foo')");
+ buildAttribute("a3", "attr.int_list(doc='foo')");
+ buildAttribute("a4", "attr.label(doc='foo')");
+ buildAttribute("a5", "attr.label_keyed_string_dict(doc='foo')");
+ buildAttribute("a6", "attr.label_list(doc='foo')");
+ buildAttribute("a8", "attr.output(doc='foo')");
+ buildAttribute("a9", "attr.output_list(doc='foo')");
+ buildAttribute("a10", "attr.string(doc='foo')");
+ buildAttribute("a11", "attr.string_dict(doc='foo')");
+ buildAttribute("a12", "attr.string_list(doc='foo')");
+ buildAttribute("a13", "attr.string_list_dict(doc='foo')");
+ }
+
+ @Test
+ public void testNoAttrLicense() throws Exception {
+ EvalException expected = assertThrows(EvalException.class, () -> ev.eval("attr.license()"));
+ assertThat(expected).hasMessageThat().contains("'attr' value has no field or method 'license'");
+ }
+
+ @Test
+ public void testAttrDocValueBadType() throws Exception {
+ ev.checkEvalErrorContains("got value of type 'int', want 'string'", "attr.string(doc = 1)");
+ }
+
+ @Test
+ public void testRuleImplementation() throws Exception {
+ evalAndExport(ev, "def impl(ctx): return None", "rule1 = rule(impl)");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("rule1")).getRuleClass();
+ assertThat(c.getConfiguredTargetFunction().getName()).isEqualTo("impl");
+ }
+
+ @Test
+ public void testRuleDoc() throws Exception {
+ evalAndExport(ev, "def impl(ctx): return None", "rule1 = rule(impl, doc='foo')");
+ }
+
+ @Test
+ public void testFunctionAsAttrDefault() throws Exception {
+ ev.exec("def f(): pass");
+
+ // Late-bound attributes, which are computed during analysis as a function
+ // of the configuration, are only available for attributes involving labels:
+ // attr.label
+ // attr.label_list
+ // attr.label_keyed_string_dict
+ // attr.output,
+ // attr.output_list
+ // (See testRuleClassImplicitOutputFunctionDependingOnComputedAttribute
+ // for a more detailed positive test.)
+ evalAndExport(
+ ev,
+ "attr.label(default=f)",
+ "attr.label_list(default=f)",
+ "attr.label_keyed_string_dict(default=f)");
+ // For all other attribute types, the default value may not be a function.
+ //
+ // (This is a regression test for github.com/bazelbuild/bazel/issues/9463.
+ // The loading-phase feature of "computed attribute defaults" is not exposed
+ // to Starlark; the bug was that the @StarlarkMethod
+ // annotation was more permissive than the method declaration.)
+ ev.checkEvalErrorContains(
+ "got value of type 'function', want 'string'", "attr.string(default=f)");
+ ev.checkEvalErrorContains(
+ "got value of type 'function', want 'sequence'", "attr.string_list(default=f)");
+ ev.checkEvalErrorContains("got value of type 'function', want 'int'", "attr.int(default=f)");
+ ev.checkEvalErrorContains(
+ "got value of type 'function', want 'sequence'", "attr.int_list(default=f)");
+ ev.checkEvalErrorContains("got value of type 'function', want 'bool'", "attr.bool(default=f)");
+ ev.checkEvalErrorContains(
+ "got value of type 'function', want 'dict'", "attr.string_dict(default=f)");
+ ev.checkEvalErrorContains(
+ "got value of type 'function', want 'dict'", "attr.string_list_dict(default=f)");
+ // Note: attr.license appears to be disabled already.
+ // (see --incompatible_no_attr_license)
+ }
+
+ private static final Label FAKE_LABEL = Label.parseAbsoluteUnchecked("//fake/label.bzl");
+
+ @Test
+ public void testRuleAddAttribute() throws Exception {
+ evalAndExport(ev, "def impl(ctx): return None", "r1 = rule(impl, attrs={'a1': attr.string()})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.hasAttr("a1", Type.STRING)).isTrue();
+ }
+
+ private static void evalAndExport(EvaluationTestCase ev, String... lines) throws Exception {
+ ParserInput input = ParserInput.fromLines(lines);
+ Module module = ev.getModule();
+ StarlarkFile file = EvalUtils.parseAndValidate(input, FileOptions.DEFAULT, module);
+ if (!file.ok()) {
+ throw new SyntaxError.Exception(file.errors());
+ }
+ BzlLoadFunction.execAndExport(
+ file, FAKE_LABEL, ev.getEventHandler(), module, ev.getStarlarkThread());
+ }
+
+ @Test
+ public void testExportAliasedName() throws Exception {
+ // When there are multiple names aliasing the same StarlarkExportable, the first one to be
+ // declared should be used. Make sure we're not using lexicographical order, hash order,
+ // non-deterministic order, or anything else.
+ evalAndExport(
+ ev,
+ "def _impl(ctx): pass",
+ "d = rule(implementation = _impl)",
+ "a = d",
+ // Having more names improves the chance that non-determinism will be caught.
+ "b = d",
+ "c = d",
+ "e = d",
+ "f = d",
+ "foo = d",
+ "bar = d",
+ "baz = d",
+ "x = d",
+ "y = d",
+ "z = d");
+ String dName = ((StarlarkRuleFunction) ev.lookup("d")).getRuleClass().getName();
+ String fooName = ((StarlarkRuleFunction) ev.lookup("foo")).getRuleClass().getName();
+ assertThat(dName).isEqualTo("d");
+ assertThat(fooName).isEqualTo("d");
+ }
+
+ @Test
+ public void testOutputToGenfiles() throws Exception {
+ evalAndExport(ev, "def impl(ctx): pass", "r1 = rule(impl, output_to_genfiles=True)");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.hasBinaryOutput()).isFalse();
+ }
+
+ @Test
+ public void testRuleAddMultipleAttributes() throws Exception {
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None",
+ "r1 = rule(impl,",
+ " attrs = {",
+ " 'a1': attr.label_list(allow_files=True),",
+ " 'a2': attr.int()",
+ "})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.hasAttr("a1", BuildType.LABEL_LIST)).isTrue();
+ assertThat(c.hasAttr("a2", Type.INTEGER)).isTrue();
+ }
+
+ @Test
+ public void testRuleAttributeFlag() throws Exception {
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None",
+ "r1 = rule(impl, attrs = {'a1': attr.string(mandatory=True)})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.getAttributeByName("a1").isMandatory()).isTrue();
+ }
+
+ @Test
+ public void testRuleOutputs() throws Exception {
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None", //
+ "r1 = rule(impl, outputs = {'a': 'a.txt'})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ ImplicitOutputsFunction function = c.getDefaultImplicitOutputsFunction();
+ assertThat(function.getImplicitOutputs(ev.getEventHandler(), null)).containsExactly("a.txt");
+ }
+
+ @Test
+ public void testRuleUnknownKeyword() throws Exception {
+ registerDummyStarlarkFunction();
+ ev.checkEvalErrorContains(
+ "unexpected keyword argument 'bad_keyword'", "rule(impl, bad_keyword = 'some text')");
+ }
+
+ @Test
+ public void testRuleImplementationMissing() throws Exception {
+ ev.checkEvalErrorContains(
+ "rule() missing 1 required positional argument: implementation", "rule(attrs = {})");
+ }
+
+ @Test
+ public void testRuleBadTypeForAdd() throws Exception {
+ registerDummyStarlarkFunction();
+ ev.checkEvalErrorContains(
+ "in call to rule(), parameter 'attrs' got value of type 'string', want 'dict or NoneType'",
+ "rule(impl, attrs = 'some text')");
+ }
+
+ @Test
+ public void testRuleBadTypeInAdd() throws Exception {
+ registerDummyStarlarkFunction();
+ ev.checkEvalErrorContains(
+ "got dict<string, string> for 'attrs', want dict<string, Attribute>",
+ "rule(impl, attrs = {'a1': 'some text'})");
+ }
+
+ @Test
+ public void testRuleBadTypeForDoc() throws Exception {
+ registerDummyStarlarkFunction();
+ ev.checkEvalErrorContains("got value of type 'int', want 'string'", "rule(impl, doc = 1)");
+ }
+
+ @Test
+ public void testLabel() throws Exception {
+ Object result = ev.eval("Label('//foo/foo:foo')");
+ assertThat(result).isInstanceOf(Label.class);
+ assertThat(result.toString()).isEqualTo("//foo/foo:foo");
+ }
+
+ @Test
+ public void testLabelSameInstance() throws Exception {
+ Object l1 = ev.eval("Label('//foo/foo:foo')");
+ // Implicitly creates a new pkgContext and environment, yet labels should be the same.
+ Object l2 = ev.eval("Label('//foo/foo:foo')");
+ assertThat(l1).isSameInstanceAs(l2);
+ }
+
+ @Test
+ public void testLabelNameAndPackage() throws Exception {
+ Object result = ev.eval("Label('//foo/bar:baz').name");
+ assertThat(result).isEqualTo("baz");
+ // NB: implicitly creates a new pkgContext and environments, yet labels should be the same.
+ result = ev.eval("Label('//foo/bar:baz').package");
+ assertThat(result).isEqualTo("foo/bar");
+ }
+
+ @Test
+ public void testRuleLabelDefaultValue() throws Exception {
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None\n"
+ + "r1 = rule(impl, attrs = {'a1': "
+ + "attr.label(default = Label('//foo:foo'), allow_files=True)})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ Attribute a = c.getAttributeByName("a1");
+ assertThat(a.getDefaultValueUnchecked()).isInstanceOf(Label.class);
+ assertThat(a.getDefaultValueUnchecked().toString()).isEqualTo("//foo:foo");
+ }
+
+ @Test
+ public void testIntDefaultValue() throws Exception {
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None",
+ "r1 = rule(impl, attrs = {'a1': attr.int(default = 40+2)})");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ Attribute a = c.getAttributeByName("a1");
+ assertThat(a.getDefaultValueUnchecked()).isEqualTo(42);
+ }
+
+ @Test
+ public void testRuleInheritsBaseRuleAttributes() throws Exception {
+ evalAndExport(ev, "def impl(ctx): return None", "r1 = rule(impl)");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.hasAttr("tags", Type.STRING_LIST)).isTrue();
+ assertThat(c.hasAttr("visibility", BuildType.NODEP_LABEL_LIST)).isTrue();
+ assertThat(c.hasAttr("deprecation", Type.STRING)).isTrue();
+ assertThat(c.hasAttr(":action_listener", BuildType.LABEL_LIST))
+ .isTrue(); // required for extra actions
+ }
+
+ private void checkTextMessage(String from, String... lines) throws Exception {
+ String[] strings = lines.clone();
+ Object result = ev.eval(from);
+ String expect = "";
+ if (strings.length > 0) {
+ expect = Joiner.on("\n").join(lines) + "\n";
+ }
+ assertThat(result).isEqualTo(expect);
+ }
+
+ @Test
+ public void testSimpleTextMessagesBooleanFields() throws Exception {
+ checkTextMessage("struct(name=True).to_proto()", "name: true");
+ checkTextMessage("struct(name=False).to_proto()", "name: false");
+ }
+
+ @Test
+ public void testStructRestrictedOverrides() throws Exception {
+ ev.checkEvalErrorContains(
+ "cannot override built-in struct function 'to_json'", "struct(to_json='foo')");
+
+ ev.checkEvalErrorContains(
+ "cannot override built-in struct function 'to_proto'", "struct(to_proto='foo')");
+ }
+
+ @Test
+ public void testSimpleTextMessages() throws Exception {
+ checkTextMessage("struct(name='value').to_proto()", "name: \"value\"");
+ checkTextMessage("struct(name=[]).to_proto()"); // empty lines
+ checkTextMessage("struct(name=['a', 'b']).to_proto()", "name: \"a\"", "name: \"b\"");
+ checkTextMessage("struct(name=123).to_proto()", "name: 123");
+ checkTextMessage("struct(name=[1, 2, 3]).to_proto()", "name: 1", "name: 2", "name: 3");
+ checkTextMessage("struct(a=struct(b='b')).to_proto()", "a {", " b: \"b\"", "}");
+ checkTextMessage(
+ "struct(a=[struct(b='x'), struct(b='y')]).to_proto()",
+ "a {",
+ " b: \"x\"",
+ "}",
+ "a {",
+ " b: \"y\"",
+ "}");
+ checkTextMessage(
+ "struct(a=struct(b=struct(c='c'))).to_proto()", "a {", " b {", " c: \"c\"", " }", "}");
+ // dict to_proto tests
+ checkTextMessage("struct(name={}).to_proto()"); // empty lines
+ checkTextMessage(
+ "struct(name={'a': 'b'}).to_proto()", "name {", " key: \"a\"", " value: \"b\"", "}");
+ checkTextMessage(
+ "struct(name={'c': 'd', 'a': 'b'}).to_proto()",
+ "name {",
+ " key: \"c\"",
+ " value: \"d\"",
+ "}",
+ "name {",
+ " key: \"a\"",
+ " value: \"b\"",
+ "}");
+ checkTextMessage(
+ "struct(x=struct(y={'a': 1})).to_proto()",
+ "x {",
+ " y {",
+ " key: \"a\"",
+ " value: 1",
+ " }",
+ "}");
+ checkTextMessage(
+ "struct(name={'a': struct(b=1, c=2)}).to_proto()",
+ "name {",
+ " key: \"a\"",
+ " value {",
+ " b: 1",
+ " c: 2",
+ " }",
+ "}");
+ checkTextMessage(
+ "struct(name={'a': struct(b={4: 'z', 3: 'y'}, c=2)}).to_proto()",
+ "name {",
+ " key: \"a\"",
+ " value {",
+ " b {",
+ " key: 4",
+ " value: \"z\"",
+ " }",
+ " b {",
+ " key: 3",
+ " value: \"y\"",
+ " }",
+ " c: 2",
+ " }",
+ "}");
+ }
+
+ @Test
+ public void testProtoFieldsOrder() throws Exception {
+ checkTextMessage("struct(d=4, b=2, c=3, a=1).to_proto()", "a: 1", "b: 2", "c: 3", "d: 4");
+ }
+
+ @Test
+ public void testTextMessageEscapes() throws Exception {
+ checkTextMessage("struct(name='a\"b').to_proto()", "name: \"a\\\"b\"");
+ checkTextMessage("struct(name='a\\'b').to_proto()", "name: \"a'b\"");
+ checkTextMessage("struct(name='a\\nb').to_proto()", "name: \"a\\nb\"");
+
+ // struct(name="a\\\"b") -> name: "a\\\"b"
+ checkTextMessage("struct(name='a\\\\\\\"b').to_proto()", "name: \"a\\\\\\\"b\"");
+ }
+
+ @Test
+ public void testTextMessageInvalidElementInListStructure() throws Exception {
+ ev.checkEvalErrorContains(
+ "Invalid text format, expected a struct, a dict, a string, a bool, or "
+ + "an int but got a list for list element in struct field 'a'",
+ "struct(a=[['b']]).to_proto()");
+ }
+
+ @Test
+ public void testTextMessageInvalidStructure() throws Exception {
+ ev.checkEvalErrorContains(
+ "Invalid text format, expected a struct, a dict, a string, a bool, or an int "
+ + "but got a function for struct field 'a'",
+ "struct(a=rule).to_proto()");
+ }
+
+ private void checkJson(String from, String expected) throws Exception {
+ Object result = ev.eval(from);
+ assertThat(result).isEqualTo(expected);
+ }
+
+ @Test
+ public void testJsonBooleanFields() throws Exception {
+ checkJson("struct(name=True).to_json()", "{\"name\":true}");
+ checkJson("struct(name=False).to_json()", "{\"name\":false}");
+ }
+
+ @Test
+ public void testJsonDictFields() throws Exception {
+ checkJson("struct(config={}).to_json()", "{\"config\":{}}");
+ checkJson("struct(config={'key': 'value'}).to_json()", "{\"config\":{\"key\":\"value\"}}");
+ ev.checkEvalErrorContains(
+ "Keys must be a string but got a int for struct field 'config'",
+ "struct(config={1:2}).to_json()");
+ ev.checkEvalErrorContains(
+ "Keys must be a string but got a int for dict value 'foo'",
+ "struct(config={'foo':{1:2}}).to_json()");
+ ev.checkEvalErrorContains(
+ "Keys must be a string but got a bool for struct field 'config'",
+ "struct(config={True: False}).to_json()");
+ }
+
+ @Test
+ public void testJsonEncoding() throws Exception {
+ checkJson("struct(name='value').to_json()", "{\"name\":\"value\"}");
+ checkJson("struct(name=['a', 'b']).to_json()", "{\"name\":[\"a\",\"b\"]}");
+ checkJson("struct(name=123).to_json()", "{\"name\":123}");
+ checkJson("struct(name=[1, 2, 3]).to_json()", "{\"name\":[1,2,3]}");
+ checkJson("struct(a=struct(b='b')).to_json()", "{\"a\":{\"b\":\"b\"}}");
+ checkJson("struct(a=[struct(b='x'), struct(b='y')]).to_json()",
+ "{\"a\":[{\"b\":\"x\"},{\"b\":\"y\"}]}");
+ checkJson("struct(a=struct(b=struct(c='c'))).to_json()",
+ "{\"a\":{\"b\":{\"c\":\"c\"}}}");
+ }
+
+ @Test
+ public void testJsonEscapes() throws Exception {
+ checkJson("struct(name='a\"b').to_json()", "{\"name\":\"a\\\"b\"}");
+ checkJson("struct(name='a\\'b').to_json()", "{\"name\":\"a'b\"}");
+ checkJson("struct(name='a\\\\b').to_json()", "{\"name\":\"a\\\\b\"}");
+ checkJson("struct(name='a\\nb').to_json()", "{\"name\":\"a\\nb\"}");
+ checkJson("struct(name='a\\rb').to_json()", "{\"name\":\"a\\rb\"}");
+ checkJson("struct(name='a\\tb').to_json()", "{\"name\":\"a\\tb\"}");
+ }
+
+ @Test
+ public void testJsonNestedListStructure() throws Exception {
+ checkJson("struct(a=[['b']]).to_json()", "{\"a\":[[\"b\"]]}");
+ }
+
+ @Test
+ public void testJsonInvalidStructure() throws Exception {
+ ev.checkEvalErrorContains(
+ "Invalid text format, expected a struct, a string, a bool, or an int but got a "
+ + "function for struct field 'a'",
+ "struct(a=rule).to_json()");
+ }
+
+ @Test
+ public void testLabelAttrWrongDefault() throws Exception {
+ ev.checkEvalErrorContains(
+ "got value of type 'int', want 'Label or string or LateBoundDefault or function or"
+ + " NoneType'",
+ "attr.label(default = 123)");
+ }
+
+ @Test
+ public void testLabelGetRelative() throws Exception {
+ assertThat(ev.eval("Label('//foo:bar').relative('baz')").toString()).isEqualTo("//foo:baz");
+ assertThat(ev.eval("Label('//foo:bar').relative('//baz:qux')").toString())
+ .isEqualTo("//baz:qux");
+ }
+
+ @Test
+ public void testLabelGetRelativeSyntaxError() throws Exception {
+ ev.checkEvalErrorContains(
+ "invalid target name 'bad//syntax': target names may not contain '//' path separators",
+ "Label('//foo:bar').relative('bad//syntax')");
+ }
+
+ @Test
+ public void testStructCreation() throws Exception {
+ // TODO(fwe): cannot be handled by current testing suite
+ ev.exec("x = struct(a = 1, b = 2)");
+ assertThat(ev.lookup("x")).isInstanceOf(ClassObject.class);
+ }
+
+ @Test
+ public void testStructFields() throws Exception {
+ // TODO(fwe): cannot be handled by current testing suite
+ ev.exec("x = struct(a = 1, b = 2)");
+ ClassObject x = (ClassObject) ev.lookup("x");
+ assertThat(x.getValue("a")).isEqualTo(1);
+ assertThat(x.getValue("b")).isEqualTo(2);
+ }
+
+ @Test
+ public void testStructEquality() throws Exception {
+ assertThat((Boolean) ev.eval("struct(a = 1, b = 2) == struct(b = 2, a = 1)")).isTrue();
+ assertThat((Boolean) ev.eval("struct(a = 1) == struct(a = 1, b = 2)")).isFalse();
+ assertThat((Boolean) ev.eval("struct(a = 1, b = 2) == struct(a = 1)")).isFalse();
+ // Compare a recursive object to itself to make sure reference equality is checked
+ ev.exec("s = struct(a = 1, b = []); s.b.append(s)");
+ assertThat((Boolean) ev.eval("s == s")).isTrue();
+ assertThat((Boolean) ev.eval("struct(a = 1, b = 2) == struct(a = 1, b = 3)")).isFalse();
+ assertThat((Boolean) ev.eval("struct(a = 1) == [1]")).isFalse();
+ assertThat((Boolean) ev.eval("[1] == struct(a = 1)")).isFalse();
+ assertThat((Boolean) ev.eval("struct() == struct()")).isTrue();
+ assertThat((Boolean) ev.eval("struct() == struct(a = 1)")).isFalse();
+
+ ev.exec("foo = provider(); bar = provider()");
+ assertThat((Boolean) ev.eval("struct(a = 1) == foo(a = 1)")).isFalse();
+ assertThat((Boolean) ev.eval("foo(a = 1) == struct(a = 1)")).isFalse();
+ assertThat((Boolean) ev.eval("foo(a = 1) == bar(a = 1)")).isFalse();
+ assertThat((Boolean) ev.eval("foo(a = 1) == foo(a = 1)")).isTrue();
+ }
+
+ @Test
+ public void testStructIncomparability() throws Exception {
+ ev.checkEvalErrorContains("Cannot compare structs", "struct(a = 1) < struct(a = 2)");
+ ev.checkEvalErrorContains("Cannot compare structs", "struct(a = 1) > struct(a = 2)");
+ ev.checkEvalErrorContains("Cannot compare structs", "struct(a = 1) <= struct(a = 2)");
+ ev.checkEvalErrorContains("Cannot compare structs", "struct(a = 1) >= struct(a = 2)");
+ }
+
+ @Test
+ public void testStructAccessingFieldsFromStarlark() throws Exception {
+ ev.exec("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
+ assertThat(ev.lookup("x1")).isEqualTo(1);
+ assertThat(ev.lookup("x2")).isEqualTo(2);
+ }
+
+ @Test
+ public void testStructAccessingUnknownField() throws Exception {
+ ev.checkEvalErrorContains(
+ "'struct' value has no field or method 'c'\n" + "Available attributes: a, b",
+ "x = struct(a = 1, b = 2)",
+ "y = x.c");
+ }
+
+ @Test
+ public void testStructAccessingUnknownFieldWithArgs() throws Exception {
+ ev.checkEvalErrorContains(
+ "'struct' value has no field or method 'c'", "x = struct(a = 1, b = 2)", "y = x.c()");
+ }
+
+ @Test
+ public void testStructAccessingNonFunctionFieldWithArgs() throws Exception {
+ ev.checkEvalErrorContains(
+ "'int' object is not callable", "x = struct(a = 1, b = 2)", "x1 = x.a(1)");
+ }
+
+ @Test
+ public void testStructAccessingFunctionFieldWithArgs() throws Exception {
+ ev.exec("def f(x): return x+5", "x = struct(a = f, b = 2)", "x1 = x.a(1)");
+ assertThat(ev.lookup("x1")).isEqualTo(6);
+ }
+
+ @Test
+ public void testStructPosArgs() throws Exception {
+ ev.checkEvalErrorContains(
+ "struct() got unexpected positional argument", "x = struct(1, b = 2)");
+ }
+
+ @Test
+ public void testStructConcatenationFieldNames() throws Exception {
+ // TODO(fwe): cannot be handled by current testing suite
+ ev.exec(
+ "x = struct(a = 1, b = 2)", //
+ "y = struct(c = 1, d = 2)",
+ "z = x + y\n");
+ StructImpl z = (StructImpl) ev.lookup("z");
+ assertThat(z.getFieldNames()).containsExactly("a", "b", "c", "d");
+ }
+
+ @Test
+ public void testStructConcatenationFieldValues() throws Exception {
+ // TODO(fwe): cannot be handled by current testing suite
+ ev.exec(
+ "x = struct(a = 1, b = 2)", //
+ "y = struct(c = 1, d = 2)",
+ "z = x + y\n");
+ StructImpl z = (StructImpl) ev.lookup("z");
+ assertThat(z.getValue("a")).isEqualTo(1);
+ assertThat(z.getValue("b")).isEqualTo(2);
+ assertThat(z.getValue("c")).isEqualTo(1);
+ assertThat(z.getValue("d")).isEqualTo(2);
+ }
+
+ @Test
+ public void testStructConcatenationCommonFields() throws Exception {
+ ev.checkEvalErrorContains(
+ "cannot add struct instances with common field 'a'",
+ "x = struct(a = 1, b = 2)",
+ "y = struct(c = 1, a = 2)",
+ "z = x + y\n");
+ }
+
+ @Test
+ public void testConditionalStructConcatenation() throws Exception {
+ // TODO(fwe): cannot be handled by current testing suite
+ ev.exec(
+ "def func():",
+ " x = struct(a = 1, b = 2)",
+ " if True:",
+ " x += struct(c = 1, d = 2)",
+ " return x",
+ "x = func()");
+ StructImpl x = (StructImpl) ev.lookup("x");
+ assertThat(x.getValue("a")).isEqualTo(1);
+ assertThat(x.getValue("b")).isEqualTo(2);
+ assertThat(x.getValue("c")).isEqualTo(1);
+ assertThat(x.getValue("d")).isEqualTo(2);
+ }
+
+ @Test
+ public void testGetattrNoAttr() throws Exception {
+ ev.checkEvalErrorContains(
+ "'struct' value has no field or method 'b'\nAvailable attributes: a",
+ "s = struct(a='val')",
+ "getattr(s, 'b')");
+ }
+
+ @Test
+ public void testGetattr() throws Exception {
+ ev.exec("s = struct(a='val')", "x = getattr(s, 'a')", "y = getattr(s, 'b', 'def')");
+ assertThat(ev.lookup("x")).isEqualTo("val");
+ assertThat(ev.lookup("y")).isEqualTo("def");
+ }
+
+ @Test
+ public void testHasattr() throws Exception {
+ ev.exec(
+ "s = struct(a=1)", //
+ "x = hasattr(s, 'a')",
+ "y = hasattr(s, 'b')\n");
+ assertThat(ev.lookup("x")).isEqualTo(true);
+ assertThat(ev.lookup("y")).isEqualTo(false);
+ }
+
+ @Test
+ public void testStructStr() throws Exception {
+ assertThat(ev.eval("str(struct(x = 2, y = 3, z = 4))"))
+ .isEqualTo("struct(x = 2, y = 3, z = 4)");
+ }
+
+ @Test
+ public void testStructsInSets() throws Exception {
+ ev.exec("depset([struct(a='a')])");
+ }
+
+ @Test
+ public void testStructsInDicts() throws Exception {
+ ev.exec("d = {struct(a = 1): 'aa', struct(b = 2): 'bb'}");
+ assertThat(ev.eval("d[struct(a = 1)]")).isEqualTo("aa");
+ assertThat(ev.eval("d[struct(b = 2)]")).isEqualTo("bb");
+ assertThat(ev.eval("str([d[k] for k in d])")).isEqualTo("[\"aa\", \"bb\"]");
+
+ ev.checkEvalErrorContains("unhashable type: 'struct'", "{struct(a = []): 'foo'}");
+ }
+
+ @Test
+ public void testStructDictMembersAreMutable() throws Exception {
+ ev.exec(
+ "s = struct(x = {'a' : 1})", //
+ "s.x['b'] = 2\n");
+ assertThat(((StructImpl) ev.lookup("s")).getValue("x"))
+ .isEqualTo(ImmutableMap.of("a", 1, "b", 2));
+ }
+
+ @Test
+ public void testDepsetGoodCompositeItem() throws Exception {
+ ev.exec("def func():", " return depset([struct(a='a')])", "s = func()");
+ ImmutableList<?> result = ((Depset) ev.lookup("s")).toList();
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0)).isInstanceOf(StructImpl.class);
+ }
+
+ private static StructImpl makeStruct(String field, Object value) {
+ return StructProvider.STRUCT.create(ImmutableMap.of(field, value), "no field '%'");
+ }
+
+ private static StructImpl makeBigStruct(@Nullable Mutability mu) {
+ // struct(a=[struct(x={1:1}), ()], b=(), c={2:2})
+ return StructProvider.STRUCT.create(
+ ImmutableMap.<String, Object>of(
+ "a",
+ StarlarkList.<Object>of(
+ mu,
+ StructProvider.STRUCT.create(
+ ImmutableMap.<String, Object>of("x", Dict.<Object, Object>of(mu, 1, 1)),
+ "no field '%s'"),
+ Tuple.of()),
+ "b", Tuple.of(),
+ "c", Dict.<Object, Object>of(mu, 2, 2)),
+ "no field '%s'");
+ }
+
+ @Test
+ public void testStructMutabilityShallow() throws Exception {
+ assertThat(EvalUtils.isImmutable(makeStruct("a", 1))).isTrue();
+ }
+
+ private static StarlarkList<Object> makeList(@Nullable Mutability mu) {
+ return StarlarkList.<Object>of(mu, 1, 2, 3);
+ }
+
+ @Test
+ public void testStructMutabilityDeep() throws Exception {
+ assertThat(EvalUtils.isImmutable(Tuple.<Object>of(makeList(null)))).isTrue();
+ assertThat(EvalUtils.isImmutable(makeStruct("a", makeList(null)))).isTrue();
+ assertThat(EvalUtils.isImmutable(makeBigStruct(null))).isTrue();
+
+ Mutability mu = Mutability.create("test");
+ assertThat(EvalUtils.isImmutable(Tuple.<Object>of(makeList(mu)))).isFalse();
+ assertThat(EvalUtils.isImmutable(makeStruct("a", makeList(mu)))).isFalse();
+ assertThat(EvalUtils.isImmutable(makeBigStruct(mu))).isFalse();
+ }
+
+ @Test
+ public void declaredProviders() throws Exception {
+ evalAndExport(ev, "data = provider()", "d = data(x = 1, y ='abc')", "d_x = d.x", "d_y = d.y");
+ assertThat(ev.lookup("d_x")).isEqualTo(1);
+ assertThat(ev.lookup("d_y")).isEqualTo("abc");
+ StarlarkProvider dataConstructor = (StarlarkProvider) ev.lookup("data");
+ StructImpl data = (StructImpl) ev.lookup("d");
+ assertThat(data.getProvider()).isEqualTo(dataConstructor);
+ assertThat(dataConstructor.isExported()).isTrue();
+ assertThat(dataConstructor.getPrintableName()).isEqualTo("data");
+ assertThat(dataConstructor.getKey()).isEqualTo(new StarlarkProvider.Key(FAKE_LABEL, "data"));
+ }
+
+ @Test
+ public void declaredProvidersConcatSuccess() throws Exception {
+ evalAndExport(
+ ev,
+ "data = provider()",
+ "dx = data(x = 1)",
+ "dy = data(y = 'abc')",
+ "dxy = dx + dy",
+ "x = dxy.x",
+ "y = dxy.y");
+ assertThat(ev.lookup("x")).isEqualTo(1);
+ assertThat(ev.lookup("y")).isEqualTo("abc");
+ StarlarkProvider dataConstructor = (StarlarkProvider) ev.lookup("data");
+ StructImpl dx = (StructImpl) ev.lookup("dx");
+ assertThat(dx.getProvider()).isEqualTo(dataConstructor);
+ StructImpl dy = (StructImpl) ev.lookup("dy");
+ assertThat(dy.getProvider()).isEqualTo(dataConstructor);
+ }
+
+ @Test
+ public void declaredProvidersConcatError() throws Exception {
+ evalAndExport(ev, "data1 = provider()", "data2 = provider()");
+
+ ev.checkEvalErrorContains(
+ "Cannot use '+' operator on instances of different providers (data1 and data2)",
+ "d1 = data1(x = 1)",
+ "d2 = data2(y = 2)",
+ "d = d1 + d2");
+ }
+
+ @Test
+ public void declaredProvidersWithFieldsConcatSuccess() throws Exception {
+ evalAndExport(
+ ev,
+ "data = provider(fields=['f1', 'f2'])",
+ "d1 = data(f1 = 4)",
+ "d2 = data(f2 = 5)",
+ "d3 = d1 + d2",
+ "f1 = d3.f1",
+ "f2 = d3.f2");
+ assertThat(ev.lookup("f1")).isEqualTo(4);
+ assertThat(ev.lookup("f2")).isEqualTo(5);
+ }
+
+ @Test
+ public void declaredProvidersWithFieldsConcatError() throws Exception {
+ evalAndExport(ev, "data1 = provider(fields=['f1', 'f2'])", "data2 = provider(fields=['f3'])");
+ ev.checkEvalErrorContains(
+ "Cannot use '+' operator on instances of different providers (data1 and data2)",
+ "d1 = data1(f1=1, f2=2)",
+ "d2 = data2(f3=3)",
+ "d = d1 + d2");
+ }
+
+ @Test
+ public void declaredProvidersWithOverlappingFieldsConcatError() throws Exception {
+ evalAndExport(ev, "data = provider(fields=['f1', 'f2'])");
+ ev.checkEvalErrorContains(
+ "cannot add struct instances with common field 'f1'",
+ "d1 = data(f1 = 4)",
+ "d2 = data(f1 = 5)",
+ "d1 + d2");
+ }
+
+ @Test
+ public void structsAsDeclaredProvidersTest() throws Exception {
+ evalAndExport(ev, "data = struct(x = 1)");
+ StructImpl data = (StructImpl) ev.lookup("data");
+ assertThat(StructProvider.STRUCT.isExported()).isTrue();
+ assertThat(data.getProvider()).isEqualTo(StructProvider.STRUCT);
+ assertThat(data.getProvider().getKey()).isEqualTo(StructProvider.STRUCT.getKey());
+ }
+
+ @Test
+ public void declaredProvidersDoc() throws Exception {
+ evalAndExport(ev, "data1 = provider(doc='foo')");
+ }
+
+ @Test
+ public void declaredProvidersBadTypeForDoc() throws Exception {
+ ev.checkEvalErrorContains("got value of type 'int', want 'string'", "provider(doc = 1)");
+ }
+
+ @Test
+ public void aspectAllAttrs() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):", //
+ " pass",
+ "my_aspect = aspect(_impl, attr_aspects=['*'])");
+
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ assertThat(myAspect.getDefinition(AspectParameters.EMPTY).propagateAlong("foo")).isTrue();
+ }
+
+ @Test
+ public void aspectRequiredAspectProvidersSingle() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "cc = provider()",
+ "my_aspect = aspect(_impl, required_aspect_providers=['java', cc])");
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ RequiredProviders requiredProviders = myAspect.getDefinition(AspectParameters.EMPTY)
+ .getRequiredProvidersForAspects();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isTrue();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY)).isFalse();
+ assertThat(
+ requiredProviders.isSatisfiedBy(
+ AdvertisedProviderSet.builder()
+ .addStarlark(declared("cc"))
+ .addStarlark("java")
+ .build()))
+ .isTrue();
+ assertThat(
+ requiredProviders.isSatisfiedBy(
+ AdvertisedProviderSet.builder().addStarlark("cc").build()))
+ .isFalse();
+ }
+
+ @Test
+ public void aspectRequiredAspectProvidersAlternatives() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "cc = provider()",
+ "my_aspect = aspect(_impl, required_aspect_providers=[['java'], [cc]])");
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ RequiredProviders requiredProviders = myAspect.getDefinition(AspectParameters.EMPTY)
+ .getRequiredProvidersForAspects();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isTrue();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY)).isFalse();
+ assertThat(
+ requiredProviders.isSatisfiedBy(
+ AdvertisedProviderSet.builder().addStarlark("java").build()))
+ .isTrue();
+ assertThat(
+ requiredProviders.isSatisfiedBy(
+ AdvertisedProviderSet.builder().addStarlark(declared("cc")).build()))
+ .isTrue();
+ assertThat(
+ requiredProviders.isSatisfiedBy(
+ AdvertisedProviderSet.builder().addStarlark("prolog").build()))
+ .isFalse();
+ }
+
+ @Test
+ public void aspectRequiredAspectProvidersEmpty() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "my_aspect = aspect(_impl, required_aspect_providers=[])");
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ RequiredProviders requiredProviders = myAspect.getDefinition(AspectParameters.EMPTY)
+ .getRequiredProvidersForAspects();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isFalse();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY)).isFalse();
+ }
+
+ @Test
+ public void aspectRequiredAspectProvidersDefault() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):", //
+ " pass",
+ "my_aspect = aspect(_impl)");
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ RequiredProviders requiredProviders = myAspect.getDefinition(AspectParameters.EMPTY)
+ .getRequiredProvidersForAspects();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isFalse();
+ assertThat(requiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY)).isFalse();
+ }
+
+ @Test
+ public void aspectProvides() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "y = provider()",
+ "my_aspect = aspect(_impl, provides = ['x', y])");
+ StarlarkDefinedAspect myAspect = (StarlarkDefinedAspect) ev.lookup("my_aspect");
+ AdvertisedProviderSet advertisedProviders = myAspect.getDefinition(AspectParameters.EMPTY)
+ .getAdvertisedProviders();
+ assertThat(advertisedProviders.canHaveAnyProvider()).isFalse();
+ assertThat(advertisedProviders.getStarlarkProviders())
+ .containsExactly(legacy("x"), declared("y"));
+ }
+
+ @Test
+ public void aspectProvidesError() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "y = provider()",
+ "my_aspect = aspect(_impl, provides = ['x', 1])");
+ MoreAsserts.assertContainsEvent(ev.getEventCollector(),
+ " Illegal argument: element in 'provides' is of unexpected type."
+ + " Should be list of providers, but got item of type int. ");
+ }
+
+ @Test
+ public void aspectDoc() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):", //
+ " pass",
+ "my_aspect = aspect(_impl, doc='foo')");
+ }
+
+ @Test
+ public void aspectBadTypeForDoc() throws Exception {
+ registerDummyStarlarkFunction();
+ ev.checkEvalErrorContains("got value of type 'int', want 'string'", "aspect(impl, doc = 1)");
+ }
+
+ @Test
+ public void fancyExports() throws Exception {
+ evalAndExport(
+ ev,
+ "def _impla(target, ctx): pass",
+ "p, (a, p1) = [",
+ " provider(),",
+ " [ aspect(_impla),",
+ " provider() ]",
+ "]");
+ StarlarkProvider p = (StarlarkProvider) ev.lookup("p");
+ StarlarkDefinedAspect a = (StarlarkDefinedAspect) ev.lookup("a");
+ StarlarkProvider p1 = (StarlarkProvider) ev.lookup("p1");
+ assertThat(p.getPrintableName()).isEqualTo("p");
+ assertThat(p.getKey()).isEqualTo(new StarlarkProvider.Key(FAKE_LABEL, "p"));
+ assertThat(p1.getPrintableName()).isEqualTo("p1");
+ assertThat(p1.getKey()).isEqualTo(new StarlarkProvider.Key(FAKE_LABEL, "p1"));
+ assertThat(a.getAspectClass()).isEqualTo(new StarlarkAspectClass(FAKE_LABEL, "a"));
+ }
+
+ @Test
+ public void multipleTopLevels() throws Exception {
+ evalAndExport(
+ ev,
+ "p = provider()", //
+ "p1 = p");
+ StarlarkProvider p = (StarlarkProvider) ev.lookup("p");
+ StarlarkProvider p1 = (StarlarkProvider) ev.lookup("p1");
+ assertThat(p).isEqualTo(p1);
+ assertThat(p.getKey()).isEqualTo(new StarlarkProvider.Key(FAKE_LABEL, "p"));
+ assertThat(p1.getKey()).isEqualTo(new StarlarkProvider.Key(FAKE_LABEL, "p"));
+ }
+
+ @Test
+ public void providerWithFields() throws Exception {
+ evalAndExport(
+ ev,
+ "p = provider(fields = ['x', 'y'])", //
+ "p1 = p(x = 1, y = 2)",
+ "x = p1.x",
+ "y = p1.y");
+ StarlarkProvider p = (StarlarkProvider) ev.lookup("p");
+ StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
+
+ assertThat(p1.getProvider()).isEqualTo(p);
+ assertThat(ev.lookup("x")).isEqualTo(1);
+ assertThat(ev.lookup("y")).isEqualTo(2);
+ }
+
+ @Test
+ public void providerWithFieldsDict() throws Exception {
+ evalAndExport(
+ ev,
+ "p = provider(fields = { 'x' : 'I am x', 'y' : 'I am y'})",
+ "p1 = p(x = 1, y = 2)",
+ "x = p1.x",
+ "y = p1.y");
+ StarlarkProvider p = (StarlarkProvider) ev.lookup("p");
+ StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
+
+ assertThat(p1.getProvider()).isEqualTo(p);
+ assertThat(ev.lookup("x")).isEqualTo(1);
+ assertThat(ev.lookup("y")).isEqualTo(2);
+ }
+
+ @Test
+ public void providerWithFieldsOptional() throws Exception {
+ evalAndExport(
+ ev,
+ "p = provider(fields = ['x', 'y'])", //
+ "p1 = p(y = 2)",
+ "y = p1.y");
+ StarlarkProvider p = (StarlarkProvider) ev.lookup("p");
+ StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
+
+ assertThat(p1.getProvider()).isEqualTo(p);
+ assertThat(ev.lookup("y")).isEqualTo(2);
+ }
+
+ @Test
+ public void providerWithFieldsOptionalError() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "p = provider(fields = ['x', 'y'])", //
+ "p1 = p(y = 2)",
+ "x = p1.x");
+ MoreAsserts.assertContainsEvent(
+ ev.getEventCollector(), " 'p' value has no field or method 'x'");
+ }
+
+ @Test
+ public void providerWithExtraFieldsError() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(ev, "p = provider(fields = ['x', 'y'])", "p1 = p(x = 1, y = 2, z = 3)");
+ MoreAsserts.assertContainsEvent(
+ ev.getEventCollector(), "unexpected keyword z in call to instantiate provider p");
+ }
+
+ @Test
+ public void providerWithEmptyFieldsError() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "p = provider(fields = [])", //
+ "p1 = p(x = 1, y = 2, z = 3)");
+ MoreAsserts.assertContainsEvent(
+ ev.getEventCollector(), "unexpected keywords x, y, z in call to instantiate provider p");
+ }
+
+ @Test
+ public void providerWithDuplicateFieldsError() throws Exception {
+ ev.setFailFast(false);
+ evalAndExport(
+ ev,
+ "p = provider(fields = ['a', 'b'])", //
+ "p(a = 1, b = 2, **dict(b = 3))");
+ MoreAsserts.assertContainsEvent(
+ ev.getEventCollector(),
+ "got multiple values for parameter b in call to instantiate provider p");
+ }
+
+ @Test
+ public void starTheOnlyAspectArg() throws Exception {
+ ev.checkEvalErrorContains(
+ "'*' must be the only string in 'attr_aspects' list",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect(_impl, attr_aspects=['*', 'foo'])");
+ }
+
+ @Test
+ public void testMandatoryConfigParameterForExecutableLabels() throws Exception {
+ scratch.file("third_party/foo/extension.bzl",
+ "def _main_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(_main_rule_impl,",
+ " attrs = { ",
+ " 'exe' : attr.label(executable = True, allow_files = True),",
+ " },",
+ ")"
+ );
+ scratch.file("third_party/foo/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'main', exe = ':tool.sh')"
+ );
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> createRuleContext("//third_party/foo:main"));
+ assertThat(expected).hasMessageThat()
+ .contains("cfg parameter is mandatory when executable=True is provided.");
+ }
+
+ @Test
+ public void testRuleAddToolchain() throws Exception {
+ scratch.file("test/BUILD", "toolchain_type(name = 'my_toolchain_type')");
+ evalAndExport(
+ ev,
+ "def impl(ctx): return None",
+ "r1 = rule(impl, toolchains=['//test:my_toolchain_type'])");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.getRequiredToolchains()).containsExactly(makeLabel("//test:my_toolchain_type"));
+ }
+
+ @Test
+ public void testRuleAddExecutionConstraints() throws Exception {
+ registerDummyStarlarkFunction();
+ scratch.file("test/BUILD", "toolchain_type(name = 'my_toolchain_type')");
+ evalAndExport(
+ ev,
+ "r1 = rule(",
+ " implementation = impl,",
+ " toolchains=['//test:my_toolchain_type'],",
+ " exec_compatible_with=['//constraint:cv1', '//constraint:cv2'],",
+ ")");
+ RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
+ assertThat(c.getExecutionPlatformConstraints())
+ .containsExactly(makeLabel("//constraint:cv1"), makeLabel("//constraint:cv2"));
+ }
+
+ @Test
+ public void testRuleAddExecGroup() throws Exception {
+ setStarlarkSemanticsOptions("--experimental_exec_groups=true");
+
+ registerDummyStarlarkFunction();
+ scratch.file("test/BUILD", "toolchain_type(name = 'my_toolchain_type')");
+ evalAndExport(
+ ev,
+ "plum = rule(",
+ " implementation = impl,",
+ " exec_groups = {",
+ " 'group': exec_group(",
+ " toolchains=['//test:my_toolchain_type'],",
+ " exec_compatible_with=['//constraint:cv1', '//constraint:cv2'],",
+ " ),",
+ " },",
+ ")");
+ RuleClass plum = ((StarlarkRuleFunction) ev.lookup("plum")).getRuleClass();
+ assertThat(plum.getRequiredToolchains()).isEmpty();
+ assertThat(plum.getExecGroups().get("group").requiredToolchains())
+ .containsExactly(makeLabel("//test:my_toolchain_type"));
+ assertThat(plum.getExecutionPlatformConstraints()).isEmpty();
+ assertThat(plum.getExecGroups().get("group").execCompatibleWith())
+ .containsExactly(makeLabel("//constraint:cv1"), makeLabel("//constraint:cv2"));
+ }
+
+ @Test
+ public void testRuleFunctionReturnsNone() throws Exception {
+ scratch.file("test/rule.bzl",
+ "def _impl(ctx):",
+ " pass",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {'params': attr.string_list()},",
+ ")");
+ scratch.file("test/BUILD",
+ "load(':rule.bzl', 'foo_rule')",
+ "r = foo_rule(name='foo')", // Custom rule should return None
+ "c = cc_library(name='cc')", // Native rule should return None
+ "",
+ "foo_rule(",
+ " name='check',",
+ " params = [type(r), type(c)]",
+ ")");
+ invalidatePackages();
+ StarlarkRuleContext context = createRuleContext("//test:check");
+ @SuppressWarnings("unchecked")
+ StarlarkList<Object> params = (StarlarkList<Object>) context.getAttr().getValue("params");
+ assertThat(params.get(0)).isEqualTo("NoneType");
+ assertThat(params.get(1)).isEqualTo("NoneType");
+ }
+
+ @Test
+ public void testTypeOfStruct() throws Exception {
+ ev.exec("p = type(struct)", "s = type(struct())");
+
+ assertThat(ev.lookup("p")).isEqualTo("Provider");
+ assertThat(ev.lookup("s")).isEqualTo("struct");
+ }
+
+ @Test
+ public void testCreateExecGroup() throws Exception {
+ setStarlarkSemanticsOptions("--experimental_exec_groups=true");
+
+ scratch.file("test/BUILD", "toolchain_type(name = 'my_toolchain_type')");
+ evalAndExport(
+ ev,
+ "group = exec_group(",
+ " toolchains=['//test:my_toolchain_type'],",
+ " exec_compatible_with=['//constraint:cv1', '//constraint:cv2'],",
+ ")");
+ ExecGroup group = ((ExecGroup) ev.lookup("group"));
+ assertThat(group.requiredToolchains()).containsExactly(makeLabel("//test:my_toolchain_type"));
+ assertThat(group.execCompatibleWith())
+ .containsExactly(makeLabel("//constraint:cv1"), makeLabel("//constraint:cv2"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
new file mode 100644
index 0000000..2567bac
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
@@ -0,0 +1,2974 @@
+// 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.devtools.build.lib.analysis.ToolchainCollection.DEFAULT_EXEC_GROUP_NAME;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ActionsProvider;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.ExecGroupCollection;
+import com.google.devtools.build.lib.analysis.ResolvedToolchainContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.StarlarkAction;
+import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.analysis.util.MockRule;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.packages.StarlarkInfo;
+import com.google.devtools.build.lib.packages.StarlarkProvider;
+import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.rules.java.JavaInfo;
+import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
+import com.google.devtools.build.lib.rules.python.PyProviderUtils;
+import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
+import com.google.devtools.build.lib.syntax.Dict;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.Sequence;
+import com.google.devtools.build.lib.syntax.Starlark;
+import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link StarlarkRuleContext}. */
+@RunWith(JUnit4.class)
+public final class StarlarkRuleContextTest extends BuildViewTestCase {
+
+ private StarlarkRuleContext createRuleContext(String label) throws Exception {
+ return new StarlarkRuleContext(
+ getRuleContextForStarlark(getConfiguredTarget(label)), null, getStarlarkSemantics());
+ }
+
+ private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+
+ /** A test rule that exercises the semantics of mandatory providers. */
+ private static final MockRule TESTING_RULE_FOR_MANDATORY_PROVIDERS =
+ () ->
+ MockRule.define(
+ "testing_rule_for_mandatory_providers",
+ (builder, env) ->
+ builder
+ .setUndocumented()
+ .add(attr("srcs", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE))
+ .add(
+ attr("deps", LABEL_LIST)
+ .legacyAllowAnyFileType()
+ .mandatoryProvidersList(
+ ImmutableList.of(
+ ImmutableList.of(StarlarkProviderIdentifier.forLegacy("a")),
+ ImmutableList.of(
+ StarlarkProviderIdentifier.forLegacy("b"),
+ StarlarkProviderIdentifier.forLegacy("c"))))));
+
+ @Override
+ protected ConfiguredRuleClassProvider createRuleClassProvider() {
+ ConfiguredRuleClassProvider.Builder builder =
+ new ConfiguredRuleClassProvider.Builder()
+ .addRuleDefinition(TESTING_RULE_FOR_MANDATORY_PROVIDERS);
+ TestRuleClassProvider.addStandardRules(builder);
+ return builder.build();
+ }
+
+ @Before
+ public final void setupMyInfoAndGenerateBuildFile() throws Exception {
+ scratch.file("myinfo/myinfo.bzl", "MyInfo = provider()");
+ scratch.file("myinfo/BUILD");
+ scratch.file(
+ "foo/BUILD",
+ "package(features = ['-f1', 'f2', 'f3'])",
+ "genrule(name = 'foo',",
+ " cmd = 'dummy_cmd',",
+ " srcs = ['a.txt', 'b.img'],",
+ " tools = ['t.exe'],",
+ " outs = ['c.txt'])",
+ "genrule(name = 'foo2',",
+ " cmd = 'dummy_cmd',",
+ " outs = ['e.txt'])",
+ "genrule(name = 'bar',",
+ " cmd = 'dummy_cmd',",
+ " srcs = [':jl', ':gl'],",
+ " outs = ['d.txt'])",
+ "java_library(name = 'jl',",
+ " srcs = ['a.java'])",
+ "android_library(name = 'androidlib',",
+ " srcs = ['a.java'])",
+ "java_import(name = 'asr',",
+ " jars = [ 'asr.jar' ],",
+ " srcjar = 'asr-src.jar',",
+ ")",
+ "genrule(name = 'gl',",
+ " cmd = 'touch $(OUTS)',",
+ " srcs = ['a.go'],",
+ " outs = [ 'gl.a', 'gl.gcgox', ],",
+ " output_to_bindir = 1,",
+ ")",
+ "cc_library(name = 'cc_with_features',",
+ " srcs = ['dummy.cc'],",
+ " features = ['f1', '-f3'],",
+ ")");
+ }
+
+ private void setRuleContext(StarlarkRuleContext ctx) throws Exception {
+ ev.update("ruleContext", ctx);
+ }
+
+ private void setUpAttributeErrorTest() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:macros.bzl', 'macro_native_rule', 'macro_starlark_rule', 'starlark_rule')",
+ "macro_native_rule(name = 'm_native',",
+ " deps = [':jlib'])",
+ "macro_starlark_rule(name = 'm_starlark',",
+ " deps = [':jlib'])",
+ "java_library(name = 'jlib',",
+ " srcs = ['bla.java'])",
+ "cc_library(name = 'cclib',",
+ " deps = [':jlib'])",
+ "starlark_rule(name = 'skyrule',",
+ " deps = [':jlib'])");
+ scratch.file(
+ "test/macros.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(providers = ['some_provider'], allow_files=True)",
+ " }",
+ ")",
+ "def macro_native_rule(name, deps): ",
+ " native.cc_library(name = name, deps = deps)",
+ "def macro_starlark_rule(name, deps):",
+ " starlark_rule(name = name, deps = deps)");
+ reporter.removeHandler(failFastHandler);
+ }
+
+ @Test
+ public void hasCorrectLocationForRuleAttributeError_NativeRuleWithMacro() throws Exception {
+ setUpAttributeErrorTest();
+ assertThrows(Exception.class, () -> createRuleContext("//test:m_native"));
+ assertContainsEvent("misplaced here");
+ // Skip the part of the error message that has details about the allowed deps since the mocks
+ // for the mac tests might have different values for them.
+ assertContainsEvent(
+ ". Since this "
+ + "rule was created by the macro 'macro_native_rule', the error might have been caused "
+ + "by the macro implementation");
+ }
+
+ @Test
+ public void hasCorrectLocationForRuleAttributeError_StarlarkRuleWithMacro() throws Exception {
+ setUpAttributeErrorTest();
+ assertThrows(Exception.class, () -> createRuleContext("//test:m_starlark"));
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:4:20: in deps attribute of starlark_rule rule "
+ + "//test:m_starlark: '//test:jlib' does not have mandatory providers:"
+ + " 'some_provider'. "
+ + "Since this rule was created by the macro 'macro_starlark_rule', the error might "
+ + "have been caused by the macro implementation");
+ }
+
+ @Test
+ public void hasCorrectLocationForRuleAttributeError_NativeRule() throws Exception {
+ setUpAttributeErrorTest();
+ assertThrows(Exception.class, () -> createRuleContext("//test:cclib"));
+ assertContainsEvent("misplaced here");
+ // Skip the part of the error message that has details about the allowed deps since the mocks
+ // for the mac tests might have different values for them.
+ assertDoesNotContainEvent("Since this rule was created by the macro");
+ }
+
+ @Test
+ public void hasCorrectLocationForRuleAttributeError_StarlarkRule() throws Exception {
+ setUpAttributeErrorTest();
+ assertThrows(Exception.class, () -> createRuleContext("//test:skyrule"));
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:10:14: in deps attribute of "
+ + "starlark_rule rule //test:skyrule: '//test:jlib' does not have mandatory providers: "
+ + "'some_provider'");
+ }
+
+ @Test
+ public void testMandatoryProvidersListWithStarlark() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'starlark_rule', 'my_rule', 'my_other_rule')",
+ "my_rule(name = 'mylib',",
+ " srcs = ['a.py'])",
+ "starlark_rule(name = 'skyrule1',",
+ " deps = [':mylib'])",
+ "my_other_rule(name = 'my_other_lib',",
+ " srcs = ['a.py'])",
+ "starlark_rule(name = 'skyrule2',",
+ " deps = [':my_other_lib'])");
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(providers = [['a'], ['b', 'c']],",
+ " allow_files=True)",
+ " }",
+ ")",
+ "def my_rule_impl(ctx):",
+ " return struct(a = [])",
+ "my_rule = rule(implementation = my_rule_impl, ",
+ " attrs = { 'srcs' : attr.label_list(allow_files=True)})",
+ "def my_other_rule_impl(ctx):",
+ " return struct(b = [])",
+ "my_other_rule = rule(implementation = my_other_rule_impl, ",
+ " attrs = { 'srcs' : attr.label_list(allow_files=True)})");
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test:skyrule1")).isNotNull();
+
+ assertThrows(Exception.class, () -> createRuleContext("//test:skyrule2"));
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:8:14: in deps attribute of "
+ + "starlark_rule rule //test:skyrule2: '//test:my_other_lib' does not have "
+ + "mandatory providers: 'a' or 'c'");
+ }
+
+ @Test
+ public void testMandatoryProvidersListWithNative() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule', 'my_other_rule')",
+ "my_rule(name = 'mylib',",
+ " srcs = ['a.py'])",
+ "testing_rule_for_mandatory_providers(name = 'skyrule1',",
+ " deps = [':mylib'])",
+ "my_other_rule(name = 'my_other_lib',",
+ " srcs = ['a.py'])",
+ "testing_rule_for_mandatory_providers(name = 'skyrule2',",
+ " deps = [':my_other_lib'])");
+ scratch.file(
+ "test/rules.bzl",
+ "def my_rule_impl(ctx):",
+ " return struct(a = [])",
+ "my_rule = rule(implementation = my_rule_impl, ",
+ " attrs = { 'srcs' : attr.label_list(allow_files=True)})",
+ "def my_other_rule_impl(ctx):",
+ " return struct(b = [])",
+ "my_other_rule = rule(implementation = my_other_rule_impl, ",
+ " attrs = { 'srcs' : attr.label_list(allow_files=True)})");
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//test:skyrule1")).isNotNull();
+
+ assertThrows(Exception.class, () -> createRuleContext("//test:skyrule2"));
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:8:37: in deps attribute of "
+ + "testing_rule_for_mandatory_providers rule //test:skyrule2: '//test:my_other_lib' "
+ + "does not have mandatory providers: 'a' or 'c'");
+ }
+
+ /* Sharing setup code between the testPackageBoundaryError*() methods is not possible since the
+ * errors already happen when loading the file. Consequently, all tests would fail at the same
+ * statement. */
+ @Test
+ public void testPackageBoundaryError_NativeRule() throws Exception {
+ scratch.file("test/BUILD", "cc_library(name = 'cclib',", " srcs = ['sub/my_sub_lib.h'])");
+ scratch.file("test/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:cclib");
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:1:11: Label '//test:sub/my_sub_lib.h' is invalid because "
+ + "'test/sub' is a subpackage; perhaps you meant to put the colon here: "
+ + "'//test/sub:my_sub_lib.h'?");
+ }
+
+ @Test
+ public void testPackageBoundaryError_StarlarkRule() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:macros.bzl', 'starlark_rule')",
+ "starlark_rule(name = 'skyrule',",
+ " srcs = ['sub/my_sub_lib.h'])");
+ scratch.file("test/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ scratch.file(
+ "test/macros.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True)",
+ " }",
+ ")");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:skyrule");
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:2:14: Label '//test:sub/my_sub_lib.h' is invalid because "
+ + "'test/sub' is a subpackage; perhaps you meant to put the colon here: "
+ + "'//test/sub:my_sub_lib.h'?");
+ }
+
+ @Test
+ public void testPackageBoundaryError_StarlarkMacro() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:macros.bzl', 'macro_starlark_rule')",
+ "macro_starlark_rule(name = 'm_starlark',",
+ " srcs = ['sub/my_sub_lib.h'])");
+ scratch.file("test/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ scratch.file(
+ "test/macros.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True)",
+ " }",
+ ")",
+ "def macro_starlark_rule(name, srcs=[]):",
+ " starlark_rule(name = name, srcs = srcs)");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:m_starlark");
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:2:20: Label '//test:sub/my_sub_lib.h' is invalid because"
+ + " 'test/sub' is a subpackage; perhaps you meant to put the colon here: "
+ + "'//test/sub:my_sub_lib.h'?");
+ }
+
+ /* The error message for this case used to be wrong. */
+ @Test
+ public void testPackageBoundaryError_ExternalRepository_Boundary() throws Exception {
+ scratch.file("r/WORKSPACE");
+ scratch.file("r/BUILD");
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='r')")
+ .build());
+ scratch.file("BUILD", "cc_library(name = 'cclib',", " srcs = ['r/my_sub_lib.h'])");
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//:cclib");
+ assertContainsEvent(
+ "/workspace/BUILD:1:11: Label '//:r/my_sub_lib.h' is invalid because "
+ + "'@r//' is a subpackage");
+ }
+
+ /* The error message for this case used to be wrong. */
+ @Test
+ public void testPackageBoundaryError_ExternalRepository_EntirelyInside() throws Exception {
+ scratch.file("/r/WORKSPACE");
+ scratch.file("/r/BUILD", "cc_library(name = 'cclib',", " srcs = ['sub/my_sub_lib.h'])");
+ scratch.file("/r/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='/r')")
+ .build());
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("@r//:cclib");
+ assertContainsEvent(
+ "/external/r/BUILD:1:11: Label '@r//:sub/my_sub_lib.h' is invalid because "
+ + "'@r//sub' is a subpackage; perhaps you meant to put the colon here: "
+ + "'@r//sub:my_sub_lib.h'?");
+ }
+
+ /*
+ * Making the location in BUILD file the default for "crosses boundary of subpackage" errors does
+ * not work in this case since the error actually happens in the bzl file. However, because of
+ * the current design, we can neither show the location in the bzl file nor display both
+ * locations (BUILD + bzl).
+ *
+ * Since this case is less common than having such an error in a BUILD file, we can live
+ * with it.
+ */
+ @Test
+ public void testPackageBoundaryError_StarlarkMacroWithErrorInBzlFile() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:macros.bzl', 'macro_starlark_rule')",
+ "macro_starlark_rule(name = 'm_starlark')");
+ scratch.file("test/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ scratch.file(
+ "test/macros.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True)",
+ " }",
+ ")",
+ "def macro_starlark_rule(name, srcs=[]):",
+ " starlark_rule(name = name, srcs = srcs + ['sub/my_sub_lib.h'])");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:m_starlark");
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:2:20: Label '//test:sub/my_sub_lib.h' "
+ + "is invalid because 'test/sub' is a subpackage");
+ }
+
+ @Test
+ public void testPackageBoundaryError_NativeMacro() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:macros.bzl', 'macro_native_rule')",
+ "macro_native_rule(name = 'm_native',",
+ " srcs = ['sub/my_sub_lib.h'])");
+ scratch.file("test/sub/BUILD", "cc_library(name = 'my_sub_lib', srcs = ['my_sub_lib.h'])");
+ scratch.file(
+ "test/macros.bzl",
+ "def macro_native_rule(name, deps=[], srcs=[]): ",
+ " native.cc_library(name = name, deps = deps, srcs = srcs)");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:m_native");
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:2:18: Label '//test:sub/my_sub_lib.h' "
+ + "is invalid because 'test/sub' is a subpackage");
+ }
+
+ @Test
+ public void shouldGetPrerequisiteArtifacts() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.files.srcs");
+ assertArtifactList(result, ImmutableList.of("a.txt", "b.img"));
+ }
+
+ private static void assertArtifactList(Object result, List<String> artifacts) {
+ assertThat(result).isInstanceOf(Sequence.class);
+ Sequence<?> resultList = (Sequence) result;
+ assertThat(resultList).hasSize(artifacts.size());
+ int i = 0;
+ for (String artifact : artifacts) {
+ assertThat(((Artifact) resultList.get(i++)).getFilename()).isEqualTo(artifact);
+ }
+ }
+
+ @Test
+ public void shouldGetPrerequisites() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:bar");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.srcs");
+ // Check for a known provider
+ TransitiveInfoCollection tic1 = (TransitiveInfoCollection) ((Sequence) result).get(0);
+ assertThat(JavaInfo.getProvider(JavaSourceJarsProvider.class, tic1)).isNotNull();
+ // Check an unimplemented provider too
+ assertThat(PyProviderUtils.hasLegacyProvider(tic1)).isFalse();
+ }
+
+ @Test
+ public void shouldGetPrerequisite() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:asr");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.srcjar");
+ TransitiveInfoCollection tic = (TransitiveInfoCollection) result;
+ assertThat(tic).isInstanceOf(FileConfiguredTarget.class);
+ assertThat(tic.getLabel().getName()).isEqualTo("asr-src.jar");
+ }
+
+ @Test
+ public void testGetRuleAttributeListType() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.outs");
+ assertThat(result).isInstanceOf(Sequence.class);
+ }
+
+ @Test
+ public void testGetRuleSelect() throws Exception {
+ scratch.file("test/starlark/BUILD");
+ scratch.file(
+ "test/starlark/rulestr.bzl", "def rule_dict(name):", " return native.existing_rule(name)");
+
+ scratch.file(
+ "test/getrule/BUILD",
+ "load('//test/starlark:rulestr.bzl', 'rule_dict')",
+ "cc_library(name ='x', ",
+ " srcs = select({'//conditions:default': []})",
+ ")",
+ "rule_dict('x')");
+
+ // Parse the BUILD file, to make sure select() makes it out of native.rule().
+ createRuleContext("//test/getrule:x");
+ }
+
+ @Test
+ public void testExistingRuleReturnNone() throws Exception {
+ scratch.file(
+ "test/rulestr.bzl",
+ "def test_rule(name, x):",
+ " print(native.existing_rule(x))",
+ " if native.existing_rule(x) == None:",
+ " native.cc_library(name = name)");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rulestr.bzl', 'test_rule')",
+ "test_rule('a', 'does not exist')",
+ "test_rule('b', 'BUILD')");
+
+ assertThat(getConfiguredTarget("//test:a")).isNotNull();
+ assertThat(getConfiguredTarget("//test:b")).isNotNull();
+ }
+
+ @Test
+ public void existingRuleWithSelect() throws Exception {
+ scratch.file(
+ "test/existing_rule.bzl",
+ "def macro():",
+ " s = select({'//foo': ['//bar']})",
+ " native.cc_library(name = 'x', srcs = s)",
+ " print(native.existing_rule('x')['srcs'])");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:existing_rule.bzl', 'macro')",
+ "macro()",
+ "cc_library(name = 'a', srcs = [])");
+ getConfiguredTarget("//test:a");
+ assertContainsEvent("select({Label(\"//foo:foo\"): [Label(\"//bar:bar\")]})");
+ }
+
+ @Test
+ public void testGetRule() throws Exception {
+ scratch.file("test/starlark/BUILD");
+ scratch.file(
+ "test/starlark/rulestr.bzl",
+ "def rule_dict(name):",
+ " return native.existing_rule(name)",
+ "def rules_dict():",
+ " return native.existing_rules()",
+ "def nop(ctx):",
+ " pass",
+ "nop_rule = rule(attrs = {'x': attr.label()}, implementation = nop)",
+ "consume_rule = rule(attrs = {'s': attr.string_list()}, implementation = nop)");
+
+ scratch.file(
+ "test/getrule/BUILD",
+ "load('//test/starlark:rulestr.bzl', 'rules_dict', 'rule_dict', 'nop_rule',"
+ + "'consume_rule')",
+ "genrule(name = 'a', outs = ['a.txt'], ",
+ " licenses = ['notice'],",
+ " output_to_bindir = False,",
+ " tools = [ '//test:bla' ], cmd = 'touch $@')",
+ "nop_rule(name = 'c', x = ':a')",
+ "rlist= rules_dict()",
+ "consume_rule(name = 'all_str', s = [rlist['a']['kind'], rlist['a']['name'], ",
+ " rlist['c']['kind'], rlist['c']['name']])",
+ "adict = rule_dict('a')",
+ "cdict = rule_dict('c')",
+ "consume_rule(name = 'a_str', ",
+ " s = [adict['kind'], adict['name'], adict['outs'][0], adict['tools'][0]])",
+ "consume_rule(name = 'genrule_attr', ",
+ " s = adict.keys())",
+ "consume_rule(name = 'c_str', s = [cdict['kind'], cdict['name'], cdict['x']])");
+
+ StarlarkRuleContext allContext = createRuleContext("//test/getrule:all_str");
+ setRuleContext(allContext);
+ List<?> result = (List) ev.eval("ruleContext.attr.s");
+ assertThat(result).containsExactly("genrule", "a", "nop_rule", "c");
+
+ setRuleContext(createRuleContext("//test/getrule:a_str"));
+ result = (List) ev.eval("ruleContext.attr.s");
+ assertThat(result).containsExactly("genrule", "a", ":a.txt", "//test:bla");
+
+ setRuleContext(createRuleContext("//test/getrule:c_str"));
+ result = (List) ev.eval("ruleContext.attr.s");
+ assertThat(result).containsExactly("nop_rule", "c", ":a");
+
+ setRuleContext(createRuleContext("//test/getrule:genrule_attr"));
+ result = (List) ev.eval("ruleContext.attr.s");
+ assertThat(result)
+ .containsAtLeast(
+ "name",
+ "visibility",
+ "transitive_configs",
+ "tags",
+ "generator_name",
+ "generator_function",
+ "generator_location",
+ "features",
+ "compatible_with",
+ "restricted_to",
+ "srcs",
+ "tools",
+ "toolchains",
+ "outs",
+ "cmd",
+ "output_to_bindir",
+ "local",
+ "message",
+ "executable",
+ "stamp",
+ "heuristic_label_expansion",
+ "kind");
+ }
+
+ @Test
+ public void testExistingRuleDictIsMutable() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('inc.bzl', 'f')", //
+ "f()");
+ scratch.file(
+ "test/inc.bzl", //
+ "def f():",
+ " native.config_setting(name='x', define_values={'key': 'value'})",
+ " r = native.existing_rule('x')",
+ " r['define_values']['key'] = 123"); // mutate the dict
+
+ // Logically this belongs among the loading-phase tests of existing_rules. Where are they?
+ assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error
+ }
+
+ @Test
+ public void testGetRuleAttributeListValue() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.outs");
+ assertThat(((Sequence) result)).hasSize(1);
+ }
+
+ @Test
+ public void testGetRuleAttributeListValueNoGet() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.outs");
+ assertThat(((Sequence) result)).hasSize(1);
+ }
+
+ @Test
+ public void testGetRuleAttributeStringTypeValue() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.cmd");
+ assertThat((String) result).isEqualTo("dummy_cmd");
+ }
+
+ @Test
+ public void testGetRuleAttributeStringTypeValueNoGet() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.attr.cmd");
+ assertThat((String) result).isEqualTo("dummy_cmd");
+ }
+
+ @Test
+ public void testGetRuleAttributeBadAttributeName() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains("No attribute 'bad'", "ruleContext.attr.bad");
+ }
+
+ @Test
+ public void testGetLabel() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.label");
+ assertThat(((Label) result).toString()).isEqualTo("//foo:foo");
+ }
+
+ @Test
+ public void testRuleError() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains("message", "fail('message')");
+ }
+
+ @Test
+ public void testAttributeError() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains("attribute srcs: message", "fail(attr='srcs', msg='message')");
+ }
+
+ @Test
+ public void testGetExecutablePrerequisite() throws Exception {
+ setRuleContext(createRuleContext("//foo:androidlib"));
+ Object result = ev.eval("ruleContext.executable._idlclass");
+ assertThat(((Artifact) result).getFilename()).matches("^IdlClass(\\.exe){0,1}$");
+ }
+
+ @Test
+ public void testCreateSpawnActionArgumentsWithExecutableFilesToRunProvider() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:androidlib");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['--a','--b'],",
+ " executable = ruleContext.executable._idlclass)");
+ StarlarkAction action =
+ (StarlarkAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getCommandFilename()).matches("^.*/IdlClass(\\.exe){0,1}$");
+ }
+
+ @Test
+ public void testCreateStarlarkActionArgumentsWithUnusedInputsList() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " executable = 'executable',",
+ " unused_inputs_list = ruleContext.files.srcs[0])");
+ StarlarkAction action =
+ (StarlarkAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getUnusedInputsList()).isPresent();
+ assertThat(action.getUnusedInputsList().get().getFilename()).isEqualTo("a.txt");
+ assertThat(action.discoversInputs()).isTrue();
+ assertThat(action.isShareable()).isFalse();
+ }
+
+ @Test
+ public void testCreateStarlarkActionArgumentsWithoutUnusedInputsList() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " executable = 'executable',",
+ " unused_inputs_list = None)");
+ StarlarkAction action =
+ (StarlarkAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getUnusedInputsList()).isEmpty();
+ assertThat(action.discoversInputs()).isFalse();
+ }
+
+ @Test
+ public void testOutputs() throws Exception {
+ setRuleContext(createRuleContext("//foo:bar"));
+ Iterable<?> result = (Iterable) ev.eval("ruleContext.outputs.outs");
+ assertThat(((Artifact) Iterables.getOnlyElement(result)).getFilename()).isEqualTo("d.txt");
+ }
+
+ @Test
+ public void testStarlarkRuleContextGetDefaultShellEnv() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.configuration.default_shell_env");
+ assertThat(result).isInstanceOf(Dict.class);
+ }
+
+ @Test
+ public void testCheckPlaceholders() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.check_placeholders('%{name}', ['name'])");
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void testCheckPlaceholdersBadPlaceholder() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.check_placeholders('%{name}', ['abc'])");
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void testExpandMakeVariables() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.expand_make_variables('cmd', '$(ABC)', {'ABC': 'DEF'})");
+ assertThat(result).isEqualTo("DEF");
+ }
+
+ @Test
+ public void testExpandMakeVariablesShell() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.expand_make_variables('cmd', '$$ABC', {})");
+ assertThat(result).isEqualTo("$ABC");
+ }
+
+ private void setUpMakeVarToolchain() throws Exception {
+ scratch.file(
+ "vars/vars.bzl",
+ "def _make_var_supplier_impl(ctx):",
+ " val = ctx.attr.value",
+ " return [platform_common.TemplateVariableInfo({'MAKE_VAR_VALUE': val})]",
+ "make_var_supplier = rule(",
+ " implementation = _make_var_supplier_impl,",
+ " attrs = {",
+ " 'value': attr.string(mandatory = True),",
+ " })",
+ "def _make_var_user_impl(ctx):",
+ " return []",
+ "make_var_user = rule(",
+ " implementation = _make_var_user_impl,",
+ ")");
+ scratch.file(
+ "vars/BUILD",
+ "load(':vars.bzl', 'make_var_supplier', 'make_var_user')",
+ "make_var_supplier(name = 'supplier', value = 'foo')",
+ "cc_toolchain_alias(name = 'current_cc_toolchain')",
+ "make_var_user(",
+ " name = 'vars',",
+ " toolchains = [':supplier', ':current_cc_toolchain'],",
+ ")");
+ }
+
+ @Test
+ public void testExpandMakeVariables_cc() throws Exception {
+ setUpMakeVarToolchain();
+ setRuleContext(createRuleContext("//vars:vars"));
+ String result = (String) ev.eval("ruleContext.expand_make_variables('cmd', '$(CC)', {})");
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ public void testExpandMakeVariables_toolchain() throws Exception {
+ setUpMakeVarToolchain();
+ setRuleContext(createRuleContext("//vars:vars"));
+ Object result = ev.eval("ruleContext.expand_make_variables('cmd', '$(MAKE_VAR_VALUE)', {})");
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void testVar_toolchain() throws Exception {
+ setUpMakeVarToolchain();
+ setRuleContext(createRuleContext("//vars:vars"));
+ Object result = ev.eval("ruleContext.var['MAKE_VAR_VALUE']");
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void testConfiguration() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.configuration");
+ assertThat(ruleContext.getRuleContext().getConfiguration()).isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testFeatures() throws Exception {
+ setRuleContext(createRuleContext("//foo:cc_with_features"));
+ Object result = ev.eval("ruleContext.features");
+ assertThat((Sequence) result).containsExactly("f1", "f2");
+ }
+
+ @Test
+ public void testDisabledFeatures() throws Exception {
+ setRuleContext(createRuleContext("//foo:cc_with_features"));
+ Object result = ev.eval("ruleContext.disabled_features");
+ assertThat((Sequence) result).containsExactly("f3");
+ }
+
+ @Test
+ public void testHostConfiguration() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.host_configuration");
+ assertThat(ruleContext.getRuleContext().getHostConfiguration()).isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testWorkspaceName() throws Exception {
+ assertThat(ruleClassProvider.getRunfilesPrefix()).isNotNull();
+ assertThat(ruleClassProvider.getRunfilesPrefix()).isNotEmpty();
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.workspace_name");
+ assertThat(ruleClassProvider.getRunfilesPrefix()).isEqualTo(result);
+ }
+
+ @Test
+ public void testDeriveArtifactLegacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=false");
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.new_file(ruleContext.genfiles_dir," + " 'a/b.txt')");
+ PathFragment fragment = ((Artifact) result).getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/a/b.txt");
+ }
+
+ @Test
+ public void testDeriveArtifact() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.actions.declare_file('a/b.txt')");
+ PathFragment fragment = ((Artifact) result).getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/a/b.txt");
+ }
+
+ @Test
+ public void testDeriveTreeArtifact() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.actions.declare_directory('a/b')");
+ Artifact artifact = (Artifact) result;
+ PathFragment fragment = artifact.getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/a/b");
+ assertThat(artifact.isTreeArtifact()).isTrue();
+ }
+
+ @Test
+ public void testDeriveTreeArtifactType() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ String result = (String) ev.eval("type(ruleContext.actions.declare_directory('a/b'))");
+ assertThat(result).isEqualTo("File");
+ }
+
+ @Test
+ public void testDeriveTreeArtifactNextToSibling() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Artifact artifact =
+ (Artifact)
+ ev.eval(
+ "ruleContext.actions.declare_directory('c',"
+ + " sibling=ruleContext.actions.declare_directory('a/b'))");
+ PathFragment fragment = artifact.getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/a/c");
+ assertThat(artifact.isTreeArtifact()).isTrue();
+ }
+
+ @Test
+ public void testParamFileLegacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=false");
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result =
+ ev.eval(
+ "ruleContext.new_file(ruleContext.bin_dir," + "ruleContext.files.tools[0], '.params')");
+ PathFragment fragment = ((Artifact) result).getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/t.exe.params");
+ }
+
+ @Test
+ public void testParamFileSuffixLegacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=false");
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result =
+ ev.eval(
+ "ruleContext.new_file(ruleContext.files.tools[0], "
+ + "ruleContext.files.tools[0].basename + '.params')");
+ PathFragment fragment = ((Artifact) result).getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/t.exe.params");
+ }
+
+ @Test
+ public void testParamFileSuffix() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result =
+ ev.eval(
+ "ruleContext.actions.declare_file(ruleContext.files.tools[0].basename + '.params', "
+ + "sibling = ruleContext.files.tools[0])");
+ PathFragment fragment = ((Artifact) result).getRootRelativePath();
+ assertThat(fragment.getPathString()).isEqualTo("foo/t.exe.params");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictConvertsToTargetToStringMap() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "filegroup(name='dep')",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={':dep': 'value'})");
+
+ invalidatePackages();
+ setRuleContext(createRuleContext("//:r"));
+ Label keyLabel = (Label) ev.eval("ruleContext.attr.label_dict.keys()[0].label");
+ assertThat(keyLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ String valueString = (String) ev.eval("ruleContext.attr.label_dict.values()[0]");
+ assertThat(valueString).isEqualTo("value");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictTranslatesAliases() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "filegroup(name='dep')",
+ "alias(name='alias', actual='dep')",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={':alias': 'value'})");
+
+ invalidatePackages();
+ setRuleContext(createRuleContext("//:r"));
+ Label keyLabel = (Label) ev.eval("ruleContext.attr.label_dict.keys()[0].label");
+ assertThat(keyLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ String valueString = (String) ev.eval("ruleContext.attr.label_dict.values()[0]");
+ assertThat(valueString).isEqualTo("value");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAcceptsDefaultValues() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(default={Label('//:default'): 'defs'}),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "filegroup(name='default')",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r')");
+
+ invalidatePackages();
+ setRuleContext(createRuleContext("//:r"));
+ Label keyLabel = (Label) ev.eval("ruleContext.attr.label_dict.keys()[0].label");
+ assertThat(keyLabel).isEqualTo(Label.parseAbsolute("//:default", ImmutableMap.of()));
+ String valueString = (String) ev.eval("ruleContext.attr.label_dict.values()[0]");
+ assertThat(valueString).isEqualTo("defs");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsFilesWhenAllowFilesIsTrue() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_files=True),",
+ " }",
+ ")");
+
+ scratch.file("myfile.cc");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={'myfile.cc': 'value'})");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsFilesOfAppropriateTypes() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_files=['.cc']),",
+ " }",
+ ")");
+
+ scratch.file("myfile.cc");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={'myfile.cc': 'value'})");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelKeyedStringDictForbidsFilesOfIncorrectTypes() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_files=['.cc']),",
+ " }",
+ ")");
+
+ scratch.file("myfile.cpp");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={'myfile.cpp': 'value'})");
+
+ invalidatePackages();
+ getConfiguredTarget("//:r");
+ assertContainsEvent("file '//:myfile.cpp' is misplaced here (expected .cc)");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictForbidsFilesWhenAllowFilesIsFalse() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_files=False),",
+ " }",
+ ")");
+
+ scratch.file("myfile.cpp");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={'myfile.cpp': 'value'})");
+
+ invalidatePackages();
+ getConfiguredTarget("//:r");
+ assertContainsEvent(
+ "in label_dict attribute of my_rule rule //:r: "
+ + "source file '//:myfile.cpp' is misplaced here (expected no files)");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsRulesWithRequiredProviders_legacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(providers=[['my_provider']]),",
+ " }",
+ ")",
+ "def _dep_impl(ctx):",
+ " return struct(my_provider=5)",
+ "my_dep_rule = rule(",
+ " implementation = _dep_impl,",
+ " attrs = {}",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule', 'my_dep_rule')",
+ "my_dep_rule(name='dep')",
+ "my_rule(name='r',",
+ " label_dict={':dep': 'value'})");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsRulesWithRequiredProviders() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(providers=[MyInfo]),",
+ " }",
+ ")",
+ "def _dep_impl(ctx):",
+ " return MyInfo(my_provider=5)",
+ "my_dep_rule = rule(",
+ " implementation = _dep_impl,",
+ " attrs = {}",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule', 'my_dep_rule')",
+ "my_dep_rule(name='dep')",
+ "my_rule(name='r',",
+ " label_dict={':dep': 'value'})");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelKeyedStringDictForbidsRulesMissingRequiredProviders() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(providers=[['my_provider']]),",
+ " }",
+ ")",
+ "def _dep_impl(ctx):",
+ " return",
+ "my_dep_rule = rule(",
+ " implementation = _dep_impl,",
+ " attrs = {}",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule', 'my_dep_rule')",
+ "my_dep_rule(name='dep')",
+ "my_rule(name='r',",
+ " label_dict={':dep': 'value'})");
+
+ invalidatePackages();
+ getConfiguredTarget("//:r");
+ assertContainsEvent(
+ "in label_dict attribute of my_rule rule //:r: "
+ + "'//:dep' does not have mandatory providers: 'my_provider'");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictForbidsEmptyDictWhenAllowEmptyIsFalse() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_empty=False),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={})");
+
+ invalidatePackages();
+ getConfiguredTarget("//:r");
+ assertContainsEvent(
+ "in label_dict attribute of my_rule rule //:r: " + "attribute must be non empty");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsEmptyDictWhenAllowEmptyIsTrue() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(allow_empty=True),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD",
+ "load('//:my_rule.bzl', 'my_rule')",
+ "my_rule(name='r',",
+ " label_dict={})");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelKeyedStringDictForbidsMissingAttributeWhenMandatoryIsTrue()
+ throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(mandatory=True),",
+ " }",
+ ")");
+
+ scratch.file("BUILD", "load('//:my_rule.bzl', 'my_rule')", "my_rule(name='r')");
+
+ invalidatePackages();
+ getConfiguredTarget("//:r");
+ assertContainsEvent("missing value for mandatory attribute 'label_dict' in 'my_rule' rule");
+ }
+
+ @Test
+ public void testLabelKeyedStringDictAllowsMissingAttributeWhenMandatoryIsFalse()
+ throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'label_dict': attr.label_keyed_string_dict(mandatory=False),",
+ " }",
+ ")");
+
+ scratch.file("BUILD", "load('//:my_rule.bzl', 'my_rule')", "my_rule(name='r')");
+
+ invalidatePackages();
+ createRuleContext("//:r");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testLabelAttributeDefault() throws Exception {
+ scratch.file(
+ "my_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'explicit_dep': attr.label(default = Label('//:dep')),",
+ " '_implicit_dep': attr.label(default = Label('//:dep')),",
+ " 'explicit_dep_list': attr.label_list(default = [Label('//:dep')]),",
+ " '_implicit_dep_list': attr.label_list(default = [Label('//:dep')]),",
+ " }",
+ ")");
+
+ scratch.file(
+ "BUILD", "filegroup(name='dep')", "load('//:my_rule.bzl', 'my_rule')", "my_rule(name='r')");
+
+ invalidatePackages();
+ setRuleContext(createRuleContext("//:r"));
+ Label explicitDepLabel = (Label) ev.eval("ruleContext.attr.explicit_dep.label");
+ assertThat(explicitDepLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ Label implicitDepLabel = (Label) ev.eval("ruleContext.attr._implicit_dep.label");
+ assertThat(implicitDepLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ Label explicitDepListLabel = (Label) ev.eval("ruleContext.attr.explicit_dep_list[0].label");
+ assertThat(explicitDepListLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ Label implicitDepListLabel = (Label) ev.eval("ruleContext.attr._implicit_dep_list[0].label");
+ assertThat(implicitDepListLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testRelativeLabelInExternalRepository() throws Exception {
+ scratch.file(
+ "external_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "external_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'internal_dep': attr.label(default = Label('//:dep'))",
+ " }",
+ ")");
+
+ scratch.file("BUILD", "filegroup(name='dep')");
+
+ scratch.file("/r/WORKSPACE");
+ scratch.file(
+ "/r/a/BUILD", "load('@//:external_rule.bzl', 'external_rule')", "external_rule(name='r')");
+
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='/r')")
+ .build());
+
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ setRuleContext(createRuleContext("@r//a:r"));
+ Label depLabel = (Label) ev.eval("ruleContext.attr.internal_dep.label");
+ assertThat(depLabel).isEqualTo(Label.parseAbsolute("//:dep", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testCallerRelativeLabelInExternalRepository() throws Exception {
+ scratch.file("BUILD");
+ scratch.file(
+ "external_rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "external_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'internal_dep': attr.label(",
+ " default = Label('//:dep', relative_to_caller_repository = True)",
+ " )",
+ " }",
+ ")");
+
+ scratch.file("/r/WORKSPACE");
+ scratch.file("/r/BUILD", "filegroup(name='dep')");
+
+ scratch.file(
+ "/r/a/BUILD", "load('@//:external_rule.bzl', 'external_rule')", "external_rule(name='r')");
+
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r', path='/r')")
+ .build());
+
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ setRuleContext(createRuleContext("@r//a:r"));
+ Label depLabel = (Label) ev.eval("ruleContext.attr.internal_dep.label");
+ assertThat(depLabel).isEqualTo(Label.parseAbsolute("@r//:dep", ImmutableMap.of()));
+ }
+
+ @Test
+ public void testExternalWorkspaceLoad() throws Exception {
+ scratch.file(
+ "/r1/BUILD",
+ "filegroup(name = 'test',",
+ " srcs = ['test.txt'],",
+ " visibility = ['//visibility:public'],",
+ ")");
+ scratch.file("/r1/WORKSPACE");
+ scratch.file("/r2/BUILD", "exports_files(['test.bzl'])");
+ scratch.file(
+ "/r2/test.bzl",
+ "def macro(name, path):",
+ " native.local_repository(name = name, path = path)");
+ scratch.file("/r2/WORKSPACE");
+ scratch.file(
+ "/r2/other_test.bzl", "def other_macro(name, path):", " print(name + ': ' + path)");
+ scratch.file("BUILD");
+
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name='r2', path='/r2')")
+ .add("load('@r2//:test.bzl', 'macro')")
+ .add("macro('r1', '/r1')")
+ .add("NEXT_NAME = 'r3'")
+ // We can still refer to r2 in other chunks:
+ .add("load('@r2//:other_test.bzl', 'other_macro')")
+ .add("macro(NEXT_NAME, '/r2')") // and we can still use macro outside of its chunk.
+ .build());
+
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ assertThat(getConfiguredTarget("@r1//:test")).isNotNull();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testLoadBlockRepositoryRedefinition() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("/bar/WORKSPACE");
+ scratch.file("/bar/bar.txt");
+ scratch.file("/bar/BUILD", "filegroup(name = 'baz', srcs = ['bar.txt'])");
+ scratch.file("/baz/WORKSPACE");
+ scratch.file("/baz/baz.txt");
+ scratch.file("/baz/BUILD", "filegroup(name = 'baz', srcs = ['baz.txt'])");
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name = 'foo', path = '/bar')")
+ .add("local_repository(name = 'foo', path = '/baz')")
+ .build());
+
+ invalidatePackages(
+ /*alsoConfigs=*/ false); // Repository shuffling messes with toolchain labels.
+ assertThat(
+ (List)
+ getConfiguredTargetAndData("@foo//:baz")
+ .getTarget()
+ .getAssociatedRule()
+ .getAttributeContainer()
+ .getAttr("srcs"))
+ .contains(Label.parseAbsolute("@foo//:baz.txt", ImmutableMap.of()));
+
+ scratch.overwriteFile("BUILD");
+ scratch.overwriteFile("bar.bzl", "dummy = 1");
+
+ scratch.overwriteFile(
+ "WORKSPACE",
+ new ImmutableList.Builder<String>()
+ .addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
+ .add("local_repository(name = 'foo', path = '/bar')")
+ .add("load('//:bar.bzl', 'dummy')")
+ .add("local_repository(name = 'foo', path = '/baz')")
+ .build());
+
+ invalidatePackages(/*alsoConfigs=*/ false); // Repository shuffling messes with toolchains.
+ assertThrows(Exception.class, () -> createRuleContext("@foo//:baz"));
+ assertContainsEvent(
+ "Cannot redefine repository after any load statement in the WORKSPACE file "
+ + "(for repository 'foo')");
+ }
+
+ @Test
+ public void testAccessingRunfiles() throws Exception {
+ scratch.file("test/a.py");
+ scratch.file("test/b.py");
+ scratch.file("test/__init__.py");
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " return",
+ "starlark_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'starlark_rule')",
+ "py_binary(name = 'lib', srcs = ['lib.py', 'lib2.py'])",
+ "starlark_rule(name = 'foo', dep = ':lib')",
+ "py_binary(name = 'lib_with_init', srcs = ['lib_with_init.py', 'lib2.py', '__init__.py'])",
+ "starlark_rule(name = 'foo_with_init', dep = ':lib_with_init')");
+
+ setRuleContext(createRuleContext("//test:foo"));
+ Object filenames =
+ ev.eval("[f.short_path for f in ruleContext.attr.dep.default_runfiles.files.to_list()]");
+ assertThat(filenames).isInstanceOf(Sequence.class);
+ Sequence<?> filenamesList = (Sequence) filenames;
+ assertThat(filenamesList).containsAtLeast("test/lib.py", "test/lib2.py");
+ Object emptyFilenames =
+ ev.eval("ruleContext.attr.dep.default_runfiles.empty_filenames.to_list()");
+ assertThat(emptyFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> emptyFilenamesList = (Sequence) emptyFilenames;
+ assertThat(emptyFilenamesList).containsExactly("test/__init__.py");
+
+ setRuleContext(createRuleContext("//test:foo_with_init"));
+ Object noEmptyFilenames =
+ ev.eval("ruleContext.attr.dep.default_runfiles.empty_filenames.to_list()");
+ assertThat(noEmptyFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> noEmptyFilenamesList = (Sequence) noEmptyFilenames;
+ assertThat(noEmptyFilenamesList).isEmpty();
+ }
+
+ @Test
+ public void testAccessingRunfilesSymlinks_legacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file("test/a.py");
+ scratch.file("test/b.py");
+ scratch.file(
+ "test/rule.bzl",
+ "def symlink_impl(ctx):",
+ " symlinks = {",
+ " 'symlink_' + f.short_path: f",
+ " for f in ctx.files.symlink",
+ " }",
+ " return struct(",
+ " runfiles = ctx.runfiles(",
+ " symlinks=symlinks,",
+ " )",
+ " )",
+ "symlink_rule = rule(",
+ " implementation = symlink_impl,",
+ " attrs = {",
+ " 'symlink': attr.label(allow_files=True),",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'symlink_rule')",
+ "symlink_rule(name = 'lib_with_symlink', symlink = ':a.py')",
+ "sh_binary(",
+ " name = 'test_with_symlink',",
+ " srcs = ['test/b.py'],",
+ " data = [':lib_with_symlink'],",
+ ")");
+ setRuleContext(createRuleContext("//test:test_with_symlink"));
+ Object symlinkPaths =
+ ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
+ assertThat(symlinkPaths).isInstanceOf(Sequence.class);
+ Sequence<?> symlinkPathsList = (Sequence) symlinkPaths;
+ assertThat(symlinkPathsList).containsExactly("symlink_test/a.py").inOrder();
+ Object symlinkFilenames =
+ ev.eval(
+ "[s.target_file.short_path for s in"
+ + " ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
+ assertThat(symlinkFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> symlinkFilenamesList = (Sequence) symlinkFilenames;
+ assertThat(symlinkFilenamesList).containsExactly("test/a.py").inOrder();
+ }
+
+ @Test
+ public void testAccessingRunfilesSymlinks() throws Exception {
+ scratch.file("test/a.py");
+ scratch.file("test/b.py");
+ scratch.file(
+ "test/rule.bzl",
+ "def symlink_impl(ctx):",
+ " symlinks = {",
+ " 'symlink_' + f.short_path: f",
+ " for f in ctx.files.symlink",
+ " }",
+ " return DefaultInfo(",
+ " runfiles = ctx.runfiles(",
+ " symlinks=symlinks,",
+ " )",
+ " )",
+ "symlink_rule = rule(",
+ " implementation = symlink_impl,",
+ " attrs = {",
+ " 'symlink': attr.label(allow_files=True),",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'symlink_rule')",
+ "symlink_rule(name = 'lib_with_symlink', symlink = ':a.py')",
+ "sh_binary(",
+ " name = 'test_with_symlink',",
+ " srcs = ['test/b.py'],",
+ " data = [':lib_with_symlink'],",
+ ")");
+ setRuleContext(createRuleContext("//test:test_with_symlink"));
+ Object symlinkPaths =
+ ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
+ assertThat(symlinkPaths).isInstanceOf(Sequence.class);
+ Sequence<?> symlinkPathsList = (Sequence) symlinkPaths;
+ assertThat(symlinkPathsList).containsExactly("symlink_test/a.py").inOrder();
+ Object symlinkFilenames =
+ ev.eval(
+ "[s.target_file.short_path for s in"
+ + " ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
+ assertThat(symlinkFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> symlinkFilenamesList = (Sequence) symlinkFilenames;
+ assertThat(symlinkFilenamesList).containsExactly("test/a.py").inOrder();
+ }
+
+ @Test
+ public void testAccessingRunfilesRootSymlinks_legacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file("test/a.py");
+ scratch.file("test/b.py");
+ scratch.file(
+ "test/rule.bzl",
+ "def root_symlink_impl(ctx):",
+ " root_symlinks = {",
+ " 'root_symlink_' + f.short_path: f",
+ " for f in ctx.files.root_symlink",
+ " }",
+ " return struct(",
+ " runfiles = ctx.runfiles(",
+ " root_symlinks=root_symlinks,",
+ " )",
+ " )",
+ "root_symlink_rule = rule(",
+ " implementation = root_symlink_impl,",
+ " attrs = {",
+ " 'root_symlink': attr.label(allow_files=True)",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'root_symlink_rule')",
+ "root_symlink_rule(name = 'lib_with_root_symlink', root_symlink = ':a.py')",
+ "sh_binary(",
+ " name = 'test_with_root_symlink',",
+ " srcs = ['test/b.py'],",
+ " data = [':lib_with_root_symlink'],",
+ ")");
+ setRuleContext(createRuleContext("//test:test_with_root_symlink"));
+ Object rootSymlinkPaths =
+ ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
+ assertThat(rootSymlinkPaths).isInstanceOf(Sequence.class);
+ Sequence<?> rootSymlinkPathsList = (Sequence) rootSymlinkPaths;
+ assertThat(rootSymlinkPathsList).containsExactly("root_symlink_test/a.py").inOrder();
+ Object rootSymlinkFilenames =
+ ev.eval(
+ "[s.target_file.short_path for s in"
+ + " ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
+ assertThat(rootSymlinkFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> rootSymlinkFilenamesList = (Sequence) rootSymlinkFilenames;
+ assertThat(rootSymlinkFilenamesList).containsExactly("test/a.py").inOrder();
+ }
+
+ @Test
+ public void testAccessingRunfilesRootSymlinks() throws Exception {
+ scratch.file("test/a.py");
+ scratch.file("test/b.py");
+ scratch.file(
+ "test/rule.bzl",
+ "def root_symlink_impl(ctx):",
+ " root_symlinks = {",
+ " 'root_symlink_' + f.short_path: f",
+ " for f in ctx.files.root_symlink",
+ " }",
+ " return DefaultInfo(",
+ " runfiles = ctx.runfiles(",
+ " root_symlinks=root_symlinks,",
+ " )",
+ " )",
+ "root_symlink_rule = rule(",
+ " implementation = root_symlink_impl,",
+ " attrs = {",
+ " 'root_symlink': attr.label(allow_files=True)",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'root_symlink_rule')",
+ "root_symlink_rule(name = 'lib_with_root_symlink', root_symlink = ':a.py')",
+ "sh_binary(",
+ " name = 'test_with_root_symlink',",
+ " srcs = ['test/b.py'],",
+ " data = [':lib_with_root_symlink'],",
+ ")");
+ setRuleContext(createRuleContext("//test:test_with_root_symlink"));
+ Object rootSymlinkPaths =
+ ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
+ assertThat(rootSymlinkPaths).isInstanceOf(Sequence.class);
+ Sequence<?> rootSymlinkPathsList = (Sequence) rootSymlinkPaths;
+ assertThat(rootSymlinkPathsList).containsExactly("root_symlink_test/a.py").inOrder();
+ Object rootSymlinkFilenames =
+ ev.eval(
+ "[s.target_file.short_path for s in"
+ + " ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
+ assertThat(rootSymlinkFilenames).isInstanceOf(Sequence.class);
+ Sequence<?> rootSymlinkFilenamesList = (Sequence) rootSymlinkFilenames;
+ assertThat(rootSymlinkFilenamesList).containsExactly("test/a.py").inOrder();
+ }
+
+ @Test
+ public void testExternalShortPath() throws Exception {
+ scratch.file("/bar/WORKSPACE");
+ scratch.file("/bar/bar.txt");
+ scratch.file("/bar/BUILD", "exports_files(['bar.txt'])");
+ FileSystemUtils.appendIsoLatin1(
+ scratch.resolve("WORKSPACE"), "local_repository(name = 'foo', path = '/bar')");
+ scratch.file(
+ "test/BUILD",
+ "genrule(",
+ " name = 'lib',",
+ " srcs = ['@foo//:bar.txt'],",
+ " cmd = 'echo $(SRCS) $@',",
+ " outs = ['lib.out'],",
+ " executable = 1,",
+ ")");
+ invalidatePackages();
+ StarlarkRuleContext ruleContext = createRuleContext("//test:lib");
+ setRuleContext(ruleContext);
+ String filename = ev.eval("ruleContext.files.srcs[0].short_path").toString();
+ assertThat(filename).isEqualTo("../foo/bar.txt");
+ }
+
+ // Borrowed from Scratch.java.
+ private static String linesAsString(String... lines) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ builder.append(line);
+ builder.append('\n');
+ }
+ return builder.toString();
+ }
+
+ // The common structure of the following actions tests is a rule under test depended upon by
+ // a testing rule, where the rule under test has one output and one caller-supplied action.
+
+ private static String getSimpleUnderTestDefinition(
+ boolean withStarlarkTestable, String[] actionLines) {
+ return linesAsString(
+ // TODO(b/153667498): Just passing fail to map_each parameter of Args.add_all does not work.
+ "def fail_with_message(s):",
+ " fail(s)",
+ "",
+ "def _undertest_impl(ctx):",
+ " out = ctx.outputs.out",
+ " " + Joiner.on("\n ").join(actionLines),
+ "undertest_rule = rule(",
+ " implementation = _undertest_impl,",
+ " outputs = {'out': '%{name}.txt'},",
+ withStarlarkTestable ? " _skylark_testable = True," : "",
+ ")");
+ }
+
+ private static String getSimpleUnderTestDefinition(String... actionLines) {
+ return getSimpleUnderTestDefinition(true, actionLines);
+ }
+
+ private static String getSimpleNontestableUnderTestDefinition(String... actionLines) {
+ return getSimpleUnderTestDefinition(false, actionLines);
+ }
+
+ private final String testingRuleDefinition =
+ linesAsString(
+ "def _testing_impl(ctx):",
+ " pass",
+ "testing_rule = rule(",
+ " implementation = _testing_impl,",
+ " attrs = {'dep': attr.label()},",
+ ")");
+
+ private final String simpleBuildDefinition =
+ linesAsString(
+ "load(':rules.bzl', 'undertest_rule', 'testing_rule')",
+ "undertest_rule(",
+ " name = 'undertest',",
+ ")",
+ "testing_rule(",
+ " name = 'testing',",
+ " dep = ':undertest',",
+ ")");
+
+ @Test
+ public void testDependencyActionsProvider() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition(
+ "ctx.actions.run_shell(outputs=[out], command='echo foo123 > ' + out.path)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+
+ Object provider = ev.eval("ruleContext.attr.dep[Actions]");
+ assertThat(provider).isInstanceOf(StructImpl.class);
+ assertThat(((StructImpl) provider).getProvider()).isEqualTo(ActionsProvider.INSTANCE);
+ ev.update("actions", provider);
+
+ Map<?, ?> mapping = (Dict<?, ?>) ev.eval("actions.by_file");
+ assertThat(mapping).hasSize(1);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ Object actionUnchecked = ev.eval("actions.by_file[file]");
+ assertThat(actionUnchecked).isInstanceOf(ActionAnalysisMetadata.class);
+ }
+
+ @Test
+ public void testNoAccessToDependencyActionsWithoutStarlarkTest() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleNontestableUnderTestDefinition(
+ "ctx.actions.run_shell(outputs=[out], command='echo foo123 > ' + out.path)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+
+ Exception e = assertThrows(Exception.class, () -> ev.eval("ruleContext.attr.dep[Actions]"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "<target //test:undertest> (rule 'undertest_rule') doesn't contain "
+ + "declared provider 'Actions'");
+ }
+
+ @Test
+ public void testAbstractActionInterface() throws Exception {
+ setStarlarkSemanticsOptions(
+ "--incompatible_disallow_struct_provider_syntax=false",
+ "--incompatible_no_rule_outputs_param=false");
+ scratch.file(
+ "test/rules.bzl",
+ "def _undertest_impl(ctx):",
+ " out1 = ctx.outputs.out1",
+ " out2 = ctx.outputs.out2",
+ " ctx.actions.write(output=out1, content='foo123')",
+ " ctx.actions.run_shell(outputs=[out2], inputs=[out1],",
+ " command='cp ' + out1.path + ' ' + out2.path)",
+ " return struct(out1=out1, out2=out2)",
+ "undertest_rule = rule(",
+ " implementation = _undertest_impl,",
+ " outputs = {'out1': '%{name}1.txt',",
+ " 'out2': '%{name}2.txt'},",
+ " _skylark_testable = True,",
+ ")",
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file1", ev.eval("ruleContext.attr.dep.out1"));
+ ev.update("file2", ev.eval("ruleContext.attr.dep.out2"));
+ ev.update("action1", ev.eval("ruleContext.attr.dep[Actions].by_file[file1]"));
+ ev.update("action2", ev.eval("ruleContext.attr.dep[Actions].by_file[file2]"));
+
+ assertThat(ev.eval("action1.inputs")).isInstanceOf(Depset.class);
+ assertThat(ev.eval("action1.outputs")).isInstanceOf(Depset.class);
+
+ assertThat(ev.eval("action1.argv")).isEqualTo(Starlark.NONE);
+ assertThat(ev.eval("action2.content")).isEqualTo(Starlark.NONE);
+ assertThat(ev.eval("action1.substitutions")).isEqualTo(Starlark.NONE);
+
+ assertThat(ev.eval("action1.inputs.to_list()")).isEqualTo(ev.eval("[]"));
+ assertThat(ev.eval("action1.outputs.to_list()")).isEqualTo(ev.eval("[file1]"));
+ assertThat(ev.eval("action2.inputs.to_list()")).isEqualTo(ev.eval("[file1]"));
+ assertThat(ev.eval("action2.outputs.to_list()")).isEqualTo(ev.eval("[file2]"));
+ }
+
+ // For created_actions() tests, the "undertest" rule represents both the code under test and the
+ // Starlark user test code itself.
+
+ @Test
+ public void testCreatedActions() throws Exception {
+ setStarlarkSemanticsOptions(
+ "--incompatible_disallow_struct_provider_syntax=false",
+ "--incompatible_no_rule_outputs_param=false");
+ // createRuleContext() gives us the context for a rule upon entry into its analysis function.
+ // But we need to inspect the result of calling created_actions() after the rule context has
+ // been modified by creating actions. So we'll call created_actions() from within the analysis
+ // function and pass it along as a provider.
+ scratch.file(
+ "test/rules.bzl",
+ "def _undertest_impl(ctx):",
+ " out1 = ctx.outputs.out1",
+ " out2 = ctx.outputs.out2",
+ " ctx.actions.run_shell(outputs=[out1], command='echo foo123 > ' + out1.path,",
+ " mnemonic='foo')",
+ " v = ctx.created_actions().by_file",
+ " ctx.actions.run_shell(outputs=[out2], command='echo bar123 > ' + out2.path)",
+ " return struct(v=v, out1=out1, out2=out2)",
+ "undertest_rule = rule(",
+ " implementation = _undertest_impl,",
+ " outputs = {'out1': '%{name}1.txt',",
+ " 'out2': '%{name}2.txt'},",
+ " _skylark_testable = True,",
+ ")",
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+
+ Object mapUnchecked = ev.eval("ruleContext.attr.dep.v");
+ assertThat(mapUnchecked).isInstanceOf(Dict.class);
+ Map<?, ?> map = (Dict) mapUnchecked;
+ // Should only have the first action because created_actions() was called
+ // before the second action was created.
+ Object file = ev.eval("ruleContext.attr.dep.out1");
+ assertThat(map).hasSize(1);
+ assertThat(map).containsKey(file);
+ Object actionUnchecked = map.get(file);
+ assertThat(actionUnchecked).isInstanceOf(ActionAnalysisMetadata.class);
+ assertThat(((ActionAnalysisMetadata) actionUnchecked).getMnemonic()).isEqualTo("foo");
+ }
+
+ @Test
+ public void testNoAccessToCreatedActionsWithoutStarlarkTest() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleNontestableUnderTestDefinition(
+ "ctx.actions.run_shell(outputs=[out], command='echo foo123 > ' + out.path)"));
+ scratch.file(
+ "test/BUILD",
+ "load(':rules.bzl', 'undertest_rule')",
+ "undertest_rule(",
+ " name = 'undertest',",
+ ")");
+ StarlarkRuleContext ruleContext = createRuleContext("//test:undertest");
+ setRuleContext(ruleContext);
+
+ Object result = ev.eval("ruleContext.created_actions()");
+ assertThat(result).isEqualTo(Starlark.NONE);
+ }
+
+ @Test
+ public void testSpawnActionInterface() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition(
+ "ctx.actions.run_shell(outputs=[out], command='echo foo123 > ' + out.path)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ Object argvUnchecked = ev.eval("action.argv");
+ assertThat(argvUnchecked).isInstanceOf(StarlarkList.class);
+ StarlarkList<?> argv = (StarlarkList) argvUnchecked;
+ assertThat(argv).hasSize(3);
+ assertThat(argv.isImmutable()).isTrue();
+ Object result = ev.eval("action.argv[2].startswith('echo foo123')");
+ assertThat((Boolean) result).isTrue();
+ }
+
+ @Test
+ public void testRunShellUsesHelperScriptForLongCommand() throws Exception {
+ setStarlarkSemanticsOptions(
+ "--incompatible_disallow_struct_provider_syntax=false",
+ "--incompatible_no_rule_outputs_param=false");
+ // createRuleContext() gives us the context for a rule upon entry into its analysis function.
+ // But we need to inspect the result of calling created_actions() after the rule context has
+ // been modified by creating actions. So we'll call created_actions() from within the analysis
+ // function and pass it along as a provider.
+ scratch.file(
+ "test/rules.bzl",
+ "def _undertest_impl(ctx):",
+ " out1 = ctx.outputs.out1",
+ " out2 = ctx.outputs.out2",
+ " out3 = ctx.outputs.out3",
+ " ctx.actions.run_shell(outputs=[out1],",
+ " command='( %s ; ) > $1' % (",
+ " ' ; '.join(['echo xxx%d' % i for i in range(0, 7000)])),",
+ " mnemonic='mnemonic1',",
+ " arguments=[out1.path])",
+ " ctx.actions.run_shell(outputs=[out2],",
+ " command='echo foo > ' + out2.path,",
+ " mnemonic='mnemonic2')",
+ " ctx.actions.run_shell(outputs=[out3],",
+ " command='( %s ; ) > $1' % (",
+ " ' ; '.join(['echo yyy%d' % i for i in range(0, 7000)])),",
+ " mnemonic='mnemonic3',",
+ " arguments=[out3.path])",
+ " v = ctx.created_actions().by_file",
+ " return struct(v=v, out1=out1, out2=out2, out3=out3)",
+ "",
+ "undertest_rule = rule(",
+ " implementation=_undertest_impl,",
+ " outputs={'out1': '%{name}1.txt',",
+ " 'out2': '%{name}2.txt',",
+ " 'out3': '%{name}3.txt'},",
+ " _skylark_testable = True,",
+ ")",
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+
+ Object mapUnchecked = ev.eval("ruleContext.attr.dep.v");
+ assertThat(mapUnchecked).isInstanceOf(Dict.class);
+ Map<?, ?> map = (Dict) mapUnchecked;
+ Object out1 = ev.eval("ruleContext.attr.dep.out1");
+ Object out2 = ev.eval("ruleContext.attr.dep.out2");
+ Object out3 = ev.eval("ruleContext.attr.dep.out3");
+ // 5 actions in total: 3 SpawnActions and 2 FileWriteActions for the two long commands.
+ assertThat(map).hasSize(5);
+ assertThat(map).containsKey(out1);
+ assertThat(map).containsKey(out2);
+ assertThat(map).containsKey(out3);
+ Object action1Unchecked = map.get(out1);
+ Object action2Unchecked = map.get(out2);
+ Object action3Unchecked = map.get(out3);
+ assertThat(action1Unchecked).isInstanceOf(ActionAnalysisMetadata.class);
+ assertThat(action2Unchecked).isInstanceOf(ActionAnalysisMetadata.class);
+ assertThat(action3Unchecked).isInstanceOf(ActionAnalysisMetadata.class);
+ ActionAnalysisMetadata spawnAction1 = (ActionAnalysisMetadata) action1Unchecked;
+ ActionAnalysisMetadata spawnAction2 = (ActionAnalysisMetadata) action2Unchecked;
+ ActionAnalysisMetadata spawnAction3 = (ActionAnalysisMetadata) action3Unchecked;
+ assertThat(spawnAction1.getMnemonic()).isEqualTo("mnemonic1");
+ assertThat(spawnAction2.getMnemonic()).isEqualTo("mnemonic2");
+ assertThat(spawnAction3.getMnemonic()).isEqualTo("mnemonic3");
+ Artifact helper1 =
+ Iterables.getOnlyElement(
+ Iterables.filter(
+ spawnAction1.getInputs().toList(),
+ a -> a.getFilename().equals("undertest.run_shell_0.sh")));
+ assertThat(
+ Iterables.filter(
+ spawnAction2.getInputs().toList(), a -> a.getFilename().contains("run_shell_")))
+ .isEmpty();
+ Artifact helper3 =
+ Iterables.getOnlyElement(
+ Iterables.filter(
+ spawnAction3.getInputs().toList(),
+ a -> a.getFilename().equals("undertest.run_shell_2.sh")));
+ assertThat(map).containsKey(helper1);
+ assertThat(map).containsKey(helper3);
+ Object action4Unchecked = map.get(helper1);
+ Object action5Unchecked = map.get(helper3);
+ assertThat(action4Unchecked).isInstanceOf(FileWriteAction.class);
+ assertThat(action5Unchecked).isInstanceOf(FileWriteAction.class);
+ FileWriteAction fileWriteAction1 = (FileWriteAction) action4Unchecked;
+ FileWriteAction fileWriteAction2 = (FileWriteAction) action5Unchecked;
+ assertThat(fileWriteAction1.getFileContents()).contains("echo xxx6999 ;");
+ assertThat(fileWriteAction2.getFileContents()).contains("echo yyy6999 ;");
+ }
+
+ @Test
+ public void testInvalidMnemonic() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " out = ctx.actions.declare_file('f')",
+ " ctx.actions.run_shell(",
+ " outputs=[out], command='false', mnemonic='@@@')",
+ "r = rule(implementation = _impl)");
+ scratch.file("test/BUILD", "load('//test:rule.bzl', 'r')", "r(name = 'target')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:target");
+ assertContainsEvent(
+ "mnemonic must only contain letters and/or digits, and have non-zero length, was: \"@@@\"");
+ }
+
+ @Test
+ public void testFileWriteActionInterface() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition("ctx.actions.write(output=out, content='foo123')"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ Object contentUnchecked = ev.eval("action.content");
+ assertThat(contentUnchecked).isInstanceOf(String.class);
+ assertThat(contentUnchecked).isEqualTo("foo123");
+ }
+
+ @Test
+ public void testFileWriteActionInterfaceWithArgs() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition(
+ "args = ctx.actions.args()",
+ "args.add('foo123')",
+ "ctx.actions.write(output=out, content=args)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ Object contentUnchecked = ev.eval("action.content");
+ assertThat(contentUnchecked).isInstanceOf(String.class);
+ // Args content ends the file with a newline
+ assertThat(contentUnchecked).isEqualTo("foo123\n");
+ }
+
+ @Test
+ public void testFileWriteActionInterfaceWithArgsContainingTreeArtifact() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition(
+ "directory = ctx.actions.declare_directory('dir')",
+ "ctx.actions.run_shell(",
+ " outputs = [directory],",
+ " command = 'mkdir {out}'",
+ ")",
+ "args = ctx.actions.args()",
+ "args.add_all([directory])",
+ "ctx.actions.write(output=out, content=args)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ // If the Args contain a directory File that needs to be expanded, the contents are not known
+ // at analysis time.
+ Object contentUnchecked = ev.eval("action.content");
+ assertThat(contentUnchecked).isEqualTo(Starlark.NONE);
+ }
+
+ @Test
+ public void testFileWriteActionInterfaceWithArgsExpansionError() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ getSimpleUnderTestDefinition(
+ "args = ctx.actions.args()",
+ "args.add_all(['args expansion error message'], map_each = fail_with_message)",
+ "ctx.actions.write(output=out, content=args)"),
+ testingRuleDefinition);
+ scratch.file("test/BUILD", simpleBuildDefinition);
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ // If there's a failure when expanding Args, that error message is propagated.
+ EvalException e =
+ assertThrows(
+ "Should be an error expanding action.content",
+ EvalException.class,
+ () -> ev.eval("action.content"));
+ assertThat(e)
+ .hasMessageThat()
+ .matches(
+ "Error expanding command line: \n\n/workspace/test/rules\\.bzl:\\d+:\\d+: args "
+ + "expansion error message");
+ }
+
+ @Test
+ public void testTemplateExpansionActionInterface() throws Exception {
+ scratch.file(
+ "test/rules.bzl",
+ "def _undertest_impl(ctx):",
+ " out = ctx.outputs.out",
+ " ctx.actions.expand_template(output=out,",
+ " template=ctx.file.template, substitutions={'a': 'b'})",
+ "undertest_rule = rule(",
+ " implementation = _undertest_impl,",
+ " outputs = {'out': '%{name}.txt'},",
+ " attrs = {'template': attr.label(allow_single_file=True)},",
+ " _skylark_testable = True,",
+ ")",
+ testingRuleDefinition);
+ scratch.file("test/template.txt", "aaaaa", "bcdef");
+ scratch.file(
+ "test/BUILD",
+ "load(':rules.bzl', 'undertest_rule', 'testing_rule')",
+ "undertest_rule(",
+ " name = 'undertest',",
+ " template = ':template.txt',",
+ ")",
+ "testing_rule(",
+ " name = 'testing',",
+ " dep = ':undertest',",
+ ")");
+ StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
+ setRuleContext(ruleContext);
+ ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
+ ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
+
+ assertThat(ev.eval("type(action)")).isEqualTo("Action");
+
+ Object contentUnchecked = ev.eval("action.content");
+ assertThat(contentUnchecked).isInstanceOf(String.class);
+ assertThat(contentUnchecked).isEqualTo("bbbbb\nbcdef\n");
+
+ Object substitutionsUnchecked = ev.eval("action.substitutions");
+ assertThat(substitutionsUnchecked).isInstanceOf(Dict.class);
+ assertThat(substitutionsUnchecked).isEqualTo(Dict.of((Mutability) null, "a", "b"));
+ }
+
+ private void setUpCoverageInstrumentedTest() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "cc_library(",
+ " name = 'foo',",
+ " srcs = ['foo.cc'],",
+ " deps = [':bar'],",
+ ")",
+ "cc_library(",
+ " name = 'bar',",
+ " srcs = ['bar.cc'],",
+ ")");
+ }
+
+ @Test
+ public void testCoverageInstrumentedCoverageDisabled() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--nocollect_code_coverage", "--instrumentation_filter=.");
+ StarlarkRuleContext ruleContext = createRuleContext("//test:foo");
+ setRuleContext(ruleContext);
+ Object result = ev.eval("ruleContext.coverage_instrumented()");
+ assertThat((Boolean) result).isFalse();
+ }
+
+ @Test
+ public void testCoverageInstrumentedFalseForSourceFileLabel() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--collect_code_coverage", "--instrumentation_filter=.");
+ setRuleContext(createRuleContext("//test:foo"));
+ Object result = ev.eval("ruleContext.coverage_instrumented(ruleContext.attr.srcs[0])");
+ assertThat((Boolean) result).isFalse();
+ }
+
+ @Test
+ public void testCoverageInstrumentedDoesNotMatchFilter() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--collect_code_coverage", "--instrumentation_filter=:foo");
+ setRuleContext(createRuleContext("//test:bar"));
+ Object result = ev.eval("ruleContext.coverage_instrumented()");
+ assertThat((Boolean) result).isFalse();
+ }
+
+ @Test
+ public void testCoverageInstrumentedMatchesFilter() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--collect_code_coverage", "--instrumentation_filter=:foo");
+ setRuleContext(createRuleContext("//test:foo"));
+ Object result = ev.eval("ruleContext.coverage_instrumented()");
+ assertThat((Boolean) result).isTrue();
+ }
+
+ @Test
+ public void testCoverageInstrumentedDoesNotMatchFilterNonDefaultLabel() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--collect_code_coverage", "--instrumentation_filter=:foo");
+ setRuleContext(createRuleContext("//test:foo"));
+ // //test:bar does not match :foo, though //test:foo would.
+ Object result = ev.eval("ruleContext.coverage_instrumented(ruleContext.attr.deps[0])");
+ assertThat((Boolean) result).isFalse();
+ }
+
+ @Test
+ public void testCoverageInstrumentedMatchesFilterNonDefaultLabel() throws Exception {
+ setUpCoverageInstrumentedTest();
+ useConfiguration("--collect_code_coverage", "--instrumentation_filter=:bar");
+ setRuleContext(createRuleContext("//test:foo"));
+ // //test:bar does match :bar, though //test:foo would not.
+ Object result = ev.eval("ruleContext.coverage_instrumented(ruleContext.attr.deps[0])");
+ assertThat((Boolean) result).isTrue();
+ }
+
+ // A list of attributes and methods ctx objects have
+ private final List<String> ctxAttributes =
+ ImmutableList.of(
+ "attr",
+ "split_attr",
+ "executable",
+ "file",
+ "files",
+ "workspace_name",
+ "label",
+ "fragments",
+ "host_fragments",
+ "configuration",
+ "host_configuration",
+ "coverage_instrumented(dep)",
+ "features",
+ "bin_dir",
+ "genfiles_dir",
+ "outputs",
+ "rule",
+ "aspect_ids",
+ "var",
+ "tokenize('foo')",
+ "expand('foo', [], Label('//test:main'))",
+ "new_file('foo.txt')",
+ "new_file(file, 'foo.txt')",
+ "actions.declare_file('foo.txt')",
+ "actions.declare_file('foo.txt', sibling = file)",
+ "actions.declare_directory('foo.txt')",
+ "actions.declare_directory('foo.txt', sibling = file)",
+ "actions.do_nothing(mnemonic = 'foo', inputs = [file])",
+ "actions.expand_template(template = file, output = file, substitutions = {})",
+ "actions.run(executable = file, outputs = [file])",
+ "actions.run_shell(command = 'foo', outputs = [file])",
+ "actions.write(file, 'foo')",
+ "check_placeholders('foo', [])",
+ "runfiles()",
+ "resolve_command(command = 'foo')",
+ "resolve_tools()");
+
+ @Test
+ public void testFrozenRuleContextHasInaccessibleAttributes() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=false");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'main_rule', 'dep_rule')",
+ "dep_rule(name = 'dep')",
+ "main_rule(name = 'main', deps = [':dep'])");
+ scratch.file("test/rules.bzl");
+
+ for (String attribute : ctxAttributes) {
+ scratch.overwriteFile(
+ "test/rules.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _main_impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " file = ctx.outputs.file",
+ " foo = dep[MyInfo].dep_ctx." + attribute,
+ "main_rule = rule(",
+ " implementation = _main_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list()",
+ " },",
+ " outputs = {'file': 'output.txt'},",
+ ")",
+ "def _dep_impl(ctx):",
+ " return MyInfo(dep_ctx = ctx)",
+ "dep_rule = rule(implementation = _dep_impl)");
+ invalidatePackages();
+ AssertionError e =
+ assertThrows(
+ "Should have been unable to access dep_ctx." + attribute,
+ AssertionError.class,
+ () -> getConfiguredTarget("//test:main"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "cannot access field or method '"
+ + Iterables.get(Splitter.on('(').split(attribute), 0)
+ + "' of rule context for '//test:dep' outside of its own rule implementation "
+ + "function");
+ }
+ }
+
+ @Test
+ public void testFrozenRuleContextForAspectsHasInaccessibleAttributes() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=false");
+ List<String> attributes = new ArrayList<>();
+ attributes.addAll(ctxAttributes);
+ attributes.addAll(
+ ImmutableList.of("rule.attr", "rule.executable", "rule.file", "rule.files", "rule.kind"));
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule')",
+ "my_rule(name = 'dep')",
+ "my_rule(name = 'mid', deps = [':dep'])",
+ "my_rule(name = 'main', deps = [':mid'])");
+ scratch.file("test/rules.bzl");
+ for (String attribute : attributes) {
+ scratch.overwriteFile(
+ "test/rules.bzl",
+ "def _rule_impl(ctx):",
+ " pass",
+ "def _aspect_impl(target, ctx):",
+ " if ctx.rule.attr.deps:",
+ " dep = ctx.rule.attr.deps[0]",
+ " file = ctx.actions.declare_file('file.txt')",
+ " foo = dep." + (attribute.startsWith("rule.") ? "" : "ctx.") + attribute,
+ " return struct(ctx = ctx, rule=ctx.rule)",
+ "MyAspect = aspect(implementation=_aspect_impl)",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(aspects = [MyAspect])",
+ " },",
+ ")");
+ invalidatePackages();
+ AssertionError e =
+ assertThrows(
+ "Should have been unable to access dep." + attribute,
+ AssertionError.class,
+ () -> getConfiguredTarget("//test:main"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "cannot access field or method '"
+ + Iterables.get(Splitter.on('(').split(attribute), 0)
+ + "' of rule context for '//test:dep' outside of its own rule implementation "
+ + "function");
+ }
+ }
+
+ private static final List<String> deprecatedActionsApi =
+ ImmutableList.of("new_file('foo.txt')", "new_file(file, 'foo.txt')");
+
+ @Test
+ public void testIncompatibleNewActionsApi() throws Exception {
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'main_rule')", "main_rule(name = 'main')");
+ scratch.file("test/rules.bzl");
+
+ for (String actionApi : deprecatedActionsApi) {
+ scratch.overwriteFile(
+ "test/rules.bzl",
+ "def _main_impl(ctx):",
+ " file = ctx.outputs.file",
+ " foo = ctx." + actionApi,
+ "main_rule = rule(",
+ " implementation = _main_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list()",
+ " },",
+ " outputs = {'file': 'output.txt'},",
+ ")");
+ setStarlarkSemanticsOptions("--incompatible_new_actions_api=true");
+ invalidatePackages();
+ AssertionError e =
+ assertThrows(
+ "Should have reported deprecation error for: " + actionApi,
+ AssertionError.class,
+ () -> getConfiguredTarget("//test:main"));
+ assertWithMessage(actionApi + " reported wrong error")
+ .that(e)
+ .hasMessageThat()
+ .contains("Use --incompatible_new_actions_api=false");
+ }
+ }
+
+ @Test
+ public void testMapAttributeOrdering() throws Exception {
+ scratch.file(
+ "a/a.bzl",
+ "key_provider = provider(fields=['keys'])",
+ "def _impl(ctx):",
+ " return [key_provider(keys=ctx.attr.value.keys())]",
+ "a = rule(implementation=_impl, attrs={'value': attr.string_dict()})");
+ scratch.file(
+ "a/BUILD",
+ "load(':a.bzl', 'a')",
+ "a(name='a', value={'c': 'c', 'b': 'b', 'a': 'a', 'f': 'f', 'e': 'e', 'd': 'd'})");
+
+ ConfiguredTarget a = getConfiguredTarget("//a");
+ StarlarkProvider.Key key =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//a:a.bzl", ImmutableMap.of()), "key_provider");
+
+ StarlarkInfo keyInfo = (StarlarkInfo) a.get(key);
+ Sequence<?> keys = (Sequence) keyInfo.getValue("keys");
+ assertThat(keys).containsExactly("c", "b", "a", "f", "e", "d").inOrder();
+ }
+
+ private void writeIntFlagBuildSettingFiles() throws Exception {
+ scratch.file(
+ "test/build_setting.bzl",
+ "BuildSettingInfo = provider(fields = ['name', 'value'])",
+ "def _impl(ctx):",
+ " return [BuildSettingInfo(name = ctx.attr.name, value = ctx.build_setting_value)]",
+ "",
+ "int_flag = rule(",
+ " implementation = _impl,",
+ " build_setting = config.int(flag = True),",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:build_setting.bzl', 'int_flag')",
+ "int_flag(name = 'int_flag', build_setting_default = 42)");
+ }
+
+ @Test
+ public void testBuildSettingValue_explicitlySet() throws Exception {
+ writeIntFlagBuildSettingFiles();
+ useConfiguration(ImmutableMap.of("//test:int_flag", 24));
+
+ ConfiguredTarget buildSetting = getConfiguredTarget("//test:int_flag");
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
+ "BuildSettingInfo");
+ StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
+
+ assertThat(buildSettingInfo.getValue("value")).isEqualTo(24);
+ }
+
+ @Test
+ public void testBuildSettingValue_defaultFallback() throws Exception {
+ writeIntFlagBuildSettingFiles();
+
+ ConfiguredTarget buildSetting = getConfiguredTarget("//test:int_flag");
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
+ "BuildSettingInfo");
+ StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
+
+ assertThat(buildSettingInfo.getValue("value")).isEqualTo(42);
+ }
+
+ @Test
+ public void testBuildSettingValue_nonBuildSettingRule() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _impl(ctx):",
+ " foo = ctx.build_setting_value",
+ " return []",
+ "non_build_setting = rule(implementation = _impl)");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'non_build_setting')",
+ "non_build_setting(name = 'my_non_build_setting')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_non_build_setting");
+ assertContainsEvent(
+ "attempting to access 'build_setting_value' of non-build setting "
+ + "//test:my_non_build_setting");
+ }
+
+ private void createToolchains() throws Exception {
+ scratch.file(
+ "rule/test_toolchain.bzl",
+ "def _impl(ctx):",
+ " value = ctx.attr.value",
+ " toolchain = platform_common.ToolchainInfo(value = value)",
+ " return [toolchain]",
+ "test_toolchain = rule(",
+ " implementation = _impl,",
+ " attrs = {'value': attr.string()},",
+ ")");
+ scratch.file(
+ "rule/test_rule.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " toolchain = ctx.toolchains['//rule:toolchain_type']",
+ " return [result(",
+ " value_from_toolchain = toolchain.value,",
+ " )]",
+ "test_rule = rule(",
+ " implementation = _impl,",
+ " toolchains = ['//rule:toolchain_type'],",
+ ")");
+ scratch.file(
+ "rule/BUILD",
+ "exports_files(['test_toolchain/bzl', 'test_rule.bzl'])",
+ "toolchain_type(name = 'toolchain_type')");
+ scratch.file(
+ "toolchain/BUILD",
+ "load('//rule:test_toolchain.bzl', 'test_toolchain')",
+ "test_toolchain(",
+ " name = 'foo',",
+ " value = 'foo',",
+ ")",
+ "toolchain(",
+ " name = 'foo_toolchain',",
+ " toolchain_type = '//rule:toolchain_type',",
+ " target_compatible_with = ['//platform:constraint_1'],",
+ " toolchain = ':foo',",
+ ")",
+ "test_toolchain(",
+ " name = 'bar',",
+ " value = 'bar',",
+ ")",
+ "toolchain(",
+ " name = 'bar_toolchain',",
+ " toolchain_type = '//rule:toolchain_type',",
+ " target_compatible_with = ['//platform:constraint_2'],",
+ " toolchain = ':bar',",
+ ")");
+ }
+
+ private void createPlatforms() throws Exception {
+ 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'],",
+ ")");
+ }
+
+ private String getToolchainResult(String targetName) throws Exception {
+ ConfiguredTarget myRuleTarget = getConfiguredTarget(targetName);
+ StructImpl info =
+ (StructImpl)
+ myRuleTarget.get(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//rule:test_rule.bzl", ImmutableMap.of()), "result"));
+
+ assertThat(info).isNotNull();
+ return (String) info.getValue("value_from_toolchain");
+ }
+
+ @Test
+ public void testToolchains() throws Exception {
+ createToolchains();
+ createPlatforms();
+ scratch.file(
+ "demo/BUILD",
+ "load('//rule:test_rule.bzl', 'test_rule')",
+ "test_rule(",
+ " name = 'demo',",
+ ")");
+
+ useConfiguration(
+ "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain",
+ "--platforms=//platform:platform_1");
+ String value = getToolchainResult("//demo");
+ assertThat(value).isEqualTo("foo");
+
+ // Re-test with the other platform.
+ useConfiguration(
+ "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain",
+ "--platforms=//platform:platform_2");
+ value = getToolchainResult("//demo");
+ assertThat(value).isEqualTo("bar");
+ }
+
+ @Test
+ public void testTargetPlatformHasConstraint() throws Exception {
+ createPlatforms();
+
+ scratch.file(
+ "demo/test_rule.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " constraint = ctx.attr._constraint[platform_common.ConstraintValueInfo]",
+ " has_constraint = ctx.target_platform_has_constraint(constraint)",
+ " return [result(",
+ " has_constraint = has_constraint,",
+ " )]",
+ "test_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " '_constraint': attr.label(default = '//platform:constraint_1'),",
+ " },",
+ ")");
+ scratch.file(
+ "demo/BUILD",
+ "load(':test_rule.bzl', 'test_rule')",
+ "test_rule(",
+ " name = 'demo',",
+ ")");
+
+ useConfiguration("--platforms=//platform:platform_1");
+
+ ConfiguredTarget myRuleTarget = getConfiguredTarget("//demo");
+ StructImpl info =
+ (StructImpl)
+ myRuleTarget.get(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//demo:test_rule.bzl", ImmutableMap.of()), "result"));
+
+ assertThat(info).isNotNull();
+ boolean hasConstraint = (boolean) info.getValue("has_constraint");
+ assertThat(hasConstraint).isTrue();
+
+ // Re-test with the other platform.
+ useConfiguration("--platforms=//platform:platform_2");
+ myRuleTarget = getConfiguredTarget("//demo");
+ info =
+ (StructImpl)
+ myRuleTarget.get(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//demo:test_rule.bzl", ImmutableMap.of()), "result"));
+
+ assertThat(info).isNotNull();
+ hasConstraint = (boolean) info.getValue("has_constraint");
+ assertThat(hasConstraint).isFalse();
+ }
+
+ private void writeExecGroups() throws Exception {
+ createToolchains();
+ createPlatforms();
+ scratch.file(
+ "something/defs.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " exec_groups = ctx.exec_groups",
+ " toolchain = ctx.exec_groups['dragonfruit'].toolchains['//rule:toolchain_type']",
+ " return [result(",
+ " toolchain_value = toolchain.value,",
+ " exec_groups = exec_groups,",
+ " )]",
+ "use_exec_groups = rule(",
+ " implementation = _impl,",
+ " exec_groups = {",
+ " 'dragonfruit': exec_group(toolchains = ['//rule:toolchain_type']),",
+ " },",
+ ")");
+ scratch.file(
+ "something/BUILD",
+ "load('//something:defs.bzl', 'use_exec_groups')",
+ "use_exec_groups(name = 'nectarine')");
+ setStarlarkSemanticsOptions("--experimental_exec_groups=true");
+ useConfiguration(
+ "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain",
+ "--platforms=//platform:platform_1");
+ }
+
+ @Test
+ public void testExecGroup_toolchain() throws Exception {
+ writeExecGroups();
+
+ ConfiguredTarget target = getConfiguredTarget("//something:nectarine");
+ StructImpl info =
+ (StructImpl)
+ target.get(
+ new StarlarkProvider.Key(
+ Label.parseAbsoluteUnchecked("//something:defs.bzl"), "result"));
+ assertThat(info).isNotNull();
+ assertThat(info.getValue("toolchain_value")).isEqualTo("foo");
+ assertThat(info.getValue("exec_groups")).isInstanceOf(ExecGroupCollection.class);
+ ImmutableMap<String, ResolvedToolchainContext> toolchainContexts =
+ ((ExecGroupCollection) info.getValue("exec_groups")).getToolchainCollectionForTesting();
+ assertThat(toolchainContexts.keySet()).containsExactly(DEFAULT_EXEC_GROUP_NAME, "dragonfruit");
+ assertThat(toolchainContexts.get(DEFAULT_EXEC_GROUP_NAME).requiredToolchainTypes()).isEmpty();
+ assertThat(toolchainContexts.get("dragonfruit").resolvedToolchainLabels())
+ .containsExactly(Label.parseAbsoluteUnchecked("//toolchain:foo"));
+ }
+
+ // Tests for an error that occurs when two exec groups have different requirements (toolchain
+ // types and exec constraints), but have the same toolchain type. This also requires the toolchain
+ // transition to be enabled.
+ @Test
+ public void testExecGroup_duplicateToolchainType() throws Exception {
+ createToolchains();
+ createPlatforms();
+ scratch.file(
+ "something/defs.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " exec_groups = ctx.exec_groups",
+ " toolchain = ctx.exec_groups['dragonfruit'].toolchains['//rule:toolchain_type']",
+ " return [result(",
+ " toolchain_value = toolchain.value,",
+ " exec_groups = exec_groups,",
+ " )]",
+ "use_exec_groups = rule(",
+ " implementation = _impl,",
+ " exec_groups = {",
+ " 'dragonfruit': exec_group(toolchains = ['//rule:toolchain_type']),",
+ " 'passionfruit': exec_group(",
+ " toolchains = ['//rule:toolchain_type'],",
+ " exec_compatible_with = ['//something:extra'],",
+ " ),",
+ " },",
+ " incompatible_use_toolchain_transition = True,",
+ ")");
+ scratch.file(
+ "something/BUILD",
+ "constraint_setting(name = 'setting', default_constraint_value = ':extra')",
+ "constraint_value(name = 'extra', constraint_setting = ':setting')",
+ "load('//something:defs.bzl', 'use_exec_groups')",
+ "use_exec_groups(name = 'nectarine')");
+ setStarlarkSemanticsOptions("--experimental_exec_groups=true");
+ useConfiguration(
+ "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain",
+ "--platforms=//platform:platform_1");
+
+ ConfiguredTarget target = getConfiguredTarget("//something:nectarine");
+ StructImpl info =
+ (StructImpl)
+ target.get(
+ new StarlarkProvider.Key(
+ Label.parseAbsoluteUnchecked("//something:defs.bzl"), "result"));
+ assertThat(info).isNotNull();
+ assertThat(info.getValue("toolchain_value")).isEqualTo("foo");
+ assertThat(info.getValue("exec_groups")).isInstanceOf(ExecGroupCollection.class);
+ ImmutableMap<String, ResolvedToolchainContext> toolchainContexts =
+ ((ExecGroupCollection) info.getValue("exec_groups")).getToolchainCollectionForTesting();
+ assertThat(toolchainContexts.keySet())
+ .containsExactly(DEFAULT_EXEC_GROUP_NAME, "dragonfruit", "passionfruit");
+ assertThat(toolchainContexts.get(DEFAULT_EXEC_GROUP_NAME).requiredToolchainTypes()).isEmpty();
+ assertThat(toolchainContexts.get("dragonfruit").resolvedToolchainLabels())
+ .containsExactly(Label.parseAbsoluteUnchecked("//toolchain:foo"));
+ assertThat(toolchainContexts.get("passionfruit").resolvedToolchainLabels())
+ .containsExactly(Label.parseAbsoluteUnchecked("//toolchain:foo"));
+ }
+
+ @Test
+ public void testInvalidExecGroup() throws Exception {
+ writeExecGroups();
+
+ scratch.overwriteFile(
+ "something/defs.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " exec_groups = ctx.exec_groups",
+ " toolchain = ctx.exec_groups['unknown_fruit']",
+ " return []",
+ "use_exec_groups = rule(",
+ " implementation = _impl,",
+ " exec_groups = {",
+ " 'dragonfruit': exec_group(toolchains = ['//rule:toolchain_type']),",
+ " },",
+ ")");
+
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//something:nectarine"));
+ assertContainsEvent(
+ "unrecognized exec group 'unknown_fruit' requested. Available exec groups: [dragonfruit]");
+ }
+
+ @Test
+ public void testCannotAccessDefaultGroupViaExecGroups() throws Exception {
+ writeExecGroups();
+
+ scratch.overwriteFile(
+ "something/defs.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " exec_groups = ctx.exec_groups",
+ " toolchain = ctx.exec_groups['" + DEFAULT_EXEC_GROUP_NAME + "']",
+ " return []",
+ "use_exec_groups = rule(",
+ " implementation = _impl,",
+ " exec_groups = {",
+ " 'dragonfruit': exec_group(toolchains = ['//rule:toolchain_type']),",
+ " },",
+ ")");
+
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//something:nectarine"));
+ assertContainsEvent(
+ "unrecognized exec group '"
+ + DEFAULT_EXEC_GROUP_NAME
+ + "' requested. Available exec groups: [dragonfruit]");
+ }
+
+ @Test
+ public void testInvalidExecGroupName() throws Exception {
+ writeExecGroups();
+ String badName = "1bad-stuff-name";
+
+ scratch.overwriteFile(
+ "something/defs.bzl",
+ "result = provider()",
+ "def _impl(ctx):",
+ " exec_groups = ctx.exec_groups",
+ " toolchain = ctx.exec_groups['" + badName + "']",
+ " return []",
+ "use_exec_groups = rule(",
+ " implementation = _impl,",
+ " exec_groups = {",
+ " '" + badName + "': exec_group(toolchains = ['//rule:toolchain_type']),",
+ " },",
+ ")");
+
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//something:nectarine"));
+ assertContainsEvent("Exec group name '" + badName + "' is not a valid name.");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java
new file mode 100644
index 0000000..00bbdad
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java
@@ -0,0 +1,3263 @@
+// Copyright 2014 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.ActionLookupKey;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpanderImpl;
+import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
+import com.google.devtools.build.lib.actions.CommandLine;
+import com.google.devtools.build.lib.actions.CommandLineExpansionException;
+import com.google.devtools.build.lib.actions.CompositeRunfilesSupplier;
+import com.google.devtools.build.lib.actions.RunfilesSupplier;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.DefaultInfo;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.actions.Substitution;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.starlark.Args;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.events.Event;
+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 com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.Sequence;
+import com.google.devtools.build.lib.syntax.Starlark;
+import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.syntax.StarlarkThread;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.OsUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import net.starlark.java.annot.Param;
+import net.starlark.java.annot.StarlarkGlobalLibrary;
+import net.starlark.java.annot.StarlarkMethod;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for Starlark functions relating to rule implementation. */
+@RunWith(JUnit4.class)
+@StarlarkGlobalLibrary // needed for CallUtils.getBuiltinCallable, sadly
+public class StarlarkRuleImplementationFunctionsTest extends BuildViewTestCase {
+
+ private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
+
+ private StarlarkRuleContext createRuleContext(String label) throws Exception {
+ return new StarlarkRuleContext(
+ getRuleContextForStarlark(getConfiguredTarget(label)), null, getStarlarkSemantics());
+ }
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ // def mock(mandatory, optional=None, *, mandatory_key, optional_key='x')
+ @StarlarkMethod(
+ name = "mock",
+ documented = false,
+ parameters = {
+ @Param(name = "mandatory", doc = "", named = true),
+ @Param(name = "optional", doc = "", defaultValue = "None", noneable = true, named = true),
+ @Param(name = "mandatory_key", doc = "", positional = false, named = true),
+ @Param(
+ name = "optional_key",
+ doc = "",
+ defaultValue = "'x'",
+ positional = false,
+ named = true)
+ },
+ useStarlarkThread = true)
+ public Object mock(
+ Object mandatory,
+ Object optional,
+ Object mandatoryKey,
+ Object optionalKey,
+ StarlarkThread thread) {
+ Map<String, Object> m = new HashMap<>();
+ m.put("mandatory", mandatory);
+ m.put("optional", optional);
+ m.put("mandatory_key", mandatoryKey);
+ m.put("optional_key", optionalKey);
+ return m;
+ }
+
+ @Before
+ public final void createBuildFile() throws Exception {
+ scratch.file("myinfo/myinfo.bzl", "MyInfo = provider()");
+
+ scratch.file("myinfo/BUILD");
+
+ scratch.file(
+ "foo/BUILD",
+ "genrule(name = 'foo',",
+ " cmd = 'dummy_cmd',",
+ " srcs = ['a.txt', 'b.img'],",
+ " tools = ['t.exe'],",
+ " outs = ['c.txt'])",
+ "genrule(name = 'bar',",
+ " cmd = 'dummy_cmd',",
+ " srcs = [':jl', ':gl'],",
+ " outs = ['d.txt'])",
+ "genrule(name = 'baz',",
+ " cmd = 'dummy_cmd',",
+ " outs = ['e.txt'])",
+ "java_library(name = 'jl',",
+ " srcs = ['a.java'])",
+ "genrule(name = 'gl',",
+ " cmd = 'touch $(OUTS)',",
+ " srcs = ['a.go'],",
+ " outs = [ 'gl.a', 'gl.gcgox', ],",
+ " output_to_bindir = 1,",
+ ")",
+ // The target below is used by testResolveCommand and testResolveTools
+ "sh_binary(name = 'mytool',",
+ " srcs = ['mytool.sh'],",
+ " data = ['file1.dat', 'file2.dat'],",
+ ")",
+ // The target below is used by testResolveCommand and testResolveTools
+ "genrule(name = 'resolve_me',",
+ " cmd = 'aa',",
+ " tools = [':mytool', 't.exe'],",
+ " srcs = ['file3.dat', 'file4.dat'],",
+ " outs = ['r1.txt', 'r2.txt'],",
+ ")");
+ }
+
+ private void setRuleContext(StarlarkRuleContext ctx) throws Exception {
+ ev.update("ruleContext", ctx);
+ }
+
+ private static void assertArtifactFilenames(Iterable<Artifact> artifacts, String... expected) {
+ ImmutableList.Builder<String> filenames = ImmutableList.builder();
+ for (Artifact a : artifacts) {
+ filenames.add(a.getFilename());
+ }
+ assertThat(filenames.build()).containsAtLeastElementsIn(Lists.newArrayList(expected));
+ }
+
+ private StructImpl getMyInfoFromTarget(ConfiguredTarget configuredTarget) throws Exception {
+ Provider.Key key =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//myinfo:myinfo.bzl", ImmutableMap.of()), "MyInfo");
+ return (StructImpl) configuredTarget.get(key);
+ }
+
+ // Defines all @StarlarkCallable-annotated methods (mock, throw, ...) in the environment.
+ private void defineTestMethods() throws Exception {
+ ImmutableMap.Builder<String, Object> env = ImmutableMap.builder();
+ Starlark.addMethods(env, this);
+ for (Map.Entry<String, Object> entry : env.build().entrySet()) {
+ ev.update(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void checkStarlarkFunctionError(String errorSubstring, String line) throws Exception {
+ defineTestMethods();
+ EvalException e = assertThrows(EvalException.class, () -> ev.exec(line));
+ assertThat(e).hasMessageThat().contains(errorSubstring);
+ }
+
+ // TODO(adonovan): move these tests of the interpreter core into lib.syntax.
+
+ @Test
+ public void testStarlarkFunctionPosArgs() throws Exception {
+ defineTestMethods();
+ ev.exec("a = mock('a', 'b', mandatory_key='c')");
+ Map<?, ?> params = (Map<?, ?>) ev.lookup("a");
+ assertThat(params.get("mandatory")).isEqualTo("a");
+ assertThat(params.get("optional")).isEqualTo("b");
+ assertThat(params.get("mandatory_key")).isEqualTo("c");
+ assertThat(params.get("optional_key")).isEqualTo("x");
+ }
+
+ @Test
+ public void testStarlarkFunctionKwArgs() throws Exception {
+ defineTestMethods();
+ ev.exec("a = mock(optional='b', mandatory='a', mandatory_key='c')");
+ Map<?, ?> params = (Map<?, ?>) ev.lookup("a");
+ assertThat(params.get("mandatory")).isEqualTo("a");
+ assertThat(params.get("optional")).isEqualTo("b");
+ assertThat(params.get("mandatory_key")).isEqualTo("c");
+ assertThat(params.get("optional_key")).isEqualTo("x");
+ }
+
+ @Test
+ public void testStarlarkFunctionTooFewArguments() throws Exception {
+ checkStarlarkFunctionError(
+ "missing 1 required positional argument: mandatory", "mock(mandatory_key='y')");
+ }
+
+ @Test
+ public void testStarlarkFunctionTooManyArguments() throws Exception {
+ checkStarlarkFunctionError(
+ "mock() accepts no more than 2 positional arguments but got 3",
+ "mock('a', 'b', 'c', mandatory_key='y')");
+ }
+
+ @Test
+ public void testStarlarkFunctionAmbiguousArguments() throws Exception {
+ checkStarlarkFunctionError(
+ "mock() got multiple values for argument 'mandatory'",
+ "mock('by position', mandatory='by_key', mandatory_key='c')");
+ }
+
+ @Test
+ public void testCreateSpawnActionCreatesSpawnAction() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ createTestSpawnAction(ruleContext);
+ ActionAnalysisMetadata action =
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action).isInstanceOf(SpawnAction.class);
+ }
+
+ @Test
+ public void testArtifactPath() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ String result = (String) ev.eval("ruleContext.files.tools[0].path");
+ assertThat(result).isEqualTo("foo/t.exe");
+ }
+
+ @Test
+ public void testArtifactShortPath() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ String result = (String) ev.eval("ruleContext.files.tools[0].short_path");
+ assertThat(result).isEqualTo("foo/t.exe");
+ }
+
+ @Test
+ public void testCreateSpawnActionArgumentsWithCommand() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ createTestSpawnAction(ruleContext);
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertArtifactFilenames(action.getInputs().toList(), "a.txt", "b.img");
+ assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img");
+ MoreAsserts.assertContainsSublist(
+ action.getArguments(), "-c", "dummy_command", "", "--a", "--b");
+ assertThat(action.getMnemonic()).isEqualTo("DummyMnemonic");
+ assertThat(action.getProgressMessage()).isEqualTo("dummy_message");
+ assertThat(action.getIncompleteEnvironmentForTesting())
+ .isEqualTo(targetConfig.getLocalShellEnvironment());
+ }
+
+ @Test
+ public void testCreateSpawnActionArgumentsWithExecutable() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['--a','--b'],",
+ " executable = ruleContext.files.tools[0])");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertArtifactFilenames(action.getInputs().toList(), "a.txt", "b.img", "t.exe");
+ assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img");
+ MoreAsserts.assertContainsSublist(action.getArguments(), "foo/t.exe", "--a", "--b");
+ }
+
+ @Test
+ public void testCreateActionWithDepsetInput() throws Exception {
+ // Same test as above, with depset as inputs.
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['--a','--b'],",
+ " executable = ruleContext.files.tools[0])");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertArtifactFilenames(action.getInputs().toList(), "a.txt", "b.img", "t.exe");
+ assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img");
+ MoreAsserts.assertContainsSublist(action.getArguments(), "foo/t.exe", "--a", "--b");
+ }
+
+ @Test
+ public void testCreateSpawnActionArgumentsBadExecutable() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "got value of type 'int', want 'File or string or FilesToRunProvider'",
+ "ruleContext.actions.run(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['--a','--b'],",
+ " executable = 123)");
+ }
+
+ @Test
+ public void testCreateSpawnActionShellCommandList() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run_shell(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " mnemonic = 'DummyMnemonic',",
+ " command = ['dummy_command', '--arg1', '--arg2'],",
+ " progress_message = 'dummy_message')");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly("dummy_command", "--arg1", "--arg2")
+ .inOrder();
+ }
+
+ @Test
+ public void testCreateSpawnActionEnvAndExecInfo() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run_shell(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " env = {'a' : 'b'},",
+ " execution_requirements = {'timeout' : '10', 'block-network' : 'foo'},",
+ " mnemonic = 'DummyMnemonic',",
+ " command = 'dummy_command',",
+ " progress_message = 'dummy_message')");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getIncompleteEnvironmentForTesting()).containsExactly("a", "b");
+ // We expect "timeout" to be filtered by TargetUtils.
+ assertThat(action.getExecutionInfo()).containsExactly("block-network", "foo");
+ }
+
+ @Test
+ public void testCreateSpawnActionEnvAndExecInfo_withWorkerKeyMnemonic() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.run_shell(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " env = {'a' : 'b'},",
+ " execution_requirements = {",
+ " 'supports-workers': '1',",
+ " 'worker-key-mnemonic': 'MyMnemonic',",
+ " },",
+ " mnemonic = 'DummyMnemonic',",
+ " command = 'dummy_command',",
+ " progress_message = 'dummy_message')");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getExecutionInfo())
+ .containsExactly("supports-workers", "1", "worker-key-mnemonic", "MyMnemonic");
+ }
+
+ @Test
+ public void testCreateSpawnActionUnknownParam() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "run() got unexpected keyword argument 'bad_param'",
+ "f = ruleContext.actions.declare_file('foo.sh')",
+ "ruleContext.actions.run(outputs=[], bad_param = 'some text', executable = f)");
+ }
+
+ private Object createTestSpawnAction(StarlarkRuleContext ruleContext) throws Exception {
+ setRuleContext(ruleContext);
+ return ev.eval(
+ "ruleContext.actions.run_shell(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['--a','--b'],",
+ " mnemonic = 'DummyMnemonic',",
+ " command = 'dummy_command',",
+ " progress_message = 'dummy_message',",
+ " use_default_shell_env = True)");
+ }
+
+ @Test
+ public void testCreateSpawnActionBadGenericArg() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "at index 0 of outputs, got element of type string, want File",
+ "l = ['a', 'b']",
+ "ruleContext.actions.run_shell(",
+ " outputs = l,",
+ " command = 'dummy_command')");
+ }
+
+ @Test
+ public void testRunShellArgumentsWithCommandSequence() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "'arguments' must be empty if 'command' is a sequence of strings",
+ "ruleContext.actions.run_shell(outputs = ruleContext.files.srcs,",
+ " command = [\"echo\", \"'hello world'\", \"&&\", \"touch\"],",
+ " arguments = [ruleContext.files.srcs[0].path])");
+ }
+
+ private void setupToolInInputsTest(String... ruleImpl) throws Exception {
+ ImmutableList.Builder<String> lines = ImmutableList.builder();
+ lines.add("def _main_rule_impl(ctx):");
+ for (String line : ruleImpl) {
+ lines.add(" " + line);
+ }
+ lines.add(
+ "my_rule = rule(",
+ " _main_rule_impl,",
+ " attrs = { ",
+ " 'exe' : attr.label(executable = True, allow_files = True, cfg='host'),",
+ " },",
+ ")");
+ scratch.file("bar/bar.bzl", lines.build().toArray(new String[] {}));
+ scratch.file(
+ "bar/BUILD",
+ "load('//bar:bar.bzl', 'my_rule')",
+ "sh_binary(",
+ " name = 'mytool',",
+ " srcs = ['mytool.sh'],",
+ " data = ['file1.dat', 'file2.dat'],",
+ ")",
+ "my_rule(",
+ " name = 'my_rule',",
+ " exe = ':mytool',",
+ ")");
+ }
+
+ @Test
+ public void testCreateSpawnActionWithToolInInputsLegacy() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_no_support_tools_in_action_inputs=false");
+ setupToolInInputsTest(
+ "output = ctx.actions.declare_file('bar.out')",
+ "ctx.actions.run_shell(",
+ " inputs = ctx.attr.exe.files,",
+ " outputs = [output],",
+ " command = 'boo bar baz',",
+ ")");
+ RuleConfiguredTarget target = (RuleConfiguredTarget) getConfiguredTarget("//bar:my_rule");
+ SpawnAction action = (SpawnAction) Iterables.getOnlyElement(target.getActions());
+ assertThat(action.getTools().toList()).isNotEmpty();
+ }
+
+ @Test
+ public void testCreateSpawnActionWithToolAttribute() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_no_support_tools_in_action_inputs=true");
+ setupToolInInputsTest(
+ "output = ctx.actions.declare_file('bar.out')",
+ "ctx.actions.run_shell(",
+ " inputs = [],",
+ " tools = ctx.attr.exe.files,",
+ " outputs = [output],",
+ " command = 'boo bar baz',",
+ ")");
+ RuleConfiguredTarget target = (RuleConfiguredTarget) getConfiguredTarget("//bar:my_rule");
+ SpawnAction action = (SpawnAction) Iterables.getOnlyElement(target.getActions());
+ assertThat(action.getTools().toList()).isNotEmpty();
+ }
+
+ @Test
+ public void testCreateSpawnActionWithToolAttributeIgnoresToolsInInputs() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_no_support_tools_in_action_inputs=true");
+ setupToolInInputsTest(
+ "output = ctx.actions.declare_file('bar.out')",
+ "ctx.actions.run_shell(",
+ " inputs = ctx.attr.exe.files,",
+ " tools = ctx.attr.exe.files,",
+ " outputs = [output],",
+ " command = 'boo bar baz',",
+ ")");
+ RuleConfiguredTarget target = (RuleConfiguredTarget) getConfiguredTarget("//bar:my_rule");
+ SpawnAction action = (SpawnAction) Iterables.getOnlyElement(target.getActions());
+ assertThat(action.getTools().toList()).isNotEmpty();
+ }
+
+ @Test
+ public void testCreateSpawnActionWithToolInInputsFailAtAnalysisTime() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_no_support_tools_in_action_inputs=true");
+ setupToolInInputsTest(
+ "output = ctx.actions.declare_file('bar.out')",
+ "ctx.actions.run_shell(",
+ " inputs = ctx.attr.exe.files,",
+ " outputs = [output],",
+ " command = 'boo bar baz',",
+ ")");
+ try {
+ getConfiguredTarget("//bar:my_rule");
+ } catch (Throwable t) {
+ // Expected
+ }
+ assertThat(eventCollector).hasSize(1);
+ assertThat(eventCollector.iterator().next().getMessage())
+ .containsMatch("Found tool\\(s\\) '.*' in inputs");
+ }
+
+ @Test
+ public void testCreateFileAction() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.write(",
+ " output = ruleContext.files.srcs[0],",
+ " content = 'hello world',",
+ " is_executable = False)");
+ FileWriteAction action =
+ (FileWriteAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(Iterables.getOnlyElement(action.getOutputs()).getExecPathString())
+ .isEqualTo("foo/a.txt");
+ assertThat(action.getFileContents()).isEqualTo("hello world");
+ assertThat(action.makeExecutable()).isFalse();
+ }
+
+ @Test
+ public void testEmptyAction() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ checkEmptyAction("mnemonic = 'test'");
+ checkEmptyAction("mnemonic = 'test', inputs = ruleContext.files.srcs");
+ checkEmptyAction("mnemonic = 'test', inputs = depset(ruleContext.files.srcs)");
+
+ ev.checkEvalErrorContains(
+ "do_nothing() missing 1 required named argument: mnemonic",
+ "ruleContext.actions.do_nothing(inputs = ruleContext.files.srcs)");
+ }
+
+ private void checkEmptyAction(String namedArgs) throws Exception {
+ assertThat(ev.eval(String.format("ruleContext.actions.do_nothing(%s)", namedArgs)))
+ .isEqualTo(Starlark.NONE);
+ }
+
+ @Test
+ public void testEmptyActionWithExtraAction() throws Exception {
+ scratch.file(
+ "test/empty.bzl",
+ "def _impl(ctx):",
+ " ctx.actions.do_nothing(",
+ " inputs = ctx.files.srcs,",
+ " mnemonic = 'EA',",
+ " )",
+ "empty_action_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:empty.bzl', 'empty_action_rule')",
+ "empty_action_rule(name = 'my_empty_action',",
+ " srcs = ['foo.in', 'other_foo.in'])",
+ "action_listener(name = 'listener',",
+ " mnemonics = ['EA'],",
+ " extra_actions = [':extra'])",
+ "extra_action(name = 'extra',",
+ " cmd='')");
+
+ getPseudoActionViaExtraAction("//test:my_empty_action", "//test:listener");
+ }
+
+ @Test
+ public void testExpandLocation() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:bar");
+ setRuleContext(ruleContext);
+
+ // If there is only a single target, both "location" and "locations" should work
+ runExpansion("location :jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+ runExpansion("locations :jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+
+ runExpansion("location //foo:jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+
+ // Multiple targets and "location" should result in an error
+ checkReportedErrorStartsWith(
+ "in genrule rule //foo:bar: label '//foo:gl' "
+ + "in $(location) expression expands to more than one file, please use $(locations "
+ + "//foo:gl) instead.",
+ "ruleContext.expand_location('$(location :gl)')");
+
+ // We have to use "locations" for multiple targets
+ runExpansion(
+ "locations :gl",
+ "[blaze]*-out/.*/bin/foo/gl.a [blaze]*-out/.*/bin/foo/gl.gcgox");
+
+ // LocationExpander just returns the input string if there is no label
+ runExpansion("location", "\\$\\(location\\)");
+
+ checkReportedErrorStartsWith(
+ "in genrule rule //foo:bar: label '//foo:abc' in $(locations) expression "
+ + "is not a declared prerequisite of this rule",
+ "ruleContext.expand_location('$(locations :abc)')");
+ }
+
+ /** Regression test to check that expand_location allows ${var} and $$. */
+ @Test
+ public void testExpandLocationWithDollarSignsAndCurlys() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:bar");
+ setRuleContext(ruleContext);
+ assertThat((String) ev.eval("ruleContext.expand_location('${abc} $(echo) $$ $')"))
+ .isEqualTo("${abc} $(echo) $$ $");
+ }
+
+ /**
+ * Invokes ctx.expand_location() with the given parameters and checks whether this led to the
+ * expected result
+ *
+ * @param command Either "location" or "locations". This only matters when the label has multiple
+ * targets
+ * @param expectedPattern Regex pattern that matches the expected result
+ */
+ private void runExpansion(String command, String expectedPattern) throws Exception {
+ assertMatches(
+ "Expanded string",
+ expectedPattern,
+ (String) ev.eval(String.format("ruleContext.expand_location('$(%s)')", command)));
+ }
+
+ private void assertMatches(String description, String expectedPattern, String computedValue)
+ throws Exception {
+ assertWithMessage(
+ Starlark.format(
+ "%s %r did not match pattern '%s'", description, computedValue, expectedPattern))
+ .that(Pattern.matches(expectedPattern, computedValue))
+ .isTrue();
+ }
+
+ @Test
+ public void testResolveCommandMakeVariables() throws Exception {
+ setRuleContext(createRuleContext("//foo:resolve_me"));
+ ev.exec(
+ "inputs, argv, manifests = ruleContext.resolve_command(",
+ " command='I got the $(HELLO) on a $(DAVE)', ",
+ " make_variables={'HELLO': 'World', 'DAVE': type('')})");
+ @SuppressWarnings("unchecked")
+ List<String> argv = (List<String>) (List<?>) (StarlarkList) ev.lookup("argv");
+ assertThat(argv).hasSize(3);
+ assertMatches("argv[0]", "^.*/bash" + OsUtils.executableExtension() + "$", argv.get(0));
+ assertThat(argv.get(1)).isEqualTo("-c");
+ assertThat(argv.get(2)).isEqualTo("I got the World on a string");
+ }
+
+ @Test
+ public void testResolveCommandInputs() throws Exception {
+ setRuleContext(createRuleContext("//foo:resolve_me"));
+ ev.exec(
+ "inputs, argv, input_manifests = ruleContext.resolve_command(",
+ " tools=ruleContext.attr.tools)");
+ @SuppressWarnings("unchecked")
+ List<Artifact> inputs = (List<Artifact>) (List<?>) (StarlarkList) ev.lookup("inputs");
+ assertArtifactFilenames(
+ inputs,
+ "mytool.sh",
+ "mytool",
+ "foo_Smytool" + OsUtils.executableExtension() + "-runfiles",
+ "t.exe");
+ @SuppressWarnings("unchecked")
+ RunfilesSupplier runfilesSupplier =
+ CompositeRunfilesSupplier.fromSuppliers(
+ (List<RunfilesSupplier>) ev.lookup("input_manifests"));
+ assertThat(runfilesSupplier.getMappings()).hasSize(1);
+ }
+
+ @Test
+ public void testResolveCommandExpandLocations() throws Exception {
+ setRuleContext(createRuleContext("//foo:resolve_me"));
+ ev.exec(
+ "def foo():", // no for loops at top-level
+ " label_dict = {}",
+ " all = []",
+ " for dep in ruleContext.attr.srcs + ruleContext.attr.tools:",
+ " all.extend(dep.files.to_list())",
+ " label_dict[dep.label] = dep.files.to_list()",
+ " return ruleContext.resolve_command(",
+ " command='A$(locations //foo:mytool) B$(location //foo:file3.dat)',",
+ " attribute='cmd', expand_locations=True, label_dict=label_dict)",
+ "inputs, argv, manifests = foo()");
+ @SuppressWarnings("unchecked")
+ List<String> argv = (List<String>) (List<?>) (StarlarkList) ev.lookup("argv");
+ assertThat(argv).hasSize(3);
+ assertMatches("argv[0]", "^.*/bash" + OsUtils.executableExtension() + "$", argv.get(0));
+ assertThat(argv.get(1)).isEqualTo("-c");
+ assertMatches("argv[2]", "A.*/mytool .*/mytool.sh B.*file3.dat", argv.get(2));
+ }
+
+ @Test
+ public void testResolveCommandExecutionRequirements() throws Exception {
+ // Tests that requires-darwin execution requirements result in the usage of /bin/bash.
+ setRuleContext(createRuleContext("//foo:resolve_me"));
+ ev.exec(
+ "inputs, argv, manifests = ruleContext.resolve_command(",
+ " execution_requirements={'requires-darwin': ''})");
+ @SuppressWarnings("unchecked")
+ List<String> argv = (List<String>) (List<?>) (StarlarkList) ev.lookup("argv");
+ assertMatches("argv[0]", "^/bin/bash$", argv.get(0));
+ }
+
+ @Test
+ public void testResolveCommandScript() throws Exception {
+ setRuleContext(createRuleContext("//foo:resolve_me"));
+ ev.exec(
+ "def foo():", // no for loops at top-level
+ " s = 'a'",
+ " for i in range(1,17): s = s + s", // 2**17 > CommandHelper.maxCommandLength (=64000)
+ " return ruleContext.resolve_command(",
+ " command=s)",
+ "argv = foo()[1]");
+ @SuppressWarnings("unchecked")
+ List<String> argv = (List<String>) (List<?>) (StarlarkList) ev.lookup("argv");
+ assertThat(argv).hasSize(2);
+ assertMatches("argv[0]", "^.*/bash" + OsUtils.executableExtension() + "$", argv.get(0));
+ assertMatches("argv[1]", "^.*/resolve_me[.][a-z0-9]+[.]script[.]sh$", argv.get(1));
+ }
+
+ @Test
+ public void testResolveTools() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:resolve_me");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "inputs, input_manifests = ruleContext.resolve_tools(tools=ruleContext.attr.tools)",
+ "ruleContext.actions.run(",
+ " outputs = [ruleContext.actions.declare_file('x.out')],",
+ " inputs = inputs,",
+ " input_manifests = input_manifests,",
+ " executable = 'dummy',",
+ ")");
+ assertArtifactFilenames(
+ ((Depset) ev.lookup("inputs")).getSet(Artifact.class).toList(),
+ "mytool.sh",
+ "mytool",
+ "foo_Smytool" + OsUtils.executableExtension() + "-runfiles",
+ "t.exe");
+ @SuppressWarnings("unchecked")
+ RunfilesSupplier runfilesSupplier =
+ CompositeRunfilesSupplier.fromSuppliers(
+ (List<RunfilesSupplier>) ev.lookup("input_manifests"));
+ assertThat(runfilesSupplier.getMappings()).hasSize(1);
+
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs()))
+ .containsAtLeast(
+ "mytool.sh",
+ "mytool",
+ "foo_Smytool" + OsUtils.executableExtension() + "-runfiles",
+ "t.exe");
+ }
+
+ @Test
+ public void testBadParamTypeErrorMessage() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "got value of type 'int', want 'string or Args'",
+ "ruleContext.actions.write(",
+ " output = ruleContext.files.srcs[0],",
+ " content = 1,",
+ " is_executable = False)");
+ }
+
+ @Test
+ public void testCreateTemplateAction() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.expand_template(",
+ " template = ruleContext.files.srcs[0],",
+ " output = ruleContext.files.srcs[1],",
+ " substitutions = {'a': 'b'},",
+ " is_executable = False)");
+
+ TemplateExpansionAction action = (TemplateExpansionAction) Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getInputs().getSingleton().getExecPathString()).isEqualTo("foo/a.txt");
+ assertThat(Iterables.getOnlyElement(action.getOutputs()).getExecPathString())
+ .isEqualTo("foo/b.img");
+ assertThat(Iterables.getOnlyElement(action.getSubstitutions()).getKey()).isEqualTo("a");
+ assertThat(Iterables.getOnlyElement(action.getSubstitutions()).getValue()).isEqualTo("b");
+ assertThat(action.makeExecutable()).isFalse();
+ }
+
+ /**
+ * Simulates the fact that the Parser currently uses Latin1 to read BUILD files, while users
+ * usually write those files using UTF-8 encoding. Currently, the string-valued 'substitutions'
+ * parameter of the template_action function contains a hack that assumes its input is a UTF-8
+ * encoded string which has been ingested as Latin 1. The hack converts the string to its
+ * "correct" UTF-8 value. Once Blaze starts calling {@link
+ * com.google.devtools.build.lib.syntax.ParserInput#fromUTF8} instead of {@code fromLatin1} and
+ * the hack for the substituations parameter is removed, this test will fail.
+ */
+ @Test
+ public void testCreateTemplateActionWithWrongEncoding() throws Exception {
+ // The following array contains bytes that represent a string of length two when treated as
+ // UTF-8 and a string of length four when treated as ISO-8859-1 (a.k.a. Latin 1).
+ byte[] bytesToDecode = {(byte) 0xC2, (byte) 0xA2, (byte) 0xC2, (byte) 0xA2};
+ Charset latin1 = StandardCharsets.ISO_8859_1;
+ Charset utf8 = StandardCharsets.UTF_8;
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "ruleContext.actions.expand_template(",
+ " template = ruleContext.files.srcs[0],",
+ " output = ruleContext.files.srcs[1],",
+ " substitutions = {'a': '" + new String(bytesToDecode, latin1) + "'},",
+ " is_executable = False)");
+ TemplateExpansionAction action = (TemplateExpansionAction) Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ List<Substitution> substitutions = action.getSubstitutions();
+ assertThat(substitutions).hasSize(1);
+ assertThat(substitutions.get(0).getValue()).isEqualTo(new String(bytesToDecode, utf8));
+ }
+
+ @Test
+ public void testRunfilesAddFromDependencies() throws Exception {
+ setRuleContext(createRuleContext("//foo:bar"));
+ Object result = ev.eval("ruleContext.runfiles(collect_default = True)");
+ assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)))
+ .contains("libjl.jar");
+ }
+
+ @Test
+ public void testRunfilesBadListGenericType() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "at index 0 of files, got element of type string, want File",
+ "ruleContext.runfiles(files = ['some string'])");
+ }
+
+ @Test
+ public void testRunfilesBadSetGenericType() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "got a depset of 'int', expected a depset of 'File'",
+ "ruleContext.runfiles(transitive_files=depset([1, 2, 3]))");
+ }
+
+ @Test
+ public void testRunfilesBadMapGenericType() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "got dict<int, File> for 'symlinks', want dict<string, File>",
+ "ruleContext.runfiles(symlinks = {123: ruleContext.files.srcs[0]})");
+ ev.checkEvalErrorContains(
+ "got dict<string, int> for 'symlinks', want dict<string, File>",
+ "ruleContext.runfiles(symlinks = {'some string': 123})");
+ ev.checkEvalErrorContains(
+ "got dict<int, File> for 'root_symlinks', want dict<string, File>",
+ "ruleContext.runfiles(root_symlinks = {123: ruleContext.files.srcs[0]})");
+ ev.checkEvalErrorContains(
+ "got dict<string, int> for 'root_symlinks', want dict<string, File>",
+ "ruleContext.runfiles(root_symlinks = {'some string': 123})");
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromArtifact() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.runfiles(files = ruleContext.files.tools)");
+ assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result))).contains("t.exe");
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromIterableArtifacts() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.runfiles(files = ruleContext.files.srcs)");
+ assertThat(ImmutableList.of("a.txt", "b.img"))
+ .isEqualTo(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)));
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromNestedSetArtifacts() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result =
+ ev.eval("ruleContext.runfiles(transitive_files = depset(ruleContext.files.srcs))");
+ assertThat(ImmutableList.of("a.txt", "b.img"))
+ .isEqualTo(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)));
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromDefaultAndFiles() throws Exception {
+ setRuleContext(createRuleContext("//foo:bar"));
+ // It would be nice to write [DEFAULT] + ruleContext.files.srcs, but artifacts
+ // is an ImmutableList and Starlark interprets it as a tuple.
+ Object result =
+ ev.eval("ruleContext.runfiles(collect_default = True, files = ruleContext.files.srcs)");
+ // From DEFAULT only libjl.jar comes, see testRunfilesAddFromDependencies().
+ assertThat(ImmutableList.of("libjl.jar", "gl.a", "gl.gcgox"))
+ .isEqualTo(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)));
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromSymlink() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.runfiles(symlinks = {'sym1': ruleContext.files.srcs[0]})");
+ assertThat(ImmutableList.of("a.txt"))
+ .isEqualTo(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)));
+ }
+
+ @Test
+ public void testRunfilesArtifactsFromRootSymlink() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result =
+ ev.eval("ruleContext.runfiles(root_symlinks = {'sym1': ruleContext.files.srcs[0]})");
+ assertThat(ImmutableList.of("a.txt"))
+ .isEqualTo(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)));
+ }
+
+ @Test
+ public void testRunfilesSymlinkConflict() throws Exception {
+ // Two different artifacts mapped to same path in runfiles
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.exec("prefix = ruleContext.workspace_name + '/' if ruleContext.workspace_name else ''");
+ Object result =
+ ev.eval(
+ "ruleContext.runfiles(",
+ " root_symlinks = {prefix + 'sym1': ruleContext.files.srcs[0]},",
+ " symlinks = {'sym1': ruleContext.files.srcs[1]})");
+ Runfiles runfiles = (Runfiles) result;
+ reporter.removeHandler(failFastHandler); // So it doesn't throw an exception.
+ runfiles.getRunfilesInputs(reporter, null);
+ assertContainsEvent("ERROR <no location>: overwrote runfile");
+ }
+
+ private static Iterable<Artifact> getRunfileArtifacts(Object runfiles) {
+ return ((Runfiles) runfiles).getAllArtifacts().toList();
+ }
+
+ @Test
+ public void testRunfilesBadKeywordArguments() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "runfiles() got unexpected keyword argument 'bad_keyword'",
+ "ruleContext.runfiles(bad_keyword = '')");
+ }
+
+ @Test
+ public void testNsetContainsList() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "depset elements must not be mutable values", "depset([[ruleContext.files.srcs]])");
+ }
+
+ @Test
+ public void testCmdJoinPaths() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("cmd_helper.join_paths(':', depset(ruleContext.files.srcs))");
+ assertThat(result).isEqualTo("foo/a.txt:foo/b.img");
+ }
+
+ @Test
+ public void testStructPlusArtifactErrorMessage() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "unsupported binary operation: File + struct",
+ "ruleContext.files.tools[0] + struct(a = 1)");
+ }
+
+ @Test
+ public void testNoSuchProviderErrorMessage() throws Exception {
+ setRuleContext(createRuleContext("//foo:bar"));
+ ev.checkEvalErrorContains(
+ "<target //foo:jl> (rule 'java_library') doesn't have provider 'my_provider'",
+ "ruleContext.attr.srcs[0].my_provider");
+ }
+
+ @Test
+ public void testFilesForRuleConfiguredTarget() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ Object result = ev.eval("ruleContext.attr.srcs[0].files");
+ assertThat(ActionsTestUtil.baseNamesOf(((Depset) result).getSet(Artifact.class)))
+ .isEqualTo("a.txt");
+ }
+
+ @Test
+ public void testDefaultProvider() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " default = DefaultInfo(",
+ " runfiles=ctx.runfiles(ctx.files.runs),",
+ " )",
+ " foo = foo_provider()",
+ " return [foo, default]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'runs': attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " provider = ctx.attr.deps[0][DefaultInfo]",
+ " return [MyInfo(",
+ " is_provided = DefaultInfo in ctx.attr.deps[0],",
+ " provider = provider,",
+ " dir = str(sorted(dir(provider))),",
+ " rule_data_runfiles = provider.data_runfiles,",
+ " rule_default_runfiles = provider.default_runfiles,",
+ " rule_files = provider.files,",
+ " rule_files_to_run = provider.files_to_run,",
+ " rule_file_executable = provider.files_to_run.executable",
+ " )]",
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule', runs = ['run.file', 'run2.file'])",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule', 'file.txt'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ StructImpl myInfo = getMyInfoFromTarget(configuredTarget);
+ assertThat((Boolean) myInfo.getValue("is_provided")).isTrue();
+
+ Object provider = myInfo.getValue("provider");
+ assertThat(provider).isInstanceOf(DefaultInfo.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(DefaultInfo.PROVIDER.getKey());
+
+ assertThat(myInfo.getValue("dir"))
+ .isEqualTo(
+ "[\"data_runfiles\", \"default_runfiles\", \"files\", \"files_to_run\", \"to_json\", "
+ + "\"to_proto\"]");
+
+ assertThat(myInfo.getValue("rule_data_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("rule_data_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .containsExactly(
+ "File:[/workspace[source]]test/run.file", "File:[/workspace[source]]test/run2.file");
+
+ assertThat(myInfo.getValue("rule_default_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("rule_default_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .containsExactly(
+ "File:[/workspace[source]]test/run.file", "File:[/workspace[source]]test/run2.file");
+
+ assertThat(myInfo.getValue("rule_files")).isInstanceOf(Depset.class);
+ assertThat(myInfo.getValue("rule_files_to_run")).isInstanceOf(FilesToRunProvider.class);
+ assertThat(myInfo.getValue("rule_file_executable")).isEqualTo(Starlark.NONE);
+ }
+
+ @Test
+ public void testDefaultProviderInStruct() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " default = DefaultInfo(",
+ " runfiles=ctx.runfiles(ctx.files.runs),",
+ " )",
+ " foo = foo_provider()",
+ " return [foo, default]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'runs': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " provider = ctx.attr.deps[0][DefaultInfo]",
+ " return [MyInfo(",
+ " is_provided = DefaultInfo in ctx.attr.deps[0],",
+ " provider = provider,",
+ " dir = str(sorted(dir(provider))),",
+ " rule_data_runfiles = provider.data_runfiles,",
+ " rule_default_runfiles = provider.default_runfiles,",
+ " rule_files = provider.files,",
+ " rule_files_to_run = provider.files_to_run,",
+ " )]",
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule', runs = ['run.file', 'run2.file'])",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule', 'file.txt'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ StructImpl myInfo = getMyInfoFromTarget(configuredTarget);
+
+ assertThat((Boolean) myInfo.getValue("is_provided")).isTrue();
+
+ Object provider = myInfo.getValue("provider");
+ assertThat(provider).isInstanceOf(DefaultInfo.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(DefaultInfo.PROVIDER.getKey());
+
+ assertThat(myInfo.getValue("dir"))
+ .isEqualTo(
+ "[\"data_runfiles\", \"default_runfiles\", \"files\", \"files_to_run\", \"to_json\", "
+ + "\"to_proto\"]");
+
+ assertThat(myInfo.getValue("rule_data_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("rule_data_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .containsExactly(
+ "File:[/workspace[source]]test/run.file", "File:[/workspace[source]]test/run2.file");
+
+ assertThat(myInfo.getValue("rule_default_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("rule_default_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .containsExactly(
+ "File:[/workspace[source]]test/run.file", "File:[/workspace[source]]test/run2.file");
+
+ assertThat(myInfo.getValue("rule_files")).isInstanceOf(Depset.class);
+ assertThat(myInfo.getValue("rule_files_to_run")).isInstanceOf(FilesToRunProvider.class);
+ }
+
+ @Test
+ public void testDefaultProviderInvalidConfiguration() throws Exception {
+ setStarlarkSemanticsOptions("--incompatible_disallow_struct_provider_syntax=false");
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " default = DefaultInfo(",
+ " runfiles=ctx.runfiles(ctx.files.runs),",
+ " )",
+ " foo = foo_provider()",
+ " return struct(providers=[foo, default], files=depset([]))",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'runs': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "foo_rule(name = 'my_rule', runs = ['run.file', 'run2.file'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "Provider 'files' should be specified in DefaultInfo "
+ + "if it's provided explicitly.");
+ }
+
+ @Test
+ public void testDefaultProviderOnFileTarget() throws Exception {
+ scratch.file(
+ "test/bar.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " provider = ctx.attr.deps[0][DefaultInfo]",
+ " return [MyInfo(",
+ " is_provided = DefaultInfo in ctx.attr.deps[0],",
+ " provider = provider,",
+ " dir = str(sorted(dir(provider))),",
+ " file_data_runfiles = provider.data_runfiles,",
+ " file_default_runfiles = provider.default_runfiles,",
+ " file_files = provider.files,",
+ " file_files_to_run = provider.files_to_run,",
+ " )]",
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':bar.bzl', 'bar_rule')",
+ "bar_rule(name = 'my_rule', deps = ['file.txt'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ StructImpl myInfo = getMyInfoFromTarget(configuredTarget);
+
+ assertThat((Boolean) myInfo.getValue("is_provided")).isTrue();
+
+ Object provider = myInfo.getValue("provider");
+ assertThat(provider).isInstanceOf(DefaultInfo.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(DefaultInfo.PROVIDER.getKey());
+
+ assertThat(myInfo.getValue("dir"))
+ .isEqualTo(
+ "[\"data_runfiles\", \"default_runfiles\", \"files\", \"files_to_run\", \"to_json\", "
+ + "\"to_proto\"]");
+
+ assertThat(myInfo.getValue("file_data_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("file_data_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .isEmpty();
+
+ assertThat(myInfo.getValue("file_default_runfiles")).isInstanceOf(Runfiles.class);
+ assertThat(
+ Iterables.transform(
+ ((Runfiles) myInfo.getValue("file_default_runfiles")).getAllArtifacts().toList(),
+ String::valueOf))
+ .isEmpty();
+
+ assertThat(myInfo.getValue("file_files")).isInstanceOf(Depset.class);
+ assertThat(myInfo.getValue("file_files_to_run")).isInstanceOf(FilesToRunProvider.class);
+ }
+
+ @Test
+ public void testDefaultProviderProvidedImplicitly() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " foo = foo_provider()",
+ " return [foo]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep[DefaultInfo]", // The goal is to test this object
+ " return [MyInfo(", // so we return it here
+ " default = provider,",
+ " )]",
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ Object provider = getMyInfoFromTarget(configuredTarget).getValue("default");
+ assertThat(provider).isInstanceOf(DefaultInfo.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(DefaultInfo.PROVIDER.getKey());
+ }
+
+ @Test
+ public void testDefaultProviderUnknownFields() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " default = DefaultInfo(",
+ " foo=ctx.runfiles(),",
+ " )",
+ " return [default]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "foo_rule(name = 'my_rule')"
+ );
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("DefaultInfo() got unexpected keyword argument 'foo'");
+ }
+
+ @Test
+ public void testDeclaredProviders() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "foobar_provider = provider()",
+ "def _impl(ctx):",
+ " foo = foo_provider()",
+ " foobar = foobar_provider()",
+ " return [foo, foobar]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep[foo_provider]", // The goal is to test this object
+ " return [MyInfo(proxy = provider)]", // so we return it here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ Object provider = getMyInfoFromTarget(configuredTarget).getValue("proxy");
+ assertThat(provider).isInstanceOf(StructImpl.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:foo.bzl", ImmutableMap.of()), "foo_provider"));
+ }
+
+ @Test
+ public void testAdvertisedProviders() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "FooInfo = provider()",
+ "BarInfo = provider()",
+ "def _impl(ctx):",
+ " foo = FooInfo()",
+ " bar = BarInfo()",
+ " return [foo, bar]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " provides = [FooInfo, BarInfo]",
+ ")");
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'FooInfo')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " proxy = dep[FooInfo]", // The goal is to test this object
+ " return [MyInfo(proxy = proxy)]", // so we return it here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ Object provider = getMyInfoFromTarget(configuredTarget).getValue("proxy");
+ assertThat(provider).isInstanceOf(StructImpl.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:foo.bzl", ImmutableMap.of()), "FooInfo"));
+ }
+
+ @Test
+ public void testLacksAdvertisedDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "FooInfo = provider()",
+ "def _impl(ctx):",
+ " default = DefaultInfo(",
+ " runfiles=ctx.runfiles(ctx.files.runs),",
+ " )",
+ " return [default]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'runs': attr.label_list(allow_files=True),",
+ " },",
+ " provides = [FooInfo, DefaultInfo]",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "foo_rule(name = 'my_rule', runs = ['run.file', 'run2.file'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("rule advertised the 'FooInfo' provider, "
+ + "but this provider was not among those returned");
+ }
+
+ @Test
+ public void testLacksAdvertisedNativeProvider() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "FooInfo = provider()",
+ "def _impl(ctx):",
+ " MyFooInfo = FooInfo()",
+ " return [MyFooInfo]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " provides = [FooInfo, JavaInfo]",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "foo_rule(name = 'my_rule')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("rule advertised the 'JavaInfo' provider, "
+ + "but this provider was not among those returned");
+ }
+
+ @Test
+ public void testBadlySpecifiedProvides() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " provides = [1]",
+ ")");
+ scratch.file("test/BUILD", "load(':foo.bzl', 'foo_rule')", "foo_rule(name = 'my_rule')");
+
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "element in 'provides' is of unexpected type. "
+ + "Should be list of providers, but got item of type int");
+ }
+
+ @Test
+ public void testSingleDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " return foo_provider(a=123)",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep[foo_provider]", // The goal is to test this object
+ " return [MyInfo(proxy = provider)]", // so we return it here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ Object provider = getMyInfoFromTarget(configuredTarget).getValue("proxy");
+ assertThat(provider).isInstanceOf(StructImpl.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:foo.bzl", ImmutableMap.of()), "foo_provider"));
+ assertThat(((StructImpl) provider).getValue("a")).isEqualTo(123);
+ }
+
+ @Test
+ public void testDeclaredProvidersAliasTarget() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "foobar_provider = provider()",
+ "def _impl(ctx):",
+ " foo = foo_provider()",
+ " foobar = foobar_provider()",
+ " return [foo, foobar]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'foo_provider')",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep[foo_provider]", // The goal is to test this object
+ " return [MyInfo(proxy = provider)]", // so we return it here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'foo_rule')",
+ "alias(name = 'dep_rule', actual=':foo_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ Object provider = getMyInfoFromTarget(configuredTarget).getValue("proxy");
+ assertThat(provider).isInstanceOf(StructImpl.class);
+ assertThat(((StructImpl) provider).getProvider().getKey())
+ .isEqualTo(
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:foo.bzl", ImmutableMap.of()), "foo_provider"));
+ }
+
+ @Test
+ public void testDeclaredProvidersWrongKey() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "unused_provider = provider()",
+ "def _impl(ctx):",
+ " foo = foo_provider()",
+ " return [foo]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "load(':foo.bzl', 'unused_provider')",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep[unused_provider]", // Should throw an error here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "<target //test:dep_rule> (rule 'foo_rule') doesn't contain "
+ + "declared provider 'unused_provider'");
+ }
+
+ @Test
+ public void testDeclaredProvidersInvalidKey() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "def _impl(ctx):",
+ " foo = foo_provider()",
+ " return [foo]",
+ "foo_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/bar.bzl",
+ "def _impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " provider = dep['foo_provider']", // Should throw an error here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " 'deps': attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'foo_rule')",
+ "load(':bar.bzl', 'bar_rule')",
+ "foo_rule(name = 'dep_rule')",
+ "bar_rule(name = 'my_rule', deps = [':dep_rule'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Type Target only supports indexing by object constructors, got string instead");
+ }
+
+ @Test
+ public void testDeclaredProvidersFileTarget() throws Exception {
+ scratch.file(
+ "test/bar.bzl",
+ "unused_provider = provider()",
+ "def _impl(ctx):",
+ " src = ctx.attr.srcs[0]",
+ " provider = src[unused_provider]", // Should throw an error here
+ "bar_rule = rule(",
+ " implementation = _impl,",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':bar.bzl', 'bar_rule')",
+ "bar_rule(name = 'my_rule', srcs = ['input.txt'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "<input file target //test:input.txt> doesn't contain "
+ + "declared provider 'unused_provider'");
+ }
+
+ @Test
+ public void testDeclaredProvidersInOperator() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "foo_provider = provider()",
+ "bar_provider = provider()",
+ "",
+ "def _inner_impl(ctx):",
+ " foo = foo_provider()",
+ " return [foo]",
+ "inner_rule = rule(",
+ " implementation = _inner_impl,",
+ ")",
+ "",
+ "def _outer_impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " return [MyInfo(",
+ " foo = (foo_provider in dep),", // Should be true
+ " bar = (bar_provider in dep),", // Should be false
+ " )]",
+ "outer_rule = rule(",
+ " implementation = _outer_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(),",
+ " }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'inner_rule', 'outer_rule')",
+ "inner_rule(name = 'dep_rule')",
+ "outer_rule(name = 'my_rule', deps = [':dep_rule'])");
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule");
+ StructImpl myInfo = getMyInfoFromTarget(configuredTarget);
+
+ Object foo = myInfo.getValue("foo");
+ assertThat(foo).isInstanceOf(Boolean.class);
+ assertThat((Boolean) foo).isTrue();
+ Object bar = myInfo.getValue("bar");
+ assertThat(bar).isInstanceOf(Boolean.class);
+ assertThat((Boolean) bar).isFalse();
+ }
+
+ @Test
+ public void testDeclaredProvidersInOperatorInvalidKey() throws Exception {
+ scratch.file(
+ "test/foo.bzl",
+ "foo_provider = provider()",
+ "bar_provider = provider()",
+ "",
+ "def _inner_impl(ctx):",
+ " foo = foo_provider()",
+ " return [foo]",
+ "inner_rule = rule(",
+ " implementation = _inner_impl,",
+ ")",
+ "",
+ "def _outer_impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " 'foo_provider' in dep", // Should throw an error here
+ "outer_rule = rule(",
+ " implementation = _outer_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':foo.bzl', 'inner_rule', 'outer_rule')",
+ "inner_rule(name = 'dep_rule')",
+ "outer_rule(name = 'my_rule', deps = [':dep_rule'])");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Type Target only supports querying by object constructors, got string instead");
+ }
+
+ @Test
+ public void testReturnNonExportedProvider() throws Exception {
+ scratch.file(
+ "test/my_rule.bzl",
+ "def _rule_impl(ctx):",
+ " foo_provider = provider()",
+ " foo = foo_provider()",
+ " return [foo]",
+ "",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ ")");
+ scratch.file("test/BUILD", "load(':my_rule.bzl', 'my_rule')", "my_rule(name = 'my_rule')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "cannot return a non-exported provider instance from a rule implementation function.");
+ }
+
+ @Test
+ public void testFilesForFileConfiguredTarget() throws Exception {
+ setRuleContext(createRuleContext("//foo:bar"));
+ Object result = ev.eval("ruleContext.attr.srcs[0].files");
+ assertThat(ActionsTestUtil.baseNamesOf(((Depset) result).getSet(Artifact.class)))
+ .isEqualTo("libjl.jar");
+ }
+
+ @Test
+ public void testCtxStructFieldsCustomErrorMessages() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains("No attribute 'foo' in attr.", "ruleContext.attr.foo");
+ ev.checkEvalErrorContains("No attribute 'foo' in outputs.", "ruleContext.outputs.foo");
+ ev.checkEvalErrorContains("No attribute 'foo' in files.", "ruleContext.files.foo");
+ ev.checkEvalErrorContains("No attribute 'foo' in file.", "ruleContext.file.foo");
+ ev.checkEvalErrorContains("No attribute 'foo' in executable.", "ruleContext.executable.foo");
+ }
+
+ @Test
+ public void testBinDirPath() throws Exception {
+ StarlarkRuleContext ctx = createRuleContext("//foo:bar");
+ setRuleContext(ctx);
+ Object result = ev.eval("ruleContext.bin_dir.path");
+ assertThat(result).isEqualTo(ctx.getConfiguration().getBinFragment().getPathString());
+ }
+
+ @Test
+ public void testEmptyLabelListTypeAttrInCtx() throws Exception {
+ setRuleContext(createRuleContext("//foo:baz"));
+ Object result = ev.eval("ruleContext.attr.srcs");
+ assertThat(result).isEqualTo(StarlarkList.empty());
+ }
+
+ @Test
+ public void testDefinedMakeVariable() throws Exception {
+ useConfiguration("--define=FOO=bar");
+ setRuleContext(createRuleContext("//foo:baz"));
+ String foo = (String) ev.eval("ruleContext.var['FOO']");
+ assertThat(foo).isEqualTo("bar");
+ }
+
+ @Test
+ public void testCodeCoverageConfigurationAccess() throws Exception {
+ StarlarkRuleContext ctx = createRuleContext("//foo:baz");
+ setRuleContext(ctx);
+ boolean coverage = (Boolean) ev.eval("ruleContext.configuration.coverage_enabled");
+ assertThat(ctx.getRuleContext().getConfiguration().isCodeCoverageEnabled()).isEqualTo(coverage);
+ }
+
+ /** Checks whether the given (invalid) statement leads to the expected error */
+ private void checkReportedErrorStartsWith(String errorMsg, String... statements)
+ throws Exception {
+ // If the component under test relies on Reporter and EventCollector for error handling, any
+ // error would lead to an asynchronous AssertionFailedError thanks to failFastHandler in
+ // FoundationTestCase.
+ //
+ // Consequently, we disable failFastHandler and check all events for the expected error message
+ reporter.removeHandler(failFastHandler);
+
+ Object result = ev.eval(statements);
+
+ String first = null;
+ int count = 0;
+
+ try {
+ for (Event evt : eventCollector) {
+ if (evt.getMessage().startsWith(errorMsg)) {
+ return;
+ }
+
+ ++count;
+ first = evt.getMessage();
+ }
+
+ if (count == 0) {
+ fail(
+ String.format(
+ "checkReportedErrorStartsWith(): There was no error; the result is '%s'", result));
+ } else {
+ fail(
+ String.format(
+ "Found %d error(s), but none with the expected message '%s'. First error: '%s'",
+ count, errorMsg, first));
+ }
+ } finally {
+ eventCollector.clear();
+ }
+ }
+
+ @StarlarkMethod(name = "throw1", documented = false)
+ public Object throw1() throws Exception {
+ class ThereIsNoMessageException extends EvalException {
+ ThereIsNoMessageException() {
+ super(null, "This is not the message you are looking for."); // Unused dummy message
+ }
+
+ @Override
+ public String getMessage() {
+ return "";
+ }
+ }
+ throw new ThereIsNoMessageException();
+ }
+
+ @Test
+ public void testStackTraceWithoutOriginalMessage() throws Exception {
+ defineTestMethods();
+ ev.checkEvalErrorContains(
+ "There Is No Message: StarlarkRuleImplementationFunctionsTest", "throw1()");
+ }
+
+ @StarlarkMethod(name = "throw2", documented = false)
+ public Object throw2() throws Exception {
+ throw new InterruptedException();
+ }
+
+ @Test
+ public void testNoStackTraceOnInterrupt() throws Exception {
+ defineTestMethods();
+ assertThrows(InterruptedException.class, () -> ev.eval("throw2()"));
+ }
+
+ @Test
+ public void testGlobInImplicitOutputs() throws Exception {
+ scratch.file(
+ "test/glob.bzl",
+ "def _impl(ctx):",
+ " ctx.actions.do_nothing(",
+ " inputs = [],",
+ " )",
+ "def _foo():",
+ " return native.glob(['*'])",
+ "glob_rule = rule(",
+ " implementation = _impl,",
+ " outputs = _foo,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:glob.bzl', 'glob_rule')",
+ "glob_rule(name = 'my_glob',",
+ " srcs = ['foo.bar', 'other_foo.bar'])");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:my_glob");
+ assertContainsEvent("The native module can be accessed only from a BUILD thread.");
+ }
+
+ @Test
+ public void testRuleFromBzlFile() throws Exception {
+ scratch.file("test/rule.bzl", "def _impl(ctx): return", "foo = rule(implementation = _impl)");
+ scratch.file("test/ext.bzl", "load('//test:rule.bzl', 'foo')", "a = 1", "foo(name = 'x')");
+ scratch.file("test/BUILD", "load('//test:ext.bzl', 'a')");
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:x");
+ assertContainsEvent("Cannot instantiate a rule when loading a .bzl file");
+ }
+
+ @Test
+ public void testImplicitOutputsFromGlob() throws Exception {
+ scratch.file(
+ "test/glob.bzl",
+ "def _impl(ctx):",
+ " outs = ctx.outputs",
+ " for i in ctx.attr.srcs:",
+ " o = getattr(outs, 'foo_' + i.label.name)",
+ " ctx.actions.write(",
+ " output = o,",
+ " content = 'hoho')",
+ "",
+ "def _foo(srcs):",
+ " outs = {}",
+ " for i in srcs:",
+ " outs['foo_' + i.name] = i.name + '.out'",
+ " return outs",
+ "",
+ "glob_rule = rule(",
+ " attrs = {",
+ " 'srcs': attr.label_list(allow_files = True),",
+ " },",
+ " outputs = _foo,",
+ " implementation = _impl,",
+ ")");
+ scratch.file("test/a.bar", "a");
+ scratch.file("test/b.bar", "b");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:glob.bzl', 'glob_rule')",
+ "glob_rule(name = 'my_glob', srcs = glob(['*.bar']))");
+ ConfiguredTarget ct = getConfiguredTarget("//test:my_glob");
+ assertThat(ct).isNotNull();
+ assertThat(getGeneratingAction(getBinArtifact("a.bar.out", ct))).isNotNull();
+ assertThat(getGeneratingAction(getBinArtifact("b.bar.out", ct))).isNotNull();
+ }
+
+ @Test
+ public void testBuiltInFunctionAsRuleImplementation() throws Exception {
+ // Using built-in functions as rule implementations shouldn't cause runtime errors
+ scratch.file(
+ "test/rule.bzl",
+ "silly_rule = rule(",
+ " implementation = int,",
+ " attrs = {",
+ " \"srcs\": attr.label_list(allow_files=True),",
+ " }",
+ ")"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'silly_rule')",
+ "silly_rule(name = 'silly')");
+ thrown.handleAssertionErrors(); // Compatibility with JUnit 4.11
+ thrown.expect(AssertionError.class);
+ // This confusing message shows why we should distinguish
+ // built-ins and Starlark functions in their repr strings.
+ thrown.expectMessage(
+ "in call to rule(), parameter 'implementation' got value of type 'function', want"
+ + " 'function'");
+ getConfiguredTarget("//test:silly");
+ }
+
+ @Test
+ public void testArgsScalarAdd() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "args.add('--foo')",
+ "args.add('-')",
+ "args.add('foo', format='format%s')",
+ "args.add('-')",
+ "args.add('--foo', 'val')",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly("foo/t.exe", "--foo", "-", "formatfoo", "-", "--foo", "val")
+ .inOrder();
+ }
+
+ @Test
+ public void testArgsScalarAddThrowsWithVectorArg() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "Args.add() doesn't accept vectorized arguments",
+ "args = ruleContext.actions.args()",
+ "args.add([1, 2])",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ }
+
+ @Test
+ public void testArgsAddAll() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "args.add_all([1, 2])",
+ "args.add('-')",
+ "args.add_all('--foo', [1, 2])",
+ "args.add('-')",
+ "args.add_all([1, 2], before_each='-before')",
+ "args.add('-')",
+ "args.add_all([1, 2], format_each='format/%s')",
+ "args.add('-')",
+ "args.add_all(ruleContext.files.srcs)",
+ "args.add('-')",
+ "args.add_all(ruleContext.files.srcs, format_each='format/%s')",
+ "args.add('-')",
+ "args.add_all([1, 2], terminate_with='--terminator')",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly(
+ "foo/t.exe",
+ "1",
+ "2",
+ "-",
+ "--foo",
+ "1",
+ "2",
+ "-",
+ "-before",
+ "1",
+ "-before",
+ "2",
+ "-",
+ "format/1",
+ "format/2",
+ "-",
+ "foo/a.txt",
+ "foo/b.img",
+ "-",
+ "format/foo/a.txt",
+ "format/foo/b.img",
+ "-",
+ "1",
+ "2",
+ "--terminator")
+ .inOrder();
+ }
+
+ @Test
+ public void testArgsAddAllWithMapEach() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "def add_one(val): return str(val + 1)",
+ "def expand_to_many(val): return ['hey', 'hey']",
+ "args = ruleContext.actions.args()",
+ "args.add_all([1, 2], map_each=add_one)",
+ "args.add('-')",
+ "args.add_all([1, 2], map_each=expand_to_many)",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly("foo/t.exe", "2", "3", "-", "hey", "hey", "hey", "hey")
+ .inOrder();
+ }
+
+ @Test
+ public void testOmitIfEmpty() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "def add_one(val): return str(val + 1)",
+ "def filter(val): return None",
+ "args = ruleContext.actions.args()",
+ "args.add_joined([], join_with=',')",
+ "args.add('-')",
+ "args.add_joined([], join_with=',', omit_if_empty=False)",
+ "args.add('-')",
+ "args.add_all('--foo', [])",
+ "args.add('-')",
+ "args.add_all('--foo', [], omit_if_empty=False)",
+ "args.add('-')",
+ "args.add_all('--foo', [1], map_each=filter, terminate_with='hello')",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly(
+ "foo/t.exe",
+ // Nothing
+ "-",
+ "", // Empty string was joined and added
+ "-",
+ // Nothing
+ "-",
+ "--foo", // Arg added regardless
+ "-"
+ // Nothing, all values were filtered
+ )
+ .inOrder();
+ }
+
+ @Test
+ public void testUniquify() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "def add_one(val): return str(val + 1)",
+ "args = ruleContext.actions.args()",
+ "args.add_all(['a', 'b', 'a'])",
+ "args.add('-')",
+ "args.add_all(['a', 'b', 'a', 'c', 'b'], uniquify=True)",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly("foo/t.exe", "a", "b", "a", "-", "a", "b", "c")
+ .inOrder();
+ }
+
+ @Test
+ public void testArgsAddJoined() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "def add_one(val): return str(val + 1)",
+ "args = ruleContext.actions.args()",
+ "args.add_joined([1, 2], join_with=':')",
+ "args.add('-')",
+ "args.add_joined([1, 2], join_with=':', format_each='format/%s')",
+ "args.add('-')",
+ "args.add_joined([1, 2], join_with=':', format_each='format/%s', format_joined='--foo=%s')",
+ "args.add('-')",
+ "args.add_joined([1, 2], join_with=':', map_each=add_one)",
+ "args.add('-')",
+ "args.add_joined(ruleContext.files.srcs, join_with=':')",
+ "args.add('-')",
+ "args.add_joined(ruleContext.files.srcs, join_with=':', format_each='format/%s')",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly(
+ "foo/t.exe",
+ "1:2",
+ "-",
+ "format/1:format/2",
+ "-",
+ "--foo=format/1:format/2",
+ "-",
+ "2:3",
+ "-",
+ "foo/a.txt:foo/b.img",
+ "-",
+ "format/foo/a.txt:format/foo/b.img")
+ .inOrder();
+ }
+
+ @Test
+ public void testMultipleLazyArgsMixedWithStrings() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "foo_args = ruleContext.actions.args()",
+ "foo_args.add('--foo')",
+ "bar_args = ruleContext.actions.args()",
+ "bar_args.add('--bar')",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = ['hello', foo_args, 'world', bar_args, 'works'],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments())
+ .containsExactly("foo/t.exe", "hello", "--foo", "world", "--bar", "works")
+ .inOrder();
+ }
+
+ @Test
+ public void testLazyArgsWithParamFile() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "def _impl(ctx):",
+ " args = ctx.actions.args()",
+ " args.add('--foo')",
+ " args.use_param_file('--file=%s', use_always=True)",
+ " output=ctx.actions.declare_file('out')",
+ " ctx.actions.run_shell(",
+ " inputs = [output],",
+ " outputs = [output],",
+ " arguments = [args],",
+ " command = 'touch out',",
+ " )",
+ "main_rule = rule(implementation = _impl)");
+ scratch.file(
+ "test/BUILD", "load('//test:main_rule.bzl', 'main_rule')", "main_rule(name='main')");
+ ConfiguredTarget ct = getConfiguredTarget("//test:main");
+ Artifact output = getBinArtifact("out", ct);
+ SpawnAction action = (SpawnAction) getGeneratingAction(output);
+ assertThat(paramFileArgsForAction(action)).containsExactly("--foo");
+ }
+
+ @Test
+ public void testWriteArgsToParamFile() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "args.add('--foo')",
+ "output=ruleContext.actions.declare_file('out')",
+ "ruleContext.actions.write(",
+ " output=output,",
+ " content=args,",
+ ")");
+ List<ActionAnalysisMetadata> actions =
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions();
+ Optional<ActionAnalysisMetadata> action =
+ actions.stream().filter(a -> a instanceof ParameterFileWriteAction).findFirst();
+ assertThat(action.isPresent()).isTrue();
+ ParameterFileWriteAction paramAction = (ParameterFileWriteAction) action.get();
+ assertThat(paramAction.getArguments()).containsExactly("--foo");
+ }
+
+ @Test
+ public void testLazyArgsWithParamFileInvalidFormatString() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "Invalid value for parameter \"param_file_arg\": Expected string with a single \"--file=\"",
+ "args = ruleContext.actions.args()\n" + "args.use_param_file('--file=')");
+ ev.checkEvalErrorContains(
+ "Invalid value for parameter \"param_file_arg\": "
+ + "Expected string with a single \"--file=%s%s\"",
+ "args = ruleContext.actions.args()\n" + "args.use_param_file('--file=%s%s')");
+ }
+
+ @Test
+ public void testLazyArgsWithParamFileInvalidFormat() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "Invalid value for parameter \"format\": Expected one of \"shell\", \"multiline\"",
+ "args = ruleContext.actions.args()\n" + "args.set_param_file_format('illegal')");
+ }
+
+ @Test
+ public void testArgsAddInvalidTypesForArgAndValues() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "expected value of type 'string' for arg name, got 'Integer'",
+ "args = ruleContext.actions.args()",
+ "args.add(1, 'value')");
+ ev.checkEvalErrorContains(
+ "expected value of type 'string' for arg name, got 'Integer'",
+ "args = ruleContext.actions.args()",
+ "args.add_all(1, [1, 2])");
+ ev.checkEvalErrorContains(
+ "expected value of type 'sequence or depset' for values, got 'Integer'",
+ "args = ruleContext.actions.args()",
+ "args.add_all(1)");
+ ev.checkEvalErrorContains(
+ "in call to add_all(), parameter 'values' got value of type 'int', want 'sequence or"
+ + " depset'",
+ "args = ruleContext.actions.args()",
+ "args.add_all('--foo', 1)");
+ }
+
+ @Test
+ public void testLazyArgIllegalFormatString() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "Invalid value for parameter \"format\": Expected string with a single \"%s\"",
+ "args = ruleContext.actions.args()",
+ "args.add('foo', format='illegal_format')", // Expects two args, will only be given one
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ }
+
+ @Test
+ public void testMapEachAcceptsBuiltinFunction() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ // map_each accepts a non-Starlark built-in function such as str.
+ ev.exec("ruleContext.actions.args().add_all(['foo'], map_each = str)");
+ }
+
+ @Test
+ public void testLazyArgMapEachThrowsError() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "def bad_fn(val): 'hello'.nosuchmethod()",
+ "args.add_all([1, 2], map_each=bad_fn)",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ CommandLineExpansionException e =
+ assertThrows(CommandLineExpansionException.class, () -> action.getArguments());
+ assertThat(e).hasMessageThat().contains("'string' value has no field or method 'nosuchmethod'");
+ }
+
+ @Test
+ public void testLazyArgMapEachReturnsNone() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "def none_fn(val): return None if val == 'nokeep' else val",
+ "args.add_all(['keep', 'nokeep'], map_each=none_fn)",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ assertThat(action.getArguments()).containsExactly("foo/t.exe", "keep").inOrder();
+ }
+
+ @Test
+ public void testLazyArgMapEachReturnsWrongType() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "def bad_fn(val): return 1",
+ "args.add_all([1, 2], map_each=bad_fn)",
+ "ruleContext.actions.run(",
+ " inputs = depset(ruleContext.files.srcs),",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " executable = ruleContext.files.tools[0],",
+ ")");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ CommandLineExpansionException e =
+ assertThrows(CommandLineExpansionException.class, () -> action.getArguments());
+ assertThat(e.getMessage())
+ .contains("Expected map_each to return string, None, or list of strings, found Integer");
+ }
+
+ @Test
+ public void createShellWithLazyArgs() throws Exception {
+ StarlarkRuleContext ruleContext = createRuleContext("//foo:foo");
+ setRuleContext(ruleContext);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "args.add('--foo')",
+ "ruleContext.actions.run_shell(",
+ " inputs = ruleContext.files.srcs,",
+ " outputs = ruleContext.files.srcs,",
+ " arguments = [args],",
+ " mnemonic = 'DummyMnemonic',",
+ " command = 'dummy_command',",
+ " progress_message = 'dummy_message',",
+ " use_default_shell_env = True)");
+ SpawnAction action =
+ (SpawnAction)
+ Iterables.getOnlyElement(
+ ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+ List<String> args = action.getArguments();
+ // We don't need to assert the entire arg list, just check that
+ // the dummy empty string is inserted followed by '--foo'
+ assertThat(args.get(args.size() - 2)).isEmpty();
+ assertThat(Iterables.getLast(args)).isEqualTo("--foo");
+ }
+
+ @Test
+ public void testLazyArgsObjectImmutability() throws Exception {
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'main_rule', 'dep_rule')",
+ "dep_rule(name = 'dep')",
+ "main_rule(name = 'main', deps = [':dep'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _main_impl(ctx):",
+ " dep = ctx.attr.deps[0]",
+ " args = dep[MyInfo].dep_arg",
+ " args.add('hello')",
+ "main_rule = rule(",
+ " implementation = _main_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list()",
+ " },",
+ " outputs = {'file': 'output.txt'},",
+ ")",
+ "def _dep_impl(ctx):",
+ " args = ctx.actions.args()",
+ " return [MyInfo(dep_arg = args)]",
+ "dep_rule = rule(implementation = _dep_impl)");
+ AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+ assertThat(e).hasMessageThat().contains("trying to mutate a frozen Args value");
+ }
+
+ @Test
+ public void testConfigurationField_StarlarkSplitTransitionProhibited() throws Exception {
+ scratch.file(
+ "tools/allowlists/function_transition_allowlist/BUILD",
+ "package_group(",
+ " name = 'function_transition_allowlist',",
+ " packages = [",
+ " '//...',",
+ " ],",
+ ")");
+
+ scratch.file(
+ "test/rule.bzl",
+ "def _foo_impl(ctx):",
+ " return []",
+ "",
+ "def _foo_transition_impl(settings):",
+ " return {'t1': {}, 't2': {}}",
+ "foo_transition = transition(implementation=_foo_transition_impl, inputs=[], outputs=[])",
+ "",
+ "foo = rule(",
+ " implementation = _foo_impl,",
+ " attrs = {",
+ " '_allowlist_function_transition': attr.label(",
+ " default = '//tools/allowlists/function_transition_allowlist'),",
+ " '_attr': attr.label(",
+ " cfg = foo_transition,",
+ " default = configuration_field(fragment='cpp', name = 'cc_toolchain'))})");
+
+ scratch.file("test/BUILD", "load('//test:rule.bzl', 'foo')", "foo(name='foo')");
+
+ setStarlarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:foo");
+ assertContainsEvent("late-bound attributes must not have a split configuration transition");
+ }
+
+ @Test
+ public void testConfigurationField_NativeSplitTransitionProviderProhibited() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _foo_impl(ctx):",
+ " return []",
+ "",
+ "foo = rule(",
+ " implementation = _foo_impl,",
+ " attrs = {",
+ " '_attr': attr.label(",
+ " cfg = apple_common.multi_arch_split,",
+ " default = configuration_field(fragment='cpp', name = 'cc_toolchain'))})");
+
+ scratch.file("test/BUILD", "load('//test:rule.bzl', 'foo')", "foo(name='foo')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:foo");
+ assertContainsEvent("late-bound attributes must not have a split configuration transition");
+ }
+
+ @Test
+ public void testConfigurationField_NativeSplitTransitionProhibited() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def _foo_impl(ctx):",
+ " return []",
+ "",
+ "foo = rule(",
+ " implementation = _foo_impl,",
+ " attrs = {",
+ " '_attr': attr.label(",
+ " cfg = android_common.multi_cpu_configuration,",
+ " default = configuration_field(fragment='cpp', name = 'cc_toolchain'))})");
+ setStarlarkSemanticsOptions("--experimental_google_legacy_api");
+
+ scratch.file("test/BUILD", "load('//test:rule.bzl', 'foo')", "foo(name='foo')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:foo");
+ assertContainsEvent("late-bound attributes must not have a split configuration transition");
+ }
+
+ @Test
+ public void testConfigurationField_invalidFragment() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "main_rule = rule(implementation = _impl,",
+ " attrs = { '_myattr': attr.label(",
+ " default = configuration_field(",
+ " fragment = 'notarealfragment', name = 'method_name')),",
+ " },",
+ ")");
+
+ scratch.file("test/BUILD",
+ "load('//test:main_rule.bzl', 'main_rule')",
+ "main_rule(name='main')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+ assertThat(expected).hasMessageThat()
+ .contains("invalid configuration fragment name 'notarealfragment'");
+ }
+
+ @Test
+ public void testConfigurationField_doesNotChangeFragmentAccess() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "load('//myinfo:myinfo.bzl', 'MyInfo')",
+ "def _impl(ctx):",
+ " return [MyInfo(platform = ctx.fragments.apple.single_arch_platform)]",
+ "main_rule = rule(implementation = _impl,",
+ " attrs = { '_myattr': attr.label(",
+ " default = configuration_field(",
+ " fragment = 'apple', name = 'xcode_config_label')),",
+ " },",
+ " fragments = [],",
+ ")");
+
+ scratch.file("test/BUILD",
+ "load('//test:main_rule.bzl', 'main_rule')",
+ "main_rule(name='main')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+
+ assertThat(expected).hasMessageThat()
+ .contains("has to declare 'apple' as a required fragment in target configuration");
+ }
+
+ @Test
+ public void testConfigurationField_invalidFieldName() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "main_rule = rule(implementation = _impl,",
+ " attrs = { '_myattr': attr.label(",
+ " default = configuration_field(",
+ " fragment = 'apple', name = 'notarealfield')),",
+ " },",
+ " fragments = ['apple'],",
+ ")");
+
+ scratch.file("test/BUILD",
+ "load('//test:main_rule.bzl', 'main_rule')",
+ "main_rule(name='main')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+
+ assertThat(expected).hasMessageThat()
+ .contains("invalid configuration field name 'notarealfield' on fragment 'apple'");
+ }
+
+ // Verifies that configuration_field can only be used on 'private' attributes.
+ @Test
+ public void testConfigurationField_invalidVisibility() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "main_rule = rule(implementation = _impl,",
+ " attrs = { 'myattr': attr.label(",
+ " default = configuration_field(",
+ " fragment = 'apple', name = 'xcode_config_label')),",
+ " },",
+ " fragments = ['apple'],",
+ ")");
+
+ scratch.file("test/BUILD",
+ "load('//test:main_rule.bzl', 'main_rule')",
+ "main_rule(name='main')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+
+ assertThat(expected).hasMessageThat()
+ .contains("When an attribute value is a function, "
+ + "the attribute must be private (i.e. start with '_')");
+ }
+
+ @Test
+ public void testFilesToRunInActionsRun() throws Exception {
+ scratch.file(
+ "a/a.bzl",
+ "def _impl(ctx):",
+ " f = ctx.actions.declare_file('output')",
+ " ctx.actions.run(",
+ " inputs = [],",
+ " outputs = [f],",
+ " executable = ctx.attr._tool[DefaultInfo].files_to_run)",
+ " return [DefaultInfo(files=depset([f]))]",
+ "r = rule(implementation=_impl, attrs = {'_tool': attr.label(default='//a:tool')})");
+
+ scratch.file(
+ "a/BUILD",
+ "load(':a.bzl', 'r')",
+ "r(name='r')",
+ "sh_binary(name='tool', srcs=['tool.sh'], data=['data'])");
+
+ ConfiguredTarget r = getConfiguredTarget("//a:r");
+ Action action =
+ getGeneratingAction(r.getProvider(FileProvider.class).getFilesToBuild().getSingleton());
+ assertThat(ActionsTestUtil.baseArtifactNames(action.getRunfilesSupplier().getArtifacts()))
+ .containsAtLeast("tool", "tool.sh", "data");
+ }
+
+ @Test
+ public void testFilesToRunInActionsTools() throws Exception {
+ scratch.file(
+ "a/a.bzl",
+ "def _impl(ctx):",
+ " f = ctx.actions.declare_file('output')",
+ " ctx.actions.run(",
+ " inputs = [],",
+ " outputs = [f],",
+ " tools = [ctx.attr._tool[DefaultInfo].files_to_run],",
+ " executable = 'a/tool')",
+ " return [DefaultInfo(files=depset([f]))]",
+ "r = rule(implementation=_impl, attrs = {'_tool': attr.label(default='//a:tool')})");
+
+ scratch.file(
+ "a/BUILD",
+ "load(':a.bzl', 'r')",
+ "r(name='r')",
+ "sh_binary(name='tool', srcs=['tool.sh'], data=['data'])");
+
+ ConfiguredTarget r = getConfiguredTarget("//a:r");
+ Action action =
+ getGeneratingAction(r.getProvider(FileProvider.class).getFilesToBuild().getSingleton());
+ assertThat(ActionsTestUtil.baseArtifactNames(action.getRunfilesSupplier().getArtifacts()))
+ .containsAtLeast("tool", "tool.sh", "data");
+ }
+
+ // Verifies that configuration_field can only be used on 'label' attributes.
+ @Test
+ public void testConfigurationField_invalidAttributeType() throws Exception {
+ scratch.file(
+ "test/main_rule.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "main_rule = rule(implementation = _impl,",
+ " attrs = { '_myattr': attr.int(",
+ " default = configuration_field(",
+ " fragment = 'apple', name = 'xcode_config_label')),",
+ " },",
+ " fragments = ['apple'],",
+ ")");
+
+ scratch.file("test/BUILD",
+ "load('//test:main_rule.bzl', 'main_rule')",
+ "main_rule(name='main')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:main"));
+
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "in call to int(), parameter 'default' got value of type 'LateBoundDefault', want"
+ + " 'int'");
+ }
+
+ @Test
+ public void testStarlarkCustomCommandLineKeyComputation() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ ImmutableList.Builder<CommandLine> commandLines = ImmutableList.builder();
+
+ commandLines.add(getCommandLine("args = ruleContext.actions.args()"));
+ commandLines.add(getCommandLine("args = ruleContext.actions.args()", "args.add('foo')"));
+ commandLines.add(
+ getCommandLine("args = ruleContext.actions.args()", "args.add('--foo', 'foo')"));
+ commandLines.add(
+ getCommandLine("args = ruleContext.actions.args()", "args.add('foo', format='--foo=%s')"));
+ commandLines.add(
+ getCommandLine("args = ruleContext.actions.args()", "args.add_all(['foo', 'bar'])"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()", "args.add_all('-foo', ['foo', 'bar'])"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "args.add_all(['foo', 'bar'], format_each='format%s')"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()", "args.add_all(['foo', 'bar'], before_each='-I')"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()", "args.add_all(['boing', 'boing', 'boing'])"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "args.add_all(['boing', 'boing', 'boing'], uniquify=True)"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "args.add_all(['foo', 'bar'], terminate_with='baz')"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()", "args.add_joined(['foo', 'bar'], join_with=',')"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "args.add_joined(['foo', 'bar'], join_with=',', format_joined='--foo=%s')"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _map_each(s): return s + '_mapped'",
+ "args.add_all(['foo', 'bar'], map_each=_map_each)"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "values = depset(['a', 'b'])",
+ "args.add_all(values)"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _map_each(s): return s + '_mapped'",
+ "values = depset(['a', 'b'])",
+ "args.add_all(values, map_each=_map_each)"));
+ commandLines.add(
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _map_each(s): return s + '_mapped_again'",
+ "values = depset(['a', 'b'])",
+ "args.add_all(values, map_each=_map_each)"));
+
+ // Ensure all these command lines have distinct keys
+ Map<String, CommandLine> digests = new HashMap<>();
+ for (CommandLine commandLine : commandLines.build()) {
+ String digest = getDigest(commandLine);
+ CommandLine previous = digests.putIfAbsent(digest, commandLine);
+ if (previous != null) {
+ fail(
+ String.format(
+ "Found two command lines with identical digest %s: '%s' and '%s'",
+ digest,
+ Joiner.on(' ').join(previous.arguments()),
+ Joiner.on(' ').join(commandLine.arguments())));
+ }
+ }
+
+ // Ensure errors are handled
+ CommandLine commandLine =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _bad_fn(s): return s.doesnotexist()",
+ "values = depset(['a', 'b'])",
+ "args.add_all(values, map_each=_bad_fn)");
+ assertThrows(
+ CommandLineExpansionException.class,
+ () ->
+ commandLine.addToFingerprint(
+ actionKeyContext, /*artifactExpander=*/ null, new Fingerprint()));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputation_differentMapEach() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _fun1(arg): return 'val1'",
+ "def _fun2(arg): return 'val2'",
+ "args.add_all(['a'], map_each=_fun1)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _fun1(arg): return 'val1'",
+ "def _fun2(arg): return 'val2'",
+ "args.add_all(['a'], map_each=_fun2)");
+
+ assertThat(getDigest(commandLine1)).isNotEqualTo(getDigest(commandLine2));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputation_differentArg() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _fun(arg): return arg",
+ "args.add_all(['a'], map_each=_fun)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "def _fun(arg): return arg",
+ "args.add_all(['b'], map_each=_fun)");
+
+ assertThat(getDigest(commandLine1)).isNotEqualTo(getDigest(commandLine2));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_equivalentMapEach_sameKey()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add_joined([directory], join_with=',', map_each=str, expand_directories=True)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def mystr(file): return str(file)",
+ "args.add_joined([directory], join_with=',', map_each=mystr, expand_directories=True)");
+
+ ArtifactExpander expander = createArtifactExpander("foo/dir", "file");
+ assertThat(getDigest(commandLine1, expander)).isEqualTo(getDigest(commandLine2, expander));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_mapEachConstantForDir()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value1'",
+ "args.add_all([directory], map_each=_constant_for_dir, expand_directories=True)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value2'",
+ "args.add_all([directory], map_each=_constant_for_dir, expand_directories=True)");
+
+ ArtifactExpander expander = createArtifactExpander("foo/dir", "file");
+ assertThat(getDigest(commandLine1, expander)).isNotEqualTo(getDigest(commandLine2, expander));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_constantForDirWithNestedSet()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "dir = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value1'",
+ "args.add_all(depset([dir]), map_each=_constant_for_dir, expand_directories=True)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "dir = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value2'",
+ "args.add_all(depset([dir]), map_each=_constant_for_dir, expand_directories=True)");
+
+ ArtifactExpander expander = createArtifactExpander("foo/dir", "file");
+ assertThat(getDigest(commandLine1, expander)).isNotEqualTo(getDigest(commandLine2, expander));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_mapEachFailsForDir()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "ruleContext.actions.run_shell(outputs=[directory], command='')",
+ "def _fail_for_dir(file):",
+ " if file.path.endswith('dir'): fail('hello')",
+ " return 'value1'",
+ "args.add_all([directory], map_each=_fail_for_dir, expand_directories=True)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "ruleContext.actions.run_shell(outputs=[directory], command='')",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _fail_for_dir(file):",
+ " if file.path.endswith('dir'): fail('hello')",
+ " return 'value2'",
+ "args.add_all([directory], map_each=_fail_for_dir, expand_directories=True)");
+
+ ArtifactExpander expander = createArtifactExpander("foo/dir", "file");
+ assertThat(getDigest(commandLine1, expander)).isNotEqualTo(getDigest(commandLine2, expander));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_differentExpansion()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ CommandLine commandLine =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "ruleContext.actions.run_shell(outputs=[directory], command='')",
+ "def _get_path(file): return file.path",
+ "args.add_all([directory], map_each=_get_path, expand_directories=True)");
+
+ ArtifactExpander expander1 = createArtifactExpander("foo/dir", "file1");
+ ArtifactExpander expander2 = createArtifactExpander("foo/dir", "file2");
+ assertThat(getDigest(commandLine, expander1)).isNotEqualTo(getDigest(commandLine, expander2));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_differentExpansionNoMapEach()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ CommandLine commandLine =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add_all([directory])");
+
+ ArtifactExpander expander1 = createArtifactExpander("foo/dir", "file1");
+ ArtifactExpander expander2 = createArtifactExpander("foo/dir", "file2");
+ assertThat(getDigest(commandLine, expander1)).isNotEqualTo(getDigest(commandLine, expander2));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_extraFileInExpansionNoMapEach()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ CommandLine commandLine =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add_all([directory])");
+
+ ArtifactExpander expander1 = createArtifactExpander("foo/dir", "file1");
+ ArtifactExpander expander2 = createArtifactExpander("foo/dir", "file1", "file2");
+ assertThat(getDigest(commandLine, expander1)).isNotEqualTo(getDigest(commandLine, expander2));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputationWithExpander_constantForDirAddJoined()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value1'",
+ "args.add_joined([directory], join_with=',', map_each=_constant_for_dir,"
+ + " expand_directories=True)");
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _constant_for_dir(f): return 'constant' if f.path.endswith('dir') else 'value2'",
+ "args.add_joined([directory], join_with=',', map_each=_constant_for_dir,"
+ + " expand_directories=True)");
+
+ ArtifactExpander expander = createArtifactExpander("foo/dir", "file");
+ assertThat(getDigest(commandLine1, expander)).isNotEqualTo(getDigest(commandLine2, expander));
+ }
+
+ @Test
+ public void starlarkCustomCommandLineKeyComputation_inconsequentialChangeToStarlarkSemantics()
+ throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ CommandLine commandLine1 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _path(f): return f.path",
+ "args.add_all([directory], map_each=_path)");
+
+ ev.setSemantics("--incompatible_run_shell_command_string");
+ // setStarlarkSemanticsOptions reinitializes the thread -- set the ruleContext on the new one.
+ setRuleContext(createRuleContext("//foo:foo"));
+
+ CommandLine commandLine2 =
+ getCommandLine(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _path(f): return f.path",
+ "args.add_all([directory], map_each=_path)");
+
+ assertThat(getDigest(commandLine1)).isEqualTo(getDigest(commandLine2));
+ }
+
+ private static ArtifactExpander createArtifactExpander(String dirRelativePath, String... files) {
+ return (artifact, output) -> {
+ Preconditions.checkArgument(
+ artifact.getRootRelativePath().equals(PathFragment.create(dirRelativePath)));
+ for (String file : files) {
+ output.add(
+ new DerivedArtifact(
+ artifact.getRoot(),
+ artifact.getExecPath().getRelative(file),
+ (ActionLookupKey) artifact.getArtifactOwner()));
+ }
+ };
+ }
+
+ private String getDigest(CommandLine commandLine) throws CommandLineExpansionException {
+ return getDigest(commandLine, /*artifactExpander=*/ null);
+ }
+
+ private String getDigest(CommandLine commandLine, ArtifactExpander artifactExpander)
+ throws CommandLineExpansionException {
+ Fingerprint fingerprint = new Fingerprint();
+ commandLine.addToFingerprint(actionKeyContext, artifactExpander, fingerprint);
+ return fingerprint.hexDigestAndReset();
+ }
+
+ private CommandLine getCommandLine(String... lines) throws Exception {
+ ev.exec(lines);
+ return ((Args) ev.eval("args")).build();
+ }
+
+ @Test
+ public void testPrintArgs() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.exec("args = ruleContext.actions.args()", "args.add_all(['--foo', '--bar'])");
+ Args args = (Args) ev.eval("args");
+ assertThat(Printer.getPrinter().debugPrint(args).toString()).isEqualTo("--foo --bar");
+ }
+
+ @Test
+ public void testDirectoryInArgs() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _short_path(f): return f.short_path", // For easier assertions
+ "args.add_all([directory], map_each=_short_path)");
+ Sequence<?> result = (Sequence<?>) ev.eval("args, directory");
+ Args args = (Args) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ CommandLine commandLine = args.build();
+
+ // When asking for arguments without an artifact expander we just return the directory
+ assertThat(commandLine.arguments()).containsExactly("foo/dir");
+
+ // Now ask for one with an expanded directory
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ assertThat(commandLine.arguments(artifactExpander))
+ .containsExactly("foo/dir/file1", "foo/dir/file2");
+ }
+
+ @Test
+ public void testDirectoryInArgsExpandDirectories() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _short_path(f): return f.short_path", // For easier assertions
+ "args.add_all([directory], map_each=_short_path, expand_directories=True)",
+ "args.add_all([directory], map_each=_short_path, expand_directories=False)");
+ Sequence<?> result = (Sequence<?>) ev.eval("args, directory");
+ Args args = (Args) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ CommandLine commandLine = args.build();
+
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ // First expanded, then not expanded (two separate calls)
+ assertThat(commandLine.arguments(artifactExpander))
+ .containsExactly("foo/dir/file1", "foo/dir/file2", "foo/dir");
+ }
+
+ @Test
+ public void testDirectoryInScalarArgsFails() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.checkEvalErrorContains(
+ "Cannot add directories to Args#add",
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add(directory)");
+ }
+
+ @Test
+ public void testParamFileHasDirectoryAsInput() throws Exception {
+ StarlarkRuleContext ctx = createRuleContext("//foo:foo");
+ setRuleContext(ctx);
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add_all([directory])",
+ "params = ruleContext.actions.declare_file('params')",
+ "ruleContext.actions.write(params, args)");
+ Sequence<?> result = (Sequence<?>) ev.eval("params, directory");
+ Artifact params = (Artifact) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ ActionAnalysisMetadata action =
+ ctx.getRuleContext().getAnalysisEnvironment().getLocalGeneratingAction(params);
+ assertThat(action.getInputs().toList()).contains(directory);
+ }
+
+ @Test
+ public void testDirectoryExpansionInArgs() throws Exception {
+ setRuleContext(createRuleContext("//foo:foo"));
+ ev.exec(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "file3 = ruleContext.actions.declare_file('file3')",
+ "def _expand_dirs(artifact, dir_expander):",
+ " return [f.short_path for f in dir_expander.expand(artifact)]",
+ "args.add_all([directory, file3], map_each=_expand_dirs)");
+ Args args = (Args) ev.eval("args");
+ Artifact directory = (Artifact) ev.eval("directory");
+ CommandLine commandLine = args.build();
+
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ assertThat(commandLine.arguments(artifactExpander))
+ .containsExactly("foo/dir/file1", "foo/dir/file2", "foo/file3");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkStringRepresentationsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkStringRepresentationsTest.java
new file mode 100644
index 0000000..b1f2764
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkStringRepresentationsTest.java
@@ -0,0 +1,377 @@
+// 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.starlark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.vfs.ModifiedFileSet;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for string representations of Starlark objects. */
+@RunWith(JUnit4.class)
+public class StarlarkStringRepresentationsTest extends BuildViewTestCase {
+
+ // Different ways to format objects, these suffixes are used in the `prepare_params` function
+ private static final ImmutableList<String> SUFFIXES =
+ ImmutableList.of("_str", "_repr", "_format", "_str_perc", "_repr_perc");
+
+ private Object starlarkLoadingEval(String code) throws Exception {
+ return starlarkLoadingEval(code, "");
+ }
+
+ /**
+ * Evaluates {@code code} in the loading phase in a .bzl file
+ *
+ * @param code The code to execute
+ * @param definition Additional code to define necessary variables
+ */
+ private Object starlarkLoadingEval(String code, String definition) throws Exception {
+ scratch.overwriteFile("eval/BUILD", "load(':eval.bzl', 'eval')", "eval(name='eval')");
+ scratch.overwriteFile(
+ "eval/eval.bzl",
+ definition,
+ String.format("x = %s", code), // Should be placed here to execute during the loading phase
+ "def _impl(ctx):",
+ " return struct(result = x)",
+ "eval = rule(implementation = _impl)");
+ skyframeExecutor.invalidateFilesUnderPathForTesting(
+ reporter,
+ new ModifiedFileSet.Builder()
+ .modify(PathFragment.create("eval/BUILD"))
+ .modify(PathFragment.create("eval/eval.bzl"))
+ .build(),
+ Root.fromPath(rootDirectory));
+
+ ConfiguredTarget target = getConfiguredTarget("//eval");
+ return target.get("result");
+ }
+
+ /**
+ * Evaluates {@code code} in the loading phase in a BUILD file. {@code code} must return a string.
+ *
+ * @param code The code to execute
+ */
+ private Object starlarkLoadingEvalInBuildFile(String code) throws Exception {
+ scratch.overwriteFile("eval/BUILD",
+ "load(':eval.bzl', 'eval')",
+ String.format("eval(name='eval', param = %s)", code));
+ scratch.overwriteFile(
+ "eval/eval.bzl",
+ "def _impl(ctx):",
+ " return struct(result = ctx.attr.param)",
+ "eval = rule(implementation = _impl, attrs = {'param': attr.string()})");
+ skyframeExecutor.invalidateFilesUnderPathForTesting(
+ reporter,
+ new ModifiedFileSet.Builder()
+ .modify(PathFragment.create("eval/BUILD"))
+ .modify(PathFragment.create("eval/eval.bzl"))
+ .build(),
+ Root.fromPath(rootDirectory));
+
+ ConfiguredTarget target = getConfiguredTarget("//eval");
+ return target.get("result");
+ }
+
+ /**
+ * Asserts that all 5 different ways to convert an object to a string of {@code expression}
+ * ({@code str}, {@code repr}, {@code '%s'}, {@code '%r'}, {@code '{}'.format} return the correct
+ * {@code representation}. Not applicable for objects that have different {@code str} and {@code
+ * repr} representations.
+ *
+ * @param expression the expression to evaluate a string representation of
+ * @param representation desired string representation
+ */
+ private void assertStringRepresentationInBuildFile(
+ String expression, String representation) throws Exception {
+ assertThat(starlarkLoadingEvalInBuildFile(String.format("str(%s)", expression)))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEvalInBuildFile(String.format("repr(%s)", expression)))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEvalInBuildFile(String.format("'%%s' %% (%s,)", expression)))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEvalInBuildFile(String.format("'%%r' %% (%s,)", expression)))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEvalInBuildFile(String.format("'{}'.format(%s)", expression)))
+ .isEqualTo(representation);
+ }
+
+ /**
+ * Asserts that all 5 different ways to convert an object to a string of {@code expression}
+ * ({@code str}, {@code repr}, {@code '%s'}, {@code '%r'}, {@code '{}'.format} return the correct
+ * {@code representation}. Not applicable for objects that have different {@code str} and {@code
+ * repr} representations.
+ *
+ * @param definition optional definition required to evaluate the {@code expression}
+ * @param expression the expression to evaluate a string representation of
+ * @param representation desired string representation
+ */
+ private void assertStringRepresentation(
+ String definition, String expression, String representation) throws Exception {
+ assertThat(starlarkLoadingEval(String.format("str(%s)", expression), definition))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEval(String.format("repr(%s)", expression), definition))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEval(String.format("'%%s' %% (%s,)", expression), definition))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEval(String.format("'%%r' %% (%s,)", expression), definition))
+ .isEqualTo(representation);
+ assertThat(starlarkLoadingEval(String.format("'{}'.format(%s)", expression), definition))
+ .isEqualTo(representation);
+ }
+
+ private void assertStringRepresentation(String expression, String representation)
+ throws Exception {
+ assertStringRepresentation("", expression, representation);
+ }
+
+ /**
+ * Creates a set of BUILD and .bzl files that gathers objects of many different types available in
+ * Starlark and creates their string representations by calling `str` and `repr` on them. The
+ * strings are available in the configured target for //test/starlark:check
+ */
+ private void generateFilesToTestStrings() throws Exception {
+ // Generate string representations of Starlark rule contexts, targets, and files.
+ // Objects are gathered in the implementation of the `check` rule.
+ // prepare_params(objects) converts a dict of objects to a dict of their string representations.
+
+ scratch.file(
+ "test/starlark/rules.bzl",
+ "aspect_ctx_provider = provider()",
+ "def prepare_params(objects):",
+ " params = {}",
+ " for k, v in objects.items():",
+ " params[k + '_str'] = str(v)",
+ " params[k + '_repr'] = repr(v)",
+ " params[k + '_format'] = '{}'.format(v)",
+ " params[k + '_str_perc'] = '%s' % (v,)",
+ " params[k + '_repr_perc'] = '%r' % (v,)",
+ " return params",
+ "",
+ "def _impl_aspect(target, ctx):",
+ " return [aspect_ctx_provider(ctx = ctx, rule = ctx.rule)]",
+ "my_aspect = aspect(implementation = _impl_aspect)",
+ "",
+ "def _impl(ctx): pass",
+ "dep = rule(implementation = _impl)",
+ "",
+ "def _genfile_impl(ctx):",
+ " ctx.actions.write(output = ctx.outputs.my_output, content = 'foo')",
+ "genfile = rule(",
+ " implementation = _genfile_impl,",
+ " outputs = {'my_output': '%{name}.txt'},",
+ ")",
+ "",
+ "def _check_impl(ctx):",
+ " source_file = ctx.attr.srcs[0].files.to_list()[0]",
+ " generated_file = ctx.attr.srcs[1].files.to_list()[0]",
+ " objects = {",
+ " 'target': ctx.attr.deps[0],",
+ " 'alias_target': ctx.attr.deps[1],",
+ " 'aspect_target': ctx.attr.asp_deps[0],",
+ " 'input_target': ctx.attr.srcs[0],",
+ " 'output_target': ctx.attr.srcs[1],",
+ " 'rule_ctx': ctx,",
+ " 'aspect_ctx': ctx.attr.asp_deps[0][aspect_ctx_provider].ctx,",
+ " 'aspect_ctx.rule': ctx.attr.asp_deps[0][aspect_ctx_provider].rule,",
+ " 'source_file': source_file,",
+ " 'generated_file': generated_file,",
+ " 'source_root': source_file.root,",
+ " 'generated_root': generated_file.root,",
+ " }",
+ " return struct(**prepare_params(objects))",
+ "check = rule(",
+ " implementation = _check_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(),",
+ " 'asp_deps': attr.label_list(aspects = [my_aspect]),",
+ " 'srcs': attr.label_list(allow_files = True),",
+ " },",
+ ")");
+
+ scratch.file(
+ "test/starlark/BUILD",
+ "load(':rules.bzl', 'check', 'dep', 'genfile')",
+ "",
+ "dep(name = 'foo')",
+ "dep(name = 'bar')",
+ "alias(name = 'foobar', actual = ':foo')",
+ "genfile(name = 'output')",
+ "check(",
+ " name = 'check',",
+ " deps = [':foo', ':foobar'],",
+ " asp_deps = [':bar'],",
+ " srcs = ['input.txt', 'output.txt'],",
+ ")");
+ }
+
+ @Test
+ public void testStringRepresentations_Strings() throws Exception {
+ assertThat(starlarkLoadingEval("str('foo')")).isEqualTo("foo");
+ assertThat(starlarkLoadingEval("'%s' % 'foo'")).isEqualTo("foo");
+ assertThat(starlarkLoadingEval("'{}'.format('foo')")).isEqualTo("foo");
+ assertThat(starlarkLoadingEval("repr('foo')")).isEqualTo("\"foo\"");
+ assertThat(starlarkLoadingEval("'%r' % 'foo'")).isEqualTo("\"foo\"");
+ }
+
+ @Test
+ public void testStringRepresentations_Labels() throws Exception {
+ assertThat(starlarkLoadingEval("str(Label('//foo:bar'))")).isEqualTo("//foo:bar");
+ assertThat(starlarkLoadingEval("'%s' % Label('//foo:bar')")).isEqualTo("//foo:bar");
+ assertThat(starlarkLoadingEval("'{}'.format(Label('//foo:bar'))")).isEqualTo("//foo:bar");
+ assertThat(starlarkLoadingEval("repr(Label('//foo:bar'))")).isEqualTo("Label(\"//foo:bar\")");
+ assertThat(starlarkLoadingEval("'%r' % Label('//foo:bar')")).isEqualTo("Label(\"//foo:bar\")");
+
+ assertThat(starlarkLoadingEval("'{}'.format([Label('//foo:bar')])"))
+ .isEqualTo("[Label(\"//foo:bar\")]");
+ }
+
+ @Test
+ public void testStringRepresentations_Primitives() throws Exception {
+ // Strings are tested in a separate test case as they have different str and repr values.
+ assertStringRepresentation("1543", "1543");
+ assertStringRepresentation("True", "True");
+ assertStringRepresentation("False", "False");
+ }
+
+ @Test
+ public void testStringRepresentations_Containers() throws Exception {
+ assertStringRepresentation("['a', 'b']", "[\"a\", \"b\"]");
+ assertStringRepresentation("('a', 'b')", "(\"a\", \"b\")");
+ assertStringRepresentation("{'a': 'b', 'c': 'd'}", "{\"a\": \"b\", \"c\": \"d\"}");
+ assertStringRepresentation("struct(d = 4, c = 3)", "struct(c = 3, d = 4)");
+ }
+
+ @Test
+ public void testStringRepresentations_Functions() throws Exception {
+ assertStringRepresentation("all", "<built-in function all>");
+ assertStringRepresentation("def f(): pass", "f", "<function f from //eval:eval.bzl>");
+ }
+
+ @Test
+ public void testStringRepresentations_Rules() throws Exception {
+ assertStringRepresentation("native.cc_library", "<built-in rule cc_library>");
+ assertStringRepresentation("def f(): pass", "rule(implementation=f)", "<rule>");
+ }
+
+ @Test
+ public void testStringRepresentations_Aspects() throws Exception {
+ assertStringRepresentation("def f(): pass", "aspect(implementation=f)", "<aspect>");
+ }
+
+ @Test
+ public void testStringRepresentations_Providers() throws Exception {
+ assertStringRepresentation("provider()", "<provider>");
+ assertStringRepresentation(
+ "p = provider()", "p(b = 'foo', a = 1)", "struct(a = 1, b = \"foo\")");
+ }
+
+ @Test
+ public void testStringRepresentations_Select() throws Exception {
+ assertStringRepresentation(
+ "select({'//foo': ['//bar']}) + select({'//foo2': ['//bar2']})",
+ "select({\"//foo\": [\"//bar\"]}) + select({\"//foo2\": [\"//bar2\"]})");
+ }
+
+ @Test
+ public void testStringRepresentations_RuleContext() throws Exception {
+ generateFilesToTestStrings();
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:check");
+
+ for (String suffix : SUFFIXES) {
+ assertThat(target.get("rule_ctx" + suffix))
+ .isEqualTo("<rule context for //test/starlark:check>");
+ assertThat(target.get("aspect_ctx" + suffix))
+ .isEqualTo("<aspect context for //test/starlark:bar>");
+ assertThat(target.get("aspect_ctx.rule" + suffix))
+ .isEqualTo("<rule collection for //test/starlark:bar>");
+ }
+ }
+
+ @Test
+ public void testStringRepresentations_Files() throws Exception {
+ generateFilesToTestStrings();
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:check");
+
+ for (String suffix : SUFFIXES) {
+ assertThat(target.get("source_file" + suffix))
+ .isEqualTo("<source file test/starlark/input.txt>");
+ assertThat(target.get("generated_file" + suffix))
+ .isEqualTo("<generated file test/starlark/output.txt>");
+ }
+ }
+
+ @Test
+ public void testStringRepresentations_Root() throws Exception {
+ generateFilesToTestStrings();
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:check");
+
+ for (String suffix : SUFFIXES) {
+ assertThat(target.get("source_root" + suffix)).isEqualTo("<source root>");
+ assertThat(target.get("generated_root" + suffix)).isEqualTo("<derived root>");
+ }
+ }
+
+ @Test
+ public void testStringRepresentations_Glob() throws Exception {
+ scratch.file("eval/one.txt");
+ scratch.file("eval/two.txt");
+ scratch.file("eval/three.txt");
+
+ assertStringRepresentationInBuildFile(
+ "glob(['*.txt'])",
+ "[\"one.txt\", \"three.txt\", \"two.txt\"]");
+ }
+
+ @Test
+ public void testStringRepresentations_Attr() throws Exception {
+ assertStringRepresentation("attr", "<attr>");
+ assertStringRepresentation("attr.int()", "<attr.int>");
+ assertStringRepresentation("attr.string()", "<attr.string>");
+ assertStringRepresentation("attr.label()", "<attr.label>");
+ assertStringRepresentation("attr.string_list()", "<attr.string_list>");
+ assertStringRepresentation("attr.int_list()", "<attr.int_list>");
+ assertStringRepresentation("attr.label_list()", "<attr.label_list>");
+ assertStringRepresentation("attr.label_keyed_string_dict()", "<attr.label_keyed_string_dict>");
+ assertStringRepresentation("attr.bool()", "<attr.bool>");
+ assertStringRepresentation("attr.output()", "<attr.output>");
+ assertStringRepresentation("attr.output_list()", "<attr.output_list>");
+ assertStringRepresentation("attr.string_dict()", "<attr.string_dict>");
+ assertStringRepresentation("attr.string_list_dict()", "<attr.string_list_dict>");
+ }
+
+ @Test
+ public void testStringRepresentations_Targets() throws Exception {
+ generateFilesToTestStrings();
+ ConfiguredTarget target = getConfiguredTarget("//test/starlark:check");
+
+ for (String suffix : SUFFIXES) {
+ assertThat(target.get("target" + suffix)).isEqualTo("<target //test/starlark:foo>");
+ assertThat(target.get("input_target" + suffix))
+ .isEqualTo("<input file target //test/starlark:input.txt>");
+ assertThat(target.get("output_target" + suffix))
+ .isEqualTo("<output file target //test/starlark:output.txt>");
+ assertThat(target.get("alias_target" + suffix))
+ .isEqualTo("<alias target //test/starlark:foobar of //test/starlark:foo>");
+ assertThat(target.get("aspect_target" + suffix))
+ .isEqualTo("<merged target //test/starlark:bar>");
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD b/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD
new file mode 100644
index 0000000..4effb55
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD
@@ -0,0 +1,37 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(["**"]),
+ visibility = ["//src:__subpackages__"],
+)
+
+java_library(
+ name = "util",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:keep-going-option",
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib:syntax",
+ "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/events",
+ "//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:starlark_semantics_options",
+ "//src/main/java/com/google/devtools/build/lib/pkgcache",
+ "//src/main/java/com/google/devtools/build/lib/rules/platform",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/test/java/com/google/devtools/build/lib/analysis/util",
+ "//src/test/java/com/google/devtools/build/lib/syntax/util",
+ "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
+ "//third_party:guava",
+ "//third_party:junit4",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java
new file mode 100644
index 0000000..5c4ac84
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java
@@ -0,0 +1,68 @@
+// Copyright 2014 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.starlark.util;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkModules;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.BazelModuleContext;
+import com.google.devtools.build.lib.packages.BazelStarlarkContext;
+import com.google.devtools.build.lib.packages.SymbolGenerator;
+import com.google.devtools.build.lib.rules.platform.PlatformCommon;
+import com.google.devtools.build.lib.syntax.Starlark;
+import com.google.devtools.build.lib.syntax.StarlarkThread;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+import com.google.devtools.build.lib.testutil.TestConstants;
+
+/**
+ * BazelEvaluationTestCase is a subclass of EvaluationTestCase that defines various Bazel built-ins
+ * in the environment.
+ */
+// TODO(adonovan): this is (and has always been) a mess, and an abuse of inheritance.
+// Once the production code API has disentangled Thread and Module, make this rational.
+public final class BazelEvaluationTestCase extends EvaluationTestCase {
+
+ @Override
+ protected Object newModuleHook(ImmutableMap.Builder<String, Object> predeclared) {
+ StarlarkModules.addStarlarkGlobalsToBuilder(predeclared);
+ Starlark.addModule(predeclared, new PlatformCommon());
+
+ // Return the module's client data. (This one uses dummy values for tests.)
+ return BazelModuleContext.create(
+ Label.parseAbsoluteUnchecked("//test:label", /*defaultToMain=*/ false),
+ "test/label.bzl",
+ /*loads=*/ ImmutableMap.of(),
+ /*bzlTransitiveDigest=*/ new byte[0]);
+ }
+
+ @Override
+ protected void newThreadHook(StarlarkThread thread) {
+ // This StarlarkThread has no PackageContext, so attempts to create a rule will fail.
+ // Rule creation is tested by StarlarkIntegrationTest.
+
+ // This is a poor approximation to the thread that Blaze would create
+ // for testing rule implementation functions. It has phase LOADING, for example.
+ // TODO(adonovan): stop creating threads in tests. This is the responsibility of the
+ // production code. Tests should provide only files and commands.
+ new BazelStarlarkContext(
+ BazelStarlarkContext.Phase.LOADING,
+ TestConstants.TOOLS_REPOSITORY,
+ /*fragmentNameToClass=*/ null,
+ /*repoMapping=*/ ImmutableMap.of(),
+ new SymbolGenerator<>(new Object()),
+ /*analysisRuleLabel=*/ null) // dummy value for tests
+ .storeInThread(thread);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java b/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java
new file mode 100644
index 0000000..ff4b909
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java
@@ -0,0 +1,100 @@
+// Copyright 2019 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.starlark.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
+import com.google.devtools.build.lib.pkgcache.LoadingOptions;
+import com.google.devtools.build.lib.pkgcache.PackageOptions;
+import com.google.devtools.build.lib.runtime.ClientOptions;
+import com.google.devtools.build.lib.runtime.CommonCommandOptions;
+import com.google.devtools.build.lib.runtime.KeepGoingOption;
+import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
+import com.google.devtools.build.lib.runtime.UiOptions;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingResult;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+
+/** Helper base class for testing the use of Starlark-style flags. */
+public class StarlarkOptionsTestCase extends BuildViewTestCase {
+
+ private static final List<Class<? extends OptionsBase>> requiredOptionsClasses =
+ ImmutableList.of(
+ PackageOptions.class,
+ StarlarkSemanticsOptions.class,
+ KeepGoingOption.class,
+ LoadingOptions.class,
+ ClientOptions.class,
+ UiOptions.class,
+ CommonCommandOptions.class);
+ private StarlarkOptionsParser starlarkOptionsParser;
+
+ @Before
+ public void setUp() throws Exception {
+ optionsParser =
+ OptionsParser.builder()
+ .optionsClasses(
+ Iterables.concat(
+ requiredOptionsClasses, ruleClassProvider.getConfigurationOptions()))
+ .build();
+ starlarkOptionsParser =
+ StarlarkOptionsParser.newStarlarkOptionsParserForTesting(
+ skyframeExecutor, reporter, PathFragment.EMPTY_FRAGMENT, optionsParser);
+ }
+
+ protected OptionsParsingResult parseStarlarkOptions(String options) throws Exception {
+ starlarkOptionsParser.setResidueForTesting(Arrays.asList(options.split(" ")));
+ starlarkOptionsParser.parse(new StoredEventHandler());
+ return starlarkOptionsParser.getNativeOptionsParserFortesting();
+ }
+
+ private void writeBuildSetting(String type, String defaultValue, boolean isFlag)
+ throws Exception {
+ String flag = isFlag ? "True" : "False";
+
+ scratch.file(
+ "test/build_setting.bzl",
+ "def _build_setting_impl(ctx):",
+ " return []",
+ type + "_setting = rule(",
+ " implementation = _build_setting_impl,",
+ " build_setting = config." + type + "(flag=" + flag + ")",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:build_setting.bzl', '" + type + "_setting')",
+ type
+ + "_setting(name = 'my_"
+ + type
+ + "_setting', build_setting_default = "
+ + defaultValue
+ + ")");
+ }
+
+ protected void writeBasicIntFlag() throws Exception {
+ writeBuildSetting("int", "42", true);
+ }
+
+ protected void writeBasicBoolFlag() throws Exception {
+ writeBuildSetting("bool", "True", true);
+ }
+}