| // Copyright 2015 The Bazel Authors. All rights reserved. |
| // |
| // 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.google.devtools.build.lib.rules.android; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Predicates.in; |
| import static com.google.common.base.Predicates.not; |
| import static com.google.common.collect.Iterables.filter; |
| import static com.google.devtools.build.lib.analysis.OutputGroupProvider.INTERNAL_SUFFIX; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.MultimapBuilder; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.FailAction; |
| import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; |
| import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.Runfiles; |
| import com.google.devtools.build.lib.analysis.RunfilesProvider; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.actions.CommandLine; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.collect.IterablesChain; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.TriState; |
| import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; |
| import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidBinaryType; |
| import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod; |
| import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode; |
| import com.google.devtools.build.lib.rules.android.ApkActionsBuilder.LegacySignerApkActionsBuilder; |
| import com.google.devtools.build.lib.rules.android.ApkActionsBuilder.SignerToolApkActionsBuilder; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider; |
| import com.google.devtools.build.lib.rules.cpp.CppHelper; |
| import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder; |
| import com.google.devtools.build.lib.rules.java.JavaCommon; |
| import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider; |
| import com.google.devtools.build.lib.rules.java.JavaConfiguration; |
| import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaOptimizationMode; |
| import com.google.devtools.build.lib.rules.java.JavaSemantics; |
| import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider; |
| import com.google.devtools.build.lib.rules.java.JavaTargetAttributes; |
| import com.google.devtools.build.lib.rules.java.ProguardHelper; |
| import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput; |
| import com.google.devtools.build.lib.syntax.Type; |
| import com.google.devtools.build.lib.util.Preconditions; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** |
| * An implementation for the "android_binary" rule. |
| */ |
| public abstract class AndroidBinary implements RuleConfiguredTargetFactory { |
| |
| protected abstract JavaSemantics createJavaSemantics(); |
| protected abstract AndroidSemantics createAndroidSemantics(); |
| |
| @Override |
| public ConfiguredTarget create(RuleContext ruleContext) |
| throws InterruptedException, RuleErrorException { |
| JavaSemantics javaSemantics = createJavaSemantics(); |
| AndroidSemantics androidSemantics = createAndroidSemantics(); |
| if (!AndroidSdkProvider.verifyPresence(ruleContext)) { |
| return null; |
| } |
| |
| NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder(); |
| ImmutableList<TransitiveInfoCollection> deps = ImmutableList.<TransitiveInfoCollection>copyOf( |
| ruleContext.getPrerequisites("deps", Mode.TARGET)); |
| JavaCommon javaCommon = new JavaCommon( |
| ruleContext, javaSemantics, deps, deps, deps); |
| javaSemantics.checkRule(ruleContext, javaCommon); |
| javaSemantics.checkForProtoLibraryAndJavaProtoLibraryOnSameProto(ruleContext, javaCommon); |
| |
| AndroidCommon androidCommon = new AndroidCommon( |
| javaCommon, true /* asNeverLink */, true /* exportDeps */); |
| ResourceDependencies resourceDeps = LocalResourceContainer.definesAndroidResources( |
| ruleContext.attributes()) |
| ? ResourceDependencies.fromRuleDeps(ruleContext, false /* neverlink */) |
| : ResourceDependencies.fromRuleResourceAndDeps(ruleContext, false /* neverlink */); |
| RuleConfiguredTargetBuilder builder = init( |
| ruleContext, |
| filesBuilder, |
| resourceDeps, |
| javaCommon, |
| androidCommon, |
| javaSemantics, |
| androidSemantics); |
| return builder.build(); |
| } |
| |
| private static RuleConfiguredTargetBuilder init( |
| RuleContext ruleContext, |
| NestedSetBuilder<Artifact> filesBuilder, |
| ResourceDependencies resourceDeps, |
| JavaCommon javaCommon, |
| AndroidCommon androidCommon, |
| JavaSemantics javaSemantics, |
| AndroidSemantics androidSemantics) |
| throws InterruptedException, RuleErrorException { |
| |
| if (getMultidexMode(ruleContext) != MultidexMode.LEGACY |
| && ruleContext.attributes().isAttributeValueExplicitlySpecified( |
| "main_dex_proguard_specs")) { |
| ruleContext.throwWithAttributeError("main_dex_proguard_specs", "The " |
| + "'main_dex_proguard_specs' attribute is only allowed if 'multidex' is set to 'legacy'"); |
| } |
| |
| if (ruleContext.attributes().isAttributeValueExplicitlySpecified("proguard_apply_mapping") |
| && ruleContext.attributes() |
| .get(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST) |
| .isEmpty()) { |
| ruleContext.throwWithAttributeError("proguard_apply_mapping", |
| "'proguard_apply_mapping' can only be used when 'proguard_specs' is also set"); |
| } |
| |
| // TODO(bazel-team): Find a way to simplify this code. |
| // treeKeys() means that the resulting map sorts the entries by key, which is necessary to |
| // ensure determinism. |
| Multimap<String, TransitiveInfoCollection> depsByArchitecture = |
| MultimapBuilder.treeKeys().arrayListValues().build(); |
| AndroidConfiguration androidConfig = ruleContext.getFragment(AndroidConfiguration.class); |
| for (Map.Entry<Optional<String>, ? extends List<? extends TransitiveInfoCollection>> entry : |
| ruleContext.getSplitPrerequisites("deps").entrySet()) { |
| String cpu = entry.getKey().or(androidConfig.getCpu()); |
| depsByArchitecture.putAll(cpu, entry.getValue()); |
| } |
| Map<String, BuildConfiguration> configurationMap = new LinkedHashMap<>(); |
| Map<String, CcToolchainProvider> toolchainMap = new LinkedHashMap<>(); |
| for (Map.Entry<Optional<String>, ? extends List<? extends TransitiveInfoCollection>> entry : |
| ruleContext.getSplitPrerequisites(":cc_toolchain_split").entrySet()) { |
| String cpu = entry.getKey().or(androidConfig.getCpu()); |
| TransitiveInfoCollection dep = Iterables.getOnlyElement(entry.getValue()); |
| CcToolchainProvider toolchain = CppHelper.getToolchain(ruleContext, dep); |
| configurationMap.put(cpu, dep.getConfiguration()); |
| toolchainMap.put(cpu, toolchain); |
| } |
| |
| NativeLibs nativeLibs = |
| NativeLibs.fromLinkedNativeDeps( |
| ruleContext, |
| androidSemantics.getNativeDepsFileName(), |
| depsByArchitecture, |
| toolchainMap, |
| configurationMap); |
| |
| // TODO(bazel-team): Resolve all the different cases of resource handling so this conditional |
| // can go away: recompile from android_resources, and recompile from android_binary attributes. |
| ApplicationManifest applicationManifest; |
| ResourceApk resourceApk; |
| ResourceApk incrementalResourceApk; |
| ResourceApk instantRunResourceApk; |
| ResourceApk splitResourceApk; |
| if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) { |
| // Retrieve and compile the resources defined on the android_binary rule. |
| LocalResourceContainer.validateRuleContext(ruleContext); |
| ApplicationManifest ruleManifest = androidSemantics.getManifestForRule(ruleContext); |
| |
| applicationManifest = ruleManifest.mergeWith(ruleContext, resourceDeps); |
| |
| resourceApk = applicationManifest.packWithDataAndResources( |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), |
| ruleContext, |
| false, /* isLibrary */ |
| resourceDeps, |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), |
| null, /* Artifact symbolsTxt */ |
| ruleContext.getTokenizedStringListAttr("resource_configuration_filters"), |
| ruleContext.getTokenizedStringListAttr("nocompress_extensions"), |
| ruleContext.attributes().get("crunch_png", Type.BOOLEAN), |
| ruleContext.getTokenizedStringListAttr("densities"), |
| false, /* incremental */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, ""), |
| createMainDexProguardSpec(ruleContext), |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)); |
| ruleContext.assertNoErrors(); |
| |
| incrementalResourceApk = applicationManifest |
| .addMobileInstallStubApplication(ruleContext) |
| .packWithDataAndResources( |
| ruleContext |
| .getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK), |
| ruleContext, |
| false, /* isLibrary */ |
| resourceDeps, |
| null, /* Artifact rTxt */ |
| null, /* Artifact symbolsTxt */ |
| ruleContext.getTokenizedStringListAttr("resource_configuration_filters"), |
| ruleContext.getTokenizedStringListAttr("nocompress_extensions"), |
| ruleContext.attributes().get("crunch_png", Type.BOOLEAN), |
| ruleContext.getTokenizedStringListAttr("densities"), |
| true, /* incremental */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), |
| null, /* mainDexProguardCfg */ |
| null, /* manifestOut */ |
| null /* mergedResourcesOut */); |
| ruleContext.assertNoErrors(); |
| |
| instantRunResourceApk = applicationManifest |
| .addInstantRunStubApplication(ruleContext) |
| .packWithDataAndResources( |
| getDxArtifact(ruleContext, "android_instant_run.ap_"), |
| ruleContext, |
| false, /* isLibrary */ |
| resourceDeps, |
| null, /* Artifact rTxt */ |
| null, /* Artifact symbolsTxt */ |
| ruleContext.getTokenizedStringListAttr("resource_configuration_filters"), |
| ruleContext.getTokenizedStringListAttr("nocompress_extensions"), |
| ruleContext.attributes().get("crunch_png", Type.BOOLEAN), |
| ruleContext.getTokenizedStringListAttr("densities"), |
| true, /* incremental */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), |
| null, /* mainDexProguardCfg */ |
| null, /* manifestOut */ |
| null /* mergedResourcesOut */); |
| ruleContext.assertNoErrors(); |
| |
| splitResourceApk = applicationManifest |
| .createSplitManifest(ruleContext, "android_resources", false) |
| .packWithDataAndResources( |
| getDxArtifact(ruleContext, "android_resources.ap_"), |
| ruleContext, |
| false, /* isLibrary */ |
| resourceDeps, |
| null, /* Artifact rTxt */ |
| null, /* Artifact symbolsTxt */ |
| ruleContext.getTokenizedStringListAttr("resource_configuration_filters"), |
| ruleContext.getTokenizedStringListAttr("nocompress_extensions"), |
| ruleContext.attributes().get("crunch_png", Type.BOOLEAN), |
| ruleContext.getTokenizedStringListAttr("densities"), |
| true, /* incremental */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), |
| null, /* mainDexProguardCfg */ |
| null, /* manifestOut */ |
| null /* mergedResourcesOut */); |
| ruleContext.assertNoErrors(); |
| |
| } else { |
| |
| if (!ruleContext.attributes().get("crunch_png", Type.BOOLEAN)) { |
| ruleContext.throwWithRuleError("Setting crunch_png = 0 is not supported for android_binary" |
| + " rules which depend on android_resources rules."); |
| } |
| |
| // Retrieve the resources from the resources attribute on the android_binary rule |
| // and recompile them if necessary. |
| ApplicationManifest resourcesManifest = ApplicationManifest.fromResourcesRule(ruleContext); |
| if (resourcesManifest == null) { |
| throw new RuleErrorException(); |
| } |
| applicationManifest = resourcesManifest.mergeWith(ruleContext, resourceDeps); |
| |
| // Always recompiling resources causes AndroidTest to fail in certain circumstances. |
| if (shouldRegenerate(ruleContext, resourceDeps)) { |
| resourceApk = applicationManifest.packWithResources( |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), |
| ruleContext, |
| resourceDeps, |
| true, /* createSource */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, ""), |
| createMainDexProguardSpec(ruleContext)); |
| ruleContext.assertNoErrors(); |
| } else { |
| resourceApk = applicationManifest.useCurrentResources( |
| ruleContext, |
| ProguardHelper.getProguardConfigArtifact(ruleContext, ""), |
| createMainDexProguardSpec(ruleContext)); |
| ruleContext.assertNoErrors(); |
| } |
| |
| incrementalResourceApk = applicationManifest |
| .addMobileInstallStubApplication(ruleContext) |
| .packWithResources( |
| ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK), |
| ruleContext, |
| resourceDeps, |
| false, /* createSource */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), |
| null /* mainDexProguardConfig */); |
| ruleContext.assertNoErrors(); |
| |
| instantRunResourceApk = applicationManifest |
| .addInstantRunStubApplication(ruleContext) |
| .packWithResources( |
| getDxArtifact(ruleContext, "android_instant_run.ap_"), |
| ruleContext, |
| resourceDeps, |
| false, /* createSource */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), |
| null /* mainDexProguardConfig */); |
| ruleContext.assertNoErrors(); |
| |
| splitResourceApk = applicationManifest |
| .createSplitManifest(ruleContext, "android_resources", false) |
| .packWithResources(getDxArtifact(ruleContext, "android_resources.ap_"), |
| ruleContext, |
| resourceDeps, |
| false, /* createSource */ |
| ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), |
| null /* mainDexProguardConfig */); |
| ruleContext.assertNoErrors(); |
| } |
| |
| JavaTargetAttributes resourceClasses = androidCommon.init( |
| javaSemantics, |
| androidSemantics, |
| resourceApk, |
| ruleContext.getConfiguration().isCodeCoverageEnabled(), |
| true /* collectJavaCompilationArgs */, |
| true /* isBinary */); |
| ruleContext.assertNoErrors(); |
| |
| Artifact deployJar = createDeployJar(ruleContext, javaSemantics, androidCommon, resourceClasses, |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR)); |
| |
| Artifact proguardMapping = ruleContext.getPrerequisiteArtifact( |
| "proguard_apply_mapping", Mode.TARGET); |
| |
| return createAndroidBinary( |
| ruleContext, |
| filesBuilder, |
| deployJar, |
| /* isBinaryJarFiltered */ false, |
| javaCommon, |
| androidCommon, |
| javaSemantics, |
| androidSemantics, |
| nativeLibs, |
| applicationManifest, |
| resourceApk, |
| incrementalResourceApk, |
| instantRunResourceApk, |
| splitResourceApk, |
| /* shrinkResources */ true, |
| resourceClasses, |
| ImmutableList.<Artifact>of(), |
| ImmutableList.<Artifact>of(), |
| proguardMapping); |
| } |
| |
| public static RuleConfiguredTargetBuilder createAndroidBinary( |
| RuleContext ruleContext, |
| NestedSetBuilder<Artifact> filesBuilder, |
| Artifact binaryJar, |
| boolean isBinaryJarFiltered, |
| JavaCommon javaCommon, |
| AndroidCommon androidCommon, |
| JavaSemantics javaSemantics, |
| AndroidSemantics androidSemantics, |
| NativeLibs nativeLibs, |
| ApplicationManifest applicationManifest, |
| ResourceApk resourceApk, |
| ResourceApk incrementalResourceApk, |
| ResourceApk instantRunResourceApk, |
| ResourceApk splitResourceApk, |
| boolean shrinkResources, |
| JavaTargetAttributes resourceClasses, |
| ImmutableList<Artifact> apksUnderTest, |
| ImmutableList<Artifact> additionalMergedManifests, |
| Artifact proguardMapping) |
| throws InterruptedException, RuleErrorException { |
| |
| ImmutableList<Artifact> proguardSpecs = ProguardHelper.collectTransitiveProguardSpecs( |
| ruleContext, ImmutableList.of(resourceApk.getResourceProguardConfig())); |
| |
| ProguardOutput proguardOutput = |
| applyProguard( |
| ruleContext, |
| androidCommon, |
| javaSemantics, |
| binaryJar, |
| filesBuilder, |
| proguardSpecs, |
| proguardMapping); |
| |
| if (shrinkResources) { |
| resourceApk = shrinkResources( |
| ruleContext, |
| resourceApk, |
| proguardSpecs, |
| proguardOutput); |
| } |
| |
| Artifact jarToDex = proguardOutput.getOutputJar(); |
| DexingOutput dexingOutput = |
| shouldDexWithJack(ruleContext) |
| ? dexWithJack(ruleContext, androidCommon, proguardSpecs) |
| : dex( |
| ruleContext, |
| androidSemantics, |
| binaryJar, |
| jarToDex, |
| isBinaryJarFiltered, |
| androidCommon, |
| resourceApk.getMainDexProguardConfig(), |
| resourceClasses); |
| |
| ApkSigningMethod signingMethod = |
| ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod(); |
| |
| Artifact unsignedApk = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK); |
| Artifact zipAlignedApk = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK); |
| |
| createApkActionsBuilder(signingMethod) |
| .setClassesDex(dexingOutput.classesDexZip) |
| .setResourceApk(resourceApk.getArtifact()) |
| .setJavaResourceZip(dexingOutput.javaResourceJar) |
| .setNativeLibs(nativeLibs) |
| .setUnsignedApk(unsignedApk) |
| .setSignedApk(zipAlignedApk) |
| .setZipalignApk(true) |
| .setApkName("apk") |
| .registerActions(ruleContext, androidSemantics); |
| |
| // Don't add blacklistedApk, so it's only built if explicitly requested. |
| filesBuilder.add(binaryJar); |
| filesBuilder.add(unsignedApk); |
| filesBuilder.add(zipAlignedApk); |
| NestedSet<Artifact> filesToBuild = filesBuilder.build(); |
| |
| Iterable<Artifact> dataDeps = ImmutableList.of(); |
| if (ruleContext.getAttribute("data") != null |
| && ruleContext.getAttributeMode("data") == Mode.DATA) { |
| dataDeps = ruleContext.getPrerequisiteArtifacts("data", Mode.DATA).list(); |
| } |
| |
| Artifact deployInfo = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEPLOY_INFO); |
| AndroidDeployInfoAction.createDeployInfoAction(ruleContext, |
| deployInfo, |
| resourceApk.getManifest(), |
| additionalMergedManifests, |
| Iterables.concat(ImmutableList.of(zipAlignedApk), apksUnderTest), |
| dataDeps); |
| |
| NestedSet<Artifact> coverageMetadata = (androidCommon.getInstrumentedJar() != null) |
| ? NestedSetBuilder.create(Order.STABLE_ORDER, androidCommon.getInstrumentedJar()) |
| : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER); |
| |
| RuleConfiguredTargetBuilder builder = |
| new RuleConfiguredTargetBuilder(ruleContext); |
| |
| Artifact incrementalApk = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_INCREMENTAL_APK); |
| |
| Artifact fullDeployMarker = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.FULL_DEPLOY_MARKER); |
| Artifact incrementalDeployMarker = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.INCREMENTAL_DEPLOY_MARKER); |
| Artifact splitDeployMarker = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.SPLIT_DEPLOY_MARKER); |
| |
| Artifact incrementalDexManifest = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEX_MANIFEST); |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setMnemonic("AndroidDexManifest") |
| .setProgressMessage("Generating incremental installation manifest for " |
| + ruleContext.getLabel()) |
| .setExecutable( |
| ruleContext.getExecutablePrerequisite("$build_incremental_dexmanifest", Mode.HOST)) |
| .addOutputArgument(incrementalDexManifest) |
| .addInputArguments(dexingOutput.shardDexZips) |
| .useParameterFile(ParameterFileType.UNQUOTED).build(ruleContext)); |
| |
| Artifact stubData = ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA); |
| Artifact stubDex = getStubDex(ruleContext, javaSemantics, false); |
| ruleContext.assertNoErrors(); |
| |
| ApkActionsBuilder incrementalActionsBuilder = createApkActionsBuilder(signingMethod) |
| .setClassesDex(stubDex) |
| .setResourceApk(incrementalResourceApk.getArtifact()) |
| .setJavaResourceZip(dexingOutput.javaResourceJar) |
| .setJavaResourceFile(stubData) |
| .setApkName("incremental apk") |
| .setSignedApk(incrementalApk); |
| |
| if (!ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) { |
| incrementalActionsBuilder.setNativeLibs(nativeLibs); |
| } |
| |
| incrementalActionsBuilder.registerActions(ruleContext, androidSemantics); |
| |
| Artifact argsArtifact = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.MOBILE_INSTALL_ARGS); |
| ruleContext.registerAction( |
| new WriteAdbArgsAction(ruleContext.getActionOwner(), argsArtifact)); |
| |
| createInstallAction(ruleContext, false, fullDeployMarker, argsArtifact, |
| incrementalDexManifest, incrementalResourceApk.getArtifact(), incrementalApk, nativeLibs, |
| stubData); |
| |
| createInstallAction(ruleContext, true, incrementalDeployMarker, |
| argsArtifact, |
| incrementalDexManifest, |
| incrementalResourceApk.getArtifact(), |
| incrementalApk, |
| nativeLibs, |
| stubData); |
| |
| Artifact incrementalDeployInfo = ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.DEPLOY_INFO_INCREMENTAL); |
| |
| AndroidDeployInfoAction.createDeployInfoAction(ruleContext, |
| incrementalDeployInfo, |
| resourceApk.getManifest(), |
| additionalMergedManifests, |
| ImmutableList.<Artifact>of(), |
| dataDeps); |
| |
| NestedSet<Artifact> fullInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() |
| .add(fullDeployMarker) |
| .add(incrementalDeployInfo) |
| .build(); |
| |
| NestedSet<Artifact> incrementalInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() |
| .add(incrementalDeployMarker) |
| .add(incrementalDeployInfo) |
| .build(); |
| |
| NestedSetBuilder<Artifact> splitApkSetBuilder = NestedSetBuilder.compileOrder(); |
| |
| // Put the Android resource APK first so that this split gets installed first. |
| // |
| // This avoids some logcat spam during installation, because otherwise the Android package |
| // manager would complain about references to missing resources in the manifest during the |
| // installation of each split (said references would eventually get installed, but it cannot |
| // know that in advance) |
| Artifact resourceSplitApk = getDxArtifact(ruleContext, "android_resources.apk"); |
| createApkActionsBuilder(signingMethod) |
| .setResourceApk(splitResourceApk.getArtifact()) |
| .setApkName("split Android resource apk") |
| .setSignedApk(resourceSplitApk) |
| .registerActions(ruleContext, androidSemantics); |
| splitApkSetBuilder.add(resourceSplitApk); |
| |
| for (int i = 0; i < dexingOutput.shardDexZips.size(); i++) { |
| String splitName = "dex" + (i + 1); |
| Artifact splitApkResources = createSplitApkResources( |
| ruleContext, applicationManifest, splitName, true); |
| Artifact splitApk = getDxArtifact(ruleContext, splitName + ".apk"); |
| createApkActionsBuilder(signingMethod) |
| .setClassesDex(dexingOutput.shardDexZips.get(i)) |
| .setResourceApk(splitApkResources) |
| .setApkName("split dex apk " + (i + 1)) |
| .setSignedApk(splitApk) |
| .registerActions(ruleContext, androidSemantics); |
| splitApkSetBuilder.add(splitApk); |
| } |
| |
| Artifact nativeSplitApkResources = createSplitApkResources( |
| ruleContext, applicationManifest, "native", false); |
| Artifact nativeSplitApk = getDxArtifact(ruleContext, "native.apk"); |
| createApkActionsBuilder(signingMethod) |
| .setResourceApk(nativeSplitApkResources) |
| .setNativeLibs(nativeLibs) |
| .setApkName("split native apk") |
| .setSignedApk(nativeSplitApk) |
| .registerActions(ruleContext, androidSemantics); |
| splitApkSetBuilder.add(nativeSplitApk); |
| |
| Artifact javaSplitApkResources = createSplitApkResources( |
| ruleContext, applicationManifest, "java_resources", false); |
| Artifact javaSplitApk = getDxArtifact(ruleContext, "java_resources.apk"); |
| createApkActionsBuilder(signingMethod) |
| .setResourceApk(javaSplitApkResources) |
| .setJavaResourceZip(dexingOutput.javaResourceJar) |
| .setApkName("split Java resource apk") |
| .setSignedApk(javaSplitApk) |
| .registerActions(ruleContext, androidSemantics); |
| splitApkSetBuilder.add(javaSplitApk); |
| |
| Artifact splitMainApkResources = getDxArtifact(ruleContext, "split_main.ap_"); |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setMnemonic("AndroidStripResources") |
| .setProgressMessage("Stripping resources from split main apk") |
| .setExecutable(ruleContext.getExecutablePrerequisite("$strip_resources", Mode.HOST)) |
| .addArgument("--input_resource_apk") |
| .addInputArgument(resourceApk.getArtifact()) |
| .addArgument("--output_resource_apk") |
| .addOutputArgument(splitMainApkResources) |
| .build(ruleContext)); |
| |
| NestedSet<Artifact> splitApks = splitApkSetBuilder.build(); |
| Artifact splitMainApk = getDxArtifact(ruleContext, "split_main.apk"); |
| Artifact splitStubDex = getStubDex(ruleContext, javaSemantics, true); |
| ruleContext.assertNoErrors(); |
| createApkActionsBuilder(signingMethod) |
| .setClassesDex(splitStubDex) |
| .setResourceApk(splitMainApkResources) |
| .setApkName("split main apk") |
| .setSignedApk(splitMainApk) |
| .registerActions(ruleContext, androidSemantics); |
| splitApkSetBuilder.add(splitMainApk); |
| NestedSet<Artifact> allSplitApks = splitApkSetBuilder.build(); |
| |
| createSplitInstallAction(ruleContext, splitDeployMarker, argsArtifact, splitMainApk, |
| splitApks, stubData); |
| |
| Artifact splitDeployInfo = ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.DEPLOY_INFO_SPLIT); |
| AndroidDeployInfoAction.createDeployInfoAction( |
| ruleContext, |
| splitDeployInfo, |
| resourceApk.getManifest(), |
| additionalMergedManifests, |
| ImmutableList.<Artifact>of(), |
| dataDeps); |
| |
| NestedSet<Artifact> splitInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() |
| .addTransitive(allSplitApks) |
| .add(splitDeployMarker) |
| .add(splitDeployInfo) |
| .build(); |
| |
| Artifact debugKeystore = androidSemantics.getApkDebugSigningKey(ruleContext); |
| Artifact apkManifest = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.APK_MANIFEST); |
| createApkManifestAction( |
| ruleContext, |
| apkManifest, |
| false, // text proto |
| androidCommon, |
| resourceClasses, |
| instantRunResourceApk, |
| nativeLibs, |
| debugKeystore); |
| |
| Artifact apkManifestText = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.APK_MANIFEST_TEXT); |
| createApkManifestAction( |
| ruleContext, |
| apkManifestText, |
| true, // text proto |
| androidCommon, |
| resourceClasses, |
| instantRunResourceApk, |
| nativeLibs, |
| debugKeystore); |
| |
| androidCommon.addTransitiveInfoProviders( |
| builder, androidSemantics, null /* aar */, resourceApk, zipAlignedApk, apksUnderTest); |
| androidSemantics.addTransitiveInfoProviders( |
| builder, ruleContext, javaCommon, androidCommon, jarToDex); |
| |
| if (proguardOutput.getMapping() != null) { |
| builder.add(ProguardMappingProvider.class, |
| ProguardMappingProvider.create( |
| proguardOutput.getMapping(), proguardOutput.getProtoMapping())); |
| } |
| |
| return builder |
| .setFilesToBuild(filesToBuild) |
| .add( |
| RunfilesProvider.class, |
| RunfilesProvider.simple( |
| new Runfiles.Builder( |
| ruleContext.getWorkspaceName(), |
| ruleContext.getConfiguration().legacyExternalRunfiles()) |
| .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES) |
| .addTransitiveArtifacts(filesToBuild) |
| .build())) |
| .add( |
| JavaSourceInfoProvider.class, |
| JavaSourceInfoProvider.fromJavaTargetAttributes(resourceClasses, javaSemantics)) |
| .add( |
| ApkProvider.class, |
| ApkProvider.create( |
| NestedSetBuilder.create(Order.STABLE_ORDER, zipAlignedApk), |
| coverageMetadata, |
| NestedSetBuilder.create(Order.STABLE_ORDER, applicationManifest.getManifest()))) |
| .add(AndroidPreDexJarProvider.class, AndroidPreDexJarProvider.create(jarToDex)) |
| .addOutputGroup("mobile_install_full" + INTERNAL_SUFFIX, fullInstallOutputGroup) |
| .addOutputGroup( |
| "mobile_install_incremental" + INTERNAL_SUFFIX, incrementalInstallOutputGroup) |
| .addOutputGroup("mobile_install_split" + INTERNAL_SUFFIX, splitInstallOutputGroup) |
| .addOutputGroup("apk_manifest", apkManifest) |
| .addOutputGroup("apk_manifest_text", apkManifestText) |
| .addOutputGroup("android_deploy_info", deployInfo); |
| } |
| |
| private static void createSplitInstallAction(RuleContext ruleContext, |
| Artifact marker, Artifact argsArtifact, Artifact splitMainApk, NestedSet<Artifact> splitApks, |
| Artifact stubDataFile) { |
| FilesToRunProvider adb = AndroidSdkProvider.fromRuleContext(ruleContext).getAdb(); |
| SpawnAction.Builder builder = new SpawnAction.Builder() |
| .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST)) |
| .addTool(adb) |
| .executeUnconditionally() |
| .setMnemonic("AndroidInstall") |
| .setProgressMessage("Installing " + ruleContext.getLabel() + " using split apks") |
| .setExecutionInfo(ImmutableMap.of("local", "")) |
| .addArgument("--output_marker") |
| .addOutputArgument(marker) |
| .addArgument("--stub_datafile") |
| .addInputArgument(stubDataFile) |
| .addArgument("--adb") |
| .addArgument(adb.getExecutable().getExecPathString()) |
| .addTool(adb) |
| .addArgument("--flagfile") |
| .addInputArgument(argsArtifact) |
| .addArgument("--split_main_apk") |
| .addInputArgument(splitMainApk); |
| |
| for (Artifact splitApk : splitApks) { |
| builder |
| .addArgument("--split_apk") |
| .addInputArgument(splitApk); |
| } |
| |
| ruleContext.registerAction(builder.build(ruleContext)); |
| } |
| |
| private static void createInstallAction(RuleContext ruleContext, |
| boolean incremental, Artifact marker, Artifact argsArtifact, |
| Artifact dexmanifest, Artifact resourceApk, Artifact apk, NativeLibs nativeLibs, |
| Artifact stubDataFile) { |
| FilesToRunProvider adb = AndroidSdkProvider.fromRuleContext(ruleContext).getAdb(); |
| SpawnAction.Builder builder = new SpawnAction.Builder() |
| .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST)) |
| // We cannot know if the user connected a new device, uninstalled the app from the device |
| // or did anything strange to it, so we always run this action. |
| .executeUnconditionally() |
| .setMnemonic("AndroidInstall") |
| .setProgressMessage( |
| "Installing " + ruleContext.getLabel() + (incremental ? " incrementally" : "")) |
| .setExecutionInfo(ImmutableMap.of("local", "")) |
| .addArgument("--output_marker") |
| .addOutputArgument(marker) |
| .addArgument("--dexmanifest") |
| .addInputArgument(dexmanifest) |
| .addArgument("--resource_apk") |
| .addInputArgument(resourceApk) |
| .addArgument("--stub_datafile") |
| .addInputArgument(stubDataFile) |
| .addArgument("--adb") |
| .addArgument(adb.getExecutable().getExecPathString()) |
| .addTool(adb) |
| .addArgument("--flagfile") |
| .addInputArgument(argsArtifact); |
| |
| if (!incremental) { |
| builder |
| .addArgument("--apk") |
| .addInputArgument(apk); |
| } |
| |
| if (ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) { |
| for (Map.Entry<String, Iterable<Artifact>> arch : nativeLibs.getMap().entrySet()) { |
| for (Artifact lib : arch.getValue()) { |
| builder |
| .addArgument("--native_lib") |
| .addArgument(arch.getKey() + ":" + lib.getExecPathString()) |
| .addInput(lib); |
| } |
| } |
| } |
| |
| ruleContext.registerAction(builder.build(ruleContext)); |
| } |
| |
| private static Artifact getStubDex( |
| RuleContext ruleContext, JavaSemantics javaSemantics, boolean split) |
| throws InterruptedException { |
| String attribute = |
| split ? "$incremental_split_stub_application" : "$incremental_stub_application"; |
| |
| TransitiveInfoCollection dep = ruleContext.getPrerequisite(attribute, Mode.TARGET); |
| if (dep == null) { |
| ruleContext.attributeError(attribute, "Stub application cannot be found"); |
| return null; |
| } |
| |
| JavaCompilationArgsProvider provider = dep.getProvider(JavaCompilationArgsProvider.class); |
| if (provider == null) { |
| ruleContext.attributeError(attribute, "'" + dep.getLabel() + "' should be a Java target"); |
| return null; |
| } |
| |
| JavaTargetAttributes attributes = new JavaTargetAttributes.Builder(javaSemantics) |
| .addRuntimeClassPathEntries(provider.getJavaCompilationArgs().getRuntimeJars()) |
| .build(); |
| |
| Artifact stubDeployJar = getDxArtifact(ruleContext, |
| split ? "split_stub_deploy.jar" : "stub_deploy.jar"); |
| new DeployArchiveBuilder(javaSemantics, ruleContext) |
| .setOutputJar(stubDeployJar) |
| .setAttributes(attributes) |
| .build(); |
| |
| Artifact stubDex = getDxArtifact(ruleContext, |
| split ? "split_stub_application.dex" : "stub_application.dex"); |
| AndroidCommon.createDexAction( |
| ruleContext, |
| stubDeployJar, |
| stubDex, |
| ImmutableList.<String>of(), |
| false, |
| null); |
| |
| return stubDex; |
| } |
| |
| private static void createApkManifestAction( |
| RuleContext ruleContext, |
| Artifact apkManfiest, |
| boolean textProto, |
| final AndroidCommon androidCommon, |
| JavaTargetAttributes resourceClasses, |
| ResourceApk resourceApk, |
| NativeLibs nativeLibs, |
| Artifact debugKeystore) { |
| |
| Iterable<Artifact> jars = IterablesChain.concat( |
| resourceClasses.getArchiveInputs(true), androidCommon.getRuntimeJars()); |
| |
| // The resources jars from android_library rules contain stub ids, so filter those out of the |
| // transitive jars. |
| Iterable<AndroidLibraryResourceClassJarProvider> libraryResourceJarProviders = |
| AndroidCommon.getTransitivePrerequisites( |
| ruleContext, Mode.TARGET, AndroidLibraryResourceClassJarProvider.class); |
| |
| NestedSetBuilder<Artifact> libraryResourceJarsBuilder = NestedSetBuilder.naiveLinkOrder(); |
| for (AndroidLibraryResourceClassJarProvider provider : libraryResourceJarProviders) { |
| libraryResourceJarsBuilder.addTransitive(provider.getResourceClassJars()); |
| } |
| NestedSet<Artifact> libraryResourceJars = libraryResourceJarsBuilder.build(); |
| |
| Iterable<Artifact> filteredJars = ImmutableList.copyOf( |
| filter(jars, not(in(libraryResourceJars.toSet())))); |
| |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| |
| ApkManifestAction manifestAction = new ApkManifestAction( |
| ruleContext.getActionOwner(), |
| apkManfiest, |
| textProto, |
| sdk, |
| filteredJars, |
| resourceApk, |
| nativeLibs, |
| debugKeystore); |
| |
| ruleContext.registerAction(manifestAction); |
| } |
| |
| /** Generates an uncompressed _deploy.jar of all the runtime jars. */ |
| public static Artifact createDeployJar( |
| RuleContext ruleContext, |
| JavaSemantics javaSemantics, |
| AndroidCommon common, |
| JavaTargetAttributes attributes, |
| Artifact deployJar) |
| throws InterruptedException { |
| new DeployArchiveBuilder(javaSemantics, ruleContext) |
| .setOutputJar(deployJar) |
| .setAttributes(attributes) |
| .addRuntimeJars(common.getRuntimeJars()) |
| .build(); |
| return deployJar; |
| } |
| |
| private static JavaOptimizationMode getJavaOptimizationMode(RuleContext ruleContext) { |
| return ruleContext.getConfiguration().getFragment(JavaConfiguration.class) |
| .getJavaOptimizationMode(); |
| } |
| |
| /** |
| * Applies the proguard specifications, and creates a ProguardedJar. Proguard's output artifacts |
| * are added to the given {@code filesBuilder}. |
| */ |
| private static ProguardOutput applyProguard( |
| RuleContext ruleContext, |
| AndroidCommon common, |
| JavaSemantics javaSemantics, |
| Artifact deployJarArtifact, |
| NestedSetBuilder<Artifact> filesBuilder, |
| ImmutableList<Artifact> proguardSpecs, |
| Artifact proguardMapping) throws InterruptedException { |
| Artifact proguardOutputJar = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR); |
| |
| // Proguard will be only used for binaries which specify a proguard_spec |
| if (proguardSpecs.isEmpty()) { |
| // Although normally the Proguard jar artifact is not needed for binaries which do not specify |
| // proguard_specs, targets which use a select to provide an empty list to proguard_specs will |
| // still have a Proguard jar implicit output, as it is impossible to tell what a select will |
| // produce at the time of implicit output determination. As a result, this artifact must |
| // always be created. |
| return createEmptyProguardAction(ruleContext, javaSemantics, proguardOutputJar, |
| deployJarArtifact); |
| } |
| |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| NestedSet<Artifact> libraryJars = NestedSetBuilder.<Artifact>naiveLinkOrder() |
| .add(sdk.getAndroidJar()) |
| .addTransitive(common.getTransitiveNeverLinkLibraries()) |
| .build(); |
| Artifact proguardSeeds = |
| ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_SEEDS); |
| Artifact proguardUsage = |
| ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_USAGE); |
| ProguardOutput result = ProguardHelper.createProguardAction( |
| ruleContext, |
| sdk.getProguard(), |
| deployJarArtifact, |
| proguardSpecs, |
| proguardSeeds, |
| proguardUsage, |
| proguardMapping, |
| libraryJars, |
| proguardOutputJar, |
| javaSemantics, |
| getProguardOptimizationPasses(ruleContext)); |
| // Since Proguard is being run, add its output artifacts to the given filesBuilder |
| result.addAllToSet(filesBuilder); |
| return result; |
| } |
| |
| @Nullable |
| private static Integer getProguardOptimizationPasses(RuleContext ruleContext) { |
| if (ruleContext.attributes().has("proguard_optimization_passes", Type.INTEGER)) { |
| return ruleContext.attributes().get("proguard_optimization_passes", Type.INTEGER); |
| } else { |
| return null; |
| } |
| } |
| |
| private static ProguardOutput createEmptyProguardAction(RuleContext ruleContext, |
| JavaSemantics semantics, Artifact proguardOutputJar, Artifact deployJarArtifact) |
| throws InterruptedException { |
| NestedSetBuilder<Artifact> failures = NestedSetBuilder.<Artifact>stableOrder(); |
| ProguardOutput outputs = |
| ProguardHelper.getProguardOutputs( |
| proguardOutputJar, |
| /* proguardSeeds */ (Artifact) null, |
| /* proguardUsage */ (Artifact) null, |
| ruleContext, |
| semantics); |
| outputs.addAllToSet(failures); |
| JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext); |
| ruleContext.registerAction( |
| new FailAction( |
| ruleContext.getActionOwner(), |
| failures.build(), |
| String.format("Can't run Proguard %s", |
| optMode == JavaOptimizationMode.LEGACY |
| ? "without proguard_specs" |
| : "in optimization mode " + optMode))); |
| return new ProguardOutput(deployJarArtifact, null, null, null, null, null); |
| } |
| |
| private static ResourceApk shrinkResources( |
| RuleContext ruleContext, |
| ResourceApk resourceApk, |
| ImmutableList<Artifact> proguardSpecs, |
| ProguardOutput proguardOutput) throws InterruptedException { |
| |
| if (ruleContext.getFragment(AndroidConfiguration.class).useAndroidResourceShrinking() |
| && LocalResourceContainer.definesAndroidResources(ruleContext.attributes()) |
| && !proguardSpecs.isEmpty()) { |
| |
| Artifact apk = new ResourceShrinkerActionBuilder(ruleContext) |
| .setResourceApkOut(ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK)) |
| .setShrunkResourcesOut(ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP)) |
| .setLogOut(ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)) |
| .withResourceFiles(ruleContext.getImplicitOutputArtifact( |
| AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) |
| .withShrunkJar(proguardOutput.getOutputJar()) |
| .withProguardMapping(proguardOutput.getMapping()) |
| .withPrimary(resourceApk.getPrimaryResource()) |
| .withDependencies(resourceApk.getResourceDependencies()) |
| .setConfigurationFilters( |
| ruleContext.getTokenizedStringListAttr("resource_configuration_filters")) |
| .setUncompressedExtensions( |
| ruleContext.getTokenizedStringListAttr("nocompress_extensions")) |
| .build(); |
| return new ResourceApk(apk, |
| resourceApk.getResourceJavaSrcJar(), |
| resourceApk.getResourceJavaClassJar(), |
| resourceApk.getResourceDependencies(), |
| resourceApk.getPrimaryResource(), |
| resourceApk.getManifest(), |
| resourceApk.getResourceProguardConfig(), |
| resourceApk.getMainDexProguardConfig(), |
| resourceApk.isLegacy()); |
| } |
| return resourceApk; |
| } |
| |
| @Immutable |
| private static final class DexingOutput { |
| private final Artifact classesDexZip; |
| private final Artifact javaResourceJar; |
| private final ImmutableList<Artifact> shardDexZips; |
| |
| private DexingOutput( |
| Artifact classesDexZip, Artifact javaResourceJar, Iterable<Artifact> shardDexZips) { |
| this.classesDexZip = classesDexZip; |
| this.javaResourceJar = javaResourceJar; |
| this.shardDexZips = ImmutableList.copyOf(shardDexZips); |
| } |
| } |
| |
| static boolean shouldDexWithJack(RuleContext ruleContext) { |
| return ruleContext |
| .getFragment(AndroidConfiguration.class) |
| .isJackUsedForDexing(); |
| } |
| |
| static DexingOutput dexWithJack( |
| RuleContext ruleContext, AndroidCommon androidCommon, ImmutableList<Artifact> proguardSpecs) { |
| Artifact classesDexZip = |
| androidCommon.compileDexWithJack( |
| getMultidexMode(ruleContext), |
| Optional.fromNullable( |
| ruleContext.getPrerequisiteArtifact("main_dex_list", Mode.TARGET)), |
| proguardSpecs); |
| return new DexingOutput(classesDexZip, null, ImmutableList.of(classesDexZip)); |
| } |
| |
| /** Creates one or more classes.dex files that correspond to {@code proguardedJar}. */ |
| private static DexingOutput dex( |
| RuleContext ruleContext, |
| AndroidSemantics androidSemantics, |
| Artifact binaryJar, |
| Artifact proguardedJar, |
| boolean isBinaryJarFiltered, |
| AndroidCommon common, |
| @Nullable Artifact mainDexProguardSpec, |
| JavaTargetAttributes attributes) |
| throws InterruptedException, RuleErrorException { |
| List<String> dexopts = ruleContext.getTokenizedStringListAttr("dexopts"); |
| MultidexMode multidexMode = getMultidexMode(ruleContext); |
| if (!supportsMultidexMode(ruleContext, multidexMode)) { |
| ruleContext.throwWithRuleError("Multidex mode \"" + multidexMode.getAttributeValue() |
| + "\" not supported by this version of the Android SDK"); |
| } |
| |
| int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER); |
| if (dexShards > 1) { |
| if (multidexMode == MultidexMode.OFF) { |
| ruleContext.throwWithRuleError(".dex sharding is only available in multidex mode"); |
| } |
| |
| if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) { |
| ruleContext.throwWithRuleError(".dex sharding is not available in manual multidex mode"); |
| } |
| } |
| |
| Artifact mainDexList = ruleContext.getPrerequisiteArtifact("main_dex_list", Mode.TARGET); |
| if ((mainDexList != null && multidexMode != MultidexMode.MANUAL_MAIN_DEX) |
| || (mainDexList == null && multidexMode == MultidexMode.MANUAL_MAIN_DEX)) { |
| ruleContext.throwWithRuleError( |
| "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified."); |
| } |
| |
| // Always OFF if finalJarIsDerived |
| ImmutableSet<AndroidBinaryType> incrementalDexing = |
| getEffectiveIncrementalDexing( |
| ruleContext, dexopts, !Objects.equals(binaryJar, proguardedJar)); |
| Artifact inclusionFilterJar = |
| isBinaryJarFiltered && Objects.equals(binaryJar, proguardedJar) ? binaryJar : null; |
| if (multidexMode == MultidexMode.OFF) { |
| // Single dex mode: generate classes.dex directly from the input jar. |
| if (incrementalDexing.contains(AndroidBinaryType.MONODEX)) { |
| Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip"); |
| Artifact jarToDex = getDxArtifact(ruleContext, "classes.jar"); |
| createShuffleJarAction(ruleContext, true, (Artifact) null, ImmutableList.of(jarToDex), |
| common, inclusionFilterJar, dexopts, attributes, (Artifact) null); |
| createDexMergerAction(ruleContext, "off", jarToDex, classesDex, (Artifact) null, dexopts); |
| return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); |
| } else { |
| // By *not* writing a zip we get dx to drop resources on the floor. |
| Artifact classesDex = getDxArtifact(ruleContext, "classes.dex"); |
| AndroidCommon.createDexAction( |
| ruleContext, proguardedJar, classesDex, dexopts, /* multidex */ false, (Artifact) null); |
| return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); |
| } |
| } else { |
| // Multidex mode: generate classes.dex.zip, where the zip contains [classes.dex, |
| // classes2.dex, ... classesN.dex]. |
| |
| if (multidexMode == MultidexMode.LEGACY) { |
| // For legacy multidex, we need to generate a list for the dexer's --main-dex-list flag. |
| mainDexList = createMainDexListAction( |
| ruleContext, androidSemantics, proguardedJar, mainDexProguardSpec); |
| } |
| |
| Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip"); |
| if (dexShards > 1) { |
| List<Artifact> shards = new ArrayList<>(dexShards); |
| for (int i = 1; i <= dexShards; i++) { |
| shards.add(getDxArtifact(ruleContext, "shard" + i + ".jar")); |
| } |
| |
| Artifact javaResourceJar = |
| createShuffleJarAction( |
| ruleContext, |
| incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED), |
| /*proguardedJar*/ !Objects.equals(binaryJar, proguardedJar) ? proguardedJar : null, |
| shards, |
| common, |
| inclusionFilterJar, |
| dexopts, |
| attributes, |
| mainDexList); |
| |
| List<Artifact> shardDexes = new ArrayList<>(dexShards); |
| for (int i = 1; i <= dexShards; i++) { |
| Artifact shard = shards.get(i - 1); |
| Artifact shardDex = getDxArtifact(ruleContext, "shard" + i + ".dex.zip"); |
| shardDexes.add(shardDex); |
| if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED)) { |
| // If there's a main dex list then the first shard contains exactly those files. |
| // To work with devices that lack native multi-dex support we need to make sure that |
| // the main dex list becomes one dex file if at all possible. |
| // Note shard here (mostly) contains of .class.dex files from shuffled dex archives, |
| // instead of being a conventional Jar file with .class files. |
| String multidexStrategy = mainDexList != null && i == 1 ? "minimal" : "best_effort"; |
| createDexMergerAction(ruleContext, multidexStrategy, shard, shardDex, (Artifact) null, |
| dexopts); |
| } else { |
| AndroidCommon.createDexAction( |
| ruleContext, shard, shardDex, dexopts, /* multidex */ true, (Artifact) null); |
| } |
| } |
| |
| CommandLine mergeCommandLine = CustomCommandLine.builder() |
| .addBeforeEachExecPath("--input_zip", shardDexes) |
| .addExecPath("--output_zip", classesDex) |
| .build(); |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setMnemonic("MergeDexZips") |
| .setProgressMessage("Merging dex shards for " + ruleContext.getLabel()) |
| .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips", Mode.HOST)) |
| .addInputs(shardDexes) |
| .addOutput(classesDex) |
| .setCommandLine(mergeCommandLine) |
| .build(ruleContext)); |
| if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED)) { |
| // Using the deploy jar for java resources gives better "bazel mobile-install" performance |
| // with incremental dexing b/c bazel can create the "incremental" and "split resource" |
| // APKs earlier (b/c these APKs don't depend on code being dexed here). This is also done |
| // for other multidex modes. |
| javaResourceJar = binaryJar; |
| } |
| return new DexingOutput(classesDex, javaResourceJar, shardDexes); |
| } else { |
| if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_UNSHARDED)) { |
| Artifact jarToDex = AndroidBinary.getDxArtifact(ruleContext, "classes.jar"); |
| createShuffleJarAction(ruleContext, true, (Artifact) null, ImmutableList.of(jarToDex), |
| common, inclusionFilterJar, dexopts, attributes, (Artifact) null); |
| createDexMergerAction(ruleContext, "minimal", jarToDex, classesDex, mainDexList, dexopts); |
| } else { |
| // Because the dexer also places resources into this zip, we also need to create a cleanup |
| // action that removes all non-.dex files before staging for apk building. |
| // Create an artifact for the intermediate zip output that includes non-.dex files. |
| Artifact classesDexIntermediate = AndroidBinary.getDxArtifact( |
| ruleContext, "intermediate_classes.dex.zip"); |
| // Have the dexer generate the intermediate file and the "cleaner" action consume this to |
| // generate the final archive with only .dex files. |
| AndroidCommon.createDexAction(ruleContext, proguardedJar, |
| classesDexIntermediate, dexopts, /* multidex */ true, mainDexList); |
| createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex); |
| } |
| return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); |
| } |
| } |
| } |
| |
| private static ImmutableSet<AndroidBinaryType> getEffectiveIncrementalDexing( |
| RuleContext ruleContext, List<String> dexopts, boolean isBinaryProguarded) { |
| TriState override = ruleContext.attributes().get("incremental_dexing", BuildType.TRISTATE); |
| // Ignore --incremental_dexing_binary_types if the incremental_dexing attribute is set, but |
| // raise an error if proguard is enabled (b/c incompatible with incremental dexing ATM). |
| if (isBinaryProguarded && override == TriState.YES) { |
| ruleContext.attributeError("incremental_dexing", |
| "target cannot be incrementally dexed because it uses Proguard"); |
| return ImmutableSet.of(); |
| } |
| if (isBinaryProguarded || override == TriState.NO) { |
| return ImmutableSet.of(); |
| } |
| ImmutableSet<AndroidBinaryType> result = |
| override == TriState.YES |
| ? ImmutableSet.copyOf(AndroidBinaryType.values()) |
| : AndroidCommon.getAndroidConfig(ruleContext).getIncrementalDexingBinaries(); |
| if (!result.isEmpty()) { |
| Iterable<String> blacklistedDexopts = |
| Iterables.filter( |
| dexopts, |
| new FlagMatcher(AndroidCommon |
| .getAndroidConfig(ruleContext) |
| .getTargetDexoptsThatPreventIncrementalDexing())); |
| if (!Iterables.isEmpty(blacklistedDexopts)) { |
| // target's dexopts include flags blacklisted with --non_incremental_per_target_dexopts. If |
| // incremental_dexing attribute is explicitly set for this target then we'll warn and |
| // incrementally dex anyway. Otherwise, just don't incrementally dex. |
| if (override == TriState.YES) { |
| Iterable<String> ignored = |
| Iterables.filter( |
| blacklistedDexopts, |
| Predicates.not( |
| Predicates.in( |
| AndroidCommon.getAndroidConfig(ruleContext) |
| .getDexoptsSupportedInIncrementalDexing()))); |
| ruleContext.attributeWarning("incremental_dexing", |
| String.format("Using incremental dexing even though dexopts %s indicate this target " |
| + "may be unsuitable for incremental dexing for the moment.%s", |
| blacklistedDexopts, |
| Iterables.isEmpty(ignored) ? "" : " These will be ignored: " + ignored)); |
| } else { |
| result = ImmutableSet.of(); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static void createDexMergerAction( |
| RuleContext ruleContext, |
| String multidexStrategy, |
| Artifact inputJar, |
| Artifact classesDex, |
| @Nullable Artifact mainDexList, |
| Collection<String> dexopts) { |
| SpawnAction.Builder dexmerger = new SpawnAction.Builder() |
| .setExecutable(ruleContext.getExecutablePrerequisite("$dexmerger", Mode.HOST)) |
| .addArgument("--input") |
| .addInputArgument(inputJar) |
| .addArgument("--output") |
| .addOutputArgument(classesDex) |
| .addArguments(DexArchiveAspect.incrementalDexopts(ruleContext, dexopts)) |
| .addArgument("--multidex=" + multidexStrategy) |
| .setMnemonic("DexMerger") |
| .setProgressMessage("Assembling dex files into " + classesDex.prettyPrint()); |
| if (mainDexList != null) { |
| dexmerger.addArgument("--main-dex-list").addInputArgument(mainDexList); |
| if (dexopts.contains("--minimal-main-dex")) { |
| dexmerger.addArgument("--minimal-main-dex"); |
| } |
| } |
| ruleContext.registerAction(dexmerger.build(ruleContext)); |
| } |
| |
| /** |
| * Returns a {@link DexArchiveProvider} of all transitively generated dex archives as well as dex |
| * archives for the Jars produced by the binary target itself. |
| */ |
| private static Function<Artifact, Artifact> collectDexArchives( |
| RuleContext ruleContext, |
| AndroidCommon common, |
| List<String> dexopts, |
| JavaTargetAttributes attributes) { |
| DexArchiveProvider.Builder result = new DexArchiveProvider.Builder() |
| // Use providers from all attributes that declare DexArchiveAspect |
| .addTransitiveProviders( |
| ruleContext.getPrerequisites("deps", Mode.TARGET, DexArchiveProvider.class)); |
| ImmutableSet<String> incrementalDexopts = |
| DexArchiveAspect.incrementalDexopts(ruleContext, dexopts); |
| for (Artifact jar : common.getJarsProducedForRuntime()) { |
| // Create dex archives next to all Jars produced by AndroidCommon for this rule. We need to |
| // do this (instead of placing dex archives into the _dx subdirectory like DexArchiveAspect) |
| // because for "legacy" ResourceApks, AndroidCommon produces Jars per resource dependency that |
| // can theoretically have duplicate basenames, so they go into special directories, and we |
| // piggyback on that naming scheme here by placing dex archives into the same directories. |
| PathFragment jarPath = jar.getRootRelativePath(); |
| Artifact jarToDex = jar; |
| if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) { |
| // desugar jars first if desired... |
| jarToDex = |
| DexArchiveAspect.desugar( |
| ruleContext, |
| jar, |
| attributes.getBootClassPath(), |
| attributes.getCompileTimeClassPath(), |
| ruleContext.getDerivedArtifact( |
| jarPath.replaceName(jarPath.getBaseName() + "_desugared.jar"), jar.getRoot())); |
| } |
| Artifact dexArchive = |
| DexArchiveAspect.createDexArchiveAction( |
| ruleContext, |
| jarToDex, |
| incrementalDexopts, |
| ruleContext.getDerivedArtifact( |
| jarPath.replaceName(jarPath.getBaseName() + ".dex.zip"), jar.getRoot())); |
| result.addDexArchive(incrementalDexopts, dexArchive, jar); |
| } |
| return result.build().archivesForDexopts(incrementalDexopts); |
| } |
| |
| private static Artifact createShuffleJarAction( |
| RuleContext ruleContext, |
| boolean useDexArchives, |
| @Nullable Artifact proguardedJar, |
| List<Artifact> shards, |
| AndroidCommon common, |
| @Nullable Artifact inclusionFilterJar, |
| List<String> dexopts, |
| JavaTargetAttributes attributes, |
| @Nullable Artifact mainDexList) |
| throws InterruptedException { |
| checkArgument(mainDexList == null || shards.size() > 1); |
| checkArgument(proguardedJar == null || inclusionFilterJar == null); |
| Artifact javaResourceJar = |
| ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.JAVA_RESOURCES_JAR); |
| |
| SpawnAction.Builder shardAction = new SpawnAction.Builder() |
| .setMnemonic("ShardClassesToDex") |
| .setProgressMessage("Sharding classes for dexing for " + ruleContext.getLabel()) |
| .setExecutable(ruleContext.getExecutablePrerequisite("$shuffle_jars", Mode.HOST)) |
| .addOutputs(shards) |
| .addOutput(javaResourceJar); |
| |
| CustomCommandLine.Builder shardCommandLine = CustomCommandLine.builder() |
| .addBeforeEachExecPath("--output_jar", shards) |
| .addExecPath("--output_resources", javaResourceJar); |
| |
| if (mainDexList != null) { |
| shardCommandLine.addExecPath("--main_dex_filter", mainDexList); |
| shardAction.addInput(mainDexList); |
| } |
| |
| // If we need to run Proguard, all the class files will be in the Proguarded jar and the |
| // deploy jar will already have been built (since it's the input of Proguard) and it will |
| // contain all the Java resources. Otherwise, we don't want to have deploy jar creation on |
| // the critical path, so we put all the jar files that constitute it on the inputs of the |
| // jar shuffler. |
| if (proguardedJar != null) { |
| // When proguard is used we can't use dex archives, so just shuffle the proguarded jar |
| checkArgument(!useDexArchives, "Dex archives are incompatible with Proguard"); |
| shardCommandLine.addExecPath("--input_jar", proguardedJar); |
| shardAction.addInput(proguardedJar); |
| } else { |
| Iterable<Artifact> classpath = |
| Iterables.concat(common.getRuntimeJars(), attributes.getRuntimeClassPathForArchive()); |
| // Check whether we can use dex archives. Besides the --incremental_dexing flag, also |
| // make sure the "dexopts" attribute on this target doesn't mention any problematic flags. |
| if (useDexArchives) { |
| // Use dex archives instead of their corresponding Jars wherever we can. At this point |
| // there should be very few or no Jar files that still end up in shards. The dexing |
| // step below will have to deal with those in addition to merging .dex files together. |
| classpath = Iterables |
| .transform(classpath, collectDexArchives(ruleContext, common, dexopts, attributes)); |
| shardCommandLine.add("--split_dexed_classes"); |
| } |
| shardCommandLine.addBeforeEachExecPath("--input_jar", classpath); |
| shardAction.addInputs(classpath); |
| |
| if (inclusionFilterJar != null) { |
| shardCommandLine.addExecPath("--inclusion_filter_jar", inclusionFilterJar); |
| shardAction.addInput(inclusionFilterJar); |
| } |
| } |
| |
| shardAction.setCommandLine(shardCommandLine.build()); |
| ruleContext.registerAction(shardAction.build(ruleContext)); |
| return javaResourceJar; |
| } |
| |
| /** |
| * Creates an action that copies a .zip file to a specified path, filtering all non-.dex files |
| * out of the output. |
| */ |
| static void createCleanDexZipAction(RuleContext ruleContext, Artifact inputZip, |
| Artifact outputZip) { |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setExecutable(ruleContext.getExecutablePrerequisite("$zip", Mode.HOST)) |
| .addInput(inputZip) |
| .addOutput(outputZip) |
| .addArgument(inputZip.getExecPathString()) |
| .addArgument("--out") |
| .addArgument(outputZip.getExecPathString()) |
| .addArgument("--copy") |
| .addArgument("classes*.dex") |
| .setProgressMessage("Trimming " + inputZip.getExecPath().getBaseName()) |
| .setMnemonic("TrimDexZip") |
| .build(ruleContext)); |
| } |
| |
| /** |
| * Creates an action that generates a list of classes to be passed to the dexer's |
| * --main-dex-list flag (which specifies the classes that need to be directly in classes.dex). |
| * Returns the file containing the list. |
| */ |
| static Artifact createMainDexListAction( |
| RuleContext ruleContext, |
| AndroidSemantics androidSemantics, |
| Artifact jar, |
| @Nullable Artifact mainDexProguardSpec) |
| throws InterruptedException { |
| // Process the input jar through Proguard into an intermediate, streamlined jar. |
| Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar"); |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| SpawnAction.Builder streamlinedBuilder = new SpawnAction.Builder() |
| .addOutput(strippedJar) |
| .setExecutable(sdk.getProguard()) |
| .setProgressMessage("Generating streamlined input jar for main dex classes list") |
| .setMnemonic("MainDexClassesIntermediate") |
| .addArgument("-forceprocessing") |
| .addArgument("-injars") |
| .addInputArgument(jar) |
| .addArgument("-libraryjars") |
| .addInputArgument(sdk.getShrinkedAndroidJar()) |
| .addArgument("-outjars") |
| .addArgument(strippedJar.getExecPathString()) |
| .addArgument("-dontwarn") |
| .addArgument("-dontnote") |
| .addArgument("-dontoptimize") |
| .addArgument("-dontobfuscate") |
| .addArgument("-dontpreverify"); |
| |
| List<Artifact> specs = new ArrayList<>(); |
| specs.addAll( |
| ruleContext.getPrerequisiteArtifacts("main_dex_proguard_specs", Mode.TARGET).list()); |
| if (specs.isEmpty()) { |
| specs.add(sdk.getMainDexClasses()); |
| } |
| if (mainDexProguardSpec != null) { |
| specs.add(mainDexProguardSpec); |
| } |
| |
| for (Artifact spec : specs) { |
| streamlinedBuilder.addArgument("-include"); |
| streamlinedBuilder.addInputArgument(spec); |
| } |
| |
| androidSemantics.addMainDexListActionArguments(ruleContext, streamlinedBuilder); |
| |
| ruleContext.registerAction(streamlinedBuilder.build(ruleContext)); |
| |
| // Create the main dex classes list. |
| Artifact mainDexList = AndroidBinary.getDxArtifact(ruleContext, "main_dex_list.txt"); |
| Builder builder = new Builder() |
| .setMnemonic("MainDexClasses") |
| .setProgressMessage("Generating main dex classes list"); |
| |
| ruleContext.registerAction(builder |
| .setExecutable(sdk.getMainDexListCreator()) |
| .addOutputArgument(mainDexList) |
| .addInputArgument(strippedJar) |
| .addInputArgument(jar) |
| .addArguments(ruleContext.getTokenizedStringListAttr("main_dex_list_opts")) |
| .build(ruleContext)); |
| return mainDexList; |
| } |
| |
| private static Artifact createSplitApkResources(RuleContext ruleContext, |
| ApplicationManifest mainManifest, String splitName, boolean hasCode) { |
| Artifact splitManifest = mainManifest.createSplitManifest(ruleContext, splitName, hasCode) |
| .getManifest(); |
| Artifact splitResources = getDxArtifact(ruleContext, "split_" + splitName + ".ap_"); |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setExecutable(sdk.getAapt()) |
| .setMnemonic("AndroidAapt") |
| .setProgressMessage("Generating resource apk for split " + splitName) |
| .addArgument("package") |
| .addArgument("-F") |
| .addOutputArgument(splitResources) |
| .addArgument("-M") |
| .addInputArgument(splitManifest) |
| .addArgument("-I") |
| .addInputArgument(sdk.getAndroidJar()) |
| .build(ruleContext)); |
| |
| return splitResources; |
| } |
| |
| @Nullable |
| private static Artifact createMainDexProguardSpec(RuleContext ruleContext) { |
| return AndroidSdkProvider.fromRuleContext(ruleContext).getAaptSupportsMainDexGeneration() |
| ? ProguardHelper.getProguardConfigArtifact(ruleContext, "main_dex") |
| : null; |
| } |
| |
| private static ApkActionsBuilder createApkActionsBuilder(ApkSigningMethod signingMethod) { |
| if (signingMethod.signLegacy()) { |
| return new LegacySignerApkActionsBuilder(); |
| } else { |
| return new SignerToolApkActionsBuilder(signingMethod); |
| } |
| } |
| |
| /** |
| * Tests if the resources need to be regenerated. |
| * |
| * <p>The resources should be regenerated (using aapt) if any of the following are true: |
| * <ul> |
| * <li>There is more than one resource container |
| * <li>There are densities to filter by. |
| * <li>There are resource configuration filters. |
| * <li>There are extensions that should be compressed. |
| * </ul> |
| */ |
| public static boolean shouldRegenerate(RuleContext ruleContext, |
| ResourceDependencies resourceDeps) { |
| return Iterables.size(resourceDeps.getResources()) > 1 |
| || ruleContext.attributes().isAttributeValueExplicitlySpecified("densities") |
| || ruleContext.attributes().isAttributeValueExplicitlySpecified( |
| "resource_configuration_filters") |
| || ruleContext.attributes().isAttributeValueExplicitlySpecified("nocompress_extensions"); |
| } |
| |
| /** |
| * Returns the multidex mode to apply to this target. |
| */ |
| public static MultidexMode getMultidexMode(RuleContext ruleContext) { |
| if (ruleContext.getRule().isAttrDefined("multidex", Type.STRING)) { |
| return Preconditions.checkNotNull( |
| MultidexMode.fromValue(ruleContext.attributes().get("multidex", Type.STRING))); |
| } else { |
| return MultidexMode.OFF; |
| } |
| } |
| |
| /** |
| * List of Android SDKs that contain runtimes that do not support the native multidexing |
| * introduced in Android L. If someone tries to build an android_binary that has multidex=native |
| * set with an old SDK, we will exit with an error to alert the developer that his application |
| * might not run on devices that the used SDK still supports. |
| */ |
| private static final Set<String> RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING = ImmutableSet.of( |
| "/android_sdk_linux/platforms/android_10/", "/android_sdk_linux/platforms/android_13/", |
| "/android_sdk_linux/platforms/android_15/", "/android_sdk_linux/platforms/android_16/", |
| "/android_sdk_linux/platforms/android_17/", "/android_sdk_linux/platforms/android_18/", |
| "/android_sdk_linux/platforms/android_19/", "/android_sdk_linux/platforms/android_20/"); |
| |
| /** |
| * Returns true if the runtime contained in the Android SDK used to build this rule supports the |
| * given version of multidex mode specified, false otherwise. |
| */ |
| public static boolean supportsMultidexMode(RuleContext ruleContext, MultidexMode mode) { |
| if (mode == MultidexMode.NATIVE) { |
| // Native mode is not supported by Android devices running Android before v21. |
| String runtime = |
| AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar().getExecPathString(); |
| for (String blacklistedRuntime : RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING) { |
| if (runtime.contains(blacklistedRuntime)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns an intermediate artifact used to support dex generation. |
| */ |
| public static Artifact getDxArtifact(RuleContext ruleContext, String baseName) { |
| return ruleContext.getUniqueDirectoryArtifact("_dx", baseName, |
| ruleContext.getBinOrGenfilesDirectory()); |
| } |
| |
| private static class FlagMatcher implements Predicate<String> { |
| private final ImmutableList<String> matching; |
| |
| FlagMatcher(ImmutableList<String> matching) { |
| this.matching = matching; |
| } |
| |
| @Override |
| public boolean apply(String input) { |
| for (String match : matching) { |
| if (input.contains(match)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| } |