All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.build.gradle.tasks.PackageApplication Maven / Gradle / Ivy

package com.android.build.gradle.tasks;

import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;

import com.android.annotations.NonNull;
import com.android.build.gradle.internal.annotations.ApkFile;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dsl.AbiSplitOptions;
import com.android.build.gradle.internal.dsl.CoreSigningConfig;
import com.android.build.gradle.internal.dsl.PackagingOptions;
import com.android.build.gradle.internal.scope.ConventionMappingHelper;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantOutputScope;
import com.android.build.gradle.internal.tasks.FileSupplier;
import com.android.build.gradle.internal.tasks.IncrementalTask;
import com.android.build.gradle.internal.tasks.ValidateSigningTask;
import com.android.build.gradle.internal.variant.ApkVariantData;
import com.android.build.gradle.internal.variant.ApkVariantOutputData;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.builder.packaging.DuplicateFileException;
import com.android.utils.StringHelper;
import com.google.common.collect.ImmutableSet;

import org.codehaus.groovy.runtime.StringGroovyMethods;
import org.gradle.api.Task;
import org.gradle.api.file.FileTree;
import org.gradle.api.logging.Logger;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.ParallelizableTask;
import org.gradle.tooling.BuildException;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;

@ParallelizableTask
public class PackageApplication extends IncrementalTask implements FileSupplier {

    // ----- PUBLIC TASK API -----

    @InputFile
    public File getResourceFile() {
        return resourceFile;
    }

    public void setResourceFile(File resourceFile) {
        this.resourceFile = resourceFile;
    }

    @InputDirectory
    public File getDexFolder() {
        return dexFolder;
    }

    public void setDexFolder(File dexFolder) {
        this.dexFolder = dexFolder;
    }

    @InputFiles
    public Collection getDexedLibraries() {
        return dexedLibraries;
    }

    public void setDexedLibraries(Collection dexedLibraries) {
        this.dexedLibraries = dexedLibraries;
    }

    @InputDirectory
    @Optional
    public File getJavaResourceDir() {
        return javaResourceDir;
    }

    public void setJavaResourceDir(File javaResourceDir) {
        this.javaResourceDir = javaResourceDir;
    }

    public Set getJniFolders() {
        return jniFolders;
    }

    public void setJniFolders(Set jniFolders) {
        this.jniFolders = jniFolders;
    }

    public File getMergingFolder() {
        return mergingFolder;
    }

    public void setMergingFolder(File mergingFolder) {
        this.mergingFolder = mergingFolder;
    }

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

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

    @Input
    @Optional
    public Set getAbiFilters() {
        return abiFilters;
    }

    public void setAbiFilters(Set abiFilters) {
        this.abiFilters = abiFilters;
    }

    // ----- PRIVATE TASK API -----

    private File resourceFile;

    private File dexFolder;

    private Collection dexedLibraries;

    private File javaResourceDir;

    private Set jniFolders;

    private File mergingFolder;

    @ApkFile
    private File outputFile;

    private Set abiFilters;

    private Set packagedJars;

    private boolean jniDebugBuild;

    private CoreSigningConfig signingConfig;

    private PackagingOptions packagingOptions;

    @InputFiles
    public Set getPackagedJars() {
        return packagedJars;
    }

    public void setPackagedJars(Set packagedJars) {
        this.packagedJars = packagedJars;
    }

    @Input
    public boolean getJniDebugBuild() {
        return jniDebugBuild;
    }

    public boolean isJniDebugBuild() {
        return jniDebugBuild;
    }

    public void setJniDebugBuild(boolean jniDebugBuild) {
        this.jniDebugBuild = jniDebugBuild;
    }

    @Nested
    @Optional
    public CoreSigningConfig getSigningConfig() {
        return signingConfig;
    }

    public void setSigningConfig(CoreSigningConfig signingConfig) {
        this.signingConfig = signingConfig;
    }

    @Nested
    public PackagingOptions getPackagingOptions() {
        return packagingOptions;
    }

    public void setPackagingOptions(PackagingOptions packagingOptions) {
        this.packagingOptions = packagingOptions;
    }

    @InputFiles
    public FileTree getNativeLibraries() {
        FileTree src = null;
        Set folders = getJniFolders();
        if (!folders.isEmpty()) {
            src = getProject().files(new ArrayList(folders)).getAsFileTree();
        }

        return src == null ? getProject().files().getAsFileTree() : src;
    }

