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.jetbrains.annotations.NotNull;
026
027import java.lang.reflect.Field;
028import java.util.Arrays;
029import java.util.List;
030import java.util.Optional;
031
032public class DexMethodCountPlugin implements Plugin<Project> {
033    private static final String VERSION_3_ZERO_FIELD = "com.android.builder.Version"; // <= 3.0
034    private static final String VERSION_3_ONE_FIELD = "com.android.builder.model.Version"; // > 3.1
035    private static final String VERSION_7_0_FIELD = "com.android.Version"; // >= 7.0
036    private static final String AGP_VERSION_FIELD = "ANDROID_GRADLE_PLUGIN_VERSION";
037
038    private static final GradleVersion MIN_GRADLE_VERSION = new GradleVersion(6, 0);
039    private static final Revision MIN_AGP_VERSION = new Revision(3, 4, 0);
040
041    @Override
042    public void apply(@NotNull Project project) {
043        GradleVersion gradleVersion = ProjectUtils.gradleVersion(project);
044        if (gradleVersion.compareTo(MIN_GRADLE_VERSION) < 0) {
045            project.getLogger().error("dexcount requires Gradle {} or above", MIN_GRADLE_VERSION);
046            return;
047        }
048
049        Revision gradlePluginRevision = getCurrentAgpRevision();
050        DexCountExtension ext = project.getExtensions().create("dexcount", DexCountExtension.class);
051
052        // If the user has passed '--stacktrace' or '--full-stacktrace', assume
053        // that they are trying to report a dexcount bug.  Help them help us out
054        // by printing the current plugin title and version.
055        if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS) {
056            ext.getPrintVersion().set(true);
057        }
058
059        // We need to do this check *after* we create the 'dexcount' Gradle extension.
060        // If we bail on instant run builds any earlier, then the build will break
061        // for anyone configuring dexcount due to the extension not being registered.
062        if (ProjectUtils.isInstantRun(project)) {
063            project.getLogger().info("Instant Run detected; disabling dexcount");
064            return;
065        }
066
067        if (gradlePluginRevision.compareTo(MIN_AGP_VERSION) < 0) {
068            project.getLogger().error("dexcount requires Android Gradle Plugin {} or above", MIN_AGP_VERSION);
069            return;
070        }
071
072        Optional<TaskApplicator.Factory> maybeFactory = TaskApplicators.getFactory(gradlePluginRevision);
073        if (maybeFactory.isPresent()) {
074            TaskApplicator applicator = maybeFactory.get().create(project, ext);
075            applicator.apply();
076        } else {
077            project.getLogger().error(
078                "No dexcount TaskApplicator configured for Gradle version {} and AGP version {}",
079                gradleVersion,
080                gradlePluginRevision);
081        }
082    }
083
084    private Revision getCurrentAgpRevision() throws RuntimeException {
085        List<String> versionClassNames = Arrays.asList(VERSION_7_0_FIELD, VERSION_3_ONE_FIELD, VERSION_3_ZERO_FIELD);
086        Exception thrown = null;
087
088        for (String className : versionClassNames) {
089            try {
090                Class<?> versionClass = Class.forName(className);
091                Field agpVersionField = versionClass.getDeclaredField(AGP_VERSION_FIELD);
092                String agpVersion = agpVersionField.get(null).toString();
093                return Revision.parseRevision(agpVersion, Precision.PREVIEW);
094            } catch (Exception e) {
095                thrown = e;
096            }
097        }
098
099        throw new IllegalStateException("dexcount requires the Android plugin to be configured", thrown);
100    }
101}