diff --git a/gradle/plugins/src/main/java/static_analysis/linters/DetektLinter.kt b/gradle/plugins/src/main/java/static_analysis/linters/DetektLinter.kt index efb1ea8..ccc6d64 100644 --- a/gradle/plugins/src/main/java/static_analysis/linters/DetektLinter.kt +++ b/gradle/plugins/src/main/java/static_analysis/linters/DetektLinter.kt @@ -2,15 +2,24 @@ package static_analysis.linters import io.gitlab.arturbosch.detekt.Detekt import org.gradle.api.Project +import org.gradle.api.file.FileTree import static_analysis.errors.DetektError import static_analysis.errors.StaticAnalysisError import static_analysis.plugins.StaticAnalysisExtension import static_analysis.utils.getSources +import static_analysis.utils.runCommand import static_analysis.utils.typedChildren import static_analysis.utils.xmlParser +import java.io.File class DetektLinter : Linter { + private companion object { + const val TAG = "DetektLinter" + const val ONLY_DIFFS_FLAG = "only-diffs" + const val GET_GIT_DIFFS_COMMAND = "git diff --name-only --ignore-submodules" + } + override val name: String = "Detekt" override fun getErrors(project: Project): List = xmlParser(project.getDetektReportFile()) @@ -50,11 +59,39 @@ class DetektLinter : Linter { } } - source = getSources(extension.excludes) + val diffsBranch = properties[ONLY_DIFFS_FLAG] as? String + source = getSources(extension.excludes, diffsBranch, project) } } } + private fun getSources(excludes: String, diffsBranch: String?, project: Project): FileTree = when (diffsBranch) { + null -> project.getSources(excludes) + else -> getGitDiffFiles(excludes, diffsBranch, project) + } + + private fun getGitDiffFiles(excludes: String, diffsBranch: String, project: Project): FileTree { + val getGitDiffsCommand = if (diffsBranch.isEmpty()) { + GET_GIT_DIFFS_COMMAND + } else { + GET_GIT_DIFFS_COMMAND.plus(" --merge-base $diffsBranch") + } + + val gitDiffs = getGitDiffsCommand.runCommand() + + if (gitDiffs.isNullOrEmpty()) { + project.logger.error("$TAG: Diffs are empty or specified branch or commit does not exists") + return project.files().asFileTree + } + + val diffFiles = gitDiffs.lines() + .map { File(it) } + .filter { (it.extension == "kt" || it.extension == "java") && !excludes.contains(it.path) } + .toList() + + return project.files(diffFiles).asFileTree + } + override fun getTaskNames(project: Project, buildType: String?): List = listOf(":detekt") private fun Project.getDetektReportFile() = file("${rootProject.buildDir}/reports/detekt.xml") diff --git a/gradle/plugins/src/main/java/static_analysis/plugins/StaticAnalysisAndroidPlugin.kt b/gradle/plugins/src/main/java/static_analysis/plugins/StaticAnalysisAndroidPlugin.kt index 7b7b4ee..98c32b9 100644 --- a/gradle/plugins/src/main/java/static_analysis/plugins/StaticAnalysisAndroidPlugin.kt +++ b/gradle/plugins/src/main/java/static_analysis/plugins/StaticAnalysisAndroidPlugin.kt @@ -27,6 +27,23 @@ class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() { buildVariant = applicationVariants.first { it.name.contains("Debug") }.name ) } + /** + * Task to run detekt checks. + * + * @param --only-diffs if specified, only files modified + * relative to this branch or commit will be checked. If specified without value + * then current uncommited changes will be checked. If not specified all source files will be checked. + * @see DetektLinter.getGitDiffFiles, 'git diff' for more info. + * */ + project.tasks.register("detektAnalysis") { + val detektLinter = linters.find { it is DetektLinter } + ?: throw IllegalStateException("DetektLinter not found") + + setupStaticAnalysisTask( + linters = listOf(detektLinter), + buildVariant = applicationVariants.first { it.name.contains("Debug") }.name + ) + } } } } diff --git a/gradle/plugins/src/main/java/static_analysis/utils/String.kt b/gradle/plugins/src/main/java/static_analysis/utils/String.kt new file mode 100644 index 0000000..a2077ba --- /dev/null +++ b/gradle/plugins/src/main/java/static_analysis/utils/String.kt @@ -0,0 +1,25 @@ +package static_analysis.utils + +import java.io.File +import java.io.IOException +import java.util.concurrent.TimeUnit + +fun String.runCommand( + directoryToExecute: File? = null, + timeoutSec: Long = 30, +): String? { + return try { + val parts = this.split("\\s".toRegex()) + val process = ProcessBuilder(*parts.toTypedArray()) + .directory(directoryToExecute) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + process.waitFor(timeoutSec, TimeUnit.SECONDS) + process.inputStream.bufferedReader().readText() + } catch(e: IOException) { + e.printStackTrace() + null + } +}