    @Override
    protected void doFullTaskAction() {
        try {
            final File dir = getJavaResourceDir();
            getBuilder().packageApk(getResourceFile().getAbsolutePath(), getDexFolder(),
                    getDexedLibraries(), getPackagedJars(),
                    (dir == null ? null : dir.getAbsolutePath()), getJniFolders(),
                    getMergingFolder(), getAbiFilters(), getJniDebugBuild(), getSigningConfig(),
                    getPackagingOptions(), getOutputFile().getAbsolutePath());
        } catch (DuplicateFileException e) {
            Logger logger = getLogger();
            logger.error("Error: duplicate files during packaging of APK " + getOutputFile()
                    .getAbsolutePath());
            logger.error("\tPath in archive: " + e.getArchivePath());
            logger.error("\tOrigin 1: " + e.getFile1());
            logger.error("\tOrigin 2: " + e.getFile2());
            logger.error("You can ignore those files in your build.gradle:");
            logger.error("\tandroid {");
            logger.error("\t  packagingOptions {");
            logger.error("\t    exclude \'" + e.getArchivePath() + "\'");
            logger.error("\t  }");
            logger.error("\t}");
            throw new BuildException(e.getMessage(), e);
        } catch (Exception e) {
            throw new BuildException(e.getMessage(), e);
        }

    }

    // ----- FileSupplierTask -----

    @Override
    public File get() {
        return getOutputFile();
    }

    @NonNull
    @Override
    public Task getTask() {
        return this;
    }

    // ----- ConfigAction -----

    public static class ConfigAction implements TaskConfigAction {

        private VariantOutputScope scope;

        public ConfigAction(VariantOutputScope scope) {
            this.scope = scope;
        }

        @Override
        public String getName() {
            return scope.getTaskName("package");
        }

        @Override
        public Class getType() {
            return PackageApplication.class;
        }

        @Override
        public void execute(PackageApplication packageApp) {
            final ApkVariantData variantData = (ApkVariantData) scope.getVariantScope().getVariantData();
            final ApkVariantOutputData variantOutputData = (ApkVariantOutputData) scope
                    .getVariantOutputData();
            final GradleVariantConfiguration config = scope.getVariantScope().getVariantConfiguration();

            variantOutputData.packageApplicationTask = packageApp;
            packageApp.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());

            if (config.isMinifyEnabled() && config.getBuildType().isShrinkResources() && !config
                    .getUseJack()) {
                ConventionMappingHelper.map(packageApp, "resourceFile", new Callable() {
                    @Override
                    public File call() {
                        return scope.getCompressedResourceFile();
                    }
                });
            } else {
                ConventionMappingHelper.map(packageApp, "resourceFile", new Callable() {
                    @Override
                    public File call() {
                        return variantOutputData.processResourcesTask.getPackageOutputFile();
                    }
                });
            }

            ConventionMappingHelper.map(packageApp, "dexFolder", new Callable() {
                @Override
                public File call() {
                    return scope.getVariantScope().getDexOutputFolder();
                }
            });
            ConventionMappingHelper.map(packageApp, "dexedLibraries", new Callable>() {
                @Override
                public Collection call() {
                    if (config.isMultiDexEnabled() && !config.isLegacyMultiDexMode()
                            && variantData.preDexTask != null) {
                        return scope.getGlobalScope().getProject()
                                .fileTree(variantData.preDexTask.getOutputFolder()).getFiles();
                    }

                    return Collections.emptyList();
                }
            });
            ConventionMappingHelper.map(packageApp, "packagedJars", new Callable>() {
                @Override
                public Set call() {
                    // when the application is obfuscated, the original resources may have been
                    // adapted to match changing package names for instance, so we take the
                    // resources from the obfuscation process results rather than the original
                    // exploded library's classes.jar files.
                    if (config.isMinifyEnabled() && variantData.obfuscationTask != null) {
                        return variantData.obfuscationTask.getOutputs().getFiles().getFiles();
                    }
                    return scope.getGlobalScope().getAndroidBuilder().getPackagedJars(config);
                }
            });

