import com.android.build.gradle.AppExtension import com.android.build.gradle.api.ApplicationVariant import groovy.lang.Closure import groovy.util.Node import groovy.util.XmlParser import org.apache.tools.ant.taskdefs.condition.Os val buildScriptsDir: String by rootProject.extra apply(from = "$buildScriptsDir/gradle/commonStaticAnalysisLegacy.gradle") buildscript { repositories { jcenter() google() maven("https://plugins.gradle.org/m2/") maven("https://maven.fabric.io/public") } dependencies { classpath("org.redundent:kotlin-xml-builder:1.5.1") classpath("com.android.tools.build:gradle:3.4.2") classpath(kotlin("gradle-plugin", version = "1.3.41")) } } apply(plugin = "checkstyle") apply(plugin = "pmd") configurations { "pngtastic"() } extra["getIdeaFormatTask"] = { isAndroidProject: Boolean, sources: List -> val ideaPath = System.getenv("IDEA_HOME") if (ideaPath == null) { tasks.create((if (isAndroidProject) "android" else "server") + "donothing") } else { tasks.create((if (isAndroidProject) "android" else "server") + "IdeaFormat_${project.name}", Exec::class.java) { val params = mutableListOf("-r", "-mask", "*.java,*.kt,*.xml") for (source in sources) { params.add(source) } val inspectionPath = if (Os.isFamily(Os.FAMILY_WINDOWS)) { listOf("cmd", "/c", "\"$ideaPath\\bin\\format.bat\" ${params.joinToString(" ")}") } else { listOf("$ideaPath/bin/format.sh") } commandLine(inspectionPath) if (!Os.isFamily(Os.FAMILY_WINDOWS)) { setArgs(params as List) } } } } extra["getStaticAnalysisTaskNames"] = { isAndroidProject: Boolean, sources: List, buildVariant: ApplicationVariant?, checkstyleEnabled: Boolean, pmdEnabled: Boolean -> val tasksNames = ArrayList() try { tasksNames.add(getCpdTask(isAndroidProject, sources)) tasksNames.add(getKotlinDetektTask()) if (isAndroidProject) { if (checkstyleEnabled) { tasksNames.add(getCheckstyleTask(sources)) } if (pmdEnabled) { tasksNames.add(getPmdTask(sources)) } tasksNames.add(getLintTask(buildVariant)) } } catch (exception: Exception) { println(exception.toString()) } tasksNames } extra["generateReport"] = { isAndroidProject: Boolean -> val consoleReport = StringBuilder() consoleReport.append("STATIC ANALYSIS RESULTS:") var count = 0 var previousCount = count count = appendCpdErrors(count, File("${project.buildDir}/reports/cpd.xml")) if (count - previousCount > 0) { consoleReport.append("\nCPD: FAILED (" + (count - previousCount) + " errors)") } else { consoleReport.append("\nCPD: PASSED") } previousCount = count count = appendKotlinErrors(count, File("${project.buildDir}/reports/kotlin-detekt.xml")) if (count - previousCount > 0) { consoleReport.append("\nKotlin-detekt: FAILED (" + (count - previousCount) + " errors)") } else { consoleReport.append("\nKotlin-detekt: PASSED") } if (isAndroidProject) { val checkstyleFile = File("${project.buildDir}/reports/checkstyle.xml") if (checkstyleFile.exists()) { previousCount = count count = appendCheckstyleErrors(count, checkstyleFile) if (count - previousCount > 0) { consoleReport.append("\nCheckstyle: FAILED (" + (count - previousCount) + " errors)") } else { consoleReport.append("\nCheckstyle: PASSED") } } val pmdFile = File("${project.buildDir}/reports/pmd.xml") if (pmdFile.exists()) { previousCount = count count = appendPmdErrors(count, pmdFile) if (count - previousCount > 0) { consoleReport.append("\nPMD: FAILED (" + (count - previousCount) + " errors)") } else { consoleReport.append("\nPMD: PASSED") } } previousCount = count count = appendLintErrors(count, File("${project.buildDir}/reports/lint_report.xml")) if (count - previousCount > 0) { consoleReport.append("\nLint: FAILED (" + (count - previousCount) + " errors)") } else { consoleReport.append("\nLint: PASSED") } } if (count > 0) { consoleReport.append("\nOverall: FAILED (" + count + " errors)") throw Exception(consoleReport.toString()) } else { consoleReport.append("\nOverall: PASSED") println(consoleReport.toString()) } } fun appendError(number: Any, analyzer: String, file: Any?, line: Any?, errorId: Any?, errorLink: Any?, description: Any?): Unit { println("$number. $analyzer : $description ($errorId)\n\tat $file: $line") } fun appendKotlinErrors(count: Int, checkstyleFile: File): Int { var newNode = count val rootNode = XmlParser().parse(checkstyleFile) for (fileNode in (rootNode.children() as List)) { if (fileNode.name() != "file") { continue } for (errorNode in (fileNode.children() as List)) { if (errorNode.name() != "error") { continue } newNode++ appendError( newNode, "Detekt", fileNode.attribute("name"), errorNode.attribute("line"), errorNode.attribute("source"), "", errorNode.attribute("message") ) } } return newNode } fun appendCpdErrors(count: Int, cpdFile: File): Int { var newCount = count val rootNode = XmlParser().parse(cpdFile) for (duplicationNode in (rootNode.children() as List)) { if (duplicationNode.name() != "duplication") { continue } newCount++ var duplicationIndex = 0 var duplicationPoints = "" for (filePointNode in (duplicationNode.children() as List)) { if (filePointNode.name() == "file") { val file = filePointNode.attribute("path") val line = filePointNode.attribute("line") duplicationPoints += "\n $file:$line" duplicationIndex++ } } println("$newCount CPD: code duplication $duplicationPoints") } return newCount } fun appendCheckstyleErrors(count: Int, checkstyleFile: File): Int { var newCount = count val rootNode = XmlParser().parse(checkstyleFile) for (fileNode in (rootNode.children() as List)) { if (fileNode.name() != "file") { continue } for (errorNode in (fileNode.children() as List)) { if (!errorNode.name().equals("error")) { continue } newCount++ val error = errorNode.attribute("source")?.toString().orEmpty() val link = "http://checkstyle.sourceforge.net/apidocs/" + error.replace(".", "/") + ".html" appendError( newCount, "Checkstyle", fileNode.attribute("name"), errorNode.attribute("line"), error, link, errorNode.attribute("message") ) } } return newCount } fun appendPmdErrors(count: Int, pmdFile: File): Int { var newCount = count val rootNode = XmlParser().parse(pmdFile) for (fileNode in (rootNode.children() as List)) { if (fileNode.name() != "file") { continue } for (errorNode in (fileNode.children() as List)) { if (errorNode.name() != "violation") { continue } newCount++ appendError( newCount, "PMD", fileNode.attribute("name"), errorNode.attribute("beginline"), errorNode.attribute("rule")?.toString().orEmpty().trim(), errorNode.attribute("externalInfoUrl")?.toString().orEmpty().trim(), errorNode.text().orEmpty().trim() ) } } return newCount } fun appendLintErrors(count: Int, lintFile: File): Int { var newCount = count val rootNode = XmlParser().parse(lintFile) for (issueNode in rootNode.children()) { if ((issueNode as Node).name() != "issue" || issueNode.attribute("severity") != "Error") { continue } for (locationNode in (issueNode.children() as List)) { if (locationNode.name() != "location") { continue } newCount++ appendError( newCount, "Lint", locationNode.attribute("file"), locationNode.attribute("line"), issueNode.attribute("id"), issueNode.attribute("explanation"), issueNode.attribute("message") ) } } return newCount } fun getCpdTask(isAndroidProject: Boolean, sources: List): String = (extra["getCpdTask"] as Closure)(isAndroidProject, sources) fun getPmdTask(sources: List): String { val taskName = "pmd_${project.name}" var task = tasks.findByName(taskName) if (task == null) { task = tasks.create(taskName, Pmd::class.java) { pmdClasspath = configurations["pmd"].asFileTree ruleSetFiles = files("$buildScriptsDir/pmd/rulesets/java/android.xml") ruleSets = emptyList() source = files(sources).asFileTree ignoreFailures = true reports { html.isEnabled = true html.destination = file("${project.buildDir}/reports/pmd.html") xml.isEnabled = true xml.destination = file("${project.buildDir}/reports/pmd.xml") } } } return task!!.name } fun getLintTask(buildVariant: ApplicationVariant?): String = (extra["getLintTask"] as Closure)(buildVariant) fun getCheckstyleTask(sources: List): String { val taskName = "checkstyle_${project.name}" var task = tasks.findByName(taskName) if (task == null) { val compileReleaseTask = tasks.matching { it.name.contains("compile") && it.name.contains("Release") && it.name.contains("Java") && !it.name.contains("UnitTest") }.last() task = tasks.create(taskName, Checkstyle::class.java) { ignoreFailures = true isShowViolations = false source = files(sources).asFileTree configFile = file("$buildScriptsDir/checkstyle/configuration/touchin_checkstyle.xml") checkstyleClasspath = configurations["checkstyle"].asFileTree classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + (extensions["android"] as AppExtension).compileSdkVersion + "/android.jar") + files(System.getProperties()["java.home"].toString() + "/lib/rt.jar") + files((compileReleaseTask.extra["classpath"] as String)) reports { xml.isEnabled = true xml.destination = file("${project.buildDir}/reports/checkstyle.xml") } } } return task!!.name } fun getKotlinDetektTask() = "detekt" task("optimizePng") { doFirst { val jarArgs = ArrayList() jarArgs.add(configurations["pngtastic"].asPath) val relatedPathIndex = "$rootDir".length + 1 for (file in fileTree("dir" to "$rootDir", "include" to "**/src/**/res/drawable**/*.png")) { jarArgs.add(file.absolutePath.substring(relatedPathIndex)) } for (file in fileTree("dir" to "$rootDir", "include" to "**/src/**/res/mipmap**/*.png")) { jarArgs.add(file.absolutePath.substring(relatedPathIndex)) } javaexec { main = "-jar"; args = jarArgs; workingDir = file("${rootDir}") } } } dependencies { "pmd"("net.sourceforge.pmd:pmd-core:5.5.3") "checkstyle"("ru.touchin:checkstyle:7.6.2-fork") "pngtastic"("com.github.depsypher:pngtastic:1.2") }