百臂巨人与塔尔塔罗斯

作者: a49f87ef5d4f | 来源:发表于2019-07-23 18:53 被阅读2次
    image

    前言

    一款静态代码检测工具,包含阿里java规约检测和lint检测,支持自定义pmd和lint配置,结合git在代码提交时进行增量检测插件,赫卡同克瑞斯,也就是百臂巨人,来自希腊神话,是天空神乌拉诺斯和地神盖娅的儿子,拥有50头和100个手臂,在帮助宙斯夺得神位后,成为了塔尔塔罗斯的守门人,塔尔塔罗斯就是希腊神话中的地狱。

    为什么要写百臂巨人

    很多时候有些bug就是因为代码不规范造成的,这种低级错误往往会造成重大损失,之前就曾经碰到过在主线程加载图片的情况,之前因为运营配置的图片较小,所以也就没什么事,直到有一天运营配置了一个大图,直接导致大面积的ANR,这是一个低级错误,后来我写了一个自定义lint检测工具,用来检测这种在主线程使用BitmapFactory的情况。之后又写了一些其他的lint检测规则,之后遇到了另外两个问题,第一,我的lint检测出来了,标注出来了,但是开发者依旧会对他进行忽视,检测出来而不修改那不就是白搞了?如何强制开发者进行检测,这是一个问题。第二,项目庞大,整体检测时间很长,而且检测出的问题都上万了,这些问题谁来改?陈芝麻烂谷子的代码,又有谁愿意改?为此我需要一个增量检测的方案,结合git,只对要提交的修改的文件进行检测,这样谁修改谁倒霉,听天由命,避免抱怨。

    原理图

    image

    核心代码

    获取git修改文件

    增量检测,必然要相应的文件,这里利用了git的命令

    fun getCommitFiles(): List<File> {
    
            val command =
                arrayOf("/bin/bash", "-c", "git diff --name-only --diff-filter=ACMRTUXB HEAD")
    
            val process = Runtime.getRuntime().exec(command)
    
            process.waitFor()
    
            val commitFileList = mutableListOf<File>()
    
            try {
                val inputReader = BufferedReader(InputStreamReader(process.inputStream))
    
    
                var fileName = inputReader.readLine()
    
                while (fileName != null) {
    
                    commitFileList.add(File(fileName))
                    fileName = inputReader.readLine()
                }
    
                inputReader.close()
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: Exception) {
                e.printStackTrace()
            }
    
            return commitFileList
        }
    

    除了删除的文件,其他文件都会作为增量文件提取出来

    阿里规约检测的接入

    阿里对Java代码规范做了详尽的说明,这里我们利用阿里规约中的检测插件中的检测工具进行java文件的检测
    首先为项目添加p3c依赖

    private fun configPmdDependency(project: Project) {
            project.plugins.apply(PMD)
            val pmdConfig = project.configurations.getByName(PMD_CONFIGURATION)
            pmdConfig.dependencies.add(project.dependencies.create(P3C_PMD_DEPENDENCY))
            pmdConfig.dependencies.add(project.dependencies.create(PMD_DEPENDENCY))
        }
    

    这样就引用了阿里规约的pmd规则,同时由于引入pmd,支持相应的配置

    private fun configPmdTask(project: Project) {
            project.afterEvaluate {
                val pmdExtension = project.extensions.findByName(PMD) as PmdExtension
                val pmdTask = project.tasks.create(PMDTASK, Pmd::class.java)
                pmdTask.targetJdk = pmdExtension.targetJdk
                pmdTask.ignoreFailures = pmdExtension.isIgnoreFailures
                ALIRULESETS.addAll(pmdExtension.ruleSets)
                pmdTask.ruleSets = ALIRULESETS
                pmdTask.ruleSetFiles = pmdExtension.ruleSetFiles
                pmdTask.source(project.rootDir)
                pmdTask.isConsoleOutput = pmdExtension.isConsoleOutput
                pmdTask.rulePriority = pmdExtension.rulePriority
                pmdTask.reports {
                    it.xml.isEnabled = true
                    it.xml.destination = File(pmdExtension.reportsDir, "report.xml")
                    it.html.isEnabled = true
                    it.html.destination = File(pmdExtension.reportsDir, "report.html")
                }
                pmdTask.group = GOUP_NAME
                pmdTask.include(GitUtil.getCommitFilesPathForPMD())
                pmdTask.exclude("**/build/**", "**/res/**", "**/*.xml", "**/*.gradle", "**/*.kt")
            }
        }
    

    lint检测

    lint检测流程图

    image

    lint的检测功能的编写因为缺乏文案,只能通过阅读源码来获取,这里主要是有两个task,一个是IncrementLintGlobalTask,这个是会检测项目中所有变体的task,另一个就是IncrementLintPerVariantTask,会根据项目中不同的变体生成不同的检测task,检测范围也局限于相应变体。

    默认的linttask是对全部文件进行检测的,为了实现对增量文件的检测,需要重写LintGradleClient的createLintRequest方法,为此我写了一个IncrementLintGradleClient

    class IncrementLintGradleClient(
        version: String,
        issueRegistry: IssueRegistry,
        lintFlags: LintCliFlags,
        gradleProject: org.gradle.api.Project,
        sdkHome: File?,
        variant: Variant?,
        variantInputs: VariantInputs?,
        buildToolInfo: BuildToolInfo?,
        isAndroid: Boolean
    ) : LintGradleClient(
        version,
        issueRegistry,
        lintFlags,
        gradleProject,
        sdkHome,
        variant,
        variantInputs,
        buildToolInfo,
        isAndroid
    ) {
    
        override fun createLintRequest(files: MutableList<File>?): LintRequest {
            val lintRequest = super.createLintRequest(files)
            val commitFiles = GitUtil.getCommitFiles()
            lintRequest.getProjects()?.forEach { project ->
    
                commitFiles.forEach {
                    project.addFile(it)
                }
            }
            return lintRequest
        }
    
    }
    

    lint的api经常变化,而且修改幅度还很大,为了屏蔽相应的lint api,gradle差异以及kotlin compiler的差异,我们使用IncrementReflectiveLintRunner来调用lint检测,这个和原项目插件中ReflectiveLintRunner没什么区别,只不过使用了我们自己的IncrementLintGradleExecution来分析增量文件,同时在插件中让当前项目使用lintclasspath引用插件

    private fun addLintClassPath(project: Project) {
            project.gradle.rootProject.configurations
            val classPathConfiguration = project.gradle.rootProject.buildscript.configurations.getByName("classpath")
            var hecatoncheiresDependency: Dependency? = null
            classPathConfiguration.dependencies.forEach {
                if (it.name.contains(Constants.HECATONCHEIRESEXTENSION_NAME)) {
                    hecatoncheiresDependency = it
                    return@forEach
                }
            }
            val lintConfiguration = project.configurations.getByName(LintBaseTask.LINT_CLASS_PATH)
            project.dependencies.add(
                lintConfiguration.name,
                hecatoncheiresDependency
            )
        }
    

    这样插件的jar包路径就能被IncrementReflectiveLintRunner获得,并且可以使用自己的classloader用来加载IncrementLintGradleExecution

    最后

    这个项目感觉算是一个较为完善的静态检测方法,使用方法与demo见下面链接

    项目地址

    相关文章

      网友评论

        本文标题:百臂巨人与塔尔塔罗斯

        本文链接:https://www.haomeiwen.com/subject/sloalctx.html