            packageApp.setMergingFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
                    variantOutputData.getFullName() + "/merging"));

            // when we use minification, the javaResources are given to the obfuscation task
            // so it has a chance to rename java resources in sync with packages renaming,
            // therefore the javaResources are located with the rest of the proguarded binary
            // files, otherwise use the output of the Java resources processing task.
            if (!config.isMinifyEnabled()) {
                ConventionMappingHelper.map(packageApp, "javaResourceDir", new Callable() {
                    @Override
                    public File call() {
                        return getOptionalDir(
                                variantData.processJavaResourcesTask.getDestinationDir());
                    }
                });
            }
            ConventionMappingHelper.map(packageApp, "jniFolders", new Callable>() {
                @Override
                public Set call() {

                    if (variantData.getSplitHandlingPolicy() ==
                            BaseVariantData.SplitHandlingPolicy.PRE_21_POLICY) {
                        return scope.getVariantScope().getJniFolders();
                    }
                    Set filters = AbiSplitOptions.getAbiFilters(
                            scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
                    return filters.isEmpty() ? scope.getVariantScope().getJniFolders() : Collections.emptySet();

                }
            });

            ConventionMappingHelper.map(packageApp, "abiFilters", new Callable>() {
                @Override
                public Set call() throws Exception {
                    if (variantOutputData.getMainOutputFile().getFilter(com.android.build.OutputFile.ABI) != null) {
                        return ImmutableSet.of(
                                variantOutputData.getMainOutputFile()
                                        .getFilter(com.android.build.OutputFile.ABI));
                    }
                    return config.getSupportedAbis();
                }
            });
            ConventionMappingHelper.map(packageApp, "jniDebugBuild", new Callable() {
                @Override
                public Boolean call() throws Exception {
                    return config.getBuildType().isJniDebuggable();
                }
            });

            CoreSigningConfig sc = (CoreSigningConfig) config.getSigningConfig();
            packageApp.setSigningConfig(sc);
            if (sc != null) {
                String validateSigningTaskName = "validate" + StringHelper.capitalize(sc.getName()) + "Signing";
                ValidateSigningTask validateSigningTask =
                        (ValidateSigningTask) scope.getGlobalScope().getProject().getTasks().findByName(validateSigningTaskName);
                if (validateSigningTask == null) {
                    validateSigningTask =
                            scope.getGlobalScope().getProject().getTasks().create(
                                    "validate" + StringHelper.capitalize(sc.getName()) + "Signing",
                                    ValidateSigningTask.class);
                    validateSigningTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
                    validateSigningTask.setSigningConfig(sc);
                }

                packageApp.dependsOn(validateSigningTask);
            }

            ConventionMappingHelper.map(packageApp, "packagingOptions", new Callable() {
                @Override
                public PackagingOptions call() throws Exception {
                    return scope.getGlobalScope().getExtension().getPackagingOptions();
                }
            });

            ConventionMappingHelper.map(packageApp, "outputFile", new Callable() {
                @Override
                public File call() throws Exception {
                    return scope.getPackageApk();
                }
            });
        }

        private ShrinkResources createShrinkResourcesTask(
                final ApkVariantOutputData variantOutputData) {
            BaseVariantData variantData = (BaseVariantData) variantOutputData.variantData;
            ShrinkResources task = scope.getGlobalScope().getProject().getTasks()
                    .create("shrink" + StringGroovyMethods
                            .capitalize(variantOutputData.getFullName())
                            + "Resources", ShrinkResources.class);
            task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
            task.variantOutputData = variantOutputData;

            final String outputBaseName = variantOutputData.getBaseName();
            task.setCompressedResources(new File(
                    scope.getGlobalScope().getBuildDir() + "/" + FD_INTERMEDIATES + "/res/" +
                            "resources-" + outputBaseName + "-stripped.ap_"));

            ConventionMappingHelper.map(task, "uncompressedResources", new Callable() {
                @Override
                public File call() {
                    return variantOutputData.processResourcesTask.getPackageOutputFile();
                }
            });

            task.dependsOn(
                    scope.getVariantScope().getObfuscationTask().getName(),
                    scope.getManifestProcessorTask().getName(),
                    variantOutputData.processResourcesTask);

            return task;
        }

        private static File getOptionalDir(File dir) {
            if (dir.isDirectory()) {
                return dir;
            }

            return null;
        }
    }
}