// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.bazel.rules.genrule; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.lib.testutil.TestConstants.GENRULE_SETUP; import static com.google.devtools.build.lib.testutil.TestConstants.GENRULE_SETUP_PATH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FileConfiguredTarget; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.util.AnalysisMock; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.rules.cpp.CppConfiguration; import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests of {@link BazelGenRule}. */ @RunWith(JUnit4.class) public class GenRuleConfiguredTargetTest extends BuildViewTestCase { /** Filter to remove implicit dependencies of C/C++ rules. */ private static final Predicate<ConfiguredTarget> CC_CONFIGURED_TARGET_FILTER = new Predicate<ConfiguredTarget>() { @Override public boolean apply(ConfiguredTarget target) { return AnalysisMock.get().ccSupport().labelFilter().apply(target.getLabel()); } }; /** Filter to remove implicit dependencies of Java rules. */ private static final Predicate<ConfiguredTarget> JAVA_CONFIGURED_TARGET_FILTER = new Predicate<ConfiguredTarget>() { @Override public boolean apply(ConfiguredTarget target) { Label label = target.getLabel(); String labelName = "//" + label.getPackageName(); return !labelName.startsWith("//third_party/java/jdk"); } }; private static final Pattern SETUP_COMMAND_PATTERN = Pattern.compile(".*/genrule-setup.sh;\\s+(?<command>.*)"); private void assertCommandEquals(String expected, String command) { // Ensure the command after the genrule setup is correct. Matcher m = SETUP_COMMAND_PATTERN.matcher(command); if (m.matches()) { command = m.group("command"); } assertThat(command).isEqualTo(expected); } public void createFiles() throws Exception { scratch.file( "hello/BUILD", "genrule(", " name = 'z',", " outs = ['x/y'],", " cmd = 'echo hi > $(@D)/y',", ")", "genrule(", " name = 'w',", " outs = ['a/b', 'c/d'],", " cmd = 'echo hi | tee $(@D)/a/b $(@D)/c/d',", ")"); } @Test public void testD() throws Exception { createFiles(); ConfiguredTarget z = getConfiguredTarget("//hello:z"); Artifact y = getOnlyElement(getFilesToBuild(z)); assertEquals(PathFragment.create("hello/x/y"), y.getRootRelativePath()); } @Test public void testDMultiOutput() throws Exception { createFiles(); ConfiguredTarget z = getConfiguredTarget("//hello:w"); List<Artifact> files = getFilesToBuild(z).toList(); assertThat(files).hasSize(2); assertEquals(PathFragment.create("hello/a/b"), files.get(0).getRootRelativePath()); assertEquals(PathFragment.create("hello/c/d"), files.get(1).getRootRelativePath()); } @Test public void testOutsWithSameNameAsRule() throws Exception { // The error was demoted to a warning. // Re-enable after June 1 2008 when we make it an error again. checkWarning( "genrule2", "hello_world", "target 'hello_world' is both a rule and a file;", "genrule(name = 'hello_world',", "srcs = ['ignore_me.txt'],", "outs = ['message.txt', 'hello_world'],", "cmd = 'echo \"Hello, world.\" >$(location message.txt)')"); } @Test public void testFilesToBuildIsOuts() throws Exception { scratch.file( "genrule1/BUILD", "genrule(name = 'hello_world',", "srcs = ['ignore_me.txt'],", "outs = ['message.txt'],", "cmd = 'echo \"Hello, world.\" >$(location message.txt)')"); Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact(); assertThat(getFilesToBuild(getConfiguredTarget("//genrule1:hello_world"))) .containsExactly(messageArtifact); } @Test public void testActionIsShellCommand() throws Exception { scratch.file( "genrule1/BUILD", "genrule(name = 'hello_world',", "srcs = ['ignore_me.txt'],", "outs = ['message.txt'],", "cmd = 'echo \"Hello, world.\" >$(location message.txt)')"); Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact(); SpawnAction shellAction = (SpawnAction) getGeneratingAction(messageArtifact); Artifact ignoreMeArtifact = getFileConfiguredTarget("//genrule1:ignore_me.txt").getArtifact(); Artifact genruleSetupArtifact = getFileConfiguredTarget(GENRULE_SETUP).getArtifact(); assertNotNull(shellAction); assertEquals( Sets.newHashSet(ignoreMeArtifact, genruleSetupArtifact), Sets.newHashSet(shellAction.getInputs())); assertEquals(Sets.newHashSet(messageArtifact), Sets.newHashSet(shellAction.getOutputs())); String expected = "echo \"Hello, world.\" >" + messageArtifact.getExecPathString(); assertEquals( targetConfig.getShellExecutable().getPathString(), shellAction.getArguments().get(0)); assertEquals("-c", shellAction.getArguments().get(1)); assertCommandEquals(expected, shellAction.getArguments().get(2)); } @Test public void testDependentGenrule() throws Exception { scratch.file( "genrule1/BUILD", "genrule(name = 'hello_world',", "srcs = ['ignore_me.txt'],", "outs = ['message.txt'],", "cmd = 'echo \"Hello, world.\" >$(location message.txt)')"); scratch.file( "genrule2/BUILD", "genrule(name = 'goodbye_world',", "srcs = ['goodbye.txt', '//genrule1:hello_world'],", "outs = ['farewell.txt'],", "cmd = 'echo $(SRCS) >$(location farewell.txt)')"); getConfiguredTarget("//genrule2:goodbye_world"); Artifact farewellArtifact = getFileConfiguredTarget("//genrule2:farewell.txt").getArtifact(); Artifact goodbyeArtifact = getFileConfiguredTarget("//genrule2:goodbye.txt").getArtifact(); Artifact messageArtifact = getFileConfiguredTarget("//genrule1:message.txt").getArtifact(); Artifact genruleSetupArtifact = getFileConfiguredTarget(GENRULE_SETUP).getArtifact(); SpawnAction shellAction = (SpawnAction) getGeneratingAction(farewellArtifact); // inputs = { "goodbye.txt", "//genrule1:message.txt" } assertEquals( Sets.newHashSet(goodbyeArtifact, messageArtifact, genruleSetupArtifact), Sets.newHashSet(shellAction.getInputs())); // outputs = { "farewell.txt" } assertEquals(Sets.newHashSet(farewellArtifact), Sets.newHashSet(shellAction.getOutputs())); String expected = "echo " + goodbyeArtifact.getExecPathString() + " " + messageArtifact.getExecPathString() + " >" + farewellArtifact.getExecPathString(); assertCommandEquals(expected, shellAction.getArguments().get(2)); } /** * Ensure that the actions / artifacts created by genrule dependencies allow us to follow the * chain of generated files backward. */ @Test public void testDependenciesViaFiles() throws Exception { scratch.file( "foo/BUILD", "genrule(name = 'bar',", " srcs = ['bar_in.txt'],", " cmd = 'touch $(OUTS)',", " outs = ['bar_out.txt'])", "genrule(name = 'baz',", " srcs = ['bar_out.txt'],", " cmd = 'touch $(OUTS)',", " outs = ['baz_out.txt'])"); FileConfiguredTarget bazOutTarget = getFileConfiguredTarget("//foo:baz_out.txt"); Action bazAction = getGeneratingAction(bazOutTarget.getArtifact()); Artifact barOut = bazAction.getInputs().iterator().next(); assertTrue(barOut.getExecPath().endsWith(PathFragment.create("foo/bar_out.txt"))); Action barAction = getGeneratingAction(barOut); Artifact barIn = barAction.getInputs().iterator().next(); assertTrue(barIn.getExecPath().endsWith(PathFragment.create("foo/bar_in.txt"))); } /** Ensure that variable $(@D) gets expanded correctly in the genrule cmd. */ @Test public void testOutputDirExpansion() throws Exception { scratch.file( "foo/BUILD", "genrule(name = 'bar',", " srcs = ['bar_in.txt'],", " cmd = 'touch $(@D)',", " outs = ['bar/bar_out.txt'])", "genrule(name = 'baz',", " srcs = ['bar/bar_out.txt'],", " cmd = 'touch $(@D)',", " outs = ['logs/baz_out.txt', 'logs/baz.log'])"); getConfiguredTarget("//foo:bar"); FileConfiguredTarget bazOutTarget = getFileConfiguredTarget("//foo:logs/baz_out.txt"); SpawnAction bazAction = (SpawnAction) getGeneratingAction(bazOutTarget.getArtifact()); // Make sure the expansion for $(@D) results in the // directory of the BUILD file ("foo"), not the common parent // directory of the output files ("logs") String bazExpected = "touch " + bazOutTarget .getArtifact() .getExecPath() .getParentDirectory() .getParentDirectory() .getPathString(); assertCommandEquals(bazExpected, bazAction.getArguments().get(2)); assertThat(bazAction.getArguments().get(2)).endsWith("/foo"); getConfiguredTarget("//foo:bar"); Artifact barOut = bazAction.getInputs().iterator().next(); assertTrue(barOut.getExecPath().endsWith(PathFragment.create("foo/bar/bar_out.txt"))); SpawnAction barAction = (SpawnAction) getGeneratingAction(barOut); String barExpected = "touch " + barOut.getExecPath().getParentDirectory().getPathString(); assertCommandEquals(barExpected, barAction.getArguments().get(2)); assertFalse(bazExpected.equals(barExpected)); } /** Ensure that variable $(CC) gets expanded correctly in the genrule cmd. */ @Test public void testMakeVarExpansion() throws Exception { scratch.file( "foo/BUILD", "genrule(name = 'bar',", " srcs = ['bar.cc'],", " cmd = '$(CC) -o $(OUTS) $(SRCS) $$shellvar',", " outs = ['bar.o'])"); FileConfiguredTarget barOutTarget = getFileConfiguredTarget("//foo:bar.o"); FileConfiguredTarget barInTarget = getFileConfiguredTarget("//foo:bar.cc"); SpawnAction barAction = (SpawnAction) getGeneratingAction(barOutTarget.getArtifact()); String cc = "" + targetConfig.getFragment(CppConfiguration.class).getCppExecutable(); String expected = cc + " -o " + barOutTarget.getArtifact().getExecPathString() + " " + barInTarget.getArtifact().getRootRelativePath().getPathString() + " $shellvar"; assertCommandEquals(expected, barAction.getArguments().get(2)); } @Test public void onlyHasCcToolchainDepWhenCcMakeVariablesArePresent() throws Exception { scratch.file( "foo/BUILD", "genrule(name = 'no_cc',", " srcs = [],", " cmd = 'echo no CC variables here > $@',", " outs = ['no_cc.out'])", "genrule(name = 'cc',", " srcs = [],", " cmd = 'echo $(CC) > $@',", " outs = ['cc.out'])"); String ccToolchainAttr = ":cc_toolchain"; assertThat(getPrerequisites(getConfiguredTarget("//foo:no_cc"), ccToolchainAttr)).isEmpty(); assertThat(getPrerequisites(getConfiguredTarget("//foo:cc"), ccToolchainAttr)).isNotEmpty(); } /** Ensure that Java make variables get expanded under the *host* configuration. */ @Test public void testJavaMakeVarExpansion() throws Exception { String ruleTemplate = "genrule(name = '%s'," + " srcs = []," + " cmd = 'echo $(%s) > $@'," + " outs = ['%s'])"; scratch.file( "foo/BUILD", String.format(ruleTemplate, "java_rule", "JAVA", "java.txt"), String.format(ruleTemplate, "javabase_rule", "JAVABASE", "javabase.txt")); Artifact javaOutput = getFileConfiguredTarget("//foo:java.txt").getArtifact(); Artifact javabaseOutput = getFileConfiguredTarget("//foo:javabase.txt").getArtifact(); String expectedPattern = "echo %s > %s"; BuildConfiguration hostConfig = getHostConfiguration(); String expectedJava = hostConfig.getFragment(Jvm.class).getJavaExecutable().getPathString(); String expectedJavabase = hostConfig.getFragment(Jvm.class).getJavaHome().getPathString(); assertCommandEquals( String.format(expectedPattern, expectedJava, javaOutput.getExecPathString()), ((SpawnAction) getGeneratingAction(javaOutput)).getArguments().get(2)); assertCommandEquals( String.format(expectedPattern, expectedJavabase, javabaseOutput.getExecPathString()), ((SpawnAction) getGeneratingAction(javabaseOutput)).getArguments().get(2)); } // Returns the expansion of 'cmd' for the specified genrule. private String getCommand(String label) throws Exception { return getSpawnAction(label).getArguments().get(2); } // Returns the SpawnAction for the specified genrule. private SpawnAction getSpawnAction(String label) throws Exception { return (SpawnAction) getGeneratingAction(getFilesToBuild(getConfiguredTarget(label)).iterator().next()); } @Test public void testMessage() throws Exception { scratch.file( "genrule3/BUILD", "genrule(name = 'hello_world',", " srcs = ['ignore_me.txt'],", " outs = ['hello.txt'],", " cmd = 'echo \"Hello, world.\" >hello.txt')", "genrule(name = 'goodbye_world',", " srcs = ['ignore_me.txt'],", " outs = ['goodbye.txt'],", " message = 'Generating message',", " cmd = 'echo \"Goodbye, world.\" >goodbye.txt')"); assertEquals( "Executing genrule //genrule3:hello_world", getSpawnAction("//genrule3:hello_world").getProgressMessage()); assertEquals( "Generating message //genrule3:goodbye_world", getSpawnAction("//genrule3:goodbye_world").getProgressMessage()); } /** Ensure that labels from binary targets expand to the executable */ @Test public void testBinaryTargetsExpandToExecutable() throws Exception { scratch.file( "genrule3/BUILD", "genrule(name = 'hello_world',", " srcs = ['ignore_me.txt'],", " tools = ['echo'],", " outs = ['message.txt'],", " cmd = '$(location :echo) \"Hello, world.\" >message.txt')", "cc_binary(name = 'echo',", " srcs = ['echo.cc'])"); String regex = "b.{4}-out/.*/bin/genrule3/echo(\\.exe)? \"Hello, world.\" >message.txt"; assertThat(getCommand("//genrule3:hello_world")).containsMatch(regex); } @Test public void testOutputToBindir() throws Exception { scratch.file( "x/BUILD", "genrule(name='bin', ", " outs=['bin.out'],", " cmd=':',", " output_to_bindir=1)", "genrule(name='genfiles', ", " outs=['genfiles.out'],", " cmd=':',", " output_to_bindir=0)"); assertEquals( getBinArtifact("bin.out", "//x:bin"), getFileConfiguredTarget("//x:bin.out").getArtifact()); assertEquals( getGenfilesArtifact("genfiles.out", "//x:genfiles"), getFileConfiguredTarget("//x:genfiles.out").getArtifact()); } @Test public void testMultipleOutputsToBindir() throws Exception { scratch.file( "x/BUILD", "genrule(name='bin', ", " outs=['bin_a.out', 'bin_b.out'],", " cmd=':',", " output_to_bindir=1)", "genrule(name='genfiles', ", " outs=['genfiles_a.out', 'genfiles_b.out'],", " cmd=':',", " output_to_bindir=0)"); assertEquals( getBinArtifact("bin_a.out", "//x:bin"), getFileConfiguredTarget("//x:bin_a.out").getArtifact()); assertEquals( getBinArtifact("bin_b.out", "//x:bin"), getFileConfiguredTarget("//x:bin_b.out").getArtifact()); assertEquals( getGenfilesArtifact("genfiles_a.out", "//x:genfiles"), getFileConfiguredTarget("//x:genfiles_a.out").getArtifact()); assertEquals( getGenfilesArtifact("genfiles_b.out", "//x:genfiles"), getFileConfiguredTarget("//x:genfiles_b.out").getArtifact()); } @Test public void testMultipleOutsPreservesOrdering() throws Exception { scratch.file( "multiple/outs/BUILD", "genrule(name='test', ", " outs=['file1.out', 'file2.out'],", " cmd='touch $(OUTS)')"); String regex = "touch b.{4}-out/.*/genfiles/multiple/outs/file1.out " + "b.{4}-out/.*/genfiles/multiple/outs/file2.out"; assertThat(getCommand("//multiple/outs:test")).containsMatch(regex); } @Test public void testToolsAreHostConfiguration() throws Exception { scratch.file( "config/BUILD", "genrule(name='src', outs=['src.out'], cmd=':')", "genrule(name='tool', outs=['tool.out'], cmd=':')", "genrule(name='config', ", " srcs=[':src'], tools=[':tool'], outs=['out'],", " cmd='$(location :tool)')"); Iterable<ConfiguredTarget> prereqs = Iterables.filter( Iterables.filter( getDirectPrerequisites(getConfiguredTarget("//config")), CC_CONFIGURED_TARGET_FILTER), JAVA_CONFIGURED_TARGET_FILTER); boolean foundSrc = false; boolean foundTool = false; boolean foundSetup = false; for (ConfiguredTarget prereq : prereqs) { String name = prereq.getLabel().getName(); if (name.startsWith("cc-") || name.startsWith("jdk-")) { // Ignore these, they are present due to the implied genrule dependency on crosstool and // JDK. continue; } switch (name) { case "src": assertConfigurationsEqual(getTargetConfiguration(), prereq.getConfiguration()); foundSrc = true; break; case "tool": assertTrue(getHostConfiguration().equalsOrIsSupersetOf(prereq.getConfiguration())); foundTool = true; break; case GENRULE_SETUP_PATH: assertNull(prereq.getConfiguration()); foundSetup = true; break; default: fail("unexpected prerequisite " + prereq + " (name: " + name + ")"); } } assertThat(foundSrc).isTrue(); assertThat(foundTool).isTrue(); assertThat(foundSetup).isTrue(); } @Test public void testLabelsContainingAtDAreExpanded() throws Exception { scratch.file( "p/BUILD", "genrule(name='gen', ", " tools=['p'],", " outs=['out'],", " cmd='echo $(@D)')"); String regex = "echo b.{4}-out/.*/genfiles/p"; assertThat(getCommand("//p:gen")).containsMatch(regex); } @Test public void testGetExecutable() throws Exception { ConfiguredTarget turtle = scratchConfiguredTarget( "java/com/google/turtle", "turtle_bootstrap", "genrule(name = 'turtle_bootstrap',", " srcs = ['Turtle.java'],", " outs = ['turtle'],", " executable = 1,", " cmd = 'touch $(OUTS)')"); assertEquals("turtle", getExecutable(turtle).getExecPath().getBaseName()); } @Test public void testGetExecutableForNonExecutableOut() throws Exception { ConfiguredTarget turtle = scratchConfiguredTarget( "java/com/google/turtle", "turtle_bootstrap", "genrule(name = 'turtle_bootstrap',", " srcs = ['Turtle.java'],", " outs = ['debugdata.txt'],", " cmd = 'touch $(OUTS)')"); assertNull(getExecutable(turtle)); } @Test public void testGetExecutableForMultipleOuts() throws Exception { ConfiguredTarget turtle = scratchConfiguredTarget( "java/com/google/turtle", "turtle_bootstrap", "genrule(name = 'turtle_bootstrap',", " srcs = ['Turtle.java'],", " outs = ['turtle', 'debugdata.txt'],", " cmd = 'touch $(OUTS)')"); assertNull(getExecutable(turtle)); } @Test public void testGetExecutableFailsForMultipleOutputs() throws Exception { // Multiple output files are invalid when executable=1. checkError( "bad", "bad", "in executable attribute of genrule rule //bad:bad: " + "if genrules produce executables, they are allowed only one output. " + "If you need the executable=1 argument, then you should split this genrule into " + "genrules producing single outputs", "genrule(name = 'bad',", " outs = [ 'bad_out1', 'bad_out2' ],", " executable = 1,", " cmd = 'touch $(OUTS)')"); } @Test public void testEmptyOutsError() throws Exception { checkError( "x", "x", "Genrules without outputs don't make sense", "genrule(name = 'x', outs = [], cmd='echo')"); } @Test public void testGenruleSetup() throws Exception { scratch.file( "foo/BUILD", "genrule(name = 'foo_sh',", " outs = [ 'foo.sh' ],", // Shell script files are known to be executable. " cmd = 'touch $@')"); assertThat(getCommand("//foo:foo_sh")).contains(GENRULE_SETUP_PATH); } private void createStampingTargets() throws Exception { scratch.file( "u/BUILD", "genrule(name='foo_stamp', srcs=[], outs=['uu'], stamp=1, cmd='')", "genrule(name='foo_nostamp', srcs=[], outs=['vv'], stamp=0, cmd='')", "genrule(name='foo_default', srcs=[], outs=['xx'], cmd='')"); } private void assertStamped(String target) throws Exception { assertStamped(getConfiguredTarget(target)); } private void assertNotStamped(String target) throws Exception { assertNotStamped(getConfiguredTarget(target)); } private void assertStamped(ConfiguredTarget target) throws Exception { Artifact out = Iterables.getFirst(getFilesToBuild(target), null); List<String> inputs = ActionsTestUtil.baseArtifactNames(getGeneratingAction(out).getInputs()); assertThat(inputs).containsAllIn(ImmutableList.of("build-info.txt", "build-changelist.txt")); } private void assertNotStamped(ConfiguredTarget target) throws Exception { Artifact out = Iterables.getFirst(getFilesToBuild(target), null); List<String> inputs = ActionsTestUtil.baseArtifactNames(getGeneratingAction(out).getInputs()); assertThat(inputs).doesNotContain("build-info.txt"); assertThat(inputs).doesNotContain("build-changelist.txt"); } @Test public void testStampingWithNoStamp() throws Exception { useConfiguration("--nostamp"); createStampingTargets(); assertStamped("//u:foo_stamp"); assertStamped(getHostConfiguredTarget("//u:foo_stamp")); assertNotStamped("//u:foo_nostamp"); assertNotStamped(getHostConfiguredTarget("//u:foo_nostamp")); assertNotStamped("//u:foo_default"); } @Test public void testStampingWithStamp() throws Exception { useConfiguration("--stamp"); createStampingTargets(); assertStamped("//u:foo_stamp"); assertStamped(getHostConfiguredTarget("//u:foo_stamp")); //assertStamped("//u:foo_nostamp"); assertNotStamped(getHostConfiguredTarget("//u:foo_nostamp")); assertNotStamped("//u:foo_default"); } @Test public void testRequiresDarwin() throws Exception { scratch.file( "foo/BUILD", "genrule(name='darwin', srcs=[], outs=['macout'], cmd='', tags=['requires-darwin'])"); SpawnAction action = getSpawnAction("//foo:darwin"); assertThat(action.getExecutionInfo().keySet()).contains("requires-darwin"); // requires-darwin causes /bin/bash to be hard-coded, see CommandHelper.shellPath(). assertThat(action.getCommandFilename()) .isEqualTo("/bin/bash"); } @Test public void testJarError() throws Exception { checkError( "foo", "grj", "in cmd attribute of genrule rule //foo:grj: $(JAR) not defined", "genrule(name='grj'," + " srcs = []," + " outs=['grj']," + " cmd='$(JAR) foo bar')"); } /** Regression test for b/15589451. */ @Test public void testDuplicateLocalFlags() throws Exception { scratch.file( "foo/BUILD", "genrule(name='g'," + " srcs = []," + " outs = ['grj']," + " cmd ='echo g'," + " local = 1," + " tags = ['local'])"); getConfiguredTarget("//foo:g"); assertNoEvents(); } }