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

com.ooftf.spy.plugin.ApiInspectTransform.groovy Maven / Gradle / Ivy

package com.ooftf.spy.plugin

import com.android.SdkConstants
import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import com.google.common.collect.ImmutableSet
import javassist.ClassPool
import javassist.CtClass
import org.gradle.api.GradleException
import org.gradle.api.Project

import java.util.jar.JarEntry
import java.util.jar.JarFile

/**
 * Created by ooftf on 2021/3/2.
 */
class ApiInspectTransform extends Transform {

    Project mProject

    ClassPool mClassPool

    ApiInspector mApiInspector

    DefaultApiInspectFilter mApiInspectFilter

    ApiInspectExtension mApiInspectExtension


    Set mJarFilePaths = new HashSet<>()
    Set mProjectFilePaths = new HashSet<>()

    ApiInspectTransform(Project project) {
        this.mProject = project
        mApiInspectExtension = mProject.extensions.findByType(ApiInspectExtension.class)
        mApiInspectFilter = new DefaultApiInspectFilter(project)
    }

    @Override
    String getName() {
        return "apiInspect"
    }

    @Override
    Set getInputTypes() {
        return ImmutableSet.of(QualifiedContent.DefaultContentType.CLASSES)
    }

    @Override
    Set getScopes() {
        return ImmutableSet.of()
    }

    @Override
    Set getReferencedScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return true
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
        if (!mApiInspectExtension.enable) {
            return
        }
        initialize()
        if (transformInvocation.isIncremental() && isIncremental()) {
            transformInvocation.referencedInputs.each { TransformInput input ->
                input.directoryInputs.each { DirectoryInput directoryInput ->
                    Map changedFiles = directoryInput.getChangedFiles()
                    for (Map.Entry changedInput : changedFiles.entrySet()) {
                        File changeInputFile = changedInput.getKey()
                        Status status = changedInput.getValue()
                        switch (status) {
                            case Status.NOTCHANGED:
                                mClassPool.appendClassPath(directoryInput.file.absolutePath)
                                break
                            case Status.ADDED:
                            case Status.CHANGED:
                                mClassPool.appendClassPath(directoryInput.file.absolutePath)
                                mProjectFilePaths.add(directoryInput.file)
                                break
                            case Status.REMOVED:
                                break
                            default:
                                break
                        }
                    }
                }

                input.jarInputs.each { JarInput jarInput ->
                    File jarFile = jarInput.file
                    switch (jarInput.status) {
                        case Status.NOTCHANGED:
                            mClassPool.appendClassPath(jarFile.absolutePath)
                            break
                        case Status.ADDED:
                        case Status.CHANGED:
                            mClassPool.appendClassPath(jarFile.absolutePath)
                            mJarFilePaths.add(jarFile)
                            break
                        case Status.REMOVED:
                            FileUtils.deleteIfExists(jarFile)
                            break
                    }
                }
            }
        } else {
            transformInvocation.referencedInputs.each { TransformInput input ->
                input.directoryInputs.each { DirectoryInput directoryInput ->
                    mClassPool.appendClassPath(directoryInput.file.absolutePath)
                    mProjectFilePaths.add(directoryInput.file)
                }

                input.jarInputs.each { JarInput jarInput ->
                    mClassPool.appendClassPath(jarInput.file.absolutePath)
                    mJarFilePaths.add(jarInput.file)
                }
            }
        }
        mProjectFilePaths.each { projectFile ->
            String root = projectFile.absolutePath
            projectFile.eachFileRecurse { File file ->
                if (file.isFile()) {
                    def entryName = file.absolutePath.replace(root, '')
                    inspectFile(entryName)
                }
            }
        }
        mJarFilePaths.each { jarFile ->
            JarFile jar = new JarFile(jarFile)
            Enumeration jarEntries = jar.entries()
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement()
                if (jarEntry.isDirectory())
                    continue
                String entryName = jarEntry.getName()
                inspectFile(entryName)
            }
        }
        Set incompatibleClassInfoSet = mApiInspector.getIncompatibleClasses()
        Set incompatibleMethodInfoSet = mApiInspector.getIncompatibleMethods()
        System.err.print("InspectApi end ,has error = " + (!incompatibleClassInfoSet.isEmpty() || !incompatibleMethodInfoSet.isEmpty()))
        if (!incompatibleClassInfoSet.isEmpty() || !incompatibleMethodInfoSet.isEmpty()) {
            int count = incompatibleClassInfoSet.size() + incompatibleMethodInfoSet.size()
            mProject.logger.error("\n=====================================>Api Incompatible<=====================================")
            mProject.logger.error(">>> Count: [$count]")
            mProject.logger.error("--------------------------------------------------------------------------------------------")
            incompatibleClassInfoSet.each {
                mProject.logger.error("Incompatible Api -> [Class: ${it.incompatibleClassName}]")
                mProject.logger.error("                 └> [Occur in class : ${it.className}]")
                mProject.logger.error("--------------------------------------------------------------------------------------------")
            }
            incompatibleMethodInfoSet.each {
                mProject.logger.error("Incompatible Api -> [Class: ${it.incompatibleClassName}]")
                mProject.logger.error("                 └> [Method: ${it.methodName}]")
                mProject.logger.error("                 └> [Occur in class: ${it.className}, Line: ${it.lineNumber}]")
                mProject.logger.error("--------------------------------------------------------------------------------------------")
            }
            mProject.logger.error("============================================================================================\n")
        }

        def variant = transformInvocation.context.variantName
        ApiInspectTools.exportApiInspectResult(mProject, variant, incompatibleClassInfoSet, incompatibleMethodInfoSet)
        mClassPool.clearImportedPackages()
        if (mApiInspectExtension.interruptBuild && (!incompatibleClassInfoSet.isEmpty() || !incompatibleMethodInfoSet.isEmpty())) {
            throw new SpyException("class error:" + incompatibleClassInfoSet.size() + "; method error:" + incompatibleMethodInfoSet.size())
        }

    }


    def setupConfig() {
        ApiInspectIncludeExtension includeExtension = mApiInspectExtension.include
        ApiInspectExcludeExtension excludeExtension = mApiInspectExtension.exclude

        if (includeExtension != null && includeExtension.apis != null)
            mApiInspectFilter.addIncludePackages(includeExtension.apis)

        if (excludeExtension != null && excludeExtension.apis != null)
            mApiInspectFilter.addExcludePackages(excludeExtension.apis)
    }

    def inspectFile(String entryName) {
        if (entryName.endsWith(SdkConstants.DOT_CLASS)) {
            String className = entryName.replace('/', '.').replace('\\', '.').replace(File.separator, '.')
            def start = 0
            if (className.startsWith(".")) {
                start = 1
            }
            className = className.substring(start, className.length() - SdkConstants.DOT_CLASS.length())
            try {
                CtClass clazz = mClassPool.getCtClass(className)
                mApiInspector.inspectClass(mClassPool, clazz)
            } catch (Exception e) {
                e.printStackTrace()
            }
        }
    }

    def initialize() {
        mClassPool = new ClassPool()
        mApiInspector = new ApiInspector(mProject, mApiInspectFilter)

        setupConfig()

        def android
        if (mProject.plugins.hasPlugin("com.android.application")) {
            android = mProject.extensions.getByType(AppExtension.class)
        } else {
            throw new GradleException("The plugin type is not supported!")
        }

        def androidJar = "${android.getSdkDirectory().getAbsolutePath()}${File.separator}platforms${File.separator}" +
                "${android.getCompileSdkVersion()}${File.separator}android.jar"

        mClassPool.appendClassPath(androidJar)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy