001/*
002 * Copyright (C) 2015-2021 KeepSafe Software
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package com.getkeepsafe.dexcount;
017
018import com.android.repository.Revision;
019import com.android.repository.Revision.Precision;
020import com.getkeepsafe.dexcount.plugin.TaskApplicator;
021import com.getkeepsafe.dexcount.plugin.TaskApplicators;
022import org.gradle.api.Plugin;
023import org.gradle.api.Project;
024import org.gradle.api.logging.configuration.ShowStacktrace;
025import org.gradle.util.GradleVersion;
026import org.jetbrains.annotations.NotNull;
027
028import java.lang.reflect.Field;
029import java.util.Arrays;
030import java.util.List;
031import java.util.Optional;
032
033public class DexMethodCountPlugin implements Plugin<Project> {
034    private static final String VERSION_3_ZERO_FIELD = "com.android.builder.Version"; // <= 3.0
035    private static final String VERSION_3_ONE_FIELD = "com.android.builder.model.Version"; // > 3.1
036    private static final String VERSION_7_0_FIELD = "com.android.Version"; // >= 7.0
037    private static final String AGP_VERSION_FIELD = "ANDROID_GRADLE_PLUGIN_VERSION";
038
039    private static final GradleVersion MIN_GRADLE_VERSION = GradleVersion.version("6.0");
040    private static final Revision MIN_AGP_VERSION = new Revision(3, 4, 0);
041
042    @Override
043    public void apply(@NotNull Project project) {
044        GradleVersion gradleVersion = GradleVersion.version(project.getGradle().getGradleVersion());
045        if (gradleVersion.compareTo(MIN_GRADLE_VERSION) < 0) {
046            project.getLogger().error("dexcount requires Gradle {} or above", MIN_GRADLE_VERSION);
047            return;
048        }
049
050        Revision gradlePluginRevision = getCurrentAgpRevision();
051        DexCountExtension ext = project.getExtensions().create("dexcount", DexCountExtension.class);
052
053        // If the user has passed '--stacktrace' or '--full-stacktrace', assume
054        // that they are trying to report a dexcount bug.  Help them help us out
055        // by printing the current plugin title and version.
056        if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS) {
057            ext.getPrintVersion().set(true);
058        }
059
060        // We need to do this check *after* we create the 'dexcount' Gradle extension.
061        // If we bail on instant run builds any earlier, then the build will break
062        // for anyone configuring dexcount due to the extension not being registered.
063        if (ProjectUtils.isInstantRun(project)) {
064            project.getLogger().info("Instant Run detected; disabling dexcount");
065            return;
066        }
067
068        if (gradlePluginRevision.compareTo(MIN_AGP_VERSION) < 0) {
069            project.getLogger().error("dexcount requires Android Gradle Plugin {} or above", MIN_AGP_VERSION);
070            return;
071        }
072
073        Optional<TaskApplicator.Factory> maybeFactory = TaskApplicators.getFactory(gradlePluginRevision);
074        if (maybeFactory.isPresent()) {
075            TaskApplicator applicator = maybeFactory.get().create(project, ext);
076            applicator.apply();
077        } else {
078            project.getLogger().error(
079                "No dexcount TaskApplicator configured for Gradle version {} and AGP version {}",
080                gradleVersion,
081                gradlePluginRevision);
082        }
083    }
084
085    private Revision getCurrentAgpRevision() throws RuntimeException {
086        List<String> versionClassNames = Arrays.asList(VERSION_7_0_FIELD, VERSION_3_ONE_FIELD, VERSION_3_ZERO_FIELD);
087        Exception thrown = null;
088
089        for (String className : versionClassNames) {
090            try {
091                Class<?> versionClass = Class.forName(className);
092                Field agpVersionField = versionClass.getDeclaredField(AGP_VERSION_FIELD);
093                String agpVersion = agpVersionField.get(null).toString();
094                return Revision.parseRevision(agpVersion, Precision.PREVIEW);
095            } catch (Exception e) {
096                thrown = e;
097            }
098        }
099
100        throw new IllegalStateException("dexcount requires the Android plugin to be configured", thrown);
101    }
102}