From 17a1ae9efd999cde0fd7d184f06512ba4b9e8f35 Mon Sep 17 00:00:00 2001 From: John Cater Date: Wed, 25 Jan 2017 15:18:25 +0000 Subject: [PATCH] (Re)-Add tests for GenRule. Reverts commit 4bf8cc30a572018ac27101396d18686b75ad1ab5. With fix for https://github.com/bazelbuild/bazel/issues/2408. -- PiperOrigin-RevId: 145544771 MOS_MIGRATED_REVID=145544771 --- .../lib/bazel/rules/genrule/BazelGenRule.java | 3 +- .../java/com/google/devtools/build/lib/BUILD | 2 + .../GenRuleCommandSubstitutionTest.java | 478 +++++++++++++ .../genrule/GenRuleConfiguredTargetTest.java | 669 ++++++++++++++++++ .../genrule/GenruleConfiguredTargetTest.java | 68 -- .../build/lib/testutil/TestConstants.java | 4 + 6 files changed, 1155 insertions(+), 69 deletions(-) create mode 100644 src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleCommandSubstitutionTest.java create mode 100644 src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java delete mode 100644 src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenruleConfiguredTargetTest.java diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/genrule/BazelGenRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/genrule/BazelGenRule.java index d257ccbbe77715..a13c022db827dc 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/genrule/BazelGenRule.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/genrule/BazelGenRule.java @@ -127,7 +127,8 @@ public ConfiguredTarget create(RuleContext ruleContext) FilesToRunProvider genruleSetup = ruleContext.getPrerequisite("$genrule_setup", Mode.HOST, FilesToRunProvider.class); inputs.addAll(genruleSetup.getFilesToRun()); - List argv = commandHelper.buildCommandLine(command, inputs, ".genrule_script.sh"); + List argv = commandHelper.buildCommandLine(command, inputs, ".genrule_script.sh", + ImmutableMap.copyOf(executionInfo)); if (ruleContext.attributes().get("stamp", Type.BOOLEAN)) { inputs.add(ruleContext.getAnalysisEnvironment().getStableWorkspaceStatusArtifact()); diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD index faf0a0faabaef3..1bcbec9eca171e 100644 --- a/src/test/java/com/google/devtools/build/lib/BUILD +++ b/src/test/java/com/google/devtools/build/lib/BUILD @@ -1024,8 +1024,10 @@ java_test( "//src/main/java/com/google/devtools/build/lib:vfs", "//src/main/java/com/google/devtools/build/lib/actions", "//src/main/java/com/google/devtools/build/lib/cmdline", + "//src/main/java/com/google/devtools/build/lib/rules/cpp", "//src/main/protobuf:crosstool_config_java_proto", "//src/test/java/com/google/devtools/build/lib:actions_testutil", + "//src/test/java/com/google/devtools/build/lib:packages_testutil", "//third_party:guava", "//third_party:junit4", "//third_party:truth", diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleCommandSubstitutionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleCommandSubstitutionTest.java new file mode 100644 index 00000000000000..a17c2ebb92a76f --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleCommandSubstitutionTest.java @@ -0,0 +1,478 @@ +// 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 org.junit.Assert.assertEquals; + +import com.google.common.base.Joiner; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * A unit test of the various kinds of label and "Make"-variable substitutions that are applied to + * the genrule "cmd" attribute. + * + *

