diff --git a/gradle/commonStaticAnalysis.gradle b/gradle/commonStaticAnalysis.gradle deleted file mode 100644 index f4c4a3c..0000000 --- a/gradle/commonStaticAnalysis.gradle +++ /dev/null @@ -1,379 +0,0 @@ -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' -} diff --git a/gradle/commonStaticAnalysis.gradle.kts b/gradle/commonStaticAnalysis.gradle.kts new file mode 100644 index 0000000..361248a --- /dev/null +++ b/gradle/commonStaticAnalysis.gradle.kts @@ -0,0 +1,360 @@ +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") +} diff --git a/gradle/commonStaticAnalysisLegacy.gradle b/gradle/commonStaticAnalysisLegacy.gradle new file mode 100644 index 0000000..93fbfdb --- /dev/null +++ b/gradle/commonStaticAnalysisLegacy.gradle @@ -0,0 +1,68 @@ +apply plugin: 'checkstyle' +apply plugin: 'cpd' +apply plugin: 'pmd' +apply plugin: 'io.gitlab.arturbosch.detekt' + +configurations { + pngtastic +} + +cpd { + skipLexicalErrors = true +} + +detekt { + input = files("${rootDir}") + config = files("$buildScriptsDir/kotlin/detekt-config.yml") + + // TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes") + filters = ".*src/test.*,.*/resources/.*,.*/tmp/.*,.*/build/.*,.*\\.kts" + + reports { + html { + enabled = true + destination = file("${project.buildDir}/reports/kotlin-detekt.html") + } + xml { + enabled = true + destination = file("${project.buildDir}/reports/kotlin-detekt.xml") + } + } +} + +ext.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 +} + +ext.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 +} diff --git a/gradle/staticAnalysis.gradle b/gradle/staticAnalysis.gradle deleted file mode 100644 index 4e08fe8..0000000 --- a/gradle/staticAnalysis.gradle +++ /dev/null @@ -1,120 +0,0 @@ -def getServerProjectSources -def getAndroidProjectSources - -apply from: "$buildScriptsDir/gradle/commonStaticAnalysis.gradle" - -gradle.projectsEvaluated { - - tasks.withType(JavaCompile) { - options.compilerArgs << - "-Xlint:cast" << - "-Xlint:divzero" << - "-Xlint:empty" << - "-Xlint:deprecation" << - "-Xlint:finally" << - "-Xlint:overrides" << - "-Xlint:path" << - "-Werror" - } - - def excludes = rootProject.extensions.findByName("staticAnalysisExcludes") - def checkstyleEnabled = rootProject.extensions.findByName("checkstyleEnabled") ?: false - def pmdEnabled = rootProject.extensions.findByName("pmdEnabled") ?: false - - def androidSources = getAndroidProjectSources(excludes) - def androidStaticAnalysisTasks = getStaticAnalysisTaskNames(true, androidSources, null, checkstyleEnabled, pmdEnabled) - def androidIdeaFormatTask = getIdeaFormatTask(true, androidSources) - - task staticAnalysisWithFormatting { - androidStaticAnalysisTasks.each { task -> - tasks.findByName(task).mustRunAfter(androidIdeaFormatTask) - } - dependsOn androidIdeaFormatTask - dependsOn androidStaticAnalysisTasks - doFirst { - generateReport(true) - } - } - - task staticAnalysis { - dependsOn androidStaticAnalysisTasks - doFirst { - generateReport(true) - } - } - - def serverStaticAnalysisTasks = getStaticAnalysisTaskNames(false, getServerProjectSources(excludes), null, checkstyleEnabled, pmdEnabled) - def serverIdeaFormatTask = getIdeaFormatTask(false, getServerProjectSources(excludes)) - - task serverStaticAnalysisWithFormatting { - serverStaticAnalysisTasks.each { task -> - tasks.findByName(task).mustRunAfter(serverIdeaFormatTask) - } - dependsOn serverIdeaFormatTask - dependsOn serverStaticAnalysisTasks - doFirst { - generateReport(false) - } - } - - task serverStaticAnalysis { - dependsOn serverStaticAnalysisTasks - doFirst { - generateReport(false) - } - } - - pluginManager.withPlugin('com.android.application') { - android.applicationVariants.all { variant -> - task("staticAnalysis${variant.name.capitalize()}") { - dependsOn getStaticAnalysisTaskNames(true, androidSources, variant, checkstyleEnabled, pmdEnabled) - doFirst { generateReport(true) } - } - } - } -} - -getServerProjectSources = { excludes -> - def sources = new ArrayList() - def sourcesDirectory = new File(project.projectDir.path, 'src') - - for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) { - def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java') - def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin') - - if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) { - sources.add(javaSourceDirectory.absolutePath) - } - if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) { - sources.add(kotlinSourceDirectory.absolutePath) - } - } - return sources -} - -getAndroidProjectSources = { excludes -> - def sources = new ArrayList() - for (def project : rootProject.subprojects) { - if (!project.subprojects.isEmpty() || (excludes != null && excludes.contains(project.path))) { - continue - } - - def sourcesDirectory = new File(project.projectDir.path, 'src') - if (!sourcesDirectory.exists() || !sourcesDirectory.isDirectory()) { - continue - } - - for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) { - def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java') - def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin') - - if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) { - sources.add(javaSourceDirectory.absolutePath) - } - if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) { - sources.add(kotlinSourceDirectory.absolutePath) - } - } - } - return sources -} diff --git a/gradle/staticAnalysis.gradle.kts b/gradle/staticAnalysis.gradle.kts new file mode 100644 index 0000000..1db1f39 --- /dev/null +++ b/gradle/staticAnalysis.gradle.kts @@ -0,0 +1,145 @@ +import com.android.build.gradle.AppExtension +import com.android.build.gradle.api.ApplicationVariant +import groovy.lang.Closure + +val buildScriptsDir: String by rootProject.extra +apply(from = "$buildScriptsDir/gradle/commonStaticAnalysis.gradle.kts") + +buildscript { + repositories { + google() + maven("https://plugins.gradle.org/m2/") + } + dependencies { + classpath("com.android.tools.build:gradle:3.4.2") + } +} + +val getStaticAnalysisTaskNames: (Boolean, List, ApplicationVariant?, Boolean, Boolean) -> List by extra +val getIdeaFormatTask: (isAndroidProject: Boolean, sources: List) -> Task by extra +val generateReport: (isAndroidProject: Boolean) -> Unit by extra + + + +//val getStaticAnalysisTaskNames: Closure> by extra +//val getIdeaFormatTask: Closure by extra +//val generateReport: Closure by extra + +gradle.projectsEvaluated { + tasks.withType(JavaCompile::class.java) { + options.compilerArgs = listOf( + "-Xlint:cast", + "-Xlint:divzero", + "-Xlint:empty", + "-Xlint:deprecation", + "-Xlint:finally", + "-Xlint:overrides", + "-Xlint:path", + "-Werror" + ) + } + + val excludes = rootProject.extensions.findByName("staticAnalysisExcludes") as List? + val checkstyleEnabled = rootProject.extensions.findByName("checkstyleEnabled") as? Boolean ?: false + val pmdEnabled = rootProject.extensions.findByName("pmdEnabled") as? Boolean ?: false + + val androidSources = getAndroidProjectSources(excludes) + val androidStaticAnalysisTasks = getStaticAnalysisTaskNames(true, androidSources, null, checkstyleEnabled, pmdEnabled) as List + val androidIdeaFormatTask = getIdeaFormatTask(true, androidSources) as Task + + task("staticAnalysisWithFormatting") { + androidStaticAnalysisTasks.forEach { task -> + tasks.findByName(task)?.mustRunAfter(androidIdeaFormatTask) + } + dependsOn(androidIdeaFormatTask) + dependsOn(androidStaticAnalysisTasks) + doFirst { + generateReport(true) + } + } + + task("staticAnalysis") { + dependsOn(androidStaticAnalysisTasks) + doFirst { + generateReport(true) + } + } + + val serverStaticAnalysisTasks = getStaticAnalysisTaskNames(false, getServerProjectSources(excludes), null, checkstyleEnabled, pmdEnabled) as List + val serverIdeaFormatTask = getIdeaFormatTask(false, getServerProjectSources(excludes)) as Task + + task("serverStaticAnalysisWithFormatting") { + serverStaticAnalysisTasks.forEach { task -> + tasks.findByName(task)?.mustRunAfter(serverIdeaFormatTask) + } + dependsOn(serverIdeaFormatTask) + dependsOn(serverStaticAnalysisTasks) + doFirst { + generateReport(false) + } + } + + task("serverStaticAnalysis") { + dependsOn(serverStaticAnalysisTasks) + doFirst { + generateReport(false) + } + } + + pluginManager.withPlugin("com.android.application") { + (rootProject.extensions.findByName("android") as AppExtension).applicationVariants.forEach { variant -> + task("staticAnalysis") { + val tasks = (getStaticAnalysisTaskNames(true, androidSources, variant, checkstyleEnabled, pmdEnabled) as List) + dependsOn(tasks) + doFirst { + generateReport(true) + } + } + } + } +} + +fun getServerProjectSources(excludes: List?): List { + val sources = ArrayList() + val sourcesDirectory = File(project.projectDir.path, "src") + + for (sourceFlavorDirectory in sourcesDirectory.listFiles().orEmpty()) { + val javaSourceDirectory = File(sourceFlavorDirectory.path, "java") + val kotlinSourceDirectory = File(sourceFlavorDirectory.path, "kotlin") + + if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory) { + sources.add(javaSourceDirectory.absolutePath) + } + if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory) { + sources.add(kotlinSourceDirectory.absolutePath) + } + } + return sources +} + +fun getAndroidProjectSources(excludes: List?): ArrayList { + val sources = ArrayList() + for (project in rootProject.subprojects) { + if (project.subprojects.isNotEmpty() || (excludes != null && excludes.contains(project.path))) { + continue + } + + val sourcesDirectory = File(project.projectDir.path, "src") + if (!sourcesDirectory.exists() || !sourcesDirectory.isDirectory) { + continue + } + + for (sourceFlavorDirectory in sourcesDirectory.listFiles().orEmpty()) { + val javaSourceDirectory = File(sourceFlavorDirectory.path, "java") + val kotlinSourceDirectory = File(sourceFlavorDirectory.path, "kotlin") + + if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory) { + sources.add(javaSourceDirectory.absolutePath) + } + if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory) { + sources.add(kotlinSourceDirectory.absolutePath) + } + } + } + return sources +} diff --git a/gradle/stringGenerator.gradle b/gradle/stringGenerator.gradle deleted file mode 100644 index a05b155..0000000 --- a/gradle/stringGenerator.gradle +++ /dev/null @@ -1,92 +0,0 @@ -import groovy.json.JsonSlurper -import groovy.xml.MarkupBuilder - -task stringGenerator { - generate(android.languageMap) - println("Strings generated!") -} - -private def generate(Map sources) { - if (sources == null || sources.isEmpty()) { - throw new IOException("languageMap can't be null or empty") - } - Map jsonMap = getJsonsMap(sources) - def diffs = calcDiffs(jsonMap) - if (!diffs.isEmpty()) { - printDiffs(diffs) - throw new IllegalStateException("Strings source can't be different") - } - def defaultLang = getDefaultLangKey(sources) - jsonMap.forEach { key, json -> - - def sw = new StringWriter() - def xml = new MarkupBuilder(sw) - - xml.setDoubleQuotes(true) - xml.mkp.xmlDeclaration(version: "1.0", encoding: "utf-8") - xml.resources() { - json.each { - k, v -> - string(name: "${k}", formatted: "false", "${v}".replace('\n', '\\n')) - } - } - - def stringsFile = getFile(key, key == defaultLang) - stringsFile.write(sw.toString(), "UTF-8") - } -} - -private printDiffs(Map diffs) { - def diffLog = new StringBuilder() - diffs.forEach { k, v -> - if (v.size() > 0) { - diffLog.append("For $k was missed string keys: ${v.size()}\n") - v.forEach { - diffLog.append("\tString key: $it\n") - } - } - } - println(diffLog.toString()) -} - -private static def calcDiffs(Map jsonsMap) { - if (jsonsMap.size() == 1) { - return [:] - } - def keys = jsonsMap.collectEntries { - [(it.key): (it.value).keySet() as List] - } - def inclusive = keys.get(keys.keySet().first()) - def diffs = keys.collectEntries { - [(it.key): inclusive - it.value.intersect(inclusive)] - }.findAll { it.value.size() > 0 } - return diffs -} - -private static Map getJsonsMap(Map sources) { - return sources.collectEntries { - [(it.key): new JsonSlurper().parseText(new File(it.value).text)] - } -} - -private static File getFile(String key, boolean defaultLang) { - if (defaultLang) { - return new File("app/src/main/res/values/strings.xml") - } else { - def directory = new File("app/src/main/res/values-$key") - if (!directory.exists()) { - directory.mkdir() - } - return new File("app/src/main/res/values-$key/strings.xml") - } -} - -private static String getDefaultLangKey(Map sources) { - def defaultLanguage = sources.find { it.value.contains("default") } - if (defaultLanguage != null) { - return defaultLanguage.key - } else { - throw new IOException("Can't find default language") - } - -} diff --git a/gradle/stringGenerator.gradle.kts b/gradle/stringGenerator.gradle.kts new file mode 100644 index 0000000..6ff6221 --- /dev/null +++ b/gradle/stringGenerator.gradle.kts @@ -0,0 +1,105 @@ +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import org.redundent.kotlin.xml.PrintOptions +import org.redundent.kotlin.xml.xml + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath("org.redundent:kotlin-xml-builder:1.5.1") + classpath("com.google.code.gson:gson:2.8.5") + } +} + +task("stringGenerator") { + val languageMap: Map? by rootProject.extra + generate(languageMap) + println("Strings generated!") +} + +fun generate(sources: Map?) { + if (sources == null || sources.isEmpty()) { + throw java.io.IOException("languageMap can't be null or empty") + } + val jsonMap = getJsonsMap(sources) + val diffs = calcDiffs(jsonMap) + if (diffs.isNotEmpty()) { + printDiffs(diffs) + throw IllegalStateException("Strings source can't be different") + } + val defaultLang = getDefaultLangKey(sources) + jsonMap.forEach { (key, json) -> + val xmlString = xml("resources") { + includeXmlProlog = true + json.entrySet().forEach { + "string" { + attribute("name", it.key) + attribute("formatted", "false") + -it.value.asString.replace("\n", "\\n") + } + } + } + + val stringsFile = getFile(key, key == defaultLang) + stringsFile.writeText(xmlString.toString(PrintOptions(singleLineTextElements = true))) + } +} + +fun printDiffs(diffs: Map>) { + val diffLog = StringBuilder() + diffs.forEach { (key, value) -> + if (value.isNotEmpty()) { + diffLog.append("For $key was missed string keys: ${value.size}\n") + value.forEach { + diffLog.append("\tString key: $it\n") + } + } + } + println(diffLog.toString()) +} + +fun calcDiffs(jsonsMap: Map): Map> { + if (jsonsMap.size == 1) { + return emptyMap() + } + val keys = jsonsMap.mapValues { + it.value.keySet().toList() + } + val inclusive = keys[keys.keys.first()]!!.toSet() + return keys.mapValues { + inclusive - it.value.intersect(inclusive) + }.filter { it.value.isNotEmpty() } +} + +fun getJsonsMap(sources: Map): Map { + return sources.mapValues { + JsonParser().parse(File(it.value).readText()).asJsonObject + } +} + +fun getFile(key: String, defaultLang: Boolean): File { + if (defaultLang) { + return File("app/src/main/res/values/strings.xml") + } else { + val directory = File("app/src/main/res/values-$key") + if (!directory.exists()) { + directory.mkdir() + } + return File("app/src/main/res/values-$key/strings.xml") + } +} + +fun getDefaultLangKey(sources: Map): String { + val defaultLanguage = sources + .filter { it.value.contains("default") } + .iterator().run { + if (hasNext()) next() else null + } + if (defaultLanguage != null) { + return defaultLanguage.key + } else { + throw java.io.IOException("Can't find default language") + } +}