blob: b36ed6769f6a87b70faf4bebb8b4acd50bb2ccbd [file] [log] [blame]
// 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.truth.Truth.assertWithMessage;
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.
*
* <p>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+(?<command>.*)");
private String getGenruleCommand(String genrule) throws Exception {
return ((SpawnAction)
getGeneratingAction(getFilesToBuild(getConfiguredTarget(genrule)).toList().get(0)))
.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");
}
assertWithMessage("Expected command to be \"" + expected + "\", but found \"" + command + "\"")
.that(command)
.isEqualTo(expected);
}
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",
// This is a horrible workaround for b/147306893:
// somehow, duplicate events (same location, same message)
// are being suppressed, so we must vary the location of the
// genrule by inserting a unique number of newlines.
new String(new char[seq++]).replace("\0", "\n"),
"genrule(name = 'test',",
" outs = ['out'],",
" cmd = '" + command + "')");
// Since we're probably re-defining "//test":
invalidatePackages();
}
private int seq = 0;
@Test
public void testLocationSyntaxErrors() throws Exception {
genrule("$(location )");
assertExpansionFails(
"invalid label in $(location) expression: empty package-relative label", "//test");
genrule("$(location foo bar");
assertExpansionFails("unterminated variable reference", "//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) 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");
genrule("$(locations foo bar");
assertExpansionFails("unterminated variable reference", "//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) 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) not defined", "//test");
assertContainsEvent("$(basename) 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 + "')");
}
}