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

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

The newest version!
/*
 * Copyright (C) 2014 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;

import static com.google.common.base.Preconditions.checkNotNull;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.apkzlib.zfile.ApkCreatorFactory;
import com.android.build.FilterData;
import com.android.build.OutputFile;
import com.android.build.gradle.api.ApkOutputFile;
import com.android.build.gradle.internal.dsl.AbiSplitOptions;
import com.android.build.gradle.internal.ide.FilterDataImpl;
import com.android.build.gradle.internal.packaging.ApkCreatorFactories;
import com.android.build.gradle.internal.pipeline.StreamFilter;
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.scope.VariantScope;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.BaseVariantOutputData;
import com.android.builder.core.VariantConfiguration;
import com.android.builder.files.IncrementalRelativeFileSets;
import com.android.builder.files.RelativeFile;
import com.android.builder.internal.packaging.IncrementalPackager;
import com.android.builder.model.AaptOptions;
import com.android.builder.model.ApiVersion;
import com.android.builder.model.SigningConfig;
import com.android.builder.packaging.PackagerException;
import com.android.builder.packaging.PackagingUtils;
import com.android.builder.packaging.SigningException;
import com.android.ide.common.res2.FileStatus;
import com.android.ide.common.signing.CertificateInfo;
import com.android.ide.common.signing.KeystoreHelper;
import com.android.ide.common.signing.KeytoolException;
import com.android.utils.FileUtils;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Callables;

import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFiles;
import org.gradle.api.tasks.ParallelizableTask;
import org.gradle.api.tasks.TaskAction;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Package a abi dimension specific split APK
 */
@ParallelizableTask
public class PackageSplitAbi extends SplitRelatedTask {

    private ImmutableList outputFiles;

    private Collection inputFiles;

    private File outputDirectory;

    private Set splits;

    private String outputBaseName;

    private boolean jniDebuggable;

    private SigningConfig signingConfig;

    private Collection jniFolders;

    private ApiVersion minSdkVersion;

    private File incrementalDir;

    private File manifest;

    private AaptOptions aaptOptions;

    @OutputFiles
    public List getOutputFiles() {
        ImmutableList.Builder builder = ImmutableList.builder();
        for (String split : splits) {
            String apkName = getApkName(split);
            builder.add(new File(outputDirectory, apkName));
        }
        return builder.build();
    }

    @Override
    @Nullable
    public File getApkMetadataFile() {
        return null;
    }

    @Override
    @NonNull
    public synchronized ImmutableList getOutputSplitFiles() {

        if (outputFiles == null) {
            ImmutableList.Builder builder = ImmutableList.builder();
            for (String split : splits) {
                String apkName = getApkName(split);
                ApkOutputFile apkOutput = new ApkOutputFile(
                        OutputFile.OutputType.SPLIT,
                        ImmutableList.of(FilterDataImpl.build(OutputFile.ABI, apkName)),
                        Callables.returning(new File(outputDirectory, apkName)));
                builder.add(apkOutput);
            }

            outputFiles = builder.build();
        }
        return outputFiles;
    }

