/*
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.android.AndroidPlatformTarget;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.JavaBinaryRuleBuilder;
import com.facebook.buck.jvm.java.JavaLibrary;
import com.facebook.buck.jvm.java.JavaLibraryBuilder;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildInfo;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FakeBuildContext;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.FakeOnDiskBuildInfo;
import com.facebook.buck.rules.FakeSourcePath;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.keys.DefaultRuleKeyFactory;
import com.facebook.buck.rules.keys.InputBasedRuleKeyFactory;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.RmStep;
import com.facebook.buck.step.fs.SymlinkFileStep;
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.cache.DefaultFileHashCache;
import com.facebook.buck.util.cache.NullFileHashCache;
import com.facebook.buck.util.cache.StackedFileHashCache;
import com.facebook.buck.util.environment.Platform;
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 com.google.common.hash.Hashing;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import org.easymock.EasyMock;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
public class GenruleTest {
private ProjectFilesystem filesystem;
@Before
public void newFakeFilesystem() throws InterruptedException {
filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem();
}
@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(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver =
new SourcePathResolver(new SourcePathRuleFinder(ruleResolver));
createSampleJavaBinaryRule(ruleResolver);
// From the Python object, create a GenruleBuildRuleFactory to create a Genrule.Builder
// that builds a Genrule from the Python object.
// BuildTargetParser parser = BuildTargetParser.INSTANCE;
// 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 =
BuildTargetFactory.newInstance(
filesystem.getRootPath(), "//src/com/facebook/katana:katana_manifest");
Genrule genrule =
GenruleBuilder.newGenruleBuilder(buildTarget)
.setBash("python convert_to_katana.py AndroidManifest.xml > $OUT")
.setCmdExe("python convert_to_katana.py AndroidManifest.xml > %OUT%")
.setOut("AndroidManifest.xml")
.setSrcs(
ImmutableList.of(
new PathSourcePath(
filesystem,
filesystem.getPath("src/com/facebook/katana/convert_to_katana.py")),
new PathSourcePath(
filesystem,
filesystem.getPath("src/com/facebook/katana/AndroidManifest.xml"))))
.build(ruleResolver, filesystem);
// Verify all of the observers of the Genrule.
assertEquals(
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest/AndroidManifest.xml"),
pathResolver.getRelativePath(genrule.getSourcePathToOutput()));
assertEquals(
filesystem
.resolve(
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest/AndroidManifest.xml"))
.toString(),
genrule.getAbsoluteOutputFilePath(pathResolver));
BuildContext buildContext = FakeBuildContext.withSourcePathResolver(pathResolver);
ImmutableList<Path> inputsToCompareToOutputs =
ImmutableList.of(
filesystem.getPath("src/com/facebook/katana/convert_to_katana.py"),
filesystem.getPath("src/com/facebook/katana/AndroidManifest.xml"));
assertEquals(
inputsToCompareToOutputs, pathResolver.filterInputsToCompareToOutput(genrule.getSrcs()));
// Verify that the shell commands that the genrule produces are correct.
List<Step> steps = genrule.getBuildSteps(buildContext, new FakeBuildableContext());
assertEquals(11, steps.size());
ExecutionContext executionContext = newEmptyExecutionContext();
assertEquals(
RmStep.of(
filesystem,
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest"))
.withRecursive(true),
steps.get(0));
assertEquals(
MkdirStep.of(
filesystem,
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest")),
steps.get(1));
assertEquals(
RmStep.of(
filesystem,
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest__tmp"))
.withRecursive(true),
steps.get(2));
assertEquals(
MkdirStep.of(
filesystem,
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest__tmp")),
steps.get(3));
Path pathToSrcDir =
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest__srcs");
assertEquals(RmStep.of(filesystem, pathToSrcDir).withRecursive(true), steps.get(4));
assertEquals(MkdirStep.of(filesystem, pathToSrcDir), steps.get(5));
assertEquals(MkdirStep.of(filesystem, pathToSrcDir), steps.get(6));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(filesystem)
.setExistingFile(filesystem.getPath("src/com/facebook/katana/convert_to_katana.py"))
.setDesiredLink(filesystem.getPath(pathToSrcDir + "/convert_to_katana.py"))
.build(),
steps.get(7));
assertEquals(MkdirStep.of(filesystem, pathToSrcDir), steps.get(8));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(filesystem)
.setExistingFile(filesystem.getPath("src/com/facebook/katana/AndroidManifest.xml"))
.setDesiredLink(filesystem.getPath(pathToSrcDir + "/AndroidManifest.xml"))
.build(),
steps.get(9));
Step sixthStep = steps.get(10);
assertTrue(sixthStep instanceof AbstractGenruleStep);
AbstractGenruleStep genruleCommand = (AbstractGenruleStep) sixthStep;
assertEquals("genrule", genruleCommand.getShortName());
assertEquals(
ImmutableMap.<String, String>builder()
.put(
"OUT",
filesystem
.resolve(
filesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/katana/katana_manifest/AndroidManifest.xml"))
.toString())
.build(),
genruleCommand.getEnvironmentVariables(executionContext));
Path scriptFilePath = genruleCommand.getScriptFilePath(executionContext);
String scriptFileContents = genruleCommand.getScriptFileContents(executionContext);
if (Platform.detect() == Platform.WINDOWS) {
assertEquals(
ImmutableList.of(scriptFilePath.toString()),
genruleCommand.getShellCommand(executionContext));
assertEquals("python convert_to_katana.py AndroidManifest.xml > %OUT%", scriptFileContents);
} else {
assertEquals(
ImmutableList.of("/bin/bash", "-e", scriptFilePath.toString()),
genruleCommand.getShellCommand(executionContext));
assertEquals("python convert_to_katana.py AndroidManifest.xml > $OUT", scriptFileContents);
}
}
@Test
public void testGenruleType() throws NoSuchBuildTargetException {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
BuildTarget buildTarget =
BuildTargetFactory.newInstance(
filesystem.getRootPath(), "//src/com/facebook/katana:katana_manifest");
BuildRule genrule =
GenruleBuilder.newGenruleBuilder(buildTarget)
.setOut("output.xml")
.setType("xxxxx")
.build(ruleResolver, filesystem);
assertTrue(genrule.getType().contains("xxxxx"));
}
private GenruleBuilder createGenruleBuilderThatUsesWorkerMacro(BuildRuleResolver resolver)
throws NoSuchBuildTargetException, IOException {
/*
* Produces a GenruleBuilder that when built produces a Genrule that uses a $(worker) macro
* that corresponds to:
*
* genrule(
* name = 'genrule_with_worker',
* srcs = [],
* cmd = '$(worker :worker_rule) abc',
* out = 'output.txt',
* )
*
* worker_tool(
* name = 'worker_rule',
* exe = ':my_exe',
* )
*
* sh_binary(
* name = 'my_exe',
* main = 'bin/exe',
* );
*/
BuildRule shBinaryRule =
new ShBinaryBuilder(BuildTargetFactory.newInstance("//:my_exe"))
.setMain(new FakeSourcePath("bin/exe"))
.build(resolver);
DefaultWorkerTool workerTool =
WorkerToolBuilder.newWorkerToolBuilder(BuildTargetFactory.newInstance("//:worker_rule"))
.setExe(shBinaryRule.getBuildTarget())
.build(resolver);
workerTool
.getBuildOutputInitializer()
.setBuildOutput(
workerTool.initializeFromDisk(
new FakeOnDiskBuildInfo()
.putMetadata(
BuildInfo.MetadataKey.RULE_KEY, Hashing.sha1().hashLong(0).toString())));
return GenruleBuilder.newGenruleBuilder(
BuildTargetFactory.newInstance("//:genrule_with_worker"))
.setCmd("$(worker :worker_rule) abc")
.setOut("output.txt");
}
@Test
public void testGenruleWithWorkerMacroUsesSpecialShellStep() throws Exception {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver =
new SourcePathResolver(new SourcePathRuleFinder(ruleResolver));
Genrule genrule = createGenruleBuilderThatUsesWorkerMacro(ruleResolver).build(ruleResolver);
ProjectFilesystem filesystem = new FakeProjectFilesystem();
List<Step> steps =
genrule.getBuildSteps(
FakeBuildContext.withSourcePathResolver(pathResolver), new FakeBuildableContext());
ExecutionContext executionContext = newEmptyExecutionContext(Platform.LINUX);
assertEquals(7, steps.size());
Step step = steps.get(6);
assertTrue(step instanceof WorkerShellStep);
WorkerShellStep workerShellStep = (WorkerShellStep) step;
assertThat(workerShellStep.getShortName(), Matchers.equalTo("worker"));
assertThat(
workerShellStep.getEnvironmentVariables(executionContext),
Matchers.hasEntry(
"OUT",
filesystem
.resolve(filesystem.getBuckPaths().getGenDir())
.resolve("genrule_with_worker/output.txt")
.toString()));
}
@Test
public void testIsWorkerGenruleReturnsTrue() throws Exception {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
Genrule genrule = createGenruleBuilderThatUsesWorkerMacro(ruleResolver).build(ruleResolver);
assertTrue(genrule.isWorkerGenrule());
}
@Test
public void testIsWorkerGenruleReturnsFalse() throws NoSuchBuildTargetException {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
Genrule genrule =
GenruleBuilder.newGenruleBuilder(
BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:genrule_no_worker"))
.setCmd("echo hello >> $OUT")
.setOut("output.txt")
.build(ruleResolver, filesystem);
assertFalse(genrule.isWorkerGenrule());
}
@Test
public void testConstructingGenruleWithBadWorkerMacroThrows() throws Exception {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
GenruleBuilder genruleBuilder = createGenruleBuilderThatUsesWorkerMacro(ruleResolver);
try {
genruleBuilder.setBash("no worker macro here").build(ruleResolver);
} catch (HumanReadableException e) {
assertEquals(
String.format(
"You cannot use a worker macro in one of the cmd, bash, or "
+ "cmd_exe properties and not in the others for genrule //:genrule_with_worker."),
e.getHumanReadableErrorMessage());
}
}
@Test
public void testGenruleWithWorkerMacroIncludesWorkerToolInDeps()
throws NoSuchBuildTargetException {
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
BuildRule shBinaryRule =
new ShBinaryBuilder(BuildTargetFactory.newInstance("//:my_exe"))
.setMain(new FakeSourcePath("bin/exe"))
.build(ruleResolver);
BuildRule workerToolRule =
WorkerToolBuilder.newWorkerToolBuilder(BuildTargetFactory.newInstance("//:worker_rule"))
.setExe(shBinaryRule.getBuildTarget())
.build(ruleResolver);
BuildRule genrule =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:genrule_with_worker"))
.setCmd("$(worker :worker_rule) abc")
.setOut("output.txt")
.build(ruleResolver);
assertThat(genrule.getBuildDeps(), Matchers.hasItems(shBinaryRule, workerToolRule));
}
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 Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
BuildTarget target = BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:example");
Genrule rule =
GenruleBuilder.newGenruleBuilder(target, filesystem)
.setBash("ignored")
.setSrcs(
ImmutableList.of(
new PathSourcePath(filesystem, filesystem.getPath("in-dir.txt")),
new PathSourcePath(filesystem, filesystem.getPath("foo/bar.html")),
new PathSourcePath(filesystem, filesystem.getPath("other/place.txt"))))
.setOut("example-file")
.build(resolver);
ImmutableList.Builder<Step> builder = ImmutableList.builder();
rule.addSymlinkCommands(FakeBuildContext.withSourcePathResolver(pathResolver), builder);
ImmutableList<Step> commands = builder.build();
Path baseTmpPath = filesystem.getBuckPaths().getGenDir().resolve("example__srcs");
assertEquals(6, commands.size());
assertEquals(MkdirStep.of(filesystem, baseTmpPath), commands.get(0));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(filesystem)
.setExistingFile(filesystem.getPath("in-dir.txt"))
.setDesiredLink(baseTmpPath.resolve("in-dir.txt"))
.build(),
commands.get(1));
assertEquals(MkdirStep.of(filesystem, baseTmpPath.resolve("foo")), commands.get(2));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(filesystem)
.setExistingFile(filesystem.getPath("foo", "bar.html"))
.setDesiredLink(baseTmpPath.resolve("foo").resolve("bar.html"))
.build(),
commands.get(3));
assertEquals(MkdirStep.of(filesystem, baseTmpPath.resolve("other")), commands.get(4));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(filesystem)
.setExistingFile(filesystem.getPath("other", "place.txt"))
.setDesiredLink(baseTmpPath.resolve("other").resolve("place.txt"))
.build(),
commands.get(5));
}
private BuildRule createSampleJavaBinaryRule(BuildRuleResolver ruleResolver)
throws NoSuchBuildTargetException {
// 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 testShouldIncludeAndroidSpecificEnvInEnvironmentIfPresent() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
AndroidPlatformTarget android = EasyMock.createNiceMock(AndroidPlatformTarget.class);
Path sdkDir = Paths.get("/opt/users/android_sdk");
Path ndkDir = Paths.get("/opt/users/android_ndk");
EasyMock.expect(android.getSdkDirectory()).andStubReturn(Optional.of(sdkDir));
EasyMock.expect(android.getNdkDirectory()).andStubReturn(Optional.of(ndkDir));
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 =
GenruleBuilder.newGenruleBuilder(target)
.setBash("echo something > $OUT")
.setOut("file")
.build(resolver);
ExecutionContext context =
TestExecutionContext.newBuilder()
.setAndroidPlatformTargetSupplier(Suppliers.ofInstance(android))
.build();
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
genrule.addEnvironmentVariables(pathResolver, context, builder);
ImmutableMap<String, String> env = builder.build();
assertEquals(Paths.get(".").toString(), env.get("DX"));
assertEquals(Paths.get("zipalign").toString(), env.get("ZIPALIGN"));
assertEquals(sdkDir.toString(), env.get("ANDROID_HOME"));
assertEquals(ndkDir.toString(), env.get("NDK_HOME"));
EasyMock.verify(android);
}
@Test
public void shouldPreventTheParentBuckdBeingUsedIfARecursiveBuckCallIsMade() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
BuildTarget target = BuildTargetFactory.newInstance("//example:genrule");
Genrule genrule =
GenruleBuilder.newGenruleBuilder(target)
.setBash("echo something > $OUT")
.setOut("file")
.build(resolver);
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
genrule.addEnvironmentVariables(pathResolver, TestExecutionContext.newInstance(), builder);
assertEquals("1", builder.build().get("NO_BUCKD"));
}
@Test
public void testGetShellCommand() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
BuildContext buildContext =
FakeBuildContext.withSourcePathResolver(
new SourcePathResolver(new SourcePathRuleFinder(resolver)));
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 =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule1"))
.setBash(bash)
.setCmdExe(cmdExe)
.setOut("out.txt")
.build(resolver);
assertGenruleCommandAndScript(
genrule.createGenruleStep(buildContext),
linuxExecutionContext,
ImmutableList.of("/bin/bash", "-e"),
bash);
assertGenruleCommandAndScript(
genrule.createGenruleStep(buildContext),
windowsExecutionContext,
ImmutableList.of(),
cmdExe);
// Test fallback
genrule =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule2"))
.setCmd(cmd)
.setOut("out.txt")
.build(resolver);
assertGenruleCommandAndScript(
genrule.createGenruleStep(buildContext),
linuxExecutionContext,
ImmutableList.of("/bin/bash", "-e"),
cmd);
assertGenruleCommandAndScript(
genrule.createGenruleStep(buildContext), windowsExecutionContext, ImmutableList.of(), cmd);
// Test command absent
genrule =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//example:genrule3"))
.setOut("out.txt")
.build(resolver);
try {
genrule.createGenruleStep(buildContext).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(buildContext).getShellCommand(windowsExecutionContext);
} catch (HumanReadableException e) {
assertEquals(
String.format(
"You must specify either cmd_exe or cmd for genrule %s.", genrule.getBuildTarget()),
e.getHumanReadableErrorMessage());
}
}
private void assertGenruleCommandAndScript(
AbstractGenruleStep genruleStep,
ExecutionContext context,
ImmutableList<String> expectedCommandPrefix,
String expectedScriptFileContents)
throws IOException {
Path scriptFilePath = genruleStep.getScriptFilePath(context);
String actualContents = genruleStep.getScriptFileContents(context);
assertThat(actualContents, Matchers.equalTo(expectedScriptFileContents));
ImmutableList<String> expectedCommand =
ImmutableList.<String>builder()
.addAll(expectedCommandPrefix)
.add(scriptFilePath.toString())
.build();
ImmutableList<String> actualCommand = genruleStep.getShellCommand(context);
assertThat(actualCommand, Matchers.equalTo(expectedCommand));
}
@Test
public void testGetOutputNameMethod() throws Exception {
{
String name = "out.txt";
Genrule genrule =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:test"))
.setOut(name)
.build(
new BuildRuleResolver(
TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()));
assertEquals(name, genrule.getOutputName());
}
{
String name = "out/file.txt";
Genrule genrule =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:test"))
.setOut(name)
.build(
new BuildRuleResolver(
TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()));
assertEquals(name, genrule.getOutputName());
}
}
@Test
public void thatChangingOutChangesRuleKey() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
DefaultRuleKeyFactory ruleKeyFactory =
new DefaultRuleKeyFactory(0, new NullFileHashCache(), pathResolver, ruleFinder);
// Get a rule key for two genrules using two different output names, but are otherwise the
// same.
RuleKey key1 =
ruleKeyFactory.build(
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:genrule1"))
.setOut("foo")
.build(resolver));
RuleKey key2 =
ruleKeyFactory.build(
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, key2);
}
@Test
public void inputBasedRuleKeyLocationMacro() throws Exception {
ProjectFilesystem filesystem = new FakeProjectFilesystem();
GenruleBuilder ruleBuilder =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:rule"))
.setCmd("run $(location //:dep)")
.setOut("output");
// Create an initial input-based rule key
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
BuildRule dep =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep"))
.setOut("dep.out")
.setCmd("something")
.build(resolver);
filesystem.writeContentsToPath(
"something", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
BuildRule rule = ruleBuilder.build(resolver);
DefaultRuleKeyFactory ruleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
InputBasedRuleKeyFactory inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey originalRuleKey = ruleKeyFactory.build(rule);
RuleKey originalInputRuleKey = inputBasedRuleKeyFactory.build(rule);
// Change the genrule's command, which will change its normal rule key, but since we're keeping
// its output the same, the input-based rule key for the consuming rule will stay the same.
// This is because the input-based rule key for the consuming rule only cares about the contents
// of the output this rule produces.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep"))
.setOut("dep.out")
.setCmd("something else")
.build(resolver);
rule = ruleBuilder.build(resolver);
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
ruleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey unchangedRuleKey = ruleKeyFactory.build(rule);
RuleKey unchangedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(unchangedRuleKey, Matchers.not(Matchers.equalTo(originalRuleKey)));
assertThat(unchangedInputBasedRuleKey, Matchers.equalTo(originalInputRuleKey));
// Make a change to the dep's output, which *should* affect the input-based rule key.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
dep =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:dep"))
.setOut("dep.out")
.setCmd("something")
.build(resolver);
filesystem.writeContentsToPath(
"something else", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
rule = ruleBuilder.build(resolver);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey changedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(changedInputBasedRuleKey, Matchers.not(Matchers.equalTo(originalInputRuleKey)));
}
@Test
public void inputBasedRuleKeyExecutableMacro() throws Exception {
ProjectFilesystem filesystem = new FakeProjectFilesystem();
GenruleBuilder ruleBuilder =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:rule"))
.setCmd("run $(exe //:dep)")
.setOut("output");
// Create an initial input-based rule key
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
BuildRule dep =
new ShBinaryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setMain(new PathSourcePath(filesystem, Paths.get("dep.exe")))
.build(resolver, filesystem);
filesystem.writeContentsToPath("something", Paths.get("dep.exe"));
filesystem.writeContentsToPath(
"something", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
BuildRule rule = ruleBuilder.build(resolver);
DefaultRuleKeyFactory defaultRuleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
InputBasedRuleKeyFactory inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey originalRuleKey = defaultRuleKeyFactory.build(rule);
RuleKey originalInputRuleKey = inputBasedRuleKeyFactory.build(rule);
// Change the dep's resource list, which will change its normal rule key, but since we're
// keeping its output the same, the input-based rule key for the consuming rule will stay the
// same. This is because the input-based rule key for the consuming rule only cares about the
// contents of the output this rule produces.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
Genrule extra =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:extra"))
.setOut("something")
.build(resolver);
new ShBinaryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setMain(new PathSourcePath(filesystem, Paths.get("dep.exe")))
.setDeps(ImmutableSortedSet.of(extra.getBuildTarget()))
.build(resolver, filesystem);
rule = ruleBuilder.build(resolver);
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
defaultRuleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey unchangedRuleKey = defaultRuleKeyFactory.build(rule);
RuleKey unchangedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(unchangedRuleKey, Matchers.not(Matchers.equalTo(originalRuleKey)));
assertThat(unchangedInputBasedRuleKey, Matchers.equalTo(originalInputRuleKey));
// Make a change to the dep's output, which *should* affect the input-based rule key.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
dep =
new ShBinaryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setMain(new PathSourcePath(filesystem, Paths.get("dep.exe")))
.build(resolver, filesystem);
filesystem.writeContentsToPath(
"something else", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
rule = ruleBuilder.build(resolver);
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey changedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(changedInputBasedRuleKey, Matchers.not(Matchers.equalTo(originalInputRuleKey)));
}
@Test
public void inputBasedRuleKeyClasspathMacro() throws Exception {
ProjectFilesystem filesystem = new FakeProjectFilesystem();
GenruleBuilder ruleBuilder =
GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:rule"))
.setCmd("run $(classpath //:dep)")
.setOut("output");
// Create an initial input-based rule key
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
JavaLibrary dep =
JavaLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:dep"))
.addSrc(Paths.get("source.java"))
.build(resolver, filesystem);
filesystem.writeContentsToPath("something", Paths.get("source.java"));
filesystem.writeContentsToPath(
"something", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
BuildRule rule = ruleBuilder.build(resolver);
DefaultRuleKeyFactory defaultRuleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
InputBasedRuleKeyFactory inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey originalRuleKey = defaultRuleKeyFactory.build(rule);
RuleKey originalInputRuleKey = inputBasedRuleKeyFactory.build(rule);
// Change the dep's resource root, which will change its normal rule key, but since we're
// keeping its output JAR the same, the input-based rule key for the consuming rule will stay
// the same. This is because the input-based rule key for the consuming rule only cares about
// the contents of the output this rule produces.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
JavaLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:dep"))
.addSrc(Paths.get("source.java"))
.setResourcesRoot(Paths.get("resource_root"))
.build(resolver, filesystem);
rule = ruleBuilder.build(resolver);
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
defaultRuleKeyFactory =
new DefaultRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey unchangedRuleKey = defaultRuleKeyFactory.build(rule);
RuleKey unchangedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(unchangedRuleKey, Matchers.not(Matchers.equalTo(originalRuleKey)));
assertThat(unchangedInputBasedRuleKey, Matchers.equalTo(originalInputRuleKey));
// Make a change to the dep's output, which *should* affect the input-based rule key.
resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
dep =
JavaLibraryBuilder.createBuilder(BuildTargetFactory.newInstance("//:dep"))
.addSrc(Paths.get("source.java"))
.build(resolver, filesystem);
filesystem.writeContentsToPath(
"something else", pathResolver.getRelativePath(dep.getSourcePathToOutput()));
rule = ruleBuilder.build(resolver);
ruleFinder = new SourcePathRuleFinder(resolver);
pathResolver = new SourcePathResolver(ruleFinder);
inputBasedRuleKeyFactory =
new InputBasedRuleKeyFactory(
0,
new StackedFileHashCache(
ImmutableList.of(DefaultFileHashCache.createDefaultFileHashCache(filesystem))),
pathResolver,
ruleFinder);
RuleKey changedInputBasedRuleKey = inputBasedRuleKeyFactory.build(rule);
assertThat(changedInputBasedRuleKey, Matchers.not(Matchers.equalTo(originalInputRuleKey)));
}
}