BuildScripts/gradle/commonStaticAnalysis.gradle

448 lines
16 KiB
Groovy

apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'cpd'
def getCpdTask
def getPmdTask
def getCheckstyleTask
def getLintTasks
def getKotlinDetektTask
def appendError
def appendCpdErrors
def appendKotlinErrors
def appendCheckstyleErrors
def appendPmdErrors
def appendLintErrors
def normalizeFileUrl
repositories {
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
jcenter()
}
configurations {
pngtastic
detekt
}
cpd {
skipLexicalErrors = true
}
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") + "do nothing")
}
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 ->
def tasksNames = new ArrayList<String>()
try {
tasksNames.add(getCpdTask(isAndroidProject, sources))
tasksNames.add(getKotlinDetektTask(isAndroidProject))
if (isAndroidProject) {
tasksNames.add(getCheckstyleTask(sources))
tasksNames.add(getPmdTask(sources))
tasksNames.addAll(getLintTasks())
}
} catch (Exception exception) {
println(exception.toString())
}
return tasksNames
}
ext.generateHtmlReport = { isAndroidProject ->
StringBuilder fullReport = new StringBuilder()
fullReport.append("<table cellpadding='10px' border='2px' cellspacing='0px' cols='4'>");
StringBuilder consoleReport = new StringBuilder()
consoleReport.append("STATIC ANALYSIS RESULTS:")
def count = 0
def previousCount = count
count = appendCpdErrors(fullReport, count, new File("${project.buildDir}/reports/cpd.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCPD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/cpd.xml"))
} else {
consoleReport.append("\n\u001B[32mCPD: PASSED\u001B[0m")
}
previousCount = count
count = appendKotlinErrors(fullReport, count, new File("${project.buildDir}/reports/kotlin-detekt.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mKotlin-detekt: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/kotlin-detekt.xml"))
} else {
consoleReport.append("\n\u001B[32mKotlin-detekt: PASSED\u001B[0m")
}
if (isAndroidProject) {
previousCount = count
count = appendPmdErrors(fullReport, count, new File("${project.buildDir}/reports/pmd.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mPMD: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/pmd.html"))
} else {
consoleReport.append("\n\u001B[32mPMD: PASSED\u001B[0m")
}
previousCount = count
count = appendLintErrors(fullReport, count, new File("${project.buildDir}/reports/lint_report.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mLint: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/lint.html"))
} else {
consoleReport.append("\n\u001B[32mLint: PASSED\u001B[0m")
}
previousCount = count
count = appendCheckstyleErrors(fullReport, count, new File("${project.buildDir}/reports/checkstyle.xml"))
if (count - previousCount > 0) {
consoleReport.append("\n\u001B[31mCheckstyle: FAILED (" + (count - previousCount) + " errors)\u001B[0m " +
normalizeFileUrl("file://${project.buildDir}/reports/checkstyle.xml"))
} else {
consoleReport.append("\n\u001B[32mCheckstyle: PASSED\u001B[0m")
}
}
fullReport.append("\n</table>\n");
fullReport.append("<script>\n" +
"\tfunction visitPage(file, line) {\n" +
"\t\tfor (port = 63330; port < 63340; port++) {\n" +
"\t\t\tvar theUrl='http://127.0.0.1:' + port + '/file?file=' + file + '&line=' + line;\n" +
"\t\t\tvar xmlHttp = new XMLHttpRequest();\n" +
"\t\t\txmlHttp.open('GET', theUrl, true);\n" +
"\t\t\txmlHttp.send(null);\n" +
"\t\t}\n" +
"\t}\n" +
"</script>")
File fullReportFile = new File("${project.buildDir}/reports/full_report.html")
fullReportFile.write(fullReport.toString());
if (count > 0) {
consoleReport.append("\n\u001B[31mOverall: FAILED (" + count + " errors)\u001B[0m "
+ normalizeFileUrl("file://${project.buildDir}/reports/full_report.html"))
throw new Exception(consoleReport.toString())
} else {
consoleReport.append("\n\u001B[32mOverall: PASSED\u001B[0m")
println(consoleReport.toString())
}
}
normalizeFileUrl = { url ->
return url.replace("\\", "/")
}
appendError = { report, number, analyzer, file, line, errorId, errorLink, description ->
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + number + "</td>")
report.append("\n\t\t<td>" + analyzer + "</td>")
report.append("\n\t\t<td>")
if (analyzer == "Lint") {
report.append("\n\t\t\t<a href='javascript:alert(\"" + errorLink.replace("'", "&apos;") + "\")'>" + description + " (" + errorId + ")</a>")
} else if (analyzer == "Detekt") {
report.append("\n\t\t\t" + description + " (" + errorId + ")")
} else {
report.append("\n\t\t\t<a target='_blank' href='" + errorLink + "'>" + description + " (" + errorId + ")</a>")
}
report.append("\n\t\t</td>")
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
report.append("\n\t\t<td>")
report.append("\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>")
report.append("\n\t\t</td>")
report.append("\n\t</tr>")
println("\n\u001B[31m" + number + " " + analyzer + ": " + description + " (" + errorId + ")\n\tat " + file + ":" + line + "\u001B[0m")
}
appendKotlinErrors = { report, 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")
appendError(report, count, "Detekt", fileNode.attribute("name"), errorNode.attribute("line"), error, "", errorNode.attribute("message"))
}
}
return count
}
appendCpdErrors = { report, count, cpdFile ->
def rootNode = new XmlParser().parse(cpdFile)
for (def duplicationNode : rootNode.children()) {
if (!duplicationNode.name().equals("duplication")) {
continue
}
count++
report.append("\n\t<tr>")
report.append("\n\t\t<td>" + count + "</td>")
report.append("\n\t\t<td>CPD</td>")
def fragment = "<b>Code duplication:</b></br></br>"
def links = ""
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");
def indexOfSrc = file.indexOf("src")
def deeplink = (indexOfSrc > 0 ? file.substring(indexOfSrc) : file).replace('\\', '/')
if (duplicationIndex > 0) {
links += "\n\t\t\t</br></br>"
}
links += "Code fragment " + (duplicationIndex + 1) + "</br>"
links += "\n\t\t\t<a href='javascript:visitPage(\"" + deeplink + "\", " + line + ")'>" + file + ":" + line + "</a>"
duplicationPoints += "\n\tat " + file + ":" + line
duplicationIndex++
} else if (filePointNode.name().equals("codefragment")) {
fragment += filePointNode.text().replace("\n", "</br>").replace(" ", "&nbsp;")
}
}
report.append("\n\t\t<td>" + fragment + "\n\t\t</td>")
report.append("\n\t\t<td>" + links + "\n\t\t</td>")
report.append("\n\t</tr>")
println("\u001B[31m" + count + " CPD: code duplication" + duplicationPoints + "\u001B[0m")
}
return count
}
appendCheckstyleErrors = { report, 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(report, count, "Checkstyle", fileNode.attribute("name"), errorNode.attribute("line"), error, link, errorNode.attribute("message"))
}
}
return count
}
appendPmdErrors = { report, 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(report, count, "PMD", fileNode.attribute("name"), errorNode.attribute("beginline"),
errorNode.attribute("rule").trim(), errorNode.attribute("externalInfoUrl").trim(), errorNode.text().trim())
}
}
return count
}
appendLintErrors = { report, 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(report, 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}"
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 taskName
}
getPmdTask = { sources ->
def taskName = "pmd_${project.name}"
tasks.create(taskName, Pmd) {
pmdClasspath = configurations.pmd.asFileTree
ruleSetFiles = files("${rootDir}/libraries/BuildScripts/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 taskName
}
getLintTasks = {
def lintTaskNames = new ArrayList<String>()
def lintReleaseTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Release")
}.first()
//TODO return on jack lintReleaseTask.dependsOn.clear()
lintTaskNames.add(lintReleaseTask.getName())
def lintDebugTask = tasks.matching {
it.getName().contains("lint") && it.getName().contains("Debug")
}.first()
//TODO return on jack lintDebugTask.dependsOn.clear()
lintTaskNames.add(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 = true
android.lintOptions.htmlOutput = file("${project.buildDir}/reports/lint_report.html")
android.lintOptions.lintConfig = file("${rootDir}/libraries/BuildScripts/lint/lint.xml")
return lintTaskNames
}
getCheckstyleTask = { sources ->
def taskName = "checkstyle_${project.name}"
def compileReleaseTask = tasks.matching {
it.getName().contains("compile") && it.getName().contains("Release") && it.getName().contains("Java") && !it.getName().contains("UnitTest")
}.last()
tasks.create(taskName, Checkstyle) {
ignoreFailures = true
showViolations = false
source files(sources)
configFile file("${rootDir}/libraries/BuildScripts/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 taskName
}
getKotlinDetektTask = { isAndroidProject ->
def taskName = (isAndroidProject ? "android" : "server") + "detektCheck_${project.name}"
tasks.create(taskName, JavaExec) {
main = "io.gitlab.arturbosch.detekt.cli.Main"
classpath = configurations.detekt
def input = "${rootDir}"
def output = "${project.buildDir}/reports"
def outputName = "kotlin-detekt"
def config = "${rootDir}/libraries/BuildScripts/kotlin/detekt-config.yml"
// TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes")
def filters = ".*src/test.*,.*/resources/.*,.*/tmp/.*"
def params = ['-i', input,
'-o', output,
'-c', config,
'-f', filters,
'--output-name', outputName]
args(params)
}
return taskName
}
task optimizePng {
doFirst {
def jarArgs = new ArrayList<String>()
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'
detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.0.RC6-2'
}