百臂巨人與塔爾塔羅斯

前言

一款靜態代碼檢測工具,包含阿里java規約檢測和lint檢測,支持自定義pmd和lint配置,結合git在代碼提交時進行增量檢測插件,赫卡同克瑞斯,也就是百臂巨人,來自希臘神話,是天空神烏拉諾斯和地神蓋婭的兒子,擁有50頭和100個手臂,在幫助宙斯奪得神位後,成爲了塔爾塔羅斯的守門人,塔爾塔羅斯就是希臘神話中的地獄。html

爲何要寫百臂巨人

不少時候有些bug就是由於代碼不規範形成的,這種低級錯誤每每會形成重大損失,以前就曾經碰到過在主線程加載圖片的狀況,以前由於運營配置的圖片較小,因此也就沒什麼事,直到有一天運營配置了一個大圖,直接致使大面積的ANR,這是一個低級錯誤,後來我寫了一個自定義lint檢測工具,用來檢測這種在主線程使用BitmapFactory的狀況。以後又寫了一些其餘的lint檢測規則,以後遇到了另外兩個問題,第一,個人lint檢測出來了,標註出來了,可是開發者依舊會對他進行忽視,檢測出來而不修改那不就是白搞了?如何強制開發者進行檢測,這是一個問題。第二,項目龐大,總體檢測時間很長,並且檢測出的問題都上萬了,這些問題誰來改?陳芝麻爛穀子的代碼,又有誰願意改?爲此我須要一個增量檢測的方案,結合git,只對要提交的修改的文件進行檢測,這樣誰修改誰倒黴,聽天由命,避免抱怨。java

原理圖

核心代碼

獲取git修改文件

增量檢測,必然要相應的文件,這裏利用了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
    }
複製代碼

除了刪除的文件,其餘文件都會做爲增量文件提取出來github

阿里規約檢測的接入

阿里對Java代碼規範作了詳盡的說明,這裏咱們利用阿里規約中的檢測插件中的檢測工具進行java文件的檢測 首先爲項目添加p3c依賴api

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,支持相應的配置bash

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檢測流程圖app

lint的檢測功能的編寫由於缺少文案,只能經過閱讀源碼來獲取,這裏主要是有兩個task,一個是IncrementLintGlobalTask,這個是會檢測項目中全部變體的task,另外一個就是IncrementLintPerVariantTask,會根據項目中不一樣的變體生成不一樣的檢測task,檢測範圍也侷限於相應變體。

默認的linttask是對所有文件進行檢測的,爲了實現對增量文件的檢測,須要重寫LintGradleClient的createLintRequest方法,爲此我寫了一個IncrementLintGradleClientide

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用來加載IncrementLintGradleExecutiongradle

最後

這個項目感受算是一個較爲完善的靜態檢測方法,使用方法與demo見下面連接

項目地址

相關文章
相關標籤/搜索