| /* |
| * 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 com.facebook.buck.io.ProjectFilesystem; |
| import com.facebook.buck.java.JavaLibraryBuilder; |
| import com.facebook.buck.java.KeystoreBuilder; |
| import com.facebook.buck.java.PrebuiltJarBuilder; |
| import com.facebook.buck.model.BuildTarget; |
| import com.facebook.buck.model.BuildTargetFactory; |
| import com.facebook.buck.rules.BuildRule; |
| import com.facebook.buck.rules.BuildRuleResolver; |
| import com.facebook.buck.rules.PathSourcePath; |
| import com.facebook.buck.rules.SourcePathResolver; |
| import com.facebook.buck.rules.TestSourcePath; |
| import com.facebook.buck.testutil.FakeProjectFilesystem; |
| import com.facebook.buck.util.BuckConstant; |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedSet; |
| |
| import org.junit.Test; |
| |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| |
| public class AndroidPackageableCollectorTest { |
| |
| /** |
| * This is a regression test to ensure that an additional 1 second startup cost is not |
| * re-introduced to fb4a. |
| */ |
| @Test |
| public void testFindTransitiveDependencies() throws IOException { |
| BuildRuleResolver ruleResolver = new BuildRuleResolver(); |
| SourcePathResolver pathResolver = new SourcePathResolver(ruleResolver); |
| ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); |
| Path prebuiltNativeLibraryPath = Paths.get("java/com/facebook/prebuilt_native_library/libs"); |
| projectFilesystem.mkdirs(prebuiltNativeLibraryPath); |
| |
| // Create an AndroidBinaryRule that transitively depends on two prebuilt JARs. One of the two |
| // prebuilt JARs will be listed in the AndroidBinaryRule's no_dx list. |
| BuildTarget guavaTarget = BuildTargetFactory.newInstance("//third_party/guava:guava"); |
| PrebuiltJarBuilder |
| .createBuilder(guavaTarget) |
| .setBinaryJar(Paths.get("third_party/guava/guava-10.0.1.jar")) |
| .build(ruleResolver); |
| |
| BuildTarget jsr305Target = BuildTargetFactory.newInstance("//third_party/jsr-305:jsr-305"); |
| PrebuiltJarBuilder |
| .createBuilder(jsr305Target) |
| .setBinaryJar(Paths.get("third_party/jsr-305/jsr305.jar")) |
| .build(ruleResolver); |
| |
| BuildRule ndkLibrary = |
| NdkLibraryBuilder |
| .createNdkLibrary(BuildTargetFactory.newInstance( |
| "//java/com/facebook/native_library:library"), |
| pathResolver) |
| .addSrc(Paths.get("Android.mk")) |
| .setIsAsset(false).build(); |
| ruleResolver.addToIndex(ndkLibrary); |
| |
| BuildTarget prebuiltNativeLibraryTarget = |
| BuildTargetFactory.newInstance("//java/com/facebook/prebuilt_native_library:library"); |
| BuildRule prebuiltNativeLibraryBuild = |
| PrebuiltNativeLibraryBuilder.newBuilder(prebuiltNativeLibraryTarget) |
| .setNativeLibs(prebuiltNativeLibraryPath) |
| .setIsAsset(true) |
| .build(ruleResolver, projectFilesystem); |
| |
| BuildTarget libraryRuleTarget = |
| BuildTargetFactory.newInstance("//java/src/com/facebook:example"); |
| JavaLibraryBuilder |
| .createBuilder(libraryRuleTarget) |
| .setProguardConfig(Paths.get("debug.pro")) |
| .addSrc(Paths.get("Example.java")) |
| .addDep(guavaTarget) |
| .addDep(jsr305Target) |
| .addDep(prebuiltNativeLibraryBuild.getBuildTarget()) |
| .addDep(ndkLibrary.getBuildTarget()) |
| .build(ruleResolver); |
| |
| BuildTarget manifestTarget = BuildTargetFactory.newInstance("//java/src/com/facebook:res"); |
| AndroidResource manifestRule = AndroidResourceRuleBuilder |
| .newBuilder() |
| .setResolver(pathResolver) |
| .setBuildTarget(manifestTarget) |
| .setManifest( |
| new PathSourcePath( |
| projectFilesystem, |
| Paths.get("java/src/com/facebook/module/AndroidManifest.xml"))) |
| .setAssets(Paths.get("assets")) |
| .build(); |
| ruleResolver.addToIndex(manifestRule); |
| |
| BuildTarget keystoreTarget = BuildTargetFactory.newInstance("//keystore:debug"); |
| KeystoreBuilder.createBuilder(keystoreTarget) |
| .setStore(Paths.get("keystore/debug.keystore")) |
| .setProperties(Paths.get("keystore/debug.keystore.properties")) |
| .build(ruleResolver); |
| |
| ImmutableSortedSet<BuildTarget> originalDepsTargets = |
| ImmutableSortedSet.of(libraryRuleTarget, manifestTarget); |
| ruleResolver.getAllRules(originalDepsTargets); |
| AndroidBinary binaryRule = (AndroidBinary) AndroidBinaryBuilder.createBuilder( |
| BuildTargetFactory.newInstance("//java/src/com/facebook:app")) |
| .setOriginalDeps(originalDepsTargets) |
| .setBuildTargetsToExcludeFromDex( |
| ImmutableSet.of(BuildTargetFactory.newInstance("//third_party/guava:guava"))) |
| .setManifest(new TestSourcePath("java/src/com/facebook/AndroidManifest.xml")) |
| .setTarget("Google Inc.:Google APIs:16") |
| .setKeystore(keystoreTarget) |
| .build(ruleResolver); |
| |
| // Verify that the correct transitive dependencies are found. |
| AndroidPackageableCollection packageableCollection = |
| binaryRule.getAndroidPackageableCollection(); |
| assertEquals( |
| "Because guava was passed to no_dx, it should not be in the classpathEntriesToDex list", |
| ImmutableSet.of( |
| Paths.get("third_party/jsr-305/jsr305.jar"), |
| BuckConstant.GEN_PATH.resolve( |
| "java/src/com/facebook/lib__example__output/example.jar")), |
| packageableCollection.getClasspathEntriesToDex()); |
| assertEquals( |
| "Because guava was passed to no_dx, it should not be treated as a third-party JAR whose " + |
| "resources need to be extracted and repacked in the APK. If this is done, then code " + |
| "in the guava-10.0.1.dex.1.jar in the APK's assets/ tmp may try to load the resource " + |
| "from the APK as a ZipFileEntry rather than as a resource within " + |
| "guava-10.0.1.dex.1.jar. Loading a resource in this way could take substantially " + |
| "longer. Specifically, this was observed to take over one second longer to load " + |
| "the resource in fb4a. Because the resource was loaded on startup, this introduced a " + |
| "substantial regression in the startup time for the fb4a app.", |
| ImmutableSet.of(Paths.get("third_party/jsr-305/jsr305.jar")), |
| packageableCollection.getPathsToThirdPartyJars()); |
| assertEquals( |
| "Because assets directory was passed an AndroidResourceRule it should be added to the " + |
| "transitive dependencies", |
| ImmutableSet.of(Paths.get("assets")), |
| packageableCollection.getAssetsDirectories()); |
| assertEquals( |
| "Because manifest file was passed an AndroidResourceRule it should be added to the " + |
| "transitive dependencies", |
| ImmutableSet.of(Paths.get("java/src/com/facebook/module/AndroidManifest.xml")), |
| packageableCollection.getManifestFiles()); |
| assertEquals( |
| "Because a native library was declared as a dependency, it should be added to the " + |
| "transitive dependencies.", |
| ImmutableSet.of(((NativeLibraryBuildRule) ndkLibrary).getLibraryPath()), |
| packageableCollection.getNativeLibsDirectories()); |
| assertEquals( |
| "Because a prebuilt native library was declared as a dependency (and asset), it should " + |
| "be added to the transitive dependecies.", |
| ImmutableSet.of(((NativeLibraryBuildRule) prebuiltNativeLibraryBuild) |
| .getLibraryPath()), |
| packageableCollection.getNativeLibAssetsDirectories()); |
| assertEquals( |
| ImmutableSet.of(Paths.get("debug.pro")), |
| packageableCollection.getProguardConfigs()); |
| } |
| |
| /** |
| * Create the following dependency graph of {@link AndroidResource}s: |
| * <pre> |
| * A |
| * / | \ |
| * B | D |
| * \ | / |
| * C |
| * </pre> |
| * Note that an ordinary breadth-first traversal would yield either {@code A B C D} or |
| * {@code A D C B}. However, either of these would be <em>wrong</em> in this case because we need |
| * to be sure that we perform a topological sort, the resulting traversal of which is either |
| * {@code A B D C} or {@code A D B C}. |
| * <p> |
| * The reason for the correct result being reversed is because we want the resources with the most |
| * dependencies listed first on the path, so that they're used in preference to the ones that they |
| * depend on (presumably, the reason for extending the initial set of resources was to override |
| * values). |
| */ |
| @Test |
| public void testGetAndroidResourceDeps() { |
| BuildRuleResolver ruleResolver = new BuildRuleResolver(); |
| SourcePathResolver pathResolver = new SourcePathResolver(ruleResolver); |
| BuildRule c = ruleResolver.addToIndex( |
| AndroidResourceRuleBuilder.newBuilder() |
| .setResolver(pathResolver) |
| .setBuildTarget(BuildTargetFactory.newInstance("//:c")) |
| .setRes(Paths.get("res_c")) |
| .setRDotJavaPackage("com.facebook") |
| .build()); |
| |
| BuildRule b = ruleResolver.addToIndex( |
| AndroidResourceRuleBuilder.newBuilder() |
| .setResolver(pathResolver) |
| .setBuildTarget(BuildTargetFactory.newInstance("//:b")) |
| .setRes(Paths.get("res_b")) |
| .setRDotJavaPackage("com.facebook") |
| .setDeps(ImmutableSortedSet.of(c)) |
| .build()); |
| |
| BuildRule d = ruleResolver.addToIndex( |
| AndroidResourceRuleBuilder.newBuilder() |
| .setResolver(pathResolver) |
| .setBuildTarget(BuildTargetFactory.newInstance("//:d")) |
| .setRes(Paths.get("res_d")) |
| .setRDotJavaPackage("com.facebook") |
| .setDeps(ImmutableSortedSet.of(c)) |
| .build()); |
| |
| AndroidResource a = ruleResolver.addToIndex( |
| AndroidResourceRuleBuilder.newBuilder() |
| .setResolver(pathResolver) |
| .setBuildTarget(BuildTargetFactory.newInstance("//:a")) |
| .setRes(Paths.get("res_a")) |
| .setRDotJavaPackage("com.facebook") |
| .setDeps(ImmutableSortedSet.of(b, c, d)) |
| .build()); |
| |
| AndroidPackageableCollector collector = new AndroidPackageableCollector(a.getBuildTarget()); |
| collector.addPackageables(ImmutableList.<AndroidPackageable>of(a)); |
| |
| // Note that a topological sort for a DAG is not guaranteed to be unique, but we order nodes |
| // within the same depth of the search. |
| ImmutableList<BuildTarget> result = FluentIterable.from(ImmutableList.of(a, d, b, c)) |
| .transform(BuildTarget.TO_TARGET) |
| .toList(); |
| |
| assertEquals( |
| String.format("Android resources should be topologically sorted."), |
| result, |
| collector.build().getResourceDetails().getResourcesWithNonEmptyResDir()); |
| |
| // Introduce an AndroidBinaryRule that depends on A and C and verify that the same topological |
| // sort results. This verifies that both AndroidResourceRule.getAndroidResourceDeps does the |
| // right thing when it gets a non-AndroidResourceRule as well as an AndroidResourceRule. |
| BuildTarget keystoreTarget = BuildTargetFactory.newInstance("//keystore:debug"); |
| KeystoreBuilder.createBuilder(keystoreTarget) |
| .setStore(Paths.get("keystore/debug.keystore")) |
| .setProperties(Paths.get("keystore/debug.keystore.properties")) |
| .build(ruleResolver); |
| |
| ImmutableSortedSet<BuildTarget> declaredDepsTargets = |
| ImmutableSortedSet.of(a.getBuildTarget(), c.getBuildTarget()); |
| AndroidBinary androidBinary = (AndroidBinary) AndroidBinaryBuilder |
| .createBuilder(BuildTargetFactory.newInstance("//:e")) |
| .setManifest(new TestSourcePath("AndroidManfiest.xml")) |
| .setTarget("Google Inc.:Google APIs:16") |
| .setKeystore(keystoreTarget) |
| .setOriginalDeps(declaredDepsTargets) |
| .build(ruleResolver); |
| |
| assertEquals( |
| String.format("Android resources should be topologically sorted."), |
| result, |
| androidBinary |
| .getAndroidPackageableCollection() |
| .getResourceDetails() |
| .getResourcesWithNonEmptyResDir()); |
| } |
| |
| /** |
| * If the keystore rule depends on an android_library, and an android_binary uses that keystore, |
| * the keystore's android_library should not contribute to the classpath of the android_binary. |
| */ |
| @Test |
| public void testGraphForAndroidBinaryExcludesKeystoreDeps() { |
| BuildRuleResolver ruleResolver = new BuildRuleResolver(); |
| |
| BuildTarget androidLibraryKeystoreTarget = |
| BuildTarget.builder("//java/com/keystore/base", "base").build(); |
| BuildRule androidLibraryKeystore = AndroidLibraryBuilder |
| .createBuilder(androidLibraryKeystoreTarget) |
| .addSrc(Paths.get("java/com/facebook/keystore/Base.java")) |
| .build(ruleResolver); |
| |
| BuildTarget keystoreTarget = BuildTarget.builder("//keystore", "debug").build(); |
| KeystoreBuilder.createBuilder(keystoreTarget) |
| .setStore(Paths.get("keystore/debug.keystore")) |
| .setProperties(Paths.get("keystore/debug.keystore.properties")) |
| .addDep(androidLibraryKeystore.getBuildTarget()) |
| .build(ruleResolver); |
| |
| BuildTarget androidLibraryTarget = |
| BuildTarget.builder("//java/com/facebook/base", "base").build(); |
| BuildRule androidLibrary = AndroidLibraryBuilder.createBuilder(androidLibraryTarget) |
| .addSrc(Paths.get("java/com/facebook/base/Base.java")) |
| .build(ruleResolver); |
| |
| ImmutableSortedSet<BuildTarget> originalDepsTargets = |
| ImmutableSortedSet.of(androidLibrary.getBuildTarget()); |
| AndroidBinary androidBinary = (AndroidBinary) AndroidBinaryBuilder.createBuilder( |
| BuildTarget.builder("//apps/sample", "app").build()) |
| .setManifest(new TestSourcePath("apps/sample/AndroidManifest.xml")) |
| .setTarget("Google Inc.:Google APIs:16") |
| .setOriginalDeps(originalDepsTargets) |
| .setKeystore(keystoreTarget) |
| .build(ruleResolver); |
| |
| AndroidPackageableCollection packageableCollection = |
| androidBinary.getAndroidPackageableCollection(); |
| assertEquals( |
| "Classpath entries should include facebook/base but not keystore/base.", |
| ImmutableSet.of( |
| BuckConstant.GEN_PATH.resolve("java/com/facebook/base/lib__base__output/base.jar")), |
| packageableCollection.getClasspathEntriesToDex()); |
| } |
| } |