| // 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.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.PathStripper; |
| import com.google.devtools.build.lib.actions.RunfilesSupplier; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.CommandHelper; |
| 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.cmdline.RepositoryName; |
| 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.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.StarlarkMethod; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Printer; |
| import net.starlark.java.eval.Sequence; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkInt; |
| import net.starlark.java.eval.StarlarkList; |
| import net.starlark.java.eval.StarlarkThread; |
| 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) |
| public final class StarlarkRuleImplementationFunctionsTest extends BuildViewTestCase { |
| |
| private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase(); |
| |
| private StarlarkRuleContext createRuleContext(String label) throws Exception { |
| return new StarlarkRuleContext(getRuleContextForStarlark(getConfiguredTarget(label)), null); |
| } |
| |
| @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", 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.parseCanonical("//myinfo:myinfo.bzl"), "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.buildOrThrow().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 Starlark interpreter core into net/starlark/java. |
| |
| @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 createSpawnAction_progressMessageWithSubstitutions() throws Exception { |
| StarlarkRuleContext ruleContext = createRuleContext("//foo:foo"); |
| setRuleContext(ruleContext); |
| ev.exec( |
| "ruleContext.actions.run(", |
| " inputs = ruleContext.files.srcs,", |
| " outputs = ruleContext.files.srcs[1:],", |
| " executable = ruleContext.files.tools[0],", |
| " mnemonic = 'DummyMnemonic',", |
| " progress_message = 'message %{label} %{input} %{output}')"); |
| |
| SpawnAction action = |
| (SpawnAction) |
| Iterables.getOnlyElement( |
| ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); |
| |
| assertThat(action.getProgressMessage()).isEqualTo("message //foo:foo foo/a.txt foo/b.img"); |
| } |
| |
| @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, 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 { |
| setBuildLanguageOptions("--incompatible_run_shell_command_string=false"); |
| 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 { |
| setBuildLanguageOptions("--incompatible_run_shell_command_string=false"); |
| 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='exec'),", |
| " },", |
| ")"); |
| 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 testCreateSpawnActionWithToolAttribute() throws Exception { |
| 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 { |
| 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 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)')"); |
| } |
| |
| @Test |
| public void testExpandLocationWithShortPathsIsPrivateAPI() throws Exception { |
| scratch.file( |
| "abc/rule.bzl", |
| "def _impl(ctx):", |
| " ctx.expand_location('', short_paths = True)", |
| " return []", |
| "", |
| "r = rule(implementation = _impl)"); |
| scratch.file("abc/BUILD", "load(':rule.bzl', 'r')", "", "r(name = 'foo')"); |
| |
| AssertionError error = |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//abc:foo")); |
| |
| assertThat(error).hasMessageThat().contains("file '//abc:rule.bzl' cannot use private API"); |
| } |
| |
| @Test |
| public void testExpandLocationWithShortPaths() throws Exception { |
| StarlarkRuleContext ruleContext = createRuleContext("//foo:bar"); |
| setRuleContext(ruleContext); |
| |
| Object loc = ev.eval("ruleContext.expand_location('$(location :jl)', short_paths = True)"); |
| |
| assertThat(loc).isEqualTo("foo/libjl.jar"); |
| } |
| |
| /** 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( |
| String.format( |
| "%s '%s' 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-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 resolveCommandScript() throws Exception { |
| setRuleContext(createRuleContext("//foo:resolve_me")); |
| ev.exec( |
| "s = 'a' * " + CommandHelper.maxCommandLength + 1, |
| "inputs, argv, _ = ruleContext.resolve_command(command = s)"); |
| |
| @SuppressWarnings("unchecked") |
| List<Artifact> inputs = (List<Artifact>) ev.lookup("inputs"); |
| @SuppressWarnings("unchecked") |
| List<String> argv = (List<String>) ev.lookup("argv"); |
| |
| assertThat(inputs).hasSize(1); |
| assertThat(argv).hasSize(2); |
| assertThat(argv.get(0)).endsWith("/bash" + OsUtils.executableExtension()); |
| assertThat(argv.get(1)).isEqualTo(inputs.get(0).getExecPathString()); |
| assertThat(inputs.get(0).getExecPathString()).endsWith(".script.sh"); |
| } |
| |
| @Test |
| public void multipleResolveCommandScripts_noConflict() throws Exception { |
| setRuleContext(createRuleContext("//foo:resolve_me")); |
| ev.exec( |
| "s1 = '1' * " + CommandHelper.maxCommandLength + 1, |
| "s2 = '2' * " + CommandHelper.maxCommandLength + 1, |
| "inputs1, argv1, _ = ruleContext.resolve_command(command = s1)", |
| "inputs2, argv2, __ = ruleContext.resolve_command(command = s2)"); |
| |
| @SuppressWarnings("unchecked") |
| List<Artifact> inputs1 = (List<Artifact>) ev.lookup("inputs1"); |
| @SuppressWarnings("unchecked") |
| List<String> argv1 = (List<String>) ev.lookup("argv1"); |
| @SuppressWarnings("unchecked") |
| List<Artifact> inputs2 = (List<Artifact>) ev.lookup("inputs2"); |
| @SuppressWarnings("unchecked") |
| List<String> argv2 = (List<String>) ev.lookup("argv2"); |
| |
| assertThat(inputs1).hasSize(1); |
| assertThat(inputs2).hasSize(1); |
| assertThat(inputs1.get(0).getExecPathString()).isNotEqualTo(inputs2.get(0).getExecPathString()); |
| assertThat(argv1).hasSize(2); |
| assertThat(argv2).hasSize(2); |
| assertThat(argv1.get(0)).endsWith("/bash" + OsUtils.executableExtension()); |
| assertThat(argv2.get(0)).endsWith("/bash" + OsUtils.executableExtension()); |
| assertThat(argv1.get(1)).isEqualTo(inputs1.get(0).getExecPathString()); |
| assertThat(argv2.get(1)).isEqualTo(inputs2.get(0).getExecPathString()); |
| } |
| |
| @Test |
| public void resolveCommandScript_namingNotDependantOnCommand() throws Exception { |
| setRuleContext(createRuleContext("//foo:resolve_me")); |
| ev.exec( |
| "s = '1' * " + CommandHelper.maxCommandLength + 1, |
| "result1 = ruleContext.resolve_command(command = s)"); |
| var result1 = ev.lookup("result1"); |
| |
| // Reset the rule context to simulate a build in a different configuration that results in a |
| // different command. |
| setRuleContext(createRuleContext("//foo:resolve_me")); |
| ev.exec( |
| "s = '2' * " + CommandHelper.maxCommandLength + 1, |
| "result2 = ruleContext.resolve_command(command = s)"); |
| var result2 = ev.lookup("result2"); |
| |
| assertThat(result1).isEqualTo(result2); |
| } |
| |
| @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-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-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 |
| * net.starlark.java.syntax.ParserInput#fromUTF8} instead of {@code fromLatin1} and the hack for |
| * the substitutions 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. |
| var unused = runfiles.getRunfilesInputs(reporter, null, 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 { |
| setBuildLanguageOptions("--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.parseCanonical("//test:foo.bzl"), "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.parseCanonical("//test:foo.bzl"), "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 testLacksAdvertisedBuiltinProvider() 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.parseCanonical("//test:foo.bzl"), "foo_provider")); |
| assertThat(((StructImpl) provider).getValue("a")).isEqualTo(StarlarkInt.of(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.parseCanonical("//test:foo.bzl"), "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 ex = |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule")); |
| String msg = ex.getMessage(); |
| assertThat(msg) |
| .contains("rule implementation function returned an instance of an unnamed provider"); |
| assertThat(msg).contains("Provider defined at /workspace/test/my_rule.bzl:2:28"); |
| } |
| |
| @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(RepositoryName.MAIN).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 = "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); |
| thrown.expectMessage( |
| "in call to rule(), parameter 'implementation' got value of type" |
| + " 'builtin_function_or_method', 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 \"%s\", got \"--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 \"%s\", got \"--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 'int'", |
| "args = ruleContext.actions.args()", |
| "args.add(1, 'value')"); |
| ev.checkEvalErrorContains( |
| "expected value of type 'string' for arg name, got 'int'", |
| "args = ruleContext.actions.args()", |
| "args.add_all(1, [1, 2])"); |
| ev.checkEvalErrorContains( |
| "expected value of type 'sequence or depset' for values, got 'int'", |
| "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 int"); |
| } |
| |
| @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.overwriteFile( |
| "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')"); |
| |
| 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 = android_common.multi_cpu_configuration,", |
| " 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'))})"); |
| setBuildLanguageOptions("--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"); |
| } |
| |
| @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(), |
| PathStripper.PathMapper.NOOP)); |
| } |
| |
| @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=false"); |
| // setBuildLanguageOptions 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( |
| DerivedArtifact.create( |
| artifact.getRoot(), |
| artifact.getExecPath().getRelative(file), |
| (ActionLookupKey) artifact.getArtifactOwner())); |
| } |
| }; |
| } |
| |
| private static ArtifactExpander createArtifactExpander( |
| Artifact directory, ImmutableList<Artifact> files) { |
| return (artifact, output) -> { |
| if (artifact.equals(directory)) { |
| output.addAll(files); |
| } |
| }; |
| } |
| |
| private String getDigest(CommandLine commandLine) |
| throws CommandLineExpansionException, InterruptedException { |
| return getDigest(commandLine, /*artifactExpander=*/ null); |
| } |
| |
| private String getDigest(CommandLine commandLine, ArtifactExpander artifactExpander) |
| throws CommandLineExpansionException, InterruptedException { |
| Fingerprint fingerprint = new Fingerprint(); |
| commandLine.addToFingerprint( |
| actionKeyContext, artifactExpander, fingerprint, PathStripper.PathMapper.NOOP); |
| 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(new Printer().debugPrint(args, getStarlarkSemantics()).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"); |
| ArtifactExpander artifactExpander = |
| createArtifactExpander(directory, ImmutableList.of(file1, file2)); |
| 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"); |
| ArtifactExpander artifactExpander = |
| createArtifactExpander(directory, ImmutableList.of(file1, file2)); |
| // 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"); |
| ArtifactExpander artifactExpander = |
| createArtifactExpander(directory, ImmutableList.of(file1, file2)); |
| assertThat(commandLine.arguments(artifactExpander)) |
| .containsExactly("foo/dir/file1", "foo/dir/file2", "foo/file3"); |
| } |
| |
| @Test |
| public void testCallDirectoryExpanderWithWrongType() throws Exception { |
| setRuleContext(createRuleContext("//foo:foo")); |
| ev.exec( |
| "args = ruleContext.actions.args()", |
| "f = ruleContext.actions.declare_file('file')", |
| "def _expand_dirs(artifact, dir_expander):", |
| " return dir_expander.expand('oh no a string')", |
| "args.add_all([f], map_each=_expand_dirs)"); |
| Args args = (Args) ev.eval("args"); |
| CommandLine commandLine = args.build(); |
| assertThrows(CommandLineExpansionException.class, commandLine::arguments); |
| } |
| |
| @Test |
| public void testDeclareSharedArtifactIsPrivateAPI() throws Exception { |
| scratch.file( |
| "abc/rule.bzl", |
| "def _impl(ctx):", |
| " ctx.actions.declare_shareable_artifact('foo')", |
| " return []", |
| "", |
| "r = rule(implementation = _impl)"); |
| scratch.file("abc/BUILD", "load(':rule.bzl', 'r')", "", "r(name = 'foo')"); |
| |
| AssertionError error = |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//abc:foo")); |
| |
| assertThat(error).hasMessageThat().contains("file '//abc:rule.bzl' cannot use private API"); |
| } |
| |
| @Test |
| public void testDisablingRunfilesSymlinkChecksIsPrivateAPI() throws Exception { |
| scratch.file( |
| "abc/rule.bzl", |
| "def _impl(ctx):", |
| " ctx.runfiles(skip_conflict_checking = True)", |
| " return []", |
| "", |
| "r = rule(implementation = _impl)"); |
| scratch.file("abc/BUILD", "load(':rule.bzl', 'r')", "", "r(name = 'foo')"); |
| |
| AssertionError error = |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//abc:foo")); |
| |
| assertThat(error).hasMessageThat().contains("file '//abc:rule.bzl' cannot use private API"); |
| } |
| |
| @Test |
| public void testDeclareSharedArtifact_differentFileRoot() throws Exception { |
| scratch.file( |
| "test/rule.bzl", |
| "RootProvider = provider(fields = ['root'])", |
| "def _impl(ctx):", |
| " if not ctx.attr.dep:", |
| " return [RootProvider(root = ctx.configuration.bin_dir)]", // This is the child. |
| " exec_config_root = ctx.attr.dep[RootProvider].root", |
| " a1 = ctx.actions.declare_shareable_artifact(ctx.label.name + '1.so')", |
| " ctx.actions.write(a1, '')", |
| " a2 = ctx.actions.declare_shareable_artifact(", |
| " ctx.label.name + '2.so',", |
| " exec_config_root", |
| " )", |
| " ctx.actions.write(a2, '')", |
| " return [DefaultInfo(files = depset([a1, a2]))]", |
| "", |
| "r = rule(", |
| " implementation = _impl,", |
| " attrs = {'dep': attr.label(cfg = 'exec')},", |
| ")"); |
| scratch.file( |
| "test/BUILD", |
| "load(':rule.bzl', 'r')", |
| "r(name = 'foo', dep = ':exec_configured_child')", |
| "r(name = 'exec_configured_child')"); |
| |
| ConfiguredTarget target = getConfiguredTarget("//test:foo"); |
| |
| assertThat(target).isNotNull(); |
| Artifact a1 = |
| getFilesToBuild(target).toSet().stream() |
| .filter(artifactNamed("foo1.so")) |
| .findFirst() |
| .orElse(null); |
| assertThat(a1).isNotNull(); |
| assertThat(a1.getRoot().getExecPathString()) |
| .isEqualTo(getRelativeOutputPath() + "/k8-fastbuild/bin"); |
| Artifact a2 = |
| getFilesToBuild(target).toSet().stream() |
| .filter(artifactNamed("foo2.so")) |
| .findFirst() |
| .orElse(null); |
| assertThat(a2).isNotNull(); |
| assertThat(a2.getRoot().getExecPathString()) |
| .matches(getRelativeOutputPath() + "/[\\w\\-]+\\-exec\\-[\\w\\-]+/bin"); |
| } |
| } |