Some of these tests are similar to tests in LabelExpanderTest and MakeVariableExpanderTest, + * but this test case exercises the composition of these various transformations. + */ +@RunWith(JUnit4.class) +public class GenRuleCommandSubstitutionTest extends BuildViewTestCase { + + private static final Pattern SETUP_COMMAND_PATTERN = + Pattern.compile(".*/genrule-setup.sh;\\s+(?.*)"); + + private String getGenruleCommand(String genrule) throws Exception { + return ((SpawnAction) + getGeneratingAction(getFilesToBuild(getConfiguredTarget(genrule)).iterator().next())) + .getArguments() + .get(2); + } + + private void assertExpansionEquals(String expected, String genrule) throws Exception { + String command = getGenruleCommand(genrule); + assertCommandEquals(expected, 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"); + } + + assertEquals( + "Expected command to be \"" + expected + "\", but found \"" + command + "\"", + expected, + command); + } + + private void assertExpansionFails(String expectedErrorSuffix, String genrule) throws Exception { + reporter.removeHandler(failFastHandler); // we expect errors + eventCollector.clear(); + getConfiguredTarget(genrule); + assertContainsEvent(expectedErrorSuffix); + } + + // Creates a BUILD file defining a genrule called "//test" with no srcs or + // deps, one output and the specified command. + private void genrule(String command) throws Exception { + scratch.overwriteFile( + "test/BUILD", + "genrule(name = 'test',", + " outs = ['out'],", + " cmd = '" + command + "')"); + + // Since we're probably re-defining "//test": + invalidatePackages(); + } + + @Test + public void testLocationSyntaxErrors() throws Exception { + genrule("$(location )"); + assertExpansionFails( + "invalid label in $(location) expression: empty package-relative label", "//test"); + + eventCollector.clear(); + + genrule("$(location foo bar"); + assertExpansionFails("unterminated $(location) expression", "//test"); + + genrule("$(location"); + assertExpansionFails("unterminated variable reference", "//test"); + + genrule("$(locationz"); + assertExpansionFails("unterminated variable reference", "//test"); + + genrule("$(locationz)"); + assertExpansionFails("$(locationz) not defined", "//test"); + + genrule("$(locationz )"); + assertExpansionFails("$(locationz ) not defined", "//test"); + + genrule("$(locationz foo )"); + assertExpansionFails("$(locationz foo ) not defined", "//test"); + } + + @Test + public void testLocationOfLabelThatIsNotAPrerequsite() throws Exception { + scratch.file( + "test/BUILD", + "exports_files(['exists'])", + "genrule(name = 'test1',", + " outs = ['test1.out'],", + " cmd = '$(location :exists)')", + "genrule(name = 'test2',", + " outs = ['test2.out'],", + " cmd = '$(location :doesnt_exist)')"); + + // $(location) of a non-prerequisite fails, even if the target exists: + + assertExpansionFails( + "label '//test:exists' in $(location) expression is " + + "not a declared prerequisite of this rule", + "//test:test1"); + + assertExpansionFails( + "label '//test:doesnt_exist' in $(location) expression is " + + "not a declared prerequisite of this rule", + "//test:test2"); + } + + @Test + public void testLocationOfMultiFileLabel() throws Exception { + scratch.file( + "deuce/BUILD", + "genrule(name = 'deuce',", + " outs = ['out.1', 'out.2'],", + " cmd = ':')"); + checkError( + "test", + "test1", + "label '//deuce:deuce' in $(location) expression expands to more than one " + + "file, please use $(locations //deuce:deuce) instead", + "genrule(name = 'test1',", + " tools = ['//deuce'],", + " outs = ['test1.out'],", + " cmd = '$(location //deuce)')"); + } + + @Test + public void testUnknownVariable() throws Exception { + genrule("$(UNKNOWN)"); + assertExpansionFails("$(UNKNOWN) not defined", "//test"); + } + + @Test + public void testLocationOfSourceLabel() throws Exception { + scratch.file( + "test1/BUILD", + "genrule(name = 'test1',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(location //test1:src)')"); + assertExpansionEquals("test1/src", "//test1"); + + scratch.file( + "test2/BUILD", + "genrule(name = 'test2',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(location src)')"); + assertExpansionEquals("test2/src", "//test2"); + + scratch.file( + "test3/BUILD", + "genrule(name = 'test3',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(location :src)')"); + assertExpansionEquals("test3/src", "//test3"); + } + + @Test + public void testLocationOfOutputLabel() throws Exception { + String gendir = targetConfig.getMakeVariableDefault("GENDIR"); + scratch.file( + "test1/BUILD", + "genrule(name = 'test1',", + " outs = ['out'],", + " cmd = '$(location //test1:out)')"); + assertExpansionEquals(gendir + "/test1/out", "//test1"); + + scratch.file( + "test2/BUILD", + "genrule(name = 'test2',", + " outs = ['out'],", + " cmd = '$(location out)')"); + assertExpansionEquals(gendir + "/test2/out", "//test2"); + + scratch.file( + "test3/BUILD", + "genrule(name = 'test3',", + " outs = ['out'],", + " cmd = '$(location out)')"); + assertExpansionEquals(gendir + "/test3/out", "//test3"); + } + + @Test + public void testLocationsSyntaxErrors() throws Exception { + genrule("$(locations )"); + assertExpansionFails( + "invalid label in $(locations) expression: empty package-relative label", "//test"); + + eventCollector.clear(); + + genrule("$(locations foo bar"); + assertExpansionFails("unterminated $(locations) expression", "//test"); + + genrule("$(locations"); + assertExpansionFails("unterminated variable reference", "//test"); + + genrule("$(locationsz"); + assertExpansionFails("unterminated variable reference", "//test"); + + genrule("$(locationsz)"); + assertExpansionFails("$(locationsz) not defined", "//test"); + + genrule("$(locationsz )"); + assertExpansionFails("$(locationsz ) not defined", "//test"); + + genrule("$(locationsz foo )"); + assertExpansionFails("$(locationsz foo ) not defined", "//test"); + } + + @Test + public void testLocationsOfLabelThatIsNotAPrerequsite() throws Exception { + scratch.file( + "test/BUILD", + "exports_files(['exists'])", + "genrule(name = 'test1',", + " outs = ['test1.out'],", + " cmd = '$(locations :exists)')", + "genrule(name = 'test2',", + " outs = ['test2.out'],", + " cmd = '$(locations :doesnt_exist)')"); + + // $(locations) of a non-prerequisite fails, even if the target exists: + + assertExpansionFails( + "label '//test:exists' in $(locations) expression is " + + "not a declared prerequisite of this rule", + "//test:test1"); + + assertExpansionFails( + "label '//test:doesnt_exist' in $(locations) expression is " + + "not a declared prerequisite of this rule", + "//test:test2"); + } + + @Test + public void testLocationsOfMultiFileLabel() throws Exception { + String gendir = targetConfig.getMakeVariableDefault("GENDIR"); + scratch.file( + "test/BUILD", + "genrule(name = 'x',", + " srcs = ['src'],", + " outs = ['out1', 'out2'],", + " cmd = ':')", + "genrule(name = 'y',", + " srcs = ['x'],", + " outs = ['out'],", + " cmd = '$(locations x)')"); + + assertExpansionEquals(gendir + "/test/out1 " + gendir + "/test/out2", "//test:y"); + } + + @Test + public void testLocationLocationsAndLabel() throws Exception { + String gendir = targetConfig.getMakeVariableDefault("GENDIR"); + scratch.file( + "test/BUILD", + "genrule(name = 'x',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = ':')", + "genrule(name = 'y',", + " srcs = ['src'],", + " outs = ['out1', 'out2'],", + " cmd = ':')", + "genrule(name = 'r',", + " srcs = ['x', 'y', 'z'],", + " outs = ['res'],", + " cmd = ' _ $(location x) _ $(locations y) _ ')"); + + String expected = + "_ " + gendir + "/test/out _ " + gendir + "/test/out1 " + gendir + "/test/out2 _ "; + assertExpansionEquals(expected, "//test:r"); + } + + @Test + public void testLocationsOfSourceLabel() throws Exception { + scratch.file( + "test1/BUILD", + "genrule(name = 'test1',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(locations //test1:src)')"); + assertExpansionEquals("test1/src", "//test1"); + + scratch.file( + "test2/BUILD", + "genrule(name = 'test2',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(locations src)')"); + assertExpansionEquals("test2/src", "//test2"); + + scratch.file( + "test3/BUILD", + "genrule(name = 'test3',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(location :src)')"); + assertExpansionEquals("test3/src", "//test3"); + } + + @Test + public void testLocationsOfOutputLabel() throws Exception { + String gendir = targetConfig.getMakeVariableDefault("GENDIR"); + scratch.file( + "test1/BUILD", + "genrule(name = 'test1',", + " outs = ['out'],", + " cmd = '$(locations //test1:out)')"); + assertExpansionEquals(gendir + "/test1/out", "//test1"); + + scratch.file( + "test2/BUILD", + "genrule(name = 'test2',", + " outs = ['out'],", + " cmd = '$(locations out)')"); + assertExpansionEquals(gendir + "/test2/out", "//test2"); + + scratch.file( + "test3/BUILD", + "genrule(name = 'test3',", + " outs = ['out'],", + " cmd = '$(locations out)')"); + assertExpansionEquals(gendir + "/test3/out", "//test3"); + } + + @Test + public void testOuts() throws Exception { + String expected = targetConfig.getMakeVariableDefault("GENDIR") + "/test/out"; + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " outs = ['out'],", + " cmd = '$(OUTS) # $@')"); + assertExpansionEquals(expected + " # " + expected, "//test"); + } + + @Test + public void testSrcs() throws Exception { + String expected = "test/src"; + + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " srcs = ['src'],", + " outs = ['out'],", + " cmd = '$(SRCS) # $<')"); + assertExpansionEquals(expected + " # " + expected, "//test"); + } + + @Test + public void testDollarDollar() throws Exception { + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " outs = ['out'],", + " cmd = '$$DOLLAR')"); + assertExpansionEquals("$DOLLAR", "//test"); + } + + @Test + public void testDollarLessThanWithZeroInputs() throws Exception { + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " outs = ['out'],", + " cmd = '$<')"); + assertExpansionFails("variable '$<' : no input file", "//test"); + } + + @Test + public void testDollarLessThanWithMultipleInputs() throws Exception { + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " srcs = ['src1', 'src2'],", + " outs = ['out'],", + " cmd = '$<')"); + assertExpansionFails("variable '$<' : more than one input file", "//test"); + } + + @Test + public void testDollarAtWithMultipleOutputs() throws Exception { + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " outs = ['out.1', 'out.2'],", + " cmd = '$@')"); + assertExpansionFails("variable '$@' : more than one output file", "//test"); + } + + @Test + public void testDollarAtWithZeroOutputs() throws Exception { + scratch.file( + "test/BUILD", + "genrule(name = 'test',", + " srcs = ['src1', 'src2'],", + " outs = [],", + " cmd = '$@')"); + assertExpansionFails("Genrules without outputs don't make sense", "//test"); + } + + @Test + public void testShellVariables() throws Exception { + genrule("for file in a b c;do echo $$file;done"); + assertExpansionEquals("for file in a b c;do echo $file;done", "//test"); + assertNoEvents(); + + genrule("$${file%:.*8}"); + assertExpansionEquals("${file%:.*8}", "//test"); + assertNoEvents(); + + genrule("$$(basename file)"); + assertExpansionEquals("$(basename file)", "//test"); + assertNoEvents(); + + genrule("$(basename file)"); + assertExpansionFails("$(basename file) not defined", "//test"); + assertContainsEvent("$(basename file) not defined"); + } + + @Test + public void testDollarFileFails() throws Exception { + checkError( + "test", + "test", + "'$file' syntax is not supported; use '$(file)' ", + getBuildFileWithCommand("for file in a b c;do echo $file;done")); + } + + @Test + public void testDollarFile2Fails() throws Exception { + checkError( + "test", + "test", + "'${file%:.*8}' syntax is not supported; use '$(file%:.*8)' ", + getBuildFileWithCommand("${file%:.*8}")); + } + + private String getBuildFileWithCommand(String command) { + return Joiner.on("\n") + .join( + "genrule(name = 'test',", + " outs = ['out'],", + " cmd = '" + command + "')"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java new file mode 100644 index 00000000000000..b83428a0dc2c03 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java @@ -0,0 +1,669 @@ +// 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 CC_CONFIGURED_TARGET_FILTER = + new Predicate() { + @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 JAVA_CONFIGURED_TARGET_FILTER = + new Predicate() { + @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+(?.*)"); + + 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(new PathFragment("hello/x/y"), y.getRootRelativePath()); + } + + @Test + public void testDMultiOutput() throws Exception { + createFiles(); + ConfiguredTarget z = getConfiguredTarget("//hello:w"); + List files = getFilesToBuild(z).toList(); + assertThat(files).hasSize(2); + assertEquals(new PathFragment("hello/a/b"), files.get(0).getRootRelativePath()); + assertEquals(new PathFragment("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(new PathFragment("foo/bar_out.txt"))); + Action barAction = getGeneratingAction(barOut); + Artifact barIn = barAction.getInputs().iterator().next(); + assertTrue(barIn.getExecPath().endsWith(new PathFragment("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(new PathFragment("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)); + } + + /** 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 prereqs = + Iterables.filter( + Iterables.filter( + getDirectPrerequisites(getConfiguredTarget("//config")), + CC_CONFIGURED_TARGET_FILTER), + JAVA_CONFIGURED_TARGET_FILTER); + + for (ConfiguredTarget prereq : prereqs) { + String name = prereq.getLabel().getName(); + switch (name) { + case "src": + assertConfigurationsEqual(getTargetConfiguration(), prereq.getConfiguration()); + break; + case "tool": + assertTrue(getHostConfiguration().equalsOrIsSupersetOf(prereq.getConfiguration())); + break; + case GENRULE_SETUP_PATH: + assertNull(prereq.getConfiguration()); + break; + default: + fail("unexpected prerequisite " + prereq); + } + } + if (Iterables.size(prereqs) != 3) { + fail("Expected 3 prerequisites, got: " + prereqs); + } + } + + @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 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 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(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenruleConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenruleConfiguredTargetTest.java deleted file mode 100644 index 00c811541204cf..00000000000000 --- a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenruleConfiguredTargetTest.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2015 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.devtools.build.lib.bazel.rules.genrule; - -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; - -import com.google.devtools.build.lib.actions.Artifact; -import com.google.devtools.build.lib.analysis.ConfiguredTarget; -import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; -import com.google.devtools.build.lib.vfs.PathFragment; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.List; - -/** - * Unit tests for {@link GenRule}. - */ -@RunWith(JUnit4.class) -public class GenruleConfiguredTargetTest extends BuildViewTestCase { - @Before - 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 { - ConfiguredTarget z = getConfiguredTarget("//hello:z"); - Artifact y = getOnlyElement(getFilesToBuild(z)); - assertEquals(new PathFragment("hello/x/y"), y.getRootRelativePath()); - } - - @Test - public void testDMultiOutput() throws Exception { - ConfiguredTarget z = getConfiguredTarget("//hello:w"); - List files = getFilesToBuild(z).toList(); - assertThat(files).hasSize(2); - assertEquals(new PathFragment("hello/a/b"), files.get(0).getRootRelativePath()); - assertEquals(new PathFragment("hello/c/d"), files.get(1).getRootRelativePath()); - } -} diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java index 8db110577168ae..d9947566a85d07 100644 --- a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java +++ b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java @@ -74,6 +74,10 @@ private TestConstants() { public static final ImmutableList DOCS_RULES_PATHS = ImmutableList.of( "src/main/java/com/google/devtools/build/lib/rules"); + // Constants used to determine how genrule pulls in the setup script. + public static final String GENRULE_SETUP = "@bazel_tools//tools/genrule:genrule-setup.sh"; + public static final String GENRULE_SETUP_PATH = "genrule-setup.sh"; + public static final InvocationPolicy TEST_INVOCATION_POLICY = InvocationPolicy.getDefaultInstance();