Compare commits

...

8 Commits

7 changed files with 109 additions and 25 deletions

View File

@ -13,7 +13,7 @@ repositories {
dependencies { dependencies {
// android gradle plugin, required by custom plugin // android gradle plugin, required by custom plugin
implementation("com.android.tools.build:gradle:4.0.1") implementation("com.android.tools.build:gradle:7.1.3")
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.10.0") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.10.0")
implementation("de.aaschmid:gradle-cpd-plugin:3.1") implementation("de.aaschmid:gradle-cpd-plugin:3.1")

View File

@ -1,9 +1,7 @@
package static_analysis.linters package static_analysis.linters
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin import com.android.build.gradle.AppPlugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import static_analysis.errors.AndroidLintError import static_analysis.errors.AndroidLintError
import static_analysis.errors.StaticAnalysisError import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension import static_analysis.plugins.StaticAnalysisExtension
@ -33,22 +31,10 @@ class AndroidLinter : Linter {
.flatten() .flatten()
override fun setupForProject(project: Project, extension: StaticAnalysisExtension) { override fun setupForProject(project: Project, extension: StaticAnalysisExtension) {
project.beforeEvaluate { // Make sure to set lint options manually in modules gradle file
subprojects // Otherwise you will get java.io.FileNotFoundException
.mapNotNull { it.extensions.findByType<AppExtension>() }
.first() // See issue: https://github.com/TouchInstinct/BuildScripts/issues/310
.lintOptions.apply {
isAbortOnError = false
isCheckAllWarnings = true
isWarningsAsErrors = false
xmlReport = true
htmlReport = false
isCheckDependencies = true
disable("MissingConstraints", "VectorRaster")
xmlOutput = getLintReportFile()
lintConfig = file("${extension.buildScriptDir}/static_analysis_configs/lint.xml")
}
}
} }
override fun getTaskNames(project: Project, buildType: String?): List<String> { override fun getTaskNames(project: Project, buildType: String?): List<String> {
@ -62,11 +48,14 @@ class AndroidLinter : Linter {
.mapNotNull { subproject: Project -> .mapNotNull { subproject: Project ->
subproject subproject
.tasks .tasks
.find { task -> task.name.contains(buildType, ignoreCase = true) && task.name.contains("lint") } .filter { task ->
?.path task.name.equals("lint${buildType}", ignoreCase = true)
|| task.name.equals("copy${buildType}AndroidLintReports", ignoreCase = true)
}
.map { it.path }
} }
.flatten()
} }
private fun Project.getLintReportFile() = file("${rootProject.buildDir}/reports/lint-report.xml") private fun Project.getLintReportFile() = file("${rootProject.buildDir}/reports/lint-report.xml")
} }

View File

@ -2,15 +2,24 @@ package static_analysis.linters
import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.FileTree
import static_analysis.errors.DetektError import static_analysis.errors.DetektError
import static_analysis.errors.StaticAnalysisError import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension import static_analysis.plugins.StaticAnalysisExtension
import static_analysis.utils.getSources import static_analysis.utils.getSources
import static_analysis.utils.runCommand
import static_analysis.utils.typedChildren import static_analysis.utils.typedChildren
import static_analysis.utils.xmlParser import static_analysis.utils.xmlParser
import java.io.File
class DetektLinter : Linter { 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 val name: String = "Detekt"
override fun getErrors(project: Project): List<StaticAnalysisError> = xmlParser(project.getDetektReportFile()) override fun getErrors(project: Project): List<StaticAnalysisError> = 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<String> = listOf(":detekt") override fun getTaskNames(project: Project, buildType: String?): List<String> = listOf(":detekt")
private fun Project.getDetektReportFile() = file("${rootProject.buildDir}/reports/detekt.xml") private fun Project.getDetektReportFile() = file("${rootProject.buildDir}/reports/detekt.xml")

View File

@ -3,6 +3,7 @@ package static_analysis.plugins
import com.android.build.gradle.AppExtension import com.android.build.gradle.AppExtension
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.getByType
import static_analysis.linters.AndroidLinter
import static_analysis.linters.CpdLinter import static_analysis.linters.CpdLinter
import static_analysis.linters.DetektLinter import static_analysis.linters.DetektLinter
import static_analysis.linters.Linter import static_analysis.linters.Linter
@ -26,6 +27,23 @@ class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() {
buildVariant = applicationVariants.first { it.name.contains("Debug") }.name buildVariant = applicationVariants.first { it.name.contains("Debug") }.name
) )
} }
/**
* Task to run detekt checks.
*
* @param -Ponly-diffs <branch or commit> 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
)
}
} }
} }
} }
@ -34,8 +52,7 @@ class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() {
override fun createLinters(): List<Linter> = listOf( override fun createLinters(): List<Linter> = listOf(
DetektLinter(), DetektLinter(),
CpdLinter(), CpdLinter(),
//TODO temporary disable Android Linter to avoid FileNotFoundException when generating report AndroidLinter()
//AndroidLinter()
) )
} }

View File

@ -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
}
}

View File

@ -0,0 +1,13 @@
android {
lint {
abortOnError false
checkAllWarnings true
warningsAsErrors false
checkDependencies true
htmlReport false
textReport false
xmlReport true
xmlOutput file("${rootProject.buildDir}/reports/lint-report.xml")
lintConfig file("${rootProject.ext["buildScriptsDir"]}/static_analysis_configs/lint.xml")
}
}

View File

@ -16,6 +16,9 @@
<!--All activities should have locked orientation--> <!--All activities should have locked orientation-->
<issue id="LockedOrientationActivity" severity="ignore" /> <issue id="LockedOrientationActivity" severity="ignore" />
<!-- TODO: Update Timber version. See this issue: https://github.com/JakeWharton/timber/issues/408 -->
<issue id="WrongTimberUsageDetector" severity="ignore" />
<issue id="AllowAllHostnameVerifier" severity="error" /> <issue id="AllowAllHostnameVerifier" severity="error" />
<issue id="InvalidUsesTagAttribute" severity="error" /> <issue id="InvalidUsesTagAttribute" severity="error" />
<issue id="MissingIntentFilterForMediaSearch" severity="error" /> <issue id="MissingIntentFilterForMediaSearch" severity="error" />