    private boolean isAbiSplit(String fileName) {
        for (String abi : getSplits()) {
            if (fileName.contains(abi)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Creates the {@link ApkCreatorFactory.CreationData} information needed to create the APK.
     *
     * @param outputApk the output file to create
     * @return the creation data
     */
    @NonNull
    private ApkCreatorFactory.CreationData makeCreationData(@NonNull File outputApk) {
        PrivateKey key;
        X509Certificate certificate;
        boolean v1SigningEnabled;
        boolean v2SigningEnabled;

        try {
            if (signingConfig != null && signingConfig.isSigningReady()) {
                CertificateInfo certificateInfo =
                        KeystoreHelper.getCertificateInfo(
                                signingConfig.getStoreType(),
                                checkNotNull(signingConfig.getStoreFile()),
                                checkNotNull(signingConfig.getStorePassword()),
                                checkNotNull(signingConfig.getKeyPassword()),
                                checkNotNull(signingConfig.getKeyAlias()));
                key = certificateInfo.getKey();
                certificate = certificateInfo.getCertificate();
                v1SigningEnabled = signingConfig.isV1SigningEnabled();
                v2SigningEnabled = signingConfig.isV2SigningEnabled();
            } else {
                key = null;
                certificate = null;
                v1SigningEnabled = false;
                v2SigningEnabled = false;
            }
        } catch (KeytoolException | IOException e) {
            throw new RuntimeException("Failed to get signing information", e);
        }

        return new ApkCreatorFactory.CreationData(
                outputApk,
                key,
                certificate,
                v1SigningEnabled,
                v2SigningEnabled,
                null, // BuiltBy
                getBuilder().getCreatedBy(),
                getMinSdkVersion(),
                PackagingUtils.getNativeLibrariesLibrariesPackagingMode(manifest),
                PackagingUtils.getNoCompressPredicate(aaptOptions, manifest));
    }

    @TaskAction
    protected void doFullTaskAction() throws FileNotFoundException, SigningException,
            KeytoolException, PackagerException, IOException {

        FileUtils.cleanOutputDir(incrementalDir);

        // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
        final Pattern pattern = Pattern.compile("resources-" + getOutputBaseName() + "-(.*).ap_");
        List unprocessedSplits = Lists.newArrayList(splits);
        for (File file : inputFiles) {
            Matcher matcher = pattern.matcher(file.getName());
            if (matcher.matches() && isAbiSplit(file.getName())) {
                String apkName = getApkName(matcher.group(1));

                File outFile = new File(getOutputDirectory(), apkName);

                ApkCreatorFactory.CreationData creationData = makeCreationData(outFile);
                try (IncrementalPackager pkg =
                        new IncrementalPackager(
                                creationData,
                                incrementalDir,
                                ApkCreatorFactories.fromProjectProperties(
                                        getProject(),
                                        isJniDebuggable()),
                                ImmutableSet.of(matcher.group(1)),
                                isJniDebuggable())) {
                    ImmutableMap nativeLibs =
                            IncrementalRelativeFileSets.fromZipsAndDirectories(getJniFolders());
                    pkg.updateNativeLibraries(nativeLibs);

                    ImmutableMap androidResources =
                            IncrementalRelativeFileSets.fromZip(file);
                    pkg.updateAndroidResources(androidResources);

                }

                unprocessedSplits.remove(matcher.group(1));
            }
        }
        if (!unprocessedSplits.isEmpty()) {
            String message = "Could not find resource package for "
                    + Joiner.on(',').join(unprocessedSplits);
            getLogger().error(message);
            throw new IllegalStateException(message);
        }
    }

    @Override
    public List getSplitsData() {
        ImmutableList.Builder filterDataBuilder = ImmutableList.builder();
        SplitRelatedTask.addAllFilterData(filterDataBuilder, splits, OutputFile.FilterType.ABI);
        return filterDataBuilder.build();
    }

    private String getApkName(final String split) {
        String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
        String apkName = archivesBaseName + "-" + getOutputBaseName() + "_" + split;
        return apkName + (getSigningConfig() == null ? "-unsigned.apk" : "-unaligned.apk");
    }

    public void setOutputFiles(ImmutableList outputFiles) {
        this.outputFiles = outputFiles;
    }

    @InputFiles
    public Collection getInputFiles() {
        return inputFiles;
    }

    public void setInputFiles(Collection inputFiles) {
        this.inputFiles = inputFiles;
    }

    public File getOutputDirectory() {
        return outputDirectory;
    }

    public void setOutputDirectory(File outputDirectory) {
        this.outputDirectory = outputDirectory;
    }

    @Input
    public Set getSplits() {
        return splits;
    }

    public void setSplits(Set splits) {
        this.splits = splits;
    }

    @Input
    public String getOutputBaseName() {
        return outputBaseName;
    }

    public void setOutputBaseName(String outputBaseName) {
        this.outputBaseName = outputBaseName;
    }

    @Input
    public boolean isJniDebuggable() {
        return jniDebuggable;
    }

    public void setJniDebuggable(boolean jniDebuggable) {
        this.jniDebuggable = jniDebuggable;
    }

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

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

    @Input
    public Collection getJniFolders() {
        return jniFolders;
    }

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

    public void setMinSdkVersion(ApiVersion version) {
        this.minSdkVersion = version;
    }

    @Input
    public int getMinSdkVersion() {
        return minSdkVersion.getApiLevel();
    }

    @Input
    public Collection getNoCompressExtensions() {
        return MoreObjects.>firstNonNull(
                aaptOptions.getNoCompress(), Collections.emptyList());
    }

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

    public static class ConfigAction implements TaskConfigAction {

        private VariantScope scope;

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

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

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

        @Override
        public void execute(@NonNull PackageSplitAbi packageSplitAbiTask) {
            BaseVariantData variantData = scope.getVariantData();
            BaseVariantOutputData variantOutputData = variantData.getMainOutput();
            VariantOutputScope variantOutputScope = variantOutputData.getScope();
            VariantConfiguration config = this.scope.getVariantConfiguration();

            variantOutputData.packageSplitAbiTask = packageSplitAbiTask;

            Set filters = AbiSplitOptions.getAbiFilters(
                    this.scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
            packageSplitAbiTask.setInputFiles(this.scope.getSplitAbiResOutputFiles());
            packageSplitAbiTask.setSplits(filters);
            packageSplitAbiTask.setOutputBaseName(config.getBaseName());
            packageSplitAbiTask.setSigningConfig(config.getSigningConfig());
            packageSplitAbiTask.setOutputDirectory(this.scope.getSplitOutputDirectory());
            packageSplitAbiTask.setAndroidBuilder(this.scope.getGlobalScope().getAndroidBuilder());
            packageSplitAbiTask.setVariantName(config.getFullName());
            packageSplitAbiTask.setMinSdkVersion(config.getMinSdkVersion());
            packageSplitAbiTask.incrementalDir = this.scope.getIncrementalDir(
                    packageSplitAbiTask.getName());

            packageSplitAbiTask.aaptOptions =
                    scope.getGlobalScope().getExtension().getAaptOptions();
            packageSplitAbiTask.manifest = variantOutputScope.getManifestOutputFile();

            ConventionMappingHelper.map(
                    packageSplitAbiTask,
                    "jniFolders",
                    () -> this.scope.getTransformManager()
                            .getPipelineOutput(StreamFilter.NATIVE_LIBS)
                            .keySet());

            ConventionMappingHelper.map(
                    packageSplitAbiTask,
                    "jniDebuggable",
                    () -> config.getBuildType().isJniDebuggable());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy