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

Java tutorial

Introduction

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

Source

// Copyright 2017 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.devtools.build.lib.analysis.OutputGroupProvider.INTERNAL_SUFFIX;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.ParamFileInfo;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
import com.google.devtools.build.lib.rules.java.JavaInfo;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
import com.google.devtools.build.lib.rules.java.ProguardHelper;
import com.google.devtools.build.lib.syntax.Type;
import java.util.Map;

/**
 * Encapsulates the logic for creating actions for mobile-install.
 */
public final class AndroidBinaryMobileInstall {

    /**
     * Data class for the resource apks created for mobile-install.
     */
    public static final class MobileInstallResourceApks {
        final ResourceApk incrementalResourceApk;
        final ResourceApk splitResourceApk;

        public MobileInstallResourceApks(ResourceApk incrementalResourceApk, ResourceApk splitResourceApk) {
            this.incrementalResourceApk = incrementalResourceApk;
            this.splitResourceApk = splitResourceApk;
        }
    }

    static MobileInstallResourceApks createMobileInstallResourceApks(RuleContext ruleContext,
            ApplicationManifest applicationManifest, ResourceDependencies resourceDeps)
            throws RuleErrorException, InterruptedException {

        ResourceApk incrementalResourceApk;
        ResourceApk splitResourceApk;

        if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) {
            incrementalResourceApk = applicationManifest.addMobileInstallStubApplication(ruleContext)
                    .packIncrementalBinaryWithDataAndResources(ruleContext,
                            ruleContext.getImplicitOutputArtifact(
                                    AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK),
                            resourceDeps,
                            ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"),
                            ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
                            ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"));
            ruleContext.assertNoErrors();

            splitResourceApk = applicationManifest.createSplitManifest(ruleContext, "android_resources", false)
                    .packIncrementalBinaryWithDataAndResources(ruleContext,
                            getMobileInstallArtifact(ruleContext, "android_resources.ap_"), resourceDeps,
                            ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"),
                            ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
                            ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"));
            ruleContext.assertNoErrors();

        } else {

            incrementalResourceApk = applicationManifest.addMobileInstallStubApplication(ruleContext)
                    .packWithResources(
                            ruleContext.getImplicitOutputArtifact(
                                    AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK),
                            ruleContext, resourceDeps, false, /* createSource */
                            ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"),
                            null /* mainDexProguardConfig */);
            ruleContext.assertNoErrors();

            splitResourceApk = applicationManifest.createSplitManifest(ruleContext, "android_resources", false)
                    .packWithResources(getMobileInstallArtifact(ruleContext, "android_resources.ap_"), ruleContext,
                            resourceDeps, false, /* createSource */
                            ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"),
                            null /* mainDexProguardConfig */);
            ruleContext.assertNoErrors();
        }

        return new MobileInstallResourceApks(incrementalResourceApk, splitResourceApk);
    }

    static void addMobileInstall(RuleContext ruleContext, RuleConfiguredTargetBuilder ruleConfiguredTargetBuilder,
            AndroidBinary.DexingOutput dexingOutput, JavaSemantics javaSemantics, NativeLibs nativeLibs,
            ResourceApk resourceApk, MobileInstallResourceApks mobileInstallResourceApks,
            FilesToRunProvider resourceExtractor, NestedSet<Artifact> nativeLibsZips, Artifact signingKey,
            ImmutableList<Artifact> dataDeps, ImmutableList<Artifact> additionalMergedManifests,
            ApplicationManifest applicationManifest) throws InterruptedException, RuleErrorException {

        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().useDefaultShellEnvironment()
                .setMnemonic("AndroidDexManifest")
                .setProgressMessage("Generating incremental installation manifest for %s", ruleContext.getLabel())
                .setExecutable(ruleContext.getExecutablePrerequisite("$build_incremental_dexmanifest", Mode.HOST))
                .addOutput(incrementalDexManifest).addInputs(dexingOutput.shardDexZips)
                .addCommandLine(
                        CustomCommandLine.builder().addExecPath(incrementalDexManifest)
                                .addExecPaths(dexingOutput.shardDexZips).build(),
                        ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
                .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")
                .setClassesDex(stubDex).addInputZip(mobileInstallResourceApks.incrementalResourceApk.getArtifact())
                .setJavaResourceZip(dexingOutput.javaResourceJar, resourceExtractor).addInputZips(nativeLibsZips)
                .setJavaResourceFile(stubData).setSignedApk(incrementalApk).setSigningKey(signingKey);

        if (!ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) {
            incrementalActionsBuilder.setNativeLibs(nativeLibs);
        }

        incrementalActionsBuilder.registerActions(ruleContext);

        Artifact argsArtifact = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.MOBILE_INSTALL_ARGS);
        ruleContext.registerAction(new WriteAdbArgsAction(ruleContext.getActionOwner(), argsArtifact));

        createInstallAction(ruleContext, /* incremental = */ false, fullDeployMarker, argsArtifact,
                incrementalDexManifest, mobileInstallResourceApks.incrementalResourceApk.getArtifact(),
                incrementalApk, nativeLibs, stubData);

        createInstallAction(ruleContext, /* incremental = */ true, incrementalDeployMarker, argsArtifact,
                incrementalDexManifest, mobileInstallResourceApks.incrementalResourceApk.getArtifact(),
                incrementalApk, nativeLibs, stubData);

        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 = getMobileInstallArtifact(ruleContext, "android_resources.apk");
        ApkActionsBuilder.create("split Android resource apk")
                .addInputZip(mobileInstallResourceApks.splitResourceApk.getArtifact())
                .setSignedApk(resourceSplitApk).setSigningKey(signingKey).registerActions(ruleContext);
        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 = getMobileInstallArtifact(ruleContext, splitName + ".apk");
            ApkActionsBuilder.create("split dex apk " + (i + 1)).setClassesDex(dexingOutput.shardDexZips.get(i))
                    .addInputZip(splitApkResources).setSignedApk(splitApk).setSigningKey(signingKey)
                    .registerActions(ruleContext);
            splitApkSetBuilder.add(splitApk);
        }

        Artifact nativeSplitApkResources = createSplitApkResources(ruleContext, applicationManifest, "native",
                false);
        Artifact nativeSplitApk = getMobileInstallArtifact(ruleContext, "native.apk");
        ApkActionsBuilder.create("split native apk").addInputZip(nativeSplitApkResources).setNativeLibs(nativeLibs)
                .setSignedApk(nativeSplitApk).setSigningKey(signingKey).registerActions(ruleContext);
        splitApkSetBuilder.add(nativeSplitApk);

        Artifact javaSplitApkResources = createSplitApkResources(ruleContext, applicationManifest, "java_resources",
                false);
        Artifact javaSplitApk = getMobileInstallArtifact(ruleContext, "java_resources.apk");
        ApkActionsBuilder.create("split Java resource apk").addInputZip(javaSplitApkResources)
                .setJavaResourceZip(dexingOutput.javaResourceJar, resourceExtractor).setSignedApk(javaSplitApk)
                .setSigningKey(signingKey).registerActions(ruleContext);
        splitApkSetBuilder.add(javaSplitApk);

        Artifact splitMainApkResources = getMobileInstallArtifact(ruleContext, "split_main.ap_");
        ruleContext.registerAction(
                new SpawnAction.Builder().useDefaultShellEnvironment().setMnemonic("AndroidStripResources")
                        .setProgressMessage("Stripping resources from split main apk")
                        .setExecutable(ruleContext.getExecutablePrerequisite("$strip_resources", Mode.HOST))
                        .addInput(resourceApk.getArtifact()).addOutput(splitMainApkResources)
                        .addCommandLine(CustomCommandLine.builder()
                                .addExecPath("--input_resource_apk", resourceApk.getArtifact())
                                .addExecPath("--output_resource_apk", splitMainApkResources).build())
                        .build(ruleContext));

        NestedSet<Artifact> splitApks = splitApkSetBuilder.build();
        Artifact splitMainApk = getMobileInstallArtifact(ruleContext, "split_main.apk");
        Artifact splitStubDex = getStubDex(ruleContext, javaSemantics, true);
        ruleContext.assertNoErrors();
        ApkActionsBuilder.create("split main apk").setClassesDex(splitStubDex).addInputZip(splitMainApkResources)
                .addInputZips(nativeLibsZips).setSignedApk(splitMainApk).setSigningKey(signingKey)
                .registerActions(ruleContext);
        splitApkSetBuilder.add(splitMainApk);
        NestedSet<Artifact> allSplitApks = splitApkSetBuilder.build();

        createSplitInstallAction(ruleContext, splitDeployMarker, argsArtifact, splitMainApk, splitApks, stubData);

        Artifact incrementalDeployInfo = ruleContext
                .getImplicitOutputArtifact(AndroidRuleClasses.DEPLOY_INFO_INCREMENTAL);
        AndroidDeployInfoAction.createDeployInfoAction(ruleContext, incrementalDeployInfo,
                resourceApk.getManifest(), additionalMergedManifests, ImmutableList.<Artifact>of(), dataDeps);

        Artifact splitDeployInfo = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEPLOY_INFO_SPLIT);
        AndroidDeployInfoAction.createDeployInfoAction(ruleContext, splitDeployInfo, 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();

        NestedSet<Artifact> splitInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder()
                .addTransitive(allSplitApks).add(splitDeployMarker).add(splitDeployInfo).build();

        ruleConfiguredTargetBuilder.addOutputGroup("mobile_install_full" + INTERNAL_SUFFIX, fullInstallOutputGroup)
                .addOutputGroup("mobile_install_incremental" + INTERNAL_SUFFIX, incrementalInstallOutputGroup)
                .addOutputGroup("mobile_install_split" + INTERNAL_SUFFIX, splitInstallOutputGroup)
                .addOutputGroup("android_incremental_deploy_info", incrementalDeployInfo);
    }

    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 = JavaInfo.getProvider(JavaCompilationArgsProvider.class, dep);
        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 = AndroidBinary
                    .collectDesugaredJarsFromAttributes(ruleContext, ImmutableList.of(attribute)).build()
                    .collapseToFunction();
        }
        Artifact stubDeployJar = getMobileInstallArtifact(ruleContext,
                split ? "split_stub_deploy.jar" : "stub_deploy.jar");
        new DeployArchiveBuilder(javaSemantics, ruleContext).setOutputJar(stubDeployJar).setAttributes(attributes)
                .setDerivedJarFunction(desugaredJars)
                .setCheckDesugarDeps(AndroidCommon.getAndroidConfig(ruleContext).checkDesugarDeps()).build();

        Artifact stubDex = getMobileInstallArtifact(ruleContext,
                split ? "split_stub_application/classes.dex" : "stub_application/classes.dex");
        AndroidCommon.createDexAction(ruleContext, stubDeployJar, stubDex, ImmutableList.<String>of(), false, null);

        return stubDex;
    }

    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().useDefaultShellEnvironment()
                .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 %s%s", ruleContext.getLabel(),
                        (incremental ? " incrementally" : ""))
                .setExecutionInfo(ImmutableMap.of("local", "")).addTool(adb).addOutput(marker).addInput(dexmanifest)
                .addInput(resourceApk).addInput(stubDataFile).addInput(argsArtifact);

        CustomCommandLine.Builder commandLine = CustomCommandLine.builder().addExecPath("--output_marker", marker)
                .addExecPath("--dexmanifest", dexmanifest).addExecPath("--resource_apk", resourceApk)
                .addExecPath("--stub_datafile", stubDataFile).addExecPath("--adb", adb.getExecutable())
                .addExecPath("--flagfile", argsArtifact);

        if (!incremental) {
            builder.addInput(apk);
            commandLine.addExecPath("--apk", apk);
        }

        if (ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) {
            for (Map.Entry<String, NestedSet<Artifact>> arch : nativeLibs.getMap().entrySet()) {
                for (Artifact lib : arch.getValue()) {
                    builder.addInput(lib);
                    commandLine.add("--native_lib").addFormatted("%s:%s", arch.getKey(), lib);
                }
            }
        }

        builder.addCommandLine(commandLine.build());
        ruleContext.registerAction(builder.build(ruleContext));
    }

    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().useDefaultShellEnvironment()
                .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST))
                .addTool(adb).executeUnconditionally().setMnemonic("AndroidInstall")
                .setProgressMessage("Installing %s using split apks", ruleContext.getLabel())
                .setExecutionInfo(ImmutableMap.of("local", "")).addTool(adb).addOutput(marker)
                .addInput(stubDataFile).addInput(argsArtifact).addInput(splitMainApk);
        CustomCommandLine.Builder commandLine = CustomCommandLine.builder().addExecPath("--output_marker", marker)
                .addExecPath("--stub_datafile", stubDataFile).addExecPath("--adb", adb.getExecutable())
                .addExecPath("--flagfile", argsArtifact).addExecPath("--split_main_apk", splitMainApk);

        for (Artifact splitApk : splitApks) {
            builder.addInput(splitApk);
            commandLine.addExecPath("--split_apk", splitApk);
        }

        builder.addCommandLine(commandLine.build());
        ruleContext.registerAction(builder.build(ruleContext));
    }

    private static Artifact createSplitApkResources(RuleContext ruleContext, ApplicationManifest mainManifest,
            String splitName, boolean hasCode) {
        Artifact splitManifest = mainManifest.createSplitManifest(ruleContext, splitName, hasCode).getManifest();
        Artifact splitResources = getMobileInstallArtifact(ruleContext, "split_" + splitName + ".ap_");
        AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
        ruleContext.registerAction(new SpawnAction.Builder().useDefaultShellEnvironment()
                .setExecutable(sdk.getAapt()).setMnemonic("AaptSplitResourceApk")
                .setProgressMessage("Generating resource apk for split %s", splitName).addOutput(splitResources)
                .addInput(splitManifest).addInput(sdk.getAndroidJar())
                .addCommandLine(CustomCommandLine.builder().add("package").addExecPath("-F", splitResources)
                        .addExecPath("-M", splitManifest).addExecPath("-I", sdk.getAndroidJar()).build())
                .build(ruleContext));

        return splitResources;
    }

    /**
     * Returns an intermediate artifact used to support mobile-install.
     */
    private static Artifact getMobileInstallArtifact(RuleContext ruleContext, String baseName) {
        return ruleContext.getUniqueDirectoryArtifact("_mobile_install", baseName,
                ruleContext.getBinOrGenfilesDirectory());
    }
}