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

com.android.build.gradle.tasks.PackageSplitRes.groovy Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show 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 com.android.annotations.NonNull
import com.android.build.FilterData
import com.android.build.OutputFile
import com.android.build.gradle.api.ApkOutputFile
import com.android.build.gradle.internal.dsl.SigningConfig
import com.android.build.gradle.internal.model.FilterDataImpl
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Callables
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.util.regex.Matcher
import java.util.regex.Pattern

/**
 * Package each split resources into a specific signed apk file.
 */
@ParallelizableTask
class PackageSplitRes extends SplitRelatedTask {

    @Input
    Set densitySplits

    @Input
    Set languageSplits

    @Input
    String outputBaseName

    @Nested @Optional
    SigningConfig signingConfig

    @InputFiles
    List getInputFiles() {
        ImmutableList.Builder builder = ImmutableList.builder();
        forEachInputFile { split, file ->
            builder.add(file)
        }
        return builder.build()
    }

    @OutputFiles
    public List getOutputFiles() {
        getOutputSplitFiles()*.getOutputFile()
    }

    /**
     * This directories are not officially input/output to the task as
     * they are shared among tasks. To be parallelizable, we must only
     * define our I/O in terms of files...
     */
    File inputDirectory
    File outputDirectory

    /**
     * Calculates the list of output files, coming from the list of input files, mangling the
     * output file name.
     */
    public List getOutputSplitFiles() {
        ImmutableList.Builder builder = ImmutableList.builder();
        forEachInputFile { String split, File file ->
            // find the split identification, if null, the split is not requested any longer.
            FilterData filterData = null;
            for (String density : densitySplits) {
                if (split.startsWith(density)) {
                    filterData = FilterDataImpl.build(
                            OutputFile.FilterType.DENSITY.toString(), density)
                }
            }
            if (languageSplits.contains(unMangleSplitName(split))) {
                filterData = FilterDataImpl.build(
                        OutputFile.FilterType.LANGUAGE.toString(), split);
            }
            if (filterData != null) {
                builder.add(new ApkOutputFile(OutputFile.OutputType.SPLIT,
                        ImmutableList.of(filterData),
                        Callables.returning(
                                new File(outputDirectory, this.getOutputFileNameForSplit(split)))))
            }
        }
        return builder.build();
    }

    @TaskAction
    protected void doFullTaskAction() {

        forEachInputFile { String split, File file ->
            File outFile = new File(outputDirectory, this.getOutputFileNameForSplit(split));
            getBuilder().signApk(file, signingConfig, outFile)
        }
    }

    /**
     * Runs the closure for each task input file, providing the split identifier (possibly with
     * a suffix generated by aapt) and the input file handle.
     * @param closure groovy closure to run on each input file.
     */
    public void forEachInputFile(Closure closure) {
        Pattern resourcePattern = Pattern.compile(
                "resources-${outputBaseName}.ap__(.*)")

        // make a copy of the expected densities and languages filters.
        List densitiesCopy = new ArrayList<>(densitySplits)
        List languagesCopy = new ArrayList<>(languageSplits)

        // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
        for (File file : inputDirectory.listFiles()) {
            Matcher match = resourcePattern.matcher(file.getName())
            // each time we match, we remove the associated filter from our copies.
            if (match.matches() && !match.group(1).isEmpty()
                    && isValidSplit(densitiesCopy, languagesCopy, match.group(1))) {
                closure(match.group(1), file)
            }
        }
        // manually invoke the closure for filters we did not find associated files, apply best
        // guess on the actual file names.
        for (String density : densitiesCopy) {
            closure(density,
                    new File(inputDirectory, "resources-${outputBaseName}.ap__${density}"));
        }
        for (String language : languagesCopy) {
            closure(language,
                    new File(inputDirectory, "resources-${outputBaseName}.ap__${language}"));

        }
    }

    /**
     * Returns true if the passed split identifier is a valid identifier (valid mean it is a
     * requested split for this task). A density split identifier can be suffixed with characters
     * added by aapt.
     */
    private static boolean isValidSplit(
            List densities,
            List languages,
            @NonNull String splitWithOptionalSuffix) {
        for (String density : densities) {
            if (splitWithOptionalSuffix.startsWith(density)) {
                densities.remove(density);
                return true;
            }
        }
        String mangledName = unMangleSplitName(splitWithOptionalSuffix);
        if (languages.contains(mangledName)) {
            languages.remove(mangledName);
            return true;
        }
        return false;
    }

    String getOutputFileNameForSplit(String split) {
        String apkName = "${project.archivesBaseName}-${outputBaseName}_${split}"
        return apkName + (signingConfig == null ? "-unsigned.apk" : "-unaligned.apk")
    }

    @Override
    List getSplitsData() {
        ImmutableList.Builder filterDataBuilder = ImmutableList.builder();
        addAllFilterData(filterDataBuilder, densitySplits, OutputFile.FilterType.DENSITY);
        addAllFilterData(filterDataBuilder, languageSplits, OutputFile.FilterType.LANGUAGE);
        return filterDataBuilder.build();
    }

    /**
     * Un-mangle a split name as created by the aapt tool to retrieve a split name as configured
     * in the project's build.gradle.
     *
     * when dealing with several split language in a single split, each language (+ optional region)
     * will be seperated by an underscore.
     *
     * note that there is currently an aapt bug, remove the 'r' in the region so for instance,
     * fr-rCA becomes fr-CA, temporarily put it back until it is fixed.
     *
     * @param splitWithOptionalSuffix the mangled split name.
     * @return
     */
    static String unMangleSplitName(String splitWithOptionalSuffix) {
        String mangledName = splitWithOptionalSuffix.replaceAll('_', ',');
        return mangledName.replace("-", "-r");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy