blob: d450f68167f13f7e453ff47bad1f2f1e5cef8eeb [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.shell;
import static com.facebook.buck.util.BuckConstant.GEN_DIR;
import static com.facebook.buck.util.BuckConstant.GEN_PATH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.android.AndroidPlatformTarget;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.JavaBinaryRuleBuilder;
import com.facebook.buck.java.JavaLibraryBuilder;
import com.facebook.buck.java.JavaLibraryDescription;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.FakeRuleKeyBuilderFactory;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.RuleKeyBuilderFactory;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.MkdirAndSymlinkFileStep;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.RmStep;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.util.Ansi;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.Verbosity;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Function;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class GenruleTest {
private static final String BASE_PATH = getAbsolutePathFor("/opt/local/fbandroid");
private static final Function<Path, Path> ABSOLUTIFIER =
new Function<Path, Path>() {
@Override
public Path apply(Path input) {
return getAbsolutePathInBase(input.toString());
}
};
private ProjectFilesystem fakeFilesystem;
private RuleKey.Builder.RuleKeyPair generateRuleKey(
RuleKeyBuilderFactory factory,
SourcePathResolver resolver,
AbstractBuildRule rule) {
RuleKey.Builder builder = factory.newInstance(rule, resolver);
rule.appendToRuleKey(builder);
return builder.build();
}
@Before
public void newFakeFilesystem() {
fakeFilesystem = EasyMock.createNiceMock(ProjectFilesystem.class);
EasyMock.expect(fakeFilesystem.getAbsolutifier())
.andReturn(ABSOLUTIFIER)
.times(0, 1);
EasyMock.replay(fakeFilesystem);
}
@After
public void verifyFakeFilesystem() {
EasyMock.verify(fakeFilesystem);
}
@Test
public void testCreateAndRunGenrule() throws IOException, NoSuchBuildTargetException {
/*
* Programmatically build up a Genrule that corresponds to:
*
* genrule(
* name = 'katana_manifest',
* srcs = [
* 'convert_to_katana.py',
* 'AndroidManifest.xml',
* ],
* cmd = 'python $SRCDIR/* > $OUT',
* out = 'AndroidManifest.xml',
* )
*/
BuildRuleResolver ruleResolver = new BuildRuleResolver();
createSampleJavaBinaryRule(ruleResolver);
// From the Python object, create a GenruleBuildRuleFactory to create a Genrule.Builder
// that builds a Genrule from the Python object.
BuildTargetParser parser = EasyMock.createNiceMock(BuildTargetParser.class);
EasyMock.expect(parser.parse(EasyMock.eq("//java/com/facebook/util:util"),
EasyMock.anyObject(BuildTargetPatternParser.class)))
.andStubReturn(BuildTargetFactory.newInstance("//java/com/facebook/util:util"));
EasyMock.replay(parser);
BuildTarget buildTarget =
BuildTarget.builder("//src/com/facebook/katana", "katana_manifest").build();
BuildRule genrule = GenruleBuilder
.newGenruleBuilder(buildTarget)
.setCmd("python convert_to_katana.py AndroidManifest.xml > $OUT")
.setOut("AndroidManifest.xml")
.setSrcs(
ImmutableList.<SourcePath>of(
new PathSourcePath(
fakeFilesystem,
Paths.get("src/com/facebook/katana/convert_to_katana.py")),
new PathSourcePath(
fakeFilesystem,
Paths.get("src/com/facebook/katana/AndroidManifest.xml"))))
.build(ruleResolver, fakeFilesystem);
// Verify all of the observers of the Genrule.
assertEquals(GEN_PATH.resolve("src/com/facebook/katana/AndroidManifest.xml"),
genrule.getPathToOutputFile());
assertEquals(
getAbsolutePathInBase(GEN_DIR + "/src/com/facebook/katana/AndroidManifest.xml").toString(),
((Genrule) genrule).getAbsoluteOutputFilePath());
BuildContext buildContext = null; // unused since there are no deps
ImmutableList<Path> inputsToCompareToOutputs = ImmutableList.of(
Paths.get("src/com/facebook/katana/convert_to_katana.py"),
Paths.get("src/com/facebook/katana/AndroidManifest.xml"));
assertEquals(
inputsToCompareToOutputs,
((Genrule) genrule).getInputsToCompareToOutput());
// Verify that the shell commands that the genrule produces are correct.
List<Step> steps = genrule.getBuildSteps(
buildContext, new FakeBuildableContext());
assertEquals(7, steps.size());
Step firstStep = steps.get(0);
assertTrue(firstStep instanceof RmStep);
RmStep rmCommand = (RmStep) firstStep;
ExecutionContext executionContext = newEmptyExecutionContext();
assertEquals(
"First command should delete the output file to be written by the genrule.",
ImmutableList.of(
"rm",
"-f",
GEN_DIR + "/src/com/facebook/katana/AndroidManifest.xml"),
rmCommand.getShellCommand(executionContext));
Step secondStep = steps.get(1);
assertTrue(secondStep instanceof MkdirStep);
MkdirStep mkdirCommand = (MkdirStep) secondStep;
assertEquals(
"Second command should make sure the output directory exists.",
Paths.get(GEN_DIR + "/src/com/facebook/katana"),
mkdirCommand.getPath(executionContext));
Step mkTmpDir = steps.get(2);
assertTrue(mkTmpDir instanceof MakeCleanDirectoryStep);
MakeCleanDirectoryStep secondMkdirCommand = (MakeCleanDirectoryStep) mkTmpDir;
Path pathToTmpDir = GEN_PATH.resolve("src/com/facebook/katana/katana_manifest__tmp");
assertEquals(
"Third command should create the temp directory to be written by the genrule.",
pathToTmpDir,
secondMkdirCommand.getPath());
Step mkSrcDir = steps.get(3);
assertTrue(mkSrcDir instanceof MakeCleanDirectoryStep);
MakeCleanDirectoryStep thirdMkdirCommand = (MakeCleanDirectoryStep) mkTmpDir;
Path pathToSrcDir = GEN_PATH.resolve("src/com/facebook/katana/katana_manifest__srcs");
assertEquals(
"Fourth command should create the temp source directory to be written by the genrule.",
pathToTmpDir,
thirdMkdirCommand.getPath());
MkdirAndSymlinkFileStep linkSource1 = (MkdirAndSymlinkFileStep) steps.get(4);
assertEquals(
Paths.get("src/com/facebook/katana/convert_to_katana.py"),
linkSource1.getSource());
assertEquals(Paths.get(pathToSrcDir + "/convert_to_katana.py"), linkSource1.getTarget());
MkdirAndSymlinkFileStep linkSource2 = (MkdirAndSymlinkFileStep) steps.get(5);
assertEquals(Paths.get("src/com/facebook/katana/AndroidManifest.xml"), linkSource2.getSource());
assertEquals(Paths.get(pathToSrcDir + "/AndroidManifest.xml"), linkSource2.getTarget());
Step sixthStep = steps.get(6);
assertTrue(sixthStep instanceof ShellStep);
ShellStep genruleCommand = (ShellStep) sixthStep;
assertEquals("genrule", genruleCommand.getShortName());
assertEquals(ImmutableMap.<String, String>builder()
.put("OUT",
getAbsolutePathInBase(
GEN_DIR + "/src/com/facebook/katana/AndroidManifest.xml").toString())
.build(),
genruleCommand.getEnvironmentVariables(executionContext));
assertEquals(
ImmutableList.of(
"/bin/bash",
"-e",
"-c",
"python convert_to_katana.py AndroidManifest.xml > $OUT"),
genruleCommand.getShellCommand(executionContext));
}
@Test
public void testDepsEnvironmentVariableIsComplete() {
BuildRuleResolver resolver = new BuildRuleResolver();
BuildTarget depTarget = BuildTarget.builder("//foo", "bar").build();
BuildRule dep = new FakeBuildRule(
JavaLibraryDescription.TYPE,
depTarget,
new SourcePathResolver(new BuildRuleResolver())) {
@Override
public Path getPathToOutputFile() {
return Paths.get("buck-out/gen/foo/bar.jar");
}
};
resolver.addToIndex(dep);
BuildRule genrule = GenruleBuilder
.newGenruleBuilder(
BuildTarget.builder(
"//foo",
"baz").build())
.setBash("cat $DEPS > $OUT")
.setOut("deps.txt")
.setDeps(ImmutableSortedSet.of(dep.getBuildTarget()))
.build(resolver, fakeFilesystem);
AbstractGenruleStep genruleStep = ((Genrule) genrule).createGenruleStep();
ExecutionContext context = newEmptyExecutionContext(Platform.LINUX);
ImmutableMap<String, String> environmentVariables =
genruleStep.getEnvironmentVariables(context);
assertEquals(
"Make sure that the use of $DEPS pulls in $GEN_DIR, as well.",
ImmutableMap.of(
"DEPS", "$GEN_DIR/foo/bar.jar",
"GEN_DIR", getAbsolutePathInBase("buck-out/gen").toString(),
"OUT", getAbsolutePathInBase("buck-out/gen/foo/deps.txt").toString()),
environmentVariables);
// Ensure that $GEN_DIR is declared before $DEPS.
List<String> keysInOrder = ImmutableList.copyOf(environmentVariables.keySet());
assertEquals("GEN_DIR", keysInOrder.get(1));
assertEquals("DEPS", keysInOrder.get(2));
}
private ExecutionContext newEmptyExecutionContext(Platform platform) {
return TestExecutionContext
.newBuilder()
.setConsole(new Console(Verbosity.SILENT, System.out, System.err, Ansi.withoutTty()))
.setPlatform(platform)
.build();
}
private ExecutionContext newEmptyExecutionContext() {
return newEmptyExecutionContext(Platform.detect());
}
@Test
public void ensureFilesInSubdirectoriesAreKeptInSubDirectories() throws IOException {
ProjectFilesystem projectFilesystem = new FakeProjectFilesystem();
BuildRuleResolver resolver = new BuildRuleResolver();
BuildTarget target = BuildTargetFactory.newInstance("//:example");
BuildRule rule = GenruleBuilder
.newGenruleBuilder(target)
.setBash("ignored")
.setSrcs(
ImmutableList.<SourcePath>of(
new PathSourcePath(projectFilesystem, Paths.get("in-dir.txt")),
new PathSourcePath(projectFilesystem, Paths.get("foo/bar.html")),
new PathSourcePath(projectFilesystem, Paths.get("other/place.txt"))))
.setOut("example-file")
.build(resolver);
ImmutableList.Builder<Step> builder = ImmutableList.builder();
((Genrule) rule).addSymlinkCommands(builder);
ImmutableList<Step> commands = builder.build();
String baseTmpPath = GEN_DIR + "/example__srcs/";
assertEquals(3, commands.size());
MkdirAndSymlinkFileStep linkCmd = (MkdirAndSymlinkFileStep) commands.get(0);
assertEquals(Paths.get("in-dir.txt"), linkCmd.getSource());
assertEquals(Paths.get(baseTmpPath + "in-dir.txt"), linkCmd.getTarget());
linkCmd = (MkdirAndSymlinkFileStep) commands.get(1);
assertEquals(Paths.get("foo/bar.html"), linkCmd.getSource());
assertEquals(Paths.get(baseTmpPath + "foo/bar.html"), linkCmd.getTarget());
linkCmd = (MkdirAndSymlinkFileStep) commands.get(2);
assertEquals(Paths.get("other/place.txt"), linkCmd.getSource());
assertEquals(Paths.get(baseTmpPath + "other/place.txt"), linkCmd.getTarget());
}
private BuildRule createSampleJavaBinaryRule(BuildRuleResolver ruleResolver) {
// Create a java_binary that depends on a java_library so it is possible to create a
// java_binary rule with a classpath entry and a main class.
BuildRule javaLibrary = JavaLibraryBuilder
.createBuilder(BuildTargetFactory.newInstance("//java/com/facebook/util:util"))
.addSrc(Paths.get("java/com/facebook/util/ManifestGenerator.java"))
.build(ruleResolver);
BuildTarget buildTarget =
BuildTargetFactory.newInstance("//java/com/facebook/util:ManifestGenerator");
return new JavaBinaryRuleBuilder(buildTarget)
.setDeps(ImmutableSortedSet.of(javaLibrary.getBuildTarget()))
.setMainClass("com.facebook.util.ManifestGenerator")
.build(ruleResolver);
}
@Test
public void testShouldIncludeDxInEnvironmentIfPresent() {
BuildRuleResolver resolver = new BuildRuleResolver();
AndroidPlatformTarget android = EasyMock.createNiceMock(AndroidPlatformTarget.class);
EasyMock.expect(android.getDxExecutable()).andStubReturn(Paths.get("."));
EasyMock.expect(android.getZipalignExecutable()).andStubReturn(Paths.get("zipalign"));
EasyMock.replay(android);
BuildTarget target = BuildTargetFactory.newInstance("//example:genrule");
Genrule genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(target)
.setBash("true")
.setOut("/dev/null")
.build(resolver);
ExecutionContext context = TestExecutionContext.newBuilder()
.setAndroidPlatformTargetSupplier(Suppliers.ofInstance(android))
.build();
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
genrule.addEnvironmentVariables(context, builder);
ImmutableMap<String, String> env = builder.build();
assertEquals(Paths.get(".").toString(), env.get("DX"));
assertEquals(Paths.get("zipalign").toString(), env.get("ZIPALIGN"));
EasyMock.verify(android);
}
@Test
public void shouldPreventTheParentBuckdBeingUsedIfARecursiveBuckCallIsMade() {
BuildRuleResolver resolver = new BuildRuleResolver();
BuildTarget target = BuildTargetFactory.newInstance("//example:genrule");
Genrule genrule = (Genrule) GenruleBuilder.newGenruleBuilder(target)
.setBash("true")
.setOut("/dev/null")
.build(resolver);
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
genrule.addEnvironmentVariables(TestExecutionContext.newInstance(), builder);
assertEquals("1", builder.build().get("NO_BUCKD"));
}
@Test
public void testGetShellCommand() {
BuildRuleResolver resolver = new BuildRuleResolver();
String bash = "rm -rf /usr";
String cmdExe = "rmdir /s /q C:\\Windows";
String cmd = "echo \"Hello\"";
ExecutionContext linuxExecutionContext = newEmptyExecutionContext(Platform.LINUX);
ExecutionContext windowsExecutionContext = newEmptyExecutionContext(Platform.WINDOWS);
// Test platform-specific
Genrule genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule1"))
.setBash(bash)
.setCmdExe(cmdExe)
.setOut("out.txt")
.build(resolver);
ImmutableList<String> command = genrule
.createGenruleStep()
.getShellCommand(linuxExecutionContext);
assertEquals(ImmutableList.of("/bin/bash", "-e", "-c", bash), command);
command = genrule.createGenruleStep().getShellCommand(windowsExecutionContext);
assertEquals(ImmutableList.of("cmd.exe", "/c", cmdExe), command);
// Test fallback
genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule2"))
.setCmd(cmd)
.setOut("out.txt")
.build(resolver);
command = genrule.createGenruleStep().getShellCommand(linuxExecutionContext);
assertEquals(ImmutableList.of("/bin/bash", "-e", "-c", cmd), command);
command = genrule.createGenruleStep().getShellCommand(windowsExecutionContext);
assertEquals(ImmutableList.of("cmd.exe", "/c", cmd), command);
// Test command absent
genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule3"))
.setOut("out.txt")
.build(resolver);
try {
genrule.createGenruleStep().getShellCommand(linuxExecutionContext);
} catch (HumanReadableException e) {
assertEquals(String.format("You must specify either bash or cmd for genrule %s.",
genrule.getBuildTarget()), e.getHumanReadableErrorMessage());
}
try {
genrule.createGenruleStep().getShellCommand(windowsExecutionContext);
} catch (HumanReadableException e) {
assertEquals(String.format("You must specify either cmd_exe or cmd for genrule %s.",
genrule.getBuildTarget()), e.getHumanReadableErrorMessage());
}
}
@Test
public void testGetOutputNameMethod() {
{
String name = "out.txt";
Genrule genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//:test"))
.setOut(name)
.build(new BuildRuleResolver());
assertEquals(name, genrule.getOutputName());
}
{
String name = "out/file.txt";
Genrule genrule = (Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//:test"))
.setOut(name)
.build(new BuildRuleResolver());
assertEquals(name, genrule.getOutputName());
}
}
@Test
public void thatChangingOutChangesRuleKey() {
BuildRuleResolver resolver = new BuildRuleResolver();
SourcePathResolver pathResolver = new SourcePathResolver(resolver);
RuleKeyBuilderFactory ruleKeyBuilderFactory = new FakeRuleKeyBuilderFactory();
// Get a rule key for two genrules using two different output names, but are otherwise the
// same.
RuleKey.Builder.RuleKeyPair key1 = generateRuleKey(
ruleKeyBuilderFactory,
pathResolver,
(Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//:genrule1"))
.setOut("foo")
.build(resolver));
RuleKey.Builder.RuleKeyPair key2 = generateRuleKey(
ruleKeyBuilderFactory,
pathResolver,
(Genrule) GenruleBuilder
.newGenruleBuilder(BuildTargetFactory.newInstance("//:genrule2"))
.setOut("bar")
.build(resolver));
// Verify that just the difference in output name is enough to make the rule key different.
assertNotEquals(key1.getTotalRuleKey(), key2.getTotalRuleKey());
}
private static String getAbsolutePathFor(String path) {
return new File(path).getAbsolutePath();
}
private static Path getAbsolutePathInBase(String path) {
return Paths.get(BASE_PATH, path);
}
}