/*
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.JavaLibraryBuilder;
import com.facebook.buck.jvm.java.Keystore;
import com.facebook.buck.jvm.java.KeystoreBuilder;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
import com.facebook.buck.parser.NoSuchBuildTargetException;
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.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FakeBuildContext;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.FakeBuildRuleParamsBuilder;
import com.facebook.buck.rules.FakeBuildableContext;
import com.facebook.buck.rules.FakeSourcePath;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TestCellBuilder;
import com.facebook.buck.shell.AbstractGenruleStep;
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.testutil.MoreAsserts;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.environment.Platform;
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 java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.easymock.EasyMock;
import org.junit.Test;
public class ApkGenruleTest {
private void createSampleAndroidBinaryRule(
BuildRuleResolver ruleResolver, ProjectFilesystem filesystem)
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.
BuildTarget libAndroidTarget =
BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:lib-android");
BuildRule androidLibRule =
JavaLibraryBuilder.createBuilder(libAndroidTarget)
.addSrc(Paths.get("java/com/facebook/util/Facebook.java"))
.build(ruleResolver, filesystem);
BuildTarget keystoreTarget =
BuildTargetFactory.newInstance(filesystem.getRootPath(), "//keystore:debug");
Keystore keystore =
KeystoreBuilder.createBuilder(keystoreTarget)
.setStore(new FakeSourcePath(filesystem, "keystore/debug.keystore"))
.setProperties(new FakeSourcePath(filesystem, "keystore/debug.keystore.properties"))
.build(ruleResolver, filesystem);
AndroidBinaryBuilder.createBuilder(
BuildTargetFactory.newInstance(filesystem.getRootPath(), "//:fb4a"))
.setManifest(new FakeSourcePath("AndroidManifest.xml"))
.setOriginalDeps(ImmutableSortedSet.of(androidLibRule.getBuildTarget()))
.setKeystore(keystore.getBuildTarget())
.build(ruleResolver, filesystem);
}
@Test
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public void testCreateAndRunApkGenrule()
throws InterruptedException, IOException, NoSuchBuildTargetException {
ProjectFilesystem projectFilesystem = FakeProjectFilesystem.createJavaOnlyFilesystem();
FileSystem fileSystem = projectFilesystem.getRootPath().getFileSystem();
BuildRuleResolver ruleResolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
createSampleAndroidBinaryRule(ruleResolver, projectFilesystem);
// 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(projectFilesystem.getRootPath(), "//:fb4a");
EasyMock.expect(
parser.parse(
EasyMock.eq(":fb4a"),
EasyMock.anyObject(BuildTargetPatternParser.class),
EasyMock.anyObject()))
.andStubReturn(apkTarget);
EasyMock.replay(parser);
BuildTarget buildTarget =
BuildTargetFactory.newInstance(
projectFilesystem.getRootPath(), "//src/com/facebook:sign_fb4a");
SourcePathResolver pathResolver =
new SourcePathResolver(new SourcePathRuleFinder(ruleResolver));
ApkGenruleDescription description = new ApkGenruleDescription();
ApkGenruleDescriptionArg arg =
ApkGenruleDescriptionArg.builder()
.setName(buildTarget.getShortName())
.setApk(new FakeInstallable(apkTarget, pathResolver).getBuildTarget())
.setBash("")
.setCmd("python signer.py $APK key.properties > $OUT")
.setCmdExe("")
.setOut("signed_fb4a.apk")
.setSrcs(
ImmutableList.of(
new PathSourcePath(
projectFilesystem, fileSystem.getPath("src/com/facebook/signer.py")),
new PathSourcePath(
projectFilesystem, fileSystem.getPath("src/com/facebook/key.properties"))))
.build();
BuildRuleParams params =
new FakeBuildRuleParamsBuilder(buildTarget).setProjectFilesystem(projectFilesystem).build();
ApkGenrule apkGenrule =
(ApkGenrule)
description.createBuildRule(
TargetGraph.EMPTY,
params,
ruleResolver,
TestCellBuilder.createCellRoots(params.getProjectFilesystem()),
arg);
ruleResolver.addToIndex(apkGenrule);
// Verify all of the observers of the Genrule.
String expectedApkOutput =
projectFilesystem
.resolve(
projectFilesystem.getBuckPaths().getGenDir().toString()
+ "/src/com/facebook/sign_fb4a/sign_fb4a.apk")
.toString();
assertEquals(expectedApkOutput, apkGenrule.getAbsoluteOutputFilePath(pathResolver));
assertEquals(
"The apk that this rule is modifying must have the apk in its deps.",
ImmutableSet.of(apkTarget.toString()),
apkGenrule
.getBuildDeps()
.stream()
.map(Object::toString)
.collect(MoreCollectors.toImmutableSet()));
BuildContext buildContext = FakeBuildContext.withSourcePathResolver(pathResolver);
Iterable<Path> expectedInputsToCompareToOutputs =
ImmutableList.of(
fileSystem.getPath("src/com/facebook/signer.py"),
fileSystem.getPath("src/com/facebook/key.properties"));
MoreAsserts.assertIterablesEquals(
expectedInputsToCompareToOutputs,
pathResolver.filterInputsToCompareToOutput(apkGenrule.getSrcs()));
// Verify that the shell commands that the genrule produces are correct.
List<Step> steps = apkGenrule.getBuildSteps(buildContext, new FakeBuildableContext());
assertEquals(11, steps.size());
ExecutionContext executionContext = newEmptyExecutionContext();
assertEquals(
RmStep.of(
projectFilesystem,
projectFilesystem.getBuckPaths().getGenDir().resolve("src/com/facebook/sign_fb4a"))
.withRecursive(true),
steps.get(0));
assertEquals(
MkdirStep.of(
projectFilesystem,
projectFilesystem.getBuckPaths().getGenDir().resolve("src/com/facebook/sign_fb4a")),
steps.get(1));
assertEquals(
RmStep.of(
projectFilesystem,
projectFilesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/sign_fb4a__tmp"))
.withRecursive(true),
steps.get(2));
assertEquals(
MkdirStep.of(
projectFilesystem,
projectFilesystem
.getBuckPaths()
.getGenDir()
.resolve("src/com/facebook/sign_fb4a__tmp")),
steps.get(3));
Path relativePathToSrcDir =
projectFilesystem.getBuckPaths().getGenDir().resolve("src/com/facebook/sign_fb4a__srcs");
assertEquals(
RmStep.of(projectFilesystem, relativePathToSrcDir).withRecursive(true), steps.get(4));
assertEquals(MkdirStep.of(projectFilesystem, relativePathToSrcDir), steps.get(5));
assertEquals(MkdirStep.of(projectFilesystem, relativePathToSrcDir), steps.get(6));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(projectFilesystem)
.setExistingFile(fileSystem.getPath("src/com/facebook/signer.py"))
.setDesiredLink(fileSystem.getPath(relativePathToSrcDir + "/signer.py"))
.build(),
steps.get(7));
assertEquals(MkdirStep.of(projectFilesystem, relativePathToSrcDir), steps.get(8));
assertEquals(
SymlinkFileStep.builder()
.setFilesystem(projectFilesystem)
.setExistingFile(fileSystem.getPath("src/com/facebook/key.properties"))
.setDesiredLink(fileSystem.getPath(relativePathToSrcDir + "/key.properties"))
.build(),
steps.get(9));
Step genruleStep = steps.get(10);
assertTrue(genruleStep instanceof AbstractGenruleStep);
AbstractGenruleStep genruleCommand = (AbstractGenruleStep) genruleStep;
assertEquals("genrule", genruleCommand.getShortName());
ImmutableMap<String, String> environmentVariables =
genruleCommand.getEnvironmentVariables(executionContext);
assertEquals(
new ImmutableMap.Builder<String, String>()
.put(
"APK",
projectFilesystem
.resolve(BuildTargets.getGenPath(projectFilesystem, apkTarget, "%s.apk"))
.toString())
.put("OUT", expectedApkOutput)
.build(),
environmentVariables);
Path scriptFilePath = genruleCommand.getScriptFilePath(executionContext);
String scriptFileContents = genruleCommand.getScriptFileContents(executionContext);
assertEquals(
ImmutableList.of("/bin/bash", "-e", scriptFilePath.toString()),
genruleCommand.getShellCommand(executionContext));
assertEquals("python signer.py $APK key.properties > $OUT", scriptFileContents);
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 HasInstallableApk {
public FakeInstallable(BuildTarget buildTarget, SourcePathResolver resolver) {
super(buildTarget, resolver);
}
@Override
public ApkInfo getApkInfo() {
return ApkInfo.builder()
.setApkPath(new FakeSourcePath("buck-out/gen/fb4a.apk"))
.setManifestPath(new FakeSourcePath("spoof"))
.build();
}
}
}