apply plugin: 'checkstyle' apply plugin: 'cpd' apply plugin: 'pmd' apply plugin: 'io.gitlab.arturbosch.detekt' def getCpdTask def getPmdTask def getCheckstyleTask def getLintTask def getKotlinDetektTask def appendError def appendCpdErrors def appendKotlinErrors def appendCheckstyleErrors def appendPmdErrors def appendLintErrors repositories { maven { url "http://dl.bintray.com/touchin/touchin-tools" } } configurations { pngtastic } cpd { skipLexicalErrors = true } detekt { config = files("$buildScriptsDir/kotlin/detekt-config.yml") parallel = true reports { html { enabled = true destination = file("${project.buildDir}/reports/kotlin-detekt.html") } xml { enabled = true destination = file("${project.buildDir}/reports/kotlin-detekt.xml") } } } import org.apache.tools.ant.taskdefs.condition.Os ext.getIdeaFormatTask = { isAndroidProject, sources -> def ideaPath = System.getenv("IDEA_HOME") if (ideaPath == null) { return tasks.create((isAndroidProject ? "android" : "server") + "donothing") } return tasks.create((isAndroidProject ? "android" : "server") + "IdeaFormat_$project.name", Exec) { def inspectionPath def params = ["-r", "-mask", "*.java,*.kt,*.xml"] for (String source : sources) { params.add(source) } if (Os.isFamily(Os.FAMILY_WINDOWS)) { inspectionPath = ['cmd', '/c', "\"${ideaPath}\\bin\\format.bat\" ${params.join(" ")}"] } else { inspectionPath = ["$ideaPath/bin/format.sh"] } commandLine inspectionPath if (!Os.isFamily(Os.FAMILY_WINDOWS)) { args = params } } } ext.getStaticAnalysisTaskNames = { isAndroidProject, sources, buildVariant, checkstyleEnabled, pmdEnabled -> def tasksNames = new 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()) } return tasksNames } ext.generateReport = { isAndroidProject -> StringBuilder consoleReport = new StringBuilder() consoleReport.append("STATIC ANALYSIS RESULTS:") def count = 0 def previousCount = count count = appendCpdErrors(count, new 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, new 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) { def checkstyleFile = new 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") } } def pmdFile = new 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, new 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 new Exception(consoleReport.toString()) } else { consoleReport.append("\nOverall: PASSED") println(consoleReport.toString()) } } appendError = { number, analyzer, file, line, errorId, errorLink, description -> println("$number. $analyzer : $description ($errorId)\n\tat $file: $line") } appendKotlinErrors = { count, checkstyleFile -> def rootNode = new XmlParser().parse(checkstyleFile) for (def fileNode : rootNode.children()) { if (!fileNode.name().equals("file")) { continue } for (def errorNode : fileNode.children()) { if (!errorNode.name().equals("error")) { continue } count++ appendError(count, "Detekt", fileNode.attribute("name"), errorNode.attribute("line"), errorNode.attribute("source"), "", errorNode.attribute("message")) } } return count } appendCpdErrors = { count, cpdFile -> def rootNode = new XmlParser().parse(cpdFile) for (def duplicationNode : rootNode.children()) { if (!duplicationNode.name().equals("duplication")) { continue } count++ def duplicationIndex = 0 String duplicationPoints = "" for (def filePointNode : duplicationNode.children()) { if (filePointNode.name().equals("file")) { def file = filePointNode.attribute("path") def line = filePointNode.attribute("line") duplicationPoints += "\n " + file + ":" + line duplicationIndex++ } } println("$count CPD: code duplication $duplicationPoints") } return count } appendCheckstyleErrors = { count, checkstyleFile -> def rootNode = new XmlParser().parse(checkstyleFile) for (def fileNode : rootNode.children()) { if (!fileNode.name().equals("file")) { continue } for (def errorNode : fileNode.children()) { if (!errorNode.name().equals("error")) { continue } count++ def error = errorNode.attribute("source") def link = "http://checkstyle.sourceforge.net/apidocs/" + error.replace('.', '/') + ".html" appendError(count, "Checkstyle", fileNode.attribute("name"), errorNode.attribute("line"), error, link, errorNode.attribute("message")) } } return count } appendPmdErrors = { count, pmdFile -> def rootNode = new XmlParser().parse(pmdFile) for (def fileNode : rootNode.children()) { if (!fileNode.name().equals("file")) { continue } for (def errorNode : fileNode.children()) { if (!errorNode.name().equals("violation")) { continue } count++ appendError(count, "PMD", fileNode.attribute("name"), errorNode.attribute("beginline"), errorNode.attribute("rule").trim(), errorNode.attribute("externalInfoUrl").trim(), errorNode.text().trim()) } } return count } appendLintErrors = { count, lintFile -> def rootNode = new XmlParser().parse(lintFile) for (def issueNode : rootNode.children()) { if (!issueNode.name().equals("issue") || !issueNode.attribute("severity").equals("Error")) { continue } for (def locationNode : issueNode.children()) { if (!locationNode.name().equals("location")) { continue } count++ appendError(count, "Lint", locationNode.attribute("file"), locationNode.attribute("line"), issueNode.attribute("id"), issueNode.attribute("explanation"), issueNode.attribute("message")) } } return count } getCpdTask = { isAndroidProject, sources -> def taskName = (isAndroidProject ? "android" : "server") + "cpd_${project.name}" def task = tasks.findByName(taskName) if (task == null) { task = tasks.create(taskName, tasks.findByName('cpdCheck').getClass().getSuperclass()) { minimumTokenCount = 60 source = files(sources) ignoreFailures = true reports { xml { enabled = true destination = file("${project.buildDir}/reports/cpd.xml") } } } } return task.name } getPmdTask = { sources -> def taskName = "pmd_${project.name}" def task = tasks.findByName(taskName) if (task == null) { task = tasks.create(taskName, Pmd) { pmdClasspath = configurations.pmd.asFileTree ruleSetFiles = files "$buildScriptsDir/pmd/rulesets/java/android.xml" ruleSets = [] source files(sources) ignoreFailures = true reports { html { enabled = true destination file("${project.buildDir}/reports/pmd.html") } xml { enabled = true destination file("${project.buildDir}/reports/pmd.xml") } } } } return task.name } getLintTask = { buildVariant -> def lintTaskName if (buildVariant != null) { lintTaskName = "lint${buildVariant.name.capitalize()}" } else { def lintDebugTask = tasks.matching { it.getName().contains("lint") && it.getName().contains("Debug") }.first() lintTaskName = lintDebugTask.getName() } android.lintOptions.abortOnError = false android.lintOptions.checkAllWarnings = true android.lintOptions.warningsAsErrors = false android.lintOptions.xmlReport = true android.lintOptions.xmlOutput = file "$project.buildDir/reports/lint_report.xml" android.lintOptions.htmlReport = false android.lintOptions.lintConfig = file "$buildScriptsDir/lint/lint.xml" return lintTaskName } getCheckstyleTask = { sources -> def taskName = "checkstyle_$project.name" def task = tasks.findByName(taskName) if (task == null) { def compileReleaseTask = tasks.matching { it.getName().contains("compile") && it.getName().contains("Release") && it.getName().contains("Java") && !it.getName().contains("UnitTest") }.last() task = tasks.create(taskName, Checkstyle) { ignoreFailures = true showViolations = false source files(sources) configFile file("$buildScriptsDir/checkstyle/configuration/touchin_checkstyle.xml") checkstyleClasspath = configurations.checkstyle.asFileTree classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + android.compileSdkVersion + "/android.jar") + files(System.properties.'java.home' + "/lib/rt.jar") + compileReleaseTask.classpath reports { xml { enabled = true destination file("${project.buildDir}/reports/checkstyle.xml") } } } } return task.name } getKotlinDetektTask = { // TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes") "detekt" } task optimizePng { doFirst { def jarArgs = new ArrayList() jarArgs.add(configurations.pngtastic.asPath) def relatedPathIndex = "${rootDir}".length() + 1 for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/drawable**/*.png')) { jarArgs.add(file.absolutePath.substring(relatedPathIndex)) } for (def file : fileTree(dir: "${rootDir}", include: '**/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' pmd 'net.sourceforge.pmd:pmd-java:5.5.3' checkstyle 'ru.touchin:checkstyle:7.6.2-fork' pngtastic 'com.github.depsypher:pngtastic:1.2' }