一款静态代码检测工具,包含阿里java规约检测和lint检测,支持自定义pmd和lint配置,结合git在代码提交时进行增量检测插件,赫卡同克瑞斯,也就是百臂巨人,来自希腊神话,是天空神乌拉诺斯和地神盖娅的儿子,拥有50头和100个手臂,在帮助宙斯夺得神位后,成为了塔尔塔罗斯的守门人,塔尔塔罗斯就是希腊神话中的地狱。
很多时候有些bug就是因为代码不规范造成的,这种低级错误往往会造成重大损失,之前就曾经碰到过在主线程加载图片的情况,之前因为运营配置的图片较小,所以也就没什么事,直到有一天运营配置了一个大图,直接导致大面积的ANR,这是一个低级错误,后来我写了一个自定义lint检测工具,用来检测这种在主线程使用BitmapFactory的情况。之后又写了一些其他的lint检测规则,之后遇到了另外两个问题,第一,我的lint检测出来了,标注出来了,但是开发者依旧会对他进行忽视,检测出来而不修改那不就是白搞了?如何强制开发者进行检测,这是一个问题。第二,项目庞大,整体检测时间很长,而且检测出的问题都上万了,这些问题谁来改?陈芝麻烂谷子的代码,又有谁愿意改?为此我需要一个增量检测的方案,结合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的检测功能的编写因为缺乏文案,只能通过阅读源码来获取,这里主要是有两个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