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

com.getkeepsafe.dexcount.DexMethodCountPlugin.groovy Maven / Gradle / Ivy

/*
 * Copyright (C) 2015-2016 KeepSafe Software
 *
 * 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.getkeepsafe.dexcount

import com.android.build.gradle.api.ApkVariant
import com.android.build.gradle.api.ApkVariantOutput
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.BaseVariantOutput
import com.android.build.gradle.api.LibraryVariant
import com.android.build.gradle.api.TestVariant
import com.android.builder.Version
import com.android.repository.Revision
import com.getkeepsafe.dexcount.sdkresolver.SdkResolver
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.gradle.api.DomainObjectCollection
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task

class DexMethodCountPlugin implements Plugin {
    static File sdkLocation = SdkResolver.resolve(null)

    @Override
    void apply(Project project) {
        if (!isAtLeastJavaEight()) {
            project.logger.error("Java 8 or above is *STRONGLY* recommended - dexcount may not work properly on Java 7 or below!")
        }

        if (isInstantRun(project)) {
            project.logger.info("Instant Run detected; disabling dexcount")
            return
        }

        try {
            Class.forName("com.android.builder.Version")
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("dexcount requires the Android plugin to be configured", e)
        }

        def gradlePluginRevision = Revision.parseRevision(Version.ANDROID_GRADLE_PLUGIN_VERSION)
        def threeOhRevision = Revision.parseRevision("3.0.0")

        Provider provider

        //noinspection ChangeToOperator
        if (gradlePluginRevision.compareTo(threeOhRevision, Revision.PreviewComparison.IGNORE) >= 0) {
            provider = new ThreeOhProvider(project)
        } else {
            provider = new LegacyProvider(project)
        }

        sdkLocation = SdkResolver.resolve(project)
        provider.apply()
    }

    static boolean isAtLeastJavaEight() {
        String version = System.properties["java.version"]
        if (version == null) {
            // All JVMs provide this property... what's going on?
            return false
        }

        // Java version strings are something like 1.8.0_65; we don't
        // care about the third component, if it exists.  Skip it.
        def indexOfDecimal = version.indexOf('.')
        indexOfDecimal = version.indexOf('.', indexOfDecimal + 1)

        if (indexOfDecimal != -1) {
            version = version.substring(0, indexOfDecimal)
        }

        try {
            def numericVersion = Double.parseDouble(version)
            return numericVersion >= 1.8
        } catch (NumberFormatException ignored) {
            // Invalid Java version number; who knows.
            return false
        }
    }

    static boolean isInstantRun(Project project) {
        def compilationOptionString = project.properties["android.optional.compilation"] ?: ""
        def compilationOptionList = compilationOptionString.split(",")
        return compilationOptionList.any { it == "INSTANT_DEV" }
    }

    abstract class Provider {
        protected Project project
        protected DexMethodCountExtension ext

        protected Provider(Project project) {
            this.project = project
            this.ext = project.extensions.create("dexcount", DexMethodCountExtension)

            // If the user has passed '--stacktrace' or '--full-stacktrace', assume
            // that they are trying to report a dexcount bug.  Help them help us out
            // by printing the current plugin title and version.
            if (GradleApi.isShowStacktrace(project.gradle.startParameter)) {
                ext.printVersion = true
            }
        }

        void apply() {
            DomainObjectCollection variants

            if (project.plugins.hasPlugin('com.android.application')) {
                variants = project.android.applicationVariants
            } else if (project.plugins.hasPlugin('com.android.test')) {
                variants = project.android.applicationVariants
            } else if (project.plugins.hasPlugin('com.android.library')) {
                variants = project.android.libraryVariants
            } else {
                throw new IllegalArgumentException('Dexcount plugin requires the Android plugin to be configured')
            }

            variants.all { variant ->
                if (variant instanceof ApplicationVariant) {
                    applyToApplicationVariant((ApplicationVariant) variant)
                } else if (variant instanceof TestVariant) {
                    applyToTestVariant((TestVariant) variant)
                } else if (variant instanceof LibraryVariant) {
                    applyToLibraryVariant((LibraryVariant) variant)
                } else {
                    project.logger.error("dexcount: Don't know how to handle variant ${variant.name} of type ${variant.class}, skipping")
                }
            }
        }

        abstract void applyToApplicationVariant(ApplicationVariant variant)
        abstract void applyToTestVariant(TestVariant variant)
        abstract void applyToLibraryVariant(LibraryVariant variant)

        protected void addDexcountTaskToGraph(Task parentTask, DexMethodCountTask dexcountTask) {
            if (dexcountTask != null && parentTask != null) {
                // Dexcount tasks require that their parent task has been run...
                dexcountTask.dependsOn(parentTask)
                dexcountTask.mustRunAfter(parentTask)

                // But package should always imply that dexcount runs, unless configured not to.
                if (ext.runOnEachPackage) {
                    parentTask.finalizedBy(dexcountTask)
                }
            }
        }

        protected DexMethodCountTask createTask(
                BaseVariant variant,
                BaseVariantOutput output,
                @ClosureParams(value = SimpleType, options = ['com.getkeepsafe.dexcount.DexMethodCountTask']) Closure applyInputConfiguration) {
            def slug = variant.name.capitalize()
            def path = "${project.buildDir}/outputs/dexcount/${variant.name}"
            if (variant.outputs.size() > 1) {
                assert output != null
                slug += output.name.capitalize()
                path += "/${output.name}"
            }

            def task = project.tasks.create("count${slug}DexMethods", DexMethodCountTask)
            task.description = "Outputs dex method count for ${variant.name}."
            task.group = 'Reporting'
            task.variantOutputName = slug.uncapitalize()
            task.mappingFile = variant.mappingFile
            task.outputFile = project.file(path + ext.format.extension)
            task.summaryFile = project.file(path + '.csv')
            task.chartDir = project.file(path + 'Chart')
            task.config = ext

            applyInputConfiguration(task)

            return task
        }
    }

    class LegacyProvider extends Provider {
        LegacyProvider(Project project) {
            super(project)
        }

        @Override
        void applyToApplicationVariant(ApplicationVariant variant) {
            applyToApkVariant(variant)
        }

        @Override
        void applyToTestVariant(TestVariant variant) {
            applyToApkVariant(variant)
        }

        @Override
        void applyToLibraryVariant(LibraryVariant variant) {
            variant.outputs.each { output ->
                def aar = output.outputFile
                def task = createTask(variant, output) { t -> t.inputFile = aar }
                addDexcountTaskToGraph(output.assemble, task)
            }
        }

        private void applyToApkVariant(ApkVariant variant) {
            variant.outputs.each { output ->
                def apk = output.outputFile
                def task = createTask(variant, output) { t -> t.inputFile = apk }
                addDexcountTaskToGraph(output.assemble, task)
            }
        }
    }

    class ThreeOhProvider extends Provider {
        ThreeOhProvider(Project project) {
            super(project)
        }

        @Override
        void applyToApplicationVariant(ApplicationVariant variant) {
            applyToApkVariant(variant)
        }

        @Override
        void applyToTestVariant(TestVariant variant) {
            applyToApkVariant(variant)
        }

        @Override
        void applyToLibraryVariant(LibraryVariant variant) {
            def packageTask = variant.packageLibrary
            def dexcountTask = createTask(variant, null) { t -> t.inputFile = packageTask.archivePath }
            addDexcountTaskToGraph(packageTask, dexcountTask)
        }

        private void applyToApkVariant(ApkVariant variant) {
            variant.outputs.each { output ->
                if (output instanceof ApkVariantOutput) {
                    // why wouldn't it be?
                    def apkOutput = (ApkVariantOutput) output
                    def packageDirectory = apkOutput.packageApplication.outputDirectory
                    def task = createTask(variant, apkOutput) { t -> t.inputDirectory = packageDirectory }
                    addDexcountTaskToGraph(apkOutput.packageApplication, task)
                } else {
                    throw new IllegalArgumentException("Unexpected output type for variant ${variant.name}: ${output.class}")
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy