blob: 9e4a69affd2712dcdb279f270e17024c867e8fec [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.android;
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.assertTrue;
import com.facebook.buck.event.BuckEventBusFactory;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.JavaLibraryBuilder;
import com.facebook.buck.java.JavaPackageFinder;
import com.facebook.buck.java.Keystore;
import com.facebook.buck.java.KeystoreBuilder;
import com.facebook.buck.model.BuildId;
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.ActionGraph;
import com.facebook.buck.rules.ArtifactCache;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.ExopackageInfo;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.FakeBuildRuleParamsBuilder;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.ImmutableBuildContext;
import com.facebook.buck.rules.InstallableApk;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.TestSourcePath;
import com.facebook.buck.shell.ShellStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepRunner;
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.testutil.MoreAsserts;
import com.facebook.buck.timing.Clock;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import org.easymock.EasyMock;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* Unit test for {@link com.facebook.buck.android.ApkGenrule}.
*/
public class ApkGenruleTest {
private static final Function<Path, Path> relativeToAbsolutePathFunction =
new Function<Path, Path>() {
@Override
public Path apply(Path path) {
return Paths.get("/opt/local/fbandroid").resolve(path);
}
};
private void createSampleAndroidBinaryRule(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.
BuildTarget libAndroidTarget = BuildTargetFactory.newInstance("//:lib-android");
BuildRule androidLibRule = JavaLibraryBuilder.createBuilder(libAndroidTarget)
.addSrc(Paths.get("java/com/facebook/util/Facebook.java"))
.build(ruleResolver);
BuildTarget keystoreTarget = BuildTargetFactory.newInstance("//keystore:debug");
Keystore keystore = (Keystore) KeystoreBuilder.createBuilder(keystoreTarget)
.setStore(Paths.get("keystore/debug.keystore"))
.setProperties(Paths.get("keystore/debug.keystore.properties"))
.build(ruleResolver);
AndroidBinaryBuilder.createBuilder(BuildTargetFactory.newInstance("//:fb4a"))
.setManifest(new TestSourcePath("AndroidManifest.xml"))
.setTarget("Google Inc.:Google APIs:16")
.setOriginalDeps(ImmutableSortedSet.of(androidLibRule.getBuildTarget()))
.setKeystore(keystore.getBuildTarget())
.build(ruleResolver);
}
@Test
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public void testCreateAndRunApkGenrule() throws IOException, NoSuchBuildTargetException {
ProjectFilesystem projectFilesystem = new FakeProjectFilesystem();
BuildRuleResolver ruleResolver = new BuildRuleResolver();
createSampleAndroidBinaryRule(ruleResolver);
// From the Python object, create a ApkGenruleBuildRuleFactory to create a ApkGenrule.Builder
// that builds a ApkGenrule from the Python object.
BuildTargetParser parser = EasyMock.createNiceMock(BuildTargetParser.class);
final BuildTarget apkTarget = BuildTargetFactory.newInstance("//:fb4a");
EasyMock.expect(
parser.parse(EasyMock.eq(":fb4a"),
EasyMock.anyObject(BuildTargetPatternParser.class)))
.andStubReturn(apkTarget);
EasyMock.replay(parser);
BuildTarget buildTarget = BuildTarget.builder("//src/com/facebook", "sign_fb4a").build();
ApkGenruleDescription description = new ApkGenruleDescription();
ApkGenruleDescription.Arg arg = description.createUnpopulatedConstructorArg();
arg.apk = new FakeInstallable(
AndroidBinaryDescription.TYPE,
apkTarget,
new SourcePathResolver(ruleResolver)).getBuildTarget();
arg.bash = Optional.of("");
arg.cmd = Optional.of("python signer.py $APK key.properties > $OUT");
arg.cmdExe = Optional.of("");
arg.out = "signed_fb4a.apk";
arg.srcs = Optional.of(ImmutableList.<SourcePath>of(
new PathSourcePath(projectFilesystem, Paths.get("src/com/facebook/signer.py")),
new PathSourcePath(projectFilesystem, Paths.get("src/com/facebook/key.properties"))));
BuildRuleParams params = new FakeBuildRuleParamsBuilder(buildTarget)
.setProjectFilesystem(
new ProjectFilesystem(Paths.get(".")) {
@Override
public Function<Path, Path> getAbsolutifier() {
return relativeToAbsolutePathFunction;
}
}).build();
ApkGenrule apkGenrule = description.createBuildRule(params, ruleResolver, arg);
ruleResolver.addToIndex(apkGenrule);
// Verify all of the observers of the Genrule.
String expectedApkOutput =
"/opt/local/fbandroid/" + GEN_DIR + "/src/com/facebook/sign_fb4a.apk";
assertEquals(expectedApkOutput,
apkGenrule.getAbsoluteOutputFilePath());
assertEquals(
"The apk that this rule is modifying must have the apk in its deps.",
ImmutableSet.of(apkTarget.toString()),
FluentIterable
.from(apkGenrule.getDeps())
.transform(Functions.toStringFunction())
.toSet());
BuildContext buildContext = ImmutableBuildContext.builder()
.setActionGraph(EasyMock.createMock(ActionGraph.class))
.setStepRunner(EasyMock.createNiceMock(StepRunner.class))
.setProjectFilesystem(EasyMock.createNiceMock(ProjectFilesystem.class))
.setClock(EasyMock.createMock(Clock.class))
.setBuildId(EasyMock.createMock(BuildId.class))
.setArtifactCache(EasyMock.createMock(ArtifactCache.class))
.setJavaPackageFinder(EasyMock.createNiceMock(JavaPackageFinder.class))
.setEventBus(BuckEventBusFactory.newInstance())
.build();
Iterable<Path> expectedInputsToCompareToOutputs = ImmutableList.of(
Paths.get("src/com/facebook/signer.py"),
Paths.get("src/com/facebook/key.properties"));
MoreAsserts.assertIterablesEquals(
expectedInputsToCompareToOutputs,
apkGenrule.getInputsToCompareToOutput());
// Verify that the shell commands that the genrule produces are correct.
List<Step> steps = apkGenrule.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",
apkGenrule.getPathToOutputFile().toString()),
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/"),
mkdirCommand.getPath(executionContext));
Step thirdStep = steps.get(2);
assertTrue(thirdStep instanceof MakeCleanDirectoryStep);
MakeCleanDirectoryStep secondMkdirCommand = (MakeCleanDirectoryStep) thirdStep;
Path relativePathToTmpDir = GEN_PATH.resolve("src/com/facebook/sign_fb4a__tmp");
assertEquals(
"Third command should make sure the temp directory exists.",
relativePathToTmpDir,
secondMkdirCommand.getPath());
Step fourthStep = steps.get(3);
assertTrue(fourthStep instanceof MakeCleanDirectoryStep);
MakeCleanDirectoryStep thirdMkdirCommand = (MakeCleanDirectoryStep) fourthStep;
Path relativePathToSrcDir = GEN_PATH.resolve("src/com/facebook/sign_fb4a__srcs");
assertEquals(
"Fourth command should make sure the temp directory exists.",
relativePathToSrcDir,
thirdMkdirCommand.getPath());
MkdirAndSymlinkFileStep linkSource1 = (MkdirAndSymlinkFileStep) steps.get(4);
assertEquals(Paths.get("src/com/facebook/signer.py"), linkSource1.getSource());
assertEquals(Paths.get(relativePathToSrcDir + "/signer.py"), linkSource1.getTarget());
MkdirAndSymlinkFileStep linkSource2 = (MkdirAndSymlinkFileStep) steps.get(5);
assertEquals(Paths.get("src/com/facebook/key.properties"), linkSource2.getSource());
assertEquals(Paths.get(relativePathToSrcDir + "/key.properties"), linkSource2.getTarget());
Step seventhStep = steps.get(6);
assertTrue(seventhStep instanceof ShellStep);
ShellStep genruleCommand = (ShellStep) seventhStep;
assertEquals("genrule", genruleCommand.getShortName());
ImmutableMap<String, String> environmentVariables = genruleCommand.getEnvironmentVariables(
executionContext);
assertEquals(new ImmutableMap.Builder<String, String>()
.put("APK", relativeToAbsolutePathFunction.apply(GEN_PATH.resolve("fb4a.apk")).toString())
.put("OUT", expectedApkOutput).build(),
environmentVariables);
assertEquals(
ImmutableList.of("/bin/bash", "-e", "-c", "python signer.py $APK key.properties > $OUT"),
genruleCommand.getShellCommand(executionContext));
EasyMock.verify(parser);
}
private ExecutionContext newEmptyExecutionContext() {
return TestExecutionContext.newBuilder()
.setPlatform(Platform.LINUX) // Fix platform to Linux to use bash in genrule.
.build();
}
private static class FakeInstallable extends FakeBuildRule implements InstallableApk {
public FakeInstallable(
BuildRuleType type,
BuildTarget buildTarget,
SourcePathResolver resolver) {
super(type, buildTarget, resolver);
}
@Override
public Path getManifestPath() {
return Paths.get("spoof");
}
@Override
public Path getApkPath() {
return Paths.get("buck-out/gen/fb4a.apk");
}
@Override
public Optional<ExopackageInfo> getExopackageInfo() {
return Optional.absent();
}
}
}