com.android.build.gradle.tasks.ir.GenerateInstantRunAppInfoTask.java Source code

Java tutorial

Introduction

Here is the source code for com.android.build.gradle.tasks.ir.GenerateInstantRunAppInfoTask.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.build.gradle.tasks.ir;

import static com.android.SdkConstants.ATTR_PACKAGE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.LCONST_0;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_6;

import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
import com.android.build.gradle.internal.scope.BuildOutput;
import com.android.build.gradle.internal.scope.BuildOutputs;
import com.android.build.gradle.internal.scope.InstantRunVariantScope;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.TransformVariantScope;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.BaseTask;
import com.android.build.gradle.tasks.PackageAndroidArtifact;
import com.android.utils.XmlUtils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.xml.parsers.ParserConfigurationException;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.tooling.BuildException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

/**
 * Reads the merged manifest file and creates an AppInfo class listing the applicationId and
 * application classes (if any).
 */
public class GenerateInstantRunAppInfoTask extends BaseTask {

    private static final String SERVER_PACKAGE = "com/android/tools/ir/server";

    private File outputFile;
    private FileCollection mergedManifests;
    private InstantRunBuildContext buildContext;

    @OutputFile
    public File getOutputFile() {
        return outputFile;
    }

    @VisibleForTesting
    void setOutputFile(File outputFile) {
        this.outputFile = outputFile;
    }

    @InputFiles
    public FileCollection getMergedManifests() {
        return mergedManifests;
    }

    @VisibleForTesting
    void setMergedManifests(FileCollection mergedManifests) {
        this.mergedManifests = mergedManifests;
    }

    @VisibleForTesting
    void setBuildContext(InstantRunBuildContext buildContext) {
        this.buildContext = buildContext;
    }

    @Input
    public long getSecretToken() {
        return buildContext.getSecretToken();
    }

    @TaskAction
    public void generateInfoTask() throws IOException {
        Collection<BuildOutput> buildOutputs = BuildOutputs
                .load(VariantScope.TaskOutputType.INSTANT_RUN_MERGED_MANIFESTS, getMergedManifests());

        if (buildOutputs.isEmpty()) {
            throw new RuntimeException("Cannot find the package-id from the merged manifest, "
                    + "please file a bug, a clean build should fix the issue.");
        }

        // obtain the application id from any of the split/main manifest file.
        BuildOutput buildOutput = buildOutputs.iterator().next();
        File manifestFile = buildOutput.getOutputFile();

        if (manifestFile.exists()) {
            try {
                // FIX ME : get the package from somewhere else.
                Document document = XmlUtils.parseUtfXmlFile(manifestFile, true);
                Element root = document.getDocumentElement();
                if (root != null) {
                    String applicationId = root.getAttribute(ATTR_PACKAGE);
                    if (!applicationId.isEmpty()) {
                        // Must be *after* extractLibrary() to replace dummy version
                        writeAppInfoClass(applicationId, getSecretToken());
                    }
                }
            } catch (ParserConfigurationException | IOException | SAXException e) {
                throw new BuildException("Failed to inject bootstrapping application", e);
            }
        } else {
            throw new FileNotFoundException("Cannot find " + manifestFile.getAbsolutePath());
        }
    }

    void writeAppInfoClass(@NonNull String applicationId, long token) throws IOException {
        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;

        String appInfoOwner = SERVER_PACKAGE + "/AppInfo";
        cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, appInfoOwner, null, "java/lang/Object", null);

        fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "applicationId", "Ljava/lang/String;", null, null);
        fv.visitEnd();
        fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "token", "J", null, null);
        fv.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        mv.visitInsn(RETURN);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitLocalVariable("this", "L" + appInfoOwner + ";", null, l0, l1, 0);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        mv.visitCode();
        mv.visitLdcInsn(applicationId);
        mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "applicationId", "Ljava/lang/String;");
        if (token != 0L) {
            mv.visitLdcInsn(token);
        } else {
            mv.visitInsn(LCONST_0);
        }
        mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "token", "J");

        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 0);
        mv.visitEnd();
        cw.visitEnd();

        byte[] bytes = cw.toByteArray();

        try (JarOutputStream outputStream = new JarOutputStream(
                new BufferedOutputStream(new FileOutputStream(getOutputFile())))) {
            outputStream.putNextEntry(new ZipEntry(SERVER_PACKAGE + "/AppInfo.class"));
            outputStream.write(bytes);
            outputStream.closeEntry();
        }
    }

    public static class ConfigAction implements TaskConfigAction<GenerateInstantRunAppInfoTask> {
        @NonNull
        private final InstantRunVariantScope variantScope;
        @NonNull
        private final TransformVariantScope transformVariantScope;
        @NonNull
        private final FileCollection manifests;

        public ConfigAction(@NonNull TransformVariantScope transformVariantScope,
                @NonNull InstantRunVariantScope variantScope, @NonNull FileCollection manifests) {
            this.transformVariantScope = transformVariantScope;
            this.variantScope = variantScope;
            this.manifests = manifests;
        }

        @NonNull
        @Override
        public String getName() {
            return transformVariantScope.getTaskName("generate", "InstantRunAppInfo");
        }

        @NonNull
        @Override
        public Class<GenerateInstantRunAppInfoTask> getType() {
            return GenerateInstantRunAppInfoTask.class;
        }

        @Override
        public void execute(@NonNull GenerateInstantRunAppInfoTask task) {
            task.setVariantName(variantScope.getFullVariantName());
            task.buildContext = variantScope.getInstantRunBuildContext();
            task.outputFile = new File(variantScope.getIncrementalApplicationSupportDir(),
                    PackageAndroidArtifact.INSTANT_RUN_PACKAGES_PREFIX + "-bootstrap.jar");

            task.mergedManifests = manifests;

        }
    }
}