| /* |
| * 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 com.facebook.buck.dalvik.ZipSplitter; |
| import com.facebook.buck.java.Classpaths; |
| import com.facebook.buck.model.BuildTarget; |
| import com.facebook.buck.rules.AbstractBuildRuleBuilder; |
| import com.facebook.buck.rules.AbstractBuildRuleBuilderParams; |
| 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.DependencyGraph; |
| import com.facebook.buck.rules.InstallableBuildRule; |
| import com.facebook.buck.rules.RuleKey; |
| import com.facebook.buck.util.HumanReadableException; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedSet; |
| |
| import java.io.IOException; |
| |
| |
| /** |
| * Apk that functions as a test package in Android. |
| * <p> |
| * Android's |
| * <a href="http://developer.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> |
| * documentation includes a diagram that shows the relationship between an "application package" and |
| * a "test package" when running a test. This corresponds to a test package. Note that a test |
| * package has an interesting quirk where it is <em>compiled against</em> an application package, |
| * but <em>must not include</em> the resources or Java classes of the application package. |
| * Therefore, this class takes responsibility for making sure the appropriate bits are excluded. |
| * Failing to do so will generate mysterious runtime errors when running the test. |
| */ |
| public class AndroidInstrumentationApk extends AndroidBinaryRule { |
| |
| private final AndroidBinaryRule apkUnderTest; |
| private final ImmutableSortedSet<BuildRule> classpathDepsForInstrumentationApk; |
| |
| private AndroidInstrumentationApk(BuildRuleParams buildRuleParams, |
| String manifest, |
| AndroidBinaryRule apkUnderTest, |
| ImmutableSortedSet<BuildRule> classpathDepsForInstrumentationApk) { |
| super(buildRuleParams, |
| manifest, |
| apkUnderTest.getTarget(), |
| classpathDepsForInstrumentationApk, |
| apkUnderTest.getKeystore(), |
| PackageType.INSTRUMENTED, |
| // Do not include the classes that will already be in the classes.dex of the APK under test. |
| ImmutableSet.<BuildRule>builder() |
| .addAll(apkUnderTest.getBuildRulesToExcludeFromDex()) |
| .addAll(Classpaths.getClasspathEntries(apkUnderTest.getClasspathDeps()).keySet()) |
| .build(), |
| // Do not split the test apk even if the tested apk is split |
| new DexSplitMode( |
| /* shouldSplitDex */ false, |
| ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE, |
| DexStore.JAR, |
| /* useLinearAllocSplitDex */ false), |
| apkUnderTest.isUseAndroidProguardConfigWithOptimizations(), |
| apkUnderTest.getProguardConfig(), |
| apkUnderTest.isCompressResources(), |
| apkUnderTest.getPrimaryDexSubstrings(), |
| apkUnderTest.getLinearAllocHardLimit(), |
| apkUnderTest.getPrimaryDexClassesFile(), |
| apkUnderTest.getResourceFilter(), |
| apkUnderTest.getCpuFilters(), |
| apkUnderTest.getPreprocessJavaClassesDeps(), |
| apkUnderTest.getPreprocessJavaClassesBash()); |
| this.apkUnderTest = apkUnderTest; |
| this.classpathDepsForInstrumentationApk = Preconditions.checkNotNull( |
| classpathDepsForInstrumentationApk); |
| } |
| |
| @Override |
| public BuildRuleType getType() { |
| return BuildRuleType.ANDROID_INSTRUMENTATION_APK; |
| } |
| |
| @Override |
| public RuleKey.Builder appendToRuleKey(RuleKey.Builder builder) throws IOException { |
| return super.appendToRuleKey(builder) |
| .set("apkUnderTest", apkUnderTest) |
| .setRuleNames("classpathDepsForInstrumentationApk", classpathDepsForInstrumentationApk); |
| } |
| |
| @Override |
| protected ImmutableList<HasAndroidResourceDeps> getAndroidResourceDepsInternal( |
| DependencyGraph graph) { |
| // Filter out the AndroidResourceRules that are needed by this APK but not the APK under test. |
| ImmutableSet<HasAndroidResourceDeps> originalResources = ImmutableSet.copyOf( |
| UberRDotJavaUtil.getAndroidResourceDeps(apkUnderTest, graph)); |
| ImmutableList<HasAndroidResourceDeps> instrumentationResources = |
| UberRDotJavaUtil.getAndroidResourceDeps(this, graph); |
| |
| // Include all of the instrumentation resources first, in their original order. |
| ImmutableList.Builder<HasAndroidResourceDeps> allResources = ImmutableList.builder(); |
| for (HasAndroidResourceDeps resource : instrumentationResources) { |
| if (!originalResources.contains(resource)) { |
| allResources.add(resource); |
| } |
| } |
| return allResources.build(); |
| } |
| |
| public static Builder newAndroidInstrumentationApkRuleBuilder( |
| AbstractBuildRuleBuilderParams params) { |
| return new Builder(params); |
| } |
| |
| public static class Builder extends AbstractBuildRuleBuilder<AndroidInstrumentationApk> { |
| |
| private String manifest = null; |
| private BuildTarget apk = null; |
| |
| /** This should always be a subset of {@link #getDeps()}. */ |
| private ImmutableSet.Builder<BuildTarget> classpathDeps = ImmutableSet.builder(); |
| |
| private Builder(AbstractBuildRuleBuilderParams params) { |
| super(params); |
| } |
| |
| @Override |
| public AndroidInstrumentationApk build(BuildRuleResolver ruleResolver) { |
| BuildRule apkRule = ruleResolver.get(this.apk); |
| if (apkRule == null) { |
| throw new HumanReadableException("Must specify apk for " + getBuildTarget()); |
| } else if (!(apkRule instanceof InstallableBuildRule)) { |
| throw new HumanReadableException( |
| "In %s, apk='%s' must be an android_binary() or apk_genrule() but was %s().", |
| getBuildTarget(), |
| apkRule.getFullyQualifiedName(), |
| apkRule.getType().getName()); |
| } |
| |
| AndroidBinaryRule underlyingApk = getUnderlyingApk((InstallableBuildRule)apkRule); |
| |
| return new AndroidInstrumentationApk(createBuildRuleParams(ruleResolver), |
| manifest, |
| underlyingApk, |
| getBuildTargetsAsBuildRules(ruleResolver, classpathDeps.build())); |
| } |
| |
| public Builder setManifest(String manifest) { |
| this.manifest = manifest; |
| return this; |
| } |
| |
| public Builder setApk(BuildTarget apk) { |
| this.apk = apk; |
| return this; |
| } |
| |
| public Builder addClasspathDep(BuildTarget classpathDep) { |
| this.classpathDeps.add(classpathDep); |
| addDep(classpathDep); |
| return this; |
| } |
| } |
| |
| private static AndroidBinaryRule getUnderlyingApk(InstallableBuildRule rule) { |
| if (rule instanceof AndroidBinaryRule) { |
| return (AndroidBinaryRule)rule; |
| } else if (rule instanceof ApkGenrule) { |
| return getUnderlyingApk(((ApkGenrule)rule).getInstallableBuildRule()); |
| } else { |
| throw new IllegalStateException( |
| rule.getFullyQualifiedName() + |
| " must be backed by either an android_binary() or an apk_genrule()"); |
| } |
| } |
| |
| } |