com.google.devtools.build.lib.rules.android.AndroidBinary.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.rules.android.AndroidBinary.java

Source

// 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.Functions;
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.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.JavaHelper;
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.JavaToolchainProvider;
import com.google.devtools.build.lib.rules.java.Jvm;
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();
        JavaCommon javaCommon = AndroidCommon.createJavaCommonWithAndroidDataBinding(ruleContext, javaSemantics,
                false);
        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");
        }

        if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map")
                && !ruleContext.attributes().get("rewrite_dexes_with_rex", Type.BOOLEAN)) {
            ruleContext.throwWithAttributeError("rex_package_map",
                    "'rex_package_map' can only be used when 'rewrite_dexes_with_rex' is also set");
        }

        if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map")
                && ruleContext.attributes().get(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST).isEmpty()) {
            ruleContext.throwWithAttributeError("rex_package_map",
                    "'rex_package_map' 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),
                    DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null);
            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 */
                            null /* dataBindingInfoZip */);
            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 */, null /* dataBindingInfoZip */);
            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 */, null /* dataBindingInfoZip */);
            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();
        }

        boolean shrinkResources = shouldShrinkResources(ruleContext);

        JavaTargetAttributes resourceClasses = androidCommon.init(javaSemantics, androidSemantics, resourceApk,
                ruleContext.getConfiguration().isCodeCoverageEnabled(), true /* collectJavaCompilationArgs */,
                true /* isBinary */);
        ruleContext.assertNoErrors();

        Function<Artifact, Artifact> derivedJarFunction = collectDesugaredJars(ruleContext, androidCommon,
                androidSemantics, resourceClasses);
        Artifact deployJar = createDeployJar(ruleContext, javaSemantics, androidCommon, resourceClasses,
                derivedJarFunction);

        Artifact proguardMapping = ruleContext.getPrerequisiteArtifact("proguard_apply_mapping", Mode.TARGET);

        return createAndroidBinary(ruleContext, filesBuilder, deployJar, derivedJarFunction,
                /* isBinaryJarFiltered */ false, javaCommon, androidCommon, javaSemantics, androidSemantics,
                nativeLibs, applicationManifest, resourceApk, incrementalResourceApk, instantRunResourceApk,
                splitResourceApk, shrinkResources, resourceClasses, ImmutableList.<Artifact>of(),
                ImmutableList.<Artifact>of(), proguardMapping);
    }

    public static RuleConfiguredTargetBuilder createAndroidBinary(RuleContext ruleContext,
            NestedSetBuilder<Artifact> filesBuilder, Artifact binaryJar,
            Function<Artifact, Artifact> derivedJarFunction, 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, derivedJarFunction);

        NestedSet<Artifact> nativeLibsZips = AndroidCommon.collectTransitiveNativeLibsZips(ruleContext).build();

        Artifact finalDexes;
        Artifact finalProguardMap;

        if (ruleContext.getFragment(AndroidConfiguration.class).useRexToCompressDexFiles()
                || (ruleContext.attributes().get("rewrite_dexes_with_rex", Type.BOOLEAN))) {
            finalDexes = getDxArtifact(ruleContext, "rexed_dexes.zip");
            Builder rexActionBuilder = new SpawnAction.Builder();
            rexActionBuilder.setExecutable(ruleContext.getExecutablePrerequisite("$rex_wrapper", Mode.HOST))
                    .setMnemonic("Rex").setProgressMessage("Rexing dex files").addArgument("--dex_input")
                    .addInputArgument(dexingOutput.classesDexZip).addArgument("--dex_output")
                    .addOutputArgument(finalDexes);
            if (proguardOutput.getMapping() != null) {
                finalProguardMap = getDxArtifact(ruleContext, "rexed_proguard.map");
                Artifact finalRexPackageMap = getDxArtifact(ruleContext, "rex_output_package.map");
                rexActionBuilder.addArgument("--proguard_input_map").addInputArgument(proguardOutput.getMapping())
                        .addArgument("--proguard_output_map").addOutputArgument(finalProguardMap)
                        .addArgument("--rex_output_package_map").addOutputArgument(finalRexPackageMap);
                if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map")) {
                    Artifact rexPackageMap = ruleContext.getPrerequisiteArtifact("rex_package_map", Mode.TARGET);
                    rexActionBuilder.addArgument("--rex_input_package_map").addInputArgument(rexPackageMap);
                }
            } else {
                finalProguardMap = proguardOutput.getMapping();
            }
            // the Rex flag --keep-main-dex is used to support builds with API level below 21 that do not
            // support native multi-dex. The blaze flag main_dex_list is set in this case so it is used to
            // determine whether or not to pass the flag to Rex
            if (ruleContext.attributes().isAttributeValueExplicitlySpecified("main_dex_list")) {
                rexActionBuilder.addArgument("--keep-main-dex");
            }
            ruleContext.registerAction(rexActionBuilder.build(ruleContext));
        } else {
            finalDexes = dexingOutput.classesDexZip;
            finalProguardMap = proguardOutput.getMapping();
        }

        ApkSigningMethod signingMethod = ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod();
        Artifact unsignedApk = ruleContext
                .getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
        Artifact zipAlignedApk = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK);

        ApkActionsBuilder.create("apk", signingMethod).setClassesDex(finalDexes)
                .setResourceApk(resourceApk.getArtifact()).setJavaResourceZip(dexingOutput.javaResourceJar)
                .setNativeLibsZips(nativeLibsZips).setNativeLibs(nativeLibs).setUnsignedApk(unsignedApk)
                .setSignedApk(zipAlignedApk).setZipalignApk(true).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.attributes().has("data", BuildType.LABEL_LIST)
                && 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 = ApkActionsBuilder.create("incremental apk", signingMethod)
                .setClassesDex(stubDex).setResourceApk(incrementalResourceApk.getArtifact())
                .setJavaResourceZip(dexingOutput.javaResourceJar).setNativeLibsZips(nativeLibsZips)
                .setJavaResourceFile(stubData).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");
        ApkActionsBuilder.create("split Android resource apk", signingMethod)
                .setResourceApk(splitResourceApk.getArtifact()).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");
            ApkActionsBuilder.create("split dex apk " + (i + 1), signingMethod)
                    .setClassesDex(dexingOutput.shardDexZips.get(i)).setResourceApk(splitApkResources)
                    .setSignedApk(splitApk).registerActions(ruleContext, androidSemantics);
            splitApkSetBuilder.add(splitApk);
        }

        Artifact nativeSplitApkResources = createSplitApkResources(ruleContext, applicationManifest, "native",
                false);
        Artifact nativeSplitApk = getDxArtifact(ruleContext, "native.apk");
        ApkActionsBuilder.create("split native apk", signingMethod).setResourceApk(nativeSplitApkResources)
                .setNativeLibs(nativeLibs).setSignedApk(nativeSplitApk)
                .registerActions(ruleContext, androidSemantics);
        splitApkSetBuilder.add(nativeSplitApk);

        Artifact javaSplitApkResources = createSplitApkResources(ruleContext, applicationManifest, "java_resources",
                false);
        Artifact javaSplitApk = getDxArtifact(ruleContext, "java_resources.apk");
        ApkActionsBuilder.create("split Java resource apk", signingMethod).setResourceApk(javaSplitApkResources)
                .setJavaResourceZip(dexingOutput.javaResourceJar).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();
        ApkActionsBuilder.create("split main apk", signingMethod).setClassesDex(splitStubDex)
                .setResourceApk(splitMainApkResources).setNativeLibsZips(nativeLibsZips).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(finalProguardMap));
        }

        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();

        Function<Artifact, Artifact> desugaredJars = Functions.identity();
        if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) {
            desugaredJars = collectDesugaredJarsFromAttributes(ruleContext, ImmutableList.of(attribute)).build()
                    .collapseToFunction();
        }
        Artifact stubDeployJar = getDxArtifact(ruleContext, split ? "split_stub_deploy.jar" : "stub_deploy.jar");
        new DeployArchiveBuilder(javaSemantics, ruleContext).setOutputJar(stubDeployJar).setAttributes(attributes)
                .setDerivedJarFunction(desugaredJars).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) {
        // TODO(bazel-team): Sufficient to use resourceClasses.getRuntimeClasspathForArchive?
        // Deleting getArchiveInputs could simplify the implementation of DeployArchiveBuidler.build()
        Iterable<Artifact> jars = IterablesChain.concat(DeployArchiveBuilder.getArchiveInputs(resourceClasses),
                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, Function<Artifact, Artifact> derivedJarFunction)
            throws InterruptedException {
        Artifact deployJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR);
        new DeployArchiveBuilder(javaSemantics, ruleContext).setOutputJar(deployJar).setAttributes(attributes)
                .addRuntimeJars(common.getRuntimeJars()).setDerivedJarFunction(derivedJarFunction).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, null);
    }

    /** Returns {@code true} if resource shrinking should be performed. */
    private static boolean shouldShrinkResources(RuleContext ruleContext) {
        TriState state = ruleContext.attributes().get("shrink_resources", BuildType.TRISTATE);
        if (state == TriState.AUTO) {
            boolean globalShrinkResources = ruleContext.getFragment(AndroidConfiguration.class)
                    .useAndroidResourceShrinking();
            state = (globalShrinkResources) ? TriState.YES : TriState.NO;
        }

        return (state == TriState.YES);
    }

    private static ResourceApk shrinkResources(RuleContext ruleContext, ResourceApk resourceApk,
            ImmutableList<Artifact> proguardSpecs, ProguardOutput proguardOutput) throws InterruptedException {

        if (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,
            Function<Artifact, Artifact> derivedJarFunction) 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, androidSemantics, attributes, derivedJarFunction,
                        (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, androidSemantics, attributes, derivedJarFunction,
                        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, androidSemantics, attributes, derivedJarFunction,
                            (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.
     */
    public static Function<Artifact, Artifact> collectDesugaredJars(RuleContext ruleContext, AndroidCommon common,
            AndroidSemantics semantics, JavaTargetAttributes attributes) {
        if (!AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) {
            return Functions.identity();
        }
        AndroidRuntimeJarProvider.Builder result = collectDesugaredJarsFromAttributes(ruleContext,
                semantics.getAttributesWithJavaRuntimeDeps(ruleContext));
        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 desugared = DexArchiveAspect.desugar(ruleContext, jar, attributes.getBootClassPath(),
                    attributes.getCompileTimeClassPath(), ruleContext.getDerivedArtifact(
                            jarPath.replaceName(jarPath.getBaseName() + "_desugared.jar"), jar.getRoot()));
            result.addDesugaredJar(jar, desugared);
        }
        return result.build().collapseToFunction();
    }

    private static AndroidRuntimeJarProvider.Builder collectDesugaredJarsFromAttributes(RuleContext ruleContext,
            ImmutableList<String> attributes) {
        AndroidRuntimeJarProvider.Builder result = new AndroidRuntimeJarProvider.Builder();
        for (String attr : attributes) {
            // Use all available AndroidRuntimeJarProvider from attributes that carry runtime dependencies
            result.addTransitiveProviders(
                    ruleContext.getPrerequisites(attr, Mode.TARGET, AndroidRuntimeJarProvider.class));
        }
        return result;
    }

    /**
     * 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, AndroidSemantics semantics, Function<Artifact, Artifact> derivedJarFunction) {
        DexArchiveProvider.Builder result = new DexArchiveProvider.Builder();
        for (String attr : semantics.getAttributesWithJavaRuntimeDeps(ruleContext)) {
            // Use all available DexArchiveProviders from attributes that carry runtime dependencies
            result.addTransitiveProviders(
                    ruleContext.getPrerequisites(attr, 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 dexArchive = DexArchiveAspect.createDexArchiveAction(ruleContext,
                    derivedJarFunction.apply(jar), 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, AndroidSemantics semantics,
            JavaTargetAttributes attributes, Function<Artifact, Artifact> derivedJarFunction,
            @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, semantics, derivedJarFunction));
                shardCommandLine.add("--split_dexed_classes");
            } else {
                classpath = Iterables.transform(classpath, derivedJarFunction);
            }
            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;
    }

    // Adds the appropriate SpawnAction options depending on if SingleJar is a jar or not.
    private static SpawnAction.Builder singleJarSpawnActionBuilder(RuleContext ruleContext) {
        Artifact singleJar = JavaToolchainProvider.fromRuleContext(ruleContext).getSingleJar();
        SpawnAction.Builder builder = new SpawnAction.Builder();
        if (singleJar.getFilename().endsWith(".jar")) {
            builder.setJarExecutable(ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable(),
                    singleJar, JavaToolchainProvider.fromRuleContext(ruleContext).getJvmOptions())
                    .addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext));
        } else {
            builder.setExecutable(singleJar);
        }
        return builder;
    }

    /**
     * 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) {
        if (ruleContext.getFragment(AndroidConfiguration.class).useSingleJarForMultidex()) {
            ruleContext.registerAction(singleJarSpawnActionBuilder(ruleContext).addArgument("--exclude_build_data")
                    .addArgument("--dont_change_compression").addArgument("--sources").addInputArgument(inputZip)
                    .addArgument("--output").addOutputArgument(outputZip).addArgument("--include_prefixes")
                    .addArgument("classes").setProgressMessage("Trimming " + inputZip.getExecPath().getBaseName())
                    .setMnemonic("TrimDexZip").build(ruleContext));
        } else {
            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;
    }

    /**
     * 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;
        }
    }
}